From 11e6197a83c3d1ebb5ff360fffb1e73a55a622b1 Mon Sep 17 00:00:00 2001 From: Parth Date: Fri, 20 Sep 2019 16:52:17 +0530 Subject: [PATCH] require stake, vote and executable accounts to be rent exempt (#5928) * require vote account to be exempt * make stake account rent exempt * add rent exempted system instruction * use rent exemption instruction in vote and stake api * use rent exempted account while creating executable account * updating chacha golden hash as instruction data has changed * rent will be initialized for genesis bank too --- cli/src/wallet.rs | 2 +- core/src/chacha.rs | 2 +- drone/src/drone.rs | 3 +- programs/stake_api/src/stake_instruction.rs | 2 +- programs/vote_api/src/vote_instruction.rs | 9 +- runtime/src/bank.rs | 1 + runtime/src/loader_utils.rs | 2 +- runtime/src/system_instruction_processor.rs | 103 ++++++++++++++++++-- sdk/src/system_instruction.rs | 34 ++++++- sdk/src/system_transaction.rs | 49 +++++++++- sdk/src/sysvar/rent.rs | 10 ++ 11 files changed, 199 insertions(+), 18 deletions(-) diff --git a/cli/src/wallet.rs b/cli/src/wallet.rs index 6a8e47fb3..c2347f4bd 100644 --- a/cli/src/wallet.rs +++ b/cli/src/wallet.rs @@ -871,7 +871,7 @@ fn process_deploy( // Build transactions to calculate fees let mut messages: Vec<&Message> = Vec::new(); let (blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; - let mut create_account_tx = system_transaction::create_account( + let mut create_account_tx = system_transaction::create_rent_exempted_account( &config.keypair, &program_id.pubkey(), blockhash, diff --git a/core/src/chacha.rs b/core/src/chacha.rs index c668d6410..833f6cdd4 100644 --- a/core/src/chacha.rs +++ b/core/src/chacha.rs @@ -153,7 +153,7 @@ mod tests { hasher.hash(&buf[..size]); // golden needs to be updated if blob stuff changes.... - let golden: Hash = "CLGvEayebjdgnLdttFAweZE9rqVkehXqEStUifG9kiU9" + let golden: Hash = "7P2RmguCZaaBDzjvAej6RnqiA3v82s3PyaCX3uNzsjfc" .parse() .unwrap(); diff --git a/drone/src/drone.rs b/drone/src/drone.rs index 8fa863ed7..9f03e4258 100644 --- a/drone/src/drone.rs +++ b/drone/src/drone.rs @@ -396,7 +396,8 @@ mod tests { SystemInstruction::CreateAccount { lamports: 2, space: 0, - program_id: Pubkey::default() + program_id: Pubkey::default(), + require_rent_exemption: false } ); diff --git a/programs/stake_api/src/stake_instruction.rs b/programs/stake_api/src/stake_instruction.rs index 12985076f..9c7853bce 100644 --- a/programs/stake_api/src/stake_instruction.rs +++ b/programs/stake_api/src/stake_instruction.rs @@ -110,7 +110,7 @@ pub fn create_stake_account_with_lockup( custodian: &Pubkey, ) -> Vec { vec![ - system_instruction::create_account( + system_instruction::create_rent_exempted_account( from_pubkey, stake_pubkey, lamports, diff --git a/programs/vote_api/src/vote_instruction.rs b/programs/vote_api/src/vote_instruction.rs index dccd433a8..05241efc1 100644 --- a/programs/vote_api/src/vote_instruction.rs +++ b/programs/vote_api/src/vote_instruction.rs @@ -87,8 +87,13 @@ pub fn create_account( lamports: u64, ) -> Vec { let space = VoteState::size_of() as u64; - let create_ix = - system_instruction::create_account(from_pubkey, vote_pubkey, lamports, space, &id()); + let create_ix = system_instruction::create_rent_exempted_account( + from_pubkey, + vote_pubkey, + lamports, + space, + &id(), + ); let init_ix = initialize_account(vote_pubkey, node_pubkey, commission); vec![create_ix, init_ix] } diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 2f9f5ed84..d2fa08cf0 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -275,6 +275,7 @@ impl Bank { bank.update_stake_history(None); } bank.update_clock(); + bank.update_rent(); bank } diff --git a/runtime/src/loader_utils.rs b/runtime/src/loader_utils.rs index 3e4065867..85f525adc 100644 --- a/runtime/src/loader_utils.rs +++ b/runtime/src/loader_utils.rs @@ -16,7 +16,7 @@ pub fn load_program( let program_keypair = Keypair::new(); let program_pubkey = program_keypair.pubkey(); - let instruction = system_instruction::create_account( + let instruction = system_instruction::create_rent_exempted_account( &from_keypair.pubkey(), &program_pubkey, 1, diff --git a/runtime/src/system_instruction_processor.rs b/runtime/src/system_instruction_processor.rs index 7c5bd64aa..5d072c5dd 100644 --- a/runtime/src/system_instruction_processor.rs +++ b/runtime/src/system_instruction_processor.rs @@ -2,18 +2,22 @@ use log::*; use solana_sdk::account::KeyedAccount; use solana_sdk::instruction::InstructionError; use solana_sdk::pubkey::Pubkey; +use solana_sdk::rent_calculator::RentCalculator; use solana_sdk::system_instruction::{SystemError, SystemInstruction}; use solana_sdk::system_program; use solana_sdk::sysvar; +use solana_sdk::sysvar::rent; const FROM_ACCOUNT_INDEX: usize = 0; const TO_ACCOUNT_INDEX: usize = 1; +const RENT_SYSVAR_ACCOUNT_INDEX: usize = 2; fn create_system_account( keyed_accounts: &mut [KeyedAccount], lamports: u64, space: u64, program_id: &Pubkey, + rent_exemption_calculator: Option, ) -> Result<(), SystemError> { if !system_program::check_id(&keyed_accounts[FROM_ACCOUNT_INDEX].account.owner) { debug!( @@ -56,6 +60,13 @@ fn create_system_account( ); Err(SystemError::ResultWithNegativeLamports)?; } + + if let Some(calculator) = rent_exemption_calculator { + if !calculator.is_exempt(lamports, space as usize) { + Err(SystemError::InsufficientFunds)?; + } + } + keyed_accounts[FROM_ACCOUNT_INDEX].account.lamports -= lamports; keyed_accounts[TO_ACCOUNT_INDEX].account.lamports += lamports; keyed_accounts[TO_ACCOUNT_INDEX].account.owner = *program_id; @@ -107,7 +118,28 @@ pub fn process_instruction( lamports, space, program_id, - } => create_system_account(keyed_accounts, lamports, space, &program_id), + require_rent_exemption, + } => { + let rent_exemption_calculator: Option = if require_rent_exemption { + if keyed_accounts.len() < (RENT_SYSVAR_ACCOUNT_INDEX + 1) { + Err(InstructionError::InvalidInstructionData)?; + } + Some( + rent::from_keyed_account(&keyed_accounts[RENT_SYSVAR_ACCOUNT_INDEX])? + .rent_calculator, + ) + } else { + None + }; + + create_system_account( + keyed_accounts, + lamports, + space, + &program_id, + rent_exemption_calculator, + ) + } SystemInstruction::Assign { program_id } => { if !system_program::check_id(&keyed_accounts[FROM_ACCOUNT_INDEX].account.owner) { Err(InstructionError::IncorrectProgramId)?; @@ -133,6 +165,7 @@ mod tests { use solana_sdk::client::SyncClient; use solana_sdk::genesis_block::create_genesis_block; use solana_sdk::instruction::{AccountMeta, Instruction, InstructionError}; + use solana_sdk::rent_calculator::RentCalculator; use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::system_program; use solana_sdk::transaction::TransactionError; @@ -150,7 +183,7 @@ mod tests { KeyedAccount::new(&from, true, &mut from_account), KeyedAccount::new(&to, false, &mut to_account), ]; - create_system_account(&mut keyed_accounts, 50, 2, &new_program_owner).unwrap(); + create_system_account(&mut keyed_accounts, 50, 2, &new_program_owner, None).unwrap(); let from_lamports = from_account.lamports; let to_lamports = to_account.lamports; let to_owner = to_account.owner; @@ -176,7 +209,7 @@ mod tests { KeyedAccount::new(&from, true, &mut from_account), KeyedAccount::new(&to, false, &mut to_account), ]; - let result = create_system_account(&mut keyed_accounts, 150, 2, &new_program_owner); + let result = create_system_account(&mut keyed_accounts, 150, 2, &new_program_owner, None); assert_eq!(result, Err(SystemError::ResultWithNegativeLamports)); let from_lamports = from_account.lamports; assert_eq!(from_lamports, 100); @@ -199,7 +232,7 @@ mod tests { KeyedAccount::new(&from, true, &mut from_account), KeyedAccount::new(&owned_key, false, &mut owned_account), ]; - let result = create_system_account(&mut keyed_accounts, 50, 2, &new_program_owner); + let result = create_system_account(&mut keyed_accounts, 50, 2, &new_program_owner, None); assert_eq!(result, Err(SystemError::AccountAlreadyInUse)); let from_lamports = from_account.lamports; assert_eq!(from_lamports, 100); @@ -220,7 +253,7 @@ mod tests { KeyedAccount::new(&to, false, &mut to_account), ]; // fail to create a sysvar::id() owned account - let result = create_system_account(&mut keyed_accounts, 50, 2, &sysvar::id()); + let result = create_system_account(&mut keyed_accounts, 50, 2, &sysvar::id(), None); assert_eq!(result, Err(SystemError::InvalidProgramId)); let to = sysvar::fees::id(); @@ -231,7 +264,7 @@ mod tests { KeyedAccount::new(&to, false, &mut to_account), ]; // fail to create an account with a sysvar id - let result = create_system_account(&mut keyed_accounts, 50, 2, &system_program::id()); + let result = create_system_account(&mut keyed_accounts, 50, 2, &system_program::id(), None); assert_eq!(result, Err(SystemError::InvalidAccountId)); let from_lamports = from_account.lamports; @@ -256,12 +289,66 @@ mod tests { KeyedAccount::new(&from, true, &mut from_account), KeyedAccount::new(&populated_key, false, &mut populated_account), ]; - let result = create_system_account(&mut keyed_accounts, 50, 2, &new_program_owner); + let result = create_system_account(&mut keyed_accounts, 50, 2, &new_program_owner, None); assert_eq!(result, Err(SystemError::AccountAlreadyInUse)); assert_eq!(from_account.lamports, 100); assert_eq!(populated_account, unchanged_account); } + #[test] + fn test_create_rent_exempt() { + let other_program = Pubkey::new(&[9; 32]); + + let from = Pubkey::new_rand(); + let mut from_account = Account::new(1000, 0, &system_program::id()); + let to = Pubkey::new_rand(); + let mut to_account = Account::new(0, 0, &Pubkey::default()); + let rent_id = rent::id(); + let rent_calculator = RentCalculator { + lamports_per_byte_year: 50, + exemption_threshold: 2.0, + burn_percent: 0, + }; + let mut rent_sysvar_account = rent::create_account(50, &rent_calculator); + let mut keyed_accounts = vec![ + KeyedAccount::new(&from, true, &mut from_account), + KeyedAccount::new(&to, false, &mut to_account), + KeyedAccount::new(&rent_id, false, &mut rent_sysvar_account), + ]; + + // if rent exemption flag is false, then account will be created successfully. + let result = create_system_account(&mut keyed_accounts, 0, 2, &other_program, None); + assert_eq!(result, Ok(())); + + let to = Pubkey::new_rand(); + let mut to_account = Account::new(0, 0, &Pubkey::default()); + keyed_accounts[1] = KeyedAccount::new(&to, false, &mut to_account); + + // there must be sufficient amount of lamport in account to be created as rent exempted + let result = create_system_account( + &mut keyed_accounts, + 0, + 2, + &other_program, + Some(rent_calculator), + ); + assert_eq!(result, Err(SystemError::InsufficientFunds)); + + let to = Pubkey::new_rand(); + let mut to_account = Account::new(0, 0, &Pubkey::default()); + keyed_accounts[1] = KeyedAccount::new(&to, false, &mut to_account); + + // amount of lamports must be sufficient for account to be rent exempt + let result = create_system_account( + &mut keyed_accounts, + 500, + 2, + &other_program, + Some(rent_calculator), + ); + assert_eq!(result, Ok(())); + } + #[test] fn test_create_not_system_account() { let other_program = Pubkey::new(&[9; 32]); @@ -274,7 +361,7 @@ mod tests { KeyedAccount::new(&from, true, &mut from_account), KeyedAccount::new(&to, false, &mut to_account), ]; - let result = create_system_account(&mut keyed_accounts, 50, 2, &other_program); + let result = create_system_account(&mut keyed_accounts, 50, 2, &other_program, None); assert_eq!(result, Err(SystemError::SourceNotSystemAccount)); } diff --git a/sdk/src/system_instruction.rs b/sdk/src/system_instruction.rs index 0a5151d1f..0d31e3010 100644 --- a/sdk/src/system_instruction.rs +++ b/sdk/src/system_instruction.rs @@ -2,6 +2,7 @@ use crate::instruction::{AccountMeta, Instruction}; use crate::instruction_processor_utils::DecodeError; use crate::pubkey::Pubkey; use crate::system_program; +use crate::sysvar::rent; use num_derive::FromPrimitive; #[derive(Serialize, Debug, Clone, PartialEq, FromPrimitive)] @@ -11,6 +12,7 @@ pub enum SystemError { SourceNotSystemAccount, InvalidProgramId, InvalidAccountId, + InsufficientFunds, } impl DecodeError for SystemError { @@ -31,13 +33,16 @@ pub enum SystemInstruction { /// Create a new account /// * Transaction::keys[0] - source /// * Transaction::keys[1] - new account key + /// * Transaction::keys[2] - rent sysvar account key (Only required if require_rent_exemption is true) /// * lamports - number of lamports to transfer to the new account /// * space - memory to allocate if greater then zero /// * program_id - the program id of the new account + /// * require_rent_exemption - if set to true, only allow account creation if it's rent exempt CreateAccount { lamports: u64, space: u64, program_id: Pubkey, + require_rent_exemption: bool, }, /// Assign account to a program /// * Transaction::keys[0] - account to assign @@ -55,16 +60,43 @@ pub fn create_account( space: u64, program_id: &Pubkey, ) -> Instruction { - let account_metas = vec![ + generate_create_account_instruction(from_pubkey, to_pubkey, lamports, space, program_id, false) +} + +pub fn create_rent_exempted_account( + from_pubkey: &Pubkey, + to_pubkey: &Pubkey, + lamports: u64, + space: u64, + program_id: &Pubkey, +) -> Instruction { + generate_create_account_instruction(from_pubkey, to_pubkey, lamports, space, program_id, true) +} + +pub(crate) fn generate_create_account_instruction( + from_pubkey: &Pubkey, + to_pubkey: &Pubkey, + lamports: u64, + space: u64, + program_id: &Pubkey, + require_rent_exemption: bool, +) -> Instruction { + let mut account_metas = vec![ AccountMeta::new(*from_pubkey, true), AccountMeta::new(*to_pubkey, false), ]; + + if require_rent_exemption { + account_metas.push(AccountMeta::new(rent::id(), false)); + } + Instruction::new( system_program::id(), &SystemInstruction::CreateAccount { lamports, space, program_id: *program_id, + require_rent_exemption, }, account_metas, ) diff --git a/sdk/src/system_transaction.rs b/sdk/src/system_transaction.rs index cc891bfde..69bba15fb 100644 --- a/sdk/src/system_transaction.rs +++ b/sdk/src/system_transaction.rs @@ -15,10 +15,55 @@ pub fn create_account( lamports: u64, space: u64, program_id: &Pubkey, +) -> Transaction { + generate_create_account_tx( + from_keypair, + to, + recent_blockhash, + lamports, + space, + program_id, + false, + ) +} + +pub fn create_rent_exempted_account( + from_keypair: &Keypair, + to: &Pubkey, + recent_blockhash: Hash, + lamports: u64, + space: u64, + program_id: &Pubkey, +) -> Transaction { + generate_create_account_tx( + from_keypair, + to, + recent_blockhash, + lamports, + space, + program_id, + true, + ) +} + +fn generate_create_account_tx( + from_keypair: &Keypair, + to: &Pubkey, + recent_blockhash: Hash, + lamports: u64, + space: u64, + program_id: &Pubkey, + require_rent_exemption: bool, ) -> Transaction { let from_pubkey = from_keypair.pubkey(); - let create_instruction = - system_instruction::create_account(&from_pubkey, to, lamports, space, program_id); + let create_instruction = system_instruction::generate_create_account_instruction( + &from_pubkey, + to, + lamports, + space, + program_id, + require_rent_exemption, + ); let instructions = vec![create_instruction]; Transaction::new_signed_instructions(&[from_keypair], instructions, recent_blockhash) } diff --git a/sdk/src/sysvar/rent.rs b/sdk/src/sysvar/rent.rs index 10e3d7a71..e8ecbda95 100644 --- a/sdk/src/sysvar/rent.rs +++ b/sdk/src/sysvar/rent.rs @@ -49,6 +49,16 @@ pub fn create_account(lamports: u64, rent_calculator: &RentCalculator) -> Accoun .unwrap() } +use crate::account::KeyedAccount; +use crate::instruction::InstructionError; + +pub fn from_keyed_account(account: &KeyedAccount) -> Result { + if !check_id(account.unsigned_key()) { + return Err(InstructionError::InvalidArgument); + } + Rent::from_account(account.account).ok_or(InstructionError::InvalidArgument) +} + #[cfg(test)] mod tests { use super::*;