pub mod instructions;
use base64::Engine;
use sha2::{Digest, Sha256};
use crate::error::{Error, Result};
use crate::pubkey::Pubkey;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AccountMeta {
pub pubkey: Pubkey,
pub is_signer: bool,
pub is_writable: bool,
}
impl AccountMeta {
pub const fn writable(pubkey: Pubkey) -> Self {
Self {
pubkey,
is_signer: false,
is_writable: true,
}
}
pub const fn readonly(pubkey: Pubkey) -> Self {
Self {
pubkey,
is_signer: false,
is_writable: false,
}
}
pub const fn writable_signer(pubkey: Pubkey) -> Self {
Self {
pubkey,
is_signer: true,
is_writable: true,
}
}
pub const fn readonly_signer(pubkey: Pubkey) -> Self {
Self {
pubkey,
is_signer: true,
is_writable: false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Instruction {
pub program_id: Pubkey,
pub accounts: Vec<AccountMeta>,
pub data: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Message {
pub num_required_signatures: u8,
pub num_readonly_signed: u8,
pub num_readonly_unsigned: u8,
pub account_keys: Vec<Pubkey>,
pub recent_blockhash: [u8; 32],
pub instructions: Vec<CompiledInstruction>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompiledInstruction {
pub program_id_index: u8,
pub accounts: Vec<u8>,
pub data: Vec<u8>,
}
pub const PACKET_DATA_SIZE: usize = 1232;
impl Message {
pub fn compile(
payer: &Pubkey,
instructions: &[Instruction],
recent_blockhash: [u8; 32],
) -> Result<Self> {
if instructions.is_empty() {
return Err(Error::Encode("no instructions".into()));
}
let mut keys: Vec<AccountMeta> = Vec::new();
let push = |meta: AccountMeta, keys: &mut Vec<AccountMeta>| {
if let Some(existing) = keys.iter_mut().find(|k| k.pubkey == meta.pubkey) {
existing.is_signer |= meta.is_signer;
existing.is_writable |= meta.is_writable;
} else {
keys.push(meta);
}
};
push(AccountMeta::writable_signer(*payer), &mut keys);
for ix in instructions {
for meta in &ix.accounts {
push(*meta, &mut keys);
}
}
for ix in instructions {
push(AccountMeta::readonly(ix.program_id), &mut keys);
}
let payer_meta = keys
.iter()
.find(|k| k.pubkey == *payer)
.copied()
.ok_or_else(|| Error::Encode("payer vanished during compilation".into()))?;
let rest: Vec<AccountMeta> = keys.into_iter().filter(|k| k.pubkey != *payer).collect();
let mut ordered = vec![payer_meta];
ordered.extend(rest.iter().filter(|k| k.is_signer && k.is_writable));
ordered.extend(rest.iter().filter(|k| k.is_signer && !k.is_writable));
ordered.extend(rest.iter().filter(|k| !k.is_signer && k.is_writable));
ordered.extend(rest.iter().filter(|k| !k.is_signer && !k.is_writable));
if ordered.len() > u8::MAX as usize {
return Err(Error::Encode("more than 255 accounts".into()));
}
let num_required_signatures = ordered.iter().filter(|k| k.is_signer).count() as u8;
let num_readonly_signed = ordered.iter().filter(|k| k.is_signer && !k.is_writable).count() as u8;
let num_readonly_unsigned =
ordered.iter().filter(|k| !k.is_signer && !k.is_writable).count() as u8;
let account_keys: Vec<Pubkey> = ordered.iter().map(|k| k.pubkey).collect();
let index_of = |pk: &Pubkey| -> Result<u8> {
account_keys
.iter()
.position(|k| k == pk)
.map(|i| i as u8)
.ok_or_else(|| Error::Encode(format!("account {} not in table", pk.abbreviated())))
};
let mut compiled = Vec::with_capacity(instructions.len());
for ix in instructions {
let mut accounts = Vec::with_capacity(ix.accounts.len());
for meta in &ix.accounts {
accounts.push(index_of(&meta.pubkey)?);
}
compiled.push(CompiledInstruction {
program_id_index: index_of(&ix.program_id)?,
accounts,
data: ix.data.clone(),
});
}
Ok(Message {
num_required_signatures,
num_readonly_signed,
num_readonly_unsigned,
account_keys,
recent_blockhash,
instructions: compiled,
})
}
pub fn serialize(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(256);
out.push(0x80);
out.push(self.num_required_signatures);
out.push(self.num_readonly_signed);
out.push(self.num_readonly_unsigned);
encode_len(&mut out, self.account_keys.len());
for key in &self.account_keys {
out.extend_from_slice(key.as_bytes());
}
out.extend_from_slice(&self.recent_blockhash);
encode_len(&mut out, self.instructions.len());
for ix in &self.instructions {
out.push(ix.program_id_index);
encode_len(&mut out, ix.accounts.len());
out.extend_from_slice(&ix.accounts);
encode_len(&mut out, ix.data.len());
out.extend_from_slice(&ix.data);
}
encode_len(&mut out, 0);
out
}
pub fn digest(&self) -> String {
let mut hasher = Sha256::new();
hasher.update(self.serialize());
let out: [u8; 32] = hasher.finalize().into();
out.iter().map(|b| format!("{b:02x}")).collect()
}
pub fn required_signers(&self) -> &[Pubkey] {
&self.account_keys[..self.num_required_signatures as usize]
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnsignedTransaction {
pub message: Message,
}
impl UnsignedTransaction {
pub fn new(message: Message) -> Self {
Self { message }
}
pub fn serialize(&self) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(512);
encode_len(&mut out, self.message.num_required_signatures as usize);
for _ in 0..self.message.num_required_signatures {
out.extend_from_slice(&[0u8; 64]);
}
out.extend_from_slice(&self.message.serialize());
if out.len() > PACKET_DATA_SIZE {
return Err(Error::Encode(format!(
"transaction is {} bytes, over the {PACKET_DATA_SIZE}-byte limit",
out.len()
)));
}
Ok(out)
}
pub fn to_base64(&self) -> Result<String> {
Ok(base64::engine::general_purpose::STANDARD.encode(self.serialize()?))
}
}
pub fn encode_len(out: &mut Vec<u8>, mut n: usize) {
loop {
let mut byte = (n & 0x7f) as u8;
n >>= 7;
if n == 0 {
out.push(byte);
break;
}
byte |= 0x80;
out.push(byte);
}
}
pub fn decode_len(input: &[u8]) -> Result<(usize, usize)> {
let mut value = 0usize;
for (i, byte) in input.iter().take(3).enumerate() {
value |= ((byte & 0x7f) as usize) << (i * 7);
if byte & 0x80 == 0 {
return Ok((value, i + 1));
}
}
Err(Error::Encode("short vec length prefix is too long".into()))
}
pub fn blockhash_from_base58(s: &str) -> Result<[u8; 32]> {
let mut out = [0u8; 32];
match bs58::decode(s).onto(&mut out) {
Ok(32) => Ok(out),
_ => Err(Error::InvalidArgument(format!(
"`{}` is not a 32-byte blockhash",
crate::shape::clip(s, 24)
))),
}
}