Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible to add description to instruction builder instructions #3

Merged
merged 1 commit into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions libs/solana-transaction-builder-executor/src/builder_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -66,10 +66,10 @@ async fn get_latest_blockhash(url: String) -> anyhow::Result<Hash> {

pub async fn execute_transactions_in_sequence(
transaction_executor: Arc<TransactionExecutor>,
async_transaction_builders: Vec<TransactionBuilderExecutionData>,
execution_data: Vec<TransactionBuilderExecutionData>,
) -> 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})");
Expand Down Expand Up @@ -98,16 +98,16 @@ pub async fn execute_transactions_in_sequence(

pub async fn execute_transactions_in_parallel(
transaction_executor: Arc<TransactionExecutor>,
async_transaction_builders: Vec<TransactionBuilderExecutionData>,
execution_data: Vec<TransactionBuilderExecutionData>,
parallel_execution_limit: Option<usize>,
) -> 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)| {
Expand Down Expand Up @@ -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()
}
26 changes: 26 additions & 0 deletions libs/solana-transaction-builder/src/prepared_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub trait SignedTransaction {
pub struct PreparedTransaction {
pub transaction: Transaction,
pub signers: Vec<Rc<Keypair>>,
pub instruction_descriptions: Vec<Option<String>>,
}

impl SignedTransaction for PreparedTransaction {
Expand All @@ -42,11 +43,36 @@ impl PreparedTransaction {
pub fn new(
transaction: Transaction,
signature_builder: &SignatureBuilder,
instruction_descriptions: Vec<Option<String>>,
) -> Result<Self, Pubkey> {
let signers = signature_builder.signers_for_transaction(&transaction)?;
Ok(Self {
transaction,
signers,
instruction_descriptions,
})
}

pub fn single_description(&self) -> Option<String> {
let mut descriptions = self
.instruction_descriptions
.iter()
.map(|desc| desc.clone())
.collect::<Vec<_>>();
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::<Vec<String>>()
.join("\n");
if descriptions.is_empty() {
None
} else {
Some(descriptions)
}
}
}
84 changes: 64 additions & 20 deletions libs/solana-transaction-builder/src/transaction_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<Instruction>>,
current_instruction_pack: OnceCell<Vec<Instruction>>,
// instruction pack contains a list of instruction with optional description to them
instruction_packs: Vec<Vec<(Instruction, Option<String>)>>,
current_instruction_pack: OnceCell<Vec<(Instruction, Option<String>)>>,
max_transaction_size: usize,
}

Expand Down Expand Up @@ -137,13 +138,44 @@ impl TransactionBuilder {
Ok(self)
}

pub fn add_instructions_with_description<I>(
&mut self,
instructions_with_description: I,
) -> anyhow::Result<&mut Self>
where
I: IntoIterator<Item = (Instruction, String)>,
{
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<String>,
) -> 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(&current.to_vec(), Some(&self.fee_payer));
current.push((instruction, description));
let transaction_candidate = Transaction::new_with_payer(
&current.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
Expand All @@ -169,11 +201,11 @@ impl TransactionBuilder {
return None;
}
if !self.instruction_packs.is_empty() {
let instructions: Vec<Instruction> =
self.instruction_packs.remove(0).into_iter().collect();
let (instructions, descriptions): (Vec<Instruction>, Vec<Option<String>>) =
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 {
Expand Down Expand Up @@ -201,19 +233,26 @@ impl TransactionBuilder {
return None;
}

let transaction = if self.max_transaction_size == 0 {
let instructions: Vec<Instruction> =
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<Instruction>, Vec<Option<String>>) =
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<Instruction> =
self.instruction_packs.remove(0).into_iter().collect();
let (mut instructions, mut descriptions): (Vec<Instruction>, Vec<Option<String>>) =
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<Instruction> = next_pack.to_vec();
let (next_instructions, next_descriptions): (
Vec<Instruction>,
Vec<Option<String>>,
) = 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));

Expand All @@ -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"),
)
}
Expand Down Expand Up @@ -261,10 +300,15 @@ impl TransactionBuilder {
}

pub fn instructions(&self) -> Vec<Instruction> {
let mut instructions: Vec<Instruction> =
self.instruction_packs.iter().flatten().cloned().collect();
let (mut instructions, _): (Vec<Instruction>, 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::<Vec<Instruction>>(),
)
}
instructions
}
Expand Down
Loading