Skip to content

Commit ffefbfd

Browse files
committed
feat: Add CommitDiff Instruction for Efficient Account Updates
1 parent fec44c5 commit ffefbfd

12 files changed

Lines changed: 395 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: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::mem::size_of;
2+
13
use borsh::{BorshDeserialize, BorshSerialize};
24

35
#[derive(Default, Debug, BorshSerialize, BorshDeserialize)]
@@ -23,3 +25,31 @@ pub struct CommitStateFromBufferArgs {
2325
/// Whether the account can be undelegated after the commit completes
2426
pub allow_undelegation: bool,
2527
}
28+
29+
#[derive(Default, Debug, BorshSerialize)]
30+
pub struct CommitDiffArgs {
31+
/// The account diff
32+
/// SAFETY: this must be the FIRST field in the struct
33+
pub diff: Vec<u8>,
34+
/// "Nonce" of an account. Updates are submitted historically and nonce incremented by 1
35+
/// Deprecated: The ephemeral slot at which the account data is committed
36+
pub nonce: u64,
37+
/// The lamports that the account holds in the ephemeral validator
38+
pub lamports: u64,
39+
/// Whether the account can be undelegated after the commit completes
40+
pub allow_undelegation: bool,
41+
}
42+
43+
#[derive(Default, Debug, BorshDeserialize)]
44+
pub struct CommitDiffArgsWithoutDiff {
45+
/// "Nonce" of an account. Updates are submitted historically and nonce incremented by 1
46+
/// Deprecated: The ephemeral slot at which the account data is committed
47+
pub nonce: u64,
48+
/// The lamports that the account holds in the ephemeral validator
49+
pub lamports: u64,
50+
/// Whether the account can be undelegated after the commit completes
51+
pub allow_undelegation: bool,
52+
}
53+
54+
pub const SIZE_COMMIT_DIFF_ARGS_WITHOUT_DIFF: usize =
55+
size_of::<u64>() + size_of::<u64>() + size_of::<bool>();

‎src/diff/types.rs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ impl<'a> DiffSet<'a> {
5454
Ok(this)
5555
}
5656

57+
pub fn try_new_from_borsh_vec(vec_buffer: &'a [u8]) -> Result<Self, ProgramError> {
58+
if vec_buffer.len() < 4 {
59+
return Err(ProgramError::InvalidInstructionData);
60+
}
61+
Self::try_new(&vec_buffer[4..])
62+
}
63+
5764
pub fn raw_diff(&self) -> &'a [u8] {
5865
unsafe { slice::from_raw_parts(self.buf, self.len) }
5966
}

‎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: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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::{CommitDiffArgsWithoutDiff, SIZE_COMMIT_DIFF_ARGS_WITHOUT_DIFF};
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+
if data.len() < SIZE_COMMIT_DIFF_ARGS_WITHOUT_DIFF {
54+
return Err(ProgramError::InvalidInstructionData);
55+
}
56+
57+
let (diff, data) = data.split_at(data.len() - SIZE_COMMIT_DIFF_ARGS_WITHOUT_DIFF);
58+
59+
let args =
60+
CommitDiffArgsWithoutDiff::try_from_slice(data).map_err(|_| ProgramError::BorshIoError)?;
61+
62+
let diffset = DiffSet::try_new_from_borsh_vec(diff)?;
63+
64+
if diffset.num_offset_pairs() == 0 {
65+
log!("WARN: noop; empty diff sent");
66+
}
67+
68+
let commit_record_lamports = args.lamports;
69+
let commit_record_nonce = args.nonce;
70+
let allow_undelegation = args.allow_undelegation;
71+
72+
// TODO (snawaz): the following approach to apply diff works, but it's not efficient.
73+
// It is also problematic for larger account as it allocates memory on the heap.
74+
// It will be fixed in a separate PR.
75+
let original = unsafe { delegated_account.borrow_data_unchecked() };
76+
let changed = apply_diff_copy(&original, &diffset);
77+
78+
let commit_args = CommitStateInternalArgs {
79+
commit_state_bytes: &changed,
80+
commit_record_lamports,
81+
commit_record_nonce,
82+
allow_undelegation,
83+
validator,
84+
delegated_account,
85+
commit_state_account,
86+
commit_record_account,
87+
delegation_record_account,
88+
delegation_metadata_account,
89+
validator_fees_vault,
90+
program_config_account,
91+
};
92+
93+
process_commit_state_internal(commit_args)
94+
}

‎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)