diff --git a/sdk/src/transaction.rs b/sdk/src/transaction.rs index 462467fd8..2bcb45226 100644 --- a/sdk/src/transaction.rs +++ b/sdk/src/transaction.rs @@ -227,6 +227,18 @@ impl Transaction { .collect(); } + /// Check keys and keypair lengths, then sign this transaction. + /// Note: this presumes signatures.capacity() was set to the number of required signatures. + pub fn sign_checked(&mut self, keypairs: &[&T], recent_blockhash: Hash) { + let signed_keys = &self.account_keys[0..self.signatures.capacity()]; + for (i, keypair) in keypairs.iter().enumerate() { + assert_eq!(keypair.pubkey(), signed_keys[i], "keypair-pubkey mismatch"); + } + assert_eq!(keypairs.len(), signed_keys.len(), "not enough keypairs"); + + self.sign(keypairs, recent_blockhash); + } + /// Verify only the transaction signature. pub fn verify_signature(&self) -> bool { self.signatures diff --git a/sdk/src/transaction_builder.rs b/sdk/src/transaction_builder.rs index 09047d6ae..d34505171 100644 --- a/sdk/src/transaction_builder.rs +++ b/sdk/src/transaction_builder.rs @@ -12,8 +12,8 @@ fn position(keys: &[Pubkey], key: Pubkey) -> u8 { keys.iter().position(|&k| k == key).unwrap() as u8 } -fn create_indexed_instruction( - ix: &Instruction, +fn compile_instruction( + ix: &BuilderInstruction, keys: &[Pubkey], program_ids: &[Pubkey], ) -> Instruction { @@ -29,6 +29,16 @@ fn create_indexed_instruction( } } +fn compile_instructions( + ixs: &[BuilderInstruction], + keys: &[Pubkey], + program_ids: &[Pubkey], +) -> Vec> { + ixs.iter() + .map(|ix| compile_instruction(ix, keys, program_ids)) + .collect() +} + /// A utility for constructing transactions #[derive(Default)] pub struct TransactionBuilder { @@ -82,25 +92,17 @@ impl TransactionBuilder { .collect() } - /// Return the instructions, but indexing lists of keys and program ids. - fn instructions(&self, keys: &[Pubkey], program_ids: &[Pubkey]) -> Vec> { - self.instructions - .iter() - .map(|ix| create_indexed_instruction(ix, keys, program_ids)) - .collect() - } - - /// Return an unsigned transaction that requires signatures. To more safely return an unsigned - /// transaction that *doesn't* require signatures, pass `sign()` zero keypairs. - pub fn unsigned(&self, recent_blockhash: Hash) -> Transaction { + /// Return an unsigned transaction with space for requires signatures. + pub fn compile(&self) -> Transaction { 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 = self.instructions(&signed_keys, &program_ids); + let instructions = compile_instructions(&self.instructions, &signed_keys, &program_ids); Transaction { - signatures: vec![], + signatures: Vec::with_capacity(signed_len), account_keys: signed_keys, - recent_blockhash, + recent_blockhash: Hash::default(), fee: self.fee, program_ids, instructions, @@ -109,14 +111,8 @@ impl TransactionBuilder { /// Return a signed transaction. pub fn sign(&self, keypairs: &[&T], recent_blockhash: Hash) -> Transaction { - let signed_keys = self.keys().0; - for (i, keypair) in keypairs.iter().enumerate() { - assert_eq!(keypair.pubkey(), signed_keys[i], "keypair-pubkey mismatch"); - } - assert_eq!(keypairs.len(), signed_keys.len(), "not enough keypairs"); - - let mut tx = self.unsigned(Hash::default()); - tx.sign(keypairs, recent_blockhash); + let mut tx = self.compile(); + tx.sign_checked(keypairs, recent_blockhash); tx } } @@ -219,6 +215,22 @@ mod tests { assert_eq!(keys, (vec![id1], vec![id0])); } + #[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); + } + #[test] #[should_panic] fn test_transaction_builder_missing_key() {