diff --git a/libs/solana-transaction-builder-executor/src/builder_executor.rs b/libs/solana-transaction-builder-executor/src/builder_executor.rs index 103edfe..f4223aa 100644 --- a/libs/solana-transaction-builder-executor/src/builder_executor.rs +++ b/libs/solana-transaction-builder-executor/src/builder_executor.rs @@ -15,7 +15,7 @@ use std::sync::Arc; use tokio::sync::Semaphore; use uuid::Uuid; -const PARRALLEL_EXECUTION_LIMIT: usize = 30; +const PARALLEL_EXECUTION_LIMIT: usize = 30; #[derive(Clone)] pub struct TransactionBuilderExecutionData { @@ -66,10 +66,10 @@ async fn get_latest_blockhash(url: String) -> anyhow::Result { pub async fn execute_transactions_in_sequence( transaction_executor: Arc, - async_transaction_builders: Vec, + execution_data: Vec, ) -> anyhow::Result<()> { - let sequence_length = async_transaction_builders.len(); - for (index, async_transaction_builder) in async_transaction_builders.into_iter().enumerate() { + let sequence_length = execution_data.len(); + for (index, async_transaction_builder) in execution_data.into_iter().enumerate() { let human_index = index + 1; let tx_uuid = &async_transaction_builder.tx_uuid; debug!("Building the transaction {human_index}/{tx_uuid} (size: {sequence_length})"); @@ -98,16 +98,16 @@ pub async fn execute_transactions_in_sequence( pub async fn execute_transactions_in_parallel( transaction_executor: Arc, - async_transaction_builders: Vec, + execution_data: Vec, parallel_execution_limit: Option, ) -> anyhow::Result<()> { - let sequence_length = async_transaction_builders.len(); + let sequence_length = execution_data.len(); - let parallel_execution_limit = parallel_execution_limit.unwrap_or(PARRALLEL_EXECUTION_LIMIT); + let parallel_execution_limit = parallel_execution_limit.unwrap_or(PARALLEL_EXECUTION_LIMIT); let semaphore = Arc::new(Semaphore::new(parallel_execution_limit)); // Prepare the list of futures with their associated tx_uuid and human_index - let futures = async_transaction_builders + let futures = execution_data .into_iter() .enumerate() .map(|(index, async_transaction_builder)| { @@ -168,13 +168,26 @@ pub fn builder_to_execution_data( transaction_builder .sequence_combined() .map(|prepared_transaction| { - TransactionBuilderExecutionData::new( + let execution_data = TransactionBuilderExecutionData::new( prepared_transaction, rpc_url.clone(), priority_fee_policy .clone() .map_or(PriorityFeePolicy::default(), |policy| policy), - ) + ); + + if log::log_enabled!(log::Level::Debug) { + let description = execution_data + .prepared_transaction + .single_description() + .map_or_else(|| "".to_string(), |v| format!(", description: {}", v)); + debug!( + "Prepared transaction {}{}", + execution_data.tx_uuid, description + ); + } + + execution_data }) .collect() } diff --git a/libs/solana-transaction-builder/src/prepared_transaction.rs b/libs/solana-transaction-builder/src/prepared_transaction.rs index 133c9c3..744307b 100644 --- a/libs/solana-transaction-builder/src/prepared_transaction.rs +++ b/libs/solana-transaction-builder/src/prepared_transaction.rs @@ -21,6 +21,7 @@ pub trait SignedTransaction { pub struct PreparedTransaction { pub transaction: Transaction, pub signers: Vec>, + pub instruction_descriptions: Vec>, } impl SignedTransaction for PreparedTransaction { @@ -42,11 +43,36 @@ impl PreparedTransaction { pub fn new( transaction: Transaction, signature_builder: &SignatureBuilder, + instruction_descriptions: Vec>, ) -> Result { let signers = signature_builder.signers_for_transaction(&transaction)?; Ok(Self { transaction, signers, + instruction_descriptions, }) } + + pub fn single_description(&self) -> Option { + let mut descriptions = self + .instruction_descriptions + .iter() + .map(|desc| desc.clone()) + .collect::>(); + for (i, description) in descriptions.iter_mut().enumerate() { + if let Some(desc) = description { + *description = Some(format!("#{}: {}", i, desc)); + } + } + let descriptions = descriptions + .into_iter() + .filter_map(|d| d.map_or_else(|| None, |d| Some(d))) + .collect::>() + .join("\n"); + if descriptions.is_empty() { + None + } else { + Some(descriptions) + } + } } diff --git a/libs/solana-transaction-builder/src/transaction_builder.rs b/libs/solana-transaction-builder/src/transaction_builder.rs index 4d6afb5..72a5c4e 100644 --- a/libs/solana-transaction-builder/src/transaction_builder.rs +++ b/libs/solana-transaction-builder/src/transaction_builder.rs @@ -23,8 +23,9 @@ pub enum TransactionBuildError { pub struct TransactionBuilder { fee_payer: Pubkey, signature_builder: SignatureBuilder, // invariant: has signers for all instructions - instruction_packs: Vec>, - current_instruction_pack: OnceCell>, + // instruction pack contains a list of instruction with optional description to them + instruction_packs: Vec)>>, + current_instruction_pack: OnceCell)>>, max_transaction_size: usize, } @@ -137,13 +138,44 @@ impl TransactionBuilder { Ok(self) } + pub fn add_instructions_with_description( + &mut self, + instructions_with_description: I, + ) -> anyhow::Result<&mut Self> + where + I: IntoIterator, + { + for (instruction, description) in instructions_with_description { + self.add_instruction_with_description(instruction, description)?; + } + Ok(self) + } + pub fn add_instruction(&mut self, instruction: Instruction) -> anyhow::Result<&mut Self> { + self.add_instruction_internal(instruction, None) + } + + pub fn add_instruction_with_description( + &mut self, + instruction: Instruction, + description: String, + ) -> anyhow::Result<&mut Self> { + self.add_instruction_internal(instruction, Some(description)) + } + + fn add_instruction_internal( + &mut self, + instruction: Instruction, + description: Option, + ) -> anyhow::Result<&mut Self> { self.check_signers(&instruction)?; let current = self.current_instruction_pack.get_mut().unwrap(); - current.push(instruction); - let transaction_candidate = - Transaction::new_with_payer(¤t.to_vec(), Some(&self.fee_payer)); + current.push((instruction, description)); + let transaction_candidate = Transaction::new_with_payer( + ¤t.iter().cloned().unzip::<_, _, Vec<_>, Vec<_>>().0, + Some(&self.fee_payer), + ); let tx_size_candidate = bincode::serialize(&transaction_candidate).unwrap().len(); if self.max_transaction_size > 0 && tx_size_candidate > self.max_transaction_size { // Transaction is too big to add new instruction, remove the last one @@ -169,11 +201,11 @@ impl TransactionBuilder { return None; } if !self.instruction_packs.is_empty() { - let instructions: Vec = - self.instruction_packs.remove(0).into_iter().collect(); + let (instructions, descriptions): (Vec, Vec>) = + self.instruction_packs.remove(0).into_iter().unzip(); let transaction = Transaction::new_with_payer(&instructions, Some(&self.fee_payer)); Some( - PreparedTransaction::new(transaction, &self.signature_builder) + PreparedTransaction::new(transaction, &self.signature_builder, descriptions) .expect("Signature keys must be checked when instruction added"), ) } else { @@ -201,19 +233,26 @@ impl TransactionBuilder { return None; } - let transaction = if self.max_transaction_size == 0 { - let instructions: Vec = - self.instruction_packs.drain(..).flatten().collect(); - Transaction::new_with_payer(&instructions, Some(&self.fee_payer)) + let (transaction, descriptions) = if self.max_transaction_size == 0 { + let (instructions, descriptions): (Vec, Vec>) = + self.instruction_packs.drain(..).flatten().unzip(); + ( + Transaction::new_with_payer(&instructions, Some(&self.fee_payer)), + descriptions, + ) } else { // One pack must fit transaction anyway - let mut instructions: Vec = - self.instruction_packs.remove(0).into_iter().collect(); + let (mut instructions, mut descriptions): (Vec, Vec>) = + self.instruction_packs.remove(0).into_iter().unzip(); let mut transaction = Transaction::new_with_payer(&instructions, Some(&self.fee_payer)); while let Some(next_pack) = self.instruction_packs.first() { - let next_instructions: Vec = next_pack.to_vec(); + let (next_instructions, next_descriptions): ( + Vec, + Vec>, + ) = next_pack.iter().cloned().unzip(); // Try to add next pack instructions.extend(next_instructions.into_iter()); + descriptions.extend(next_descriptions.into_iter()); let transaction_candidate = Transaction::new_with_payer(&instructions, Some(&self.fee_payer)); @@ -229,10 +268,10 @@ impl TransactionBuilder { break; } } - transaction + (transaction, descriptions) }; Some( - PreparedTransaction::new(transaction, &self.signature_builder) + PreparedTransaction::new(transaction, &self.signature_builder, descriptions) .expect("Signature keys must be checked when instruction added"), ) } @@ -261,10 +300,15 @@ impl TransactionBuilder { } pub fn instructions(&self) -> Vec { - let mut instructions: Vec = - self.instruction_packs.iter().flatten().cloned().collect(); + let (mut instructions, _): (Vec, Vec<_>) = + self.instruction_packs.iter().flatten().cloned().unzip(); if let Some(current_instructions) = self.current_instruction_pack.get() { - instructions.extend(current_instructions.iter().cloned()) + instructions.extend( + current_instructions + .iter() + .map(|(instr, _)| instr.clone()) + .collect::>(), + ) } instructions }