use solana_sdk::{
address_lookup_table::AddressLookupTableAccount,
compute_budget::ComputeBudgetInstruction,
hash::Hash,
instruction::Instruction,
message::{v0, VersionedMessage},
signature::{Keypair, NullSigner},
signer::Signer,
transaction::VersionedTransaction,
};
use crate::error::Error;
pub const MAX_TRANSACTION_SIZE: usize = 1232;
#[derive(Debug)]
pub struct PackedTransaction {
pub instructions: Vec<Instruction>,
pub task_ids: Vec<usize>,
}
impl Default for PackedTransaction {
fn default() -> Self {
Self {
instructions: vec![
ComputeBudgetInstruction::set_compute_unit_limit(1000000),
ComputeBudgetInstruction::set_compute_unit_price(1),
],
task_ids: Default::default(),
}
}
}
impl PackedTransaction {
pub fn push(&mut self, instructions: &[Instruction], index: usize) {
self.instructions.extend_from_slice(instructions);
self.task_ids.push(index);
}
pub fn is_empty(&self) -> bool {
self.task_ids.is_empty()
}
pub fn mk_transaction(
&self,
extra_ixs: &[Instruction],
lookup_tables: &[AddressLookupTableAccount],
payer: &Keypair,
) -> Result<VersionedTransaction, Error> {
let ixs = &[&self.instructions, extra_ixs].concat();
v0::Message::try_compile(&payer.pubkey(), ixs, lookup_tables, Hash::default())
.map_err(Error::from)
.map(VersionedMessage::V0)
.and_then(|message| {
VersionedTransaction::try_new(message, &[&NullSigner::new(&payer.pubkey())])
.map_err(|e| Error::SignerError(e.to_string()))
})
}
pub fn transaction_len(
&self,
extra_ixs: &[Instruction],
lookup_tables: &[AddressLookupTableAccount],
payer: &Keypair,
) -> Result<usize, Error> {
let tx = self.mk_transaction(extra_ixs, lookup_tables, payer)?;
bincode::serialize(&tx)
.map(|data| data.len())
.map_err(|e| Error::SerializationError(e.to_string()))
}
}
pub fn pack_instructions_into_transactions(
instructions: &[&[Instruction]],
payer: &Keypair,
lookup_tables: Option<Vec<AddressLookupTableAccount>>,
) -> Result<Vec<PackedTransaction>, Error> {
let mut transactions = Vec::new();
let mut curr_transaction = PackedTransaction::default();
let lookup_tables = lookup_tables.unwrap_or_default();
for (group_idx, group) in instructions.iter().enumerate() {
if curr_transaction.transaction_len(group, &lookup_tables, payer)? > MAX_TRANSACTION_SIZE
&& !curr_transaction.is_empty()
{
transactions.push(curr_transaction);
curr_transaction = PackedTransaction::default();
}
curr_transaction.push(group, group_idx);
if curr_transaction.transaction_len(&[], &lookup_tables, payer)? > MAX_TRANSACTION_SIZE {
return Err(Error::IxGroupTooLarge);
}
}
if !curr_transaction.is_empty() {
transactions.push(curr_transaction);
}
Ok(transactions)
}