From e8124324ff19b9b854eae58c353cfdad7299b854 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Thu, 20 Feb 2020 13:13:23 -0700 Subject: [PATCH] Support transaction signing by heterogenous lists of keypairs (#8342) automerge --- archiver-lib/src/archiver.rs | 4 +- bench-exchange/src/bench.rs | 15 +++- client/src/rpc_client.rs | 15 ++-- client/src/thin_client.rs | 19 +++-- ledger/src/staking_utils.rs | 15 ++-- programs/ownable/src/ownable_processor.rs | 4 +- programs/vest/src/vest_processor.rs | 8 +- runtime/src/accounts.rs | 2 +- runtime/src/bank_client.rs | 11 +-- runtime/tests/stake.rs | 2 +- runtime/tests/storage.rs | 2 +- sdk-c/src/lib.rs | 10 ++- sdk/src/client.rs | 7 +- sdk/src/lib.rs | 2 + sdk/src/signers.rs | 97 +++++++++++++++++++++++ sdk/src/transaction.rs | 64 ++++++++------- 16 files changed, 196 insertions(+), 81 deletions(-) create mode 100644 sdk/src/signers.rs diff --git a/archiver-lib/src/archiver.rs b/archiver-lib/src/archiver.rs index ba4f2109fa..0053a9e865 100644 --- a/archiver-lib/src/archiver.rs +++ b/archiver-lib/src/archiver.rs @@ -384,7 +384,7 @@ impl Archiver { ); let message = Message::new_with_payer(vec![ix], Some(&archiver_keypair.pubkey())); - if let Err(e) = client.send_message(&[&archiver_keypair], message) { + if let Err(e) = client.send_message(&[archiver_keypair.as_ref()], message) { error!("unable to redeem reward, tx failed: {:?}", e); } else { info!( @@ -671,7 +671,7 @@ impl Archiver { blockhash, ); if let Err(err) = client.send_and_confirm_transaction( - &[&archiver_keypair, &storage_keypair], + &[archiver_keypair.as_ref(), storage_keypair.as_ref()], &mut transaction, 10, 0, diff --git a/bench-exchange/src/bench.rs b/bench-exchange/src/bench.rs index fce6631a67..d84274bee4 100644 --- a/bench-exchange/src/bench.rs +++ b/bench-exchange/src/bench.rs @@ -701,7 +701,7 @@ fn verify_funding_transfer( false } -pub fn fund_keys(client: &dyn Client, source: &Keypair, dests: &[Arc], lamports: u64) { +pub fn fund_keys(client: &T, source: &Keypair, dests: &[Arc], lamports: u64) { let total = lamports * (dests.len() as u64 + 1); let mut funded: Vec<(&Keypair, u64)> = vec![(source, total)]; let mut notfunded: Vec<&Arc> = dests.iter().collect(); @@ -824,7 +824,11 @@ pub fn fund_keys(client: &dyn Client, source: &Keypair, dests: &[Arc], } } -pub fn create_token_accounts(client: &dyn Client, signers: &[Arc], accounts: &[Keypair]) { +pub fn create_token_accounts( + client: &T, + signers: &[Arc], + accounts: &[Keypair], +) { let mut notfunded: Vec<(&Arc, &Keypair)> = signers.iter().zip(accounts).collect(); while !notfunded.is_empty() { @@ -968,7 +972,12 @@ fn generate_keypairs(num: u64) -> Vec { rnd.gen_n_keypairs(num) } -pub fn airdrop_lamports(client: &dyn Client, faucet_addr: &SocketAddr, id: &Keypair, amount: u64) { +pub fn airdrop_lamports( + client: &T, + faucet_addr: &SocketAddr, + id: &Keypair, + amount: u64, +) { let balance = client.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent()); let balance = balance.unwrap_or(0); if balance >= amount { diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index 936b365a49..1aaa9a5eb7 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -22,7 +22,8 @@ use solana_sdk::{ hash::Hash, inflation::Inflation, pubkey::Pubkey, - signature::{KeypairUtil, Signature}, + signature::Signature, + signers::Signers, transaction::{self, Transaction, TransactionError}, }; use std::{ @@ -405,10 +406,10 @@ impl RpcClient { }) } - pub fn send_and_confirm_transaction( + pub fn send_and_confirm_transaction( &self, transaction: &mut Transaction, - signer_keys: &[&T], + signer_keys: &T, ) -> Result { let mut send_retries = 20; loop { @@ -456,10 +457,10 @@ impl RpcClient { } } - pub fn send_and_confirm_transactions( + pub fn send_and_confirm_transactions( &self, mut transactions: Vec, - signer_keys: &[&T], + signer_keys: &T, ) -> Result<(), Box> { let mut send_retries = 5; loop { @@ -526,10 +527,10 @@ impl RpcClient { } } - pub fn resign_transaction( + pub fn resign_transaction( &self, tx: &mut Transaction, - signer_keys: &[&T], + signer_keys: &T, ) -> Result<(), ClientError> { let (blockhash, _fee_calculator) = self.get_new_blockhash(&tx.message().recent_blockhash)?; diff --git a/client/src/thin_client.rs b/client/src/thin_client.rs index 00e656c016..fd1327c5e5 100644 --- a/client/src/thin_client.rs +++ b/client/src/thin_client.rs @@ -18,6 +18,7 @@ use solana_sdk::{ packet::PACKET_DATA_SIZE, pubkey::Pubkey, signature::{Keypair, KeypairUtil, Signature}, + signers::Signers, system_instruction, timing::duration_as_ms, transaction::{self, Transaction}, @@ -202,9 +203,9 @@ impl ThinClient { } /// Retry sending a signed Transaction to the server for processing - pub fn send_and_confirm_transaction( + pub fn send_and_confirm_transaction( &self, - keypairs: &[&Keypair], + keypairs: &T, transaction: &mut Transaction, tries: usize, pending_confirmations: usize, @@ -351,9 +352,13 @@ impl Client for ThinClient { } impl SyncClient for ThinClient { - fn send_message(&self, keypairs: &[&Keypair], message: Message) -> TransportResult { + fn send_message( + &self, + keypairs: &T, + message: Message, + ) -> TransportResult { let (blockhash, _fee_calculator) = self.get_recent_blockhash()?; - let mut transaction = Transaction::new(&keypairs, message, blockhash); + let mut transaction = Transaction::new(keypairs, message, blockhash); let signature = self.send_and_confirm_transaction(keypairs, &mut transaction, 5, 0)?; Ok(signature) } @@ -561,13 +566,13 @@ impl AsyncClient for ThinClient { .send_to(&buf[..], &self.tpu_addr())?; Ok(transaction.signatures[0]) } - fn async_send_message( + fn async_send_message( &self, - keypairs: &[&Keypair], + keypairs: &T, message: Message, recent_blockhash: Hash, ) -> io::Result { - let transaction = Transaction::new(&keypairs, message, recent_blockhash); + let transaction = Transaction::new(keypairs, message, recent_blockhash); self.async_send_transaction(transaction) } fn async_send_instruction( diff --git a/ledger/src/staking_utils.rs b/ledger/src/staking_utils.rs index a662cadea2..b96f5c87f4 100644 --- a/ledger/src/staking_utils.rs +++ b/ledger/src/staking_utils.rs @@ -108,6 +108,7 @@ pub(crate) mod tests { instruction::Instruction, pubkey::Pubkey, signature::{Keypair, KeypairUtil}, + signers::Signers, sysvar::{ stake_history::{self, StakeHistory}, Sysvar, @@ -133,18 +134,14 @@ pub(crate) mod tests { amount: u64, ) { let vote_pubkey = vote_account.pubkey(); - fn process_instructions( - bank: &Bank, - keypairs: &[&T], - ixs: Vec, - ) { - bank.process_transaction(&Transaction::new_signed_with_payer( + fn process_instructions(bank: &Bank, keypairs: &T, ixs: Vec) { + let tx = Transaction::new_signed_with_payer( ixs, - Some(&keypairs[0].pubkey()), + Some(&keypairs.pubkeys()[0]), keypairs, bank.last_blockhash(), - )) - .unwrap(); + ); + bank.process_transaction(&tx).unwrap(); } process_instructions( diff --git a/programs/ownable/src/ownable_processor.rs b/programs/ownable/src/ownable_processor.rs index 98cf977dd1..f63a03393c 100644 --- a/programs/ownable/src/ownable_processor.rs +++ b/programs/ownable/src/ownable_processor.rs @@ -94,7 +94,7 @@ mod tests { lamports, ); let message = Message::new(instructions); - bank_client.send_message(&[&payer_keypair, &account_keypair], message) + bank_client.send_message(&[payer_keypair, account_keypair], message) } fn send_set_owner( @@ -110,7 +110,7 @@ mod tests { new_owner_pubkey, ); let message = Message::new_with_payer(vec![instruction], Some(&payer_keypair.pubkey())); - bank_client.send_message(&[&payer_keypair, &old_owner_keypair], message) + bank_client.send_message(&[payer_keypair, old_owner_keypair], message) } #[test] diff --git a/programs/vest/src/vest_processor.rs b/programs/vest/src/vest_processor.rs index 89af1f1c06..25e71b257e 100644 --- a/programs/vest/src/vest_processor.rs +++ b/programs/vest/src/vest_processor.rs @@ -188,7 +188,7 @@ mod tests { instructions.push(date_instruction::store(&date_pubkey, date)); let message = Message::new(instructions); - bank_client.send_message(&[&payer_keypair, &date_keypair], message) + bank_client.send_message(&[payer_keypair, date_keypair], message) } fn store_date( @@ -200,7 +200,7 @@ mod tests { let date_pubkey = date_keypair.pubkey(); let instruction = date_instruction::store(&date_pubkey, date); let message = Message::new_with_payer(vec![instruction], Some(&payer_keypair.pubkey())); - bank_client.send_message(&[&payer_keypair, &date_keypair], message) + bank_client.send_message(&[payer_keypair, date_keypair], message) } fn create_vest_account( @@ -223,7 +223,7 @@ mod tests { lamports, ); let message = Message::new(instructions); - bank_client.send_message(&[&payer_keypair, &contract_keypair], message) + bank_client.send_message(&[payer_keypair, contract_keypair], message) } fn send_set_terminator( @@ -258,7 +258,7 @@ mod tests { let instruction = vest_instruction::redeem_tokens(&contract_pubkey, &date_pubkey, &payee_pubkey); let message = Message::new_with_payer(vec![instruction], Some(&payer_keypair.pubkey())); - bank_client.send_message(&[&payer_keypair], message) + bank_client.send_message(&[payer_keypair], message) } #[test] diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index 7306988be6..41af27a0ac 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -744,7 +744,7 @@ mod tests { let mut error_counters = ErrorCounters::default(); let instructions = vec![CompiledInstruction::new(0, &(), vec![0])]; - let tx = Transaction::new_with_compiled_instructions::( + let tx = Transaction::new_with_compiled_instructions::<[&Keypair; 0]>( &[], &[], Hash::default(), diff --git a/runtime/src/bank_client.rs b/runtime/src/bank_client.rs index e033c45fe5..0399bd23f3 100644 --- a/runtime/src/bank_client.rs +++ b/runtime/src/bank_client.rs @@ -9,6 +9,7 @@ use solana_sdk::{ message::Message, pubkey::Pubkey, signature::{Keypair, KeypairUtil, Signature}, + signers::Signers, system_instruction, transaction::{self, Transaction}, transport::{Result, TransportError}, @@ -42,13 +43,13 @@ impl AsyncClient for BankClient { Ok(signature) } - fn async_send_message( + fn async_send_message( &self, - keypairs: &[&Keypair], + keypairs: &T, message: Message, recent_blockhash: Hash, ) -> io::Result { - let transaction = Transaction::new(&keypairs, message, recent_blockhash); + let transaction = Transaction::new(keypairs, message, recent_blockhash); self.async_send_transaction(transaction) } @@ -77,9 +78,9 @@ impl AsyncClient for BankClient { } impl SyncClient for BankClient { - fn send_message(&self, keypairs: &[&Keypair], message: Message) -> Result { + fn send_message(&self, keypairs: &T, message: Message) -> Result { let blockhash = self.bank.last_blockhash(); - let transaction = Transaction::new(&keypairs, message, blockhash); + let transaction = Transaction::new(keypairs, message, blockhash); self.bank.process_transaction(&transaction)?; Ok(transaction.signatures.get(0).cloned().unwrap_or_default()) } diff --git a/runtime/tests/stake.rs b/runtime/tests/stake.rs index c3f71b149f..a293ba3dd4 100644 --- a/runtime/tests/stake.rs +++ b/runtime/tests/stake.rs @@ -61,7 +61,7 @@ fn fill_epoch_with_votes( Some(&mint_pubkey), ); assert!(bank_client - .send_message(&[&mint_keypair, &vote_keypair], message) + .send_message(&[mint_keypair, vote_keypair], message) .is_ok()); } bank diff --git a/runtime/tests/storage.rs b/runtime/tests/storage.rs index 3e36dba2f3..152a4a2507 100644 --- a/runtime/tests/storage.rs +++ b/runtime/tests/storage.rs @@ -371,7 +371,7 @@ fn submit_proof( ); assert_matches!( - bank_client.send_message(&[&mint_keypair, &storage_keypair], message), + bank_client.send_message(&[mint_keypair, storage_keypair], message), Ok(_) ); ProofStatus::Valid diff --git a/sdk-c/src/lib.rs b/sdk-c/src/lib.rs index 22ab2745e3..c491db9fc9 100644 --- a/sdk-c/src/lib.rs +++ b/sdk-c/src/lib.rs @@ -428,7 +428,7 @@ pub unsafe extern "C" fn deserialize_transaction( /// # Undefined Behavior /// /// Causes UB if any of the pointers is `NULL`, or if `keypairs` does not point to a valid array of -/// `Keypairs` of length `num_keypairs` +/// `Signers` of length `num_keypairs` /// /// # Safety #[no_mangle] @@ -453,7 +453,11 @@ pub unsafe extern "C" fn transaction_partial_sign( }; let keypairs_ref: Vec<&KeypairNative> = keypairs.iter().collect(); - let positions = if let Ok(v) = tx_native.get_signing_keypair_positions(&keypairs_ref[..]) { + let pubkeys: Vec<_> = keypairs_ref + .iter() + .map(|keypair| keypair.pubkey()) + .collect(); + let positions = if let Ok(v) = tx_native.get_signing_keypair_positions(&pubkeys) { v } else { return 2; @@ -467,7 +471,7 @@ pub unsafe extern "C" fn transaction_partial_sign( return 3; }; - tx_native.partial_sign_unchecked(&keypairs_ref[..], positions, *recent_blockhash); + tx_native.partial_sign_unchecked(&keypairs_ref, positions, *recent_blockhash); *tx = Transaction::from_native(tx_native); Box::into_raw(tx); 0 diff --git a/sdk/src/client.rs b/sdk/src/client.rs index ea78c52deb..32f837b78e 100644 --- a/sdk/src/client.rs +++ b/sdk/src/client.rs @@ -17,6 +17,7 @@ use crate::{ message::Message, pubkey::Pubkey, signature::{Keypair, Signature}, + signers::Signers, transaction, transport::Result, }; @@ -29,7 +30,7 @@ pub trait Client: SyncClient + AsyncClient { pub trait SyncClient { /// Create a transaction from the given message, and send it to the /// server, retrying as-needed. - fn send_message(&self, keypairs: &[&Keypair], message: Message) -> Result; + fn send_message(&self, keypairs: &T, message: Message) -> Result; /// Create a transaction from a single instruction that only requires /// a single signer. Then send it to the server, retrying as-needed. @@ -121,9 +122,9 @@ pub trait AsyncClient { /// Create a transaction from the given message, and send it to the /// server, but don't wait for to see if the server accepted it. - fn async_send_message( + fn async_send_message( &self, - keypairs: &[&Keypair], + keypairs: &T, message: Message, recent_blockhash: Hash, ) -> io::Result; diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 859254ab9f..934bea50f3 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -72,6 +72,8 @@ pub mod genesis_config; #[cfg(not(feature = "program"))] pub mod signature; #[cfg(not(feature = "program"))] +pub mod signers; +#[cfg(not(feature = "program"))] pub mod system_transaction; #[cfg(not(feature = "program"))] pub mod transaction; diff --git a/sdk/src/signers.rs b/sdk/src/signers.rs new file mode 100644 index 0000000000..b8c09e083e --- /dev/null +++ b/sdk/src/signers.rs @@ -0,0 +1,97 @@ +use crate::{ + pubkey::Pubkey, + signature::{KeypairUtil, Signature}, +}; + +pub trait Signers { + fn pubkeys(&self) -> Vec; + fn sign_message(&self, message: &[u8]) -> Vec; +} + +macro_rules! default_keypairs_impl { + () => ( + fn pubkeys(&self) -> Vec { + self.iter().map(|keypair| keypair.pubkey()).collect() + } + + fn sign_message(&self, message: &[u8]) -> Vec { + self.iter() + .map(|keypair| keypair.sign_message(message)) + .collect() + } + ); +} + +impl Signers for [&T] { + default_keypairs_impl!(); +} + +impl Signers for [Box] { + default_keypairs_impl!(); +} + +impl Signers for [&T; 0] { + default_keypairs_impl!(); +} + +impl Signers for [&T; 1] { + default_keypairs_impl!(); +} + +impl Signers for [&T; 2] { + default_keypairs_impl!(); +} + +impl Signers for [&T; 3] { + default_keypairs_impl!(); +} + +impl Signers for [&T; 4] { + default_keypairs_impl!(); +} + +impl Signers for Vec<&T> { + default_keypairs_impl!(); +} + +#[cfg(test)] +mod tests { + use super::*; + use std::error; + + struct Foo; + impl KeypairUtil for Foo { + fn try_pubkey(&self) -> Result> { + Ok(Pubkey::default()) + } + fn try_sign_message(&self, _message: &[u8]) -> Result> { + Ok(Signature::default()) + } + } + + struct Bar; + impl KeypairUtil for Bar { + fn try_pubkey(&self) -> Result> { + Ok(Pubkey::default()) + } + fn try_sign_message(&self, _message: &[u8]) -> Result> { + Ok(Signature::default()) + } + } + + #[test] + fn test_dyn_keypairs_compile() { + let xs: Vec> = vec![Box::new(Foo {}), Box::new(Bar {})]; + assert_eq!( + xs.sign_message(b""), + vec![Signature::default(), Signature::default()], + ); + + // Same as above, but less compiler magic. + let xs_ref: &[Box] = &xs; + assert_eq!( + Signers::sign_message(xs_ref, b""), + vec![Signature::default(), Signature::default()], + ); + } +} diff --git a/sdk/src/transaction.rs b/sdk/src/transaction.rs index c0e8525d76..df8d4c49ba 100644 --- a/sdk/src/transaction.rs +++ b/sdk/src/transaction.rs @@ -6,7 +6,8 @@ use crate::{ message::Message, pubkey::Pubkey, short_vec, - signature::{KeypairUtil, Signature}, + signature::Signature, + signers::Signers, system_instruction, }; use std::result; @@ -95,20 +96,20 @@ impl Transaction { Self::new_unsigned(message) } - pub fn new_signed_with_payer( + pub fn new_signed_with_payer( instructions: Vec, payer: Option<&Pubkey>, - signing_keypairs: &[&T], + signing_keypairs: &T, recent_blockhash: Hash, ) -> Self { let message = Message::new_with_payer(instructions, payer); Self::new(signing_keypairs, message, recent_blockhash) } - pub fn new_signed_with_nonce( + pub fn new_signed_with_nonce( mut instructions: Vec, payer: Option<&Pubkey>, - signing_keypairs: &[&T], + signing_keypairs: &T, nonce_account_pubkey: &Pubkey, nonce_authority_pubkey: &Pubkey, nonce_hash: Hash, @@ -126,8 +127,8 @@ impl Transaction { Self::new_unsigned(message) } - pub fn new( - from_keypairs: &[&T], + pub fn new( + from_keypairs: &T, message: Message, recent_blockhash: Hash, ) -> Transaction { @@ -136,8 +137,8 @@ impl Transaction { tx } - pub fn new_signed_instructions( - from_keypairs: &[&T], + pub fn new_signed_instructions( + from_keypairs: &T, instructions: Vec, recent_blockhash: Hash, ) -> Transaction { @@ -152,21 +153,19 @@ impl Transaction { /// * `recent_blockhash` - The PoH hash. /// * `program_ids` - The keys that identify programs used in the `instruction` vector. /// * `instructions` - Instructions that will be executed atomically. - pub fn new_with_compiled_instructions( - from_keypairs: &[&T], + pub fn new_with_compiled_instructions( + from_keypairs: &T, keys: &[Pubkey], recent_blockhash: Hash, program_ids: Vec, instructions: Vec, ) -> Self { - let mut account_keys: Vec<_> = from_keypairs - .iter() - .map(|keypair| (*keypair).pubkey()) - .collect(); + let mut account_keys = from_keypairs.pubkeys(); + let from_keypairs_len = account_keys.len(); account_keys.extend_from_slice(keys); account_keys.extend(&program_ids); let message = Message::new_with_compiled_instructions( - from_keypairs.len() as u8, + from_keypairs_len as u8, 0, program_ids.len() as u8, account_keys, @@ -214,7 +213,7 @@ impl Transaction { } /// Check keys and keypair lengths, then sign this transaction. - pub fn sign(&mut self, keypairs: &[&T], recent_blockhash: Hash) { + pub fn sign(&mut self, keypairs: &T, recent_blockhash: Hash) { self.partial_sign(keypairs, recent_blockhash); assert_eq!(self.is_signed(), true, "not enough keypairs"); @@ -223,9 +222,9 @@ impl Transaction { /// Sign using some subset of required keys /// if recent_blockhash is not the same as currently in the transaction, /// clear any prior signatures and update recent_blockhash - pub fn partial_sign(&mut self, keypairs: &[&T], recent_blockhash: Hash) { + pub fn partial_sign(&mut self, keypairs: &T, recent_blockhash: Hash) { let positions = self - .get_signing_keypair_positions(keypairs) + .get_signing_keypair_positions(&keypairs.pubkeys()) .expect("account_keys doesn't contain num_required_signatures keys"); let positions: Vec = positions .iter() @@ -236,9 +235,9 @@ impl Transaction { /// Sign the transaction and place the signatures in their associated positions in `signatures` /// without checking that the positions are correct. - pub fn partial_sign_unchecked( + pub fn partial_sign_unchecked( &mut self, - keypairs: &[&T], + keypairs: &T, positions: Vec, recent_blockhash: Hash, ) { @@ -250,8 +249,9 @@ impl Transaction { .for_each(|signature| *signature = Signature::default()); } + let signatures = keypairs.sign_message(&self.message_data()); for i in 0..positions.len() { - self.signatures[positions[i]] = keypairs[i].sign_message(&self.message_data()) + self.signatures[positions[i]] = signatures[i]; } } @@ -271,23 +271,16 @@ impl Transaction { } /// Get the positions of the pubkeys in `account_keys` associated with signing keypairs - pub fn get_signing_keypair_positions( - &self, - keypairs: &[&T], - ) -> Result>> { + pub fn get_signing_keypair_positions(&self, pubkeys: &[Pubkey]) -> Result>> { if self.message.account_keys.len() < self.message.header.num_required_signatures as usize { return Err(TransactionError::InvalidAccountIndex); } let signed_keys = &self.message.account_keys[0..self.message.header.num_required_signatures as usize]; - Ok(keypairs + Ok(pubkeys .iter() - .map(|keypair| { - signed_keys - .iter() - .position(|pubkey| pubkey == &keypair.pubkey()) - }) + .map(|pubkey| signed_keys.iter().position(|x| x == pubkey)) .collect()) } @@ -338,7 +331,12 @@ impl Transaction { #[cfg(test)] mod tests { use super::*; - use crate::{hash::hash, instruction::AccountMeta, signature::Keypair, system_instruction}; + use crate::{ + hash::hash, + instruction::AccountMeta, + signature::{Keypair, KeypairUtil}, + system_instruction, + }; use bincode::{deserialize, serialize, serialized_size}; use std::mem::size_of;