Skip to content

fix: use committed account size for buffer allocation to handle >10KB growth#943

Open
vikions wants to merge 7 commits intomagicblock-labs:masterfrom
vikions:fix/issue-878-realloc-buffer-size
Open

fix: use committed account size for buffer allocation to handle >10KB growth#943
vikions wants to merge 7 commits intomagicblock-labs:masterfrom
vikions:fix/issue-878-realloc-buffer-size

Conversation

@vikions
Copy link

@vikions vikions commented Feb 10, 2026

Description

Fixes #878

This PR addresses the issue where CommitDiff tasks would fail when account growth exceeded Solana's 10KB reallocation limit per instruction.

Problem

Previously, the CommitDiff flow used the diff size (diff.len()) to calculate buffer allocation size. When an account grew from 5KB to 16KB (11KB growth), the diff might only be 100 bytes, causing the realloc logic to skip generating multiple realloc instructions. This led to runtime failures when the commit instruction tried to reallocate more than 10KB at once.

Solution

  • Added buffer_account_size: u64 field to PreparationTask to track the actual committed account size separately from the diff data
  • Updated CommitDiff case to use committed_account.account.data.len() for buffer allocation while keeping diff.len() for write instructions
  • Modified init_instruction() and realloc_instructions() to use the new buffer_account_size field

This ensures that when (committed_account_datalen - base_account_datalen) > 10KB, the existing realloc splitting logic in realloc_buffer.rs correctly generates multiple realloc instructions before the commit instruction.

Changes

  • magicblock-committor-service/src/tasks/mod.rs: Added buffer_account_size field to PreparationTask
  • magicblock-committor-service/src/tasks/buffer_task.rs: Updated CommitDiff and Commit cases to properly set buffer size

Testing

The existing realloc infrastructure (MAX_ACCOUNT_ALLOC_PER_INSTRUCTION_SIZE = 10,240) will now correctly handle large account growth scenarios.

Summary by CodeRabbit

  • Refactor

    • Improved buffer-size tracking for account initialization and reallocation to prevent sizing errors during commits.
  • Bug Fixes

    • Reject mutations on delegated accounts unless undelegating; reject updates with older remote_slot values (new runtime validations and error responses).
    • Preserve local delegation state and improve subscription/airdrop slot handling for delegated accounts.
    • Truncation now logs delete failures instead of aborting.
  • Tests

    • Added coverage for delegation, remote_slot, realloc, subscription, and related behaviors.

…Add buffer_account_size field to PreparationTask- Use committed account size instead of diff size for realloc calculations- Ensures realloc instructions are generated when account growth > 10KB- Fixes magicblock-labs#878
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 10, 2026

📝 Walkthrough

Walkthrough

The PR adds a new public field buffer_account_size: u64 to PreparationTask and populates it in both BufferTask::preparation_required branches (Commit and CommitDiff). Preparatory logic (init and realloc instruction construction) now uses buffer_account_size instead of deriving sizes from diff data. CommitDiff preparation computes and forwards the committed account size separately to enable correct realloc reasoning. Several tests were added to cover buffer sizing and realloc behavior.

Assessment against linked issues

Objective Addressed Explanation
Detect when data growth exceeds Solana's ~10KB realloc limit and issue dedicated realloc instruction(s) before commit [#878] The changes add tracking of existing buffer/account size and tests for splitting reallocs, but there is no explicit detection of the ~10KB threshold documented in the diffs or an explicit constant/logic referencing the 10KB Solana realloc limit.

Assessment against linked issues: Out-of-scope changes

Code Change (file_path) Explanation
Early delegated-account unsubscribe/guarding and airdrop remote_slot handling (magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs) Delegation/subscription and airdrop remote_slot logic are unrelated to realloc splitting/reallocation objectives.
New fetch_cloner tests around delegation, undelegation, eATA, and airdrop behavior (magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs) These tests exercise delegation/airdrop behavior, not realloc detection or pre-commit realloc generation.
Added new error variants for delegation and remote_slot ordering (programs/magicblock/src/errors.rs) New program error variants pertain to delegation and remote_slot validation, unrelated to realloc requirements.
Runtime validation for delegated/undelegating and remote_slot staleness (programs/magicblock/src/mutate_accounts/process_mutate_accounts.rs) Mutation-time delegation and remote_slot checks change account mutation behavior and do not implement realloc threshold detection or realloc splitting.

Suggested reviewers

  • thlorenz
  • Dodecahedr0x
  • bmuddha
✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@magicblock-committor-service/src/tasks/buffer_task.rs`:
- Around line 64-70: The code unnecessarily clones `data` when building
PreparationState::Required; instead compute let buffer_account_size = data.len()
as u64 before constructing the PreparationTask and then move `data` into the
committed_data field (committed_data: data) without cloning; update the
PreparationTask construction in the PreparationState::Required branch so it uses
the precomputed buffer_account_size and the moved `data` value, keeping
commit_id and pubkey the same.

@taco-paco taco-paco requested a review from snawaz February 17, 2026 07:42
@snawaz
Copy link
Contributor

snawaz commented Feb 17, 2026

@vikions... is this a complete work, or are there more changes coming? Please add some tests as well.

I converted this into draft mode as the PR does not seem complete to me.

@snawaz snawaz marked this pull request as draft February 17, 2026 08:10
@vikions
Copy link
Author

vikions commented Feb 17, 2026

@vikions... is this a complete work, or are there more changes coming? Please add some tests as well.

I converted this into draft mode as the PR does not seem complete to me.

I’ll add tests for the >10KB growth case!
or are there any other problems?

@vikions vikions marked this pull request as ready for review February 18, 2026 09:03
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
magicblock-ledger/src/ledger_truncator.rs (1)

283-298: ⚠️ Potential issue | 🟡 Minor

Silent error on delete_slots after set_lowest_cleanup_slot has already advanced.

set_lowest_cleanup_slot(to_slot) runs at line 283 before delete_slots. If delete_slots now fails silently (line 284–286), the cleanup pointer has permanently advanced past [from_slot, to_slot] without tombstones being inserted. Future truncation cycles will not revisit those slots, so unless the CompactionFilter independently cleans data up to lowest_cleanup_slot (without tombstones), those slots silently accumulate.

There is also a consistency gap with truncate_fat_ledger (line 141), which still propagates the delete_slots error via ?. Both paths call the same function with the same risk profile; the diverging error semantics is surprising.

Consider either:

  • Moving set_lowest_cleanup_slot to after a successful delete_slots, or
  • Aligning truncate_fat_ledger to the same best-effort pattern and documenting the assumption that the CompactionFilter covers cleanup without tombstones.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@magicblock-ledger/src/ledger_truncator.rs` around lines 283 - 298, The code
advances the cleanup pointer via ledger.set_lowest_cleanup_slot(to_slot) before
calling Self::delete_slots, so if delete_slots fails the pointer is moved and
slots may never get tombstones; fix by either moving the
ledger.set_lowest_cleanup_slot(to_slot) call to after a successful
Self::delete_slots(ledger, from_slot, to_slot) (so only advance when deletion
succeeded), or change this path to propagate the delete_slots error (make this
function return Err like truncate_fat_ledger does) so both paths have consistent
semantics; reference functions: set_lowest_cleanup_slot, delete_slots,
truncate_fat_ledger, and ensure any ledger.flush/compact_slot_range behavior
remains correct after reordering.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs`:
- Around line 2327-2344: The test currently polls until eata_pubkey appears and
then immediately inspects ata_pubkey, which can race with the projection
decision; after the tokio::time::timeout(...).await.expect(...) block add a
short stable-state guard (e.g., await tokio::task::yield_now() or an additional
small poll that confirms ATA projection work has run) before reading ata_pubkey
to ensure any pending async projection/handler tasks complete; update the test
function around the timeout block referencing eata_pubkey and ata_pubkey (the
timeout/poll loop) to include this yield/poll to avoid false positives.

In `@magicblock-committor-service/src/tasks/mod.rs`:
- Around line 458-500: Add a new test alongside
test_commit_diff_preparation_tracks_committed_account_size that constructs a
BufferTask::new_preparation_required with a CommitDiffTask where committed_len -
base_len > MAX_ACCOUNT_ALLOC_PER_INSTRUCTION_SIZE (10_240) so the code path that
splits reallocs is exercised; after extracting the
PreparationState::Required(preparation_task) assert that
preparation_task.buffer_account_size equals committed_len as u64 and also assert
preparation_task.realloc_instructions.len() > 1 to confirm realloc splitting
behavior (use the same CommitDiffTask/CommittedAccount/base Account construction
pattern from the existing test to locate the setup).
- Around line 503-523: The test
test_realloc_instructions_use_buffer_account_size_not_diff_size currently uses
buffer_account_size = 12_288 which only yields one realloc instruction and
doesn't exercise the multi-instruction split; update the test to use a larger
buffer_account_size (e.g., 20_480 or higher) so
PreparationTask.realloc_instructions(&authority) produces 2+ instructions and
assert on the expected count, or add an additional assertion comparing a smaller
buffer to the larger one (e.g., verify buffer_account_size = 12_288 yields 1
instruction while buffer_account_size = 20_480 yields 2) to validate the
multi-instruction behavior.

In `@programs/magicblock/src/mutate_accounts/process_mutate_accounts.rs`:
- Around line 818-903: Add a new unit test that mirrors the existing remote-slot
tests but verifies an advancing update is accepted: create an account with
remote_slot 100 (like in test_mod_remote_slot_allows_equal_update), build an
Instruction via InstructionUtils::modify_accounts_instruction with
AccountModification.remote_slot set to 101, run process_instruction (expect
Ok(())), and assert the returned modified account has lamports updated and
remote_slot == 101; refer to the existing test names
(test_mod_remote_slot_rejects_stale_update,
test_mod_remote_slot_allows_equal_update), process_instruction, and
AccountModification to match setup/collection logic.

---

Outside diff comments:
In `@magicblock-ledger/src/ledger_truncator.rs`:
- Around line 283-298: The code advances the cleanup pointer via
ledger.set_lowest_cleanup_slot(to_slot) before calling Self::delete_slots, so if
delete_slots fails the pointer is moved and slots may never get tombstones; fix
by either moving the ledger.set_lowest_cleanup_slot(to_slot) call to after a
successful Self::delete_slots(ledger, from_slot, to_slot) (so only advance when
deletion succeeded), or change this path to propagate the delete_slots error
(make this function return Err like truncate_fat_ledger does) so both paths have
consistent semantics; reference functions: set_lowest_cleanup_slot,
delete_slots, truncate_fat_ledger, and ensure any
ledger.flush/compact_slot_range behavior remains correct after reordering.

Comment on lines +2327 to +2344
tokio::time::timeout(TIMEOUT, async {
while accounts_bank.get_account(&eata_pubkey).is_none() {
tokio::time::sleep(POLL_INTERVAL).await;
}
})
.await
.expect("timed out waiting for delegated eATA subscription update");

let ata_after = accounts_bank
.get_account(&ata_pubkey)
.expect("ATA should still exist in bank");
assert!(ata_after.delegated(), "ATA must remain delegated");
assert_eq!(
ata_after.remote_slot(),
CURRENT_SLOT - 1,
"Delegated ATA should not be overwritten by chain update",
);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Minor: poll condition waits for eATA only — ATA-projection decision may not be settled yet.

The test polls until eata_pubkey appears in the bank and then immediately reads the ATA. If the subscription handler processes the eATA clone and the ATA-projection-skip in separate async steps (with a yield between them), there is a brief window where the eATA is visible in the bank but the handler hasn't yet decided whether to overwrite the ATA. This means the test could produce a false-positive if a bug causes the ATA overwrite to happen after this check.

Existing test test_delegated_eata_subscription_update_clones_raw_eata_and_projects_ata (line 2224) polls for has_eata && has_ata precisely to bridge this gap. Consider adding a small stable-state guard, or simply adding a tokio::task::yield_now().await after the timeout block to let any pending projection task run before asserting the ATA state.

💡 Proposed guard (minimal change)
 })
 .await
 .expect("timed out waiting for delegated eATA subscription update");
+// Yield to let any pending ATA-projection step complete before asserting.
+tokio::task::yield_now().await;

 let ata_after = accounts_bank
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs` around lines 2327 -
2344, The test currently polls until eata_pubkey appears and then immediately
inspects ata_pubkey, which can race with the projection decision; after the
tokio::time::timeout(...).await.expect(...) block add a short stable-state guard
(e.g., await tokio::task::yield_now() or an additional small poll that confirms
ATA projection work has run) before reading ata_pubkey to ensure any pending
async projection/handler tasks complete; update the test function around the
timeout block referencing eata_pubkey and ata_pubkey (the timeout/poll loop) to
include this yield/poll to avoid false positives.

Comment on lines +818 to +903
#[test]
fn test_mod_remote_slot_rejects_stale_update() {
init_logger!();

let mod_key = Pubkey::new_unique();
let mut account = AccountSharedData::new(100, 0, &mod_key);
account.set_remote_slot(100);
let mut account_data = {
let mut map = HashMap::new();
map.insert(mod_key, account);
map
};
ensure_started_validator(&mut account_data);

let ix = InstructionUtils::modify_accounts_instruction(
vec![AccountModification {
pubkey: mod_key,
lamports: Some(200),
remote_slot: Some(99),
..Default::default()
}],
None,
);
let transaction_accounts = ix
.accounts
.iter()
.flat_map(|acc| {
account_data
.remove(&acc.pubkey)
.map(|shared_data| (acc.pubkey, shared_data))
})
.collect();

let _accounts = process_instruction(
ix.data.as_slice(),
transaction_accounts,
ix.accounts,
Err(MagicBlockProgramError::IncomingRemoteSlotIsOlderThanCurrentRemoteSlot.into()),
);
}

#[test]
fn test_mod_remote_slot_allows_equal_update() {
init_logger!();

let mod_key = Pubkey::new_unique();
let mut account = AccountSharedData::new(100, 0, &mod_key);
account.set_remote_slot(100);
let mut account_data = {
let mut map = HashMap::new();
map.insert(mod_key, account);
map
};
ensure_started_validator(&mut account_data);

let ix = InstructionUtils::modify_accounts_instruction(
vec![AccountModification {
pubkey: mod_key,
lamports: Some(200),
remote_slot: Some(100),
..Default::default()
}],
None,
);
let transaction_accounts = ix
.accounts
.iter()
.flat_map(|acc| {
account_data
.remove(&acc.pubkey)
.map(|shared_data| (acc.pubkey, shared_data))
})
.collect();

let mut accounts = process_instruction(
ix.data.as_slice(),
transaction_accounts,
ix.accounts,
Ok(()),
);

let _account_authority = accounts.drain(0..1).next().unwrap();
let modified_account = accounts.drain(0..1).next().unwrap();
assert_eq!(modified_account.lamports(), 200);
assert_eq!(modified_account.remote_slot(), 100);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Remote slot edge-case tests are thorough.

Stale rejection (slot 99 < 100) and equal-slot acceptance (slot 100 == 100) are both tested. Consider adding a test for the straightforward advancing case (e.g., incoming slot 101 > current slot 100) for completeness — though this is implicitly covered by test_mod_remote_slot which sets a remote_slot on a fresh account (slot 0).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@programs/magicblock/src/mutate_accounts/process_mutate_accounts.rs` around
lines 818 - 903, Add a new unit test that mirrors the existing remote-slot tests
but verifies an advancing update is accepted: create an account with remote_slot
100 (like in test_mod_remote_slot_allows_equal_update), build an Instruction via
InstructionUtils::modify_accounts_instruction with
AccountModification.remote_slot set to 101, run process_instruction (expect
Ok(())), and assert the returned modified account has lamports updated and
remote_slot == 101; refer to the existing test names
(test_mod_remote_slot_rejects_stale_update,
test_mod_remote_slot_allows_equal_update), process_instruction, and
AccountModification to match setup/collection logic.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@magicblock-committor-service/src/tasks/mod.rs`:
- Around line 457-581: Remove the redundant comparison assertion in
test_realloc_instructions_use_buffer_account_size_not_diff_size: the final
assert!(large_realloc_instructions.len() > small_realloc_instructions.len()) is
tautological given the preceding assert_eq! checks. Edit the test (function
test_realloc_instructions_use_buffer_account_size_not_diff_size) and delete that
last assert referencing large_realloc_instructions and
small_realloc_instructions (leave the two assert_eq! checks and the helper
make_preparation_task unchanged).

Comment on lines +457 to +581
#[test]
fn test_commit_diff_preparation_tracks_committed_account_size() {
setup();
let base_len = 11_264usize;
let committed_len = 12_288usize;
let owner = Pubkey::new_unique();

let base_data = vec![1u8; base_len];
let mut committed_data = base_data.clone();
committed_data.extend(vec![2u8; committed_len - base_len]);

let buffer_task = BufferTask::new_preparation_required(
BufferTaskType::CommitDiff(CommitDiffTask {
commit_id: 900,
allow_undelegation: false,
committed_account: CommittedAccount {
pubkey: Pubkey::new_unique(),
account: Account {
lamports: 4000,
data: committed_data,
owner,
executable: false,
rent_epoch: 0,
},
remote_slot: Default::default(),
},
base_account: Account {
lamports: 2000,
data: base_data,
owner,
executable: false,
rent_epoch: 0,
},
}),
);

let PreparationState::Required(preparation_task) =
buffer_task.preparation_state()
else {
panic!("invalid preparation state on creation!");
};

assert_eq!(preparation_task.buffer_account_size, committed_len as u64);
}

#[test]
fn test_commit_diff_preparation_large_growth_splits_reallocs() {
setup();
let authority = Pubkey::new_unique();
let base_len = 8_192usize;
let committed_len = 22_528usize;
let owner = Pubkey::new_unique();

assert!(
committed_len - base_len
> magicblock_committor_program::consts::MAX_ACCOUNT_ALLOC_PER_INSTRUCTION_SIZE as usize
);

let base_data = vec![1u8; base_len];
let mut committed_data = base_data.clone();
committed_data.extend(vec![2u8; committed_len - base_len]);

let buffer_task = BufferTask::new_preparation_required(
BufferTaskType::CommitDiff(CommitDiffTask {
commit_id: 902,
allow_undelegation: false,
committed_account: CommittedAccount {
pubkey: Pubkey::new_unique(),
account: Account {
lamports: 5000,
data: committed_data,
owner,
executable: false,
rent_epoch: 0,
},
remote_slot: Default::default(),
},
base_account: Account {
lamports: 2500,
data: base_data,
owner,
executable: false,
rent_epoch: 0,
},
}),
);

let PreparationState::Required(preparation_task) =
buffer_task.preparation_state()
else {
panic!("invalid preparation state on creation!");
};

assert_eq!(preparation_task.buffer_account_size, committed_len as u64);
assert!(preparation_task.realloc_instructions(&authority).len() > 1);
}

#[test]
fn test_realloc_instructions_use_buffer_account_size_not_diff_size() {
setup();
let authority = Pubkey::new_unique();
let committed_data = vec![9u8; 64];
let make_preparation_task =
|buffer_account_size: u64| PreparationTask {
commit_id: 901,
pubkey: Pubkey::new_unique(),
chunks: Chunks::from_data_length(
committed_data.len(),
crate::consts::MAX_WRITE_CHUNK_SIZE,
),
committed_data: committed_data.clone(),
buffer_account_size,
};

let small_realloc_instructions =
make_preparation_task(12_288).realloc_instructions(&authority);
let large_realloc_instructions =
make_preparation_task(30_720).realloc_instructions(&authority);

assert_eq!(small_realloc_instructions.len(), 1);
assert_eq!(large_realloc_instructions.len(), 2);
assert!(
large_realloc_instructions.len() > small_realloc_instructions.len()
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Tests adequately cover the fix; one redundant assertion is a nitpick.

The three tests cover orthogonal properties: field-assignment, multi-realloc splitting with large growth, and the buffer-size-vs-diff-size distinction. The math holds for all assertions. Past review concerns about insufficient coverage are fully addressed.

The assertion at lines 578–580 is redundant given the assert_eq! calls immediately above it (if len == 1 and len == 2, 2 > 1 is tautological).

🧹 Optional: remove redundant assertion
         assert_eq!(small_realloc_instructions.len(), 1);
         assert_eq!(large_realloc_instructions.len(), 2);
-        assert!(
-            large_realloc_instructions.len() > small_realloc_instructions.len()
-        );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[test]
fn test_commit_diff_preparation_tracks_committed_account_size() {
setup();
let base_len = 11_264usize;
let committed_len = 12_288usize;
let owner = Pubkey::new_unique();
let base_data = vec![1u8; base_len];
let mut committed_data = base_data.clone();
committed_data.extend(vec![2u8; committed_len - base_len]);
let buffer_task = BufferTask::new_preparation_required(
BufferTaskType::CommitDiff(CommitDiffTask {
commit_id: 900,
allow_undelegation: false,
committed_account: CommittedAccount {
pubkey: Pubkey::new_unique(),
account: Account {
lamports: 4000,
data: committed_data,
owner,
executable: false,
rent_epoch: 0,
},
remote_slot: Default::default(),
},
base_account: Account {
lamports: 2000,
data: base_data,
owner,
executable: false,
rent_epoch: 0,
},
}),
);
let PreparationState::Required(preparation_task) =
buffer_task.preparation_state()
else {
panic!("invalid preparation state on creation!");
};
assert_eq!(preparation_task.buffer_account_size, committed_len as u64);
}
#[test]
fn test_commit_diff_preparation_large_growth_splits_reallocs() {
setup();
let authority = Pubkey::new_unique();
let base_len = 8_192usize;
let committed_len = 22_528usize;
let owner = Pubkey::new_unique();
assert!(
committed_len - base_len
> magicblock_committor_program::consts::MAX_ACCOUNT_ALLOC_PER_INSTRUCTION_SIZE as usize
);
let base_data = vec![1u8; base_len];
let mut committed_data = base_data.clone();
committed_data.extend(vec![2u8; committed_len - base_len]);
let buffer_task = BufferTask::new_preparation_required(
BufferTaskType::CommitDiff(CommitDiffTask {
commit_id: 902,
allow_undelegation: false,
committed_account: CommittedAccount {
pubkey: Pubkey::new_unique(),
account: Account {
lamports: 5000,
data: committed_data,
owner,
executable: false,
rent_epoch: 0,
},
remote_slot: Default::default(),
},
base_account: Account {
lamports: 2500,
data: base_data,
owner,
executable: false,
rent_epoch: 0,
},
}),
);
let PreparationState::Required(preparation_task) =
buffer_task.preparation_state()
else {
panic!("invalid preparation state on creation!");
};
assert_eq!(preparation_task.buffer_account_size, committed_len as u64);
assert!(preparation_task.realloc_instructions(&authority).len() > 1);
}
#[test]
fn test_realloc_instructions_use_buffer_account_size_not_diff_size() {
setup();
let authority = Pubkey::new_unique();
let committed_data = vec![9u8; 64];
let make_preparation_task =
|buffer_account_size: u64| PreparationTask {
commit_id: 901,
pubkey: Pubkey::new_unique(),
chunks: Chunks::from_data_length(
committed_data.len(),
crate::consts::MAX_WRITE_CHUNK_SIZE,
),
committed_data: committed_data.clone(),
buffer_account_size,
};
let small_realloc_instructions =
make_preparation_task(12_288).realloc_instructions(&authority);
let large_realloc_instructions =
make_preparation_task(30_720).realloc_instructions(&authority);
assert_eq!(small_realloc_instructions.len(), 1);
assert_eq!(large_realloc_instructions.len(), 2);
assert!(
large_realloc_instructions.len() > small_realloc_instructions.len()
);
}
#[test]
fn test_commit_diff_preparation_tracks_committed_account_size() {
setup();
let base_len = 11_264usize;
let committed_len = 12_288usize;
let owner = Pubkey::new_unique();
let base_data = vec![1u8; base_len];
let mut committed_data = base_data.clone();
committed_data.extend(vec![2u8; committed_len - base_len]);
let buffer_task = BufferTask::new_preparation_required(
BufferTaskType::CommitDiff(CommitDiffTask {
commit_id: 900,
allow_undelegation: false,
committed_account: CommittedAccount {
pubkey: Pubkey::new_unique(),
account: Account {
lamports: 4000,
data: committed_data,
owner,
executable: false,
rent_epoch: 0,
},
remote_slot: Default::default(),
},
base_account: Account {
lamports: 2000,
data: base_data,
owner,
executable: false,
rent_epoch: 0,
},
}),
);
let PreparationState::Required(preparation_task) =
buffer_task.preparation_state()
else {
panic!("invalid preparation state on creation!");
};
assert_eq!(preparation_task.buffer_account_size, committed_len as u64);
}
#[test]
fn test_commit_diff_preparation_large_growth_splits_reallocs() {
setup();
let authority = Pubkey::new_unique();
let base_len = 8_192usize;
let committed_len = 22_528usize;
let owner = Pubkey::new_unique();
assert!(
committed_len - base_len
> magicblock_committor_program::consts::MAX_ACCOUNT_ALLOC_PER_INSTRUCTION_SIZE as usize
);
let base_data = vec![1u8; base_len];
let mut committed_data = base_data.clone();
committed_data.extend(vec![2u8; committed_len - base_len]);
let buffer_task = BufferTask::new_preparation_required(
BufferTaskType::CommitDiff(CommitDiffTask {
commit_id: 902,
allow_undelegation: false,
committed_account: CommittedAccount {
pubkey: Pubkey::new_unique(),
account: Account {
lamports: 5000,
data: committed_data,
owner,
executable: false,
rent_epoch: 0,
},
remote_slot: Default::default(),
},
base_account: Account {
lamports: 2500,
data: base_data,
owner,
executable: false,
rent_epoch: 0,
},
}),
);
let PreparationState::Required(preparation_task) =
buffer_task.preparation_state()
else {
panic!("invalid preparation state on creation!");
};
assert_eq!(preparation_task.buffer_account_size, committed_len as u64);
assert!(preparation_task.realloc_instructions(&authority).len() > 1);
}
#[test]
fn test_realloc_instructions_use_buffer_account_size_not_diff_size() {
setup();
let authority = Pubkey::new_unique();
let committed_data = vec![9u8; 64];
let make_preparation_task =
|buffer_account_size: u64| PreparationTask {
commit_id: 901,
pubkey: Pubkey::new_unique(),
chunks: Chunks::from_data_length(
committed_data.len(),
crate::consts::MAX_WRITE_CHUNK_SIZE,
),
committed_data: committed_data.clone(),
buffer_account_size,
};
let small_realloc_instructions =
make_preparation_task(12_288).realloc_instructions(&authority);
let large_realloc_instructions =
make_preparation_task(30_720).realloc_instructions(&authority);
assert_eq!(small_realloc_instructions.len(), 1);
assert_eq!(large_realloc_instructions.len(), 2);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@magicblock-committor-service/src/tasks/mod.rs` around lines 457 - 581, Remove
the redundant comparison assertion in
test_realloc_instructions_use_buffer_account_size_not_diff_size: the final
assert!(large_realloc_instructions.len() > small_realloc_instructions.len()) is
tautological given the preceding assert_eq! checks. Edit the test (function
test_realloc_instructions_use_buffer_account_size_not_diff_size) and delete that
last assert referencing large_realloc_instructions and
small_realloc_instructions (leave the two assert_eq! checks and the helper
make_preparation_task unchanged).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add separate realloc instruction when data growth exceeds Solana limit (10KB)

4 participants

Comments