use serde::{Deserialize, Serialize};
use crate::{
error::{Result, RialoError},
keyring::Keyring,
rpc::types::Pubkey,
transaction::instruction::{self, Instruction},
};
#[derive(Debug, Serialize, Deserialize)]
pub struct Transaction {
pub signatures: Vec<Vec<u8>>,
pub message: Vec<u8>,
}
impl Transaction {
pub fn new(message: Vec<u8>, num_signatures: usize) -> Self {
Self {
signatures: vec![vec![0u8; 64]; num_signatures],
message,
}
}
pub fn serialize_wired(&self) -> Result<Vec<u8>> {
serialize_transaction(self)
}
pub fn deserialize_wired(data: &[u8]) -> Result<Self> {
deserialize_transaction(data)
}
pub fn add_signature(&mut self, index: usize, signature: Vec<u8>) -> Result<()> {
if signature.len() != 64 {
return Err(RialoError::Transaction(
"Signature must be 64 bytes long".to_string(),
));
}
if index >= self.signatures.len() {
return Err(RialoError::Transaction(format!(
"Signature index {} out of bounds (max: {})",
index,
self.signatures.len() - 1
)));
}
self.signatures[index] = signature;
Ok(())
}
pub fn add_signature_for_pubkey(&mut self, pubkey: &Pubkey, signature: Vec<u8>) -> Result<()> {
let message_header = parse_message_header(&self.message)?;
let account_keys = parse_account_keys(&self.message, &message_header)?;
let signer_index = account_keys
.iter()
.take(message_header.num_required_signatures as usize)
.position(|key| key == pubkey)
.ok_or_else(|| {
RialoError::Transaction(format!("Public key {} not found as a signer", pubkey))
})?;
self.add_signature(signer_index, signature)
}
pub fn clear_signatures(&mut self) {
for sig in &mut self.signatures {
sig.fill(0);
}
}
pub fn signature_count(&self) -> usize {
self.signatures.len()
}
pub fn get_signature(&self, index: usize) -> Option<&Vec<u8>> {
self.signatures.get(index)
}
pub fn is_fully_signed(&self) -> bool {
self.signatures
.iter()
.all(|sig| sig.iter().any(|&b| b != 0))
}
pub fn message(&self) -> &[u8] {
&self.message
}
pub fn deserialize_message(&self) -> Result<DeserializedMessage> {
deserialize_message(&self.message)
}
}
pub struct TransactionBuilder {
fee_payer: Pubkey,
valid_from: i64,
instructions: Vec<Instruction>,
account_index: Option<usize>,
}
impl TransactionBuilder {
pub fn new(fee_payer: Pubkey, valid_from: i64) -> Self {
Self {
fee_payer,
valid_from,
instructions: Vec::new(),
account_index: None,
}
}
pub fn add_instruction(&mut self, instruction: Instruction) -> &mut Self {
self.instructions.push(instruction);
self
}
pub fn add_transfer_instruction(
&mut self,
from: &Pubkey,
to: &Pubkey,
kelvin: u64,
) -> &mut Self {
let instruction = instruction::transfer_instruction(from, to, kelvin);
self.add_instruction(instruction)
}
pub fn with_account_index(&mut self, account_index: usize) -> &mut Self {
self.account_index = Some(account_index);
self
}
pub fn build(&self) -> Result<Vec<u8>> {
let mut account_metas = Vec::new();
let mut account_indices = std::collections::HashMap::new();
account_metas.push(AccountMeta {
pubkey: self.fee_payer,
is_signer: true,
is_writable: true,
});
account_indices.insert(self.fee_payer, 0);
for instruction in &self.instructions {
for account_meta in &instruction.accounts {
if let std::collections::hash_map::Entry::Vacant(e) =
account_indices.entry(account_meta.pubkey)
{
e.insert(account_metas.len());
account_metas.push(AccountMeta {
pubkey: account_meta.pubkey,
is_signer: account_meta.is_signer,
is_writable: account_meta.is_writable,
});
} else {
let index = *account_indices.get(&account_meta.pubkey).unwrap();
let existing = &mut account_metas[index];
existing.is_signer |= account_meta.is_signer;
existing.is_writable |= account_meta.is_writable;
}
}
if let std::collections::hash_map::Entry::Vacant(e) =
account_indices.entry(instruction.program_id)
{
e.insert(account_metas.len());
account_metas.push(AccountMeta {
pubkey: instruction.program_id,
is_signer: false,
is_writable: false,
});
}
}
let mut pubkey_to_meta = std::collections::HashMap::new();
for meta in &account_metas {
pubkey_to_meta.insert(meta.pubkey, meta.clone());
}
let mut keys_except_payer: Vec<Pubkey> = account_metas
.iter()
.skip(1)
.map(|meta| meta.pubkey)
.collect();
keys_except_payer.sort_by(|a, b| {
let a_meta = pubkey_to_meta.get(a).unwrap();
let b_meta = pubkey_to_meta.get(b).unwrap();
match (
a_meta.is_signer,
a_meta.is_writable,
b_meta.is_signer,
b_meta.is_writable,
) {
(true, true, true, false) => std::cmp::Ordering::Less,
(true, false, true, true) => std::cmp::Ordering::Greater,
(true, _, false, _) => std::cmp::Ordering::Less,
(false, _, true, _) => std::cmp::Ordering::Greater,
(true, true, true, true) | (false, true, false, true) => a.cmp(b),
(true, false, true, false) | (false, false, false, false) => a.cmp(b),
(false, true, false, false) => std::cmp::Ordering::Less,
(false, false, false, true) => std::cmp::Ordering::Greater,
}
});
let mut sorted_keys = vec![self.fee_payer];
sorted_keys.extend(keys_except_payer);
account_indices.clear();
account_metas.clear();
for (i, key) in sorted_keys.iter().enumerate() {
let meta = pubkey_to_meta.get(key).unwrap();
account_metas.push(meta.clone());
account_indices.insert(*key, i);
}
let compiled_instructions = self
.instructions
.iter()
.map(|ix| {
let program_idx = *account_indices.get(&ix.program_id).ok_or_else(|| {
RialoError::Transaction(format!(
"Program ID not found in accounts: {}",
ix.program_id
))
})?;
let account_indices = ix
.accounts
.iter()
.map(|meta| {
account_indices.get(&meta.pubkey).copied().ok_or_else(|| {
RialoError::Transaction(format!("Account not found: {}", meta.pubkey))
})
})
.collect::<Result<Vec<_>>>()?;
Ok(CompiledInstruction {
program_idx: program_idx as u8,
accounts: account_indices.into_iter().map(|idx| idx as u8).collect(),
data: ix.data.clone(),
})
})
.collect::<Result<Vec<_>>>()?;
let num_required_signatures = account_metas.iter().filter(|a| a.is_signer).count() as u8;
let num_readonly_signed_accounts = account_metas
.iter()
.filter(|a| a.is_signer && !a.is_writable)
.count() as u8;
let num_readonly_unsigned_accounts = account_metas
.iter()
.filter(|a| !a.is_signer && !a.is_writable)
.count() as u8;
let message = Message {
header: MessageHeader {
num_required_signatures,
num_readonly_signed_accounts,
num_readonly_unsigned_accounts,
},
account_keys: account_metas.iter().map(|a| a.pubkey).collect(),
valid_from: self.valid_from,
instructions: compiled_instructions,
};
let message_bytes = serialize_message(&message)?;
Ok(message_bytes)
}
pub fn sign(&self, keyring: &Keyring) -> Result<Vec<u8>> {
let message_bytes = self.build()?;
let signature = match self.account_index {
Some(index) => keyring.sign_with_keypair(&message_bytes, index as u32)?,
None => keyring.sign(&message_bytes),
};
let transaction = Transaction {
signatures: vec![signature.to_bytes().to_vec()],
message: message_bytes.clone(),
};
let transaction_bytes = serialize_transaction(&transaction)?;
Ok(transaction_bytes)
}
pub fn sign_with_keypair(&self, keyring: &Keyring, keypair_index: u32) -> Result<Vec<u8>> {
let message_bytes = self.build()?;
let signature = keyring.sign_with_keypair(&message_bytes, keypair_index)?;
let transaction = Transaction {
signatures: vec![signature.to_bytes().to_vec()],
message: message_bytes.clone(),
};
let transaction_bytes = serialize_transaction(&transaction)?;
Ok(transaction_bytes)
}
#[deprecated(since = "0.2.0", note = "Use sign_with_keypair instead")]
pub fn sign_with_account(&self, keyring: &Keyring, account_index: u32) -> Result<Vec<u8>> {
self.sign_with_keypair(keyring, account_index)
}
pub fn build_transaction(&self) -> Result<Transaction> {
let message_bytes = self.build()?;
let message_header = parse_message_header(&message_bytes)?;
let num_signatures = message_header.num_required_signatures as usize;
Ok(Transaction::new(message_bytes, num_signatures))
}
}
#[derive(Clone, Debug)]
struct AccountMeta {
pubkey: Pubkey,
is_signer: bool,
is_writable: bool,
}
#[derive(Debug, Clone)]
pub struct MessageHeader {
pub num_required_signatures: u8,
pub num_readonly_signed_accounts: u8,
pub num_readonly_unsigned_accounts: u8,
}
#[derive(Debug, Clone)]
pub struct DeserializedMessage {
pub header: MessageHeader,
pub account_keys: Vec<Pubkey>,
pub instructions: Vec<DeserializedInstruction>,
}
#[derive(Debug, Clone)]
pub struct DeserializedInstruction {
pub program_id: Pubkey,
pub accounts: Vec<Pubkey>,
pub data: Vec<u8>,
}
#[derive(Debug, Clone)]
struct CompiledInstruction {
program_idx: u8,
accounts: Vec<u8>,
data: Vec<u8>,
}
#[derive(Debug)]
struct Message {
header: MessageHeader,
account_keys: Vec<Pubkey>,
valid_from: i64,
instructions: Vec<CompiledInstruction>,
}
fn serialize_message(message: &Message) -> Result<Vec<u8>> {
let mut buffer = Vec::new();
buffer.push(message.header.num_required_signatures);
buffer.push(message.header.num_readonly_signed_accounts);
buffer.push(message.header.num_readonly_unsigned_accounts);
serialize_compact_array(&mut buffer, &message.account_keys, |buf, key: &Pubkey| {
buf.extend_from_slice(key.as_ref());
Ok(())
})?;
buffer.extend_from_slice(&message.valid_from.to_le_bytes());
serialize_compact_array(&mut buffer, &message.instructions, |buf, instruction| {
buf.push(instruction.program_idx);
serialize_compact_array(buf, &instruction.accounts, |b, &idx| {
b.push(idx);
Ok(())
})?;
serialize_compact_array(buf, &instruction.data, |b, &byte| {
b.push(byte);
Ok(())
})?;
Ok(())
})?;
Ok(buffer)
}
fn serialize_transaction(transaction: &Transaction) -> Result<Vec<u8>> {
let mut buffer = Vec::new();
serialize_compact_array(&mut buffer, &transaction.signatures, |buf, sig| {
if sig.len() != 64 {
return Err(RialoError::Transaction(
"Invalid signature length".to_string(),
));
}
buf.extend_from_slice(sig);
Ok(())
})?;
buffer.extend_from_slice(&transaction.message);
Ok(buffer)
}
fn serialize_compact_array<T, F>(
buffer: &mut Vec<u8>,
items: &[T],
mut serialize_item: F,
) -> Result<()>
where
F: FnMut(&mut Vec<u8>, &T) -> Result<()>,
{
let len = items.len();
if len > 0x7FFF {
return Err(RialoError::Transaction(
"Array too large (max 32767 items)".to_string(),
));
}
if len < 128 {
buffer.push(len as u8);
} else {
let lower_seven_bits = (len & 0x7F) as u8;
let upper_bits = ((len >> 7) & 0x7F) as u8;
buffer.push(lower_seven_bits | 0x80); buffer.push(upper_bits);
}
for item in items {
serialize_item(buffer, item)?;
}
Ok(())
}
fn deserialize_transaction(data: &[u8]) -> Result<Transaction> {
if data.is_empty() {
return Err(RialoError::Transaction(
"Empty transaction data".to_string(),
));
}
let mut cursor = 0;
let (signatures, new_cursor) = deserialize_compact_array(data, cursor, |data, cursor| {
if data.len() < cursor + 64 {
return Err(RialoError::Transaction(
"Insufficient data for signature".to_string(),
));
}
let signature = data[cursor..cursor + 64].to_vec();
Ok((signature, cursor + 64))
})?;
cursor = new_cursor;
if cursor >= data.len() {
return Err(RialoError::Transaction("No message data found".to_string()));
}
let message = data[cursor..].to_vec();
Ok(Transaction {
signatures,
message,
})
}
fn deserialize_compact_array<T, F>(
data: &[u8],
mut cursor: usize,
mut deserialize_item: F,
) -> Result<(Vec<T>, usize)>
where
F: FnMut(&[u8], usize) -> Result<(T, usize)>,
{
if cursor >= data.len() {
return Err(RialoError::Transaction(
"Insufficient data for array length".to_string(),
));
}
let first_byte = data[cursor];
cursor += 1;
let len = if first_byte & 0x80 == 0 {
first_byte as usize
} else {
if cursor >= data.len() {
return Err(RialoError::Transaction(
"Insufficient data for array length continuation".to_string(),
));
}
let second_byte = data[cursor];
cursor += 1;
((first_byte & 0x7F) as usize) | ((second_byte as usize) << 7)
};
let mut items = Vec::with_capacity(len);
for _ in 0..len {
let (item, new_cursor) = deserialize_item(data, cursor)?;
items.push(item);
cursor = new_cursor;
}
Ok((items, cursor))
}
pub fn parse_message_header(message: &[u8]) -> Result<MessageHeader> {
if message.len() < 3 {
return Err(RialoError::Transaction(
"Message too short for header".to_string(),
));
}
Ok(MessageHeader {
num_required_signatures: message[0],
num_readonly_signed_accounts: message[1],
num_readonly_unsigned_accounts: message[2],
})
}
pub fn parse_account_keys(message: &[u8], header: &MessageHeader) -> Result<Vec<Pubkey>> {
let cursor = 3;
let (account_keys, _) = deserialize_compact_array(message, cursor, |data, cursor| {
if data.len() < cursor + 32 {
return Err(RialoError::Transaction(
"Insufficient data for pubkey".to_string(),
));
}
let mut pubkey_bytes = [0u8; 32];
pubkey_bytes.copy_from_slice(&data[cursor..cursor + 32]);
Ok((Pubkey::new_from_array(pubkey_bytes), cursor + 32))
})?;
let expected_signers = header.num_required_signatures as usize;
if account_keys.len() < expected_signers {
return Err(RialoError::Transaction(format!(
"Not enough account keys for signers: expected {}, got {}",
expected_signers,
account_keys.len()
)));
}
Ok(account_keys)
}
pub fn deserialize_message(data: &[u8]) -> Result<DeserializedMessage> {
if data.len() < 3 {
return Err(RialoError::Transaction(
"Message too short for header".to_string(),
));
}
let header = parse_message_header(data)?;
let mut cursor = 3;
let (account_keys, new_cursor) = deserialize_compact_array(data, cursor, |data, cursor| {
if data.len() < cursor + 32 {
return Err(RialoError::Transaction(
"Insufficient data for pubkey".to_string(),
));
}
let mut pubkey_bytes = [0u8; 32];
pubkey_bytes.copy_from_slice(&data[cursor..cursor + 32]);
Ok((Pubkey::new_from_array(pubkey_bytes), cursor + 32))
})?;
cursor = new_cursor;
if data.len() < cursor + 8 {
return Err(RialoError::Transaction(
"Insufficient data for valid_from".to_string(),
));
}
let mut valid_from_bytes = [0u8; 8];
valid_from_bytes.copy_from_slice(&data[cursor..cursor + 8]);
let _valid_from = i64::from_le_bytes(valid_from_bytes);
cursor += 8;
let (compiled_instructions, _) = deserialize_compact_array(data, cursor, |data, cursor| {
if data.len() < cursor + 1 {
return Err(RialoError::Transaction(
"Insufficient data for program index".to_string(),
));
}
let program_idx = data[cursor];
let mut cursor = cursor + 1;
let (accounts, new_cursor) = deserialize_compact_array(data, cursor, |data, cursor| {
if data.len() < cursor + 1 {
return Err(RialoError::Transaction(
"Insufficient data for account index".to_string(),
));
}
Ok((data[cursor], cursor + 1))
})?;
cursor = new_cursor;
let (data_bytes, new_cursor) = deserialize_compact_array(data, cursor, |data, cursor| {
if data.len() < cursor + 1 {
return Err(RialoError::Transaction(
"Insufficient data for instruction data".to_string(),
));
}
Ok((data[cursor], cursor + 1))
})?;
cursor = new_cursor;
Ok((
CompiledInstruction {
program_idx,
accounts,
data: data_bytes,
},
cursor,
))
})?;
let instructions = compiled_instructions
.into_iter()
.map(|compiled| {
let program_id = *account_keys
.get(compiled.program_idx as usize)
.ok_or_else(|| {
RialoError::Transaction(format!(
"Invalid program index: {}",
compiled.program_idx
))
})?;
let accounts = compiled
.accounts
.iter()
.map(|&idx| {
account_keys.get(idx as usize).copied().ok_or_else(|| {
RialoError::Transaction(format!("Invalid account index: {}", idx))
})
})
.collect::<Result<Vec<_>>>()?;
Ok(DeserializedInstruction {
program_id,
accounts,
data: compiled.data,
})
})
.collect::<Result<Vec<_>>>()?;
Ok(DeserializedMessage {
header,
account_keys,
instructions,
})
}