use std::{borrow::Cow, collections::BTreeMap, ops::Deref};
use smallvec::SmallVec;
use solana_sdk::{
hash::Hash,
instruction::Instruction,
message::{v0, VersionedMessage},
pubkey::Pubkey,
signature::NullSigner,
signer::Signer,
transaction::VersionedTransaction,
};
use crate::{
address_lookup_table::AddressLookupTables, compute_budget::ComputeBudget,
signer::BoxClonableSigner, transaction_group::TransactionGroupOptions,
};
const ATOMIC_SIZE: usize = 3;
const PARALLEL_SIZE: usize = 2;
pub trait IntoAtomicGroup {
type Hint;
fn into_atomic_group(self, hint: &Self::Hint) -> crate::Result<AtomicGroup>;
}
#[derive(Debug, Clone, Default)]
pub struct GetInstructionsOptions {
pub without_compute_budget: bool,
pub compute_unit_price_micro_lamports: Option<u64>,
pub memo: Option<String>,
}
#[derive(Debug, Clone)]
pub struct AtomicGroup {
payer: Pubkey,
signers: BTreeMap<Pubkey, NullSigner>,
owned_signers: BTreeMap<Pubkey, BoxClonableSigner<'static>>,
instructions: SmallVec<[Instruction; ATOMIC_SIZE]>,
compute_budget: ComputeBudget,
}
impl AtomicGroup {
pub fn with_instructions(
payer: &Pubkey,
instructions: impl IntoIterator<Item = Instruction>,
) -> Self {
Self {
payer: *payer,
signers: BTreeMap::from([(*payer, NullSigner::new(payer))]),
owned_signers: Default::default(),
instructions: SmallVec::from_iter(instructions),
compute_budget: Default::default(),
}
}
pub fn new(payer: &Pubkey) -> Self {
Self::with_instructions(payer, None)
}
pub fn add(&mut self, instruction: Instruction) -> &mut Self {
self.instructions.push(instruction);
self
}
pub fn add_signer(&mut self, signer: &Pubkey) -> &mut Self {
self.signers.insert(*signer, NullSigner::new(signer));
self
}
pub fn add_owned_signer(&mut self, signer: impl Signer + Clone + 'static) -> &mut Self {
self.owned_signers
.insert(signer.pubkey(), BoxClonableSigner::new(signer));
self
}
pub fn compute_budget(&self) -> &ComputeBudget {
&self.compute_budget
}
pub fn compute_budget_mut(&mut self) -> &mut ComputeBudget {
&mut self.compute_budget
}
pub fn payer(&self) -> &Pubkey {
&self.payer
}
pub fn external_signers(&self) -> impl Iterator<Item = &Pubkey> + '_ {
self.signers.keys()
}
fn compute_budget_instructions(
&self,
compute_unit_price_micro_lamports: Option<u64>,
) -> Vec<Instruction> {
self.compute_budget
.compute_budget_instructions(compute_unit_price_micro_lamports)
}
pub fn instructions_with_options(
&self,
options: GetInstructionsOptions,
) -> impl Iterator<Item = Cow<'_, Instruction>> {
let compute_budget_instructions = if options.without_compute_budget {
Vec::default()
} else {
self.compute_budget_instructions(options.compute_unit_price_micro_lamports)
};
let memo_instruction = options
.memo
.as_ref()
.map(|s| spl_memo::build_memo(s.as_bytes(), &[&self.payer]));
compute_budget_instructions
.into_iter()
.chain(memo_instruction)
.map(Cow::Owned)
.chain(self.instructions.iter().map(Cow::Borrowed))
}
pub fn transaction_size(
&self,
is_versioned_transaction: bool,
luts: Option<&AddressLookupTables>,
options: GetInstructionsOptions,
) -> usize {
let addresses = luts.as_ref().map(|luts| luts.addresses());
crate::utils::transaction_size(
self.payer,
&self.instructions_with_options(options).collect::<Vec<_>>(),
is_versioned_transaction,
addresses.as_ref(),
luts.as_ref().map(|luts| luts.len()).unwrap_or_default(),
)
}
pub fn transaction_size_after_merge(
&self,
other: &Self,
is_versioned_transaction: bool,
luts: Option<&AddressLookupTables>,
options: GetInstructionsOptions,
) -> usize {
let addresses = luts.as_ref().map(|luts| luts.addresses());
crate::utils::transaction_size(
self.payer,
&self
.instructions_with_options(options)
.chain(other.instructions_with_options(GetInstructionsOptions {
without_compute_budget: true,
..Default::default()
}))
.collect::<Vec<_>>(),
is_versioned_transaction,
addresses.as_ref(),
luts.as_ref().map(|luts| luts.len()).unwrap_or_default(),
)
}
pub fn merge(&mut self, mut other: Self) -> &mut Self {
self.signers.append(&mut other.signers);
self.owned_signers.append(&mut other.owned_signers);
self.instructions.extend(other.instructions);
self.compute_budget += other.compute_budget;
self
}
fn v0_message_with_blockhash_and_options(
&self,
recent_blockhash: Hash,
options: GetInstructionsOptions,
luts: Option<&AddressLookupTables>,
) -> crate::Result<v0::Message> {
let instructions = self
.instructions_with_options(options)
.map(|ix| (*ix).clone())
.collect::<Vec<_>>();
let luts = luts
.map(|t| t.accounts().collect::<Vec<_>>())
.unwrap_or_default();
Ok(v0::Message::try_compile(
self.payer(),
&instructions,
&luts,
recent_blockhash,
)?)
}
pub fn message_with_blockhash_and_options(
&self,
recent_blockhash: Hash,
options: GetInstructionsOptions,
luts: Option<&AddressLookupTables>,
) -> crate::Result<VersionedMessage> {
Ok(VersionedMessage::V0(
self.v0_message_with_blockhash_and_options(recent_blockhash, options, luts)?,
))
}
pub fn partially_signed_transaction_with_blockhash_and_options(
&self,
recent_blockhash: Hash,
options: GetInstructionsOptions,
luts: Option<&AddressLookupTables>,
) -> crate::Result<VersionedTransaction> {
let message = self.message_with_blockhash_and_options(recent_blockhash, options, luts)?;
let signers = self
.signers
.values()
.map(|s| s as &dyn Signer)
.chain(self.owned_signers.values().map(|s| s as &dyn Signer))
.collect::<Vec<_>>();
Ok(VersionedTransaction::try_new(message, &signers)?)
}
}
impl Extend<Instruction> for AtomicGroup {
fn extend<T: IntoIterator<Item = Instruction>>(&mut self, iter: T) {
self.instructions.extend(iter);
}
}
impl Deref for AtomicGroup {
type Target = [Instruction];
fn deref(&self) -> &Self::Target {
self.instructions.deref()
}
}
#[derive(Debug, Clone, Default)]
pub struct ParallelGroup(SmallVec<[AtomicGroup; PARALLEL_SIZE]>);
impl ParallelGroup {
pub fn add(&mut self, group: AtomicGroup) -> &mut Self {
self.0.push(group);
self
}
pub(crate) fn optimize(
&mut self,
options: &TransactionGroupOptions,
luts: &AddressLookupTables,
allow_payer_change: bool,
) -> &mut Self {
if options.optimize(&mut self.0, luts, allow_payer_change) {
self.0 = self.0.drain(..).filter(|group| !group.is_empty()).collect();
}
self
}
pub(crate) fn single(&self) -> Option<&AtomicGroup> {
if self.0.len() == 1 {
Some(&self.0[0])
} else {
None
}
}
pub(crate) fn single_mut(&mut self) -> Option<&mut AtomicGroup> {
if self.0.len() == 1 {
Some(&mut self.0[0])
} else {
None
}
}
pub(crate) fn into_single(mut self) -> Option<AtomicGroup> {
if self.0.len() == 1 {
Some(self.0.remove(0))
} else {
None
}
}
}
impl From<AtomicGroup> for ParallelGroup {
fn from(value: AtomicGroup) -> Self {
let mut this = Self::default();
this.add(value);
this
}
}
impl FromIterator<AtomicGroup> for ParallelGroup {
fn from_iter<T: IntoIterator<Item = AtomicGroup>>(iter: T) -> Self {
Self(FromIterator::from_iter(iter))
}
}
impl Deref for ParallelGroup {
type Target = [AtomicGroup];
fn deref(&self) -> &Self::Target {
self.0.deref()
}
}