use crate::base58::Pubkey;
use std::collections::BTreeMap;
pub type Blockhash = [u8; 32];
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct AccountMeta {
pub pubkey: Pubkey,
pub is_signer: bool,
pub is_writable: bool,
}
impl AccountMeta {
pub fn signer_writable(pubkey: Pubkey) -> Self { Self { pubkey, is_signer: true, is_writable: true } }
pub fn signer_readonly(pubkey: Pubkey) -> Self { Self { pubkey, is_signer: true, is_writable: false } }
pub fn writable(pubkey: Pubkey) -> Self { Self { pubkey, is_signer: false, is_writable: true } }
pub fn readonly(pubkey: Pubkey) -> Self { Self { pubkey, is_signer: false, is_writable: false } }
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Instruction {
pub program_id: Pubkey,
pub accounts: Vec<AccountMeta>,
pub data: Vec<u8>,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct MessageHeader {
pub num_required_signatures: u8,
pub num_readonly_signed_accounts: u8,
pub num_readonly_unsigned_accounts: u8,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct CompiledInstruction {
pub program_id_index: u8,
pub accounts: Vec<u8>,
pub data: Vec<u8>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct MessageAddressTableLookup {
pub account_key: Pubkey,
pub writable_indexes: Vec<u8>,
pub readonly_indexes: Vec<u8>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct MessageV0 {
pub header: MessageHeader,
pub account_keys: Vec<Pubkey>,
pub recent_blockhash: Blockhash,
pub instructions: Vec<CompiledInstruction>,
pub address_table_lookups: Vec<MessageAddressTableLookup>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct VersionedTransaction {
pub signatures: Vec<[u8; 64]>,
pub message: MessageV0,
}
#[derive(Default, Clone, Copy)]
struct KeyMeta {
is_signer: bool,
is_writable: bool,
}
pub fn build_unsigned(ixs: &[Instruction], payer: Pubkey, blockhash: Blockhash) -> VersionedTransaction {
let mut key_map: BTreeMap<Pubkey, KeyMeta> = BTreeMap::new();
for ix in ixs {
key_map.entry(ix.program_id).or_default();
for am in &ix.accounts {
let m = key_map.entry(am.pubkey).or_default();
m.is_signer |= am.is_signer;
m.is_writable |= am.is_writable;
}
}
{
let m = key_map.entry(payer).or_default();
m.is_signer = true;
m.is_writable = true;
}
key_map.remove(&payer);
let writable_signers: Vec<Pubkey> = std::iter::once(payer)
.chain(
key_map
.iter()
.filter(|(_, m)| m.is_signer && m.is_writable)
.map(|(k, _)| *k),
)
.collect();
let readonly_signers: Vec<Pubkey> = key_map
.iter()
.filter(|(_, m)| m.is_signer && !m.is_writable)
.map(|(k, _)| *k)
.collect();
let writable_non_signers: Vec<Pubkey> = key_map
.iter()
.filter(|(_, m)| !m.is_signer && m.is_writable)
.map(|(k, _)| *k)
.collect();
let readonly_non_signers: Vec<Pubkey> = key_map
.iter()
.filter(|(_, m)| !m.is_signer && !m.is_writable)
.map(|(k, _)| *k)
.collect();
let account_keys: Vec<Pubkey> = writable_signers
.iter()
.chain(readonly_signers.iter())
.chain(writable_non_signers.iter())
.chain(readonly_non_signers.iter())
.copied()
.collect();
let header = MessageHeader {
num_required_signatures: u8::try_from(writable_signers.len() + readonly_signers.len())
.expect("<=255 signers"),
num_readonly_signed_accounts: u8::try_from(readonly_signers.len()).expect("<=255 readonly signers"),
num_readonly_unsigned_accounts: u8::try_from(readonly_non_signers.len())
.expect("<=255 readonly non-signers"),
};
let mut index_map: BTreeMap<Pubkey, u8> = BTreeMap::new();
for (i, k) in account_keys.iter().enumerate() {
index_map.insert(*k, u8::try_from(i).expect("<=255 account keys"));
}
let instructions: Vec<CompiledInstruction> = ixs
.iter()
.map(|ix| {
let accounts: Vec<u8> = ix
.accounts
.iter()
.map(|am| *index_map.get(&am.pubkey).expect("account key present in account_keys"))
.collect();
CompiledInstruction {
program_id_index: *index_map.get(&ix.program_id).expect("program id present in account_keys"),
accounts,
data: ix.data.clone(),
}
})
.collect();
let message = MessageV0 {
header,
account_keys,
recent_blockhash: blockhash,
instructions,
address_table_lookups: Vec::new(),
};
VersionedTransaction { signatures: Vec::new(), message }
}
pub fn serialize(tx: &VersionedTransaction) -> Vec<u8> {
let mut out = Vec::new();
encode_short_vec(tx.signatures.len(), &mut out);
for sig in &tx.signatures {
out.extend_from_slice(sig);
}
encode_v0_message(&tx.message, &mut out);
out
}
pub fn serialize_message(msg: &MessageV0) -> Vec<u8> {
let mut out = Vec::new();
encode_v0_message(msg, &mut out);
out
}
fn encode_v0_message(msg: &MessageV0, out: &mut Vec<u8>) {
out.push(0x80); out.push(msg.header.num_required_signatures);
out.push(msg.header.num_readonly_signed_accounts);
out.push(msg.header.num_readonly_unsigned_accounts);
encode_short_vec(msg.account_keys.len(), out);
for k in &msg.account_keys {
out.extend_from_slice(k.as_bytes());
}
out.extend_from_slice(&msg.recent_blockhash);
encode_short_vec(msg.instructions.len(), out);
for ci in &msg.instructions {
out.push(ci.program_id_index);
encode_short_vec(ci.accounts.len(), out);
out.extend_from_slice(&ci.accounts);
encode_short_vec(ci.data.len(), out);
out.extend_from_slice(&ci.data);
}
encode_short_vec(msg.address_table_lookups.len(), out);
for alt in &msg.address_table_lookups {
out.extend_from_slice(alt.account_key.as_bytes());
encode_short_vec(alt.writable_indexes.len(), out);
out.extend_from_slice(&alt.writable_indexes);
encode_short_vec(alt.readonly_indexes.len(), out);
out.extend_from_slice(&alt.readonly_indexes);
}
}
fn encode_short_vec(len: usize, out: &mut Vec<u8>) {
let len = u16::try_from(len).expect("short-vec length fits in u16 (<=65535)");
if len < 0x80 {
out.push(len as u8);
} else if len < 0x4000 {
out.push(0x80 | (len & 0x7f) as u8);
out.push((len >> 7) as u8);
} else {
out.push(0x80 | (len & 0x7f) as u8);
out.push(0x80 | ((len >> 7) & 0x7f) as u8);
out.push((len >> 14) as u8);
}
}