Skip to content

Commit f2edfcc

Browse files
authored
feat: optimize finalize instruction using Pinocchio for lower CU consumption (#98)
It is the fifth PR in the current stack. See #94 (the first PR in the stack) for a detailed description. CU consumption is reduced from **23.7** to **17.4K**. ## Learning - 5 calls to `find_program_address` consume close to `13K` compute-units: ``` ‘Program log: >> find_program_address => 3204 CU’, ‘Program log: >> find_program_address => 1704 CU’, ‘Program log: >> find_program_address => 1704 CU’, ‘Program log: >> find_program_address => 4704 CU’, ‘Program log: >> find_program_address => 1704 CU’, ``` - So technically `finaize` itself consumes `4.4K` which we probably cannot optimize any further. ## Before & After Screenshots **BEFORE**: <img width="1027" height="367" alt="image" src="https://github.com/user-attachments/assets/615793fe-c344-45f8-9fbe-1b11fc3a61d7" /> **AFTER**: <img width="1024" height="345" alt="image" src="https://github.com/user-attachments/assets/262277e9-7081-4e16-aed5-744dc1b1f4af" />
1 parent b676c0b commit f2edfcc

5 files changed

Lines changed: 212 additions & 0 deletions

File tree

‎src/lib.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ pub fn fast_process_instruction(
115115
discriminator::DlpDiscriminator::CommitStateFromBuffer => Some(
116116
processor::fast::process_commit_state_from_buffer(program_id, accounts, data),
117117
),
118+
discriminator::DlpDiscriminator::Finalize => Some(processor::fast::process_finalize(
119+
program_id, accounts, data,
120+
)),
118121
discriminator::DlpDiscriminator::Undelegate => Some(processor::fast::process_undelegate(
119122
program_id, accounts, data,
120123
)),

‎src/processor/fast/finalize.rs‎

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
use pinocchio::account_info::AccountInfo;
2+
use pinocchio::program_error::ProgramError;
3+
use pinocchio::pubkey::{pubkey_eq, Pubkey};
4+
use pinocchio::ProgramResult;
5+
use pinocchio_log::log;
6+
7+
use crate::error::DlpError;
8+
use crate::processor::fast::utils::pda::close_pda;
9+
use crate::processor::fast::utils::requires::{
10+
is_uninitialized_account, require_initialized_commit_record, require_initialized_commit_state,
11+
require_initialized_delegation_metadata, require_initialized_delegation_record,
12+
require_initialized_validator_fees_vault, require_owned_pda, require_program, require_signer,
13+
};
14+
use crate::state::{CommitRecord, DelegationMetadata, DelegationRecord};
15+
16+
use super::to_pinocchio_program_error;
17+
18+
pub fn process_finalize(
19+
_program_id: &Pubkey,
20+
accounts: &[AccountInfo],
21+
_data: &[u8],
22+
) -> ProgramResult {
23+
let [validator, delegated_account, commit_state_account, commit_record_account, delegation_record_account, delegation_metadata_account, validator_fees_vault, system_program] =
24+
accounts
25+
else {
26+
return Err(ProgramError::NotEnoughAccountKeys);
27+
};
28+
29+
require_signer(validator, "validator")?;
30+
require_owned_pda(delegated_account, &crate::fast::ID, "delegated account")?;
31+
require_initialized_delegation_record(delegated_account, delegation_record_account, true)?;
32+
require_initialized_delegation_metadata(delegated_account, delegation_metadata_account, true)?;
33+
require_initialized_validator_fees_vault(validator, validator_fees_vault, true)?;
34+
require_program(system_program, &pinocchio_system::ID, "system program")?;
35+
36+
let require_cs =
37+
require_initialized_commit_state(delegated_account, commit_state_account, true);
38+
let require_cr =
39+
require_initialized_commit_record(delegated_account, commit_record_account, true);
40+
41+
// Since finalize instructions are typically bundled, we return without error
42+
// if there is nothing to be finalized, so that correct finalizes are executed
43+
if let (Err(ProgramError::InvalidAccountOwner), Err(ProgramError::InvalidAccountOwner)) =
44+
(&require_cs, &require_cr)
45+
{
46+
if is_uninitialized_account(commit_state_account)
47+
&& is_uninitialized_account(commit_record_account)
48+
{
49+
log!("No state to be finalized. Skipping finalize.");
50+
return Ok(());
51+
}
52+
}
53+
require_cs?;
54+
require_cr?;
55+
56+
// Load delegation metadata
57+
let mut delegation_metadata_data = delegation_metadata_account.try_borrow_mut_data()?;
58+
let mut delegation_metadata =
59+
DelegationMetadata::try_from_bytes_with_discriminator(&delegation_metadata_data)
60+
.map_err(to_pinocchio_program_error)?;
61+
62+
let mut delegation_record_data = delegation_record_account.try_borrow_mut_data()?;
63+
let delegation_record =
64+
DelegationRecord::try_from_bytes_with_discriminator_mut(&mut delegation_record_data)
65+
.map_err(to_pinocchio_program_error)?;
66+
67+
// Load commit record
68+
let commit_record_data = commit_record_account.try_borrow_data()?;
69+
let commit_record = CommitRecord::try_from_bytes_with_discriminator(&commit_record_data)
70+
.map_err(to_pinocchio_program_error)?;
71+
72+
// Check that the commit record is the right one
73+
if !pubkey_eq(commit_record.account.as_array(), delegated_account.key()) {
74+
return Err(DlpError::InvalidDelegatedAccount.into());
75+
}
76+
if !pubkey_eq(commit_record.identity.as_array(), validator.key()) {
77+
return Err(DlpError::InvalidReimbursementAccount.into());
78+
}
79+
80+
// Settle accounts lamports
81+
settle_lamports_balance(
82+
delegated_account,
83+
commit_state_account,
84+
validator_fees_vault,
85+
delegation_record.lamports,
86+
commit_record.lamports,
87+
)?;
88+
89+
// Update the delegation metadata
90+
delegation_metadata.last_update_nonce = commit_record.nonce;
91+
delegation_metadata
92+
.to_bytes_with_discriminator(&mut delegation_metadata_data.as_mut())
93+
.map_err(to_pinocchio_program_error)?;
94+
95+
// Update the delegation record
96+
delegation_record.lamports = delegated_account.lamports();
97+
98+
// Load commit state
99+
let commit_state_data = commit_state_account.try_borrow_data()?;
100+
101+
// Copying the new commit state to the delegated account
102+
delegated_account.resize(commit_state_data.len())?;
103+
let mut delegated_account_data = delegated_account.try_borrow_mut_data()?;
104+
(*delegated_account_data).copy_from_slice(&commit_state_data);
105+
106+
// Drop remaining reference before closing accounts
107+
drop(commit_record_data);
108+
drop(commit_state_data);
109+
110+
// Closing accounts
111+
close_pda(commit_state_account, validator)?;
112+
close_pda(commit_record_account, validator)?;
113+
114+
Ok(())
115+
}
116+
117+
/// Settle the committed lamports to the delegated account
118+
fn settle_lamports_balance(
119+
delegated_account: &AccountInfo,
120+
commit_state_account: &AccountInfo,
121+
validator_fees_vault: &AccountInfo,
122+
delegation_record_lamports: u64,
123+
commit_record_lamports: u64,
124+
) -> Result<(), ProgramError> {
125+
let (transfer_source, transfer_destination, transfer_lamports) =
126+
match delegation_record_lamports.cmp(&commit_record_lamports) {
127+
std::cmp::Ordering::Greater => (
128+
delegated_account,
129+
validator_fees_vault,
130+
delegation_record_lamports
131+
.checked_sub(commit_record_lamports)
132+
.ok_or(DlpError::Overflow)?,
133+
),
134+
std::cmp::Ordering::Less => (
135+
commit_state_account,
136+
delegated_account,
137+
commit_record_lamports
138+
.checked_sub(delegation_record_lamports)
139+
.ok_or(DlpError::Overflow)?,
140+
),
141+
std::cmp::Ordering::Equal => return Ok(()),
142+
};
143+
144+
*transfer_source.try_borrow_mut_lamports()? = transfer_source
145+
.lamports()
146+
.checked_sub(transfer_lamports)
147+
.ok_or(DlpError::Overflow)?;
148+
*transfer_destination.try_borrow_mut_lamports()? = transfer_destination
149+
.lamports()
150+
.checked_add(transfer_lamports)
151+
.ok_or(DlpError::Overflow)?;
152+
153+
Ok(())
154+
}

‎src/processor/fast/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
mod commit_state;
22
mod commit_state_from_buffer;
33
mod delegate;
4+
mod finalize;
45
mod undelegate;
56
mod utils;
67

78
pub use commit_state::*;
89
pub use commit_state_from_buffer::*;
910
pub use delegate::*;
11+
pub use finalize::*;
1012
pub use undelegate::*;
1113

1214
pub fn to_pinocchio_program_error(

‎src/processor/fast/utils/requires.rs‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ pub fn require_pda(
8585
Ok(pda.1)
8686
}
8787

88+
/// Returns true if the account is uninitialized based on the following conditions:
89+
/// - Owner is the system program.
90+
/// - Data is empty.
91+
pub fn is_uninitialized_account(info: &AccountInfo) -> bool {
92+
pubkey_eq(info.owner(), &pinocchio_system::ID) && info.data_is_empty()
93+
}
94+
8895
/// Errors if:
8996
/// - Owner is not the system program.
9097
/// - Data is not empty.
@@ -296,3 +303,37 @@ pub fn require_initialized_delegation_metadata(
296303
)?;
297304
Ok(())
298305
}
306+
307+
/// Load initialized commit state account
308+
/// - Commit state account must be derived from the delegated account pubkey
309+
pub fn require_initialized_commit_state(
310+
delegated_account: &AccountInfo,
311+
commit_state: &AccountInfo,
312+
is_writable: bool,
313+
) -> Result<(), ProgramError> {
314+
require_initialized_pda(
315+
commit_state,
316+
&[pda::COMMIT_STATE_TAG, delegated_account.key()],
317+
&crate::fast::ID,
318+
is_writable,
319+
"commit state",
320+
)?;
321+
Ok(())
322+
}
323+
324+
/// Load initialized commit state record
325+
/// - Commit record account must be derived from the delegated account pubkey
326+
pub fn require_initialized_commit_record(
327+
delegated_account: &AccountInfo,
328+
commit_record: &AccountInfo,
329+
is_writable: bool,
330+
) -> Result<(), ProgramError> {
331+
require_initialized_pda(
332+
commit_record,
333+
&[pda::COMMIT_RECORD_TAG, delegated_account.key()],
334+
&crate::fast::ID,
335+
is_writable,
336+
"commit record",
337+
)?;
338+
Ok(())
339+
}

‎tests/integration/tests/test-delegation.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,18 @@ describe("TestDelegation", () => {
245245
const ix = createFinalizeInstruction(validator, pda);
246246
const txId = await processInstruction(ix);
247247
console.log("Finalize signature", txId);
248+
const tx = await fetchTransaction(txId);
249+
console.log(tx.meta.logMessages);
250+
251+
const consumedLog = tx.meta.logMessages.find((m) =>
252+
m.includes("DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh consumed")
253+
);
254+
255+
assert.isAtMost(
256+
parseInt(consumedLog.split(" ").at(3)),
257+
17500,
258+
"finalize instruction must consume less than 17500"
259+
);
248260
});
249261

250262
it("Commit a new state to the PDA", async () => {

0 commit comments

Comments
 (0)