Skip to content

Commit fa1cc24

Browse files
committed
feat: add CommitFinalize
1 parent 1874b4f commit fa1cc24

5 files changed

Lines changed: 230 additions & 6 deletions

File tree

src/discriminator.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ pub enum DlpDiscriminator {
3939
CommitDiff = 16,
4040
/// See [crate::processor::process_commit_diff_from_buffer] for docs.
4141
CommitDiffFromBuffer = 17,
42+
/// See [crate::processor::process_commit_finalize] for docs.
43+
CommitFinalize = 18,
4244
}
4345

4446
impl DlpDiscriminator {

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ pub fn fast_process_instruction(
113113
DlpDiscriminator::CommitDiffFromBuffer => Some(
114114
processor::fast::process_commit_diff_from_buffer(program_id, accounts, data),
115115
),
116+
DlpDiscriminator::CommitFinalize => Some(processor::fast::process_commit_finalize(
117+
program_id, accounts, data,
118+
)),
116119
DlpDiscriminator::Finalize => Some(processor::fast::process_finalize(
117120
program_id, accounts, data,
118121
)),
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
use borsh::BorshDeserialize;
2+
use pinocchio::pubkey::{self, pubkey_eq};
3+
use pinocchio::{
4+
account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
5+
};
6+
use pinocchio_log::log;
7+
8+
use crate::apply_diff_in_place;
9+
use crate::args::CommitStateArgs;
10+
use crate::error::DlpError;
11+
use crate::processor::fast::utils::requires::{
12+
require_initialized_delegation_metadata, require_initialized_delegation_record,
13+
require_initialized_validator_fees_vault, require_owned_pda, require_program_config,
14+
require_signer,
15+
};
16+
use crate::processor::fast::NewState;
17+
use crate::state::{DelegationMetadata, DelegationRecord, ProgramConfig};
18+
19+
use super::to_pinocchio_program_error;
20+
21+
/// Commit a new state of a delegated PDA
22+
///
23+
/// Accounts:
24+
///
25+
/// 0: `[signer]` the validator requesting the commit
26+
/// 1: `[]` the delegated account
27+
/// 2: `[writable]` the PDA storing the new state
28+
/// 3: `[writable]` the PDA storing the commit record
29+
/// 4: `[]` the delegation record
30+
/// 5: `[writable]` the delegation metadata
31+
/// 6: `[]` the validator fees vault
32+
/// 7: `[]` the program config account
33+
///
34+
/// Requirements:
35+
///
36+
/// - delegation record is initialized
37+
/// - delegation metadata is initialized
38+
/// - validator fees vault is initialized
39+
/// - program config is initialized
40+
/// - commit state is uninitialized
41+
/// - commit record is uninitialized
42+
/// - delegated account holds at least the lamports indicated in the delegation record
43+
/// - account was not committed at a later slot
44+
///
45+
/// Steps:
46+
/// 1. Check that the pda is delegated
47+
/// 2. Init a new PDA to store the new state
48+
/// 3. Copy the new state to the new PDA
49+
/// 4. Init a new PDA to store the record of the new state commitment
50+
pub fn process_commit_finalize(
51+
_program_id: &Pubkey,
52+
accounts: &[AccountInfo],
53+
data: &[u8],
54+
) -> ProgramResult {
55+
let args = CommitStateArgs::try_from_slice(data).map_err(|_| ProgramError::BorshIoError)?;
56+
57+
// let commit_record_lamports = args.lamports;
58+
let commit_record_nonce = args.nonce;
59+
let allow_undelegation = args.allow_undelegation;
60+
61+
let [validator, delegated_account, delegation_record_account, delegation_metadata_account, validator_fees_vault, program_config_account, _system_program] =
62+
accounts
63+
else {
64+
return Err(ProgramError::NotEnoughAccountKeys);
65+
};
66+
67+
let commit_args = CommitFinalizeInternalArgs {
68+
commit_state_bytes: NewState::FullBytes(&args.data),
69+
// commit_record_lamports,
70+
commit_record_nonce,
71+
allow_undelegation,
72+
validator,
73+
delegated_account,
74+
delegation_record_account,
75+
delegation_metadata_account,
76+
validator_fees_vault,
77+
program_config_account,
78+
};
79+
80+
process_commit_finalize_internal(commit_args)
81+
}
82+
83+
/// Arguments for the commit state internal function
84+
struct CommitFinalizeInternalArgs<'a> {
85+
pub(crate) commit_state_bytes: NewState<'a>,
86+
// pub(crate) commit_record_lamports: u64,
87+
pub(crate) commit_record_nonce: u64,
88+
pub(crate) allow_undelegation: bool,
89+
pub(crate) validator: &'a AccountInfo,
90+
pub(crate) delegated_account: &'a AccountInfo,
91+
pub(crate) delegation_record_account: &'a AccountInfo,
92+
pub(crate) delegation_metadata_account: &'a AccountInfo,
93+
pub(crate) validator_fees_vault: &'a AccountInfo,
94+
pub(crate) program_config_account: &'a AccountInfo,
95+
}
96+
97+
/// Commit a new state of a delegated Pda
98+
fn process_commit_finalize_internal(args: CommitFinalizeInternalArgs) -> Result<(), ProgramError> {
99+
// Check that the origin account is delegated
100+
require_owned_pda(
101+
args.delegated_account,
102+
&crate::fast::ID,
103+
"delegated account",
104+
)?;
105+
require_signer(args.validator, "validator account")?;
106+
require_initialized_delegation_record(
107+
args.delegated_account,
108+
args.delegation_record_account,
109+
false,
110+
)?;
111+
require_initialized_delegation_metadata(
112+
args.delegated_account,
113+
args.delegation_metadata_account,
114+
true,
115+
)?;
116+
require_initialized_validator_fees_vault(args.validator, args.validator_fees_vault, false)?;
117+
118+
// Read delegation metadata
119+
let mut delegation_metadata_data = args.delegation_metadata_account.try_borrow_mut_data()?;
120+
let mut delegation_metadata =
121+
DelegationMetadata::try_from_bytes_with_discriminator(&delegation_metadata_data)
122+
.map_err(to_pinocchio_program_error)?;
123+
124+
// To preserve correct history of account updates we require sequential commits
125+
if args.commit_record_nonce != delegation_metadata.last_update_nonce + 1 {
126+
log!(
127+
"Nonce {} is incorrect, previous nonce is {}. Rejecting commit",
128+
args.commit_record_nonce,
129+
delegation_metadata.last_update_nonce
130+
);
131+
return Err(DlpError::NonceOutOfOrder.into());
132+
}
133+
134+
// Once the account is marked as undelegatable, any subsequent commit should fail
135+
if delegation_metadata.is_undelegatable {
136+
log!("delegation metadata is already undelegated: ");
137+
pubkey::log(args.delegation_metadata_account.key());
138+
return Err(DlpError::AlreadyUndelegated.into());
139+
}
140+
141+
// Update delegation metadata undelegation flag
142+
delegation_metadata.is_undelegatable = args.allow_undelegation;
143+
delegation_metadata
144+
.to_bytes_with_discriminator(&mut delegation_metadata_data.as_mut())
145+
.map_err(to_pinocchio_program_error)?;
146+
147+
// Load delegation record
148+
let delegation_record_data = args.delegation_record_account.try_borrow_data()?;
149+
let delegation_record =
150+
DelegationRecord::try_from_bytes_with_discriminator(&delegation_record_data)
151+
.map_err(to_pinocchio_program_error)?;
152+
153+
// Check that the authority is allowed to commit
154+
if !pubkey_eq(delegation_record.authority.as_array(), args.validator.key()) {
155+
log!("validator is not the delegation authority. validator: ");
156+
pubkey::log(args.validator.key());
157+
log!("delegation authority: ");
158+
pubkey::log(delegation_record.authority.as_array());
159+
return Err(DlpError::InvalidAuthority.into());
160+
}
161+
162+
// If there was an issue with the lamport accounting in the past, abort (this should never happen)
163+
if args.delegated_account.lamports() < delegation_record.lamports {
164+
log!(
165+
"delegated account has less lamports than the delegation record indicates. delegation account: ");
166+
pubkey::log(args.delegated_account.key());
167+
return Err(DlpError::InvalidDelegatedState.into());
168+
}
169+
170+
// If committed lamports are more than the previous lamports balance, deposit the difference in the commitment account
171+
// If committed lamports are less than the previous lamports balance, we have collateral to settle the balance at state finalization
172+
// We need to do that so that the finalizer already have all the lamports from the validators ready at finalize time
173+
// The finalizer can return any extra lamport to the validator during finalize, but this acts as the validator's proof of collateral
174+
// if args.commit_record_lamports > delegation_record.lamports {
175+
// system::Transfer {
176+
// from: args.validator,
177+
// to: args.commit_state_account,
178+
// lamports: args.commit_record_lamports - delegation_record.lamports,
179+
// }
180+
// .invoke()?;
181+
// }
182+
183+
// Load the program configuration and validate it, if any
184+
let has_program_config = require_program_config(
185+
args.program_config_account,
186+
delegation_record.owner.as_array(),
187+
false,
188+
)?;
189+
if has_program_config {
190+
let program_config_data = args.program_config_account.try_borrow_data()?;
191+
192+
let program_config = ProgramConfig::try_from_bytes_with_discriminator(&program_config_data)
193+
.map_err(to_pinocchio_program_error)?;
194+
if !program_config
195+
.approved_validators
196+
.contains(&(*args.validator.key()).into())
197+
{
198+
log!("validator is not whitelisted in the program config: ");
199+
pubkey::log(args.validator.key());
200+
return Err(DlpError::InvalidWhitelistProgramConfig.into());
201+
}
202+
}
203+
204+
// Copy the new state to the initialized PDA
205+
let mut delegated_account_data = args.delegated_account.try_borrow_mut_data()?;
206+
207+
match args.commit_state_bytes {
208+
NewState::FullBytes(bytes) => (*delegated_account_data).copy_from_slice(bytes),
209+
NewState::Diff(diff) => {
210+
apply_diff_in_place(&mut delegated_account_data, &diff)?;
211+
}
212+
}
213+
214+
// TODO - Add additional validation for the commitment, e.g. sufficient validator stake
215+
216+
Ok(())
217+
}

src/processor/fast/commit_state.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@ pub(crate) fn process_commit_state_internal(
184184
return Err(DlpError::InvalidAuthority.into());
185185
}
186186

187+
// TODO (snawaz): what exactly is ensured here? why can't the delegated_account's lamports be
188+
// different?
189+
//
187190
// If there was an issue with the lamport accounting in the past, abort (this should never happen)
188191
if args.delegated_account.lamports() < delegation_record.lamports {
189192
log!(
@@ -197,15 +200,12 @@ pub(crate) fn process_commit_state_internal(
197200
// We need to do that so that the finalizer already have all the lamports from the validators ready at finalize time
198201
// The finalizer can return any extra lamport to the validator during finalize, but this acts as the validator's proof of collateral
199202
if args.commit_record_lamports > delegation_record.lamports {
200-
let extra_lamports = args
201-
.commit_record_lamports
202-
.checked_sub(delegation_record.lamports)
203-
.ok_or(DlpError::Overflow)?;
204-
203+
// TODO (snawaz): commit_state_account does not exist yet. So how do we transfer lamports
204+
// to non-existent account? we can do that when we create it?
205205
system::Transfer {
206206
from: args.validator,
207207
to: args.commit_state_account,
208-
lamports: extra_lamports,
208+
lamports: args.commit_record_lamports - delegation_record.lamports,
209209
}
210210
.invoke()?;
211211
}

src/processor/fast/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod commit_diff;
22
mod commit_diff_from_buffer;
3+
mod commit_finalize;
34
mod commit_state;
45
mod commit_state_from_buffer;
56
mod delegate;
@@ -9,6 +10,7 @@ mod utils;
910

1011
pub use commit_diff::*;
1112
pub use commit_diff_from_buffer::*;
13+
pub use commit_finalize::*;
1214
pub use commit_state::*;
1315
pub use commit_state_from_buffer::*;
1416
pub use delegate::*;

0 commit comments

Comments
 (0)