Inline InstructionCompiler

The object-oriented paradigm isn't helpful here; go functional.
This commit is contained in:
Greg Fitzgerald 2019-03-24 21:20:34 -06:00
parent 4efa144916
commit 5c536e423c
1 changed files with 86 additions and 103 deletions

View File

@ -1,4 +1,4 @@
//! A library for compiling instructions //! A library for generating a message from a sequence of instructions
use crate::hash::Hash; use crate::hash::Hash;
use crate::instruction::{CompiledInstruction, Instruction}; use crate::instruction::{CompiledInstruction, Instruction};
@ -10,7 +10,7 @@ fn position(keys: &[Pubkey], key: &Pubkey) -> u8 {
} }
fn compile_instruction( fn compile_instruction(
ix: &Instruction, ix: Instruction,
keys: &[Pubkey], keys: &[Pubkey],
program_ids: &[Pubkey], program_ids: &[Pubkey],
) -> CompiledInstruction { ) -> CompiledInstruction {
@ -28,73 +28,43 @@ fn compile_instruction(
} }
fn compile_instructions( fn compile_instructions(
ixs: &[Instruction], ixs: Vec<Instruction>,
keys: &[Pubkey], keys: &[Pubkey],
program_ids: &[Pubkey], program_ids: &[Pubkey],
) -> Vec<CompiledInstruction> { ) -> Vec<CompiledInstruction> {
ixs.iter() ixs.into_iter()
.map(|ix| compile_instruction(ix, keys, program_ids)) .map(|ix| compile_instruction(ix, keys, program_ids))
.collect() .collect()
} }
/// A utility for constructing transactions /// Return pubkeys referenced by all instructions, with the ones needing signatures first.
pub struct InstructionCompiler { /// No duplicates and order is preserved.
instructions: Vec<Instruction>, fn get_keys(instructions: &[Instruction]) -> (Vec<Pubkey>, Vec<Pubkey>) {
let mut keys_and_signed: Vec<_> = instructions
.iter()
.flat_map(|ix| ix.accounts.iter())
.collect();
keys_and_signed.sort_by(|x, y| y.is_signer.cmp(&x.is_signer));
let mut signed_keys = vec![];
let mut unsigned_keys = vec![];
for account_meta in keys_and_signed.into_iter().unique_by(|x| x.pubkey) {
if account_meta.is_signer {
signed_keys.push(account_meta.pubkey);
} else {
unsigned_keys.push(account_meta.pubkey);
}
}
(signed_keys, unsigned_keys)
} }
impl InstructionCompiler { /// Return program ids referenced by all instructions. No duplicates and order is preserved.
/// Create a new unsigned transaction from a single instruction fn get_program_ids(instructions: &[Instruction]) -> Vec<Pubkey> {
pub fn new(instructions: Vec<Instruction>) -> Self { instructions
Self { instructions } .iter()
} .map(|ix| ix.program_ids_index)
.unique()
/// Return pubkeys referenced by all instructions, with the ones needing signatures first. .collect()
/// No duplicates and order is preserved.
fn keys(&self) -> (Vec<Pubkey>, Vec<Pubkey>) {
let mut keys_and_signed: Vec<_> = self
.instructions
.iter()
.flat_map(|ix| ix.accounts.iter())
.collect();
keys_and_signed.sort_by(|x, y| y.is_signer.cmp(&x.is_signer));
let mut signed_keys = vec![];
let mut unsigned_keys = vec![];
for account_meta in keys_and_signed.into_iter().unique_by(|x| x.pubkey) {
if account_meta.is_signer {
signed_keys.push(account_meta.pubkey);
} else {
unsigned_keys.push(account_meta.pubkey);
}
}
(signed_keys, unsigned_keys)
}
/// 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) -> Message {
let program_ids = self.program_ids();
let (mut signed_keys, unsigned_keys) = self.keys();
let num_signatures = signed_keys.len() as u8;
signed_keys.extend(&unsigned_keys);
let instructions = compile_instructions(&self.instructions, &signed_keys, &program_ids);
Message {
num_signatures,
account_keys: signed_keys,
recent_blockhash: Hash::default(),
fee: 0,
program_ids,
instructions,
}
}
} }
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
@ -108,8 +78,21 @@ pub struct Message {
} }
impl Message { impl Message {
/// Return an unsigned transaction with space for requires signatures.
pub fn new(instructions: Vec<Instruction>) -> Self { pub fn new(instructions: Vec<Instruction>) -> Self {
InstructionCompiler::new(instructions).compile() let program_ids = get_program_ids(&instructions);
let (mut signed_keys, unsigned_keys) = get_keys(&instructions);
let num_signatures = signed_keys.len() as u8;
signed_keys.extend(&unsigned_keys);
let instructions = compile_instructions(instructions, &signed_keys, &program_ids);
Self {
num_signatures,
account_keys: signed_keys,
recent_blockhash: Hash::default(),
fee: 0,
program_ids,
instructions,
}
} }
} }
@ -120,135 +103,135 @@ mod tests {
use crate::signature::{Keypair, KeypairUtil}; use crate::signature::{Keypair, KeypairUtil};
#[test] #[test]
fn test_transaction_builder_unique_program_ids() { fn test_message_unique_program_ids() {
let program_id0 = Pubkey::default(); let program_id0 = Pubkey::default();
let program_ids = InstructionCompiler::new(vec![ let program_ids = get_program_ids(&[
Instruction::new(program_id0, &0, vec![]), Instruction::new(program_id0, &0, vec![]),
Instruction::new(program_id0, &0, vec![]), Instruction::new(program_id0, &0, vec![]),
]) ]);
.program_ids();
assert_eq!(program_ids, vec![program_id0]); assert_eq!(program_ids, vec![program_id0]);
} }
#[test] #[test]
fn test_transaction_builder_unique_program_ids_not_adjacent() { fn test_message_unique_program_ids_not_adjacent() {
let program_id0 = Pubkey::default(); let program_id0 = Pubkey::default();
let program_id1 = Keypair::new().pubkey(); let program_id1 = Keypair::new().pubkey();
let program_ids = InstructionCompiler::new(vec![ let program_ids = get_program_ids(&[
Instruction::new(program_id0, &0, vec![]), Instruction::new(program_id0, &0, vec![]),
Instruction::new(program_id1, &0, vec![]), Instruction::new(program_id1, &0, vec![]),
Instruction::new(program_id0, &0, vec![]), Instruction::new(program_id0, &0, vec![]),
]) ]);
.program_ids();
assert_eq!(program_ids, vec![program_id0, program_id1]); assert_eq!(program_ids, vec![program_id0, program_id1]);
} }
#[test] #[test]
fn test_transaction_builder_unique_program_ids_order_preserved() { fn test_message_unique_program_ids_order_preserved() {
let program_id0 = Keypair::new().pubkey(); let program_id0 = Keypair::new().pubkey();
let program_id1 = Pubkey::default(); // Key less than program_id0 let program_id1 = Pubkey::default(); // Key less than program_id0
let program_ids = InstructionCompiler::new(vec![ let program_ids = get_program_ids(&[
Instruction::new(program_id0, &0, vec![]), Instruction::new(program_id0, &0, vec![]),
Instruction::new(program_id1, &0, vec![]), Instruction::new(program_id1, &0, vec![]),
Instruction::new(program_id0, &0, vec![]), Instruction::new(program_id0, &0, vec![]),
]) ]);
.program_ids();
assert_eq!(program_ids, vec![program_id0, program_id1]); assert_eq!(program_ids, vec![program_id0, program_id1]);
} }
#[test] #[test]
fn test_transaction_builder_unique_keys_both_signed() { fn test_message_unique_keys_both_signed() {
let program_id = Pubkey::default(); let program_id = Pubkey::default();
let id0 = Pubkey::default(); let id0 = Pubkey::default();
let keys = InstructionCompiler::new(vec![ let keys = get_keys(&[
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]), Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]),
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]), Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]),
]) ]);
.keys();
assert_eq!(keys, (vec![id0], vec![])); assert_eq!(keys, (vec![id0], vec![]));
} }
#[test] #[test]
fn test_transaction_builder_unique_keys_one_signed() { fn test_message_unique_keys_one_signed() {
let program_id = Pubkey::default(); let program_id = Pubkey::default();
let id0 = Pubkey::default(); let id0 = Pubkey::default();
let keys = InstructionCompiler::new(vec![ let keys = get_keys(&[
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]), Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]),
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]), Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]),
]) ]);
.keys();
assert_eq!(keys, (vec![id0], vec![])); assert_eq!(keys, (vec![id0], vec![]));
} }
#[test] #[test]
fn test_transaction_builder_unique_keys_order_preserved() { fn test_message_unique_keys_order_preserved() {
let program_id = Pubkey::default(); let program_id = Pubkey::default();
let id0 = Keypair::new().pubkey(); let id0 = Keypair::new().pubkey();
let id1 = Pubkey::default(); // Key less than id0 let id1 = Pubkey::default(); // Key less than id0
let keys = InstructionCompiler::new(vec![ let keys = get_keys(&[
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]), Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]),
Instruction::new(program_id, &0, vec![AccountMeta::new(id1, false)]), Instruction::new(program_id, &0, vec![AccountMeta::new(id1, false)]),
]) ]);
.keys();
assert_eq!(keys, (vec![], vec![id0, id1])); assert_eq!(keys, (vec![], vec![id0, id1]));
} }
#[test] #[test]
fn test_transaction_builder_unique_keys_not_adjacent() { fn test_message_unique_keys_not_adjacent() {
let program_id = Pubkey::default(); let program_id = Pubkey::default();
let id0 = Pubkey::default(); let id0 = Pubkey::default();
let id1 = Keypair::new().pubkey(); let id1 = Keypair::new().pubkey();
let keys = InstructionCompiler::new(vec![ let keys = get_keys(&[
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]), Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]),
Instruction::new(program_id, &0, vec![AccountMeta::new(id1, false)]), Instruction::new(program_id, &0, vec![AccountMeta::new(id1, false)]),
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]), Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]),
]) ]);
.keys();
assert_eq!(keys, (vec![id0], vec![id1])); assert_eq!(keys, (vec![id0], vec![id1]));
} }
#[test] #[test]
fn test_transaction_builder_signed_keys_first() { fn test_message_signed_keys_first() {
let program_id = Pubkey::default(); let program_id = Pubkey::default();
let id0 = Pubkey::default(); let id0 = Pubkey::default();
let id1 = Keypair::new().pubkey(); let id1 = Keypair::new().pubkey();
let keys = InstructionCompiler::new(vec![ let keys = get_keys(&[
Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]), Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]),
Instruction::new(program_id, &0, vec![AccountMeta::new(id1, true)]), Instruction::new(program_id, &0, vec![AccountMeta::new(id1, true)]),
]) ]);
.keys();
assert_eq!(keys, (vec![id1], vec![id0])); assert_eq!(keys, (vec![id1], vec![id0]));
} }
#[test] #[test]
// Ensure there's a way to calculate the number of required signatures. // Ensure there's a way to calculate the number of required signatures.
fn test_transaction_builder_signed_keys_len() { fn test_message_signed_keys_len() {
let program_id = Pubkey::default(); let program_id = Pubkey::default();
let id0 = Pubkey::default(); let id0 = Pubkey::default();
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]); let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]);
let message = InstructionCompiler::new(vec![ix]).compile(); let message = Message::new(vec![ix]);
assert_eq!(message.num_signatures, 0); assert_eq!(message.num_signatures, 0);
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]); let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]);
let message = InstructionCompiler::new(vec![ix]).compile(); let message = Message::new(vec![ix]);
assert_eq!(message.num_signatures, 1); assert_eq!(message.num_signatures, 1);
} }
#[test] #[test]
fn test_transaction_builder_kitchen_sink() { fn test_message_kitchen_sink() {
let program_id0 = Pubkey::default(); let program_id0 = Pubkey::default();
let program_id1 = Keypair::new().pubkey(); let program_id1 = Keypair::new().pubkey();
let id0 = Pubkey::default(); let id0 = Pubkey::default();
let keypair1 = Keypair::new(); let keypair1 = Keypair::new();
let id1 = keypair1.pubkey(); let id1 = keypair1.pubkey();
let tx = InstructionCompiler::new(vec![ let message = Message::new(vec![
Instruction::new(program_id0, &0, vec![AccountMeta::new(id0, false)]), Instruction::new(program_id0, &0, vec![AccountMeta::new(id0, false)]),
Instruction::new(program_id1, &0, vec![AccountMeta::new(id1, true)]), Instruction::new(program_id1, &0, vec![AccountMeta::new(id1, true)]),
Instruction::new(program_id0, &0, vec![AccountMeta::new(id1, false)]), Instruction::new(program_id0, &0, vec![AccountMeta::new(id1, false)]),
]) ]);
.compile(); assert_eq!(
assert_eq!(tx.instructions[0], CompiledInstruction::new(0, &0, vec![1])); message.instructions[0],
assert_eq!(tx.instructions[1], CompiledInstruction::new(1, &0, vec![0])); CompiledInstruction::new(0, &0, vec![1])
assert_eq!(tx.instructions[2], CompiledInstruction::new(0, &0, vec![0])); );
assert_eq!(
message.instructions[1],
CompiledInstruction::new(1, &0, vec![0])
);
assert_eq!(
message.instructions[2],
CompiledInstruction::new(0, &0, vec![0])
);
} }
} }