Skip to content

Commit 1db562c

Browse files
committed
feat: Add CommitDiff Instruction for Efficient Account Updates
1 parent 8965cf2 commit 1db562c

11 files changed

Lines changed: 364 additions & 21 deletions

File tree

‎Cargo.lock‎

Lines changed: 32 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ pinocchio = "0.9.2"
4646
pinocchio-pubkey = "0.3.0"
4747
pinocchio-system = "0.3.0"
4848
rkyv = "0.7.45"
49+
strum = { version = "0.27.2", features = ["derive"] }
4950

5051
[dev-dependencies]
5152
base64 = "0.22.1"

‎src/args/commit_state.rs‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,16 @@ pub struct CommitStateFromBufferArgs {
2323
/// Whether the account can be undelegated after the commit completes
2424
pub allow_undelegation: bool,
2525
}
26+
27+
#[derive(Default, Debug, BorshSerialize, BorshDeserialize)]
28+
pub struct CommitDiffArgs {
29+
/// "Nonce" of an account. Updates are submitted historically and nonce incremented by 1
30+
/// Deprecated: The ephemeral slot at which the account data is committed
31+
pub nonce: u64,
32+
/// The lamports that the account holds in the ephemeral validator
33+
pub lamports: u64,
34+
/// Whether the account can be undelegated after the commit completes
35+
pub allow_undelegation: bool,
36+
/// The account data
37+
pub diff: Vec<u8>,
38+
}

‎src/discriminator.rs‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use num_enum::TryFromPrimitive;
2+
use strum::IntoStaticStr;
23

34
#[repr(u8)]
4-
#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)]
5+
#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive, IntoStaticStr)]
56
#[rustfmt::skip]
67
pub enum DlpDiscriminator {
78
/// See [crate::processor::process_delegate] for docs.
@@ -34,11 +35,17 @@ pub enum DlpDiscriminator {
3435
CloseValidatorFeesVault = 14,
3536
/// See [crate::processor::process_call_handler] for docs.
3637
CallHandler = 15,
38+
/// See [crate::processor::process_commit_diff] for docs.
39+
CommitDiff = 16,
3740
}
3841

3942
impl DlpDiscriminator {
4043
pub fn to_vec(self) -> Vec<u8> {
4144
let num = self as u64;
4245
num.to_le_bytes().to_vec()
4346
}
47+
48+
pub fn name(&self) -> &'static str {
49+
self.into()
50+
}
4451
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use borsh::to_vec;
2+
use solana_program::instruction::Instruction;
3+
use solana_program::system_program;
4+
use solana_program::{instruction::AccountMeta, pubkey::Pubkey};
5+
6+
use crate::args::CommitDiffArgs;
7+
use crate::discriminator::DlpDiscriminator;
8+
use crate::pda::{
9+
commit_record_pda_from_delegated_account, commit_state_pda_from_delegated_account,
10+
delegation_metadata_pda_from_delegated_account, delegation_record_pda_from_delegated_account,
11+
program_config_from_program_id, validator_fees_vault_pda_from_validator,
12+
};
13+
14+
/// Builds a commit state instruction.
15+
/// See [crate::processor::process_commit_diff] for docs.
16+
pub fn commit_diff(
17+
validator: Pubkey,
18+
delegated_account: Pubkey,
19+
delegated_account_owner: Pubkey,
20+
commit_args: CommitDiffArgs,
21+
) -> Instruction {
22+
let commit_args = to_vec(&commit_args).unwrap();
23+
let delegation_record_pda = delegation_record_pda_from_delegated_account(&delegated_account);
24+
let commit_state_pda = commit_state_pda_from_delegated_account(&delegated_account);
25+
let commit_record_pda = commit_record_pda_from_delegated_account(&delegated_account);
26+
let validator_fees_vault_pda = validator_fees_vault_pda_from_validator(&validator);
27+
let delegation_metadata_pda =
28+
delegation_metadata_pda_from_delegated_account(&delegated_account);
29+
let program_config_pda = program_config_from_program_id(&delegated_account_owner);
30+
Instruction {
31+
program_id: crate::id(),
32+
accounts: vec![
33+
AccountMeta::new_readonly(validator, true),
34+
AccountMeta::new_readonly(delegated_account, false),
35+
AccountMeta::new(commit_state_pda, false),
36+
AccountMeta::new(commit_record_pda, false),
37+
AccountMeta::new_readonly(delegation_record_pda, false),
38+
AccountMeta::new(delegation_metadata_pda, false),
39+
AccountMeta::new_readonly(validator_fees_vault_pda, false),
40+
AccountMeta::new_readonly(program_config_pda, false),
41+
AccountMeta::new_readonly(system_program::id(), false),
42+
],
43+
data: [DlpDiscriminator::CommitDiff.to_vec(), commit_args].concat(),
44+
}
45+
}

‎src/instruction_builder/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mod call_handler;
22
mod close_ephemeral_balance;
33
mod close_validator_fees_vault;
4+
mod commit_diff;
45
mod commit_state;
56
mod commit_state_from_buffer;
67
mod delegate;
@@ -17,6 +18,7 @@ mod whitelist_validator_for_program;
1718
pub use call_handler::*;
1819
pub use close_ephemeral_balance::*;
1920
pub use close_validator_fees_vault::*;
21+
pub use commit_diff::*;
2022
pub use commit_state::*;
2123
pub use commit_state_from_buffer::*;
2224
pub use delegate::*;

‎src/lib.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ pub fn fast_process_instruction(
7676
DlpDiscriminator::CommitStateFromBuffer => Some(
7777
processor::fast::process_commit_state_from_buffer(program_id, accounts, data),
7878
),
79+
DlpDiscriminator::CommitDiff => Some(processor::fast::process_commit_diff(
80+
program_id, accounts, data,
81+
)),
7982
DlpDiscriminator::Finalize => Some(processor::fast::process_finalize(
8083
program_id, accounts, data,
8184
)),

‎src/processor/fast/commit_diff.rs‎

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use borsh::BorshDeserialize;
2+
use pinocchio::{
3+
account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
4+
};
5+
use pinocchio_log::log;
6+
7+
use crate::args::CommitDiffArgs;
8+
use crate::processor::fast::{process_commit_state_internal, CommitStateInternalArgs};
9+
use crate::{apply_diff_copy, DiffSet};
10+
11+
/// Commit diff to a delegated PDA
12+
///
13+
/// Accounts:
14+
///
15+
/// 0: `[signer]` the validator requesting the commit
16+
/// 1: `[]` the delegated account
17+
/// 2: `[writable]` the PDA storing the new state
18+
/// 3: `[writable]` the PDA storing the commit record
19+
/// 4: `[]` the delegation record
20+
/// 5: `[writable]` the delegation metadata
21+
/// 6: `[]` the validator fees vault
22+
/// 7: `[]` the program config account
23+
///
24+
/// Requirements:
25+
///
26+
/// - The following accounts must be initialized:
27+
/// - delegation record
28+
/// - delegation metadata
29+
/// - validator fees vault
30+
/// - program config
31+
/// - The following accounts must be uninitialized:
32+
/// - commit state
33+
/// - commit record
34+
/// - delegated account holds at least the lamports indicated in the delegation record
35+
/// - account was not committed at a later slot
36+
///
37+
/// Steps:
38+
/// 1. Check that the pda is delegated
39+
/// 2. Init a new PDA to store the new state
40+
/// 3. Copy the new state to the new PDA
41+
/// 4. Init a new PDA to store the record of the new state commitment
42+
pub fn process_commit_diff(
43+
_program_id: &Pubkey,
44+
accounts: &[AccountInfo],
45+
data: &[u8],
46+
) -> ProgramResult {
47+
let [validator, delegated_account, commit_state_account, commit_record_account, delegation_record_account, delegation_metadata_account, validator_fees_vault, program_config_account, _system_program] =
48+
accounts
49+
else {
50+
return Err(ProgramError::NotEnoughAccountKeys);
51+
};
52+
53+
let args = CommitDiffArgs::try_from_slice(data).map_err(|_| ProgramError::BorshIoError)?;
54+
55+
let diffset = DiffSet::try_new(args.diff.as_slice())?;
56+
57+
if diffset.num_offset_pairs() == 0 {
58+
log!("WARN: noop; empty diff sent");
59+
}
60+
61+
let commit_record_lamports = args.lamports;
62+
let commit_record_nonce = args.nonce;
63+
let allow_undelegation = args.allow_undelegation;
64+
65+
// TODO (snawaz): the following approach to apply diff works, but it's not efficient.
66+
// It is also problematic for larger account as it allocates memory on the heap.
67+
// It will be fixed in a separate PR.
68+
let original = unsafe { delegated_account.borrow_data_unchecked() };
69+
let changed = apply_diff_copy(&original, &diffset);
70+
71+
let commit_args = CommitStateInternalArgs {
72+
commit_state_bytes: &changed,
73+
commit_record_lamports,
74+
commit_record_nonce,
75+
allow_undelegation,
76+
validator,
77+
delegated_account,
78+
commit_state_account,
79+
commit_record_account,
80+
delegation_record_account,
81+
delegation_metadata_account,
82+
validator_fees_vault,
83+
program_config_account,
84+
};
85+
86+
process_commit_state_internal(commit_args)
87+
}

‎src/processor/fast/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
mod commit_diff;
12
mod commit_state;
23
mod commit_state_from_buffer;
34
mod delegate;
45
mod finalize;
56
mod undelegate;
67
mod utils;
78

9+
pub use commit_diff::*;
810
pub use commit_state::*;
911
pub use commit_state_from_buffer::*;
1012
pub use delegate::*;

0 commit comments

Comments
 (0)