Repository navigation
feat(direct): Implement CommitFinalizeFromBuffer #263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
snawaz
merged 5 commits into
snawaz/commit-finalize
from
snawaz/commit-finalize-from-buffer
Nov 5, 2026
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
b4c726c
feat(direct): Implement CommitFinalizeFromBuffer
snawaz a078cc7
Keep state buffer hashing processor-local
snawaz 4317a1e
Spell out commit sequence numbers in prose
snawaz 6e4f6c7
Use u32 for commit sequences
snawaz b06ba53
Use u32 for commit-state validation
snawaz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| use wheels::variable_offset_layout; | ||
|
|
||
| use crate::compat::Pubkey; | ||
|
|
||
| #[derive(Clone, Debug, PartialEq, Eq)] | ||
| #[variable_offset_layout(buffer_offset = 1)] | ||
| pub struct CommitFinalizeFromBufferArgs { | ||
| /// Next commit sequence number for this delegated account and its StateBuffer. | ||
| pub commit_seq: u32, | ||
|
|
||
| /// Account lamports on the ER after this commit. | ||
| pub lamports: u64, | ||
|
|
||
| /// Account owner committed by the ER. | ||
| pub owner: Pubkey, | ||
|
|
||
| /// Whether the validator requests undelegation after this commit. | ||
| pub allow_undelegation: bool, | ||
|
|
||
| /// Stored as `CommitPayloadKind::value()`. | ||
| pub payload_kind: u8, | ||
| } | ||
|
|
||
| #[derive(Clone, Debug, PartialEq, Eq)] | ||
| #[variable_offset_layout(buffer_offset = 1)] | ||
| pub struct PrepareCommitFinalizeArgs { | ||
| /// Next commit sequence number whose finalization needs this account size. | ||
| pub commit_seq: u32, | ||
|
|
||
| /// Final delegated-account data length. | ||
| pub target_data_len: u32, | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 106 additions & 0 deletions
106
interface/src/instruction_builder/commit_finalize_from_buffer.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| use solana_program::{ | ||
| instruction::{AccountMeta, Instruction}, | ||
| pubkey::Pubkey, | ||
| }; | ||
| use solana_sdk_ids::system_program; | ||
| use wheels::{layout::Encodable, DataLayoutError}; | ||
|
|
||
| use crate::{ | ||
| compat::{Compatize, Modernize}, | ||
| pda::{delegation_state_pda, state_buffer_pda}, | ||
| CommitFinalizeFromBufferArgs, DlpV2Instruction, PrepareCommitFinalizeArgs, | ||
| }; | ||
|
|
||
| /// Builds a direct-cluster commit that consumes a finalized StateBuffer. | ||
| pub fn commit_finalize_from_buffer( | ||
| current_validator: Pubkey, | ||
| delegation_cluster: Pubkey, | ||
| delegated_account: Pubkey, | ||
| args: CommitFinalizeFromBufferArgs, | ||
| ) -> Result<Instruction, DataLayoutError> { | ||
| Ok(Instruction { | ||
| program_id: crate::id().modernize(), | ||
| accounts: vec![ | ||
| AccountMeta::new(current_validator, true), | ||
| AccountMeta::new(delegated_account, false), | ||
| AccountMeta::new( | ||
| delegation_state_pda(&delegated_account.compatize()) | ||
| .modernize(), | ||
| false, | ||
| ), | ||
| AccountMeta::new_readonly( | ||
| state_buffer_pda( | ||
| &delegated_account.compatize(), | ||
| args.commit_seq, | ||
| ¤t_validator.compatize(), | ||
| ) | ||
| .modernize(), | ||
| false, | ||
| ), | ||
| AccountMeta::new_readonly(delegation_cluster, false), | ||
| AccountMeta::new_readonly(system_program::id(), false), | ||
| ], | ||
| data: [ | ||
| DlpV2Instruction::CommitFinalizeFromBuffer.to_vec(), | ||
| args.encode()?, | ||
| ] | ||
| .concat(), | ||
| }) | ||
| } | ||
|
|
||
| /// Builds one growth step to run before finalizing a large account update. | ||
| pub fn prepare_commit_finalize( | ||
| current_validator: Pubkey, | ||
| delegation_cluster: Pubkey, | ||
| delegated_account: Pubkey, | ||
| args: PrepareCommitFinalizeArgs, | ||
| ) -> Result<Instruction, DataLayoutError> { | ||
| Ok(Instruction { | ||
| program_id: crate::id().modernize(), | ||
| accounts: vec![ | ||
| AccountMeta::new(current_validator, true), | ||
| AccountMeta::new(delegated_account, false), | ||
| AccountMeta::new_readonly( | ||
| delegation_state_pda(&delegated_account.compatize()) | ||
| .modernize(), | ||
| false, | ||
| ), | ||
| AccountMeta::new_readonly(delegation_cluster, false), | ||
| AccountMeta::new_readonly(system_program::id(), false), | ||
| ], | ||
| data: [ | ||
| DlpV2Instruction::PrepareCommitFinalize.to_vec(), | ||
| args.encode()?, | ||
| ] | ||
| .concat(), | ||
| }) | ||
| } | ||
|
|
||
| /// Builds every growth step needed before finalizing `target_data_len`. | ||
| pub fn prepare_commit_finalize_instructions( | ||
| current_validator: Pubkey, | ||
| delegation_cluster: Pubkey, | ||
| delegated_account: Pubkey, | ||
| commit_seq: u32, | ||
| current_data_len: usize, | ||
| target_data_len: u32, | ||
| ) -> Result<Vec<Instruction>, DataLayoutError> { | ||
| let growth = (target_data_len as usize).saturating_sub(current_data_len); | ||
| let step_count = | ||
| growth.div_ceil(crate::StateBuffer::MAX_ACCOUNT_DATA_GROWTH_PER_WRITE); | ||
| let mut instructions = Vec::with_capacity(step_count); | ||
|
|
||
| for _ in 0..step_count { | ||
| instructions.push(prepare_commit_finalize( | ||
| current_validator, | ||
| delegation_cluster, | ||
| delegated_account, | ||
| PrepareCommitFinalizeArgs { | ||
| commit_seq, | ||
| target_data_len, | ||
| }, | ||
| )?); | ||
| } | ||
|
|
||
| Ok(instructions) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
158 changes: 158 additions & 0 deletions
158
program/src/processor/direct/commit_finalize_from_buffer.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| use delegation_program_interface as dpi; | ||
| use dpi::{ | ||
| error::DlpError, pda::STATE_BUFFER_SEED, CommitFinalizeFromBufferArgs, | ||
| CommitPayloadKind, StateBuffer, | ||
| }; | ||
| use pinocchio::{ | ||
| address::address_eq, error::ProgramError, AccountView, ProgramResult, | ||
| }; | ||
| use wheels::{ | ||
| layout::Decodable, require_eq, require_eq_keys, require_n_accounts, | ||
| }; | ||
|
|
||
| use crate::processor::internal::{ | ||
| apply_commit_finalize, CommitFinalizeInput, CommitStateUpdate, | ||
| }; | ||
|
|
||
| /// Commit and apply one finalized StateBuffer for a Direct cluster. | ||
| /// | ||
| /// Accounts: | ||
| /// 0: `[signer, writable]` current validator and lamport settlement account | ||
| /// 1: `[writable]` delegated account | ||
| /// 2: `[writable]` DelegationState PDA | ||
| /// 3: `[]` finalized StateBuffer PDA | ||
| /// 4: `[]` DelegationCluster PDA | ||
| /// 5: `[]` system program, required by system CPI | ||
| #[inline(never)] | ||
| pub fn process_commit_finalize_from_buffer( | ||
| accounts: &[AccountView], | ||
| data: &[u8], | ||
| ) -> ProgramResult { | ||
| let [ | ||
| current_validator, // force multi-line | ||
| delegated_account, | ||
| delegation_state, | ||
| state_buffer, | ||
| delegation_cluster, | ||
| _system_program, | ||
| ] = require_n_accounts!(accounts, 6); | ||
|
|
||
| let args = CommitFinalizeFromBufferArgs::decode(data)?; | ||
| let state_buffer_data = state_buffer.try_borrow()?; | ||
| let buffer = StateBuffer::decode(state_buffer_data.as_ref())?; | ||
| validate_state_buffer( | ||
| current_validator, | ||
| delegated_account, | ||
| state_buffer, | ||
| &buffer, | ||
| args.commit_seq(), | ||
| )?; | ||
|
|
||
| let state_update = match args.payload_kind() { | ||
| value if value == CommitPayloadKind::FullData.value() => { | ||
| CommitStateUpdate::FullData(buffer.payload().as_slice()) | ||
| } | ||
| value if value == CommitPayloadKind::Diff.value() => { | ||
| CommitStateUpdate::Diff(dpi::diff::DiffSet::try_new( | ||
| buffer.payload().as_slice(), | ||
| )?) | ||
| } | ||
| _ => return Err(ProgramError::InvalidInstructionData), | ||
| }; | ||
|
|
||
| apply_commit_finalize( | ||
| current_validator, | ||
| delegated_account, | ||
| delegation_state, | ||
| delegation_cluster, | ||
| CommitFinalizeInput { | ||
| commit_seq: args.commit_seq(), | ||
| lamports: args.lamports(), | ||
| owner: args.owner(), | ||
| allow_undelegation: args.allow_undelegation(), | ||
| state_update, | ||
| }, | ||
| ) | ||
| } | ||
|
|
||
| fn validate_state_buffer( | ||
| current_validator: &AccountView, | ||
| delegated_account: &AccountView, | ||
| state_buffer: &AccountView, | ||
| buffer: &dpi::StateBufferView<'_>, | ||
| commit_seq: u32, | ||
| ) -> ProgramResult { | ||
| let commit_seq_bytes = commit_seq.to_le_bytes(); | ||
| let bump = [buffer.bump()]; | ||
| dpi::require_initialized_pda!( | ||
| state_buffer, | ||
| &[ | ||
| STATE_BUFFER_SEED, | ||
| delegated_account.address().as_ref(), | ||
| &commit_seq_bytes, | ||
| current_validator.address().as_ref(), | ||
| &bump, | ||
| ], | ||
| &crate::fast::ID, | ||
| false | ||
| ); | ||
| require_eq!( | ||
| &buffer.discriminator(), | ||
| &StateBuffer::DISCRIMINATOR, | ||
| ProgramError::InvalidAccountData | ||
| ); | ||
| require_eq_keys!( | ||
| buffer.authority(), | ||
| current_validator.address(), | ||
| DlpError::InvalidAuthority | ||
| ); | ||
| require_eq_keys!( | ||
| buffer.account_pubkey(), | ||
| delegated_account.address(), | ||
| DlpError::InvalidDelegatedAccount | ||
| ); | ||
| require_eq!( | ||
| buffer.commit_seq(), | ||
| commit_seq, | ||
| ProgramError::InvalidInstructionData | ||
| ); | ||
| require_eq!( | ||
| buffer.finalized(), | ||
| true, | ||
| ProgramError::InvalidInstructionData | ||
| ); | ||
| require_eq!( | ||
| &buffer.payload_alignment_padding(), | ||
| &[0; 2], | ||
| ProgramError::InvalidAccountData | ||
| ); | ||
| require_eq!( | ||
| buffer.payload().len(), | ||
| buffer.total_len() as usize, | ||
| ProgramError::InvalidInstructionData | ||
| ); | ||
| require_eq!( | ||
| buffer.payload().capacity(), | ||
| buffer.total_len() as usize, | ||
| ProgramError::InvalidInstructionData | ||
| ); | ||
| require_eq!( | ||
| state_buffer.data_len(), | ||
| StateBuffer::data_len_from_payload_capacity( | ||
| buffer.payload().capacity() | ||
| )?, | ||
| ProgramError::InvalidAccountData | ||
| ); | ||
| require_eq!( | ||
| buffer.data_hash(), | ||
| &account_data_hash(buffer.payload().as_slice()), | ||
| ProgramError::InvalidInstructionData | ||
| ); | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| fn account_data_hash(data: &[u8]) -> [u8; 32] { | ||
| solana_sha256_hasher::hashv(&[b"magicblock.account_data.v1", data]) | ||
| .to_bytes() | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,9 @@ | ||
| //! Processors for clusters that finalize without fraud proofs. | ||
|
|
||
| mod commit_finalize; | ||
| mod commit_finalize_from_buffer; | ||
| mod prepare_commit_finalize; | ||
|
|
||
| pub use commit_finalize::*; | ||
| pub use commit_finalize_from_buffer::*; | ||
| pub use prepare_commit_finalize::*; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the same growth constant as the processor.
The builder computes
step_countwithStateBuffer::MAX_ACCOUNT_DATA_GROWTH_PER_WRITE.process_prepare_commit_finalizegrows the account bypinocchio::account::MAX_PERMITTED_DATA_INCREASE. Both values are currently 10,240, so the step count is correct today. The two constants have different meanings: one is a StateBuffer limit, and the other is the runtime realloc limit. IfMAX_ACCOUNT_DATA_GROWTH_PER_WRITEchanges later, the builder emits too few steps, and the final finalization fails. Add a dedicated growth-step constant in the interface. Use it in both the builder and the processor, or add astatic_assertionscheck that the two values are equal.♻️ Proposed change