From 281deae102feb50b6159bb95561c907374971604 Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Mon, 8 Jul 2019 18:33:56 -0500 Subject: [PATCH] Update config program to accommodate multiple signers (#4946) * Update config program to accommodate multiple signers * Update install CLI * Remove account_type u32; add handling for unsigned keys in list * ConfigKeys doc --- install/src/command.rs | 9 +- programs/config_api/Cargo.toml | 1 - programs/config_api/src/config_instruction.rs | 38 +++++- programs/config_api/src/config_processor.rs | 124 ++++++++++++++++-- 4 files changed, 155 insertions(+), 17 deletions(-) diff --git a/install/src/command.rs b/install/src/command.rs index 590ef5e40..fa1a113a2 100644 --- a/install/src/command.rs +++ b/install/src/command.rs @@ -6,7 +6,7 @@ use console::{style, Emoji}; use indicatif::{ProgressBar, ProgressStyle}; use ring::digest::{Context, Digest, SHA256}; use solana_client::rpc_client::RpcClient; -use solana_config_api::config_instruction; +use solana_config_api::config_instruction::{self, ConfigKeys}; use solana_sdk::message::Message; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil, Signable}; @@ -203,7 +203,8 @@ fn new_update_manifest( let new_account = config_instruction::create_account::( &from_keypair.pubkey(), &update_manifest_keypair.pubkey(), - 1, // lamports + 1, // lamports + vec![], // additional keys ); let mut transaction = Transaction::new_unsigned_instructions(vec![new_account]); transaction.sign(&[from_keypair], recent_blockhash); @@ -225,6 +226,7 @@ fn store_update_manifest( let signers = [from_keypair, update_manifest_keypair]; let instruction = config_instruction::store::( &update_manifest_keypair.pubkey(), + vec![], // additional keys update_manifest, ); @@ -239,9 +241,10 @@ fn get_update_manifest( rpc_client: &RpcClient, update_manifest_pubkey: &Pubkey, ) -> Result { - let data = rpc_client + let mut data = rpc_client .get_account_data(update_manifest_pubkey) .map_err(|err| format!("Unable to fetch update manifest: {}", err))?; + data.split_off(ConfigKeys::serialized_size(vec![])); let signed_update_manifest = SignedUpdateManifest::deserialize(update_manifest_pubkey, &data) diff --git a/programs/config_api/Cargo.toml b/programs/config_api/Cargo.toml index 305c6353f..f08cf8f8b 100644 --- a/programs/config_api/Cargo.toml +++ b/programs/config_api/Cargo.toml @@ -22,4 +22,3 @@ solana-runtime = { path = "../../runtime", version = "0.17.0" } [lib] crate-type = ["lib"] name = "solana_config_api" - diff --git a/programs/config_api/src/config_instruction.rs b/programs/config_api/src/config_instruction.rs index c1690a6b3..34cba51e5 100644 --- a/programs/config_api/src/config_instruction.rs +++ b/programs/config_api/src/config_instruction.rs @@ -1,26 +1,56 @@ use crate::id; use crate::ConfigState; +use bincode::serialize; +use serde_derive::{Deserialize, Serialize}; use solana_sdk::instruction::{AccountMeta, Instruction}; use solana_sdk::pubkey::Pubkey; +use solana_sdk::short_vec; use solana_sdk::system_instruction; +/// A collection of keys to be stored in Config account data. +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct ConfigKeys { + // Each key tuple comprises a unique `Pubkey` identifier, + // and `bool` whether that key is a signer of the data + #[serde(with = "short_vec")] + pub keys: Vec<(Pubkey, bool)>, +} + +impl ConfigKeys { + pub fn serialized_size(keys: Vec<(Pubkey, bool)>) -> usize { + serialize(&ConfigKeys { keys }) + .unwrap_or_else(|_| vec![]) + .len() + } +} + /// Create a new, empty configuration account pub fn create_account( from_account_pubkey: &Pubkey, config_account_pubkey: &Pubkey, lamports: u64, + keys: Vec<(Pubkey, bool)>, ) -> Instruction { + let space = T::max_space() + ConfigKeys::serialized_size(keys) as u64; system_instruction::create_account( from_account_pubkey, config_account_pubkey, lamports, - T::max_space(), + space, &id(), ) } /// Store new data in a configuration account -pub fn store(config_account_pubkey: &Pubkey, data: &T) -> Instruction { - let account_metas = vec![AccountMeta::new(*config_account_pubkey, true)]; - Instruction::new(id(), data, account_metas) +pub fn store( + config_account_pubkey: &Pubkey, + keys: Vec<(Pubkey, bool)>, + data: &T, +) -> Instruction { + let mut account_metas = vec![AccountMeta::new(*config_account_pubkey, true)]; + for (signer_pubkey, _) in keys.iter().filter(|(_, is_signer)| *is_signer) { + account_metas.push(AccountMeta::new(*signer_pubkey, true)); + } + let account_data = (ConfigKeys { keys }, data); + Instruction::new(id(), &account_data, account_metas) } diff --git a/programs/config_api/src/config_processor.rs b/programs/config_api/src/config_processor.rs index ffe157696..ea99790a5 100644 --- a/programs/config_api/src/config_processor.rs +++ b/programs/config_api/src/config_processor.rs @@ -1,5 +1,7 @@ //! Config program +use crate::config_instruction::ConfigKeys; +use bincode::deserialize; use log::*; use solana_sdk::account::KeyedAccount; use solana_sdk::instruction::InstructionError; @@ -15,12 +17,34 @@ pub fn process_instruction( Err(InstructionError::MissingRequiredSignature)?; } + let key_list: ConfigKeys = deserialize(data).unwrap(); + for (i, (signer, _)) in key_list + .keys + .iter() + .filter(|(_, is_signer)| *is_signer) + .enumerate() + { + let account_index = i + 1; + let signer_account = keyed_accounts[account_index].signer_key(); + if signer_account.is_none() { + error!("account[{:?}].signer_key().is_none()", account_index); + Err(InstructionError::MissingRequiredSignature)?; + } + if signer_account.unwrap() != signer { + error!( + "account[{:?}].signer_key() does not match Config data)", + account_index + ); + Err(InstructionError::MissingRequiredSignature)?; + } + } + if keyed_accounts[0].account.data.len() < data.len() { error!("instruction data too large"); Err(InstructionError::InvalidInstructionData)?; } - keyed_accounts[0].account.data[0..data.len()].copy_from_slice(data); + keyed_accounts[0].account.data[0..data.len()].copy_from_slice(&data); Ok(()) } @@ -64,7 +88,11 @@ mod tests { (bank, mint_keypair) } - fn create_config_account(bank: Bank, mint_keypair: &Keypair) -> (BankClient, Keypair) { + fn create_config_account( + bank: Bank, + mint_keypair: &Keypair, + keys: Vec<(Pubkey, bool)>, + ) -> (BankClient, Keypair) { let config_keypair = Keypair::new(); let config_pubkey = config_keypair.pubkey(); @@ -76,6 +104,7 @@ mod tests { &mint_keypair.pubkey(), &config_pubkey, 1, + keys, ), ) .expect("new_account"); @@ -87,7 +116,7 @@ mod tests { fn test_process_create_ok() { solana_logger::setup(); let (bank, mint_keypair) = create_bank(10_000); - let (bank_client, config_keypair) = create_config_account(bank, &mint_keypair); + let (bank_client, config_keypair) = create_config_account(bank, &mint_keypair, vec![]); let config_account_data = bank_client .get_account_data(&config_keypair.pubkey()) .unwrap() @@ -102,13 +131,16 @@ mod tests { fn test_process_store_ok() { solana_logger::setup(); let (bank, mint_keypair) = create_bank(10_000); - let (bank_client, config_keypair) = create_config_account(bank, &mint_keypair); + let keys = vec![]; + let (bank_client, config_keypair) = + create_config_account(bank, &mint_keypair, keys.clone()); let config_pubkey = config_keypair.pubkey(); let my_config = MyConfig::new(42); - let instruction = config_instruction::store(&config_pubkey, &my_config); + let instruction = config_instruction::store(&config_pubkey, keys.clone(), &my_config); let message = Message::new_with_payer(vec![instruction], Some(&mint_keypair.pubkey())); + bank_client .send_message(&[&mint_keypair, &config_keypair], message) .unwrap(); @@ -117,6 +149,8 @@ mod tests { .get_account_data(&config_pubkey) .unwrap() .unwrap(); + let meta_length = ConfigKeys::serialized_size(keys); + let config_account_data = &config_account_data[meta_length..config_account_data.len()]; assert_eq!( my_config, MyConfig::deserialize(&config_account_data).unwrap() @@ -127,12 +161,12 @@ mod tests { fn test_process_store_fail_instruction_data_too_large() { solana_logger::setup(); let (bank, mint_keypair) = create_bank(10_000); - let (bank_client, config_keypair) = create_config_account(bank, &mint_keypair); + let (bank_client, config_keypair) = create_config_account(bank, &mint_keypair, vec![]); let config_pubkey = config_keypair.pubkey(); let my_config = MyConfig::new(42); - let mut instruction = config_instruction::store(&config_pubkey, &my_config); + let mut instruction = config_instruction::store(&config_pubkey, vec![], &my_config); instruction.data = vec![0; 123]; // <-- Replace data with a vector that's too large let message = Message::new(vec![instruction]); bank_client @@ -148,13 +182,13 @@ mod tests { let system_pubkey = system_keypair.pubkey(); bank.transfer(42, &mint_keypair, &system_pubkey).unwrap(); - let (bank_client, config_keypair) = create_config_account(bank, &mint_keypair); + let (bank_client, config_keypair) = create_config_account(bank, &mint_keypair, vec![]); let config_pubkey = config_keypair.pubkey(); let transfer_instruction = system_instruction::transfer(&system_pubkey, &Pubkey::new_rand(), 42); let my_config = MyConfig::new(42); - let mut store_instruction = config_instruction::store(&config_pubkey, &my_config); + let mut store_instruction = config_instruction::store(&config_pubkey, vec![], &my_config); store_instruction.accounts[0].is_signer = false; // <----- not a signer let message = Message::new(vec![transfer_instruction, store_instruction]); @@ -162,4 +196,76 @@ mod tests { .send_message(&[&system_keypair], message) .unwrap_err(); } + + #[test] + fn test_process_store_with_additional_signers() { + solana_logger::setup(); + let (bank, mint_keypair) = create_bank(10_000); + let pubkey = Pubkey::new_rand(); + let signer0 = Keypair::new(); + let signer1 = Keypair::new(); + let keys = vec![ + (pubkey, false), + (signer0.pubkey(), true), + (signer1.pubkey(), true), + ]; + let (bank_client, config_keypair) = + create_config_account(bank, &mint_keypair, keys.clone()); + let config_pubkey = config_keypair.pubkey(); + + let my_config = MyConfig::new(42); + + let instruction = config_instruction::store(&config_pubkey, keys.clone(), &my_config); + let message = Message::new_with_payer(vec![instruction], Some(&mint_keypair.pubkey())); + + bank_client + .send_message( + &[&mint_keypair, &config_keypair, &signer0, &signer1], + message, + ) + .unwrap(); + + let config_account_data = bank_client + .get_account_data(&config_pubkey) + .unwrap() + .unwrap(); + let meta_length = ConfigKeys::serialized_size(keys.clone()); + let meta_data: ConfigKeys = deserialize(&config_account_data[0..meta_length]).unwrap(); + assert_eq!(meta_data.keys, keys); + let config_account_data = &config_account_data[meta_length..config_account_data.len()]; + assert_eq!( + my_config, + MyConfig::deserialize(&config_account_data).unwrap() + ); + } + + #[test] + fn test_process_store_with_bad_additional_signer() { + solana_logger::setup(); + let (bank, mint_keypair) = create_bank(10_000); + let signer0 = Keypair::new(); + let signer1 = Keypair::new(); + let keys = vec![(signer0.pubkey(), true)]; + let (bank_client, config_keypair) = + create_config_account(bank, &mint_keypair, keys.clone()); + let config_pubkey = config_keypair.pubkey(); + + let my_config = MyConfig::new(42); + + // Config-data pubkey doesn't match signer + let instruction = config_instruction::store(&config_pubkey, keys.clone(), &my_config); + let mut message = + Message::new_with_payer(vec![instruction.clone()], Some(&mint_keypair.pubkey())); + message.account_keys[2] = signer1.pubkey(); + bank_client + .send_message(&[&mint_keypair, &config_keypair, &signer1], message) + .unwrap_err(); + + // Config-data pubkey not a signer + let mut message = Message::new_with_payer(vec![instruction], Some(&mint_keypair.pubkey())); + message.header.num_required_signatures = 2; + bank_client + .send_message(&[&mint_keypair, &config_keypair], message) + .unwrap_err(); + } }