Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 19c2dd7

Browse files
authored
token-client: Use transaction simulation results for the compute unit limit (#6500)
* token-client: Add priority fees * Create and send range proofs without helpers
1 parent 6573768 commit 19c2dd7

File tree

4 files changed

+131
-28
lines changed

4 files changed

+131
-28
lines changed

token/client/src/client.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ pub trait SendTransaction {
2020

2121
/// Basic trait for simulating transactions in a validator.
2222
pub trait SimulateTransaction {
23-
type SimulationOutput;
23+
type SimulationOutput: SimulationResult;
24+
}
25+
26+
/// Trait for the output of a simulation
27+
pub trait SimulationResult {
28+
fn get_compute_units_consumed(&self) -> ProgramClientResult<u64>;
2429
}
2530

2631
/// Extends basic `SendTransaction` trait with function `send` where client is
@@ -66,6 +71,15 @@ impl SendTransactionBanksClient for ProgramBanksClientProcessTransaction {
6671
}
6772
}
6873

74+
impl SimulationResult for BanksTransactionResultWithSimulation {
75+
fn get_compute_units_consumed(&self) -> ProgramClientResult<u64> {
76+
self.simulation_details
77+
.as_ref()
78+
.map(|x| x.units_consumed)
79+
.ok_or("No simulation results found".into())
80+
}
81+
}
82+
6983
impl SimulateTransaction for ProgramBanksClientProcessTransaction {
7084
type SimulationOutput = BanksTransactionResultWithSimulation;
7185
}
@@ -139,6 +153,20 @@ impl SendTransactionRpc for ProgramRpcClientSendTransaction {
139153
}
140154
}
141155

156+
impl SimulationResult for RpcClientResponse {
157+
fn get_compute_units_consumed(&self) -> ProgramClientResult<u64> {
158+
match self {
159+
// `Transaction` is the result of an offline simulation. The error
160+
// should be properly handled by a caller that supports offline
161+
// signing
162+
Self::Signature(_) | Self::Transaction(_) => Err("Not a simulation result".into()),
163+
Self::Simulation(simulation_result) => simulation_result
164+
.units_consumed
165+
.ok_or("No simulation results found".into()),
166+
}
167+
}
168+
}
169+
142170
impl SimulateTransaction for ProgramRpcClientSendTransaction {
143171
type SimulationOutput = RpcClientResponse;
144172
}

token/client/src/token.rs

Lines changed: 96 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use {
22
crate::{
3-
client::{ProgramClient, ProgramClientError, SendTransaction, SimulateTransaction},
3+
client::{
4+
ProgramClient, ProgramClientError, SendTransaction, SimulateTransaction,
5+
SimulationResult,
6+
},
47
proof_generation::transfer_with_fee_split_proof_data,
58
},
69
futures::{future::join_all, try_join},
@@ -522,6 +525,45 @@ where
522525
}
523526
}
524527

528+
/// Helper function to add a compute unit limit instruction to a given set
529+
/// of instructions
530+
async fn add_compute_unit_limit_from_simulation(
531+
&self,
532+
instructions: &mut Vec<Instruction>,
533+
blockhash: &Hash,
534+
) -> TokenResult<()> {
535+
// add a max compute unit limit instruction for the simulation
536+
const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000;
537+
instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(
538+
MAX_COMPUTE_UNIT_LIMIT,
539+
));
540+
541+
let transaction = Transaction::new_unsigned(Message::new_with_blockhash(
542+
instructions,
543+
Some(&self.payer.pubkey()),
544+
blockhash,
545+
));
546+
let simulation_result = self
547+
.client
548+
.simulate_transaction(&transaction)
549+
.await
550+
.map_err(TokenError::Client)?;
551+
if let Ok(units_consumed) = simulation_result.get_compute_units_consumed() {
552+
// Overwrite the compute unit limit instruction with the actual units consumed
553+
let compute_unit_limit =
554+
u32::try_from(units_consumed).map_err(|x| TokenError::Client(x.into()))?;
555+
instructions
556+
.last_mut()
557+
.expect("Compute budget instruction was added earlier")
558+
.data = ComputeBudgetInstruction::set_compute_unit_limit(compute_unit_limit).data;
559+
} else {
560+
// `get_compute_units_consumed()` fails for offline signing, so we
561+
// catch that error and remove the instruction that was added
562+
instructions.pop();
563+
}
564+
Ok(())
565+
}
566+
525567
async fn construct_tx<S: Signers>(
526568
&self,
527569
token_instructions: &[Instruction],
@@ -549,18 +591,6 @@ where
549591

550592
instructions.extend_from_slice(token_instructions);
551593

552-
if let Some(compute_unit_limit) = self.compute_unit_limit {
553-
instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(
554-
compute_unit_limit,
555-
));
556-
}
557-
558-
if let Some(compute_unit_price) = self.compute_unit_price {
559-
instructions.push(ComputeBudgetInstruction::set_compute_unit_price(
560-
compute_unit_price,
561-
));
562-
}
563-
564594
let blockhash = if let (Some(nonce_account), Some(nonce_authority), Some(nonce_blockhash)) = (
565595
self.nonce_account,
566596
&self.nonce_authority,
@@ -579,6 +609,25 @@ where
579609
.map_err(TokenError::Client)?
580610
};
581611

612+
if let Some(compute_unit_price) = self.compute_unit_price {
613+
instructions.push(ComputeBudgetInstruction::set_compute_unit_price(
614+
compute_unit_price,
615+
));
616+
}
617+
618+
// The simulation to find out the compute unit usage must be run after
619+
// all instructions have been added to the transaction, so be sure to
620+
// keep this instruction as the last one before creating and sending the
621+
// transaction.
622+
if let Some(compute_unit_limit) = self.compute_unit_limit {
623+
instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(
624+
compute_unit_limit,
625+
));
626+
} else {
627+
self.add_compute_unit_limit_from_simulation(&mut instructions, &blockhash)
628+
.await?;
629+
}
630+
582631
let message = Message::new_with_blockhash(&instructions, fee_payer, &blockhash);
583632
let mut transaction = Transaction::new_unsigned(message);
584633
let signing_pubkeys = signing_keypairs.pubkeys();
@@ -2087,12 +2136,26 @@ where
20872136
)
20882137
.await?;
20892138

2090-
self.process_ixs(
2139+
// This instruction is right at the transaction size limit, so we cannot
2140+
// add any other instructions to it
2141+
let blockhash = self
2142+
.client
2143+
.get_latest_blockhash()
2144+
.await
2145+
.map_err(TokenError::Client)?;
2146+
2147+
let transaction = Transaction::new_signed_with_payer(
20912148
&[instruction_type
20922149
.encode_verify_proof(Some(withdraw_proof_context_state_info), withdraw_proof_data)],
2093-
&[] as &[&dyn Signer; 0],
2094-
)
2095-
.await
2150+
Some(&self.payer.pubkey()),
2151+
&[self.payer.as_ref()],
2152+
blockhash,
2153+
);
2154+
2155+
self.client
2156+
.send_transaction(&transaction)
2157+
.await
2158+
.map_err(TokenError::Client)
20962159
}
20972160

20982161
/// Transfer tokens confidentially
@@ -2571,12 +2634,24 @@ where
25712634

25722635
// This instruction is right at the transaction size limit, but in the
25732636
// future it might be able to support the transfer too
2574-
self.process_ixs(
2637+
let blockhash = self
2638+
.client
2639+
.get_latest_blockhash()
2640+
.await
2641+
.map_err(TokenError::Client)?;
2642+
2643+
let transaction = Transaction::new_signed_with_payer(
25752644
&[instruction_type
25762645
.encode_verify_proof(Some(range_proof_context_state_info), range_proof_data)],
2577-
&[] as &[&dyn Signer; 0],
2578-
)
2579-
.await
2646+
Some(&self.payer.pubkey()),
2647+
&[self.payer.as_ref()],
2648+
blockhash,
2649+
);
2650+
2651+
self.client
2652+
.send_transaction(&transaction)
2653+
.await
2654+
.map_err(TokenError::Client)
25802655
}
25812656

25822657
/// Create a range proof context state account with a confidential transfer

token/program-2022-test/tests/confidential_transfer.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1110,7 +1110,6 @@ async fn confidential_transfer_transfer_with_fee() {
11101110
let bob_meta = ConfidentialTokenAccountMeta::new(&token, &bob, None, false, true).await;
11111111

11121112
// Self-transfer of 0 tokens
1113-
let token = token.with_compute_unit_limit(500_000);
11141113
token
11151114
.confidential_transfer_transfer_with_fee(
11161115
&alice_meta.token_account,
@@ -2524,6 +2523,9 @@ async fn confidential_transfer_transfer_with_split_proof_contexts_in_parallel()
25242523
&range_proof_context_state_account,
25252524
&context_state_authority,
25262525
];
2526+
// With split proofs in parallel, one of the transactions does more work
2527+
// than the other, which isn't caught during the simulation to discover the
2528+
// compute unit limit.
25272529
let token = token.with_compute_unit_limit(500_000);
25282530
token
25292531
.confidential_transfer_transfer_with_split_proofs_in_parallel(
@@ -2943,6 +2945,9 @@ async fn confidential_transfer_transfer_with_fee_and_split_proof_context_in_para
29432945
&range_proof_context_state_account,
29442946
&context_state_authority,
29452947
];
2948+
// With split proofs in parallel, one of the transactions does more work
2949+
// than the other, which isn't caught during the simulation to discover the
2950+
// compute unit limit.
29462951
let token = token.with_compute_unit_limit(500_000);
29472952
token
29482953
.confidential_transfer_transfer_with_fee_and_split_proofs_in_parallel(

token/program-2022-test/tests/confidential_transfer_fee.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,6 @@ async fn confidential_transfer_withdraw_withheld_tokens_from_mint() {
513513
};
514514

515515
// Test fee is 2.5% so the withheld fees should be 3
516-
let token = token.with_compute_unit_limit(500_000);
517516
token
518517
.confidential_transfer_transfer_with_fee(
519518
&alice_meta.token_account,
@@ -672,7 +671,6 @@ async fn confidential_transfer_withdraw_withheld_tokens_from_accounts() {
672671
};
673672

674673
// Test fee is 2.5% so the withheld fees should be 3
675-
let token = token.with_compute_unit_limit(500_000);
676674
token
677675
.confidential_transfer_transfer_with_fee(
678676
&alice_meta.token_account,
@@ -803,7 +801,6 @@ async fn confidential_transfer_withdraw_withheld_tokens_from_mint_with_proof_con
803801
};
804802

805803
// Test fee is 2.5% so the withheld fees should be 3
806-
let token = token.with_compute_unit_limit(500_000);
807804
token
808805
.confidential_transfer_transfer_with_fee(
809806
&alice_meta.token_account,
@@ -972,7 +969,6 @@ async fn confidential_transfer_withdraw_withheld_tokens_from_accounts_with_proof
972969
};
973970

974971
// Test fee is 2.5% so the withheld fees should be 3
975-
let token = token.with_compute_unit_limit(500_000);
976972
token
977973
.confidential_transfer_transfer_with_fee(
978974
&alice_meta.token_account,
@@ -1163,7 +1159,6 @@ async fn confidential_transfer_harvest_withheld_tokens_to_mint() {
11631159
.unwrap();
11641160

11651161
// Test fee is 2.5% so the withheld fees should be 3
1166-
let token = token.with_compute_unit_limit(500_000);
11671162
token
11681163
.confidential_transfer_transfer_with_fee(
11691164
&alice_meta.token_account,

0 commit comments

Comments
 (0)