Skip to content

Commit 735d4ff

Browse files
authored
[TKN-522] optionally simulate for compute units, use correct number of default signatures (#1026)
1 parent 69a902e commit 735d4ff

File tree

5 files changed

+156
-53
lines changed

5 files changed

+156
-53
lines changed

.changeset/weak-peaches-cheer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@orca-so/rust-tx-sender": minor
3+
---
4+
5+
Provide the ability to skip simulation for compute units, set correct number of default signatures when creating a Versioned Transaction

rust-sdk/tx-sender/README.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,78 @@ orca_tx_sender = { version = "0.1.0" }
2828

2929
## Usage Examples
3030

31+
## Per-Function Configuration Examples
32+
33+
Below is an example of how to use the per-function configuration for `build_and_send_transaction_with_config`. However, you can also use the per-function configuration for the build and send steps separately using the following functions: `build_transaction_with_config` and `send_transaction_with_config`.
34+
35+
### Basic Example
36+
37+
```rust
38+
use orca_tx_sender::{
39+
build_and_send_transaction_with_config,
40+
PriorityFeeStrategy, Percentile,
41+
set_priority_fee_strategy
42+
};
43+
use solana_sdk::pubkey::Pubkey;
44+
use solana_sdk::signature::{Keypair, Signer};
45+
use solana_sdk::system_instruction;
46+
use solana_sdk::commitment_config::CommitmentLevel;
47+
use std::error::Error;
48+
use std::str::FromStr;
49+
50+
#[tokio::main]
51+
async fn main() -> Result<(), Box<dyn Error>> {
52+
let rpc_client = RpcClient::new("https://api.mainnet-beta.solana.com");
53+
54+
// Use the cluster URL to create a new instance of RpcConfig. You can also create the struct manually.
55+
let rpc_config = RpcConfig::new(rpc_client.url());
56+
57+
// Configure the fee config with priority fees
58+
let fee_config = FeeConfig {
59+
priority_fee_strategy: PriorityFeeStrategy::Dynamic {
60+
percentile: Percentile::P95,
61+
max_lamports: 10_000,
62+
},
63+
..Default::default()
64+
};
65+
66+
// Create a keypair for signing
67+
let payer = Keypair::new();
68+
println!("Using keypair: {}", payer.pubkey());
69+
70+
// Check balance
71+
let balance = client.get_balance(&payer.pubkey()).await?;
72+
println!("Account balance: {} lamports", balance);
73+
74+
// Jupiter Program address as an example recipient
75+
let recipient = Pubkey::from_str("JUP2jxvXaqu7NQY1GmNF4m1vodw12LVXYxbFL2uJvfo").unwrap();
76+
77+
// Create transfer instruction
78+
let transfer_ix = system_instruction::transfer(
79+
&payer.pubkey(),
80+
&recipient,
81+
1_000_000, // 0.001 SOL
82+
);
83+
84+
// Build and send transaction
85+
println!("Sending transaction...");
86+
let signature = build_and_send_transaction_with_config(
87+
vec![transfer_ix],
88+
&[&payer],
89+
Some(CommitmentLevel::Confirmed),
90+
None, // No address lookup tables
91+
&rpc_client,
92+
&rpc_config,
93+
&fee_config,
94+
).await?;
95+
96+
println!("Transaction sent: {}", signature);
97+
Ok(())
98+
}
99+
```
100+
101+
## Global Configuration Examples
102+
31103
### Basic Example
32104

33105
```rust

rust-sdk/tx-sender/src/compute_budget.rs

Lines changed: 19 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,21 @@ use solana_sdk::compute_budget::ComputeBudgetInstruction;
1010
use solana_sdk::message::{v0::Message, VersionedMessage};
1111
use solana_sdk::transaction::VersionedTransaction;
1212

13-
const SET_COMPUTE_UNIT_LIMIT_DISCRIMINATOR: u8 = 0x02;
13+
/// Compute unit limit strategy to apply when building a transaction.
14+
/// - Dynamic: Estimate compute units by simulating the transaction.
15+
/// If the simulation fails, the transaction will not build.
16+
/// - Exact: Directly use the provided compute unit limit.
17+
#[derive(Debug, Default)]
18+
pub enum ComputeUnitLimitStrategy {
19+
#[default]
20+
Dynamic,
21+
Exact(u32),
22+
}
23+
24+
#[derive(Debug, Default)]
25+
pub struct ComputeConfig {
26+
pub unit_limit: ComputeUnitLimitStrategy,
27+
}
1428

1529
/// Estimate compute units by simulating a transaction
1630
pub async fn estimate_compute_units(
@@ -25,10 +39,10 @@ pub async fn estimate_compute_units(
2539
.await
2640
.map_err(|e| format!("Failed to get recent blockhash: {}", e))?;
2741

28-
let mut simulation_instructions = instructions.to_vec();
29-
if extract_compute_unit_limit(instructions).is_none() {
30-
simulation_instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(1_400_000));
31-
}
42+
// Add max compute unit limit instruction so that the simulation does not fail
43+
let mut simulation_instructions =
44+
vec![ComputeBudgetInstruction::set_compute_unit_limit(1_400_000)];
45+
simulation_instructions.extend_from_slice(instructions);
3246

3347
let message = Message::try_compile(payer, &simulation_instructions, &alt_accounts, blockhash)
3448
.map_err(|e| format!("Failed to compile message: {}", e))?;
@@ -57,7 +71,6 @@ pub async fn estimate_compute_units(
5771
if let Some(err) = simulation_result.value.err {
5872
return Err(format!("Transaction simulation failed: {}", err));
5973
}
60-
6174
match simulation_result.value.units_consumed {
6275
Some(units) => Ok(units as u32),
6376
None => Err("Transaction simulation didn't return consumed units".to_string()),
@@ -209,38 +222,3 @@ pub fn get_writable_accounts(instructions: &[Instruction]) -> Vec<Pubkey> {
209222

210223
writable.into_iter().collect()
211224
}
212-
213-
/// Extract the compute unit limit from a list of instructions
214-
fn extract_compute_unit_limit(instructions: &[Instruction]) -> Option<u32> {
215-
for ix in instructions {
216-
if ix.program_id == solana_sdk::compute_budget::ID {
217-
if ix.data.first() == Some(&SET_COMPUTE_UNIT_LIMIT_DISCRIMINATOR) {
218-
let limit_bytes_array: [u8; 4] = ix.data.get(1..5)?.try_into().ok()?;
219-
return Some(u32::from_le_bytes(limit_bytes_array));
220-
} else {
221-
return None;
222-
}
223-
}
224-
}
225-
None
226-
}
227-
228-
#[cfg(test)]
229-
mod tests {
230-
use super::*;
231-
232-
#[tokio::test]
233-
async fn test_extract_compute_unit_limit() {
234-
let instructions = vec![];
235-
let compute_unit_limit = extract_compute_unit_limit(&instructions);
236-
assert_eq!(compute_unit_limit, None);
237-
}
238-
239-
#[tokio::test]
240-
async fn test_extract_compute_unit_limit_with_limit() {
241-
let instructions = vec![ComputeBudgetInstruction::set_compute_unit_limit(1_400_000)];
242-
243-
let compute_unit_limit = extract_compute_unit_limit(&instructions);
244-
assert_eq!(compute_unit_limit, Some(1_400_000));
245-
}
246-
}

rust-sdk/tx-sender/src/lib.rs

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -89,20 +89,26 @@ pub async fn build_and_send_transaction<S: Signer>(
8989
.await
9090
}
9191

92+
#[derive(Debug, Default)]
93+
pub struct BuildTransactionConfig {
94+
pub rpc_config: RpcConfig,
95+
pub fee_config: FeeConfig,
96+
pub compute_config: ComputeConfig,
97+
}
98+
9299
/// Build a transaction with compute budget and priority fees from the supplied configuration
93100
///
94101
/// This function handles:
95102
/// 1. Building a transaction message with all instructions
96103
/// 2. Adding compute budget instructions
97104
/// 3. Adding any Jito tip instructions
98105
/// 4. Supporting address lookup tables for account compression
99-
pub async fn build_transaction_with_config(
106+
pub async fn build_transaction_with_config_obj(
100107
mut instructions: Vec<Instruction>,
101108
payer: &Pubkey,
102109
address_lookup_tables: Option<Vec<AddressLookupTableAccount>>,
103110
rpc_client: &RpcClient,
104-
rpc_config: &RpcConfig,
105-
fee_config: &FeeConfig,
111+
config: &BuildTransactionConfig,
106112
) -> Result<VersionedTransaction, String> {
107113
let recent_blockhash = rpc_client
108114
.get_latest_blockhash()
@@ -113,13 +119,21 @@ pub async fn build_transaction_with_config(
113119

114120
let address_lookup_tables_clone = address_lookup_tables.clone();
115121

116-
let compute_units = compute_budget::estimate_compute_units(
117-
rpc_client,
118-
&instructions,
119-
payer,
120-
address_lookup_tables_clone,
121-
)
122-
.await?;
122+
let compute_units = match config.compute_config.unit_limit {
123+
ComputeUnitLimitStrategy::Dynamic => {
124+
compute_budget::estimate_compute_units(
125+
rpc_client,
126+
&instructions,
127+
payer,
128+
address_lookup_tables_clone,
129+
)
130+
.await?
131+
}
132+
ComputeUnitLimitStrategy::Exact(units) => units,
133+
};
134+
135+
let rpc_config = &config.rpc_config;
136+
let fee_config = &config.fee_config;
123137
let budget_instructions = compute_budget::get_compute_budget_instruction(
124138
rpc_client,
125139
compute_units,
@@ -153,12 +167,46 @@ pub async fn build_transaction_with_config(
153167
Message::try_compile(payer, &instructions, &[], recent_blockhash)
154168
.map_err(|e| format!("Failed to compile message: {}", e))?
155169
};
170+
171+
// Provide the correct number of signatures for the transaction, otherwise (de)serialization can fail
156172
Ok(VersionedTransaction {
157-
signatures: vec![],
173+
signatures: vec![
174+
solana_sdk::signature::Signature::default();
175+
message.header.num_required_signatures.into()
176+
],
158177
message: VersionedMessage::V0(message),
159178
})
160179
}
161180

181+
/// Build a transaction with compute budget and priority fees from the supplied configuration
182+
///
183+
/// This function handles:
184+
/// 1. Building a transaction message with all instructions
185+
/// 2. Adding compute budget instructions
186+
/// 3. Adding any Jito tip instructions
187+
/// 4. Supporting address lookup tables for account compression
188+
pub async fn build_transaction_with_config(
189+
instructions: Vec<Instruction>,
190+
payer: &Pubkey,
191+
address_lookup_tables: Option<Vec<AddressLookupTableAccount>>,
192+
rpc_client: &RpcClient,
193+
rpc_config: &RpcConfig,
194+
fee_config: &FeeConfig,
195+
) -> Result<VersionedTransaction, String> {
196+
build_transaction_with_config_obj(
197+
instructions,
198+
payer,
199+
address_lookup_tables,
200+
rpc_client,
201+
&BuildTransactionConfig {
202+
rpc_config: (*rpc_config).clone(),
203+
fee_config: (*fee_config).clone(),
204+
..Default::default()
205+
},
206+
)
207+
.await
208+
}
209+
162210
/// Build a transaction with compute budget and priority fees from the global configuration
163211
///
164212
/// This function handles:

rust-sdk/tx-sender/src/rpc_config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ impl From<Hash> for ChainId {
4141
}
4242

4343
/// RPC configuration for connecting to Solana nodes
44-
#[derive(Debug, Clone, PartialEq)]
44+
#[derive(Debug, Default, Clone, PartialEq)]
4545
pub struct RpcConfig {
4646
pub url: String,
4747
pub supports_priority_fee_percentile: bool,

0 commit comments

Comments
 (0)