From d9e18a71ec54f60f2b7decf60a3e2371a9f43b93 Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Tue, 7 May 2019 15:00:54 -0700 Subject: [PATCH] Pay program loading fees from a system account (#4190) --- client/src/rpc_client.rs | 24 ++++++++++++------------ install/src/command.rs | 4 ++-- runtime/src/loader_utils.rs | 8 +++++--- sdk/src/message.rs | 33 +++++++++++++++++++++++++++++++++ wallet/src/wallet.rs | 34 +++++++++++++++++++--------------- 5 files changed, 71 insertions(+), 32 deletions(-) diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index cf27c4d4d2..5899338beb 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -10,7 +10,7 @@ use serde_json::{json, Value}; use solana_sdk::account::Account; use solana_sdk::hash::Hash; use solana_sdk::pubkey::Pubkey; -use solana_sdk::signature::{Keypair, KeypairUtil, Signature}; +use solana_sdk::signature::{KeypairUtil, Signature}; use solana_sdk::timing::{DEFAULT_TICKS_PER_SLOT, NUM_TICKS_PER_SECOND}; use solana_sdk::transaction::{self, Transaction, TransactionError}; use std::error; @@ -78,7 +78,7 @@ impl RpcClient { pub fn send_and_confirm_transaction( &self, transaction: &mut Transaction, - signer: &T, + signer_keys: &[&T], ) -> Result { let mut send_retries = 5; loop { @@ -106,7 +106,7 @@ impl RpcClient { Ok(_) => return Ok(signature_str), Err(TransactionError::AccountInUse) => { // Fetch a new blockhash and re-sign the transaction before sending it again - self.resign_transaction(transaction, signer)?; + self.resign_transaction(transaction, signer_keys)?; send_retries - 1 } Err(_) => 0, @@ -127,10 +127,10 @@ impl RpcClient { } } - pub fn send_and_confirm_transactions( + pub fn send_and_confirm_transactions( &self, mut transactions: Vec, - signer: &Keypair, + signer_keys: &[&T], ) -> Result<(), Box> { let mut send_retries = 5; loop { @@ -192,7 +192,7 @@ impl RpcClient { transactions = transactions_signatures .into_iter() .map(|(mut transaction, _)| { - transaction.sign(&[signer], blockhash); + transaction.sign(signer_keys, blockhash); transaction }) .collect(); @@ -202,10 +202,10 @@ impl RpcClient { pub fn resign_transaction( &self, tx: &mut Transaction, - signer_key: &T, + signer_keys: &[&T], ) -> Result<(), ClientError> { let blockhash = self.get_new_blockhash(&tx.message().recent_blockhash)?; - tx.sign(&[signer_key], blockhash); + tx.sign(signer_keys, blockhash); Ok(()) } @@ -710,15 +710,15 @@ mod tests { let blockhash = Hash::default(); let mut tx = system_transaction::create_user_account(&key, &to, 50, blockhash, 0); - let result = rpc_client.send_and_confirm_transaction(&mut tx, &key); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&key]); result.unwrap(); let rpc_client = RpcClient::new_mock("account_in_use".to_string()); - let result = rpc_client.send_and_confirm_transaction(&mut tx, &key); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&key]); assert!(result.is_err()); let rpc_client = RpcClient::new_mock("fails".to_string()); - let result = rpc_client.send_and_confirm_transaction(&mut tx, &key); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&key]); assert!(result.is_err()); } @@ -735,7 +735,7 @@ mod tests { let prev_tx = system_transaction::create_user_account(&key, &to, 50, blockhash, 0); let mut tx = system_transaction::create_user_account(&key, &to, 50, blockhash, 0); - rpc_client.resign_transaction(&mut tx, &key).unwrap(); + rpc_client.resign_transaction(&mut tx, &[&key]).unwrap(); assert_ne!(prev_tx, tx); assert_ne!(prev_tx.signatures, tx.signatures); diff --git a/install/src/command.rs b/install/src/command.rs index 1d2ca06dfb..e98841addf 100644 --- a/install/src/command.rs +++ b/install/src/command.rs @@ -206,7 +206,7 @@ fn new_update_manifest( let mut transaction = Transaction::new_unsigned_instructions(vec![new_account]); transaction.sign(&[from_keypair], recect_blockhash); - rpc_client.send_and_confirm_transaction(&mut transaction, from_keypair)?; + rpc_client.send_and_confirm_transaction(&mut transaction, &[from_keypair])?; } Ok(()) } @@ -227,7 +227,7 @@ fn store_update_manifest( ); let mut transaction = Transaction::new_unsigned_instructions(vec![new_store]); transaction.sign(&[from_keypair, update_manifest_keypair], recect_blockhash); - rpc_client.send_and_confirm_transaction(&mut transaction, from_keypair)?; + rpc_client.send_and_confirm_transaction(&mut transaction, &[from_keypair])?; Ok(()) } diff --git a/runtime/src/loader_utils.rs b/runtime/src/loader_utils.rs index f96aa066cd..57ccfccc42 100644 --- a/runtime/src/loader_utils.rs +++ b/runtime/src/loader_utils.rs @@ -3,9 +3,9 @@ use serde::Serialize; use solana_sdk::client::SyncClient; use solana_sdk::instruction::{AccountMeta, Instruction}; use solana_sdk::loader_instruction; +use solana_sdk::message::Message; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::{Keypair, KeypairUtil}; - use solana_sdk::system_instruction; pub fn load_program( @@ -33,15 +33,17 @@ pub fn load_program( for chunk in program.chunks(chunk_size) { let instruction = loader_instruction::write(&program_pubkey, loader_id, offset, chunk.to_vec()); + let message = Message::new_with_payer(vec![instruction], Some(&from_keypair.pubkey())); bank_client - .send_instruction(&program_keypair, instruction) + .send_message(&[from_keypair, &program_keypair], message) .unwrap(); offset += chunk_size as u32; } let instruction = loader_instruction::finalize(&program_pubkey, loader_id); + let message = Message::new_with_payer(vec![instruction], Some(&from_keypair.pubkey())); bank_client - .send_instruction(&program_keypair, instruction) + .send_message(&[from_keypair, &program_keypair], message) .unwrap(); program_pubkey diff --git a/sdk/src/message.rs b/sdk/src/message.rs index 654bad71b1..c62c2a9d73 100644 --- a/sdk/src/message.rs +++ b/sdk/src/message.rs @@ -109,8 +109,17 @@ impl Message { } pub fn new(instructions: Vec) -> Self { + Self::new_with_payer(instructions, None) + } + + pub fn new_with_payer(instructions: Vec, payer: Option<&Pubkey>) -> Self { let program_ids = get_program_ids(&instructions); let (mut signed_keys, unsigned_keys) = get_keys(&instructions); + if let Some(payer) = payer { + if signed_keys.is_empty() || signed_keys[0] != *payer { + signed_keys.insert(0, *payer); + } + } let num_required_signatures = signed_keys.len() as u8; signed_keys.extend(&unsigned_keys); let instructions = compile_instructions(instructions, &signed_keys, &program_ids); @@ -266,4 +275,28 @@ mod tests { CompiledInstruction::new(0, &0, vec![0]) ); } + + #[test] + fn test_message_payer_first() { + let program_id = Pubkey::default(); + let payer = Pubkey::new_rand(); + let id0 = Pubkey::default(); + + let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]); + let message = Message::new_with_payer(vec![ix], Some(&payer)); + assert_eq!(message.num_required_signatures, 1); + + let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]); + let message = Message::new_with_payer(vec![ix], Some(&payer)); + assert_eq!(message.num_required_signatures, 2); + + let ix = Instruction::new( + program_id, + &0, + vec![AccountMeta::new(payer, true), AccountMeta::new(id0, true)], + ); + let message = Message::new_with_payer(vec![ix], Some(&payer)); + assert_eq!(message.num_required_signatures, 2); + } + } diff --git a/wallet/src/wallet.rs b/wallet/src/wallet.rs index 83c48f8125..15f341466c 100644 --- a/wallet/src/wallet.rs +++ b/wallet/src/wallet.rs @@ -20,6 +20,7 @@ use solana_sdk::hash::Hash; use solana_sdk::instruction::InstructionError; use solana_sdk::instruction_processor_utils::DecodeError; use solana_sdk::loader_instruction; +use solana_sdk::message::Message; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::{Keypair, KeypairUtil, Signature}; use solana_sdk::system_instruction::SystemError; @@ -31,7 +32,7 @@ use std::io::Read; use std::net::{IpAddr, SocketAddr}; use std::{error, fmt, mem}; -const USERDATA_CHUNK_SIZE: usize = 256; +const USERDATA_CHUNK_SIZE: usize = 229; // Keep program chunks under PACKET_DATA_SIZE #[derive(Debug, PartialEq)] pub enum WalletCommand { @@ -352,7 +353,7 @@ fn process_authorize_voter( )]; let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash); - let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair)?; + let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair])?; Ok(signature_str.to_string()) } @@ -373,7 +374,7 @@ fn process_create_staking( lamports, ); let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash); - let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair)?; + let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair])?; Ok(signature_str.to_string()) } @@ -452,12 +453,13 @@ fn process_deploy( 0, ); trace!("Creating program account"); - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); log_instruction_custom_error::(result).map_err(|_| { WalletError::DynamicProgramError("Program allocate space failed".to_string()) })?; trace!("Writing program data"); + let signers = [&config.keypair, &program_id]; let write_transactions: Vec<_> = program_data .chunks(USERDATA_CHUNK_SIZE) .zip(0..) @@ -468,16 +470,18 @@ fn process_deploy( (i * USERDATA_CHUNK_SIZE) as u32, chunk.to_vec(), ); - Transaction::new_signed_instructions(&[&program_id], vec![instruction], blockhash) + let message = Message::new_with_payer(vec![instruction], Some(&signers[0].pubkey())); + Transaction::new(&signers, message, blockhash) }) .collect(); - rpc_client.send_and_confirm_transactions(write_transactions, &program_id)?; + rpc_client.send_and_confirm_transactions(write_transactions, &signers)?; trace!("Finalizing program account"); let instruction = loader_instruction::finalize(&program_id.pubkey(), &bpf_loader::id()); - let mut tx = Transaction::new_signed_instructions(&[&program_id], vec![instruction], blockhash); + let message = Message::new_with_payer(vec![instruction], Some(&signers[0].pubkey())); + let mut tx = Transaction::new(&signers, message, blockhash); rpc_client - .send_and_confirm_transaction(&mut tx, &program_id) + .send_and_confirm_transaction(&mut tx, &signers) .map_err(|_| { WalletError::DynamicProgramError("Program finalize transaction failed".to_string()) })?; @@ -502,7 +506,7 @@ fn process_pay( if timestamp == None && *witnesses == None { let mut tx = system_transaction::transfer(&config.keypair, to, lamports, blockhash, 0); - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); let signature_str = log_instruction_custom_error::(result)?; Ok(signature_str.to_string()) } else if *witnesses == None { @@ -525,7 +529,7 @@ fn process_pay( lamports, ); let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, blockhash); - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); let signature_str = log_instruction_custom_error::(result)?; Ok(json!({ @@ -556,7 +560,7 @@ fn process_pay( lamports, ); let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, blockhash); - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); let signature_str = log_instruction_custom_error::(result)?; Ok(json!({ @@ -577,7 +581,7 @@ fn process_cancel(rpc_client: &RpcClient, config: &WalletConfig, pubkey: &Pubkey &config.keypair.pubkey(), ); let mut tx = Transaction::new_signed_instructions(&[&config.keypair], vec![ix], blockhash); - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); let signature_str = log_instruction_custom_error::(result)?; Ok(signature_str.to_string()) } @@ -605,7 +609,7 @@ fn process_time_elapsed( let ix = budget_instruction::apply_timestamp(&config.keypair.pubkey(), pubkey, to, dt); let mut tx = Transaction::new_signed_instructions(&[&config.keypair], vec![ix], blockhash); - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); let signature_str = log_instruction_custom_error::(result)?; Ok(signature_str.to_string()) @@ -627,7 +631,7 @@ fn process_witness( let blockhash = rpc_client.get_recent_blockhash()?; let ix = budget_instruction::apply_signature(&config.keypair.pubkey(), pubkey, to); let mut tx = Transaction::new_signed_instructions(&[&config.keypair], vec![ix], blockhash); - let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); let signature_str = log_instruction_custom_error::(result)?; Ok(signature_str.to_string()) @@ -774,7 +778,7 @@ pub fn request_and_confirm_airdrop( let blockhash = rpc_client.get_recent_blockhash()?; let keypair = DroneKeypair::new_keypair(drone_addr, to_pubkey, lamports, blockhash)?; let mut tx = keypair.airdrop_transaction(); - let result = rpc_client.send_and_confirm_transaction(&mut tx, &keypair); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&keypair]); log_instruction_custom_error::(result)?; Ok(()) }