From 92ea11fca17626787a2f2dd67a5caac0c30f18bb Mon Sep 17 00:00:00 2001 From: Parth Date: Fri, 4 Oct 2019 02:52:48 +0530 Subject: [PATCH] make executable, vote and stake account rent exempt (#6017) * add missing convenience method * require vote account to be exempt * make stake account rent exempt * making executable rent exempt * rent will be initialized in genesis * add test for update_rent --- programs/bpf_loader_api/src/lib.rs | 7 ++ programs/move_loader_api/src/processor.rs | 94 +++++++++++++++++++-- programs/stake_api/src/stake_instruction.rs | 20 ++++- programs/vote_api/src/vote_instruction.rs | 15 +++- runtime/src/bank.rs | 17 +++- sdk/src/loader_instruction.rs | 7 +- sdk/src/sysvar/rent.rs | 24 ++++++ 7 files changed, 168 insertions(+), 16 deletions(-) diff --git a/programs/bpf_loader_api/src/lib.rs b/programs/bpf_loader_api/src/lib.rs index 1b5a20ecfb..f1bfd38d90 100644 --- a/programs/bpf_loader_api/src/lib.rs +++ b/programs/bpf_loader_api/src/lib.rs @@ -20,6 +20,7 @@ use solana_sdk::account::KeyedAccount; use solana_sdk::instruction::InstructionError; use solana_sdk::loader_instruction::LoaderInstruction; use solana_sdk::pubkey::Pubkey; +use solana_sdk::sysvar::rent; use std::convert::TryFrom; use std::io::prelude::*; use std::io::Error; @@ -109,10 +110,16 @@ pub fn process_instruction( keyed_accounts[0].account.data[offset..offset + len].copy_from_slice(&bytes); } LoaderInstruction::Finalize => { + if keyed_accounts.len() < 2 { + return Err(InstructionError::InvalidInstructionData); + } if keyed_accounts[0].signer_key().is_none() { warn!("key[0] did not sign the transaction"); return Err(InstructionError::GenericError); } + + rent::verify_rent_exemption(&keyed_accounts[0], &keyed_accounts[1])?; + keyed_accounts[0].account.executable = true; info!( "Finalize: account {:?}", diff --git a/programs/move_loader_api/src/processor.rs b/programs/move_loader_api/src/processor.rs index 62a86daa02..d709474d32 100644 --- a/programs/move_loader_api/src/processor.rs +++ b/programs/move_loader_api/src/processor.rs @@ -7,7 +7,7 @@ use log::*; use serde_derive::{Deserialize, Serialize}; use solana_sdk::{ account::KeyedAccount, instruction::InstructionError, loader_instruction::LoaderInstruction, - pubkey::Pubkey, + pubkey::Pubkey, sysvar::rent, }; use types::{ account_address::AccountAddress, @@ -294,11 +294,16 @@ impl MoveProcessor { } pub fn do_finalize(keyed_accounts: &mut [KeyedAccount]) -> Result<(), InstructionError> { + if keyed_accounts.len() < 2 { + return Err(InstructionError::InvalidInstructionData); + } if keyed_accounts[PROGRAM_INDEX].signer_key().is_none() { debug!("Error: key[0] did not sign the transaction"); return Err(InstructionError::GenericError); } + rent::verify_rent_exemption(&keyed_accounts[0], &keyed_accounts[1])?; + let (compiled_script, compiled_modules) = Self::deserialize_compiled_program(&keyed_accounts[PROGRAM_INDEX].account.data)?; @@ -402,6 +407,8 @@ mod tests { use super::*; use language_e2e_tests::account::AccountResource; use solana_sdk::account::Account; + use solana_sdk::rent_calculator::RentCalculator; + use solana_sdk::sysvar::rent; #[test] fn test_finalize() { @@ -410,7 +417,12 @@ mod tests { let code = "main() { return; }"; let sender_address = AccountAddress::default(); let mut program = LibraAccount::create_program(&sender_address, code, vec![]); - let mut keyed_accounts = vec![KeyedAccount::new(&program.key, true, &mut program.account)]; + let rent_id = rent::id(); + let mut rent_account = rent::create_account(1, &RentCalculator::default()); + let mut keyed_accounts = vec![ + KeyedAccount::new(&program.key, true, &mut program.account), + KeyedAccount::new(&rent_id, false, &mut rent_account), + ]; MoveProcessor::do_finalize(&mut keyed_accounts).unwrap(); let (_, _) = MoveProcessor::deserialize_verified_program(&program.account.data).unwrap(); } @@ -451,11 +463,20 @@ mod tests { let mut program = LibraAccount::create_program(&sender_address, code, vec![]); let mut genesis = LibraAccount::create_genesis(1_000_000_000); + let rent_id = rent::id(); + let mut rent_account = rent::create_account(1, &RentCalculator::default()); + let mut keyed_accounts = vec![ + KeyedAccount::new(&program.key, true, &mut program.account), + KeyedAccount::new(&rent_id, false, &mut rent_account), + ]; + + MoveProcessor::do_finalize(&mut keyed_accounts).unwrap(); + let mut keyed_accounts = vec![ KeyedAccount::new(&program.key, true, &mut program.account), KeyedAccount::new(&genesis.key, false, &mut genesis.account), ]; - MoveProcessor::do_finalize(&mut keyed_accounts).unwrap(); + MoveProcessor::do_invoke_main( &mut keyed_accounts, &bincode::serialize(&InvokeCommand::RunProgram { @@ -482,11 +503,20 @@ mod tests { let mut program = LibraAccount::create_program(&sender_address, code, vec![]); let mut genesis = LibraAccount::create_genesis(1_000_000_000); + let rent_id = rent::id(); + let mut rent_account = rent::create_account(1, &RentCalculator::default()); + let mut keyed_accounts = vec![ + KeyedAccount::new(&program.key, true, &mut program.account), + KeyedAccount::new(&rent_id, false, &mut rent_account), + ]; + + MoveProcessor::do_finalize(&mut keyed_accounts).unwrap(); + let mut keyed_accounts = vec![ KeyedAccount::new(&program.key, true, &mut program.account), KeyedAccount::new(&genesis.key, false, &mut genesis.account), ]; - MoveProcessor::do_finalize(&mut keyed_accounts).unwrap(); + assert_eq!( MoveProcessor::do_invoke_main( &mut keyed_accounts, @@ -547,13 +577,23 @@ mod tests { let (genesis, sender) = accounts.split_at_mut(GENESIS_INDEX + 1); let genesis = &mut genesis[1]; let sender = &mut sender[0]; + + let rent_id = rent::id(); + let mut rent_account = rent::create_account(1, &RentCalculator::default()); + let mut keyed_accounts = vec![ + KeyedAccount::new(&program.key, true, &mut program.account), + KeyedAccount::new(&rent_id, false, &mut rent_account), + ]; + + MoveProcessor::do_finalize(&mut keyed_accounts).unwrap(); + let mut keyed_accounts = vec![ KeyedAccount::new(&program.key, true, &mut program.account), KeyedAccount::new(&genesis.key, false, &mut genesis.account), KeyedAccount::new(&sender.key, false, &mut sender.account), KeyedAccount::new(&payee.key, false, &mut payee.account), ]; - MoveProcessor::do_finalize(&mut keyed_accounts).unwrap(); + let amount = 2; MoveProcessor::do_invoke_main( &mut keyed_accounts, @@ -610,12 +650,21 @@ mod tests { let mut payee = LibraAccount::create_unallocated(); let mut program = LibraAccount::create_program(&payee.address, code, vec![]); + let rent_id = rent::id(); + let mut rent_account = rent::create_account(1, &RentCalculator::default()); + let mut keyed_accounts = vec![ + KeyedAccount::new(&program.key, true, &mut program.account), + KeyedAccount::new(&rent_id, false, &mut rent_account), + ]; + + MoveProcessor::do_finalize(&mut keyed_accounts).unwrap(); + let mut keyed_accounts = vec![ KeyedAccount::new(&program.key, true, &mut program.account), KeyedAccount::new(&genesis.key, false, &mut genesis.account), KeyedAccount::new(&payee.key, false, &mut payee.account), ]; - MoveProcessor::do_finalize(&mut keyed_accounts).unwrap(); + MoveProcessor::do_invoke_main( &mut keyed_accounts, &bincode::serialize(&InvokeCommand::RunProgram { @@ -645,12 +694,21 @@ mod tests { let mut program = LibraAccount::create_program(&module.address, code, vec![]); let mut genesis = LibraAccount::create_genesis(1_000_000_000); + let rent_id = rent::id(); + let mut rent_account = rent::create_account(1, &RentCalculator::default()); + let mut keyed_accounts = vec![ + KeyedAccount::new(&program.key, true, &mut program.account), + KeyedAccount::new(&rent_id, false, &mut rent_account), + ]; + + MoveProcessor::do_finalize(&mut keyed_accounts).unwrap(); + let mut keyed_accounts = vec![ KeyedAccount::new(&program.key, true, &mut program.account), KeyedAccount::new(&genesis.key, false, &mut genesis.account), KeyedAccount::new(&module.key, false, &mut module.account), ]; - MoveProcessor::do_finalize(&mut keyed_accounts).unwrap(); + MoveProcessor::do_invoke_main( &mut keyed_accounts, &bincode::serialize(&InvokeCommand::RunProgram { @@ -678,12 +736,21 @@ mod tests { let mut program = LibraAccount::create_program(&module.address, &code, vec![&module.account.data]); + let rent_id = rent::id(); + let mut rent_account = rent::create_account(1, &RentCalculator::default()); + let mut keyed_accounts = vec![ + KeyedAccount::new(&program.key, true, &mut program.account), + KeyedAccount::new(&rent_id, false, &mut rent_account), + ]; + + MoveProcessor::do_finalize(&mut keyed_accounts).unwrap(); + let mut keyed_accounts = vec![ KeyedAccount::new(&program.key, true, &mut program.account), KeyedAccount::new(&genesis.key, false, &mut genesis.account), KeyedAccount::new(&module.key, false, &mut module.account), ]; - MoveProcessor::do_finalize(&mut keyed_accounts).unwrap(); + MoveProcessor::do_invoke_main( &mut keyed_accounts, &bincode::serialize(&InvokeCommand::RunProgram { @@ -711,12 +778,21 @@ mod tests { let mut program = LibraAccount::create_program(&genesis.address, code, vec![]); let mut payee = LibraAccount::create_unallocated(); + let rent_id = rent::id(); + let mut rent_account = rent::create_account(1, &RentCalculator::default()); + let mut keyed_accounts = vec![ + KeyedAccount::new(&program.key, true, &mut program.account), + KeyedAccount::new(&rent_id, false, &mut rent_account), + ]; + + MoveProcessor::do_finalize(&mut keyed_accounts).unwrap(); + let mut keyed_accounts = vec![ KeyedAccount::new(&program.key, true, &mut program.account), KeyedAccount::new(&genesis.key, false, &mut genesis.account), KeyedAccount::new(&payee.key, false, &mut payee.account), ]; - MoveProcessor::do_finalize(&mut keyed_accounts).unwrap(); + MoveProcessor::do_invoke_main( &mut keyed_accounts, &bincode::serialize(&InvokeCommand::RunProgram { diff --git a/programs/stake_api/src/stake_instruction.rs b/programs/stake_api/src/stake_instruction.rs index 707b6d77c4..b72125247a 100644 --- a/programs/stake_api/src/stake_instruction.rs +++ b/programs/stake_api/src/stake_instruction.rs @@ -12,6 +12,7 @@ use solana_sdk::{ instruction_processor_utils::DecodeError, pubkey::Pubkey, system_instruction, sysvar, + sysvar::rent, }; /// Reasons the stake might have had an error @@ -125,7 +126,10 @@ pub fn create_stake_account_with_lockup( Instruction::new( id(), &StakeInstruction::Initialize(*authorized, *lockup), - vec![AccountMeta::new(*stake_pubkey, false)], + vec![ + AccountMeta::new(*stake_pubkey, false), + AccountMeta::new(sysvar::rent::id(), false), + ], ), ] } @@ -281,7 +285,13 @@ pub fn process_instruction( // TODO: data-driven unpack and dispatch of KeyedAccounts match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? { - StakeInstruction::Initialize(authorized, lockup) => me.initialize(&authorized, &lockup), + StakeInstruction::Initialize(authorized, lockup) => { + if rest.is_empty() { + Err(InstructionError::InvalidInstructionData)?; + } + rent::verify_rent_exemption(me, &rest[0])?; + me.initialize(&authorized, &lockup) + } StakeInstruction::Authorize(authorized_pubkey, stake_authorize) => { me.authorize(&authorized_pubkey, stake_authorize, &rest) } @@ -349,7 +359,9 @@ pub fn process_instruction( mod tests { use super::*; use bincode::serialize; - use solana_sdk::{account::Account, sysvar::stake_history::StakeHistory}; + use solana_sdk::{ + account::Account, rent_calculator::RentCalculator, sysvar::stake_history::StakeHistory, + }; fn process_instruction(instruction: &Instruction) -> Result<(), InstructionError> { let mut accounts: Vec<_> = instruction @@ -364,6 +376,8 @@ mod tests { sysvar::stake_history::create_account(1, &StakeHistory::default()) } else if config::check_id(&meta.pubkey) { config::create_account(1, &config::Config::default()) + } else if sysvar::rent::check_id(&meta.pubkey) { + sysvar::rent::create_account(1, &RentCalculator::default()) } else { Account::default() } diff --git a/programs/vote_api/src/vote_instruction.rs b/programs/vote_api/src/vote_instruction.rs index 0c1fd0b72b..ad1cedf5bf 100644 --- a/programs/vote_api/src/vote_instruction.rs +++ b/programs/vote_api/src/vote_instruction.rs @@ -15,7 +15,8 @@ use solana_sdk::{ instruction::{AccountMeta, Instruction, InstructionError}, instruction_processor_utils::DecodeError, pubkey::Pubkey, - system_instruction, sysvar, + system_instruction, + sysvar::{self, rent}, }; /// Reasons the stake might have had an error @@ -65,7 +66,10 @@ pub enum VoteInstruction { } fn initialize_account(vote_pubkey: &Pubkey, vote_init: &VoteInit) -> Instruction { - let account_metas = vec![AccountMeta::new(*vote_pubkey, false)]; + let account_metas = vec![ + AccountMeta::new(*vote_pubkey, false), + AccountMeta::new(sysvar::rent::id(), false), + ]; Instruction::new( id(), &VoteInstruction::InitializeAccount(*vote_init), @@ -176,6 +180,10 @@ pub fn process_instruction( // TODO: data-driven unpack and dispatch of KeyedAccounts match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? { VoteInstruction::InitializeAccount(vote_init) => { + if rest.is_empty() { + Err(InstructionError::InvalidInstructionData)?; + } + rent::verify_rent_exemption(me, &rest[0])?; vote_state::initialize_account(me, &vote_init) } VoteInstruction::Authorize(voter_pubkey, vote_authorize) => { @@ -212,6 +220,7 @@ pub fn process_instruction( mod tests { use super::*; use solana_sdk::account::Account; + use solana_sdk::rent_calculator::RentCalculator; // these are for 100% coverage in this file #[test] @@ -231,6 +240,8 @@ mod tests { sysvar::clock::new_account(1, 0, 0, 0, 0) } else if sysvar::slot_hashes::check_id(&meta.pubkey) { sysvar::slot_hashes::create_account(1, &[]) + } else if sysvar::rent::check_id(&meta.pubkey) { + sysvar::rent::create_account(1, &RentCalculator::default()) } else { Account::default() } diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index c0e5370da4..cb0d569ee6 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -283,6 +283,7 @@ impl Bank { bank.update_stake_history(None); } bank.update_clock(); + bank.update_rent(); bank } @@ -1594,6 +1595,7 @@ mod tests { use solana_sdk::hash; use solana_sdk::instruction::InstructionError; use solana_sdk::poh_config::PohConfig; + use solana_sdk::rent_calculator::RentCalculator; use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::system_instruction; use solana_sdk::system_transaction; @@ -1611,7 +1613,7 @@ mod tests { let dummy_leader_lamports = BOOTSTRAP_LEADER_LAMPORTS; let mint_lamports = 10_000; let GenesisBlockInfo { - genesis_block, + mut genesis_block, mint_keypair, voting_keypair, .. @@ -1620,12 +1622,25 @@ mod tests { &dummy_leader_pubkey, dummy_leader_lamports, ); + genesis_block.rent_calculator = RentCalculator { + lamports_per_byte_year: 5, + exemption_threshold: 1.2, + burn_percent: 5, + }; + let bank = Bank::new(&genesis_block); assert_eq!(bank.get_balance(&mint_keypair.pubkey()), mint_lamports); assert_eq!( bank.get_balance(&voting_keypair.pubkey()), dummy_leader_lamports /* 1 token goes to the vote account associated with dummy_leader_lamports */ ); + + let rent_account = bank.get_account(&rent::id()).unwrap(); + let rent_sysvar = rent::Rent::from_account(&rent_account).unwrap(); + + assert_eq!(rent_sysvar.rent_calculator.burn_percent, 5); + assert_eq!(rent_sysvar.rent_calculator.exemption_threshold, 1.2); + assert_eq!(rent_sysvar.rent_calculator.lamports_per_byte_year, 5); } #[test] diff --git a/sdk/src/loader_instruction.rs b/sdk/src/loader_instruction.rs index 77b0c49daa..2d7d0af01e 100644 --- a/sdk/src/loader_instruction.rs +++ b/sdk/src/loader_instruction.rs @@ -1,5 +1,6 @@ use crate::instruction::{AccountMeta, Instruction}; use crate::pubkey::Pubkey; +use crate::sysvar::rent; #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum LoaderInstruction { @@ -15,6 +16,7 @@ pub enum LoaderInstruction { /// bit of the Account /// /// * key[0] - the account to prepare for execution + /// * key[1] - rent sysvar account /// /// The transaction must be signed by key[0] Finalize, @@ -40,6 +42,9 @@ pub fn write( } pub fn finalize(account_pubkey: &Pubkey, program_id: &Pubkey) -> Instruction { - let account_metas = vec![AccountMeta::new(*account_pubkey, true)]; + let account_metas = vec![ + AccountMeta::new(*account_pubkey, true), + AccountMeta::new(rent::id(), false), + ]; Instruction::new(*program_id, &LoaderInstruction::Finalize, account_metas) } diff --git a/sdk/src/sysvar/rent.rs b/sdk/src/sysvar/rent.rs index 10e3d7a712..84b6de5c00 100644 --- a/sdk/src/sysvar/rent.rs +++ b/sdk/src/sysvar/rent.rs @@ -49,6 +49,30 @@ 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) +} + +pub fn verify_rent_exemption( + account: &KeyedAccount, + rent_sysvar_account: &KeyedAccount, +) -> Result<(), InstructionError> { + if !from_keyed_account(rent_sysvar_account)? + .rent_calculator + .is_exempt(account.account.lamports, account.account.data.len()) + { + Err(InstructionError::InsufficientFunds) + } else { + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*;