Files
solana/sdk/src/transaction_builder.rs

293 lines
10 KiB
Rust
Raw Normal View History

2019-02-28 04:48:44 -07:00
//! A library for composing transactions.
use crate::hash::Hash;
use crate::pubkey::Pubkey;
use crate::signature::KeypairUtil;
use crate::transaction::{Instruction, Transaction};
use itertools::Itertools;
pub type BuilderInstruction = Instruction<Pubkey, (Pubkey, bool)>;
fn position(keys: &[Pubkey], key: &Pubkey) -> u8 {
keys.iter().position(|k| k == key).unwrap() as u8
2019-02-28 04:48:44 -07:00
}
fn compile_instruction(
ix: &BuilderInstruction,
2019-02-28 04:48:44 -07:00
keys: &[Pubkey],
program_ids: &[Pubkey],
) -> Instruction<u8, u8> {
let accounts: Vec<_> = ix.accounts.iter().map(|(k, _)| position(keys, k)).collect();
2019-02-28 04:48:44 -07:00
Instruction {
program_ids_index: position(program_ids, &ix.program_ids_index),
2019-02-28 04:48:44 -07:00
userdata: ix.userdata.clone(),
accounts,
}
}
fn compile_instructions(
ixs: &[BuilderInstruction],
keys: &[Pubkey],
program_ids: &[Pubkey],
) -> Vec<Instruction<u8, u8>> {
ixs.iter()
.map(|ix| compile_instruction(ix, keys, program_ids))
.collect()
}
2019-02-28 04:48:44 -07:00
/// A utility for constructing transactions
#[derive(Default)]
pub struct TransactionBuilder {
fee: u64,
instructions: Vec<BuilderInstruction>,
}
impl TransactionBuilder {
/// Create a new TransactionBuilder.
pub fn new(fee: u64) -> Self {
Self {
fee,
instructions: vec![],
}
}
/// Add an instruction.
pub fn push(&mut self, instruction: BuilderInstruction) -> &mut Self {
self.instructions.push(instruction);
self
}
/// Return pubkeys referenced by all instructions, with the ones needing signatures first.
/// No duplicates and order is preserved.
fn keys(&self) -> (Vec<Pubkey>, Vec<Pubkey>) {
let mut keys_and_signed: Vec<_> = self
2019-02-28 04:48:44 -07:00
.instructions
.iter()
.flat_map(|ix| ix.accounts.iter())
.collect();
keys_and_signed.sort_by(|x, y| y.1.cmp(&x.1));
let mut signed_keys = vec![];
let mut unsigned_keys = vec![];
for (key, signed) in keys_and_signed.into_iter().unique_by(|x| x.0) {
if *signed {
signed_keys.push(*key);
} else {
unsigned_keys.push(*key);
}
}
(signed_keys, unsigned_keys)
2019-02-28 04:48:44 -07:00
}
/// Return program ids referenced by all instructions. No duplicates and order is preserved.
fn program_ids(&self) -> Vec<Pubkey> {
self.instructions
.iter()
.map(|ix| ix.program_ids_index)
.unique()
.collect()
}
/// Return an unsigned transaction with space for requires signatures.
pub fn compile(&self) -> Transaction {
2019-02-28 04:48:44 -07:00
let program_ids = self.program_ids();
let (mut signed_keys, unsigned_keys) = self.keys();
let signed_len = signed_keys.len();
signed_keys.extend(&unsigned_keys);
let instructions = compile_instructions(&self.instructions, &signed_keys, &program_ids);
Transaction {
signatures: Vec::with_capacity(signed_len),
account_keys: signed_keys,
recent_blockhash: Hash::default(),
fee: self.fee,
2019-02-28 04:48:44 -07:00
program_ids,
instructions,
}
}
/// Return a signed transaction.
pub fn sign<T: KeypairUtil>(&self, keypairs: &[&T], recent_blockhash: Hash) -> Transaction {
let mut tx = self.compile();
tx.sign_checked(keypairs, recent_blockhash);
tx
2019-02-28 04:48:44 -07:00
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::signature::{Keypair, KeypairUtil};
#[test]
fn test_transaction_builder_unique_program_ids() {
let program_id0 = Pubkey::default();
let program_ids = TransactionBuilder::default()
.push(Instruction::new(program_id0, &0, vec![]))
.push(Instruction::new(program_id0, &0, vec![]))
.program_ids();
assert_eq!(program_ids, vec![program_id0]);
}
#[test]
fn test_transaction_builder_unique_program_ids_not_adjacent() {
let program_id0 = Pubkey::default();
let program_id1 = Keypair::new().pubkey();
let program_ids = TransactionBuilder::default()
.push(Instruction::new(program_id0, &0, vec![]))
.push(Instruction::new(program_id1, &0, vec![]))
.push(Instruction::new(program_id0, &0, vec![]))
.program_ids();
assert_eq!(program_ids, vec![program_id0, program_id1]);
}
#[test]
fn test_transaction_builder_unique_program_ids_order_preserved() {
let program_id0 = Keypair::new().pubkey();
let program_id1 = Pubkey::default(); // Key less than program_id0
let program_ids = TransactionBuilder::default()
.push(Instruction::new(program_id0, &0, vec![]))
.push(Instruction::new(program_id1, &0, vec![]))
.push(Instruction::new(program_id0, &0, vec![]))
.program_ids();
assert_eq!(program_ids, vec![program_id0, program_id1]);
}
#[test]
fn test_transaction_builder_unique_keys_both_signed() {
let program_id = Pubkey::default();
let id0 = Pubkey::default();
let keys = TransactionBuilder::default()
.push(Instruction::new(program_id, &0, vec![(id0, true)]))
.push(Instruction::new(program_id, &0, vec![(id0, true)]))
.keys();
assert_eq!(keys, (vec![id0], vec![]));
2019-02-28 04:48:44 -07:00
}
#[test]
fn test_transaction_builder_unique_keys_one_signed() {
let program_id = Pubkey::default();
let id0 = Pubkey::default();
let keys = TransactionBuilder::default()
.push(Instruction::new(program_id, &0, vec![(id0, false)]))
.push(Instruction::new(program_id, &0, vec![(id0, true)]))
.keys();
assert_eq!(keys, (vec![id0], vec![]));
2019-02-28 04:48:44 -07:00
}
#[test]
fn test_transaction_builder_unique_keys_order_preserved() {
let program_id = Pubkey::default();
let id0 = Keypair::new().pubkey();
let id1 = Pubkey::default(); // Key less than id0
let keys = TransactionBuilder::default()
.push(Instruction::new(program_id, &0, vec![(id0, false)]))
.push(Instruction::new(program_id, &0, vec![(id1, false)]))
.keys();
assert_eq!(keys, (vec![], vec![id0, id1]));
2019-02-28 04:48:44 -07:00
}
#[test]
fn test_transaction_builder_unique_keys_not_adjacent() {
let program_id = Pubkey::default();
let id0 = Pubkey::default();
let id1 = Keypair::new().pubkey();
let keys = TransactionBuilder::default()
.push(Instruction::new(program_id, &0, vec![(id0, false)]))
.push(Instruction::new(program_id, &0, vec![(id1, false)]))
.push(Instruction::new(program_id, &0, vec![(id0, true)]))
.keys();
assert_eq!(keys, (vec![id0], vec![id1]));
2019-02-28 04:48:44 -07:00
}
#[test]
fn test_transaction_builder_signed_keys_first() {
let program_id = Pubkey::default();
let id0 = Pubkey::default();
let id1 = Keypair::new().pubkey();
let keys = TransactionBuilder::default()
.push(Instruction::new(program_id, &0, vec![(id0, false)]))
.push(Instruction::new(program_id, &0, vec![(id1, true)]))
.keys();
assert_eq!(keys, (vec![id1], vec![id0]));
2019-02-28 04:48:44 -07:00
}
#[test]
// Ensure there's a way to calculate the number of required signatures.
fn test_transaction_builder_signed_keys_len() {
let program_id = Pubkey::default();
let id0 = Pubkey::default();
let tx = TransactionBuilder::default()
.push(Instruction::new(program_id, &0, vec![(id0, false)]))
.compile();
assert_eq!(tx.signatures.capacity(), 0);
let tx = TransactionBuilder::default()
.push(Instruction::new(program_id, &0, vec![(id0, true)]))
.compile();
assert_eq!(tx.signatures.capacity(), 1);
}
2019-02-28 04:48:44 -07:00
#[test]
#[should_panic]
fn test_transaction_builder_missing_key() {
let keypair = Keypair::new();
TransactionBuilder::default().sign(&[&keypair], Hash::default());
}
#[test]
#[should_panic]
fn test_transaction_builder_missing_keypair() {
let program_id = Pubkey::default();
let keypair0 = Keypair::new();
let id0 = keypair0.pubkey();
TransactionBuilder::default()
.push(Instruction::new(program_id, &0, vec![(id0, true)]))
.sign(&Vec::<&Keypair>::new(), Hash::default());
}
2019-02-28 04:48:44 -07:00
#[test]
#[should_panic]
fn test_transaction_builder_wrong_key() {
let program_id = Pubkey::default();
let keypair0 = Keypair::new();
let wrong_id = Pubkey::default();
TransactionBuilder::default()
.push(Instruction::new(program_id, &0, vec![(wrong_id, true)]))
.sign(&[&keypair0], Hash::default());
}
#[test]
fn test_transaction_builder_correct_key() {
let program_id = Pubkey::default();
let keypair0 = Keypair::new();
let id0 = keypair0.pubkey();
let tx = TransactionBuilder::default()
.push(Instruction::new(program_id, &0, vec![(id0, true)]))
.sign(&[&keypair0], Hash::default());
assert_eq!(tx.instructions[0], Instruction::new(0, &0, vec![0]));
}
2019-02-28 06:40:57 -07:00
#[test]
fn test_transaction_builder_fee() {
let tx = TransactionBuilder::new(42).sign(&Vec::<&Keypair>::new(), Hash::default());
assert_eq!(tx.fee, 42);
}
2019-02-28 04:48:44 -07:00
#[test]
fn test_transaction_builder_kitchen_sink() {
let program_id0 = Pubkey::default();
let program_id1 = Keypair::new().pubkey();
let id0 = Pubkey::default();
let keypair1 = Keypair::new();
let id1 = keypair1.pubkey();
let tx = TransactionBuilder::default()
.push(Instruction::new(program_id0, &0, vec![(id0, false)]))
.push(Instruction::new(program_id1, &0, vec![(id1, true)]))
.push(Instruction::new(program_id0, &0, vec![(id1, false)]))
.sign(&[&keypair1], Hash::default());
assert_eq!(tx.instructions[0], Instruction::new(0, &0, vec![1]));
assert_eq!(tx.instructions[1], Instruction::new(1, &0, vec![0]));
assert_eq!(tx.instructions[2], Instruction::new(0, &0, vec![0]));
}
}