use crate::error::Error;
use itertools::Itertools;
use solana_sdk::{
address_lookup_table::AddressLookupTableAccount,
compute_budget::ComputeBudgetInstruction,
hash::Hash,
instruction::Instruction,
message::{v0, VersionedMessage},
pubkey::Pubkey,
signature::NullSigner,
transaction::VersionedTransaction,
};
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],
signers: &[Pubkey],
) -> Result<VersionedTransaction, Error> {
let ixs = &[&self.instructions, extra_ixs].concat();
v0::Message::try_compile(
signers
.first()
.ok_or_else(|| Error::signer("missing payer"))?,
ixs,
lookup_tables,
Hash::default(),
)
.map_err(Error::from)
.map(VersionedMessage::V0)
.and_then(|message| {
VersionedTransaction::try_new(
message,
&signers.iter().map(NullSigner::new).collect_vec(),
)
.map_err(Error::signer)
})
}
pub fn transaction_len(
&self,
extra_ixs: &[Instruction],
lookup_tables: &[AddressLookupTableAccount],
) -> Result<usize, Error> {
let mut signers = self
.instructions
.iter()
.chain(extra_ixs.iter())
.flat_map(|ix| {
ix.accounts
.iter()
.filter_map(|a| a.is_signer.then_some(a.pubkey))
})
.unique()
.collect_vec();
if signers.is_empty() {
signers.push(Pubkey::default());
}
let tx = self.mk_transaction(extra_ixs, lookup_tables, &signers)?;
bincode::serialize(&tx)
.map(|data| data.len())
.map_err(Error::serialization)
}
}
pub fn pack_instructions_into_transactions(
instructions: &[&[Instruction]],
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)? > 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)? > MAX_TRANSACTION_SIZE {
return Err(Error::IxGroupTooLarge);
}
}
if !curr_transaction.is_empty() {
transactions.push(curr_transaction);
}
Ok(transactions)
}