From 685a815d137bcd355bbd78753bc40185913657fe Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 8 Feb 2023 18:55:43 +0100 Subject: [PATCH 01/23] Implements block gas Date: Wed Feb 8 18:55:43 2023 +0100 --- apps/src/lib/config/genesis.rs | 16 +- .../lib/node/ledger/shell/finalize_block.rs | 415 +++++++++++------- apps/src/lib/node/ledger/shell/governance.rs | 14 +- apps/src/lib/node/ledger/shell/init_chain.rs | 4 + apps/src/lib/node/ledger/shell/mod.rs | 172 ++++++-- .../lib/node/ledger/shell/prepare_proposal.rs | 23 +- .../lib/node/ledger/shell/process_proposal.rs | 191 ++++++-- apps/src/lib/wasm_loader/mod.rs | 20 + core/src/ledger/gas.rs | 204 +++++---- core/src/ledger/parameters/mod.rs | 34 ++ core/src/ledger/parameters/storage.rs | 22 + core/src/ledger/storage/mod.rs | 4 + .../storage_api/collections/lazy_map.rs | 10 +- core/src/ledger/testnet_pow.rs | 18 +- core/src/types/internal.rs | 8 +- genesis/dev.toml | 2 + genesis/e2e-tests-single-node.toml | 4 + shared/src/ledger/ibc/vp/mod.rs | 200 +++------ shared/src/ledger/protocol/mod.rs | 202 ++++++--- shared/src/ledger/queries/mod.rs | 64 ++- shared/src/ledger/queries/shell.rs | 22 +- shared/src/vm/host_env.rs | 9 +- shared/src/vm/wasm/run.rs | 27 +- tests/src/native_vp/mod.rs | 4 +- tests/src/native_vp/pos.rs | 51 ++- tests/src/vm_host_env/ibc.rs | 4 +- tests/src/vm_host_env/mod.rs | 34 +- tests/src/vm_host_env/tx.rs | 7 +- tests/src/vm_host_env/vp.rs | 2 +- 29 files changed, 1172 insertions(+), 615 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 922b0c445f..41db5d81cb 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -1,6 +1,6 @@ //! The parameters used for the chain's genesis -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; #[cfg(not(feature = "dev"))] use std::path::Path; @@ -24,7 +24,7 @@ use rust_decimal::Decimal; /// Genesis configuration file format pub mod genesis_config { use std::array::TryFromSliceError; - use std::collections::HashMap; + use std::collections::{BTreeMap, HashMap}; use std::convert::TryInto; use std::path::Path; use std::str::FromStr; @@ -247,6 +247,8 @@ pub mod genesis_config { /// room for header data, evidence and protobuf /// serialization overhead in Tendermint blocks. pub max_proposal_bytes: ProposalBytes, + /// Max block gas + pub max_block_gas: u64, /// Minimum number of blocks per epoch. // XXX: u64 doesn't work with toml-rs! pub min_num_of_blocks: u64, @@ -270,6 +272,8 @@ pub mod genesis_config { #[cfg(not(feature = "mainnet"))] /// Fix wrapper tx fees pub wrapper_tx_fees: Option, + /// Gas table + pub gas_table: BTreeMap, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -607,6 +611,7 @@ pub mod genesis_config { ) .into(), max_proposal_bytes: parameters.max_proposal_bytes, + max_block_gas: parameters.max_block_gas, vp_whitelist: parameters.vp_whitelist.unwrap_or_default(), tx_whitelist: parameters.tx_whitelist.unwrap_or_default(), implicit_vp_code_path, @@ -617,6 +622,7 @@ pub mod genesis_config { staked_ratio: Decimal::ZERO, pos_inflation_amount: 0, wrapper_tx_fees: parameters.wrapper_tx_fees, + gas_table: parameters.gas_table, }; let GovernanceParamsConfig { @@ -840,6 +846,8 @@ pub struct ImplicitAccount { pub struct Parameters { // Max payload size, in bytes, for a tx batch proposal. pub max_proposal_bytes: ProposalBytes, + /// Max block gas + pub max_block_gas: u64, /// Epoch duration pub epoch_duration: EpochDuration, /// Maximum expected time per block @@ -865,6 +873,8 @@ pub struct Parameters { /// Fixed Wrapper tx fees #[cfg(not(feature = "mainnet"))] pub wrapper_tx_fees: Option, + /// Gas table + pub gas_table: BTreeMap, } #[cfg(not(feature = "dev"))] @@ -951,6 +961,7 @@ pub fn genesis(num_validators: u64) -> Genesis { }, max_expected_time_per_block: namada::types::time::DurationSecs(30), max_proposal_bytes: Default::default(), + max_block_gas: u64::MAX, //FIXME: adjust this value vp_whitelist: vec![], tx_whitelist: vec![], implicit_vp_code_path: vp_implicit_path.into(), @@ -962,6 +973,7 @@ pub fn genesis(num_validators: u64) -> Genesis { staked_ratio: dec!(0.0), pos_inflation_amount: 0, wrapper_tx_fees: Some(token::Amount::whole(0)), + gas_table: BTreeMap::default(), }; let albert = EstablishedAccount { address: wallet::defaults::albert_address(), diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index f252b03cbc..371822eaf5 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -3,6 +3,9 @@ use std::collections::HashMap; use data_encoding::HEXUPPER; +use std::collections::BTreeMap; + +use namada::ledger::gas::TxGasMeter; use namada::ledger::parameters::storage as params_storage; use namada::ledger::pos::types::{decimal_mult_u64, into_tm_voting_power}; use namada::ledger::pos::{namada_proof_of_stake, staking_token_address}; @@ -18,9 +21,11 @@ use namada::proof_of_stake::{ write_last_block_proposer_address, }; use namada::types::address::Address; +use namada::types::hash; use namada::types::key::tm_raw_hash_to_string; use namada::types::storage::{BlockHash, BlockResults, Epoch, Header}; use namada::types::token::{total_supply_key, Amount}; +use namada::types::transaction::WrapperTx; use rust_decimal::prelude::Decimal; use super::governance::execute_governance_proposals; @@ -60,9 +65,6 @@ where &mut self, req: shim::request::FinalizeBlock, ) -> Result { - // Reset the gas meter before we start - self.gas_meter.reset(); - let mut response = shim::response::FinalizeBlock::default(); // Begin the new block and check if a new epoch has begun @@ -79,6 +81,9 @@ where "Block height: {height}, epoch: {current_epoch}, new epoch: \ {new_epoch}." ); + let gas_table: BTreeMap = self + .read_storage_key(¶meters::storage::get_gas_table_storage_key()) + .expect("Missing gas table in storage"); if new_epoch { namada::ledger::storage::update_allowed_conversions( @@ -86,7 +91,7 @@ where )?; let _proposals_result = - execute_governance_proposals(self, &mut response)?; + execute_governance_proposals(self, &mut response, &gas_table)?; // Copy the new_epoch + pipeline_len - 1 validator set into // new_epoch + pipeline_len @@ -105,7 +110,6 @@ where // `copy_validator_sets_and_positions` if we're starting a new epoch self.slash(); - let wrapper_fees = self.get_wrapper_tx_fees(); let mut stats = InternalStats::default(); // Tracks the accepted transactions @@ -121,7 +125,6 @@ where ); continue; }; - let tx_length = processed_tx.tx.len(); // If [`process_proposal`] rejected a Tx due to invalid signature, // emit an event here and move on to next tx. if ErrorCodes::from_u32(processed_tx.result.code).unwrap() @@ -194,149 +197,167 @@ where .storage .delete(&tx_hash_key) .expect("Error while deleting tx hash from storage"); + } else if let TxType::Wrapper(wrapper) = &tx_type { + // Charge fee if needed + if ErrorCodes::from_u32(processed_tx.result.code) + .unwrap() + .charges_fee() + { + #[cfg(not(feature = "mainnet"))] + let has_valid_pow = + self.invalidate_pow_solution_if_valid(wrapper); + let _ = self.charge_fee( + wrapper, + #[cfg(not(feature = "mainnet"))] + has_valid_pow, + ); + } } continue; } - let (mut tx_event, tx_unsigned_hash) = match &tx_type { - TxType::Wrapper(wrapper) => { - let mut tx_event = Event::new_tx_event(&tx_type, height.0); + let (mut tx_event, tx_unsigned_hash, mut tx_gas_meter) = + match &tx_type { + TxType::Wrapper(wrapper) => { + let mut tx_event = + Event::new_tx_event(&tx_type, height.0); + + let mut gas_meter = + TxGasMeter::new(u64::from(&wrapper.gas_limit)); + + // Writes both txs hash to storage + let tx = + Tx::try_from(processed_tx.tx.as_ref()).unwrap(); + let wrapper_tx_hash_key = + replay_protection::get_tx_hash_key(&hash::Hash( + tx.unsigned_hash(), + )); + self.wl_storage + .storage + .write(&wrapper_tx_hash_key, vec![]) + .expect("Error while writing tx hash to storage"); + + let inner_tx_hash_key = + replay_protection::get_tx_hash_key( + &wrapper.tx_hash, + ); + self.wl_storage + .storage + .write(&inner_tx_hash_key, vec![]) + .expect("Error while writing tx hash to storage"); - // Writes both txs hash to storage - let tx = Tx::try_from(processed_tx.tx.as_ref()).unwrap(); - let wrapper_tx_hash_key = - replay_protection::get_tx_hash_key(&hash::Hash( - tx.unsigned_hash(), - )); - self.wl_storage - .storage - .write(&wrapper_tx_hash_key, vec![]) - .expect("Error while writing tx hash to storage"); + // Charge fee before performing any fallible operations + #[cfg(not(feature = "mainnet"))] + let has_valid_pow = + self.invalidate_pow_solution_if_valid(wrapper); - let inner_tx_hash_key = - replay_protection::get_tx_hash_key(&wrapper.tx_hash); - self.wl_storage - .storage - .write(&inner_tx_hash_key, vec![]) - .expect("Error while writing tx hash to storage"); + if let Err(e) = self.charge_fee( + wrapper, + #[cfg(not(feature = "mainnet"))] + has_valid_pow, + ) { + tx_event["info"] = e.to_string(); + tx_event["code"] = ErrorCodes::InvalidTx.into(); + tx_event["gas_used"] = gas_meter + .get_current_transaction_gas() + .to_string(); - #[cfg(not(feature = "mainnet"))] - let has_valid_pow = - self.invalidate_pow_solution_if_valid(wrapper); - - // Charge fee - let fee_payer = - if wrapper.pk != address::masp_tx_key().ref_to() { - wrapper.fee_payer() - } else { - address::masp() - }; - - let balance_key = - token::balance_key(&wrapper.fee.token, &fee_payer); - let balance: token::Amount = self - .wl_storage - .read(&balance_key) - .expect("must be able to read") - .unwrap_or_default(); + response.events.push(tx_event); + continue; + } - match balance.checked_sub(wrapper_fees) { - Some(amount) => { - self.wl_storage - .storage - .write( - &balance_key, - amount.try_to_vec().unwrap(), - ) - .unwrap(); + // Account for gas + if let Err(e) = + gas_meter.add_tx_size_gas(processed_tx.tx.len()) + { + tx_event["info"] = format!("{}", e); + tx_event["code"] = ErrorCodes::TxGasLimit.into(); + tx_event["gas_used"] = gas_meter + .get_current_transaction_gas() + .to_string(); + + response.events.push(tx_event); + continue; } - None => { - #[cfg(not(feature = "mainnet"))] - let reject = !has_valid_pow; - #[cfg(feature = "mainnet")] - let reject = true; - if reject { - // Burn remaining funds - self.wl_storage - .storage - .write( - &balance_key, - Amount::from(0).try_to_vec().unwrap(), + + let spare_gas = u64::from(&wrapper.gas_limit) + - gas_meter.get_current_transaction_gas(); + self.wl_storage.storage.tx_queue.push( + WrapperTxInQueue { + tx: wrapper.clone(), + gas: spare_gas, + #[cfg(not(feature = "mainnet"))] + has_valid_pow, + }, + ); + ( + tx_event, + None, + TxGasMeter::new(spare_gas), // This is just for logging/events purposes, no more gas is actually used by the wrapper + ) + } + TxType::Decrypted(inner) => { + // We remove the corresponding wrapper tx from the queue + let wrapper = self + .wl_storage + .storage + .tx_queue + .pop() + .expect("Missing wrapper tx in queue"); + let mut event = Event::new_tx_event(&tx_type, height.0); + + match inner { + DecryptedTx::Decrypted { + tx, + has_valid_pow: _, + } => { + stats.increment_tx_type( + namada::core::types::hash::Hash( + tx.code_hash(), ) - .unwrap(); - tx_event["info"] = - "Insufficient balance for fee".into(); - tx_event["code"] = ErrorCodes::InvalidTx.into(); - tx_event["gas_used"] = "0".to_string(); - - response.events.push(tx_event); - continue; + .to_string(), + ); + } + DecryptedTx::Undecryptable(_) => { + event["log"] = + "Transaction could not be decrypted." + .into(); + event["code"] = + ErrorCodes::Undecryptable.into(); } } - } - self.wl_storage.storage.tx_queue.push(WrapperTxInQueue { - tx: wrapper.clone(), - #[cfg(not(feature = "mainnet"))] - has_valid_pow, - }); - (tx_event, None) - } - TxType::Decrypted(inner) => { - // We remove the corresponding wrapper tx from the queue - let wrapper_hash = self - .wl_storage - .storage - .tx_queue - .pop() - .expect("Missing wrapper tx in queue") - .tx - .tx_hash; - let mut event = Event::new_tx_event(&tx_type, height.0); - - match inner { - DecryptedTx::Decrypted { - tx, - has_valid_pow: _, - } => { - stats.increment_tx_type( - namada::core::types::hash::Hash(tx.code_hash()) - .to_string(), - ); - } - DecryptedTx::Undecryptable(_) => { - event["log"] = - "Transaction could not be decrypted.".into(); - event["code"] = ErrorCodes::Undecryptable.into(); - } + ( + event, + Some(wrapper.tx.tx_hash), + TxGasMeter::new(wrapper.gas), + ) } - (event, Some(wrapper_hash)) - } - TxType::Raw(_) => { - tracing::error!( - "Internal logic error: FinalizeBlock received a \ + TxType::Raw(_) => { + tracing::error!( + "Internal logic error: FinalizeBlock received a \ TxType::Raw transaction" - ); - continue; - } - TxType::Protocol(_) => { - tracing::error!( - "Internal logic error: FinalizeBlock received a \ + ); + continue; + } + TxType::Protocol(_) => { + tracing::error!( + "Internal logic error: FinalizeBlock received a \ TxType::Protocol transaction" - ); - continue; - } - }; + ); + continue; + } + }; match protocol::apply_tx( tx_type, - tx_length, TxIndex( tx_index .try_into() .expect("transaction index out of bounds"), ), - &mut self.gas_meter, + &mut tx_gas_meter, + &gas_table, &mut self.wl_storage.write_log, &self.wl_storage.storage, &mut self.vp_wasm_cache, @@ -408,7 +429,7 @@ where // out of gas, remove its hash from storage to allow // rewrapping it if let Some(hash) = tx_unsigned_hash { - if let Error::TxApply(protocol::Error::GasError(namada::ledger::gas::Error::TransactionGasExceededError)) = + if let Error::TxApply(protocol::Error::GasError(_)) = msg { let tx_hash_key = @@ -423,10 +444,8 @@ where } self.wl_storage.drop_tx(); - tx_event["gas_used"] = self - .gas_meter - .get_current_transaction_gas() - .to_string(); + tx_event["gas_used"] = + tx_gas_meter.get_current_transaction_gas().to_string(); tx_event["info"] = msg.to_string(); tx_event["code"] = ErrorCodes::WasmRuntimeError.into(); } @@ -504,11 +523,6 @@ where )?; } - let _ = self - .gas_meter - .finalize_transaction() - .map_err(|_| Error::GasOverflow)?; - self.event_log_mut().log_events(response.events.clone()); tracing::debug!("End finalize_block {height} of epoch {current_epoch}"); @@ -528,8 +542,6 @@ where ) -> (BlockHeight, bool) { let height = self.wl_storage.storage.last_height + 1; - self.gas_meter.reset(); - self.wl_storage .storage .begin_block(hash, height) @@ -809,6 +821,58 @@ where Ok(()) } + + /// Charge fee for the provided wrapper transaction + fn charge_fee( + &mut self, + wrapper: &WrapperTx, + #[cfg(not(feature = "mainnet"))] has_valid_pow: bool, + ) -> Result<()> { + // Charge fee + let fee_payer = if wrapper.pk != address::masp_tx_key().ref_to() { + wrapper.fee_payer() + } else { + address::masp() + }; + + let balance_key = token::balance_key(&wrapper.fee.token, &fee_payer); + let balance: token::Amount = self + .wl_storage + .read(&balance_key) + .expect("must be able to read") + .unwrap_or_default(); + + let wrapper_fees = self.get_wrapper_tx_fees(); + match balance.checked_sub(wrapper_fees) { + Some(amount) => { + self.wl_storage + .storage + .write(&balance_key, amount.try_to_vec().unwrap()) + .unwrap(); + } + None => { + #[cfg(not(feature = "mainnet"))] + let reject = !has_valid_pow; + #[cfg(feature = "mainnet")] + let reject = true; + + if reject { + // Burn remaining funds + self.wl_storage + .storage + .write( + &balance_key, + Amount::from(0).try_to_vec().unwrap(), + ) + .unwrap(); + + return Err(Error::TxApply(protocol::Error::FeeError)); + } + } + } + + Ok(()) + } } /// Convert ABCI vote info to PoS vote info. Any info which fails the conversion @@ -916,6 +980,8 @@ mod test_finalize_block { FinalizeBlock, ProcessedTx, }; + const GAS_LIMIT_MULTIPLIER: u64 = 1; + /// Check that if a wrapper tx was rejected by [`process_proposal`], /// check that the correct event is returned. Check that it does /// not appear in the queue of txs to be decrypted @@ -952,7 +1018,7 @@ mod test_finalize_block { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), raw_tx.clone(), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -971,7 +1037,10 @@ mod test_finalize_block { }, }); } else { - shell.enqueue_tx(wrapper.clone()); + shell.enqueue_tx( + wrapper.clone(), + u64::from(&wrapper.gas_limit) - tx.to_bytes().len() as u64, + ); } if i != 3 { @@ -1009,7 +1078,7 @@ mod test_finalize_block { } /// Check that if a decrypted tx was rejected by [`process_proposal`], - /// check that the correct event is returned. Check that it is still + /// the correct event is returned. Check that it is still /// removed from the queue of txs to be included in the next block /// proposal #[test] @@ -1029,12 +1098,16 @@ mod test_finalize_block { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), raw_tx.clone(), Default::default(), #[cfg(not(feature = "mainnet"))] None, ); + let signed_wrapper = wrapper + .sign(&keypair, shell.chain_id.clone(), None) + .unwrap() + .to_bytes(); let processed_tx = ProcessedTx { tx: Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { @@ -1048,7 +1121,9 @@ mod test_finalize_block { info: "".into(), }, }; - shell.enqueue_tx(wrapper); + let gas_limit = + u64::from(&wrapper.gas_limit) - signed_wrapper.len() as u64; + shell.enqueue_tx(wrapper, gas_limit); // check that the decrypted tx was not applied for event in shell @@ -1087,12 +1162,16 @@ mod test_finalize_block { }, pk: keypair.ref_to(), epoch: Epoch(0), - gas_limit: 0.into(), + gas_limit: GAS_LIMIT_MULTIPLIER.into(), inner_tx, tx_hash: hash_tx(&tx), #[cfg(not(feature = "mainnet"))] pow_solution: None, }; + let signed_wrapper = wrapper + .sign(&keypair, shell.chain_id.clone(), None) + .unwrap() + .to_bytes(); let processed_tx = ProcessedTx { tx: Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( wrapper.clone(), @@ -1104,7 +1183,9 @@ mod test_finalize_block { }, }; - shell.enqueue_tx(wrapper); + let gas_limit = + u64::from(&wrapper.gas_limit) - signed_wrapper.len() as u64; + shell.enqueue_tx(wrapper, gas_limit); // check that correct error message is returned for event in shell @@ -1165,13 +1246,19 @@ mod test_finalize_block { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), raw_tx.clone(), Default::default(), #[cfg(not(feature = "mainnet"))] None, ); - shell.enqueue_tx(wrapper_tx); + let signed_wrapper = wrapper_tx + .sign(&keypair, shell.chain_id.clone(), None) + .unwrap() + .to_bytes(); + let gas_limit = + u64::from(&wrapper_tx.gas_limit) - signed_wrapper.len() as u64; + shell.enqueue_tx(wrapper_tx, gas_limit); processed_txs.push(ProcessedTx { tx: Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { tx: raw_tx, @@ -1204,7 +1291,7 @@ mod test_finalize_block { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), raw_tx.clone(), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1507,11 +1594,9 @@ mod test_finalize_block { // won't receive votes from TM since we receive votes at a 1-block // delay, so votes will be empty here next_block_for_inflation(&mut shell, pkh1.clone(), vec![]); - assert!( - rewards_accumulator_handle() - .is_empty(&shell.wl_storage) - .unwrap() - ); + assert!(rewards_accumulator_handle() + .is_empty(&shell.wl_storage) + .unwrap()); // FINALIZE BLOCK 2. Tell Namada that val1 is the block proposer. // Include votes that correspond to block 1. Make val2 the next block's @@ -1521,11 +1606,9 @@ mod test_finalize_block { assert!(rewards_prod_2.is_empty(&shell.wl_storage).unwrap()); assert!(rewards_prod_3.is_empty(&shell.wl_storage).unwrap()); assert!(rewards_prod_4.is_empty(&shell.wl_storage).unwrap()); - assert!( - !rewards_accumulator_handle() - .is_empty(&shell.wl_storage) - .unwrap() - ); + assert!(!rewards_accumulator_handle() + .is_empty(&shell.wl_storage) + .unwrap()); // Val1 was the proposer, so its reward should be larger than all // others, which should themselves all be equal let acc_sum = get_rewards_sum(&shell.wl_storage); @@ -1626,11 +1709,9 @@ mod test_finalize_block { ); next_block_for_inflation(&mut shell, pkh1.clone(), votes.clone()); } - assert!( - rewards_accumulator_handle() - .is_empty(&shell.wl_storage) - .unwrap() - ); + assert!(rewards_accumulator_handle() + .is_empty(&shell.wl_storage) + .unwrap()); let rp1 = rewards_prod_1 .get(&shell.wl_storage, &Epoch::default()) .unwrap() @@ -1713,12 +1794,16 @@ mod test_finalize_block { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), raw_tx.clone(), Default::default(), #[cfg(not(feature = "mainnet"))] None, ); + let signed_wrapper = wrapper_tx + .sign(&keypair, shell.chain_id.clone(), None) + .unwrap() + .to_bytes(); // Write inner hash in storage let inner_hash_key = @@ -1741,7 +1826,9 @@ mod test_finalize_block { info: "".into(), }, }; - shell.enqueue_tx(wrapper_tx); + let gas_limit = + u64::from(&wrapper_tx.gas_limit) - signed_wrapper.len() as u64; + shell.enqueue_tx(wrapper_tx, gas_limit); let _event = &shell .finalize_block(FinalizeBlock { diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index dfdae4d04e..e3409d19f8 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + use namada::core::ledger::slash_fund::ADDRESS as slash_fund_address; use namada::core::types::transaction::governance::ProposalType; use namada::ledger::events::EventType; @@ -27,6 +29,7 @@ pub struct ProposalsResult { pub fn execute_governance_proposals( shell: &mut Shell, response: &mut shim::response::FinalizeBlock, + gas_table: &BTreeMap, ) -> Result where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, @@ -76,7 +79,9 @@ where let transfer_address = match tally_result { TallyResult::Passed(tally) => { let (successful_execution, proposal_event) = match tally { - Tally::Default => execute_default_proposal(shell, id), + Tally::Default => { + execute_default_proposal(shell, id, gas_table) + } Tally::PGFCouncil(council) => { execute_pgf_proposal(id, council) } @@ -138,6 +143,7 @@ where fn execute_default_proposal( shell: &mut Shell, id: u64, + gas_table: &BTreeMap, ) -> (bool, Event) where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, @@ -166,11 +172,9 @@ where .expect("Should be able to write to storage."); let tx_result = protocol::apply_tx( tx_type, - 0, /* this is used to compute the fee - * based on the code size. We dont - * need it here. */ TxIndex::default(), - &mut BlockGasMeter::default(), + &mut TxGasMeter::new(u64::MAX), // No gas limit for governance proposals + gas_table, &mut shell.wl_storage.write_log, &shell.wl_storage.storage, &mut shell.vp_wasm_cache, diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 3aabed3196..4e79d1f097 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -77,6 +77,7 @@ where let genesis::Parameters { epoch_duration, max_proposal_bytes, + max_block_gas, max_expected_time_per_block, vp_whitelist, tx_whitelist, @@ -88,6 +89,7 @@ where staked_ratio, pos_inflation_amount, wrapper_tx_fees, + gas_table, } = genesis.parameters; #[cfg(not(feature = "mainnet"))] // Try to find a faucet account @@ -170,6 +172,7 @@ where let parameters = Parameters { epoch_duration, max_proposal_bytes, + max_block_gas, max_expected_time_per_block, vp_whitelist, tx_whitelist, @@ -183,6 +186,7 @@ where faucet_account, #[cfg(not(feature = "mainnet"))] wrapper_tx_fees, + gas_table, }; parameters .init_storage(&mut self.wl_storage) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 8c617e396b..2a49c81ace 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -14,7 +14,7 @@ mod process_proposal; mod queries; mod stats; -use std::collections::HashSet; +use std::collections::{BTreeMap, HashSet}; use std::convert::{TryFrom, TryInto}; use std::mem; use std::path::{Path, PathBuf}; @@ -24,7 +24,7 @@ use std::rc::Rc; use borsh::{BorshDeserialize, BorshSerialize}; use namada::ledger::events::log::EventLog; use namada::ledger::events::Event; -use namada::ledger::gas::BlockGasMeter; +use namada::ledger::gas::{BlockGasMeter, TxGasMeter}; use namada::ledger::pos::namada_proof_of_stake::types::{ ConsensusValidator, ValidatorSetUpdate, }; @@ -33,7 +33,7 @@ use namada::ledger::storage::{ DBIter, Sha256Hasher, Storage, StorageHasher, WlStorage, DB, }; use namada::ledger::storage_api::{self, StorageRead}; -use namada::ledger::{ibc, pos, protocol, replay_protection}; +use namada::ledger::{ibc, parameters, pos, protocol, replay_protection}; use namada::proof_of_stake::{self, read_pos_params, slash}; use namada::proto::{self, Tx}; use namada::types::address::{masp, masp_tx_key, Address}; @@ -131,16 +131,19 @@ pub enum ErrorCodes { Ok = 0, InvalidDecryptedChainId = 1, ExpiredDecryptedTx = 2, - WasmRuntimeError = 3, - InvalidTx = 4, - InvalidSig = 5, - InvalidOrder = 6, - ExtraTxs = 7, - Undecryptable = 8, - AllocationError = 9, - ReplayTx = 10, - InvalidChainId = 11, - ExpiredTx = 12, + DecryptedGasLimit = 3, + WasmRuntimeError = 4, + InvalidTx = 5, + InvalidSig = 6, + InvalidOrder = 7, + ExtraTxs = 8, + Undecryptable = 9, + AllocationError = 10, + ReplayTx = 11, + InvalidChainId = 12, + ExpiredTx = 13, + BlockGasLimit = 14, + TxGasLimit = 15, } impl ErrorCodes { @@ -154,14 +157,22 @@ impl ErrorCodes { Ok | InvalidDecryptedChainId | ExpiredDecryptedTx - | WasmRuntimeError => true, + | WasmRuntimeError + | DecryptedGasLimit => true, InvalidTx | InvalidSig | InvalidOrder | ExtraTxs | Undecryptable | AllocationError | ReplayTx | InvalidChainId - | ExpiredTx => false, + | ExpiredTx | BlockGasLimit | TxGasLimit => false, } } } +impl ErrorCodes { + /// Whether to charge fees depending on the exit code of a transaction + pub fn charges_fee(&self) -> bool { + matches!(self, Self::Ok | Self::WasmRuntimeError | Self::TxGasLimit) + } +} + impl From for u32 { fn from(code: ErrorCodes) -> u32 { code.to_u32().unwrap() @@ -247,8 +258,6 @@ where chain_id: ChainId, /// The persistent storage with write log pub(super) wl_storage: WlStorage, - /// Gas meter for the current block - gas_meter: BlockGasMeter, /// Byzantine validators given from ABCI++ `prepare_proposal` are stored in /// this field. They will be slashed when we finalize the block. byzantine_validators: Vec, @@ -375,7 +384,6 @@ where Self { chain_id, wl_storage, - gas_meter: BlockGasMeter::default(), byzantine_validators: vec![], base_dir, wasm_dir, @@ -709,6 +717,30 @@ where // Tx type check if let TxType::Wrapper(wrapper) = tx_type { + // Tx gas limit + let mut gas_meter = TxGasMeter::new(u64::from(&wrapper.gas_limit)); + if let Err(_) = gas_meter.add_tx_size_gas(tx_bytes.len()) { + response.code = ErrorCodes::TxGasLimit.into(); + response.log = + "Wrapper transactions exceeds its gas limit".to_string(); + return response; + } + + // Max block gas + let block_gas_limit: u64 = self + .read_storage_key(¶meters::storage::get_max_block_gas_key()) + .expect("Missing max_block_gas parameter in storage"); + let mut block_gas_meter = BlockGasMeter::new(block_gas_limit); + if let Err(_) = block_gas_meter + .finalize_transaction(gas_meter.get_current_transaction_gas()) + { + response.code = ErrorCodes::BlockGasLimit.into(); + response.log = + "Wrapper transaction exceeds the maximum block gas limit" + .to_string(); + return response; + } + // Replay protection check let inner_hash_key = replay_protection::get_tx_hash_key(&wrapper.tx_hash); @@ -788,7 +820,16 @@ where /// Simulate validation and application of a transaction. fn dry_run_tx(&self, tx_bytes: &[u8]) -> response::Query { let mut response = response::Query::default(); - let mut gas_meter = BlockGasMeter::default(); + let gas_table: BTreeMap = self + .read_storage_key(¶meters::storage::get_gas_table_storage_key()) + .expect("Missing gas table in storage"); + let mut gas_meter = + TxGasMeter::new( + self.read_storage_key( + ¶meters::storage::get_max_block_gas_key(), + ) + .expect("Missing parameter in storage"), + ); let mut write_log = WriteLog::default(); let mut vp_wasm_cache = self.vp_wasm_cache.read_only(); let mut tx_wasm_cache = self.tx_wasm_cache.read_only(); @@ -803,9 +844,9 @@ where }); match protocol::apply_tx( tx, - tx_bytes.len(), TxIndex::default(), &mut gas_meter, + &gas_table, &mut write_log, &self.wl_storage.storage, &mut vp_wasm_cache, @@ -1015,8 +1056,7 @@ mod test_utils { let base_dir = tempdir().unwrap().as_ref().canonicalize().unwrap(); let vp_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB let tx_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB - ( - Self { + let mut shell = Self { shell: Shell::::new( config::Ledger::new( base_dir, @@ -1030,7 +1070,73 @@ mod test_utils { tx_wasm_compilation_cache, address::nam(), ), - }, + }; + + // Initialize gas table + let mut checksums: BTreeMap = serde_json::from_slice( + &std::fs::read("../wasm/checksums.json").unwrap(), + ) + .unwrap(); + // Extend gas table with test transactions and vps + checksums.extend(serde_json::from_slice::>( + &std::fs::read("../wasm_for_tests/checksums.json").unwrap(), + ) + .unwrap().into_iter()); + + let gas_file: BTreeMap = serde_json::from_slice( + &std::fs::read("../wasm/gas.json").unwrap(), + ) + .unwrap(); + + let mut gas_table = BTreeMap::::new(); + + for id in checksums.keys().chain(gas_file.keys()){ + // Get tx/vp hash (or name if native) + let hash = match checksums.get(id.as_str()) { + Some(v) => { +v + .split_once('.') + .unwrap() + .1 + .split_once('.') + .unwrap() + .0.to_owned() + } + None => { + id.to_owned() + } + }; + let gas = gas_file.get(id).unwrap_or(&1_000).to_owned(); + gas_table.insert(hash, gas); + } + + + let gas_table_key = + namada::core::ledger::parameters::storage::get_gas_table_storage_key(); + shell.wl_storage + .storage + .write(&gas_table_key, + namada::core::ledger::storage::types::encode(&gas_table)) + .expect( + "Gas table parameter must be initialized in the genesis block", + ); + + let max_block_gas_key = + namada::core::ledger::parameters::storage::get_max_block_gas_key( + ); + shell.wl_storage + .storage + .write( + &max_block_gas_key, + namada::core::ledger::storage::types::encode( + &10_000_000_u64, + ), + ) + .expect( + "Max block gas parameter must be initialized in storage", + ); + ( + shell, receiver, ) } @@ -1085,15 +1191,17 @@ mod test_utils { } /// Add a wrapper tx to the queue of txs to be decrypted - /// in the current block proposal + /// in the current block proposal. Takes the length of the encoded wrapper + /// as parameter. #[cfg(test)] - pub fn enqueue_tx(&mut self, wrapper: WrapperTx) { + pub fn enqueue_tx(&mut self, wrapper: WrapperTx, inner_tx_gas: u64) { self.shell .wl_storage .storage .tx_queue .push(WrapperTxInQueue { tx: wrapper, + gas: inner_tx_gas, #[cfg(not(feature = "mainnet"))] has_valid_pow: false, }); @@ -1183,14 +1291,21 @@ mod test_utils { }, &keypair, Epoch(0), - 0.into(), + 1.into(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] None, ); + let signed_wrapper = wrapper + .sign(&keypair, shell.chain_id.clone(), None) + .unwrap() + .to_bytes(); + let gas_limit = + u64::from(&wrapper.gas_limit) - signed_wrapper.len() as u64; shell.wl_storage.storage.tx_queue.push(WrapperTxInQueue { tx: wrapper, + gas: gas_limit, #[cfg(not(feature = "mainnet"))] has_valid_pow: false, }); @@ -1233,6 +1348,9 @@ mod test_mempool_validate { use super::test_utils::TestShell; use super::{MempoolTxType, *}; + +const GAS_LIMIT_MULTIPLIER: u64 = 1; + /// Mempool validation must reject unsigned wrappers #[test] fn test_missing_signature() { @@ -1412,7 +1530,7 @@ mod test_mempool_validate { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index d48d5cfcec..0b6a62c0bf 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1,6 +1,8 @@ //! Implementation of the [`RequestPrepareProposal`] ABCI++ method for the Shell use namada::core::hints; +use namada::core::ledger::parameters; +use namada::ledger::gas::BlockGasMeter; use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::proof_of_stake::pos_queries::PosQueries; use namada::proto::Tx; @@ -128,6 +130,14 @@ where // valid because of mempool check TryInto::::try_into(block_time).ok() }); + let mut temp_block_gas_meter = + BlockGasMeter::new( + self.read_storage_key( + ¶meters::storage::get_max_block_gas_key(), + ) + .expect("Missing max_block_gas parameter in storage"), + ); + let txs = txs .iter() .filter_map(|tx_bytes| { @@ -138,8 +148,12 @@ where if let (Some(block_time), Some(exp)) = (block_time.as_ref(), &tx.expiration) { if block_time > exp { return None } } - if let Ok(TxType::Wrapper(_)) = process_tx(tx) { + if let Ok(TxType::Wrapper(ref wrapper)) = process_tx(tx) { + +// Check tx gas limit + if temp_block_gas_meter.try_finalize_transaction(wrapper.gas_limit.clone().into()).is_ok() { return Some(tx_bytes.clone()); + } } } None @@ -205,6 +219,7 @@ where .map( |WrapperTxInQueue { tx, + gas: _, #[cfg(not(feature = "mainnet"))] has_valid_pow, }| { @@ -277,6 +292,8 @@ mod test_prepare_proposal { use super::*; use crate::node::ledger::shell::test_utils::{self, gen_keypair}; + const GAS_LIMIT_MULTIPLIER: u64 = 1; + /// Test that if a tx from the mempool is not a /// WrapperTx type, it is not included in the /// proposed block. @@ -376,7 +393,7 @@ mod test_prepare_proposal { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -385,7 +402,7 @@ mod test_prepare_proposal { let wrapper = wrapper_tx .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); - shell.enqueue_tx(wrapper_tx); + shell.enqueue_tx(wrapper_tx, 0); expected_wrapper.push(wrapper.clone()); req.txs.push(wrapper.to_bytes()); } diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index acc57e5979..22b88e1146 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -4,6 +4,9 @@ use data_encoding::HEXUPPER; use namada::core::hints; use namada::core::ledger::storage::WlStorage; +use namada::types::hash::HASH_LENGTH; +use std::collections::BTreeMap; + use namada::core::types::hash::Hash; use namada::ledger::storage::TempWlStorage; use namada::proof_of_stake::pos_queries::PosQueries; @@ -139,6 +142,19 @@ where let mut tx_queue_iter = self.wl_storage.storage.tx_queue.iter(); let mut temp_wl_storage = TempWlStorage::new(&self.wl_storage.storage); let mut metadata = ValidationMeta::from(&self.wl_storage); + let mut temp_block_gas_meter = + BlockGasMeter::new( + self.read_storage_key( + ¶meters::storage::get_max_block_gas_key(), + ) + .expect("Missing parameter in storage"), + ); + let gas_table: BTreeMap = self + .read_storage_key(¶meters::storage::get_gas_table_storage_key()) + .expect("Missing gas table in storage"); + + let mut wrapper_index = 0; + let tx_results = txs .iter() .map(|tx_bytes| { @@ -147,7 +163,10 @@ where &mut tx_queue_iter, &mut metadata, &mut temp_wl_storage, + &mut temp_block_gas_meter, block_time, + &gas_table, + &mut wrapper_index ); if let ErrorCodes::Ok = ErrorCodes::from_u32(result.code).unwrap() @@ -192,7 +211,10 @@ where tx_queue_iter: &mut impl Iterator, metadata: &mut ValidationMeta, temp_wl_storage: &mut TempWlStorage, + temp_block_gas_meter: &mut BlockGasMeter, block_time: DateTimeUtc, + gas_table: &BTreeMap, + wrapper_index: &mut usize, ) -> TxResult { // try to allocate space for this tx if let Err(e) = metadata.txs_bin.try_dump(tx_bytes) { @@ -286,29 +308,32 @@ where .into(), } } - TxType::Decrypted(tx) => { - metadata.has_decrypted_txs = true; - match tx_queue_iter.next() { - Some(wrapper) => { - if wrapper.tx.tx_hash != tx.hash_commitment() { - TxResult { - code: ErrorCodes::InvalidOrder.into(), - info: "Process proposal rejected a decrypted \ - transaction that violated the tx order \ - determined in the previous block" - .into(), - } - } else if verify_decrypted_correctly(&tx, privkey) { - if let DecryptedTx::Decrypted { - tx, - has_valid_pow: _, - } = tx - { - // Tx chain id - if tx.chain_id != self.chain_id { - return TxResult { - code: - ErrorCodes::InvalidDecryptedChainId + TxType::Decrypted(tx) => { + // Increase wrapper index + let tx_index = *wrapper_index; + *wrapper_index += 1; + + match tx_queue_iter.next() { + Some(wrapper) => { + if wrapper.tx.tx_hash != tx.hash_commitment() { + TxResult { + code: ErrorCodes::InvalidOrder.into(), + info: "Process proposal rejected a \ + decrypted transaction that \ + violated the tx order determined \ + in the previous block" + .into(), + } + } else if verify_decrypted_correctly(&tx, privkey) { + if let DecryptedTx::Decrypted { + tx, + has_valid_pow: _, + } = tx + { + // Tx chain id + if tx.chain_id != self.chain_id { + return TxResult { + code: ErrorCodes::InvalidDecryptedChainId .into(), info: format!( "Decrypted tx carries a wrong \ @@ -333,7 +358,41 @@ where }; } } + + // Tx gas (partial check) + let tx_hash = if tx.code_or_hash.len() == HASH_LENGTH { + match Hash::try_from(tx.code_or_hash.as_slice()) { + Ok(hash) => hash, + Err(_) => return TxResult { + code: ErrorCodes::Undecryptable.into(), + info: format!("Failed conversion of transaction's hash") + } + } + } else { + Hash(tx.code_hash()) + }; + let tx_gas = match gas_table.get(&tx_hash.to_string().to_ascii_lowercase()) { + Some(gas) => gas.to_owned(), + #[cfg(any(test, feature = "testing"))] + None => 1000, + #[cfg(not(any(test, feature = "testing")))] + None => return TxResult { + // Tx is not whitelisted + code: ErrorCodes::Undecryptable.into(), + info: "Tx is not whitelisted".to_string() + } + }; + let inner_tx_gas_limit = temp_wl_storage.storage.tx_queue.get(tx_index).map_or(0, |wrapper| wrapper.gas); + let mut tx_gas_meter = TxGasMeter::new(inner_tx_gas_limit); + if let Err(e) = tx_gas_meter.add(tx_gas) { + + return TxResult { + code: ErrorCodes::TxGasLimit.into(), + info: format!("Decrypted transaction gas error: {}", e) + }; + } } + TxResult { code: ErrorCodes::Ok.into(), info: "Process Proposal accepted this \ @@ -355,9 +414,31 @@ where info: "Received more decrypted txs than expected" .into(), }, - } - } - TxType::Wrapper(wrapper) => { + }} + TxType::Wrapper(wrapper) => { + // Account for gas. This is done even if the transaction is later deemed invalid, to incentivize the proposer to + // include only valid transaction and avoid wasting block gas limit + // Wrapper gas limit, Max block gas and cumulated block gas + let mut tx_gas_meter = TxGasMeter::new(u64::from(&wrapper.gas_limit)); + if let Err(_) = tx_gas_meter.add_tx_size_gas(tx_bytes.len()) { + let _ = temp_block_gas_meter.finalize_transaction(tx_gas_meter.get_current_transaction_gas()); + + return TxResult { + code: ErrorCodes::TxGasLimit.into(), + info: "Wrapper transactions exceeds its gas limit".to_string(), + }; + } + + if let Err(_) = temp_block_gas_meter.finalize_transaction(tx_gas_meter.get_current_transaction_gas()){ + return TxResult { + code: ErrorCodes::BlockGasLimit.into(), + + info: + "Wrapper transaction exceeds the maximum block gas limit" + .to_string() + }; + } + // decrypted txs shouldn't show up before wrapper txs if metadata.has_decrypted_txs { return TxResult { @@ -405,7 +486,7 @@ where }; } - // Tx expiration + // Tx expiration if let Some(exp) = tx_expiration { if block_time > exp { return TxResult { @@ -418,6 +499,7 @@ where } } + // validate the ciphertext via Ferveo if !wrapper.validate_ciphertext() { TxResult { @@ -553,6 +635,8 @@ mod test_process_proposal { self, gen_keypair, ProcessProposal, TestError, }; +const GAS_LIMIT_MULTIPLIER: u64 = 1; + /// Test that if a wrapper tx is not signed, the block is rejected /// by [`process_proposal`]. #[test] @@ -572,7 +656,7 @@ mod test_process_proposal { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -625,7 +709,7 @@ mod test_process_proposal { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -722,7 +806,7 @@ mod test_process_proposal { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -790,7 +874,7 @@ mod test_process_proposal { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -842,13 +926,15 @@ mod test_process_proposal { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), tx.clone(), Default::default(), #[cfg(not(feature = "mainnet"))] None, ); - shell.enqueue_tx(wrapper); + let signed_wrapper = wrapper.sign(&keypair, shell.chain_id.clone(), None).unwrap().to_bytes(); + let gas_limit = u64::from(&wrapper.gas_limit) - signed_wrapper.len() as u64; + shell.enqueue_tx(wrapper, gas_limit); let mut decrypted_tx = Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { tx, @@ -905,13 +991,14 @@ mod test_process_proposal { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] None, ); - shell.enqueue_tx(wrapper.clone()); + let signed_wrapper = wrapper.sign(&keypair, shell.chain_id.clone(), None).unwrap().to_bytes(); + shell.enqueue_tx(wrapper.clone(), u64::from(&wrapper.gas_limit) - signed_wrapper.len() as u64); let mut tx = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable(wrapper))); @@ -960,15 +1047,16 @@ mod test_process_proposal { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] None, ); wrapper.tx_hash = Hash([0; 32]); + let signed_wrapper = wrapper.sign(&keypair, shell.chain_id.clone(), None).unwrap().to_bytes(); - shell.enqueue_tx(wrapper.clone()); + shell.enqueue_tx(wrapper.clone(), u64::from(&wrapper.gas_limit) - signed_wrapper.len() as u64); let mut tx = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( #[allow(clippy::redundant_clone)] wrapper.clone(), @@ -1008,14 +1096,15 @@ mod test_process_proposal { }, pk: keypair.ref_to(), epoch: Epoch(0), - gas_limit: 0.into(), + gas_limit: GAS_LIMIT_MULTIPLIER.into(), inner_tx, tx_hash: hash_tx(&tx), #[cfg(not(feature = "mainnet"))] pow_solution: None, }; - shell.enqueue_tx(wrapper.clone()); + let signed_wrapper = wrapper.sign(&keypair, shell.chain_id.clone(), None).unwrap().to_bytes(); + shell.enqueue_tx(wrapper.clone(), u64::from(&wrapper.gas_limit) - signed_wrapper.len() as u64); let mut signed = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( #[allow(clippy::redundant_clone)] @@ -1134,7 +1223,7 @@ mod test_process_proposal { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1208,7 +1297,7 @@ mod test_process_proposal { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1266,7 +1355,7 @@ mod test_process_proposal { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1352,7 +1441,7 @@ mod test_process_proposal { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), tx.clone(), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1370,7 +1459,7 @@ mod test_process_proposal { }, &keypair_2, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1424,7 +1513,7 @@ mod test_process_proposal { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), tx.clone(), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1493,14 +1582,17 @@ mod test_process_proposal { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] None, ); + let signed_wrapper = wrapper.sign(&keypair, shell.chain_id.clone(), None).unwrap().to_bytes(); + let gas_limit = u64::from(&wrapper.gas_limit) - signed_wrapper.len() as u64; let wrapper_in_queue = WrapperTxInQueue { tx: wrapper, + gas: gas_limit, has_valid_pow: false, }; shell.wl_storage.storage.tx_queue.push(wrapper_in_queue); @@ -1548,7 +1640,7 @@ mod test_process_proposal { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1599,14 +1691,17 @@ mod test_process_proposal { }, &keypair, Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] None, ); + let signed_wrapper_tx = wrapper.sign(&keypair, shell.chain_id.clone(), None).unwrap().to_bytes(); + let gas_limit = u64::from(&wrapper.gas_limit) - signed_wrapper_tx.len() as u64; let wrapper_in_queue = WrapperTxInQueue { tx: wrapper, + gas: gas_limit, has_valid_pow: false, }; shell.wl_storage.storage.tx_queue.push(wrapper_in_queue); diff --git a/apps/src/lib/wasm_loader/mod.rs b/apps/src/lib/wasm_loader/mod.rs index 9a075fbcf8..4eadf9d565 100644 --- a/apps/src/lib/wasm_loader/mod.rs +++ b/apps/src/lib/wasm_loader/mod.rs @@ -326,3 +326,23 @@ async fn download_wasm(url: String) -> Result, Error> { Err(e) => Err(Error::Download(url, e)), } } + +/// Read the json file containing the gas costs for the whitelisted vps and txsi from +/// the default "gas.json" file in the given directory +pub fn read_gas_file(wasm_directory: impl AsRef) -> HashMap { + let gas_path = wasm_directory.as_ref().join("gas.json"); + + match fs::File::open(&gas_path) { + Ok(file) => match serde_json::from_reader(file) { + Ok(result) => result, + Err(_) => { + eprintln!("Can't read gas from {}", gas_path.to_string_lossy()); + safe_exit(1); + } + }, + Err(_) => { + eprintln!("Can't find gas at {}", gas_path.to_string_lossy()); + safe_exit(1); + } + } +} diff --git a/core/src/ledger/gas.rs b/core/src/ledger/gas.rs index e51c9f78ef..1f7a1c17c3 100644 --- a/core/src/ledger/gas.rs +++ b/core/src/ledger/gas.rs @@ -1,8 +1,6 @@ //! Gas accounting module to track the gas usage in a block for transactions and //! validity predicates triggered by transactions. -use std::convert::TryFrom; - use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use thiserror::Error; @@ -17,15 +15,10 @@ pub enum Error { GasOverflow, } +const TX_SIZE_GAS_PER_BYTE: u64 = 1; //FIXME: value here? const COMPILE_GAS_PER_BYTE: u64 = 1; -const BASE_TRANSACTION_FEE: u64 = 2; const PARALLEL_GAS_DIVIDER: u64 = 10; -/// The maximum value should be less or equal to i64::MAX -/// to avoid the gas overflow when sending this to ABCI -const BLOCK_GAS_LIMIT: u64 = 10_000_000_000_000; -const TRANSACTION_GAS_LIMIT: u64 = 10_000_000_000; - /// The minimum gas cost for accessing the storage pub const MIN_STORAGE_GAS: u64 = 1; @@ -34,15 +27,26 @@ pub type Result = std::result::Result; /// Gas metering in a block. Tracks the gas in a current block and a current /// transaction. -#[derive(Debug, Default, Clone)] +#[derive(Debug, Clone)] pub struct BlockGasMeter { + /// The max amount of gas allowed per block, defined by the protocol parameter + pub block_gas_limit: u64, block_gas: u64, +} + +/// Gas metering in a transaction +#[derive(Debug)] +pub struct TxGasMeter { + /// The gas limit for a transaction + pub tx_gas_limit: u64, transaction_gas: u64, } /// Gas metering in a validity predicate -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct VpGasMeter { + /// The transaction gas limit + tx_gas_limit: u64, /// The gas used in the transaction before the VP run initial_gas: u64, /// The current gas usage in the VP @@ -59,8 +63,58 @@ pub struct VpsGas { } impl BlockGasMeter { + /// Build a new instance. Requires the block_gas_limit protocol parameter. + pub fn new(block_gas_limit: u64) -> Self { + Self { + block_gas_limit, + block_gas: 0, + } + } + + /// Add the transaction gas to the block's total gas. It will return + /// error when the consumed gas exceeds the block gas limit, but the state + /// will still be updated. + pub fn finalize_transaction(&mut self, tx_gas: u64) -> Result<()> { + self.block_gas = self + .block_gas + .checked_add(tx_gas) + .ok_or(Error::GasOverflow)?; + + if self.block_gas > self.block_gas_limit { + return Err(Error::BlockGasExceeded); + } + Ok(()) + } + + /// Tries to add the transaction gas to the block's total gas. + /// If the operation returns an error, propagates this errors without updating the state. + pub fn try_finalize_transaction(&mut self, tx_gas: u64) -> Result<()> { + let updated_gas = self + .block_gas + .checked_add(tx_gas) + .ok_or(Error::GasOverflow)?; + + if updated_gas > self.block_gas_limit { + return Err(Error::BlockGasExceeded); + } + + self.block_gas = updated_gas; + + Ok(()) + } +} + +impl TxGasMeter { + /// Initialize a new Tx gas meter. Requires the gas limit for the specific transaction + pub fn new(tx_gas_limit: u64) -> Self { + Self { + tx_gas_limit, + transaction_gas: 0, + } + } + /// Add gas cost for the current transaction. It will return error when the - /// consumed gas exceeds the transaction gas limit, but the state will still + /// consumed gas exceeds the provided transaction gas limit, but the state will still /// be updated. pub fn add(&mut self, gas: u64) -> Result<()> { self.transaction_gas = self @@ -68,17 +122,11 @@ impl BlockGasMeter { .checked_add(gas) .ok_or(Error::GasOverflow)?; - if self.transaction_gas > TRANSACTION_GAS_LIMIT { + if self.transaction_gas > self.tx_gas_limit { return Err(Error::TransactionGasExceededError); } - Ok(()) - } - /// Add the base transaction fee and the fee per transaction byte that's - /// charged the moment we try to apply the transaction. - pub fn add_base_transaction_fee(&mut self, bytes_len: usize) -> Result<()> { - tracing::trace!("add_base_transaction_fee {}", bytes_len); - self.add(BASE_TRANSACTION_FEE) + Ok(()) } /// Add the compiling cost proportionate to the code length @@ -86,46 +134,28 @@ impl BlockGasMeter { self.add(bytes_len as u64 * COMPILE_GAS_PER_BYTE) } - /// Add the transaction gas to the block's total gas. Returns the - /// transaction's gas cost and resets the transaction meter. It will return - /// error when the consumed gas exceeds the block gas limit, but the state - /// will still be updated. - pub fn finalize_transaction(&mut self) -> Result { - self.block_gas = self - .block_gas - .checked_add(self.transaction_gas) - .ok_or(Error::GasOverflow)?; - - let transaction_gas = self.transaction_gas; - self.transaction_gas = 0; - if self.block_gas > BLOCK_GAS_LIMIT { - return Err(Error::BlockGasExceeded); - } - Ok(transaction_gas) + /// Add the gas for the space that the transaction requires in the block + pub fn add_tx_size_gas(&mut self, bytes_len: usize) -> Result<()> { + self.add(bytes_len as u64 * TX_SIZE_GAS_PER_BYTE) } - /// Reset the gas meter. - pub fn reset(&mut self) { - self.transaction_gas = 0; - self.block_gas = 0; + /// Add the gas cost used in validity predicates to the current transaction. + pub fn add_vps_gas(&mut self, vps_gas: &VpsGas) -> Result<()> { + self.add(vps_gas.get_current_gas()?) } /// Get the total gas used in the current transaction. pub fn get_current_transaction_gas(&self) -> u64 { self.transaction_gas } - - /// Add the gas cost used in validity predicates to the current transaction. - pub fn add_vps_gas(&mut self, vps_gas: &VpsGas) -> Result<()> { - self.add(vps_gas.get_current_gas()?) - } } impl VpGasMeter { /// Initialize a new VP gas meter, starting with the gas consumed in the - /// transaction so far. - pub fn new(initial_gas: u64) -> Self { + /// transaction so far. Also requires the transaction gas limit. + pub fn new(tx_gas_limit: u64, initial_gas: u64) -> Self { Self { + tx_gas_limit, initial_gas, current_gas: 0, } @@ -147,9 +177,10 @@ impl VpGasMeter { .checked_add(self.current_gas) .ok_or(Error::GasOverflow)?; - if current_total > TRANSACTION_GAS_LIMIT { + if current_total > self.tx_gas_limit { return Err(Error::TransactionGasExceededError); } + Ok(()) } @@ -165,13 +196,14 @@ impl VpsGas { debug_assert_eq!(self.max, None); debug_assert!(self.rest.is_empty()); self.max = Some(vp_gas_meter.current_gas); - self.check_limit(vp_gas_meter.initial_gas) + self.check_limit(vp_gas_meter.tx_gas_limit, vp_gas_meter.initial_gas) } /// Merge validity predicates gas meters from parallelized runs. pub fn merge( &mut self, other: &mut VpsGas, + tx_gas_limit: u64, initial_gas: u64, ) -> Result<()> { match (self.max, other.max) { @@ -190,15 +222,15 @@ impl VpsGas { } self.rest.append(&mut other.rest); - self.check_limit(initial_gas) + self.check_limit(tx_gas_limit, initial_gas) } - fn check_limit(&self, initial_gas: u64) -> Result<()> { + fn check_limit(&self, tx_gas_limit: u64, initial_gas: u64) -> Result<()> { let total = initial_gas .checked_add(self.get_current_gas()?) .ok_or(Error::GasOverflow)?; - if total > TRANSACTION_GAS_LIMIT { - return Err(Error::GasOverflow); + if total > tx_gas_limit { + return Err(Error::TransactionGasExceededError); } Ok(()) } @@ -213,38 +245,34 @@ impl VpsGas { } } -/// Convert the gas from signed to unsigned int. This will panic on overflow, -/// but it should never occur for our gas limits (see -/// `tests::gas_limits_cannot_overflow_i64`). -pub fn as_i64(gas: u64) -> i64 { - i64::try_from(gas).expect("Gas should never overflow i64") -} - #[cfg(test)] mod tests { use proptest::prelude::*; use super::*; + const BLOCK_GAS_LIMIT: u64 = 10_000_000_000; + const TX_GAS_LIMIT: u64 = 1_000_000; proptest! { #[test] - fn test_vp_gas_meter_add(gas in 0..TRANSACTION_GAS_LIMIT) { - let mut meter = VpGasMeter::new(0); + fn test_vp_gas_meter_add(gas in 0..BLOCK_GAS_LIMIT) { + let mut meter = VpGasMeter::new(BLOCK_GAS_LIMIT, 0); meter.add(gas).expect("cannot add the gas"); } #[test] - fn test_block_gas_meter_add(gas in 0..TRANSACTION_GAS_LIMIT) { - let mut meter = BlockGasMeter::default(); - meter.add(gas).expect("cannot add the gas"); - let result = meter.finalize_transaction().expect("cannot finalize the tx"); - assert_eq!(result, gas); + fn test_block_gas_meter_add(gas in 0..BLOCK_GAS_LIMIT) { + let mut meter = BlockGasMeter::new(BLOCK_GAS_LIMIT); + let mut tx_gas_meter = TxGasMeter::new(BLOCK_GAS_LIMIT + 1); + tx_gas_meter.add( gas).expect("cannot add the gas"); + meter.finalize_transaction(tx_gas_meter.get_current_transaction_gas()).expect("cannot finalize the tx"); + assert_eq!(meter.block_gas, gas); } } #[test] fn test_vp_gas_overflow() { - let mut meter = VpGasMeter::new(1); + let mut meter = VpGasMeter::new(BLOCK_GAS_LIMIT, 1); assert_matches!( meter.add(u64::MAX).expect_err("unexpectedly succeeded"), Error::GasOverflow @@ -253,18 +281,16 @@ mod tests { #[test] fn test_vp_gas_limit() { - let mut meter = VpGasMeter::new(1); + let mut meter = VpGasMeter::new(TX_GAS_LIMIT, 1); assert_matches!( - meter - .add(TRANSACTION_GAS_LIMIT) - .expect_err("unexpectedly succeeded"), + meter.add(TX_GAS_LIMIT).expect_err("unexpectedly succeeded"), Error::TransactionGasExceededError ); } #[test] fn test_tx_gas_overflow() { - let mut meter = BlockGasMeter::default(); + let mut meter = TxGasMeter::new(BLOCK_GAS_LIMIT); meter.add(1).expect("cannot add the gas"); assert_matches!( meter.add(u64::MAX).expect_err("unexpectedly succeeded"), @@ -274,10 +300,10 @@ mod tests { #[test] fn test_tx_gas_limit() { - let mut meter = BlockGasMeter::default(); + let mut meter = TxGasMeter::new(TX_GAS_LIMIT); assert_matches!( meter - .add(TRANSACTION_GAS_LIMIT + 1) + .add(TX_GAS_LIMIT + 1) .expect_err("unexpectedly succeeded"), Error::TransactionGasExceededError ); @@ -285,36 +311,32 @@ mod tests { #[test] fn test_block_gas_limit() { - let mut meter = BlockGasMeter::default(); + let mut meter = BlockGasMeter::new(BLOCK_GAS_LIMIT); + let transaction_gas = 10_000_u64; // add the maximum tx gas - for _ in 0..(BLOCK_GAS_LIMIT / TRANSACTION_GAS_LIMIT) { - meter - .add(TRANSACTION_GAS_LIMIT) + for _ in 0..(BLOCK_GAS_LIMIT / transaction_gas) { + let mut tx_gas_meter = TxGasMeter::new(transaction_gas + 1); + tx_gas_meter + .add(transaction_gas) .expect("over the tx gas limit"); meter - .finalize_transaction() + .finalize_transaction( + tx_gas_meter.get_current_transaction_gas(), + ) .expect("over the block gas limit"); } - meter - .add(TRANSACTION_GAS_LIMIT) + let mut tx_gas_meter = TxGasMeter::new(transaction_gas + 1); + tx_gas_meter + .add(transaction_gas) .expect("over the tx gas limit"); match meter - .finalize_transaction() + .finalize_transaction(tx_gas_meter.get_current_transaction_gas()) .expect_err("unexpectedly succeeded") { Error::BlockGasExceeded => {} _ => panic!("unexpected error happened"), } } - - /// Test that the function [`as_i64`] cannot fail for transaction and block - /// gas limit + some "tolerance" for gas exhaustion. - #[test] - fn gas_limits_cannot_overflow_i64() { - let tolerance = 10_000; - as_i64(BLOCK_GAS_LIMIT + tolerance); - as_i64(TRANSACTION_GAS_LIMIT + tolerance); - } } diff --git a/core/src/ledger/parameters/mod.rs b/core/src/ledger/parameters/mod.rs index bb9ae99577..e256a6d8a4 100644 --- a/core/src/ledger/parameters/mod.rs +++ b/core/src/ledger/parameters/mod.rs @@ -1,6 +1,8 @@ //! Protocol parameters pub mod storage; +use std::collections::BTreeMap; + use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use rust_decimal::Decimal; use thiserror::Error; @@ -36,6 +38,8 @@ pub struct Parameters { pub max_expected_time_per_block: DurationSecs, /// Max payload size, in bytes, for a tx batch proposal. pub max_proposal_bytes: ProposalBytes, + /// Max gas for block + pub max_block_gas: u64, /// Whitelisted validity predicate hashes (read only) pub vp_whitelist: Vec, /// Whitelisted tx hashes (read only) @@ -58,6 +62,8 @@ pub struct Parameters { #[cfg(not(feature = "mainnet"))] /// Fixed fees for a wrapper tx to be accepted pub wrapper_tx_fees: Option, + /// Gas table + pub gas_table: BTreeMap, } /// Epoch duration. A new epoch begins as soon as both the `min_num_of_blocks` @@ -111,6 +117,7 @@ impl Parameters { epoch_duration, max_expected_time_per_block, max_proposal_bytes, + max_block_gas, vp_whitelist, tx_whitelist, implicit_vp_code_hash, @@ -123,16 +130,25 @@ impl Parameters { faucet_account, #[cfg(not(feature = "mainnet"))] wrapper_tx_fees, + gas_table, } = self; // write max proposal bytes parameter let max_proposal_bytes_key = storage::get_max_proposal_bytes_key(); storage.write(&max_proposal_bytes_key, max_proposal_bytes)?; + // write max block gas parameter + let max_block_gas_key = storage::get_max_block_gas_key(); + storage.write(&max_block_gas_key, max_block_gas)?; + // write epoch parameters let epoch_key = storage::get_epoch_duration_storage_key(); storage.write(&epoch_key, epoch_duration)?; + // write gas table + let gas_table_key = storage::get_gas_table_storage_key(); + storage.write(&gas_table_key, gas_table)?; + // write vp whitelist parameter let vp_whitelist_key = storage::get_vp_whitelist_storage_key(); let vp_whitelist = vp_whitelist @@ -392,6 +408,15 @@ where .into_storage_result()? }; + // read max block gas + let max_block_gas: u64 = { + let key = storage::get_max_block_gas_key(); + let value = storage.read(&key)?; + value + .ok_or(ReadError::ParametersMissing) + .into_storage_result()? + }; + // read epoch duration let epoch_duration = read_epoch_duration_parameter(storage)?; @@ -425,6 +450,13 @@ where let implicit_vp_code_hash = Hash::try_from(&value[..]).into_storage_result()?; + // read gas table + let gas_table_key = storage::get_gas_table_storage_key(); + let value = storage.read(&gas_table_key)?; + let gas_table: BTreeMap = value + .ok_or(ReadError::ParametersMissing) + .into_storage_result()?; + // read epochs per year let epochs_per_year_key = storage::get_epochs_per_year_key(); let value = storage.read(&epochs_per_year_key)?; @@ -472,6 +504,7 @@ where epoch_duration, max_expected_time_per_block, max_proposal_bytes, + max_block_gas, vp_whitelist, tx_whitelist, implicit_vp_code_hash, @@ -484,5 +517,6 @@ where faucet_account, #[cfg(not(feature = "mainnet"))] wrapper_tx_fees, + gas_table, }) } diff --git a/core/src/ledger/parameters/storage.rs b/core/src/ledger/parameters/storage.rs index 178b0860eb..581f0e4b6d 100644 --- a/core/src/ledger/parameters/storage.rs +++ b/core/src/ledger/parameters/storage.rs @@ -19,8 +19,10 @@ struct Keys { tx_whitelist: &'static str, vp_whitelist: &'static str, max_proposal_bytes: &'static str, + max_block_gas: &'static str, faucet_account: &'static str, wrapper_tx_fees: &'static str, + gas_table: &'static str, } /// Returns if the key is a parameter key. @@ -159,6 +161,16 @@ pub fn get_tx_whitelist_storage_key() -> Key { } } +/// Storage key used for gas cost. +pub fn get_gas_table_storage_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(Keys::VALUES.gas_table.to_string()), + ], + } +} + /// Storage key used for max_epected_time_per_block parameter. pub fn get_max_expected_time_per_block_key() -> Key { Key { @@ -241,6 +253,16 @@ pub fn get_max_proposal_bytes_key() -> Key { } } +/// Storage key used for the max block gas. +pub fn get_max_block_gas_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(Keys::VALUES.max_block_gas.to_string()), + ], + } +} + /// Storage key used for faucet account. pub fn get_faucet_account_key() -> Key { Key { diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index cb3f9299df..e5f6cac5a8 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -996,6 +996,8 @@ pub mod testing { #[cfg(test)] mod tests { + use std::collections::BTreeMap; + use chrono::{TimeZone, Utc}; use proptest::prelude::*; use proptest::test_runner::Config; @@ -1076,6 +1078,7 @@ mod tests { }; let mut parameters = Parameters { max_proposal_bytes: Default::default(), + max_block_gas: 0, epoch_duration: epoch_duration.clone(), max_expected_time_per_block: Duration::seconds(max_expected_time_per_block).into(), vp_whitelist: vec![], @@ -1090,6 +1093,7 @@ mod tests { faucet_account: None, #[cfg(not(feature = "mainnet"))] wrapper_tx_fees: None, + gas_table: BTreeMap::default() }; parameters.init_storage(&mut wl_storage).unwrap(); diff --git a/core/src/ledger/storage_api/collections/lazy_map.rs b/core/src/ledger/storage_api/collections/lazy_map.rs index c1e8ae6dbf..9c9603e851 100644 --- a/core/src/ledger/storage_api/collections/lazy_map.rs +++ b/core/src/ledger/storage_api/collections/lazy_map.rs @@ -405,11 +405,11 @@ where storage: &'iter impl StorageRead, ) -> Result< impl Iterator< - Item = Result<( - ::SubKey, - ::Value, - )>, - > + 'iter, + Item = Result<( + ::SubKey, + ::Value, + )>, + > + 'iter, > { let iter = storage_api::iter_prefix_with_filter( storage, diff --git a/core/src/ledger/testnet_pow.rs b/core/src/ledger/testnet_pow.rs index bed2273a70..4eccaab40f 100644 --- a/core/src/ledger/testnet_pow.rs +++ b/core/src/ledger/testnet_pow.rs @@ -297,14 +297,10 @@ pub fn is_counter_key<'a>( faucet_address: &Address, ) -> Option<&'a Address> { match &key.segments[..] { - [ - DbKeySeg::AddressSeg(address), - DbKeySeg::StringSeg(sub_key), - DbKeySeg::StringSeg(data), - DbKeySeg::AddressSeg(owner), - ] if address == faucet_address - && sub_key.as_str() == Keys::VALUES.counters - && data.as_str() == lazy_map::DATA_SUBKEY => + [DbKeySeg::AddressSeg(address), DbKeySeg::StringSeg(sub_key), DbKeySeg::StringSeg(data), DbKeySeg::AddressSeg(owner)] + if address == faucet_address + && sub_key.as_str() == Keys::VALUES.counters + && data.as_str() == lazy_map::DATA_SUBKEY => { Some(owner) } @@ -417,7 +413,11 @@ pub struct Difficulty(u8); impl Difficulty { /// The value must be between `0..=9` (inclusive upper bound). pub fn try_new(raw: u8) -> Option { - if raw > 9 { None } else { Some(Self(raw)) } + if raw > 9 { + None + } else { + Some(Self(raw)) + } } } diff --git a/core/src/types/internal.rs b/core/src/types/internal.rs index d13d392381..4534832c69 100644 --- a/core/src/types/internal.rs +++ b/core/src/types/internal.rs @@ -40,7 +40,11 @@ impl HostEnvResult { impl From for HostEnvResult { fn from(success: bool) -> Self { - if success { Self::Success } else { Self::Fail } + if success { + Self::Success + } else { + Self::Fail + } } } @@ -54,6 +58,8 @@ mod tx_queue { pub struct WrapperTxInQueue { /// Wrapper tx pub tx: crate::types::transaction::WrapperTx, + /// The available gas remaining for the inner tx (for gas accounting) + pub gas: u64, #[cfg(not(feature = "mainnet"))] /// A PoW solution can be used to allow zero-fee testnet /// transactions. diff --git a/genesis/dev.toml b/genesis/dev.toml index 0aff92206f..6973aae34a 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -148,6 +148,8 @@ max_expected_time_per_block = 30 epochs_per_year = 525_600 # Max payload size, in bytes, for a tx batch proposal. max_proposal_bytes = 22020096 +# Max amount of gas per block FIXME: adjust value +max_block_gas = 1000000 # Proof of stake parameters. [pos_params] diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 6a1cc634f3..76e0a8abd0 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -155,6 +155,10 @@ min_num_of_blocks = 4 max_expected_time_per_block = 30 # Max payload size, in bytes, for a tx batch proposal. max_proposal_bytes = 22020096 +# Max amount of gas per block FIXME: adjust value +max_block_gas = 1000000 +# Gas table +gas_table = {} # vp whitelist vp_whitelist = [] # tx whitelist diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index dc0a021b6f..243e0835ca 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -224,8 +224,8 @@ pub fn get_dummy_header() -> crate::types::storage::Header { /// A dummy validator used for testing #[cfg(any(feature = "test", feature = "testing"))] -pub fn get_dummy_genesis_validator() --> namada_proof_of_stake::types::GenesisValidator { +pub fn get_dummy_genesis_validator( +) -> namada_proof_of_stake::types::GenesisValidator { use rust_decimal::prelude::Decimal; use crate::core::types::address::testing::established_address_1; @@ -359,6 +359,7 @@ mod tests { const ADDRESS: Address = Address::Internal(InternalAddress::Ibc); const COMMITMENT_PREFIX: &[u8] = b"ibc"; + const TX_GAS_LIMIT: u64 = 1_000_000; fn get_client_id() -> ClientId { let id = format!("{}-0", MOCK_CLIENT_TYPE); @@ -717,7 +718,7 @@ mod tests { None, ) .sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); + let gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -736,19 +737,18 @@ mod tests { let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] fn test_create_client_fail() { let mut wl_storage = TestWlStorage::default(); + let gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); + let (vp_wasm_cache, _vp_cache_dir) = + wasm::compilation_cache::common::testing::cache(); + let mut keys_changed = BTreeSet::new(); // initialize the storage @@ -798,7 +798,7 @@ mod tests { None, ) .sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); + let gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -952,14 +952,9 @@ mod tests { ); let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1044,7 +1039,7 @@ mod tests { None, ) .sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); + let gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1062,14 +1057,9 @@ mod tests { ); let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1142,7 +1132,7 @@ mod tests { None, ) .sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); + let gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1287,14 +1277,9 @@ mod tests { ); let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1380,7 +1365,7 @@ mod tests { None, ) .sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); + let gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1397,14 +1382,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1468,7 +1448,7 @@ mod tests { None, ) .sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); + let gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1485,14 +1465,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1592,7 +1567,7 @@ mod tests { None, ) .sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); + let gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1609,14 +1584,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1717,7 +1687,7 @@ mod tests { None, ) .sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); + let gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1734,14 +1704,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1826,7 +1791,7 @@ mod tests { None, ) .sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); + let gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1843,14 +1808,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1933,7 +1893,7 @@ mod tests { None, ) .sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); + let gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1950,14 +1910,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } // skip test_close_init_channel() and test_close_confirm_channel() since it @@ -2090,14 +2045,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -2268,14 +2218,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -2398,7 +2343,7 @@ mod tests { None, ) .sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); + let gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2415,14 +2360,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -2550,7 +2490,7 @@ mod tests { None, ) .sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); + let gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2567,14 +2507,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -2702,7 +2637,7 @@ mod tests { None, ) .sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); + let gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2719,13 +2654,8 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } } diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 4090c05a4d..1520b88cd7 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -1,12 +1,15 @@ //! The ledger's protocol -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use std::panic; +use namada_core::ledger::gas::TxGasMeter; +use namada_core::ledger::storage::write_log::StorageModification; +use namada_core::types::hash::{Hash, HASH_LENGTH}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use thiserror::Error; use crate::ledger::eth_bridge::vp::EthBridge; -use crate::ledger::gas::{self, BlockGasMeter, VpGasMeter}; +use crate::ledger::gas::{self, VpGasMeter}; use crate::ledger::ibc::vp::{Ibc, IbcToken}; use crate::ledger::native_vp::governance::GovernanceVp; use crate::ledger::native_vp::parameters::{self, ParametersVp}; @@ -18,7 +21,6 @@ use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use crate::proto::{self, Tx}; use crate::types::address::{Address, InternalAddress}; -use crate::types::hash::Hash; use crate::types::storage; use crate::types::storage::TxIndex; use crate::types::transaction::{DecryptedTx, TxResult, TxType, VpsResult}; @@ -38,6 +40,8 @@ pub enum Error { TxTypeError, #[error("Gas error: {0}")] GasError(gas::Error), + #[error("Insufficient balance to pay fee")] + FeeError, #[error("Error executing VP for addresses: {0:?}")] VpRunnerError(vm::wasm::run::Error), #[error("The address {0} doesn't exist")] @@ -64,6 +68,12 @@ pub enum Error { ), #[error("Access to an internal address {0} is forbidden")] AccessForbidden(InternalAddress), + #[error("The gas cost for the tx/vp {0} was not found in storage")] + MissingGasCost(String), + #[error("Error while converting the transaction code's hash")] + TxCodeHashConversion, + #[error("Could not retrieve wasm code from storage for hash {0}")] + MissingWasmCodeInStorage(Hash), } /// Result of applying a transaction @@ -79,9 +89,9 @@ pub type Result = std::result::Result; #[allow(clippy::too_many_arguments)] pub fn apply_tx( tx: TxType, - tx_length: usize, tx_index: TxIndex, - block_gas_meter: &mut BlockGasMeter, + tx_gas_meter: &mut TxGasMeter, + gas_table: &BTreeMap, write_log: &mut WriteLog, storage: &Storage, vp_wasm_cache: &mut VpCache, @@ -92,10 +102,6 @@ where H: 'static + StorageHasher + Sync, CA: 'static + WasmCacheAccess + Sync, { - // Base gas cost for applying the tx - block_gas_meter - .add_base_transaction_fee(tx_length) - .map_err(Error::GasError)?; match tx { TxType::Raw(_) => Err(Error::TxTypeError), TxType::Decrypted(DecryptedTx::Decrypted { @@ -107,7 +113,8 @@ where &tx, &tx_index, storage, - block_gas_meter, + tx_gas_meter, + gas_table, write_log, vp_wasm_cache, tx_wasm_cache, @@ -117,7 +124,8 @@ where &tx, &tx_index, storage, - block_gas_meter, + tx_gas_meter, + gas_table, write_log, &verifiers, vp_wasm_cache, @@ -125,9 +133,7 @@ where has_valid_pow, )?; - let gas_used = block_gas_meter - .finalize_transaction() - .map_err(Error::GasError)?; + let gas_used = tx_gas_meter.get_current_transaction_gas(); let initialized_accounts = write_log.get_initialized_accounts(); let changed_keys = write_log.get_keys(); let ibc_events = write_log.take_ibc_events(); @@ -140,15 +146,7 @@ where ibc_events, }) } - _ => { - let gas_used = block_gas_meter - .finalize_transaction() - .map_err(Error::GasError)?; - Ok(TxResult { - gas_used, - ..Default::default() - }) - } + _ => Ok(TxResult::default()), } } @@ -157,7 +155,8 @@ fn execute_tx( tx: &Tx, tx_index: &TxIndex, storage: &Storage, - gas_meter: &mut BlockGasMeter, + tx_gas_meter: &mut TxGasMeter, + gas_table: &BTreeMap, write_log: &mut WriteLog, vp_wasm_cache: &mut VpCache, tx_wasm_cache: &mut TxCache, @@ -167,12 +166,54 @@ where H: 'static + StorageHasher + Sync, CA: 'static + WasmCacheAccess + Sync, { + // Always account for compilation gas cost + let (tx_hash, tx_code) = if tx.code_or_hash.len() == HASH_LENGTH { + // we assume that there is no wasm code with HASH_LENGTH + let code_hash = Hash::try_from(tx.code_or_hash.as_slice()) + .map_err(|_| Error::TxCodeHashConversion)?; + + let key = storage::Key::wasm_code(&code_hash); + let tx_code = match write_log.read(&key).0 { + Some(StorageModification::Write { value }) => value.clone(), + _ => storage + .read(&key) + .map_err(Error::StorageError)? + .0 + .ok_or_else(|| { + Error::MissingWasmCodeInStorage(code_hash.clone()) + })?, + }; + + (code_hash, std::borrow::Cow::Owned(tx_code)) + } else { + ( + Hash(tx.code_hash()), + std::borrow::Cow::Borrowed(&tx.code_or_hash), + ) + }; + + tx_gas_meter + .add_compiling_fee(tx_code.len()) + .map_err(Error::GasError)?; + let tx_hash = tx_hash.to_string().to_ascii_lowercase(); + let tx_gas_required = match gas_table.get(tx_hash.as_str()) { + Some(gas) => gas.to_owned(), + #[cfg(any(test, feature = "testing"))] + None => 1_000, + #[cfg(not(any(test, feature = "testing")))] + None => return Err(Error::MissingGasCost(tx_hash)), + }; + tx_gas_meter.add(tx_gas_required).map_err(Error::GasError)?; + let empty = vec![]; let tx_data = tx.data.as_ref().unwrap_or(&empty); + // Mock gas meter to ignore runtime gas metering while we + // rely on the gas table + let mut mock_gas_meter = TxGasMeter::new(u64::MAX); wasm::run::tx( storage, write_log, - gas_meter, + &mut mock_gas_meter, tx_index, &tx.code_or_hash, tx_data, @@ -188,7 +229,8 @@ fn check_vps( tx: &Tx, tx_index: &TxIndex, storage: &Storage, - gas_meter: &mut BlockGasMeter, + tx_gas_meter: &mut TxGasMeter, + gas_table: &BTreeMap, write_log: &WriteLog, verifiers_from_tx: &BTreeSet
, vp_wasm_cache: &mut VpCache, @@ -205,8 +247,6 @@ where let (verifiers, keys_changed) = write_log.verifiers_and_changed_keys(verifiers_from_tx); - let initial_gas = gas_meter.get_current_transaction_gas(); - let vps_result = execute_vps( verifiers, keys_changed, @@ -214,14 +254,16 @@ where tx_index, storage, write_log, - initial_gas, + tx_gas_meter.tx_gas_limit, + tx_gas_meter.get_current_transaction_gas(), + gas_table, vp_wasm_cache, #[cfg(not(feature = "mainnet"))] has_valid_pow, )?; tracing::debug!("Total VPs gas cost {:?}", vps_result.gas_used); - gas_meter + tx_gas_meter .add_vps_gas(&vps_result.gas_used) .map_err(Error::GasError)?; @@ -237,7 +279,9 @@ fn execute_vps( tx_index: &TxIndex, storage: &Storage, write_log: &WriteLog, + tx_gas_limit: u64, initial_gas: u64, + gas_table: &BTreeMap, vp_wasm_cache: &mut VpCache, #[cfg(not(feature = "mainnet"))] // This is true when the wrapper of this tx contained a valid @@ -252,7 +296,7 @@ where verifiers .par_iter() .try_fold(VpsResult::default, |mut result, addr| { - let mut gas_meter = VpGasMeter::new(initial_gas); + let mut gas_meter = VpGasMeter::new(tx_gas_limit, initial_gas); let accept = match &addr { Address::Implicit(_) | Address::Established(_) => { let (vp_hash, gas) = storage @@ -267,6 +311,15 @@ where } }; + add_precomputed_gas( + &mut gas_meter, + gas_table, + &vp_code_hash.to_string().to_ascii_lowercase(), + )?; + + // Mock gas meter to ignore runtime gas metering while we + // rely on the gas table + let mut mock_gas_meter = VpGasMeter::new(u64::MAX, 0); wasm::run::vp( &vp_code_hash, tx, @@ -274,7 +327,7 @@ where addr, storage, write_log, - &mut gas_meter, + &mut mock_gas_meter, &keys_changed, &verifiers, vp_wasm_cache.clone(), @@ -284,13 +337,16 @@ where .map_err(Error::VpRunnerError) } Address::Internal(internal_addr) => { + // Mock gas meter to ignore runtime gas metering while we + // rely on the gas table + let mock_gas_meter = VpGasMeter::new(u64::MAX, 0); let ctx = native_vp::Ctx::new( addr, storage, write_log, tx, tx_index, - gas_meter, + mock_gas_meter, &keys_changed, &verifiers, vp_wasm_cache.clone(), @@ -302,6 +358,13 @@ where let accepted: Result = match internal_addr { InternalAddress::PoS => { + // No hash for native vps, just use the name + add_precomputed_gas( + &mut gas_meter, + gas_table, + "native_vp_pos", + )?; + let pos = PosVP { ctx }; let verifiers_addr_ref = &verifiers; let pos_ref = &pos; @@ -328,79 +391,105 @@ where Err(Error::PosNativeVpRuntime) } }; - // Take the gas meter back out of the context - gas_meter = pos.ctx.gas_meter.into_inner(); result } InternalAddress::Ibc => { + add_precomputed_gas( + &mut gas_meter, + gas_table, + "native_vp_ibc", + )?; let ibc = Ibc { ctx }; let result = ibc .validate_tx(tx_data, &keys_changed, &verifiers) .map_err(Error::IbcNativeVpError); - // Take the gas meter back out of the context - gas_meter = ibc.ctx.gas_meter.into_inner(); result } InternalAddress::Parameters => { + add_precomputed_gas( + &mut gas_meter, + gas_table, + "native_vp_parameters", + )?; let parameters = ParametersVp { ctx }; let result = parameters .validate_tx(tx_data, &keys_changed, &verifiers) .map_err(Error::ParametersNativeVpError); - // Take the gas meter back out of the context - gas_meter = parameters.ctx.gas_meter.into_inner(); result } InternalAddress::PosSlashPool => { - // Take the gas meter back out of the context - gas_meter = ctx.gas_meter.into_inner(); + add_precomputed_gas( + &mut gas_meter, + gas_table, + "native_vp_pos_slash_pool", + )?; Err(Error::AccessForbidden( (*internal_addr).clone(), )) } InternalAddress::Governance => { + add_precomputed_gas( + &mut gas_meter, + gas_table, + "native_vp_governance", + )?; let governance = GovernanceVp { ctx }; let result = governance .validate_tx(tx_data, &keys_changed, &verifiers) .map_err(Error::GovernanceNativeVpError); - gas_meter = governance.ctx.gas_meter.into_inner(); result } InternalAddress::SlashFund => { + add_precomputed_gas( + &mut gas_meter, + gas_table, + "native_vp_slash_fund", + )?; let slash_fund = SlashFundVp { ctx }; let result = slash_fund .validate_tx(tx_data, &keys_changed, &verifiers) .map_err(Error::SlashFundNativeVpError); - gas_meter = slash_fund.ctx.gas_meter.into_inner(); result } InternalAddress::IbcToken(_) | InternalAddress::IbcEscrow | InternalAddress::IbcBurn | InternalAddress::IbcMint => { + add_precomputed_gas( + &mut gas_meter, + gas_table, + "native_vp_ibc_token", + )?; // validate the transfer let ibc_token = IbcToken { ctx }; let result = ibc_token .validate_tx(tx_data, &keys_changed, &verifiers) .map_err(Error::IbcTokenNativeVpError); - gas_meter = ibc_token.ctx.gas_meter.into_inner(); result } InternalAddress::EthBridge => { + add_precomputed_gas( + &mut gas_meter, + gas_table, + "native_vp_eth_bridge", + )?; let bridge = EthBridge { ctx }; let result = bridge .validate_tx(tx_data, &keys_changed, &verifiers) .map_err(Error::EthBridgeNativeVpError); - gas_meter = bridge.ctx.gas_meter.into_inner(); result } InternalAddress::ReplayProtection => { + add_precomputed_gas( + &mut gas_meter, + gas_table, + "native_vp_replay_protection", + )?; let replay_protection_vp = ReplayProtectionVp { ctx }; let result = replay_protection_vp .validate_tx(tx_data, &keys_changed, &verifiers) .map_err(Error::ReplayProtectionNativeVpError); - gas_meter = - replay_protection_vp.ctx.gas_meter.into_inner(); result } }; @@ -433,7 +522,7 @@ where } }) .try_reduce(VpsResult::default, |a, b| { - merge_vp_results(a, b, initial_gas) + merge_vp_results(a, b, tx_gas_limit, initial_gas) }) } @@ -441,6 +530,7 @@ where fn merge_vp_results( a: VpsResult, mut b: VpsResult, + tx_gas_limit: u64, initial_gas: u64, ) -> Result { let mut accepted_vps = a.accepted_vps; @@ -456,7 +546,7 @@ fn merge_vp_results( // gas costs gas_used - .merge(&mut b.gas_used, initial_gas) + .merge(&mut b.gas_used, tx_gas_limit, initial_gas) .map_err(Error::GasError)?; Ok(VpsResult { @@ -466,3 +556,17 @@ fn merge_vp_results( errors, }) } + +/// Add the precomputed cost for the vp +fn add_precomputed_gas( + gas_meter: &mut VpGasMeter, + gas_table: &BTreeMap, + vp: &str, +) -> Result<()> { + let vp_gas_required = match gas_table.get(vp) { + Some(gas) => gas.to_owned(), + None => return Err(Error::MissingGasCost(vp.to_owned())), + }; + + gas_meter.add(vp_gas_required).map_err(Error::GasError) +} diff --git a/shared/src/ledger/queries/mod.rs b/shared/src/ledger/queries/mod.rs index 565100b81b..9c75edbada 100644 --- a/shared/src/ledger/queries/mod.rs +++ b/shared/src/ledger/queries/mod.rs @@ -155,6 +155,8 @@ pub mod tm { /// Queries testing helpers #[cfg(any(test, feature = "testing"))] mod testing { + use std::collections::BTreeMap; + use tempfile::TempDir; use super::*; @@ -191,9 +193,67 @@ mod testing { { #[allow(dead_code)] /// Initialize a test client for the given root RPC router - pub fn new(rpc: RPC) -> Self { + pub fn new(rpc: RPC) -> Self { // Initialize the `TestClient` - let wl_storage = TestWlStorage::default(); + let mut wl_storage = TestWlStorage::default(); + + // Initialize gas table + let checksums: BTreeMap = serde_json::from_slice( + &std::fs::read("../wasm/checksums.json").unwrap(), + ) + .unwrap(); + + let gas_file: BTreeMap = serde_json::from_slice( + &std::fs::read("../wasm/gas.json").unwrap(), + ) + .unwrap(); + + let mut gas_table = BTreeMap::::new(); + + for id in checksums.keys().chain(gas_file.keys()){ + // Get tx/vp hash (or name if native) + let hash = match checksums.get(id.as_str()) { + Some(v) => { +v + .split_once('.') + .unwrap() + .1 + .split_once('.') + .unwrap() + .0.to_owned() + } + None => { + id.to_owned() + } + }; + let gas = gas_file.get(id).unwrap_or(&1_000).to_owned(); + gas_table.insert(hash, gas); + } + + let gas_table_key = + namada_core::ledger::parameters::storage::get_gas_table_storage_key(); + wl_storage + .storage + .write(&gas_table_key, + namada_core::ledger::storage::types::encode(&gas_table)) + .expect( + "Gas table parameter must be initialized in the genesis block", + ); + + let max_block_gas_key = + namada_core::ledger::parameters::storage::get_max_block_gas_key( + ); + wl_storage + .storage + .write( + &max_block_gas_key, + namada_core::ledger::storage::types::encode( + &10_000_000_u64, + ), + ) + .expect( + "Max block gas parameter must be initialized in storage", + ); let event_log = EventLog::default(); let (vp_wasm_cache, vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 14bc0a8806..4757a6c43e 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -5,6 +5,7 @@ use masp_primitives::sapling::Node; use namada_core::types::address::Address; use namada_core::types::hash::Hash; use namada_core::types::storage::BlockResults; +use std::collections::BTreeMap; use crate::ledger::events::log::dumb_queries; use crate::ledger::events::Event; @@ -69,14 +70,27 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - use crate::ledger::gas::BlockGasMeter; + use namada_core::ledger::gas::TxGasMeter; + use namada_core::ledger::parameters; + use crate::ledger::protocol; use crate::ledger::storage::write_log::WriteLog; use crate::proto::Tx; use crate::types::storage::TxIndex; use crate::types::transaction::{DecryptedTx, TxType}; - let mut gas_meter = BlockGasMeter::default(); + let gas_table: BTreeMap = ctx + .wl_storage + .read(¶meters::storage::get_gas_table_storage_key()) + .expect("Error while reading storage") + .expect("Missing gas table in storage"); + + let mut tx_gas_meter = TxGasMeter::new( + ctx.wl_storage + .read(¶meters::storage::get_max_block_gas_key()) + .expect("Error while reading storage key") + .expect("Missing parameter in storage"), + ); let mut write_log = WriteLog::default(); let tx = Tx::try_from(&request.data[..]).into_storage_result()?; let tx = TxType::Decrypted(DecryptedTx::Decrypted { @@ -86,9 +100,9 @@ where }); let data = protocol::apply_tx( tx, - request.data.len(), TxIndex(0), - &mut gas_meter, + &mut tx_gas_meter, + &gas_table, &mut write_log, &ctx.wl_storage.storage, &mut ctx.vp_wasm_cache, diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 49625b9097..2f4c463a21 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -5,6 +5,7 @@ use std::convert::TryInto; use std::num::TryFromIntError; use borsh::{BorshDeserialize, BorshSerialize}; +use namada_core::ledger::gas::TxGasMeter; use namada_core::types::internal::KeyVal; use thiserror::Error; @@ -13,7 +14,7 @@ use super::wasm::TxCache; #[cfg(feature = "wasm-runtime")] use super::wasm::VpCache; use super::WasmCacheAccess; -use crate::ledger::gas::{self, BlockGasMeter, VpGasMeter, MIN_STORAGE_GAS}; +use crate::ledger::gas::{self, VpGasMeter, MIN_STORAGE_GAS}; use crate::ledger::storage::write_log::{self, WriteLog}; use crate::ledger::storage::{self, Storage, StorageHasher}; use crate::ledger::vp_host_fns; @@ -91,7 +92,7 @@ where /// Storage prefix iterators. pub iterators: MutHostRef<'a, &'a PrefixIterators<'a, DB>>, /// Transaction gas meter. - pub gas_meter: MutHostRef<'a, &'a BlockGasMeter>, + pub gas_meter: MutHostRef<'a, &'a TxGasMeter>, /// The transaction index is used to identify a shielded transaction's /// parent pub tx_index: HostRef<'a, &'a TxIndex>, @@ -131,7 +132,7 @@ where storage: &Storage, write_log: &mut WriteLog, iterators: &mut PrefixIterators<'a, DB>, - gas_meter: &mut BlockGasMeter, + gas_meter: &mut TxGasMeter, tx_index: &TxIndex, verifiers: &mut BTreeSet
, result_buffer: &mut Option>, @@ -2001,7 +2002,7 @@ pub mod testing { write_log: &mut WriteLog, iterators: &mut PrefixIterators<'static, DB>, verifiers: &mut BTreeSet
, - gas_meter: &mut BlockGasMeter, + gas_meter: &mut TxGasMeter, tx_index: &TxIndex, result_buffer: &mut Option>, #[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache, diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index 35e7875af2..011e30a538 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -3,6 +3,7 @@ use std::collections::BTreeSet; use std::marker::PhantomData; +use namada_core::ledger::gas::TxGasMeter; use parity_wasm::elements; use pwasm_utils::{self, rules}; use thiserror::Error; @@ -10,7 +11,7 @@ use wasmer::{BaseTunables, Module, Store}; use super::memory::{Limit, WasmMemory}; use super::TxCache; -use crate::ledger::gas::{BlockGasMeter, VpGasMeter}; +use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::{self, Storage, StorageHasher}; use crate::proto::Tx; @@ -82,7 +83,7 @@ pub type Result = std::result::Result; pub fn tx( storage: &Storage, write_log: &mut WriteLog, - gas_meter: &mut BlockGasMeter, + gas_meter: &mut TxGasMeter, tx_index: &TxIndex, tx_code: impl AsRef<[u8]>, tx_data: impl AsRef<[u8]>, @@ -485,6 +486,8 @@ mod tests { use crate::types::validity_predicate::EvalVp; use crate::vm::wasm; + const TX_GAS_LIMIT: u64 = 100_000_000; + /// Test that when a transaction wasm goes over the stack-height limit, the /// execution is aborted. #[test] @@ -531,7 +534,7 @@ mod tests { fn test_tx_memory_limiter_in_guest() { let storage = TestStorage::default(); let mut write_log = WriteLog::default(); - let mut gas_meter = BlockGasMeter::default(); + let mut gas_meter = TxGasMeter::new(TX_GAS_LIMIT); let tx_index = TxIndex::default(); // This code will allocate memory of the given size @@ -589,7 +592,7 @@ mod tests { let mut storage = TestStorage::default(); let addr = storage.address_gen.generate_address("rng seed"); let write_log = WriteLog::default(); - let mut gas_meter = VpGasMeter::new(0); + let mut gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); let tx_index = TxIndex::default(); @@ -677,7 +680,7 @@ mod tests { let mut storage = TestStorage::default(); let addr = storage.address_gen.generate_address("rng seed"); let write_log = WriteLog::default(); - let mut gas_meter = VpGasMeter::new(0); + let mut gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); let tx_index = TxIndex::default(); @@ -742,7 +745,7 @@ mod tests { fn test_tx_memory_limiter_in_host_input() { let storage = TestStorage::default(); let mut write_log = WriteLog::default(); - let mut gas_meter = BlockGasMeter::default(); + let mut gas_meter = TxGasMeter::new(TX_GAS_LIMIT); let tx_index = TxIndex::default(); let tx_no_op = TestWasms::TxNoOp.read_bytes(); @@ -798,7 +801,7 @@ mod tests { let mut storage = TestStorage::default(); let addr = storage.address_gen.generate_address("rng seed"); let write_log = WriteLog::default(); - let mut gas_meter = VpGasMeter::new(0); + let mut gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); let tx_index = TxIndex::default(); @@ -860,7 +863,7 @@ mod tests { fn test_tx_memory_limiter_in_host_env() { let mut storage = TestStorage::default(); let mut write_log = WriteLog::default(); - let mut gas_meter = BlockGasMeter::default(); + let mut gas_meter = TxGasMeter::new(TX_GAS_LIMIT); let tx_index = TxIndex::default(); let tx_read_key = TestWasms::TxReadStorageKey.read_bytes(); @@ -908,7 +911,7 @@ mod tests { let mut storage = TestStorage::default(); let addr = storage.address_gen.generate_address("rng seed"); let write_log = WriteLog::default(); - let mut gas_meter = VpGasMeter::new(0); + let mut gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); let tx_index = TxIndex::default(); @@ -961,7 +964,7 @@ mod tests { let mut storage = TestStorage::default(); let addr = storage.address_gen.generate_address("rng seed"); let write_log = WriteLog::default(); - let mut gas_meter = VpGasMeter::new(0); + let mut gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); let tx_index = TxIndex::default(); @@ -1055,7 +1058,7 @@ mod tests { let tx_index = TxIndex::default(); let storage = TestStorage::default(); let mut write_log = WriteLog::default(); - let mut gas_meter = BlockGasMeter::default(); + let mut gas_meter = TxGasMeter::new(TX_GAS_LIMIT); let (mut vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let (mut tx_cache, _) = @@ -1111,7 +1114,7 @@ mod tests { let mut storage = TestStorage::default(); let addr = storage.address_gen.generate_address("rng seed"); let write_log = WriteLog::default(); - let mut gas_meter = VpGasMeter::new(0); + let mut gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); diff --git a/tests/src/native_vp/mod.rs b/tests/src/native_vp/mod.rs index 6baf214a3b..5ffc2e65bb 100644 --- a/tests/src/native_vp/mod.rs +++ b/tests/src/native_vp/mod.rs @@ -1,7 +1,9 @@ pub mod pos; +use std::cell::RefCell; use std::collections::BTreeSet; +use namada::ledger::gas::VpGasMeter; use namada::ledger::native_vp::{Ctx, NativeVp}; use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::Sha256Hasher; @@ -48,7 +50,7 @@ impl TestNativeVpEnv { { let ctx = Ctx { iterators: Default::default(), - gas_meter: Default::default(), + gas_meter: RefCell::new(VpGasMeter::new(0, 0)), storage: &self.tx_env.wl_storage.storage, write_log: &self.tx_env.wl_storage.write_log, tx: &self.tx_env.tx, diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index b60e627804..e591e281bf 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -562,6 +562,7 @@ pub mod testing { use derivative::Derivative; use itertools::Either; + use namada::ledger::gas::TxGasMeter; use namada::proof_of_stake::epoched::DynEpochOffset; use namada::proof_of_stake::parameters::testing::arb_rate; use namada::proof_of_stake::parameters::PosParams; @@ -843,7 +844,7 @@ pub mod testing { let current_epoch = tx_host_env::with(|env| { // Reset the gas meter on each change, so that we never run // out in this test - env.gas_meter.reset(); + env.gas_meter = TxGasMeter::new(env.gas_meter.tx_gas_limit); env.wl_storage.storage.block.epoch }); println!("Current epoch {}", current_epoch); @@ -1522,31 +1523,29 @@ pub mod testing { let arb_delta = prop_oneof![(-(u32::MAX as i128)..0), (1..=u32::MAX as i128),]; - prop_oneof![ - ( - arb_address_or_validator.clone(), - arb_address_or_validator, - arb_offset, - arb_delta, - ) - .prop_map(|(validator, owner, offset, delta)| { - vec![ - // We have to ensure that the addresses exists - PosStorageChange::SpawnAccount { - address: validator.clone(), - }, - PosStorageChange::SpawnAccount { - address: owner.clone(), - }, - PosStorageChange::Bond { - owner, - validator, - delta, - offset, - }, - ] - }) - ] + prop_oneof![( + arb_address_or_validator.clone(), + arb_address_or_validator, + arb_offset, + arb_delta, + ) + .prop_map(|(validator, owner, offset, delta)| { + vec![ + // We have to ensure that the addresses exists + PosStorageChange::SpawnAccount { + address: validator.clone(), + }, + PosStorageChange::SpawnAccount { + address: owner.clone(), + }, + PosStorageChange::Bond { + owner, + validator, + delta, + offset, + }, + ] + })] } impl InvalidPosAction { diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 47ff3808fd..b2906fee60 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -157,7 +157,7 @@ pub fn validate_ibc_vp_from_tx<'a>( &tx_env.wl_storage.write_log, tx, &TxIndex(0), - VpGasMeter::new(0), + VpGasMeter::new(1_000_000, 0), &keys_changed, &verifiers, vp_wasm_cache, @@ -193,7 +193,7 @@ pub fn validate_token_vp_from_tx<'a>( &tx_env.wl_storage.write_log, tx, &TxIndex(0), - VpGasMeter::new(0), + VpGasMeter::new(1_000_000, 0), &keys_changed, &verifiers, vp_wasm_cache, diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 1ad4b22599..1a9474c6cb 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -133,13 +133,11 @@ mod tests { // Trying to delete a validity predicate should fail let key = storage::Key::validity_predicate(&test_account); - assert!( - panic::catch_unwind(|| { tx::ctx().delete(&key).unwrap() }) - .err() - .map(|a| a.downcast_ref::().cloned().unwrap()) - .unwrap() - .contains("CannotDeleteVp") - ); + assert!(panic::catch_unwind(|| { tx::ctx().delete(&key).unwrap() }) + .err() + .map(|a| a.downcast_ref::().cloned().unwrap()) + .unwrap() + .contains("CannotDeleteVp")); } #[test] @@ -470,21 +468,17 @@ mod tests { .expect("decoding signed data we just signed") }); assert_eq!(&signed_tx_data.data, data); - assert!( - vp::CTX - .verify_tx_signature(&pk, &signed_tx_data.sig) - .unwrap() - ); + assert!(vp::CTX + .verify_tx_signature(&pk, &signed_tx_data.sig) + .unwrap()); let other_keypair = key::testing::keypair_2(); - assert!( - !vp::CTX - .verify_tx_signature( - &other_keypair.ref_to(), - &signed_tx_data.sig - ) - .unwrap() - ); + assert!(!vp::CTX + .verify_tx_signature( + &other_keypair.ref_to(), + &signed_tx_data.sig + ) + .unwrap()); } } diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index b67cac2416..771e8e587e 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -1,7 +1,7 @@ use std::borrow::Borrow; use std::collections::BTreeSet; -use namada::ledger::gas::BlockGasMeter; +use namada::ledger::gas::TxGasMeter; use namada::ledger::parameters::{self, EpochDuration}; use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::testing::TestStorage; @@ -47,7 +47,7 @@ pub struct TestTxEnv { pub wl_storage: WlStorage, pub iterators: PrefixIterators<'static, MockDB>, pub verifiers: BTreeSet
, - pub gas_meter: BlockGasMeter, + pub gas_meter: TxGasMeter, pub tx_index: TxIndex, pub result_buffer: Option>, pub vp_wasm_cache: VpCache, @@ -71,7 +71,7 @@ impl Default for TestTxEnv { Self { wl_storage, iterators: PrefixIterators::default(), - gas_meter: BlockGasMeter::default(), + gas_meter: TxGasMeter::new(10_000_000), tx_index: TxIndex::default(), verifiers: BTreeSet::default(), result_buffer: None, @@ -168,7 +168,6 @@ impl TestTxEnv { .ok(); self.iterators = PrefixIterators::default(); self.verifiers = BTreeSet::default(); - self.gas_meter = BlockGasMeter::default(); } /// Credit tokens to the target account. diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index 8220ce4c64..f030cb8c51 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -73,7 +73,7 @@ impl Default for TestVpEnv { addr: address::testing::established_address_1(), wl_storage, iterators: PrefixIterators::default(), - gas_meter: VpGasMeter::default(), + gas_meter: VpGasMeter::new(10_000_000, 0), tx: Tx::new(vec![], None, chain_id, None), tx_index: TxIndex::default(), keys_changed: BTreeSet::default(), From 682d81b46907c4acec3bd1c2d68fbda4455132cd Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 6 Mar 2023 16:49:03 +0100 Subject: [PATCH 02/23] Adds benchmarks --- Cargo.toml | 1 + Makefile | 6 +- apps/src/lib/cli/context.rs | 32 +- apps/src/lib/client/rpc.rs | 65 +- apps/src/lib/client/tx.rs | 116 ++-- apps/src/lib/node/ledger/mod.rs | 2 +- apps/src/lib/node/ledger/shell/mod.rs | 8 +- .../lib/node/ledger/shell/process_proposal.rs | 2 +- apps/src/lib/wallet/mod.rs | 2 +- benches/Cargo.toml | 56 ++ benches/host_env.rs | 38 ++ benches/mod.rs | 616 ++++++++++++++++++ benches/native_vps.rs | 447 +++++++++++++ benches/process_wrapper.rs | 90 +++ benches/txs.rs | 542 +++++++++++++++ benches/vps.rs | 559 ++++++++++++++++ core/src/ledger/gas.rs | 6 +- shared/src/ledger/queries/types.rs | 4 +- wasm/gas.json | 21 + 19 files changed, 2508 insertions(+), 105 deletions(-) create mode 100644 benches/Cargo.toml create mode 100644 benches/host_env.rs create mode 100644 benches/mod.rs create mode 100644 benches/native_vps.rs create mode 100644 benches/process_wrapper.rs create mode 100644 benches/txs.rs create mode 100644 benches/vps.rs create mode 100644 wasm/gas.json diff --git a/Cargo.toml b/Cargo.toml index e6908495a1..264c366ddc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "apps", + "benches", "core", "proof_of_stake", "shared", diff --git a/Makefile b/Makefile index d42b3fe3ec..bbb2553b11 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,7 @@ check-abcipp: $(cargo) +$(nightly) check \ --workspace \ --exclude namada_tests \ + --exclude namada_benchmarks \ --all-targets \ --no-default-features \ --features "abcipp ibc-mocks-abcipp testing" \ @@ -213,6 +214,9 @@ watch: clean: $(cargo) clean +bench: + $(cargo) +$(nightly) bench -Z unstable-options + build-doc: $(cargo) doc --no-deps @@ -266,4 +270,4 @@ test-miri: MIRIFLAGS="-Zmiri-disable-isolation" $(cargo) +$(nightly) miri test -.PHONY : build check build-release clippy install run-ledger run-gossip reset-ledger test test-debug fmt watch clean build-doc doc build-wasm-scripts-docker debug-wasm-scripts-docker build-wasm-scripts debug-wasm-scripts clean-wasm-scripts dev-deps test-miri test-unit test-unit-abcipp clippy-abcipp +.PHONY : build check build-release clippy install run-ledger run-gossip reset-ledger test test-debug fmt watch clean build-doc doc build-wasm-scripts-docker debug-wasm-scripts-docker build-wasm-scripts debug-wasm-scripts clean-wasm-scripts dev-deps test-miri test-unit test-unit-abcipp clippy-abcipp bench-gas diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index dd3b5304e5..7f449658f8 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -14,7 +14,7 @@ use namada::types::masp::*; use super::args; use crate::client::tx::ShieldedContext; -use crate::config::genesis::genesis_config; +use crate::config::genesis; use crate::config::global::GlobalConfig; use crate::config::{self, Config}; use crate::wallet::{AddressVpType, Wallet}; @@ -92,15 +92,29 @@ impl Context { let chain_dir = global_args .base_dir .join(global_config.default_chain_id.as_str()); - let genesis_file_path = global_args - .base_dir - .join(format!("{}.toml", global_config.default_chain_id.as_str())); - let genesis = genesis_config::read_genesis_config(&genesis_file_path); + + #[cfg(not(feature = "dev"))] + let genesis = genesis::genesis( + &global_args.base_dir, + &global_config.default_chain_id, + ); + #[cfg(feature = "dev")] + let genesis = genesis::genesis(1); + let native_token = genesis.native_token; - let default_genesis = - genesis_config::open_genesis_config(genesis_file_path)?; - let wallet = - Wallet::load_or_new_from_genesis(&chain_dir, default_genesis); + #[cfg(not(feature = "dev"))] + let wallet = { + let genesis_file_path = global_args.base_dir.join(format!( + "{}.toml", + global_config.default_chain_id.as_str() + )); + let default_genesis = genesis::genesis_config::open_genesis_config( + genesis_file_path, + )?; + Wallet::load_or_new_from_genesis(&chain_dir, default_genesis) + }; + #[cfg(feature = "dev")] + let wallet = Wallet::load_or_new(&chain_dir); // If the WASM dir specified, put it in the config match global_args.wasm_dir.as_ref() { diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index f5e9fa8c57..b78bba930a 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -3,6 +3,7 @@ use std::cmp::Ordering; use std::collections::{BTreeMap, HashMap, HashSet}; use std::convert::TryInto; +use std::fmt::Display; use std::fs::File; use std::io::{self, Write}; use std::iter::Iterator; @@ -31,7 +32,7 @@ use namada::ledger::parameters::{storage as param_storage, EpochDuration}; use namada::ledger::pos::{ self, BondId, BondsAndUnbondsDetail, CommissionPair, PosParams, Slash, }; -use namada::ledger::queries::{self, RPC}; +use namada::ledger::queries::RPC; use namada::ledger::storage::ConversionState; use namada::proto::{SignedTxData, Tx}; use namada::types::address::{masp, Address}; @@ -125,7 +126,10 @@ pub async fn query_and_print_epoch(args: args::Query) -> Epoch { } /// Query the epoch of the last committed block -pub async fn query_epoch(client: &HttpClient) -> Epoch { +pub async fn query_epoch(client: &CLIENT) -> Epoch +where + CLIENT: namada::ledger::queries::Client + Sync, +{ unwrap_client_response(RPC.shell().epoch(client).await) } @@ -171,7 +175,7 @@ pub async fn query_tx_deltas( .values() .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) .collect(); - ctx.shielded.fetch(&ledger_address, &[], &fvks).await; + ctx.shielded.fetch(&client, &[], &fvks).await; // Save the update state so that future fetches can be short-circuited let _ = ctx.shielded.save(); // Required for filtering out rejected transactions from Tendermint @@ -305,15 +309,14 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { let amt = ctx .shielded .compute_exchanged_amount( - client.clone(), + &client, amt, epoch, Conversions::new(), ) .await .0; - let dec = - ctx.shielded.decode_amount(client.clone(), amt, epoch).await; + let dec = ctx.shielded.decode_amount(&client, amt, epoch).await; shielded_accounts.insert(acc, dec); } // Check if this transfer pertains to the supplied token @@ -634,10 +637,8 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { (Ok((balance, epoch)), None) => { let mut found_any = false; // Print balances by human-readable token names - let balance = ctx - .shielded - .decode_amount(client.clone(), balance, epoch) - .await; + let balance = + ctx.shielded.decode_amount(&client, balance, epoch).await; for (addr, value) in balance.components() { let asset_value = token::Amount::from(*value as u64); if !found_any { @@ -896,15 +897,13 @@ pub async fn query_shielded_balance( .iter() .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) .collect(); - ctx.shielded - .fetch(&args.query.ledger_address, &[], &fvks) - .await; + // Establish connection with which to do exchange rate queries + let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + ctx.shielded.fetch(&client, &[], &fvks).await; // Save the update state so that future fetches can be short-circuited let _ = ctx.shielded.save(); // The epoch is required to identify timestamped tokens let epoch = query_and_print_epoch(args.query.clone()).await; - // Establish connection with which to do exchange rate queries - let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); // Map addresses to token names let tokens = ctx.tokens(); match (args.token, owner.is_some()) { @@ -982,10 +981,8 @@ pub async fn query_shielded_balance( // Print non-zero balances whose asset types can be decoded for (asset_type, balances) in balances { // Decode the asset type - let decoded = ctx - .shielded - .decode_asset_type(client.clone(), asset_type) - .await; + let decoded = + ctx.shielded.decode_asset_type(&client, asset_type).await; match decoded { Some((addr, asset_epoch)) if asset_epoch == epoch => { // Only assets with the current timestamp count @@ -1084,10 +1081,8 @@ pub async fn query_shielded_balance( .compute_shielded_balance(&viewing_key) .expect("context should contain viewing key"); // Print balances by human-readable token names - let decoded_balance = ctx - .shielded - .decode_all_amounts(client.clone(), balance) - .await; + let decoded_balance = + ctx.shielded.decode_all_amounts(&client, balance).await; print_decoded_balance_with_epoch(ctx, decoded_balance); } else { balance = ctx @@ -1100,10 +1095,8 @@ pub async fn query_shielded_balance( .await .expect("context should contain viewing key"); // Print balances by human-readable token names - let decoded_balance = ctx - .shielded - .decode_amount(client.clone(), balance, epoch) - .await; + let decoded_balance = + ctx.shielded.decode_amount(&client, balance, epoch).await; print_decoded_balance(ctx, decoded_balance); } } @@ -1928,17 +1921,20 @@ pub async fn query_conversions(ctx: Context, args: args::QueryConversions) { } /// Query a conversion. -pub async fn query_conversion( - client: HttpClient, +pub async fn query_conversion( + client: &CLIENT, asset_type: AssetType, ) -> Option<( Address, Epoch, masp_primitives::transaction::components::Amount, MerklePath, -)> { +)> +where + CLIENT: namada::ledger::queries::Client + Sync, +{ Some(unwrap_client_response( - RPC.shell().read_conversion(&client, &asset_type).await, + RPC.shell().read_conversion(client, &asset_type).await, )) } @@ -1969,7 +1965,7 @@ pub async fn query_wasm_code_hash( /// Query a storage value and decode it with [`BorshDeserialize`]. pub async fn query_storage_value( - client: &HttpClient, + client: &(impl namada::ledger::queries::Client + Sync), key: &storage::Key, ) -> Option where @@ -2609,7 +2605,10 @@ fn lookup_alias(ctx: &Context, addr: &Address) -> String { } /// A helper to unwrap client's response. Will shut down process on error. -fn unwrap_client_response(response: Result) -> T { +fn unwrap_client_response(response: Result) -> T +where + E: Display, +{ response.unwrap_or_else(|err| { eprintln!("Error in the query {}", err); cli::safe_exit(1) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 70762443ca..be64e57c11 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -644,12 +644,14 @@ impl ShieldedContext { /// Fetch the current state of the multi-asset shielded pool into a /// ShieldedContext - pub async fn fetch( + pub async fn fetch( &mut self, - ledger_address: &TendermintAddress, + client: &CLIENT, sks: &[ExtendedSpendingKey], fvks: &[ViewingKey], - ) { + ) where + CLIENT: namada::ledger::queries::Client + Sync, + { // First determine which of the keys requested to be fetched are new. // Necessary because old transactions will need to be scanned for new // keys. @@ -671,7 +673,7 @@ impl ShieldedContext { let (txs, mut tx_iter); if !unknown_keys.is_empty() { // Load all transactions accepted until this point - txs = Self::fetch_shielded_transfers(ledger_address, 0).await; + txs = Self::fetch_shielded_transfers(client, 0).await; tx_iter = txs.iter(); // Do this by constructing a shielding context only for unknown keys let mut tx_ctx = ShieldedContext::new(self.context_dir.clone()); @@ -691,9 +693,7 @@ impl ShieldedContext { self.merge(tx_ctx); } else { // Load only transactions accepted from last_txid until this point - txs = - Self::fetch_shielded_transfers(ledger_address, self.last_txidx) - .await; + txs = Self::fetch_shielded_transfers(client, self.last_txidx).await; tx_iter = txs.iter(); } // Now that we possess the unspent notes corresponding to both old and @@ -733,11 +733,13 @@ impl ShieldedContext { /// transactions as a vector. More concretely, the HEAD_TX_KEY location /// stores the index of the last accepted transaction and each transaction /// is stored at a key derived from its index. - pub async fn fetch_shielded_transfers( - ledger_address: &TendermintAddress, + pub async fn fetch_shielded_transfers( + client: &CLIENT, last_txidx: u64, - ) -> BTreeMap<(BlockHeight, TxIndex), (Epoch, Transfer)> { - let client = HttpClient::new(ledger_address.clone()).unwrap(); + ) -> BTreeMap<(BlockHeight, TxIndex), (Epoch, Transfer)> + where + CLIENT: namada::ledger::queries::Client + Sync, + { // The address of the MASP account let masp_addr = masp(); // Construct the key where last transaction pointer is stored @@ -745,7 +747,7 @@ impl ShieldedContext { .push(&HEAD_TX_KEY.to_owned()) .expect("Cannot obtain a storage key"); // Query for the index of the last accepted transaction - let head_txidx = query_storage_value::(&client, &head_tx_key) + let head_txidx = query_storage_value::(client, &head_tx_key) .await .unwrap_or(0); let mut shielded_txs = BTreeMap::new(); @@ -758,7 +760,7 @@ impl ShieldedContext { // Obtain the current transaction let (tx_epoch, tx_height, tx_index, current_tx) = query_storage_value::<(Epoch, BlockHeight, TxIndex, Transfer)>( - &client, + client, ¤t_tx_key, ) .await @@ -917,11 +919,14 @@ impl ShieldedContext { /// Query the ledger for the decoding of the given asset type and cache it /// if it is found. - pub async fn decode_asset_type( + pub async fn decode_asset_type( &mut self, - client: HttpClient, + client: &CLIENT, asset_type: AssetType, - ) -> Option<(Address, Epoch)> { + ) -> Option<(Address, Epoch)> + where + CLIENT: namada::ledger::queries::Client + Sync, + { // Try to find the decoding in the cache if let decoded @ Some(_) = self.asset_types.get(&asset_type) { return decoded.cloned(); @@ -935,12 +940,15 @@ impl ShieldedContext { /// Query the ledger for the conversion that is allowed for the given asset /// type and cache it. - async fn query_allowed_conversion<'a>( + async fn query_allowed_conversion<'a, CLIENT>( &'a mut self, - client: HttpClient, + client: &CLIENT, asset_type: AssetType, conversions: &'a mut Conversions, - ) -> Option<&'a mut (AllowedConversion, MerklePath, i64)> { + ) -> Option<&'a mut (AllowedConversion, MerklePath, i64)> + where + CLIENT: namada::ledger::queries::Client + Sync, + { match conversions.entry(asset_type) { Entry::Occupied(conv_entry) => Some(conv_entry.into_mut()), Entry::Vacant(conv_entry) => { @@ -973,7 +981,7 @@ impl ShieldedContext { // And then exchange balance into current asset types Some( self.compute_exchanged_amount( - client, + &client, balance, target_epoch, HashMap::new(), @@ -1027,13 +1035,16 @@ impl ShieldedContext { /// note of the conversions that were used. Note that this function does /// not assume that allowed conversions from the ledger are expressed in /// terms of the latest asset types. - pub async fn compute_exchanged_amount( + pub async fn compute_exchanged_amount( &mut self, - client: HttpClient, + client: &CLIENT, mut input: Amount, target_epoch: Epoch, mut conversions: Conversions, - ) -> (Amount, Conversions) { + ) -> (Amount, Conversions) + where + CLIENT: namada::ledger::queries::Client + Sync, + { // Where we will store our exchanged value let mut output = Amount::zero(); // Repeatedly exchange assets until it is no longer possible @@ -1041,14 +1052,14 @@ impl ShieldedContext { input.components().next().map(cloned_pair) { let target_asset_type = self - .decode_asset_type(client.clone(), asset_type) + .decode_asset_type(client, asset_type) .await .map(|(addr, _epoch)| make_asset_type(target_epoch, &addr)) .unwrap_or(asset_type); let at_target_asset_type = asset_type == target_asset_type; if let (Some((conv, _wit, usage)), false) = ( self.query_allowed_conversion( - client.clone(), + client, asset_type, &mut conversions, ) @@ -1071,7 +1082,7 @@ impl ShieldedContext { ); } else if let (Some((conv, _wit, usage)), false) = ( self.query_allowed_conversion( - client.clone(), + client, target_asset_type, &mut conversions, ) @@ -1107,9 +1118,9 @@ impl ShieldedContext { /// of the specified asset type. Return the total value accumulated plus /// notes and the corresponding diversifiers/merkle paths that were used to /// achieve the total value. - pub async fn collect_unspent_notes( + pub async fn collect_unspent_notes( &mut self, - ledger_address: TendermintAddress, + client: &CLIENT, vk: &ViewingKey, target: Amount, target_epoch: Epoch, @@ -1117,9 +1128,10 @@ impl ShieldedContext { Amount, Vec<(Diversifier, Note, MerklePath)>, Conversions, - ) { - // Establish connection with which to do exchange rate queries - let client = HttpClient::new(ledger_address.clone()).unwrap(); + ) + where + CLIENT: namada::ledger::queries::Client + Sync, + { let mut conversions = HashMap::new(); let mut val_acc = Amount::zero(); let mut notes = Vec::new(); @@ -1143,7 +1155,7 @@ impl ShieldedContext { .expect("received note has invalid value or asset type"); let (contr, proposed_convs) = self .compute_exchanged_amount( - client.clone(), + client, pre_contr, target_epoch, conversions.clone(), @@ -1264,7 +1276,7 @@ impl ShieldedContext { let client = HttpClient::new(ledger_address.clone()).unwrap(); // Finally, exchange the balance to the transaction's epoch Ok(( - self.compute_exchanged_amount(client, amt, ep, HashMap::new()) + self.compute_exchanged_amount(&client, amt, ep, HashMap::new()) .await .0, ep, @@ -1276,15 +1288,14 @@ impl ShieldedContext { /// the given epoch are ignored. pub async fn decode_amount( &mut self, - client: HttpClient, + client: &HttpClient, amt: Amount, target_epoch: Epoch, ) -> Amount
{ let mut res = Amount::zero(); for (asset_type, val) in amt.components() { // Decode the asset type - let decoded = - self.decode_asset_type(client.clone(), *asset_type).await; + let decoded = self.decode_asset_type(client, *asset_type).await; // Only assets with the target timestamp count match decoded { Some((addr, epoch)) if epoch == target_epoch => { @@ -1300,14 +1311,13 @@ impl ShieldedContext { /// Addresses that they decode to. pub async fn decode_all_amounts( &mut self, - client: HttpClient, + client: &HttpClient, amt: Amount, ) -> Amount<(Address, Epoch)> { let mut res = Amount::zero(); for (asset_type, val) in amt.components() { // Decode the asset type - let decoded = - self.decode_asset_type(client.clone(), *asset_type).await; + let decoded = self.decode_asset_type(client, *asset_type).await; // Only assets with the target timestamp count if let Some((addr, epoch)) = decoded { res += &Amount::from_pair((addr, epoch), *val).unwrap() @@ -1347,12 +1357,15 @@ fn convert_amount( /// transactions balanced, but it is understood that transparent account changes /// are effected only by the amounts and signatures specified by the containing /// Transfer object. -async fn gen_shielded_transfer( +pub async fn gen_shielded_transfer( ctx: &mut Context, - client: &HttpClient, + client: &CLIENT, args: &args::TxTransfer, shielded_gas: bool, -) -> Result, builder::Error> { +) -> Result, builder::Error> +where + CLIENT: namada::ledger::queries::Client + Sync, +{ // No shielded components are needed when neither source nor destination // are shielded let spending_key = ctx.get_cached(&args.source).spending_key(); @@ -1368,9 +1381,7 @@ async fn gen_shielded_transfer( // Load the current shielded context given the spending key we // possess let _ = ctx.shielded.load(); - ctx.shielded - .fetch(&args.tx.ledger_address, &spending_keys, &[]) - .await; + ctx.shielded.fetch(client, &spending_keys, &[]).await; // Save the update state so that future fetches can be // short-circuited let _ = ctx.shielded.save(); @@ -1398,6 +1409,7 @@ async fn gen_shielded_transfer( args.tx.fee_amount, ); builder.set_fee(fee.clone())?; + // FIXME: fix gas here? // If the gas is coming from the shielded pool, then our shielded inputs // must also cover the gas fee let required_amt = if shielded_gas { amount + fee } else { amount }; @@ -1405,7 +1417,7 @@ async fn gen_shielded_transfer( let (_, unspent_notes, used_convs) = ctx .shielded .collect_unspent_notes( - args.tx.ledger_address.clone(), + client, &to_viewing_key(&sk).vk, required_amt, epoch, @@ -1428,6 +1440,7 @@ async fn gen_shielded_transfer( } else { // No transfer fees come from the shielded transaction for non-MASP // sources + //FIXME: fix gas here? builder.set_fee(Amount::zero())?; // We add a dummy UTXO to our transaction, but only the source of the // parent Transfer object is used to validate fund availability @@ -1437,8 +1450,9 @@ async fn gen_shielded_transfer( let secp_pk = secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) .serialize(); - let hash = - ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); + let hash = ripemd160::Ripemd160::digest( + sha2::Sha256::digest(&secp_pk).as_slice(), + ); let script = TransparentAddress::PublicKey(hash.into()).script(); builder.add_transparent_input( secp_sk, @@ -1470,9 +1484,9 @@ async fn gen_shielded_transfer( .expect("target address should be transparent") .try_to_vec() .expect("target address encoding"); - let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest( - target_enc.as_ref(), - )); + let hash = ripemd160::Ripemd160::digest( + sha2::Sha256::digest(target_enc.as_ref()).as_slice(), + ); builder.add_transparent_output( &TransparentAddress::PublicKey(hash.into()), asset_type, diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 810b2eead8..8ea421af56 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -1,6 +1,6 @@ mod abortable; mod broadcaster; -mod shell; +pub mod shell; mod shims; pub mod storage; pub mod tendermint_node; diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 2a49c81ace..55398fd2a9 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -10,7 +10,7 @@ mod finalize_block; mod governance; mod init_chain; mod prepare_proposal; -mod process_proposal; +pub mod process_proposal; mod queries; mod stats; @@ -257,7 +257,7 @@ where #[allow(dead_code)] chain_id: ChainId, /// The persistent storage with write log - pub(super) wl_storage: WlStorage, + pub wl_storage: WlStorage, /// Byzantine validators given from ABCI++ `prepare_proposal` are stored in /// this field. They will be slashed when we finalize the block. byzantine_validators: Vec, @@ -270,9 +270,9 @@ where #[allow(dead_code)] mode: ShellMode, /// VP WASM compilation cache - vp_wasm_cache: VpCache, + pub vp_wasm_cache: VpCache, /// Tx WASM compilation cache - tx_wasm_cache: TxCache, + pub tx_wasm_cache: TxCache, /// Taken from config `storage_read_past_height_limit`. When set, will /// limit the how many block heights in the past can the storage be /// queried for reading values. diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 22b88e1146..05fdc36aa2 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -205,7 +205,7 @@ where /// INVARIANT: Any changes applied in this method must be reverted if the /// proposal is rejected (unless we can simply overwrite them in the /// next block). - pub(crate) fn process_single_tx<'a>( + pub fn process_single_tx<'a>( &self, tx_bytes: &[u8], tx_queue_iter: &mut impl Iterator, diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index d823334a00..9e73bf1e43 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -258,7 +258,7 @@ impl Wallet { } pub fn find_viewing_key( - &mut self, + &self, alias: impl AsRef, ) -> Result<&ExtendedViewingKey, FindKeyError> { self.store diff --git a/benches/Cargo.toml b/benches/Cargo.toml new file mode 100644 index 0000000000..c61e367947 --- /dev/null +++ b/benches/Cargo.toml @@ -0,0 +1,56 @@ +[package] +authors = ["Heliax AG "] +description = "Namada benchmarks" +edition = "2021" +license = "GPL-3.0" +name = "namada_benchmarks" +resolver = "2" +version = "0.14.2" + +[lib] +name = "namada_benches" +path = "mod.rs" + +[[bench]] +name = "whitelisted_txs" +harness = false +path = "txs.rs" + +[[bench]] +name = "whitelisted_vps" +harness = false +path = "vps.rs" + +[[bench]] +name = "native_vps" +harness = false +path = "native_vps.rs" + +[[bench]] +name = "process_wrapper" +harness = false +path = "process_wrapper.rs" + +[[bench]] +name = "host_env" +harness = false +path = "host_env.rs" + +[dependencies] +async-trait = "0.1.51" +borsh = "0.9.0" +ferveo-common = {git = "https://github.com/anoma/ferveo"} +masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } +namada = {path = "../shared" } +namada_apps = {path = "../apps", features = ["dev"]} +namada_test_utils = {path = "../test_utils"} +namada_tx_prelude = {path = "../tx_prelude", default-features = false} +prost = "0.9.0" +rand = "0.8" +rand_core = "0.6" +rust_decimal = "1.26.1" +tokio = "1.8.2" +tempfile = "3.2.0" + +[dev-dependencies] +criterion = { version = "0.4", features = ["html_reports"] } diff --git a/benches/host_env.rs b/benches/host_env.rs new file mode 100644 index 0000000000..f9af56b159 --- /dev/null +++ b/benches/host_env.rs @@ -0,0 +1,38 @@ +use borsh::BorshDeserialize; +use criterion::{criterion_group, criterion_main, Criterion}; +use namada::core::types::address; +use namada::core::types::token::{Amount, Transfer}; +use namada_apps::wallet::defaults; +use namada_benches::{generate_tx, TX_TRANSFER_WASM}; + +use namada::core::proto::SignedTxData; +use namada::core::types::key::RefTo; + +fn tx_signature_validation(c: &mut Criterion) { + let tx = generate_tx( + TX_TRANSFER_WASM, + Transfer { + source: defaults::albert_address(), + target: defaults::bertha_address(), + token: address::nam(), + sub_prefix: None, + amount: Amount::whole(500), + key: None, + shielded: None, + }, + &defaults::albert_keypair(), + ); + + let SignedTxData { data: _, ref sig } = + SignedTxData::try_from_slice(&tx.data.as_ref().unwrap()).unwrap(); + + c.bench_function("tx_signature_validation", |b| { + b.iter(|| { + tx.verify_sig(&defaults::albert_keypair().ref_to(), sig) + .unwrap() + }) + }); +} + +criterion_group!(host_env, tx_signature_validation); +criterion_main!(host_env); diff --git a/benches/mod.rs b/benches/mod.rs new file mode 100644 index 0000000000..ec9655fad8 --- /dev/null +++ b/benches/mod.rs @@ -0,0 +1,616 @@ +//! Benchmarks module based on [`criterion`]. +//! +//! Measurements are taken on the elapsed wall-time. +//! +//! The benchmarks only focus on sucessfull transactions and vps: in case of failure, +//! the bench function shall panic to avoid timing incomplete execution paths. +//! +//! In addition, this module also contains benchmarks for [`WrapperTx`][`namada::core::types::transaction::wrapper::WrapperTx`] validation and +//! [`host_env`][`namada::vm::host_env`] exposed functions that define the gas constants of [`gas`][`namada::core::ledger::gas`]. +//! +//! For more realistic results these benchmarks should be run on all the combination of +//! supported OS/architecture. + +use borsh::BorshSerialize; +use masp_primitives::zip32::ExtendedFullViewingKey; +use namada::core::ledger::ibc::actions; +use namada::core::types::address::{self, Address}; +use namada::core::types::key::common::SecretKey; +use namada::core::types::storage::Key; +use namada::core::types::token::{Amount, Transfer}; +use namada::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; +use namada::ibc::clients::ics07_tendermint::client_state::AllowUpdate; +use namada::ibc::clients::ics07_tendermint::client_state::ClientState; +use namada::ibc::clients::ics07_tendermint::consensus_state::ConsensusState; +use namada::ibc::core::ics02_client::client_consensus::AnyConsensusState; +use namada::ibc::core::ics02_client::client_state::AnyClientState; +use namada::ibc::core::ics02_client::client_type::ClientType; +use namada::ibc::core::ics02_client::trust_threshold::TrustThreshold; +use namada::ibc::core::ics03_connection::connection::{ + ConnectionEnd, Counterparty, State as ConnectionState, +}; +use namada::ibc::core::ics03_connection::version::Version; +use namada::ibc::core::ics04_channel::channel::{ + ChannelEnd, Counterparty as ChannelCounterparty, Order, State, +}; +use namada::ibc::core::ics04_channel::Version as ChannelVersion; +use namada::ibc::core::ics23_commitment::commitment::CommitmentRoot; +use namada::ibc::core::ics23_commitment::specs::ProofSpecs; +use namada::ibc::core::ics24_host::identifier::ChainId as IbcChainId; +use namada::ibc::core::ics24_host::identifier::{ + ChannelId, ClientId, ConnectionId, PortId, +}; +use namada::ibc::core::ics24_host::path::{ChannelEndsPath, ConnectionsPath}; +use namada::ibc::core::ics24_host::Path as IbcPath; +use namada::ibc::signer::Signer; +use namada::ibc::timestamp::Timestamp as IbcTimestamp; +use namada::ibc::tx_msg::Msg; +use namada::ibc::Height as IbcHeight; +use namada::ibc_proto::cosmos::base::v1beta1::Coin; +use namada::ledger::gas::TxGasMeter; +use namada::ledger::queries::RPC; +use namada::ledger::queries::{ + Client, EncodedResponseQuery, RequestCtx, RequestQuery, Router, +}; +use namada::proof_of_stake; +use namada::proto::Tx; +use namada::tendermint::Hash; +use namada::tendermint_proto::Protobuf; +use namada::types::address::InternalAddress; +use namada::types::chain::ChainId; +use namada::types::masp::{ + ExtendedViewingKey, PaymentAddress, TransferSource, TransferTarget, +}; +use namada::types::storage::{BlockHeight, KeySeg, TxIndex}; +use namada::types::time::DateTimeUtc; +use namada::types::transaction::governance::InitProposalData; +use namada::types::transaction::governance::ProposalType; +use namada::types::transaction::pos::Bond; +use namada::types::transaction::GasLimit; +use namada::vm::wasm::run; +use namada_apps::cli::args::{Tx as TxArgs, TxTransfer}; +use namada_apps::cli::context::FromContext; +use namada_apps::cli::Context; +use namada_apps::client::tx::find_valid_diversifier; +use namada_apps::client::tx::gen_shielded_transfer; +use namada_apps::config::TendermintMode; +use namada_apps::facade::tendermint_config::net::Address as TendermintAddress; +use namada_apps::facade::tendermint_proto::abci::RequestInitChain; +use namada_apps::facade::tendermint_proto::google::protobuf::Timestamp; +use namada_apps::node::ledger::shell::Shell; +use namada_apps::wallet::defaults; +use namada_apps::{config, wasm_loader}; +use namada_test_utils::tx_data::TxWriteData; +use rand_core::OsRng; +use std::ops::{Deref, DerefMut}; +use tempfile::TempDir; + +pub const WASM_DIR: &str = "../wasm"; +pub const TX_BOND_WASM: &str = "tx_bond.wasm"; +pub const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; +pub const TX_UPDATE_VP_WASM: &str = "tx_update_vp.wasm"; +pub const TX_VOTE_PROPOSAL_WASM: &str = "tx_vote_proposal.wasm"; +pub const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; +pub const TX_INIT_PROPOSAL_WASM: &str = "tx_init_proposal.wasm"; +pub const TX_REVEAL_PK_WASM: &str = "tx_reveal_pk.wasm"; +pub const TX_CHANGE_VALIDATOR_COMMISSION_WASM: &str = + "tx_change_validator_commission.wasm"; +pub const TX_IBC_WASM: &str = "tx_ibc.wasm"; + +pub const ALBERT_PAYMENT_ADDRESS: &str = "albert_payment"; +pub const ALBERT_SPENDING_KEY: &str = "albert_spending"; +pub const BERTHA_PAYMENT_ADDRESS: &str = "bertha_payment"; +const BERTHA_SPENDING_KEY: &str = "bertha_spending"; + +pub struct BenchShell { + pub inner: Shell, + /// NOTE: Temporary directory should be dropped last since Shell need to flush data on drop + tempdir: TempDir, +} + +impl Deref for BenchShell { + type Target = Shell; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for BenchShell { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl BenchShell { + pub fn new() -> Self { + let (sender, _) = tokio::sync::mpsc::unbounded_channel(); + let tempdir = tempfile::tempdir().unwrap(); + let path = tempdir.path().canonicalize().unwrap(); + + let mut shell = Shell::new( + config::Ledger::new( + path, + Default::default(), + TendermintMode::Validator, + ), + WASM_DIR.into(), + sender, + None, + 50 * 1024 * 1024, // 50 kiB + 50 * 1024 * 1024, // 50 kiB + address::nam(), + ); + + shell + .init_chain( + RequestInitChain { + time: Some(Timestamp { + seconds: 0, + nanos: 0, + }), + chain_id: ChainId::default().to_string(), + ..Default::default() + }, + 1, + ) + .unwrap(); + + // Bond from Albert to validator + let bond = Bond { + validator: defaults::validator_address(), + amount: Amount::whole(1000), + source: Some(defaults::albert_address()), + }; + let signed_tx = generate_tx( + TX_BOND_WASM, + bond.clone(), + &defaults::albert_keypair(), + ); + + let mut bench_shell = BenchShell { + inner: shell, + tempdir, + }; + + bench_shell.execute_tx(&signed_tx); + bench_shell.wl_storage.commit_tx(); + + // Initialize governance proposal + let signed_tx = generate_tx( + TX_INIT_PROPOSAL_WASM, + InitProposalData { + id: None, + content: vec![], + author: defaults::albert_address(), + r#type: ProposalType::Default(None), + voting_start_epoch: 12.into(), + voting_end_epoch: 15.into(), + grace_epoch: 18.into(), + }, + &defaults::albert_keypair(), + ); + + bench_shell.execute_tx(&signed_tx); + bench_shell.wl_storage.commit_tx(); + bench_shell.commit(); + + for _ in 0..=12 { + bench_shell.advance_epoch(); + } + + bench_shell + } + + pub fn execute_tx(&mut self, tx: &Tx) { + run::tx( + &self.inner.wl_storage.storage, + &mut self.inner.wl_storage.write_log, + &mut TxGasMeter::new(u64::MAX), + &TxIndex(0), + &tx.code, + tx.data.as_ref().unwrap(), + &mut self.inner.vp_wasm_cache, + &mut self.inner.tx_wasm_cache, + ) + .unwrap(); + } + + pub fn advance_epoch(&mut self) { + let pipeline_len = + proof_of_stake::read_pos_params(&self.inner.wl_storage) + .unwrap() + .pipeline_len; + + self.wl_storage.storage.block.epoch = + self.wl_storage.storage.block.epoch.next(); + let current_epoch = self.wl_storage.storage.block.epoch; + + proof_of_stake::copy_validator_sets_and_positions( + &mut self.wl_storage, + current_epoch, + current_epoch + pipeline_len, + &proof_of_stake::consensus_validator_set_handle(), + &proof_of_stake::below_capacity_validator_set_handle(), + ) + .unwrap(); + } + + pub fn init_ibc_channel(&mut self) { + // Set connection open + let client_id = ClientId::new(ClientType::Tendermint, 1).unwrap(); + let connection = ConnectionEnd::new( + ConnectionState::Open, + client_id.clone(), + Counterparty::new( + client_id, + Some(ConnectionId::new(1)), + actions::commitment_prefix(), + ), + vec![Version::default()], + std::time::Duration::new(100, 0), + ); + + let addr_key = + Key::from(Address::Internal(InternalAddress::Ibc).to_db_key()); + + let path = IbcPath::Connections(ConnectionsPath(ConnectionId::new(1))); + let connection_key = + addr_key.join(&Key::parse(path.to_string()).unwrap()); + + self.wl_storage + .storage + .write(&connection_key, connection.encode_vec().unwrap()) + .unwrap(); + + // Set port + let path = Key::parse( + IbcPath::Ports(namada::ibc::core::ics24_host::path::PortsPath( + PortId::transfer(), + )) + .to_string(), + ) + .unwrap(); + let port_key = addr_key.join(&path); + + let index_key = addr_key + .join(&Key::from("capabilities/index".to_string().to_db_key())); + self.wl_storage + .storage + .write(&index_key, 1u64.to_be_bytes()) + .unwrap(); + self.wl_storage + .storage + .write(&port_key, 1u64.to_be_bytes()) + .unwrap(); + let cap_key = + addr_key.join(&Key::from("capabilities/1".to_string().to_db_key())); + self.wl_storage + .storage + .write(&cap_key, PortId::transfer().as_bytes()) + .unwrap(); + + //Set Channel open + let counterparty = ChannelCounterparty::new( + PortId::transfer(), + Some(ChannelId::new(5)), + ); + let channel = ChannelEnd::new( + State::Open, + Order::Unordered, + counterparty, + vec![ConnectionId::new(1)], + ChannelVersion::ics20(), + ); + let path = IbcPath::ChannelEnds(ChannelEndsPath( + PortId::transfer(), + ChannelId::new(5), + )); + let channel_key = addr_key.join(&Key::parse(path.to_string()).unwrap()); + self.wl_storage + .storage + .write(&channel_key, channel.encode_vec().unwrap()) + .unwrap(); + + // Set client state + let client_id = ClientId::new(ClientType::Tendermint, 1).unwrap(); + let client_state_key = addr_key.join(&Key::from( + IbcPath::ClientState( + namada::ibc::core::ics24_host::path::ClientStatePath( + client_id.clone(), + ), + ) + .to_string() + .to_db_key(), + )); + let client_state = ClientState::new( + IbcChainId::from(ChainId::default().to_string()), + TrustThreshold::ONE_THIRD, + std::time::Duration::new(1, 0), + std::time::Duration::new(2, 0), + std::time::Duration::new(1, 0), + IbcHeight::new(0, 1), + ProofSpecs::cosmos(), + vec![], + AllowUpdate { + after_expiry: true, + after_misbehaviour: true, + }, + ) + .unwrap(); + let bytes = AnyClientState::Tendermint(client_state) + .encode_vec() + .expect("encoding failed"); + self.wl_storage + .storage + .write(&client_state_key, bytes) + .expect("write failed"); + + // Set consensus state + let now: namada::tendermint::Time = + DateTimeUtc::now().try_into().unwrap(); + let consensus_key = addr_key.join(&Key::from( + IbcPath::ClientConsensusState( + namada::ibc::core::ics24_host::path::ClientConsensusStatePath { + client_id, + epoch: 0, + height: 1, + }, + ) + .to_string() + .to_db_key(), + )); + + let consensus_state = ConsensusState { + timestamp: now, + root: CommitmentRoot::from_bytes(&vec![]), + next_validators_hash: Hash::Sha256([0u8; 32]), + }; + + let bytes = AnyConsensusState::Tendermint(consensus_state) + .encode_vec() + .unwrap(); + self.wl_storage + .storage + .write(&consensus_key, bytes) + .unwrap(); + } +} + +pub fn generate_tx( + wasm_code_path: &str, + data: impl BorshSerialize, + signer: &SecretKey, +) -> Tx { + let tx = Tx::new( + wasm_loader::read_wasm_or_exit(WASM_DIR, wasm_code_path), + Some(data.try_to_vec().unwrap()), + ChainId::default(), + None, + ); + + tx.sign(signer) +} + +pub fn generate_foreign_key_tx(signer: &SecretKey) -> Tx { + let wasm_code = std::fs::read("../wasm_for_tests/tx_write.wasm").unwrap(); + + let tx = Tx::new( + wasm_code, + Some( + TxWriteData { + key: Key::from("bench_foreing_key".to_string().to_db_key()), + value: vec![0; 64], + } + .try_to_vec() + .unwrap(), + ), + ChainId::default(), + None, + ); + + tx.sign(signer) +} + +pub fn generate_ibc_transfer_tx() -> Tx { + let token = Some(Coin { + denom: address::nam().to_string(), + amount: Amount::whole(1000).to_string(), + }); + + let timeout_height = IbcHeight::new(0, 100); + + let now: namada::tendermint::Time = DateTimeUtc::now().try_into().unwrap(); + let now: IbcTimestamp = now.into(); + let timeout_timestamp = (now + std::time::Duration::new(3600, 0)).unwrap(); + + let msg = MsgTransfer { + source_port: PortId::transfer(), + source_channel: ChannelId::new(5), + token, + sender: Signer::new(defaults::albert_address()), + receiver: Signer::new(defaults::bertha_address()), + timeout_height, + timeout_timestamp, + }; + let any_msg = msg.to_any(); + let mut data = vec![]; + prost::Message::encode(&any_msg, &mut data).unwrap(); + + // Don't use execute_tx to avoid serializing the data again with borsh + Tx::new( + wasm_loader::read_wasm_or_exit(WASM_DIR, TX_IBC_WASM), + Some(data), + ChainId::default(), + None, + ) + .sign(&defaults::albert_keypair()) +} + +pub struct BenchShieldedCtx { + pub ctx: Context, + pub shell: BenchShell, +} + +#[async_trait::async_trait(?Send)] +impl Client for BenchShell { + type Error = std::io::Error; + + async fn request( + &self, + path: String, + data: Option>, + height: Option, + prove: bool, + ) -> Result { + let data = data.unwrap_or_default(); + let height = height.unwrap_or_default(); + + let request = RequestQuery { + data, + path, + height, + prove, + }; + + let ctx = RequestCtx { + wl_storage: &self.wl_storage, + event_log: self.event_log(), + vp_wasm_cache: self.vp_wasm_cache.read_only(), + tx_wasm_cache: self.tx_wasm_cache.read_only(), + storage_read_past_height_limit: None, + }; + + RPC.handle(ctx, &request) + .map_err(|_| std::io::Error::from(std::io::ErrorKind::NotFound)) + } +} + +impl BenchShieldedCtx { + pub fn new() -> Self { + let mut shell = BenchShell::new(); + + let mut ctx = Context::new(namada_apps::cli::args::Global { + chain_id: None, + base_dir: shell.tempdir.as_ref().canonicalize().unwrap(), + wasm_dir: None, + mode: None, + }) + .unwrap(); + + // Generate spending key for Albert and Bertha + ctx.wallet + .gen_spending_key(ALBERT_SPENDING_KEY.to_string(), true); + ctx.wallet + .gen_spending_key(BERTHA_SPENDING_KEY.to_string(), true); + ctx.wallet.save().unwrap(); + + // Generate payment addresses for both Albert and Bertha + for (alias, viewing_alias) in [ + (ALBERT_PAYMENT_ADDRESS, ALBERT_SPENDING_KEY), + (BERTHA_PAYMENT_ADDRESS, BERTHA_SPENDING_KEY), + ] + .map(|(p, s)| (p.to_owned(), s.to_owned())) + { + let viewing_key: FromContext = FromContext::new( + ctx.wallet + .find_viewing_key(viewing_alias) + .unwrap() + .to_string(), + ); + let viewing_key = + ExtendedFullViewingKey::from(ctx.get_cached(&viewing_key)) + .fvk + .vk; + let (div, _g_d) = find_valid_diversifier(&mut OsRng); + let payment_addr = viewing_key.to_payment_address(div).unwrap(); + let _ = ctx + .wallet + .insert_payment_addr( + alias, + PaymentAddress::from(payment_addr).pinned(false), + ) + .unwrap(); + } + + ctx.wallet.save().unwrap(); + namada::ledger::storage::update_allowed_conversions( + &mut shell.wl_storage, + ) + .unwrap(); + + Self { ctx, shell } + } + + pub fn generate_masp_tx( + &mut self, + amount: Amount, + source: TransferSource, + target: TransferTarget, + ) -> Tx { + let mock_args = TxArgs { + dry_run: false, + dump_tx: false, + force: false, + broadcast_only: false, + ledger_address: TendermintAddress::Tcp { + peer_id: None, + host: "bench-host".to_string(), + port: 1, + }, + initialized_account_alias: None, + fee_amount: Amount::whole(0), + fee_token: FromContext::new(address::nam().to_string()), + gas_limit: GasLimit::from(u64::MAX), + expiration: None, + signing_key: Some(FromContext::new( + defaults::albert_keypair().to_string(), + )), + signer: None, + }; + + let args = TxTransfer { + tx: mock_args, + source: FromContext::new(source.to_string()), + target: FromContext::new(target.to_string()), + token: FromContext::new(address::nam().to_string()), + sub_prefix: None, + amount, + }; + + let async_runtime = tokio::runtime::Runtime::new().unwrap(); + let spending_key = self + .ctx + .wallet + .find_spending_key(ALBERT_SPENDING_KEY) + .unwrap(); + async_runtime.block_on(self.ctx.shielded.fetch( + &self.shell, + &[spending_key.into()], + &[], + )); + let shielded = async_runtime + .block_on(gen_shielded_transfer( + &mut self.ctx, + &self.shell, + &args, + false, + )) + .unwrap() + .map(|x| x.0); + + generate_tx( + TX_TRANSFER_WASM, + Transfer { + source: source.effective_address(), + target: target.effective_address(), + token: address::nam(), + sub_prefix: None, + amount, + key: None, + shielded, + }, + &defaults::albert_keypair(), + ) + } +} diff --git a/benches/native_vps.rs b/benches/native_vps.rs new file mode 100644 index 0000000000..d44a1edcea --- /dev/null +++ b/benches/native_vps.rs @@ -0,0 +1,447 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use namada::core::ledger::ibc::actions; +use namada::core::types::address::{self, Address}; +use namada::ibc::core::ics02_client::client_type::ClientType; +use namada::ibc::core::ics03_connection::connection::Counterparty; +use namada::ibc::core::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; +use namada::ibc::core::ics03_connection::version::Version; +use namada::ibc::core::ics04_channel::channel::{ + ChannelEnd, Counterparty as ChannelCounterparty, Order, State, +}; +use namada::ibc::core::ics04_channel::msgs::chan_open_init::MsgChannelOpenInit; +use namada::ibc::core::ics04_channel::Version as ChannelVersion; +use namada::ibc::core::ics24_host::identifier::{ + ChannelId, ClientId, ConnectionId, PortId, +}; +use namada::ibc::signer::Signer; +use namada::ibc::tx_msg::Msg; +use namada::ledger::gas::VpGasMeter; +use namada::ledger::governance; +use namada::ledger::ibc::vp::{Ibc, IbcToken}; +use namada::ledger::native_vp::replay_protection::ReplayProtectionVp; +use namada::ledger::native_vp::slash_fund::SlashFundVp; +use namada::ledger::native_vp::{Ctx, NativeVp}; +use namada::proto::Tx; +use namada::types::address::InternalAddress; +use namada::types::chain::ChainId; +use namada::types::governance::{ProposalVote, VoteType}; +use namada::types::storage::TxIndex; +use namada::types::transaction::governance::ProposalType; +use namada_apps::wasm_loader; +use namada_benches::{ + generate_foreign_key_tx, generate_ibc_transfer_tx, generate_tx, BenchShell, + TX_IBC_WASM, TX_INIT_PROPOSAL_WASM, TX_VOTE_PROPOSAL_WASM, WASM_DIR, +}; +use std::collections::BTreeSet; + +use namada::types::transaction::governance::{ + InitProposalData, VoteProposalData, +}; +use namada_apps::wallet::defaults; + +fn replay_protection(c: &mut Criterion) { + // Write a random key under the replay protection subspace + let tx = generate_foreign_key_tx(&defaults::albert_keypair()); + let mut shell = BenchShell::new(); + + shell.execute_tx(&tx); + let (verifiers, keys_changed) = shell + .wl_storage + .write_log + .verifiers_and_changed_keys(&BTreeSet::default()); + + let replay_protection = ReplayProtectionVp { + ctx: Ctx::new( + &Address::Internal(InternalAddress::ReplayProtection), + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + &tx, + &TxIndex(0), + VpGasMeter::new(u64::MAX, 0), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + ), + }; + + c.bench_function("vp_replay_protection", |b| { + b.iter(|| { + // NOTE: thiv VP will always fail when triggered so don't assert here + replay_protection + .validate_tx( + tx.data.as_ref().unwrap(), + replay_protection.ctx.keys_changed, + replay_protection.ctx.verifiers, + ) + .unwrap() + }) + }); +} + +fn governance(c: &mut Criterion) { + let mut group = c.benchmark_group("vp_governance"); + + for bench_name in [ + "foreign_key_write", + "delegator_vote", + "validator_vote", + "minimal_proposal", + "complete_proposal", + ] { + let mut shell = BenchShell::new(); + + let signed_tx = match bench_name { + "foreign_key_write" => { + generate_foreign_key_tx(&defaults::albert_keypair()) + } + "delegator_vote" => generate_tx( + TX_VOTE_PROPOSAL_WASM, + VoteProposalData { + id: 0, + vote: ProposalVote::Yay(VoteType::Default), + voter: defaults::albert_address(), + delegations: vec![defaults::validator_address()], + }, + &defaults::albert_keypair(), + ), + "validator_vote" => generate_tx( + TX_VOTE_PROPOSAL_WASM, + VoteProposalData { + id: 0, + vote: namada::types::governance::ProposalVote::Nay, + voter: defaults::validator_address(), + delegations: vec![], + }, + &defaults::validator_keypair(), + ), + "minimal_proposal" => generate_tx( + TX_INIT_PROPOSAL_WASM, + InitProposalData { + id: None, + content: vec![], + author: defaults::albert_address(), + r#type: ProposalType::Default(None), + voting_start_epoch: 12.into(), + voting_end_epoch: 15.into(), + grace_epoch: 18.into(), + }, + &defaults::albert_keypair(), + ), + "complete_proposal" => { + let max_code_size_key = + governance::storage::get_max_proposal_code_size_key(); + let max_proposal_content_key = + governance::storage::get_max_proposal_content_key(); + let max_code_size = + shell.read_storage_key(&max_code_size_key).unwrap(); + let max_proposal_content_size = + shell.read_storage_key(&max_proposal_content_key).unwrap(); + + generate_tx( + TX_INIT_PROPOSAL_WASM, + InitProposalData { + id: Some(1), + content: vec![0; max_proposal_content_size], + author: defaults::albert_address(), + r#type: ProposalType::Default(Some(vec![ + 0; + max_code_size + ])), + voting_start_epoch: 12.into(), + voting_end_epoch: 15.into(), + grace_epoch: 18.into(), + }, + &defaults::albert_keypair(), + ) + } + _ => panic!("Unexpected bench test"), + }; + + // Run the tx to validate + shell.execute_tx(&signed_tx); + + let (verifiers, keys_changed) = shell + .wl_storage + .write_log + .verifiers_and_changed_keys(&BTreeSet::default()); + + let governance = SlashFundVp { + ctx: Ctx::new( + &Address::Internal(InternalAddress::Governance), + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + &signed_tx, + &TxIndex(0), + VpGasMeter::new(u64::MAX, 0), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + ), + }; + + group.bench_function(bench_name, |b| { + b.iter(|| { + assert!(governance + .validate_tx( + signed_tx.data.as_ref().unwrap(), + governance.ctx.keys_changed, + governance.ctx.verifiers, + ) + .unwrap()) + }) + }); + } + + group.finish(); +} + +fn slash_fund(c: &mut Criterion) { + let mut group = c.benchmark_group("vp_slash_fund"); + + // Write a random key under a foreign subspace + let foreign_key_write = + generate_foreign_key_tx(&defaults::albert_keypair()); + + let governance_proposal = generate_tx( + TX_INIT_PROPOSAL_WASM, + InitProposalData { + id: None, + content: vec![], + author: defaults::albert_address(), + r#type: ProposalType::Default(None), + voting_start_epoch: 12.into(), + voting_end_epoch: 15.into(), + grace_epoch: 18.into(), + }, + &defaults::albert_keypair(), + ); + + for (tx, bench_name) in [foreign_key_write, governance_proposal] + .into_iter() + .zip(["foreign_key_write", "governance_proposal"]) + { + let mut shell = BenchShell::new(); + + // Run the tx to validate + shell.execute_tx(&tx); + + let (verifiers, keys_changed) = shell + .wl_storage + .write_log + .verifiers_and_changed_keys(&BTreeSet::default()); + + let slash_fund = SlashFundVp { + ctx: Ctx::new( + &Address::Internal(InternalAddress::SlashFund), + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + &tx, + &TxIndex(0), + VpGasMeter::new(u64::MAX, 0), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + ), + }; + + group.bench_function(bench_name, |b| { + b.iter(|| { + assert!(slash_fund + .validate_tx( + tx.data.as_ref().unwrap(), + slash_fund.ctx.keys_changed, + slash_fund.ctx.verifiers, + ) + .unwrap()) + }) + }); + } + + group.finish(); +} + +fn ibc(c: &mut Criterion) { + let mut group = c.benchmark_group("vp_ibc"); + + let foreign_key_write = + generate_foreign_key_tx(&defaults::albert_keypair()); + + // Connection handshake + let msg = MsgConnectionOpenInit { + client_id: ClientId::new(ClientType::Tendermint, 1).unwrap(), + counterparty: Counterparty::new( + ClientId::new(ClientType::Tendermint, 1).unwrap(), + Some(ConnectionId::new(1)), + actions::commitment_prefix(), + ), + version: Some(Version::default()), + delay_period: std::time::Duration::new(100, 0), + signer: Signer::new(defaults::albert_address()), + }; + let any_msg = msg.to_any(); + let mut data = vec![]; + prost::Message::encode(&any_msg, &mut data) + .expect("Encoding tx data shouldn't fail"); + + // Avoid serializing the data again with borsh + let open_connection = Tx::new( + wasm_loader::read_wasm_or_exit(WASM_DIR, TX_IBC_WASM), + Some(data), + ChainId::default(), + None, + ) + .sign(&defaults::albert_keypair()); + + // Channel handshake + let counterparty = + ChannelCounterparty::new(PortId::transfer(), Some(ChannelId::new(5))); + let channel = ChannelEnd::new( + State::Init, + Order::Unordered, + counterparty, + vec![ConnectionId::new(1)], + ChannelVersion::ics20(), + ); + let msg = MsgChannelOpenInit { + port_id: PortId::transfer(), + channel: channel.clone(), + signer: Signer::new(defaults::albert_address()), + }; + + let any_msg = msg.to_any(); + let mut data = vec![]; + prost::Message::encode(&any_msg, &mut data) + .expect("Encoding tx data shouldn't fail"); + + // Avoid serializing the data again with borsh + let open_channel = Tx::new( + wasm_loader::read_wasm_or_exit(WASM_DIR, TX_IBC_WASM), + Some(data), + ChainId::default(), + None, + ) + .sign(&defaults::albert_keypair()); + + // Ibc transfer + let outgoing_transfer = generate_ibc_transfer_tx(); + + for (signed_tx, bench_name) in [ + foreign_key_write, + open_connection, + open_channel, + outgoing_transfer, + ] + .iter() + .zip([ + "foreign_key_write", + "open_connection", + "open_channel", + "outgoing_transfer", + ]) { + let mut shell = BenchShell::new(); + shell.init_ibc_channel(); + + shell.execute_tx(signed_tx); + let (verifiers, keys_changed) = shell + .wl_storage + .write_log + .verifiers_and_changed_keys(&BTreeSet::default()); + + let ibc = Ibc { + ctx: Ctx::new( + &Address::Internal(InternalAddress::Ibc), + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + signed_tx, + &TxIndex(0), + VpGasMeter::new(u64::MAX, 0), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + ), + }; + + group.bench_function(bench_name, |b| { + b.iter(|| { + assert!(ibc + .validate_tx( + signed_tx.data.as_ref().unwrap(), + ibc.ctx.keys_changed, + ibc.ctx.verifiers, + ) + .unwrap()) + }) + }); + } + + group.finish(); +} + +fn ibc_token(c: &mut Criterion) { + let mut group = c.benchmark_group("vp_ibc_token"); + + let foreign_key_write = + generate_foreign_key_tx(&defaults::albert_keypair()); + let outgoing_transfer = generate_ibc_transfer_tx(); + + for (signed_tx, bench_name) in [foreign_key_write, outgoing_transfer] + .iter() + .zip(["foreign_key_write", "outgoing_transfer"]) + { + let mut shell = BenchShell::new(); + shell.init_ibc_channel(); + + shell.execute_tx(&signed_tx); + + let (verifiers, keys_changed) = shell + .wl_storage + .write_log + .verifiers_and_changed_keys(&BTreeSet::default()); + + let ibc_token_address = + namada::core::types::address::InternalAddress::ibc_token_address( + PortId::transfer().to_string(), + ChannelId::new(5).to_string(), + &address::nam(), + ); + let internal_address = Address::Internal(ibc_token_address); + + let ibc = IbcToken { + ctx: Ctx::new( + &internal_address, + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + &signed_tx, + &TxIndex(0), + VpGasMeter::new(u64::MAX, 0), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + ), + }; + + group.bench_function(bench_name, |b| { + b.iter(|| { + assert!(ibc + .validate_tx( + signed_tx.data.as_ref().unwrap(), + ibc.ctx.keys_changed, + ibc.ctx.verifiers, + ) + .unwrap()) + }) + }); + } + + group.finish(); +} + +criterion_group!( + native_vps, + replay_protection, + governance, + slash_fund, + ibc, + ibc_token +); +criterion_main!(native_vps); diff --git a/benches/process_wrapper.rs b/benches/process_wrapper.rs new file mode 100644 index 0000000000..29482810a1 --- /dev/null +++ b/benches/process_wrapper.rs @@ -0,0 +1,90 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use namada::core::types::address; +use namada::core::types::token::{Amount, Transfer}; +use namada::ledger::gas::BlockGasMeter; +use namada::ledger::storage::TempWlStorage; +use namada::types::chain::ChainId; +use namada::types::time::DateTimeUtc; +use std::collections::BTreeMap; + +use namada::types::transaction::{Fee, WrapperTx}; +use namada_apps::node::ledger::shell::process_proposal::ValidationMeta; +use namada_apps::wallet::defaults; +use namada_benches::{generate_tx, BenchShell, TX_TRANSFER_WASM}; + +fn process_tx(c: &mut Criterion) { + let shell = BenchShell::new(); + let tx = generate_tx( + TX_TRANSFER_WASM, + Transfer { + source: defaults::albert_address(), + target: defaults::bertha_address(), + token: address::nam(), + sub_prefix: None, + amount: Amount::whole(1000), + key: None, + shielded: None, + }, + &defaults::albert_keypair(), + ); + + let wrapper = WrapperTx::new( + Fee { + token: address::nam(), + amount: Amount::whole(200), + }, + &defaults::albert_keypair(), + 0.into(), + 1000.into(), + tx, + Default::default(), + None, + ) + .sign(&defaults::albert_keypair(), ChainId::default(), None) + .unwrap() + .to_bytes(); + + let datetime = DateTimeUtc::now(); + let gas_table = BTreeMap::default(); + + c.bench_function("wrapper_tx_validation", |b| { + b.iter_batched( + || { + ( + shell.wl_storage.storage.tx_queue.clone(), + // Prevent block out of gas and replay protection + TempWlStorage::new(&shell.wl_storage.storage), + BlockGasMeter::new(u64::MAX), + ValidationMeta::default(), + ) + }, + |( + tx_queue, + mut temp_wl_storage, + mut block_gas_meter, + mut validation_meta, + )| { + assert_eq!( + // Assert that the wrapper transaction was valid + shell + .process_single_tx( + &wrapper, + &mut tx_queue.iter(), + &mut validation_meta, + &mut temp_wl_storage, + &mut block_gas_meter, + datetime, + &gas_table, + &mut 0, + ) + .code, + 0 + ) + }, + criterion::BatchSize::LargeInput, + ) + }); +} + +criterion_group!(process_wrapper, process_tx); +criterion_main!(process_wrapper); diff --git a/benches/txs.rs b/benches/txs.rs new file mode 100644 index 0000000000..9e59af3e4a --- /dev/null +++ b/benches/txs.rs @@ -0,0 +1,542 @@ +use borsh::BorshSerialize; +use criterion::{criterion_group, criterion_main, Criterion}; +use namada::core::types::key::{ + common, SecretKey as SecretKeyInterface, SigScheme, +}; +use namada::core::types::token::Amount; +use namada::ledger::governance; +use namada::proof_of_stake; +use namada::proto::Tx; +use namada::types::chain::ChainId; +use namada::types::governance::{ProposalVote, VoteType}; +use namada::types::key::{ed25519, secp256k1}; +use namada::types::masp::{TransferSource, TransferTarget}; +use namada::types::transaction::governance::{ + InitProposalData, ProposalType, VoteProposalData, +}; +use namada::types::transaction::pos::{Bond, CommissionChange, Withdraw}; +use namada::types::transaction::EllipticCurve; +use namada::types::transaction::{InitAccount, InitValidator, UpdateVp}; +use namada_apps::wallet::defaults; +use namada_apps::wasm_loader; +use namada_benches::{ + generate_ibc_transfer_tx, generate_tx, BenchShell, BenchShieldedCtx, + ALBERT_PAYMENT_ADDRESS, ALBERT_SPENDING_KEY, BERTHA_PAYMENT_ADDRESS, + TX_BOND_WASM, TX_CHANGE_VALIDATOR_COMMISSION_WASM, TX_INIT_PROPOSAL_WASM, + TX_REVEAL_PK_WASM, TX_UNBOND_WASM, TX_UPDATE_VP_WASM, + TX_VOTE_PROPOSAL_WASM, WASM_DIR, +}; +use rand::rngs::StdRng; +use rand::SeedableRng; +use rust_decimal::Decimal; + +const TX_WITHDRAW_WASM: &str = "tx_withdraw.wasm"; +const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; +const TX_INIT_VALIDATOR_WASM: &str = "tx_init_validator.wasm"; + +fn transfer(c: &mut Criterion) { + let mut group = c.benchmark_group("transfer"); + let amount = Amount::whole(500); + + for bench_name in ["transparent", "shielding", "unshielding", "shielded"] { + group.bench_function(bench_name, |b| { + b.iter_batched_ref( + || { + let mut shielded_ctx = BenchShieldedCtx::new(); + + let albert_spending_key = shielded_ctx + .ctx + .wallet + .find_spending_key(ALBERT_SPENDING_KEY) + .unwrap() + .to_owned(); + let albert_payment_addr = shielded_ctx + .ctx + .wallet + .find_payment_addr(ALBERT_PAYMENT_ADDRESS) + .unwrap() + .to_owned(); + let bertha_payment_addr = shielded_ctx + .ctx + .wallet + .find_payment_addr(BERTHA_PAYMENT_ADDRESS) + .unwrap() + .to_owned(); + + // Shield some tokens for Albert + let shield_tx = shielded_ctx.generate_masp_tx( + amount, + TransferSource::Address(defaults::albert_address()), + TransferTarget::PaymentAddress(albert_payment_addr), + ); + shielded_ctx.shell.execute_tx(&shield_tx); + shielded_ctx.shell.wl_storage.commit_tx(); + shielded_ctx.shell.commit(); + + let signed_tx = match bench_name { + "transparent" => shielded_ctx.generate_masp_tx( + amount, + TransferSource::Address(defaults::albert_address()), + TransferTarget::Address(defaults::bertha_address()), + ), + "shielding" => shielded_ctx.generate_masp_tx( + amount, + TransferSource::Address(defaults::albert_address()), + TransferTarget::PaymentAddress(albert_payment_addr), + ), + "unshielding" => shielded_ctx.generate_masp_tx( + amount, + TransferSource::ExtendedSpendingKey( + albert_spending_key, + ), + TransferTarget::Address(defaults::albert_address()), + ), + "shielded" => shielded_ctx.generate_masp_tx( + amount, + TransferSource::ExtendedSpendingKey( + albert_spending_key, + ), + TransferTarget::PaymentAddress(bertha_payment_addr), + ), + _ => panic!("Unexpected bench test"), + }; + + (shielded_ctx, signed_tx) + }, + |(shielded_ctx, signed_tx)| { + shielded_ctx.shell.execute_tx(&signed_tx); + }, + criterion::BatchSize::LargeInput, + ) + }); + } + + group.finish(); +} + +fn bond(c: &mut Criterion) { + let mut group = c.benchmark_group("bond"); + + let bond = generate_tx( + TX_BOND_WASM, + Bond { + validator: defaults::validator_address(), + amount: Amount::whole(1000), + source: Some(defaults::albert_address()), + }, + &defaults::albert_keypair(), + ); + + let self_bond = generate_tx( + TX_BOND_WASM, + Bond { + validator: defaults::validator_address(), + amount: Amount::whole(1000), + source: None, + }, + &defaults::validator_keypair(), + ); + + for (signed_tx, bench_name) in + [bond, self_bond].iter().zip(["bond", "self_bond"]) + { + group.bench_function(bench_name, |b| { + b.iter_batched_ref( + BenchShell::new, + |shell| shell.execute_tx(signed_tx), + criterion::BatchSize::LargeInput, + ) + }); + } + + group.finish(); +} + +fn unbond(c: &mut Criterion) { + let mut group = c.benchmark_group("unbond"); + + let unbond = generate_tx( + TX_UNBOND_WASM, + Bond { + validator: defaults::validator_address(), + amount: Amount::whole(1000), + source: Some(defaults::albert_address()), + }, + &defaults::albert_keypair(), + ); + + let self_unbond = generate_tx( + TX_UNBOND_WASM, + Bond { + validator: defaults::validator_address(), + amount: Amount::whole(1000), + source: None, + }, + &defaults::validator_keypair(), + ); + + for (signed_tx, bench_name) in + [unbond, self_unbond].iter().zip(["unbond", "self_unbond"]) + { + group.bench_function(bench_name, |b| { + b.iter_batched_ref( + BenchShell::new, + |shell| shell.execute_tx(signed_tx), + criterion::BatchSize::LargeInput, + ) + }); + } + + group.finish(); +} + +fn withdraw(c: &mut Criterion) { + let mut group = c.benchmark_group("withdraw"); + + let withdraw = generate_tx( + TX_WITHDRAW_WASM, + Withdraw { + validator: defaults::validator_address(), + source: Some(defaults::albert_address()), + }, + &defaults::albert_keypair(), + ); + + let self_withdraw = generate_tx( + TX_WITHDRAW_WASM, + Withdraw { + validator: defaults::validator_address(), + source: None, + }, + &defaults::validator_keypair(), + ); + + for (signed_tx, bench_name) in [withdraw, self_withdraw] + .iter() + .zip(["withdraw", "self_withdraw"]) + { + group.bench_function(bench_name, |b| { + b.iter_batched_ref( + || { + let mut shell = BenchShell::new(); + + // Unbond funds + let unbond_tx = match bench_name { + "withdraw" => generate_tx( + TX_UNBOND_WASM, + Bond { + validator: defaults::validator_address(), + amount: Amount::whole(1000), + source: Some(defaults::albert_address()), + }, + &defaults::albert_keypair(), + ), + "self_withdraw" => generate_tx( + TX_UNBOND_WASM, + Bond { + validator: defaults::validator_address(), + amount: Amount::whole(1000), + source: None, + }, + &defaults::validator_keypair(), + ), + _ => panic!("Unexpected bench test"), + }; + + shell.execute_tx(&unbond_tx); + + // Advance Epoch for pipeline and unbonding length + let params = + proof_of_stake::read_pos_params(&shell.wl_storage) + .unwrap(); + let advance_epochs = + params.pipeline_len + params.unbonding_len; + + for _ in 0..=advance_epochs { + shell.advance_epoch(); + } + + shell + }, + |shell| shell.execute_tx(signed_tx), + criterion::BatchSize::LargeInput, + ) + }); + } + + group.finish(); +} + +fn reveal_pk(c: &mut Criterion) { + let mut csprng = rand::rngs::OsRng {}; + let new_implicit_account: common::SecretKey = + ed25519::SigScheme::generate(&mut csprng) + .try_to_sk() + .unwrap(); + + let tx = Tx::new( + wasm_loader::read_wasm_or_exit(WASM_DIR, TX_REVEAL_PK_WASM), + Some(new_implicit_account.to_public().try_to_vec().unwrap()), + ChainId("bench".to_string()), + None, + ); + + c.bench_function("reveal_pk", |b| { + b.iter_batched_ref( + BenchShell::new, + |shell| shell.execute_tx(&tx), + criterion::BatchSize::LargeInput, + ) + }); +} + +fn update_vp(c: &mut Criterion) { + let signed_tx = generate_tx( + TX_UPDATE_VP_WASM, + UpdateVp { + addr: defaults::albert_address(), + vp_code: wasm_loader::read_wasm_or_exit( + WASM_DIR, + "vp_validator.wasm", + ), + }, + &defaults::albert_keypair(), + ); + + c.bench_function("update_vp", |b| { + b.iter_batched_ref( + BenchShell::new, + |shell| shell.execute_tx(&signed_tx), + criterion::BatchSize::LargeInput, + ) + }); +} + +fn init_account(c: &mut Criterion) { + let mut csprng = rand::rngs::OsRng {}; + let new_account: common::SecretKey = + ed25519::SigScheme::generate(&mut csprng) + .try_to_sk() + .unwrap(); + + let signed_tx = generate_tx( + TX_INIT_ACCOUNT_WASM, + InitAccount { + public_key: new_account.to_public(), + vp_code: wasm_loader::read_wasm_or_exit(WASM_DIR, "vp_user.wasm"), + }, + &new_account, + ); + + c.bench_function("init_account", |b| { + b.iter_batched_ref( + BenchShell::new, + |shell| shell.execute_tx(&signed_tx), + criterion::BatchSize::LargeInput, + ) + }); +} + +fn init_proposal(c: &mut Criterion) { + let mut group = c.benchmark_group("init_proposal"); + + for bench_name in ["minimal_proposal", "complete_proposal"] { + group.bench_function(bench_name, |b| { + b.iter_batched_ref( + || { + let shell = BenchShell::new(); + + let signed_tx = match bench_name { + "minimal_proposal" => generate_tx( + TX_INIT_PROPOSAL_WASM, + InitProposalData { + id: None, + content: vec![], + author: defaults::albert_address(), + r#type: ProposalType::Default(None), + voting_start_epoch: 12.into(), + voting_end_epoch: 15.into(), + grace_epoch: 18.into(), + }, + &defaults::albert_keypair(), + ), + "complete_proposal" => { + let max_code_size_key = + governance::storage::get_max_proposal_code_size_key(); + let max_proposal_content_key = + governance::storage::get_max_proposal_content_key(); + let max_code_size = shell + .read_storage_key(&max_code_size_key) + .unwrap(); + let max_proposal_content_size = shell + .read_storage_key(&max_proposal_content_key) + .unwrap(); + + generate_tx( + TX_INIT_PROPOSAL_WASM, + InitProposalData { + id: Some(1), + content: vec![0; max_proposal_content_size], + author: defaults::albert_address(), + r#type: ProposalType::Default(Some( + vec![0; max_code_size], + )), + voting_start_epoch: 12.into(), + voting_end_epoch: 15.into(), + grace_epoch: 18.into(), + }, + &defaults::albert_keypair(), + ) + } + _ => panic!("unexpected bench test"), + }; + + (shell, signed_tx) + }, + |(shell, signed_tx)| shell.execute_tx(signed_tx), + criterion::BatchSize::LargeInput, + ) + }); + } + + group.finish(); +} + +fn vote_proposal(c: &mut Criterion) { + let mut group = c.benchmark_group("vote_proposal"); + let delegator_vote = generate_tx( + TX_VOTE_PROPOSAL_WASM, + VoteProposalData { + id: 0, + vote: ProposalVote::Yay(VoteType::Default), + voter: defaults::albert_address(), + delegations: vec![defaults::validator_address()], + }, + &defaults::albert_keypair(), + ); + + let validator_vote = generate_tx( + TX_VOTE_PROPOSAL_WASM, + VoteProposalData { + id: 0, + vote: ProposalVote::Nay, + voter: defaults::validator_address(), + delegations: vec![], + }, + &defaults::validator_keypair(), + ); + + for (signed_tx, bench_name) in [delegator_vote, validator_vote] + .iter() + .zip(["delegator_vote", "validator_vote"]) + { + group.bench_function(bench_name, |b| { + b.iter_batched_ref( + BenchShell::new, + |shell| shell.execute_tx(signed_tx), + criterion::BatchSize::LargeInput, + ) + }); + } + + group.finish(); +} + +fn init_validator(c: &mut Criterion) { + let mut csprng = rand::rngs::OsRng {}; + let consensus_key: common::PublicKey = + secp256k1::SigScheme::generate(&mut csprng) + .try_to_sk::() + .unwrap() + .to_public(); + + let protocol_key: common::PublicKey = + secp256k1::SigScheme::generate(&mut csprng) + .try_to_sk::() + .unwrap() + .to_public(); + + let dkg_key = ferveo_common::Keypair::::new( + &mut StdRng::from_entropy(), + ) + .public() + .into(); + + let signed_tx = generate_tx( + TX_INIT_VALIDATOR_WASM, + InitValidator { + account_key: defaults::albert_keypair().to_public(), + consensus_key, + protocol_key, + dkg_key, + commission_rate: Decimal::default(), + max_commission_rate_change: Decimal::default(), + validator_vp_code: wasm_loader::read_wasm_or_exit( + WASM_DIR, + "vp_validator.wasm", + ), + }, + &defaults::albert_keypair(), + ); + + c.bench_function("init_validator", |b| { + b.iter_batched_ref( + BenchShell::new, + |shell| shell.execute_tx(&signed_tx), + criterion::BatchSize::LargeInput, + ) + }); +} + +fn change_validator_commission(c: &mut Criterion) { + let signed_tx = generate_tx( + TX_CHANGE_VALIDATOR_COMMISSION_WASM, + CommissionChange { + validator: defaults::validator_address(), + new_rate: Decimal::new(6, 2), + }, + &defaults::validator_keypair(), + ); + + c.bench_function("change_validator_commission", |b| { + b.iter_batched_ref( + BenchShell::new, + |shell| shell.execute_tx(&signed_tx), + criterion::BatchSize::LargeInput, + ) + }); +} + +fn ibc(c: &mut Criterion) { + let signed_tx = generate_ibc_transfer_tx(); + + c.bench_function("ibc_transfer", |b| { + b.iter_batched_ref( + || { + let mut shell = BenchShell::new(); + shell.init_ibc_channel(); + + shell + }, + |shell| shell.execute_tx(&signed_tx), + criterion::BatchSize::LargeInput, + ) + }); +} + +criterion_group!( + whitelisted_txs, + transfer, + bond, + unbond, + withdraw, + reveal_pk, + update_vp, + init_account, + init_proposal, + vote_proposal, + init_validator, + change_validator_commission, + ibc +); +criterion_main!(whitelisted_txs); diff --git a/benches/vps.rs b/benches/vps.rs new file mode 100644 index 0000000000..e1df633948 --- /dev/null +++ b/benches/vps.rs @@ -0,0 +1,559 @@ +use borsh::BorshSerialize; +use criterion::{criterion_group, criterion_main, Criterion}; +use namada::core::types::address::{self, Address}; +use namada::core::types::key::{ + common, SecretKey as SecretKeyInterface, SigScheme, +}; +use namada::core::types::token::{Amount, Transfer}; +use namada::ledger::gas::VpGasMeter; +use namada::proto::Tx; +use namada::types::chain::ChainId; +use namada::types::governance::{ProposalVote, VoteType}; +use namada::types::key::ed25519; +use namada::types::masp::{TransferSource, TransferTarget}; +use namada::types::storage::TxIndex; +use namada::vm::wasm::run; +use namada_apps::wasm_loader; +use namada_benches::{ + generate_foreign_key_tx, generate_tx, BenchShell, BenchShieldedCtx, + ALBERT_PAYMENT_ADDRESS, ALBERT_SPENDING_KEY, BERTHA_PAYMENT_ADDRESS, + TX_BOND_WASM, TX_CHANGE_VALIDATOR_COMMISSION_WASM, TX_REVEAL_PK_WASM, + TX_TRANSFER_WASM, TX_UNBOND_WASM, TX_UPDATE_VP_WASM, TX_VOTE_PROPOSAL_WASM, + WASM_DIR, +}; +use rust_decimal::Decimal; +use std::collections::BTreeSet; + +use namada::types::transaction::governance::VoteProposalData; +use namada::types::transaction::pos::{Bond, CommissionChange}; +use namada::types::transaction::UpdateVp; +use namada_apps::wallet::defaults; + +const VP_USER_WASM: &str = "vp_user.wasm"; +const VP_TOKEN_WASM: &str = "vp_token.wasm"; +const VP_IMPLICIT_WASM: &str = "vp_implicit.wasm"; +const VP_VALIDATOR_WASM: &str = "vp_validator.wasm"; +const VP_MASP_WASM: &str = "vp_masp.wasm"; + +fn vp_user(c: &mut Criterion) { + let vp_code = wasm_loader::read_wasm_or_exit(WASM_DIR, VP_USER_WASM); + let mut group = c.benchmark_group("vp_user"); + + let foreign_key_write = + generate_foreign_key_tx(&defaults::albert_keypair()); + + let transfer = generate_tx( + TX_TRANSFER_WASM, + Transfer { + source: defaults::albert_address(), + target: defaults::bertha_address(), + token: address::nam(), + sub_prefix: None, + amount: Amount::whole(1000), + key: None, + shielded: None, + }, + &defaults::albert_keypair(), + ); + + let received_transfer = generate_tx( + TX_TRANSFER_WASM, + Transfer { + source: defaults::bertha_address(), + target: defaults::albert_address(), + token: address::nam(), + sub_prefix: None, + amount: Amount::whole(1000), + key: None, + shielded: None, + }, + &defaults::bertha_keypair(), + ); + + let vp = generate_tx( + TX_UPDATE_VP_WASM, + UpdateVp { + addr: defaults::albert_address(), + vp_code: wasm_loader::read_wasm_or_exit( + WASM_DIR, + "vp_validator.wasm", + ), + }, + &defaults::albert_keypair(), + ); + + let vote = generate_tx( + TX_VOTE_PROPOSAL_WASM, + VoteProposalData { + id: 0, + vote: ProposalVote::Yay(VoteType::Default), + voter: defaults::albert_address(), + delegations: vec![defaults::validator_address()], + }, + &defaults::albert_keypair(), + ); + + let pos = generate_tx( + TX_UNBOND_WASM, + Bond { + validator: defaults::validator_address(), + amount: Amount::whole(1000), + source: Some(defaults::albert_address()), + }, + &defaults::albert_keypair(), + ); + + for (signed_tx, bench_name) in [ + foreign_key_write, + transfer, + received_transfer, + vote, + pos, + vp, + ] + .iter() + .zip([ + "foreign_key_write", + "transfer", + "received_transfer", + "governance_vote", + "pos", + "vp", + ]) { + let mut shell = BenchShell::new(); + shell.execute_tx(&signed_tx); + let (verifiers, keys_changed) = shell + .wl_storage + .write_log + .verifiers_and_changed_keys(&BTreeSet::default()); + + group.bench_function(bench_name, |b| { + b.iter(|| { + assert!(run::vp( + &vp_code, + signed_tx, + &TxIndex(0), + &defaults::albert_address(), + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + &mut VpGasMeter::new(u64::MAX, 0), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + false, + ) + .unwrap()); + }) + }); + } + + group.finish(); +} + +fn vp_implicit(c: &mut Criterion) { + let vp_code = wasm_loader::read_wasm_or_exit(WASM_DIR, VP_IMPLICIT_WASM); + let mut group = c.benchmark_group("vp_implicit"); + + let mut csprng = rand::rngs::OsRng {}; + let implicit_account: common::SecretKey = + ed25519::SigScheme::generate(&mut csprng) + .try_to_sk() + .unwrap(); + + let foreign_key_write = + generate_foreign_key_tx(&defaults::albert_keypair()); + + let transfer = generate_tx( + TX_TRANSFER_WASM, + Transfer { + source: Address::from(&implicit_account.to_public()), + target: defaults::bertha_address(), + token: address::nam(), + sub_prefix: None, + amount: Amount::whole(500), + key: None, + shielded: None, + }, + &implicit_account, + ); + + let received_transfer = generate_tx( + TX_TRANSFER_WASM, + Transfer { + source: defaults::bertha_address(), + target: Address::from(&implicit_account.to_public()), + token: address::nam(), + sub_prefix: None, + amount: Amount::whole(1000), + key: None, + shielded: None, + }, + &defaults::bertha_keypair(), + ); + + let reveal_pk = Tx::new( + wasm_loader::read_wasm_or_exit(WASM_DIR, TX_REVEAL_PK_WASM), + Some(implicit_account.to_public().try_to_vec().unwrap()), + ChainId("bench".to_string()), + None, + ); + + let pos = generate_tx( + TX_BOND_WASM, + Bond { + validator: defaults::validator_address(), + amount: Amount::whole(1000), + source: Some(Address::from(&implicit_account.to_public())), + }, + &implicit_account, + ); + + let vote = generate_tx( + TX_VOTE_PROPOSAL_WASM, + VoteProposalData { + id: 0, + vote: ProposalVote::Yay(VoteType::Default), + voter: Address::from(&implicit_account.to_public()), + delegations: vec![], //NOTE: no need to bond tokens because the implicit vp doesn't check that + }, + &implicit_account, + ); + + for (tx, bench_name) in [ + &foreign_key_write, + &reveal_pk, + &transfer, + &received_transfer, + &pos, + &vote, + ] + .into_iter() + .zip([ + "foreign_key_write", + "reveal_pk", + "transfer", + "received_transfer", + "pos", + "governance_vote", + ]) { + let mut shell = BenchShell::new(); + + if bench_name != "reveal_pk" { + // Reveal publick key + shell.execute_tx(&reveal_pk); + shell.wl_storage.commit_tx(); + shell.commit(); + } + + if bench_name == "transfer" { + // Transfer some tokens to the implicit address + shell.execute_tx(&received_transfer); + shell.wl_storage.commit_tx(); + shell.commit(); + } + + // Run the tx to validate + shell.execute_tx(tx); + let (verifiers, keys_changed) = shell + .wl_storage + .write_log + .verifiers_and_changed_keys(&BTreeSet::default()); + + group.bench_function(bench_name, |b| { + b.iter(|| { + assert!(run::vp( + &vp_code, + tx, + &TxIndex(0), + &Address::from(&implicit_account.to_public()), + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + &mut VpGasMeter::new(u64::MAX, 0), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + false, + ) + .unwrap()) + }) + }); + } + + group.finish(); +} + +fn vp_validator(c: &mut Criterion) { + let vp_code = wasm_loader::read_wasm_or_exit(WASM_DIR, VP_VALIDATOR_WASM); + let mut group = c.benchmark_group("vp_validator"); + + let foreign_key_write = + generate_foreign_key_tx(&defaults::albert_keypair()); + + let transfer = generate_tx( + TX_TRANSFER_WASM, + Transfer { + source: defaults::validator_address(), + target: defaults::bertha_address(), + token: address::nam(), + sub_prefix: None, + amount: Amount::whole(1000), + key: None, + shielded: None, + }, + &defaults::validator_keypair(), + ); + + let received_transfer = generate_tx( + TX_TRANSFER_WASM, + Transfer { + source: defaults::bertha_address(), + target: defaults::validator_address(), + token: address::nam(), + sub_prefix: None, + amount: Amount::whole(1000), + key: None, + shielded: None, + }, + &defaults::bertha_keypair(), + ); + + let vp = generate_tx( + TX_UPDATE_VP_WASM, + UpdateVp { + addr: defaults::validator_address(), + vp_code: wasm_loader::read_wasm_or_exit( + WASM_DIR, + "vp_validator.wasm", + ), + }, + &defaults::validator_keypair(), + ); + + let commission_rate = generate_tx( + TX_CHANGE_VALIDATOR_COMMISSION_WASM, + CommissionChange { + validator: defaults::validator_address(), + new_rate: Decimal::new(6, 2), + }, + &defaults::validator_keypair(), + ); + + let vote = generate_tx( + TX_VOTE_PROPOSAL_WASM, + VoteProposalData { + id: 0, + vote: ProposalVote::Yay(VoteType::Default), + voter: defaults::validator_address(), + delegations: vec![], + }, + &defaults::validator_keypair(), + ); + + let pos = generate_tx( + TX_UNBOND_WASM, + Bond { + validator: defaults::validator_address(), + amount: Amount::whole(1000), + source: None, + }, + &defaults::validator_keypair(), + ); + + for (signed_tx, bench_name) in [ + foreign_key_write, + transfer, + received_transfer, + vote, + pos, + commission_rate, + vp, + ] + .iter() + .zip([ + "foreign_key_write", + "transfer", + "received_transfer", + "governance_vote", + "pos", + "commission_rate", + "vp", + ]) { + let mut shell = BenchShell::new(); + + shell.execute_tx(&signed_tx); + let (verifiers, keys_changed) = shell + .wl_storage + .write_log + .verifiers_and_changed_keys(&BTreeSet::default()); + + group.bench_function(bench_name, |b| { + b.iter(|| { + assert!(run::vp( + &vp_code, + signed_tx, + &TxIndex(0), + &defaults::validator_address(), + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + &mut VpGasMeter::new(u64::MAX, 0), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + false, + ) + .unwrap()); + }) + }); + } + + group.finish(); +} + +fn vp_token(c: &mut Criterion) { + let vp_code = wasm_loader::read_wasm_or_exit(WASM_DIR, VP_TOKEN_WASM); + let mut group = c.benchmark_group("vp_token"); + + let foreign_key_write = + generate_foreign_key_tx(&defaults::albert_keypair()); + + let transfer = generate_tx( + TX_TRANSFER_WASM, + Transfer { + source: defaults::albert_address(), + target: defaults::bertha_address(), + token: address::nam(), + sub_prefix: None, + amount: Amount::whole(1000), + key: None, + shielded: None, + }, + &defaults::albert_keypair(), + ); + + for (signed_tx, bench_name) in [foreign_key_write, transfer] + .iter() + .zip(["foreign_key_write", "transfer"]) + { + let mut shell = BenchShell::new(); + shell.execute_tx(&signed_tx); + let (verifiers, keys_changed) = shell + .wl_storage + .write_log + .verifiers_and_changed_keys(&BTreeSet::default()); + + group.bench_function(bench_name, |b| { + b.iter(|| { + assert!(run::vp( + &vp_code, + signed_tx, + &TxIndex(0), + &defaults::albert_address(), + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + &mut VpGasMeter::new(u64::MAX, 0), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + false, + ) + .unwrap()); + }) + }); + } +} + +fn vp_masp(c: &mut Criterion) { + let vp_code = wasm_loader::read_wasm_or_exit(WASM_DIR, VP_MASP_WASM); + let mut group = c.benchmark_group("vp_masp"); + + let amount = Amount::whole(500); + + for bench_name in ["shielding", "unshielding", "shielded"] { + group.bench_function(bench_name, |b| { + let mut shielded_ctx = BenchShieldedCtx::new(); + + let albert_spending_key = shielded_ctx + .ctx + .wallet + .find_spending_key(ALBERT_SPENDING_KEY) + .unwrap() + .to_owned(); + let albert_payment_addr = shielded_ctx + .ctx + .wallet + .find_payment_addr(ALBERT_PAYMENT_ADDRESS) + .unwrap() + .to_owned(); + let bertha_payment_addr = shielded_ctx + .ctx + .wallet + .find_payment_addr(BERTHA_PAYMENT_ADDRESS) + .unwrap() + .to_owned(); + + // Shield some tokens for Albert + let shield_tx = shielded_ctx.generate_masp_tx( + amount, + TransferSource::Address(defaults::albert_address()), + TransferTarget::PaymentAddress(albert_payment_addr), + ); + shielded_ctx.shell.execute_tx(&shield_tx); + shielded_ctx.shell.wl_storage.commit_tx(); + shielded_ctx.shell.commit(); + + let signed_tx = match bench_name { + "shielding" => shielded_ctx.generate_masp_tx( + amount, + TransferSource::Address(defaults::albert_address()), + TransferTarget::PaymentAddress(albert_payment_addr), + ), + "unshielding" => shielded_ctx.generate_masp_tx( + amount, + TransferSource::ExtendedSpendingKey(albert_spending_key), + TransferTarget::Address(defaults::albert_address()), + ), + "shielded" => shielded_ctx.generate_masp_tx( + amount, + TransferSource::ExtendedSpendingKey(albert_spending_key), + TransferTarget::PaymentAddress(bertha_payment_addr), + ), + _ => panic!("Unexpected bench test"), + }; + shielded_ctx.shell.execute_tx(&signed_tx); + let (verifiers, keys_changed) = shielded_ctx + .shell + .wl_storage + .write_log + .verifiers_and_changed_keys(&BTreeSet::default()); + + b.iter(|| { + assert!(run::vp( + &vp_code, + &signed_tx, + &TxIndex(0), + &defaults::validator_address(), + &shielded_ctx.shell.wl_storage.storage, + &shielded_ctx.shell.wl_storage.write_log, + &mut VpGasMeter::new(u64::MAX, 0), + &keys_changed, + &verifiers, + shielded_ctx.shell.vp_wasm_cache.clone(), + false, + ) + .unwrap()); + }) + }); + } + + group.finish(); +} + +criterion_group!( + whitelisted_vps, + vp_user, + vp_implicit, + vp_validator, + vp_masp, + vp_token +); +criterion_main!(whitelisted_vps); diff --git a/core/src/ledger/gas.rs b/core/src/ledger/gas.rs index 1f7a1c17c3..a5e28d5846 100644 --- a/core/src/ledger/gas.rs +++ b/core/src/ledger/gas.rs @@ -15,7 +15,7 @@ pub enum Error { GasOverflow, } -const TX_SIZE_GAS_PER_BYTE: u64 = 1; //FIXME: value here? +const TX_SIZE_GAS_PER_BYTE: u64 = 1; const COMPILE_GAS_PER_BYTE: u64 = 1; const PARALLEL_GAS_DIVIDER: u64 = 10; @@ -25,8 +25,7 @@ pub const MIN_STORAGE_GAS: u64 = 1; /// Gas module result for functions that may fail pub type Result = std::result::Result; -/// Gas metering in a block. Tracks the gas in a current block and a current -/// transaction. +/// Gas metering in a block. #[derive(Debug, Clone)] pub struct BlockGasMeter { /// The max amount of gas allowed per block, defined by the protocol parameter @@ -131,6 +130,7 @@ impl TxGasMeter { /// Add the compiling cost proportionate to the code length pub fn add_compiling_fee(&mut self, bytes_len: usize) -> Result<()> { + //FIXME: rename to add_compiling_gas self.add(bytes_len as u64 * COMPILE_GAS_PER_BYTE) } diff --git a/shared/src/ledger/queries/types.rs b/shared/src/ledger/queries/types.rs index fd65edcfd9..fcd43e081c 100644 --- a/shared/src/ledger/queries/types.rs +++ b/shared/src/ledger/queries/types.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use namada_core::ledger::storage::WlStorage; use crate::ledger::events::log::EventLog; @@ -75,7 +77,7 @@ pub trait Router { pub trait Client { /// `std::io::Error` can happen in decoding with /// `BorshDeserialize::try_from_slice` - type Error: From; + type Error: From + Display; /// Send a simple query request at the given path. For more options, use the /// `request` method. diff --git a/wasm/gas.json b/wasm/gas.json new file mode 100644 index 0000000000..e01e679acf --- /dev/null +++ b/wasm/gas.json @@ -0,0 +1,21 @@ +{ + "tx_bond.wasm": 1, + "tx_change_validator_commission.wasm": 1, + "tx_ibc.wasm": 1, + "tx_init_account.wasm": 1, + "tx_init_proposal.wasm": 1, + "tx_init_validator.wasm": 1, + "tx_reveal_pk.wasm": 1, + "tx_transfer.wasm": 1, + "tx_unbond.wasm": 1, + "tx_update_vp.wasm": 1, + "tx_vote_proposal.wasm": 1, + "tx_withdraw.wasm": 1, + "vp_implicit.wasm": 1, + "vp_masp.wasm": 1, + "vp_testnet_faucet.wasm": 0, + "vp_token.wasm": 1, + "vp_user.wasm": 1, + "vp_validator.wasm": 1, + "single_signature": 1 +} From 30f9590efecafb20671e0b94a9dafe685b0cfb7c Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 20 Mar 2023 15:16:06 +0100 Subject: [PATCH 03/23] Improves gas for native vps Date: Mon Mar 20 15:16:06 2023 +0100 --- apps/src/lib/cli.rs | 26 +-- apps/src/lib/config/genesis.rs | 4 +- .../lib/node/ledger/shell/finalize_block.rs | 111 ++++++++--- apps/src/lib/node/ledger/shell/mod.rs | 164 +++++++++-------- .../lib/node/ledger/shell/prepare_proposal.rs | 113 +++++++++++- .../lib/node/ledger/shell/process_proposal.rs | 173 +++++++++++++++++- core/src/ledger/gas.rs | 62 ++++--- core/src/types/transaction/wrapper.rs | 15 +- shared/src/ledger/native_vp/mod.rs | 8 +- shared/src/ledger/protocol/mod.rs | 100 +++++----- shared/src/ledger/vp_host_fns.rs | 14 ++ shared/src/vm/host_env.rs | 14 +- tests/src/native_vp/mod.rs | 5 +- wasm/checksums.py | 36 +++- 14 files changed, 616 insertions(+), 229 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 253d73a353..458e963c7e 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1702,12 +1702,12 @@ pub mod args { const EXPIRATION_OPT: ArgOpt = arg_opt("expiration"); const FORCE: ArgFlag = flag("force"); const DONT_PREFETCH_WASM: ArgFlag = flag("dont-prefetch-wasm"); - const GAS_AMOUNT: ArgDefault = - arg_default("gas-amount", DefaultFn(|| token::Amount::from(0))); - const GAS_LIMIT: ArgDefault = - arg_default("gas-limit", DefaultFn(|| token::Amount::from(0))); - const GAS_TOKEN: ArgDefaultFromCtx = - arg_default_from_ctx("gas-token", DefaultFn(|| "NAM".into())); + const FEE_AMOUNT: ArgDefault = + arg_default("fee-amount", DefaultFn(|| token::Amount::from(0))); + const GAS_LIMIT: ArgDefault = + arg_default("gas-limit", DefaultFn(|| GasLimit::from(10))); //FIXME: fix this default value + const FEE_TOKEN: ArgDefaultFromCtx = + arg_default_from_ctx("fee-token", DefaultFn(|| "NAM".into())); const GENESIS_PATH: Arg = arg("genesis-path"); const GENESIS_VALIDATOR: ArgOpt = arg("genesis-validator").opt(); const HALT_ACTION: ArgFlag = flag("halt"); @@ -3024,7 +3024,7 @@ pub mod args { /// If any new account is initialized by the tx, use the given alias to /// save it in the wallet. pub initialized_account_alias: Option, - /// The amount being payed to include the transaction + /// The amount being payed (for gas unit) to include the transaction pub fee_amount: token::Amount, /// The token in which the fee is being paid pub fee_token: WalletAddress, @@ -3066,13 +3066,13 @@ pub mod args { initialized, the alias will be the prefix of each new \ address joined with a number.", )) - .arg(GAS_AMOUNT.def().about( + .arg(FEE_AMOUNT.def().about( "The amount being paid for the inclusion of this transaction", )) - .arg(GAS_TOKEN.def().about("The token for paying the gas")) + .arg(FEE_TOKEN.def().about("The token for paying the gas")) .arg( GAS_LIMIT.def().about( - "The maximum amount of gas needed to run transaction", + "The multiplier of the gas limit resolution definying the maximum amount of gas needed to run transaction", ), ) .arg(EXPIRATION_OPT.def().about( @@ -3109,9 +3109,9 @@ pub mod args { let broadcast_only = BROADCAST_ONLY.parse(matches); let ledger_address = LEDGER_ADDRESS_DEFAULT.parse(matches); let initialized_account_alias = ALIAS_OPT.parse(matches); - let fee_amount = GAS_AMOUNT.parse(matches); - let fee_token = GAS_TOKEN.parse(matches); - let gas_limit = GAS_LIMIT.parse(matches).into(); + let fee_amount = FEE_AMOUNT.parse(matches); + let fee_token = FEE_TOKEN.parse(matches); + let gas_limit = GAS_LIMIT.parse(matches); let expiration = EXPIRATION_OPT.parse(matches); let signing_key = SIGNING_KEY_OPT.parse(matches); diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 41db5d81cb..b61e93086d 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -961,7 +961,7 @@ pub fn genesis(num_validators: u64) -> Genesis { }, max_expected_time_per_block: namada::types::time::DurationSecs(30), max_proposal_bytes: Default::default(), - max_block_gas: u64::MAX, //FIXME: adjust this value + max_block_gas: 100_000_000, //FIXME: adjust this value vp_whitelist: vec![], tx_whitelist: vec![], implicit_vp_code_path: vp_implicit_path.into(), @@ -972,7 +972,7 @@ pub fn genesis(num_validators: u64) -> Genesis { pos_gain_d: dec!(0.1), staked_ratio: dec!(0.0), pos_inflation_amount: 0, - wrapper_tx_fees: Some(token::Amount::whole(0)), + wrapper_tx_fees: Some(token::Amount::whole(100)), gas_table: BTreeMap::default(), }; let albert = EstablishedAccount { diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 371822eaf5..d4a55e42ea 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -832,7 +832,7 @@ where let fee_payer = if wrapper.pk != address::masp_tx_key().ref_to() { wrapper.fee_payer() } else { - address::masp() + address::masp() //FIXME: here? }; let balance_key = token::balance_key(&wrapper.fee.token, &fee_payer); @@ -1794,16 +1794,12 @@ mod test_finalize_block { }, &keypair, Epoch(0), - GAS_LIMIT_MULTIPLIER.into(), + 0.into(), raw_tx.clone(), Default::default(), #[cfg(not(feature = "mainnet"))] None, ); - let signed_wrapper = wrapper_tx - .sign(&keypair, shell.chain_id.clone(), None) - .unwrap() - .to_bytes(); // Write inner hash in storage let inner_hash_key = @@ -1826,30 +1822,101 @@ mod test_finalize_block { info: "".into(), }, }; - let gas_limit = - u64::from(&wrapper_tx.gas_limit) - signed_wrapper.len() as u64; + let gas_limit = u64::from(&wrapper_tx.gas_limit); shell.enqueue_tx(wrapper_tx, gas_limit); - let _event = &shell + let event = &shell .finalize_block(FinalizeBlock { txs: vec![processed_tx], ..Default::default() }) .expect("Test failed")[0]; - // FIXME: uncomment when proper gas metering is in place - // // Check inner tx hash has been removed from storage - // assert_eq!(event.event_type.to_string(), String::from("applied")); - // let code = event.attributes.get("code").expect("Test - // failed").as_str(); assert_eq!(code, - // String::from(ErrorCodes::WasmRuntimeError).as_str()); - - // assert!( - // !shell - // .storage - // .has_key(&inner_hash_key) - // .expect("Test failed") - // .0 + // Check inner tx hash has been removed from storage + assert_eq!(event.event_type.to_string(), String::from("applied")); + let code = event.attributes.get("code").expect("Testfailed").as_str(); + assert_eq!(code, String::from(ErrorCodes::WasmRuntimeError).as_str()); + + assert!(!shell + .shell + .wl_storage + .has_key(&inner_hash_key) + .expect("Test failed")) + } + + /// Test that a wrapper transaction rejected by [`process_proposal`] because of gas, + /// still pays the fee + #[test] + fn test_rejected_wrapper_for_gas_pays_fee() { + let (mut shell, _) = setup(1); + + let keypair = gen_keypair(); + let address = Address::from(&keypair.to_public()); + + let mut wasm_path = top_level_directory(); + wasm_path.push("wasm_for_tests/tx_no_op.wasm"); + let tx_code = std::fs::read(wasm_path) + .expect("Expected a file at given code path"); + let raw_tx = Tx::new( + tx_code, + Some("Encrypted transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, + ); + let wrapper_tx = WrapperTx::new( + Fee { + amount: 1.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 1.into(), + raw_tx.clone(), + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ); + + let wrapper = wrapper_tx + .sign(&keypair, shell.chain_id.clone(), None) + .expect("Test failed"); + + let _processed_tx = ProcessedTx { + tx: wrapper.to_bytes(), + result: TxResult { + code: ErrorCodes::TxGasLimit.into(), + info: "".into(), + }, + }; + + let initial_balance = Amount::whole(1_000_000); + let balance_key = token::balance_key(&wrapper_tx.fee.token, &address); + shell + .wl_storage + .storage + .write(&balance_key, initial_balance.try_to_vec().unwrap()) + .unwrap(); + + //FIXME: uncomment when variable fees + // let event = &shell + // .finalize_block(FinalizeBlock { + // txs: vec![processed_tx], + // ..Default::default() + // }) + // .expect("Test failed")[0]; + + // assert_eq!(event.event_type.to_string(), String::from("accepted")); + // let code = event.attributes.get("code").expect("Testfailed").as_str(); + // assert_eq!(code, String::from(ErrorCodes::TxGasLimit).as_str()); + + // assert_eq!( + // storage_api::token::read_balance( + // &shell.wl_storage, + // &wrapper_tx.fee.token, + // &address + // ) + // .unwrap(), + // Amount::whole(1000) // ) } } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 55398fd2a9..206377ade3 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -131,7 +131,7 @@ pub enum ErrorCodes { Ok = 0, InvalidDecryptedChainId = 1, ExpiredDecryptedTx = 2, - DecryptedGasLimit = 3, + DecryptedTxGasLimit = 3, WasmRuntimeError = 4, InvalidTx = 5, InvalidSig = 6, @@ -158,7 +158,7 @@ impl ErrorCodes { | InvalidDecryptedChainId | ExpiredDecryptedTx | WasmRuntimeError - | DecryptedGasLimit => true, + | DecryptedTxGasLimit => true, InvalidTx | InvalidSig | InvalidOrder | ExtraTxs | Undecryptable | AllocationError | ReplayTx | InvalidChainId | ExpiredTx | BlockGasLimit | TxGasLimit => false, @@ -731,9 +731,7 @@ where .read_storage_key(¶meters::storage::get_max_block_gas_key()) .expect("Missing max_block_gas parameter in storage"); let mut block_gas_meter = BlockGasMeter::new(block_gas_limit); - if let Err(_) = block_gas_meter - .finalize_transaction(gas_meter.get_current_transaction_gas()) - { + if let Err(_) = block_gas_meter.finalize_transaction(gas_meter) { response.code = ErrorCodes::BlockGasLimit.into(); response.log = "Wrapper transaction exceeds the maximum block gas limit" @@ -819,6 +817,7 @@ where #[allow(dead_code)] /// Simulate validation and application of a transaction. fn dry_run_tx(&self, tx_bytes: &[u8]) -> response::Query { + //FIXME: should we simulate also the wrapper part for gas? let mut response = response::Query::default(); let gas_table: BTreeMap = self .read_storage_key(¶meters::storage::get_gas_table_storage_key()) @@ -1056,7 +1055,8 @@ mod test_utils { let base_dir = tempdir().unwrap().as_ref().canonicalize().unwrap(); let vp_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB let tx_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB - let mut shell = Self { + ( + Self { shell: Shell::::new( config::Ledger::new( base_dir, @@ -1070,73 +1070,7 @@ mod test_utils { tx_wasm_compilation_cache, address::nam(), ), - }; - - // Initialize gas table - let mut checksums: BTreeMap = serde_json::from_slice( - &std::fs::read("../wasm/checksums.json").unwrap(), - ) - .unwrap(); - // Extend gas table with test transactions and vps - checksums.extend(serde_json::from_slice::>( - &std::fs::read("../wasm_for_tests/checksums.json").unwrap(), - ) - .unwrap().into_iter()); - - let gas_file: BTreeMap = serde_json::from_slice( - &std::fs::read("../wasm/gas.json").unwrap(), - ) - .unwrap(); - - let mut gas_table = BTreeMap::::new(); - - for id in checksums.keys().chain(gas_file.keys()){ - // Get tx/vp hash (or name if native) - let hash = match checksums.get(id.as_str()) { - Some(v) => { -v - .split_once('.') - .unwrap() - .1 - .split_once('.') - .unwrap() - .0.to_owned() - } - None => { - id.to_owned() - } - }; - let gas = gas_file.get(id).unwrap_or(&1_000).to_owned(); - gas_table.insert(hash, gas); - } - - - let gas_table_key = - namada::core::ledger::parameters::storage::get_gas_table_storage_key(); - shell.wl_storage - .storage - .write(&gas_table_key, - namada::core::ledger::storage::types::encode(&gas_table)) - .expect( - "Gas table parameter must be initialized in the genesis block", - ); - - let max_block_gas_key = - namada::core::ledger::parameters::storage::get_max_block_gas_key( - ); - shell.wl_storage - .storage - .write( - &max_block_gas_key, - namada::core::ledger::storage::types::encode( - &10_000_000_u64, - ), - ) - .expect( - "Max block gas parameter must be initialized in storage", - ); - ( - shell, + }, receiver, ) } @@ -1291,7 +1225,7 @@ v }, &keypair, Epoch(0), - 1.into(), + 0.into(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1348,9 +1282,6 @@ mod test_mempool_validate { use super::test_utils::TestShell; use super::{MempoolTxType, *}; - -const GAS_LIMIT_MULTIPLIER: u64 = 1; - /// Mempool validation must reject unsigned wrappers #[test] fn test_missing_signature() { @@ -1530,7 +1461,7 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; }, &keypair, Epoch(0), - GAS_LIMIT_MULTIPLIER.into(), + 0.into(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1671,4 +1602,81 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; ); assert_eq!(result.code, u32::from(ErrorCodes::ExpiredTx)); } + + /// Check that a tx requiring more gas than the block limit gets rejected + #[test] + fn test_exceeding_max_block_gas_tx() { + let (shell, _) = test_utils::setup(1); + + let block_gas_limit: u64 = shell + .read_storage_key(¶meters::storage::get_max_block_gas_key()) + .expect("Missing max_block_gas parameter in storage"); + let keypair = super::test_utils::gen_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, + ); + + let wrapper = WrapperTx::new( + Fee { + amount: 100.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + (block_gas_limit + 1).into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ) + .sign(&keypair, shell.chain_id.clone(), None) + .expect("Wrapper signing failed"); + + let result = shell.mempool_validate( + wrapper.to_bytes().as_ref(), + MempoolTxType::NewTransaction, + ); + assert_eq!(result.code, u32::from(ErrorCodes::BlockGasLimit)); + } + + // Check that a tx requiring more gas than its limit gets rejected + #[test] + fn test_exceeding_gas_limit_tx() { + let (shell, _) = test_utils::setup(1); + + let keypair = super::test_utils::gen_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, + ); + + let wrapper = WrapperTx::new( + Fee { + amount: 100.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ) + .sign(&keypair, shell.chain_id.clone(), None) + .expect("Wrapper signing failed"); + + let result = shell.mempool_validate( + wrapper.to_bytes().as_ref(), + MempoolTxType::NewTransaction, + ); + assert_eq!(result.code, u32::from(ErrorCodes::TxGasLimit)); + } } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 0b6a62c0bf..77b64c0750 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1,6 +1,7 @@ //! Implementation of the [`RequestPrepareProposal`] ABCI++ method for the Shell use namada::core::hints; +use namada::core::ledger::gas::TxGasMeter; use namada::core::ledger::parameters; use namada::ledger::gas::BlockGasMeter; use namada::ledger::storage::{DBIter, StorageHasher, DB}; @@ -148,10 +149,16 @@ where if let (Some(block_time), Some(exp)) = (block_time.as_ref(), &tx.expiration) { if block_time > exp { return None } } - if let Ok(TxType::Wrapper(ref wrapper)) = process_tx(tx) { + if let Ok(TxType::Wrapper(wrapper)) = process_tx(tx) { -// Check tx gas limit - if temp_block_gas_meter.try_finalize_transaction(wrapper.gas_limit.clone().into()).is_ok() { + // Check tx gas limit + let mut tx_gas_meter = TxGasMeter::new(wrapper.gas_limit.into()); + if tx_gas_meter + .add_tx_size_gas(tx_bytes.len()).is_err() { + return None; + } + + if temp_block_gas_meter.try_finalize_transaction(tx_gas_meter).is_ok() { return Some(tx_bytes.clone()); } } @@ -474,4 +481,104 @@ mod test_prepare_proposal { eprintln!("Proposal: {:?}", result.txs); assert!(result.txs.is_empty()); } + + /// Check that a tx requiring more gas than the block limit is not included in the block + #[test] + fn test_exceeding_max_block_gas_tx() { + let (shell, _) = test_utils::setup(1); + + let block_gas_limit: u64 = shell + .read_storage_key(¶meters::storage::get_max_block_gas_key()) + .expect("Missing max_block_gas parameter in storage"); + let keypair = gen_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, + ); + + let wrapper = WrapperTx::new( + Fee { + amount: 100.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + (block_gas_limit + 1).into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ) + .sign(&keypair, shell.chain_id.clone(), None) + .expect("Wrapper signing failed"); + + let req = RequestPrepareProposal { + txs: vec![wrapper.to_bytes()], + max_tx_bytes: 0, + time: None, + ..Default::default() + }; + #[cfg(feature = "abcipp")] + assert_eq!( + shell.prepare_proposal(req).tx_records, + vec![record::remove(wrapper.to_bytes())] + ); + #[cfg(not(feature = "abcipp"))] + { + let result = shell.prepare_proposal(req); + eprintln!("Proposal: {:?}", result.txs); + assert!(result.txs.is_empty()); + } + } + + // Check that a wrapper requiring more gas than its limit is not included in the block + #[test] + fn test_exceeding_gas_limit_wrapper() { + let (shell, _) = test_utils::setup(1); + let keypair = gen_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, + ); + + let wrapper = WrapperTx::new( + Fee { + amount: 100.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ) + .sign(&keypair, shell.chain_id.clone(), None) + .expect("Wrapper signing failed"); + + let req = RequestPrepareProposal { + txs: vec![wrapper.to_bytes()], + max_tx_bytes: 0, + time: None, + ..Default::default() + }; + #[cfg(feature = "abcipp")] + assert_eq!( + shell.prepare_proposal(req).tx_records, + vec![record::remove(wrapper.to_bytes())] + ); + #[cfg(not(feature = "abcipp"))] + { + let result = shell.prepare_proposal(req); + eprintln!("Proposal: {:?}", result.txs); + assert!(result.txs.is_empty()); + } + } } diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 05fdc36aa2..77aac7e32f 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -147,7 +147,7 @@ where self.read_storage_key( ¶meters::storage::get_max_block_gas_key(), ) - .expect("Missing parameter in storage"), + .expect("Missing max_block_gas parameter in storage"), ); let gas_table: BTreeMap = self .read_storage_key(¶meters::storage::get_gas_table_storage_key()) @@ -387,7 +387,7 @@ where if let Err(e) = tx_gas_meter.add(tx_gas) { return TxResult { - code: ErrorCodes::TxGasLimit.into(), + code: ErrorCodes::DecryptedTxGasLimit.into(), info: format!("Decrypted transaction gas error: {}", e) }; } @@ -418,10 +418,10 @@ where TxType::Wrapper(wrapper) => { // Account for gas. This is done even if the transaction is later deemed invalid, to incentivize the proposer to // include only valid transaction and avoid wasting block gas limit - // Wrapper gas limit, Max block gas and cumulated block gas let mut tx_gas_meter = TxGasMeter::new(u64::from(&wrapper.gas_limit)); if let Err(_) = tx_gas_meter.add_tx_size_gas(tx_bytes.len()) { - let _ = temp_block_gas_meter.finalize_transaction(tx_gas_meter.get_current_transaction_gas()); + // Add the declared tx gas limit to the block gas meter even in case of an error + let _ = temp_block_gas_meter.finalize_transaction(tx_gas_meter); return TxResult { code: ErrorCodes::TxGasLimit.into(), @@ -429,7 +429,7 @@ where }; } - if let Err(_) = temp_block_gas_meter.finalize_transaction(tx_gas_meter.get_current_transaction_gas()){ + if let Err(_) = temp_block_gas_meter.finalize_transaction(tx_gas_meter){ return TxResult { code: ErrorCodes::BlockGasLimit.into(), @@ -793,6 +793,17 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; ) .unwrap(); let keypair = gen_keypair(); +// reduce address balance to match the 100 token min fee + let balance_key = token::balance_key( + &shell.wl_storage.storage.native_token, + &Address::from(&keypair.ref_to()), + ); + shell + .wl_storage + .storage + .write(&balance_key, Amount::whole(99).try_to_vec().unwrap()) + .unwrap(); + let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), @@ -842,7 +853,7 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; fn test_wrapper_insufficient_balance_address() { let (mut shell, _) = test_utils::setup(1); let keypair = crate::wallet::defaults::daewon_keypair(); - // reduce address balance to match the 100 token fee + // reduce address balance to match the 100 token min fee let balance_key = token::balance_key( &shell.wl_storage.storage.native_token, &Address::from(&keypair.ref_to()), @@ -869,7 +880,7 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; ); let wrapper = WrapperTx::new( Fee { - amount: Amount::whole(1_000_100), + amount: Amount::whole(100), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, @@ -1720,4 +1731,152 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; Err(_) => panic!("Test failed"), } } + + + /// Test that a decrypted transaction requiring more gas than the limit imposed + /// by its wrapper is rejected. + #[test] + fn test_decrypted_gas_limit() { + let (mut shell, _) = test_utils::setup(1); + let keypair = crate::wallet::defaults::daewon_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("new transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None + ); + let decrypted: Tx = DecryptedTx::Decrypted { + tx: tx.clone(), + has_valid_pow: false, + } + .into(); + let signed_decrypted = decrypted.sign(&keypair); + let wrapper = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ); + let gas = u64::from(&wrapper.gas_limit) ; + let wrapper_in_queue = WrapperTxInQueue { + tx: wrapper, + gas, + has_valid_pow: false, + }; + shell.wl_storage.storage.tx_queue.push(wrapper_in_queue); + + // Run validation + let request = ProcessProposal { + txs: vec![signed_decrypted.to_bytes()], + }; + match shell.process_proposal(request) { + Ok(response) => { + assert_eq!( + response[0].result.code, + u32::from(ErrorCodes::DecryptedTxGasLimit) + ); + } + Err(_) => panic!("Test failed"), + } + } + + /// Check that a tx requiring more gas than the block limit causes a block rejection + #[test] + fn test_exceeding_max_block_gas_tx() { + let (mut shell, _) = test_utils::setup(1); + + let block_gas_limit: u64 = shell + .read_storage_key(¶meters::storage::get_max_block_gas_key()) + .expect("Missing max_block_gas parameter in storage"); + let keypair = super::test_utils::gen_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, + ); + + let wrapper = WrapperTx::new( + Fee { + amount: 100.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + (block_gas_limit + 1).into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ) + .sign(&keypair, shell.chain_id.clone(), None) + .expect("Wrapper signing failed"); + + // Run validation + let request = ProcessProposal { + txs: vec![wrapper.to_bytes()], + }; +match shell.process_proposal(request) { + Ok(_) => panic!("Test failed"), + Err(TestError::RejectProposal(response)) => { + assert_eq!( + response[0].result.code, + u32::from(ErrorCodes::BlockGasLimit) + ); + } + } + } + +// Check that a wrapper requiring more gas than its limit causes a block rejection + #[test] + fn test_exceeding_gas_limit_wrapper() { + let (mut shell, _) = test_utils::setup(1); + let keypair = super::test_utils::gen_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, + ); + + let wrapper = WrapperTx::new( + Fee { + amount: 100.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ) + .sign(&keypair, shell.chain_id.clone(), None) + .expect("Wrapper signing failed"); + + // Run validation + let request = ProcessProposal { + txs: vec![wrapper.to_bytes()], + }; +match shell.process_proposal(request) { + Ok(_) => panic!("Test failed"), + Err(TestError::RejectProposal(response)) => { + assert_eq!( + response[0].result.code, + u32::from(ErrorCodes::TxGasLimit) + ); + } + } + } } diff --git a/core/src/ledger/gas.rs b/core/src/ledger/gas.rs index a5e28d5846..746260e93f 100644 --- a/core/src/ledger/gas.rs +++ b/core/src/ledger/gas.rs @@ -21,11 +21,16 @@ const PARALLEL_GAS_DIVIDER: u64 = 10; /// The minimum gas cost for accessing the storage pub const MIN_STORAGE_GAS: u64 = 1; +/// The gas cost for verifying the signature of a transaction +pub const VERIFY_TX_SIG_GAS_COST: u64 = 1000; +/// The gas cost for validating wasm vp code +pub const WASM_VALIDATION_GAS_PER_BYTE: u64 = 1; /// Gas module result for functions that may fail pub type Result = std::result::Result; -/// Gas metering in a block. +/// Gas metering in a block. The amount of gas consumed in a block is based on the +/// tx_gas_limit declared by each [`TxGasMeter`] #[derive(Debug, Clone)] pub struct BlockGasMeter { /// The max amount of gas allowed per block, defined by the protocol parameter @@ -70,13 +75,16 @@ impl BlockGasMeter { } } - /// Add the transaction gas to the block's total gas. It will return + /// Add the transaction gas limit to the block's total gas. It will return /// error when the consumed gas exceeds the block gas limit, but the state - /// will still be updated. - pub fn finalize_transaction(&mut self, tx_gas: u64) -> Result<()> { + /// will still be updated. This function consumes the [`TxGasMeter`] which shouldn't be updated after this point. + pub fn finalize_transaction( + &mut self, + tx_gas_meter: TxGasMeter, + ) -> Result<()> { self.block_gas = self .block_gas - .checked_add(tx_gas) + .checked_add(tx_gas_meter.tx_gas_limit) .ok_or(Error::GasOverflow)?; if self.block_gas > self.block_gas_limit { @@ -85,12 +93,15 @@ impl BlockGasMeter { Ok(()) } - /// Tries to add the transaction gas to the block's total gas. - /// If the operation returns an error, propagates this errors without updating the state. - pub fn try_finalize_transaction(&mut self, tx_gas: u64) -> Result<()> { + /// Tries to add the transaction gas limit to the block's total gas. + /// If the operation returns an error, propagates this errors without updating the state. This function consumes the [`TxGasMeter`] which shouldn't be updated after this point. + pub fn try_finalize_transaction( + &mut self, + tx_gas_meter: TxGasMeter, + ) -> Result<()> { let updated_gas = self .block_gas - .checked_add(tx_gas) + .checked_add(tx_gas_meter.tx_gas_limit) .ok_or(Error::GasOverflow)?; if updated_gas > self.block_gas_limit { @@ -129,8 +140,7 @@ impl TxGasMeter { } /// Add the compiling cost proportionate to the code length - pub fn add_compiling_fee(&mut self, bytes_len: usize) -> Result<()> { - //FIXME: rename to add_compiling_gas + pub fn add_compiling_gas(&mut self, bytes_len: usize) -> Result<()> { self.add(bytes_len as u64 * COMPILE_GAS_PER_BYTE) } @@ -185,14 +195,14 @@ impl VpGasMeter { } /// Add the compiling cost proportionate to the code length - pub fn add_compiling_fee(&mut self, bytes_len: usize) -> Result<()> { + pub fn add_compiling_gas(&mut self, bytes_len: usize) -> Result<()> { self.add(bytes_len as u64 * COMPILE_GAS_PER_BYTE) } } impl VpsGas { - /// Set the gas cost from a single VP run. - pub fn set(&mut self, vp_gas_meter: &VpGasMeter) -> Result<()> { + /// Set the gas cost from a single VP run. It consumes the [`VpGasMeter`] instance which shouldn't be accessed passed this point. + pub fn set(&mut self, vp_gas_meter: VpGasMeter) -> Result<()> { debug_assert_eq!(self.max, None); debug_assert!(self.rest.is_empty()); self.max = Some(vp_gas_meter.current_gas); @@ -263,10 +273,10 @@ mod tests { #[test] fn test_block_gas_meter_add(gas in 0..BLOCK_GAS_LIMIT) { let mut meter = BlockGasMeter::new(BLOCK_GAS_LIMIT); - let mut tx_gas_meter = TxGasMeter::new(BLOCK_GAS_LIMIT + 1); - tx_gas_meter.add( gas).expect("cannot add the gas"); - meter.finalize_transaction(tx_gas_meter.get_current_transaction_gas()).expect("cannot finalize the tx"); - assert_eq!(meter.block_gas, gas); + let mut tx_gas_meter = TxGasMeter::new(BLOCK_GAS_LIMIT ); + tx_gas_meter.add(gas).expect("cannot add the gas"); + meter.finalize_transaction(tx_gas_meter).expect("cannot finalize the tx"); + assert_eq!(meter.block_gas, BLOCK_GAS_LIMIT); } } @@ -316,23 +326,15 @@ mod tests { // add the maximum tx gas for _ in 0..(BLOCK_GAS_LIMIT / transaction_gas) { - let mut tx_gas_meter = TxGasMeter::new(transaction_gas + 1); - tx_gas_meter - .add(transaction_gas) - .expect("over the tx gas limit"); + let tx_gas_meter = TxGasMeter::new(transaction_gas); meter - .finalize_transaction( - tx_gas_meter.get_current_transaction_gas(), - ) + .finalize_transaction(tx_gas_meter) .expect("over the block gas limit"); } - let mut tx_gas_meter = TxGasMeter::new(transaction_gas + 1); - tx_gas_meter - .add(transaction_gas) - .expect("over the tx gas limit"); + let tx_gas_meter = TxGasMeter::new(transaction_gas); match meter - .finalize_transaction(tx_gas_meter.get_current_transaction_gas()) + .finalize_transaction(tx_gas_meter) .expect_err("unexpectedly succeeded") { Error::BlockGasExceeded => {} diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index 5de138bacd..8eda482e8b 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -4,6 +4,8 @@ #[cfg(feature = "ferveo-tpke")] pub mod wrapper_tx { use std::convert::TryFrom; + use std::num::ParseIntError; + use std::str::FromStr; pub use ark_bls12_381::Bls12_381 as EllipticCurve; pub use ark_ec::{AffineCurve, PairingEngine}; @@ -58,7 +60,7 @@ pub mod wrapper_tx { Deserialize, )] pub struct Fee { - /// amount of the fee + /// amount of fee per gas unit pub amount: Amount, /// address of the token pub token: Address, @@ -151,6 +153,17 @@ pub mod wrapper_tx { } } + impl FromStr for GasLimit { + type Err = ParseIntError; + + fn from_str(s: &str) -> Result { + // Expect input to be the multiplier + Ok(Self { + multiplier: s.parse()?, + }) + } + } + /// A transaction with an encrypted payload as well /// as some non-encrypted metadata for inclusion /// and / or verification purposes diff --git a/shared/src/ledger/native_vp/mod.rs b/shared/src/ledger/native_vp/mod.rs index bfd14e7c1b..a71146b573 100644 --- a/shared/src/ledger/native_vp/mod.rs +++ b/shared/src/ledger/native_vp/mod.rs @@ -537,7 +537,13 @@ where pk: &crate::types::key::common::PublicKey, sig: &crate::types::key::common::Signature, ) -> Result { - Ok(self.tx.verify_sig(pk, sig).is_ok()) + vp_host_fns::verify_tx_signature( + &mut self.gas_meter.borrow_mut(), + self.tx, + pk, + sig, + ) + .into_storage_result() } fn verify_masp(&self, _tx: Vec) -> Result { diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 1520b88cd7..842718389c 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -193,7 +193,7 @@ where }; tx_gas_meter - .add_compiling_fee(tx_code.len()) + .add_compiling_gas(tx_code.len()) .map_err(Error::GasError)?; let tx_hash = tx_hash.to_string().to_ascii_lowercase(); let tx_gas_required = match gas_table.get(tx_hash.as_str()) { @@ -311,15 +311,37 @@ where } }; + // Always account for compilation gas cost + let vp_code = { + let key = storage::Key::wasm_code(&vp_code_hash); + match write_log.read(&key).0 { + Some(StorageModification::Write { value }) => { + value.clone() + } + _ => storage + .read(&key) + .map_err(Error::StorageError)? + .0 + .ok_or_else(|| { + Error::MissingWasmCodeInStorage( + vp_code_hash.clone(), + ) + })?, + } + }; + + gas_meter + .add_compiling_gas(vp_code_hash.len()) + .map_err(Error::GasError)?; + add_precomputed_gas( &mut gas_meter, gas_table, &vp_code_hash.to_string().to_ascii_lowercase(), )?; - // Mock gas meter to ignore runtime gas metering while we - // rely on the gas table - let mut mock_gas_meter = VpGasMeter::new(u64::MAX, 0); + // NOTE: because of the whitelisted gas and the gas metering for the exposed vm env functions, + // the first signature verification (if any) is accounted twice wasm::run::vp( &vp_code_hash, tx, @@ -327,7 +349,7 @@ where addr, storage, write_log, - &mut mock_gas_meter, + &mut gas_meter, &keys_changed, &verifiers, vp_wasm_cache.clone(), @@ -337,16 +359,13 @@ where .map_err(Error::VpRunnerError) } Address::Internal(internal_addr) => { - // Mock gas meter to ignore runtime gas metering while we - // rely on the gas table - let mock_gas_meter = VpGasMeter::new(u64::MAX, 0); let ctx = native_vp::Ctx::new( addr, storage, write_log, tx, tx_index, - mock_gas_meter, + gas_meter, &keys_changed, &verifiers, vp_wasm_cache.clone(), @@ -358,13 +377,6 @@ where let accepted: Result = match internal_addr { InternalAddress::PoS => { - // No hash for native vps, just use the name - add_precomputed_gas( - &mut gas_meter, - gas_table, - "native_vp_pos", - )?; - let pos = PosVP { ctx }; let verifiers_addr_ref = &verifiers; let pos_ref = &pos; @@ -391,105 +403,79 @@ where Err(Error::PosNativeVpRuntime) } }; + // Take the gas meter back out of the context + gas_meter = pos.ctx.gas_meter.into_inner(); result } InternalAddress::Ibc => { - add_precomputed_gas( - &mut gas_meter, - gas_table, - "native_vp_ibc", - )?; let ibc = Ibc { ctx }; let result = ibc .validate_tx(tx_data, &keys_changed, &verifiers) .map_err(Error::IbcNativeVpError); + // Take the gas meter back out of the context + gas_meter = ibc.ctx.gas_meter.into_inner(); result } InternalAddress::Parameters => { - add_precomputed_gas( - &mut gas_meter, - gas_table, - "native_vp_parameters", - )?; let parameters = ParametersVp { ctx }; let result = parameters .validate_tx(tx_data, &keys_changed, &verifiers) .map_err(Error::ParametersNativeVpError); + // Take the gas meter back out of the context + gas_meter = parameters.ctx.gas_meter.into_inner(); result } InternalAddress::PosSlashPool => { - add_precomputed_gas( - &mut gas_meter, - gas_table, - "native_vp_pos_slash_pool", - )?; + // Take the gas meter back out of the context + gas_meter = ctx.gas_meter.into_inner(); Err(Error::AccessForbidden( (*internal_addr).clone(), )) } InternalAddress::Governance => { - add_precomputed_gas( - &mut gas_meter, - gas_table, - "native_vp_governance", - )?; let governance = GovernanceVp { ctx }; let result = governance .validate_tx(tx_data, &keys_changed, &verifiers) .map_err(Error::GovernanceNativeVpError); + gas_meter = governance.ctx.gas_meter.into_inner(); result } InternalAddress::SlashFund => { - add_precomputed_gas( - &mut gas_meter, - gas_table, - "native_vp_slash_fund", - )?; let slash_fund = SlashFundVp { ctx }; let result = slash_fund .validate_tx(tx_data, &keys_changed, &verifiers) .map_err(Error::SlashFundNativeVpError); + gas_meter = slash_fund.ctx.gas_meter.into_inner(); result } InternalAddress::IbcToken(_) | InternalAddress::IbcEscrow | InternalAddress::IbcBurn | InternalAddress::IbcMint => { - add_precomputed_gas( - &mut gas_meter, - gas_table, - "native_vp_ibc_token", - )?; // validate the transfer let ibc_token = IbcToken { ctx }; let result = ibc_token .validate_tx(tx_data, &keys_changed, &verifiers) .map_err(Error::IbcTokenNativeVpError); + gas_meter = ibc_token.ctx.gas_meter.into_inner(); result } InternalAddress::EthBridge => { - add_precomputed_gas( - &mut gas_meter, - gas_table, - "native_vp_eth_bridge", - )?; let bridge = EthBridge { ctx }; let result = bridge .validate_tx(tx_data, &keys_changed, &verifiers) .map_err(Error::EthBridgeNativeVpError); + gas_meter = bridge.ctx.gas_meter.into_inner(); result } InternalAddress::ReplayProtection => { - add_precomputed_gas( - &mut gas_meter, - gas_table, - "native_vp_replay_protection", - )?; let replay_protection_vp = ReplayProtectionVp { ctx }; let result = replay_protection_vp .validate_tx(tx_data, &keys_changed, &verifiers) .map_err(Error::ReplayProtectionNativeVpError); + gas_meter = + replay_protection_vp.ctx.gas_meter.into_inner(); result } }; @@ -501,7 +487,7 @@ where // Returning error from here will short-circuit the VP parallel // execution. It's important that we only short-circuit gas // errors to get deterministic gas costs - result.gas_used.set(&gas_meter).map_err(Error::GasError)?; + result.gas_used.set(gas_meter).map_err(Error::GasError)?; match accept { Ok(accepted) => { if !accepted { diff --git a/shared/src/ledger/vp_host_fns.rs b/shared/src/ledger/vp_host_fns.rs index 84b3e6f357..14bd8cfa56 100644 --- a/shared/src/ledger/vp_host_fns.rs +++ b/shared/src/ledger/vp_host_fns.rs @@ -2,8 +2,10 @@ use std::num::TryFromIntError; +use namada_core::ledger::gas::VERIFY_TX_SIG_GAS_COST; use namada_core::types::address::Address; use namada_core::types::hash::{Hash, HASH_LENGTH}; +use namada_core::types::key::common; use namada_core::types::storage::{ BlockHash, BlockHeight, Epoch, Header, Key, TxIndex, }; @@ -383,3 +385,15 @@ where } Ok(None) } + +/// Verify the signature of a transaction +pub fn verify_tx_signature( + gas_meter: &mut VpGasMeter, + tx: &Tx, + pk: &common::PublicKey, + sig: &common::Signature, +) -> EnvResult { + add_gas(gas_meter, VERIFY_TX_SIG_GAS_COST)?; + + Ok(tx.verify_sig(pk, sig).is_ok()) +} diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 2f4c463a21..d22b68ff00 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -14,7 +14,9 @@ use super::wasm::TxCache; #[cfg(feature = "wasm-runtime")] use super::wasm::VpCache; use super::WasmCacheAccess; -use crate::ledger::gas::{self, VpGasMeter, MIN_STORAGE_GAS}; +use crate::ledger::gas::{ + self, VpGasMeter, MIN_STORAGE_GAS, WASM_VALIDATION_GAS_PER_BYTE, +}; use crate::ledger::storage::write_log::{self, WriteLog}; use crate::ledger::storage::{self, Storage, StorageHasher}; use crate::ledger::vp_host_fns; @@ -29,9 +31,6 @@ use crate::vm::memory::VmMemory; use crate::vm::prefix_iter::{PrefixIteratorId, PrefixIterators}; use crate::vm::{HostRef, MutHostRef}; -const VERIFY_TX_SIG_GAS_COST: u64 = 1000; -const WASM_VALIDATION_GAS_PER_BYTE: u64 = 1; - /// These runtime errors will abort tx WASM execution immediately #[allow(missing_docs)] #[derive(Error, Debug)] @@ -1784,9 +1783,12 @@ where let sig: common::Signature = BorshDeserialize::try_from_slice(&sig) .map_err(vp_host_fns::RuntimeError::EncodingError)?; - vp_host_fns::add_gas(gas_meter, VERIFY_TX_SIG_GAS_COST)?; let tx = unsafe { env.ctx.tx.get() }; - Ok(HostEnvResult::from(tx.verify_sig(&pk, &sig).is_ok()).to_i64()) + + Ok(HostEnvResult::from(vp_host_fns::verify_tx_signature( + gas_meter, &tx, &pk, &sig, + )?) + .to_i64()) } /// Verify a ShieldedTransaction. diff --git a/tests/src/native_vp/mod.rs b/tests/src/native_vp/mod.rs index 5ffc2e65bb..c521b79a90 100644 --- a/tests/src/native_vp/mod.rs +++ b/tests/src/native_vp/mod.rs @@ -50,7 +50,10 @@ impl TestNativeVpEnv { { let ctx = Ctx { iterators: Default::default(), - gas_meter: RefCell::new(VpGasMeter::new(0, 0)), + gas_meter: RefCell::new(VpGasMeter::new( + self.tx_env.gas_meter.tx_gas_limit, + self.tx_env.gas_meter.get_current_transaction_gas(), + )), storage: &self.tx_env.wl_storage.storage, write_log: &self.tx_env.wl_storage.write_log, tx: &self.tx_env.tx, diff --git a/wasm/checksums.py b/wasm/checksums.py index 4b532b2ee8..5657b5c0f1 100644 --- a/wasm/checksums.py +++ b/wasm/checksums.py @@ -2,22 +2,42 @@ import glob import hashlib import os +import sys +gas = json.load(open("wasm/gas.json")) checksums = {} + for wasm in sorted(glob.glob("wasm/*.wasm")): basename = os.path.basename(wasm) - file_name = os.path.splitext(basename)[0] if wasm.count( - ".") == 1 else os.path.splitext(basename)[0].split('.')[0] - checksums["{}.wasm".format(file_name)] = "{}.{}.wasm".format( - file_name, hashlib.sha256(open(wasm, "rb").read()).hexdigest()) - os.rename(wasm, 'wasm/{}'.format(checksums["{}.wasm".format(file_name)])) + file_name = ( + os.path.splitext(basename)[0] + if wasm.count(".") == 1 + else os.path.splitext(basename)[0].split(".")[0] + ) + file_key = "{}.wasm".format(file_name) + checksums[file_key] = "{}.{}.wasm".format( + file_name, hashlib.sha256(open(wasm, "rb").read()).hexdigest() + ) + + # Check gas in whitelist + if file_key not in gas: + print("{} doesn't have an associated gas cost in gas.json".format(file_key)) + sys.exit(1) + + os.rename(wasm, "wasm/{}".format(checksums[file_key])) + +# Prune unused gas entries if needed (in case of a tx/vp removal) +for k in list(gas.keys()): + if k not in checksums: + del gas[k] + +json.dump(gas, open("wasm/gas.json", "w"), indent=4, sort_keys=True) updated_wasms = list(checksums.values()) for wasm in sorted(glob.glob("wasm/*.wasm")): basename = os.path.basename(wasm) - if not basename in updated_wasms: + if basename not in updated_wasms: os.remove(wasm) -json.dump(checksums, open("wasm/checksums.json", "w"), - indent=4, sort_keys=True) +json.dump(checksums, open("wasm/checksums.json", "w"), indent=4, sort_keys=True) From 0241b6f3b7ba3977b49a12db7e5e477715845fac Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 22 Mar 2023 12:16:47 +0100 Subject: [PATCH 04/23] Removes MASP shielded fees --- apps/src/lib/client/tx.rs | 67 +++++-------------- .../lib/node/ledger/shell/finalize_block.rs | 9 +-- apps/src/lib/node/ledger/shell/mod.rs | 20 ++---- .../lib/node/ledger/shell/process_proposal.rs | 15 +---- benches/mod.rs | 7 +- core/src/types/address.rs | 11 --- 6 files changed, 28 insertions(+), 101 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index be64e57c11..66612d2461 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -8,8 +8,8 @@ use std::io::{Read, Write}; use std::path::PathBuf; use std::str::FromStr; +use async_std::io; use async_std::io::prelude::WriteExt; -use async_std::io::{self}; use borsh::{BorshDeserialize, BorshSerialize}; use data_encoding::HEXLOWER_PERMISSIVE; use itertools::Either::*; @@ -42,7 +42,7 @@ use namada::ledger::governance::storage as gov_storage; use namada::ledger::masp; use namada::ledger::pos::{CommissionPair, PosParams}; use namada::proto::Tx; -use namada::types::address::{masp, masp_tx_key, Address}; +use namada::types::address::{masp, Address}; use namada::types::governance::{ OfflineProposal, OfflineVote, Proposal, ProposalVote, VoteType, }; @@ -71,7 +71,7 @@ use crate::cli::{args, safe_exit, Context}; use crate::client::rpc::{ query_conversion, query_epoch, query_storage_value, query_wasm_code_hash, }; -use crate::client::signing::{find_keypair, sign_tx, tx_signer, TxSigningKey}; +use crate::client::signing::{find_keypair, sign_tx, TxSigningKey}; use crate::client::tendermint_rpc_types::{TxBroadcastData, TxResponse}; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; @@ -1361,7 +1361,6 @@ pub async fn gen_shielded_transfer( ctx: &mut Context, client: &CLIENT, args: &args::TxTransfer, - shielded_gas: bool, ) -> Result, builder::Error> where CLIENT: namada::ledger::queries::Client + Sync, @@ -1399,27 +1398,18 @@ where let (asset_type, amount) = convert_amount(epoch, &ctx.get(&args.token), args.amount); + // Fees are always paid outside of MASP + builder.set_fee(Amount::zero())?; + // If there are shielded inputs if let Some(sk) = spending_key { - // Transaction fees need to match the amount in the wrapper Transfer - // when MASP source is used - let (_, fee) = convert_amount( - epoch, - &ctx.get(&args.tx.fee_token), - args.tx.fee_amount, - ); - builder.set_fee(fee.clone())?; - // FIXME: fix gas here? - // If the gas is coming from the shielded pool, then our shielded inputs - // must also cover the gas fee - let required_amt = if shielded_gas { amount + fee } else { amount }; // Locate unspent notes that can help us meet the transaction amount let (_, unspent_notes, used_convs) = ctx .shielded .collect_unspent_notes( client, &to_viewing_key(&sk).vk, - required_amt, + amount, epoch, ) .await; @@ -1438,10 +1428,6 @@ where } } } else { - // No transfer fees come from the shielded transaction for non-MASP - // sources - //FIXME: fix gas here? - builder.set_fee(Amount::zero())?; // We add a dummy UTXO to our transaction, but only the source of the // parent Transfer object is used to validate fund availability let secp_sk = @@ -1590,37 +1576,15 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { }; let masp_addr = masp(); - // For MASP sources, use a special sentinel key recognized by VPs as default - // signer. Also, if the transaction is shielded, redact the amount and token + // For MASP sources, redact the amount and token // types by setting the transparent value to 0 and token type to a constant. // This has no side-effect because transaction is to self. - let (default_signer, amount, token) = - if source == masp_addr && target == masp_addr { - // TODO Refactor me, we shouldn't rely on any specific token here. - ( - TxSigningKey::SecretKey(masp_tx_key()), - 0.into(), - ctx.native_token.clone(), - ) - } else if source == masp_addr { - ( - TxSigningKey::SecretKey(masp_tx_key()), - args.amount, - token.clone(), - ) - } else { - ( - TxSigningKey::WalletAddress(args.source.to_address()), - args.amount, - token, - ) - }; - // If our chosen signer is the MASP sentinel key, then our shielded inputs - // will need to cover the gas fees. - let chosen_signer = tx_signer(&mut ctx, &args.tx, default_signer.clone()) - .await - .ref_to(); - let shielded_gas = masp_tx_key().ref_to() == chosen_signer; + let (amount, token) = if source == masp_addr && target == masp_addr { + // TODO Refactor me, we shouldn't rely on any specific token here. + (0.into(), token) + } else { + (args.amount, token) + }; // Determine whether to pin this transaction to a storage key let key = match ctx.get(&args.target) { TransferTarget::PaymentAddress(pa) if pa.is_pinned() => Some(pa.hash()), @@ -1640,8 +1604,7 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { // Loop twice in case the first submission attempt fails for _ in 0..2 { // Construct the shielded part of the transaction, if any - let stx_result = - gen_shielded_transfer(&mut ctx, &client, &args, shielded_gas).await; + let stx_result = gen_shielded_transfer(&mut ctx, &client, &args).await; let (shielded, shielded_tx_epoch) = match stx_result { Ok(stx) => unzip_option(stx.map(|x| (x.0, x.2))), diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index d4a55e42ea..1dfcfd68a6 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -829,13 +829,8 @@ where #[cfg(not(feature = "mainnet"))] has_valid_pow: bool, ) -> Result<()> { // Charge fee - let fee_payer = if wrapper.pk != address::masp_tx_key().ref_to() { - wrapper.fee_payer() - } else { - address::masp() //FIXME: here? - }; - - let balance_key = token::balance_key(&wrapper.fee.token, &fee_payer); + let balance_key = + token::balance_key(&wrapper.fee.token, &wrapper.fee_payer()); let balance: token::Amount = self .wl_storage .read(&balance_key) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 206377ade3..8fea607d4d 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -36,13 +36,13 @@ use namada::ledger::storage_api::{self, StorageRead}; use namada::ledger::{ibc, parameters, pos, protocol, replay_protection}; use namada::proof_of_stake::{self, read_pos_params, slash}; use namada::proto::{self, Tx}; -use namada::types::address::{masp, masp_tx_key, Address}; +use namada::types::address::Address; use namada::types::chain::ChainId; use namada::types::internal::WrapperTxInQueue; use namada::types::key::*; use namada::types::storage::{BlockHeight, Key, TxIndex}; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; -use namada::types::token::{self}; +use namada::types::token; #[cfg(not(feature = "mainnet"))] use namada::types::transaction::MIN_FEE; use namada::types::transaction::{ @@ -779,14 +779,9 @@ where return response; } - // Check balance for fee - let fee_payer = if wrapper.pk != masp_tx_key().ref_to() { - wrapper.fee_payer() - } else { - masp() - }; // check that the fee payer has sufficient balance - let balance = self.get_balance(&wrapper.fee.token, &fee_payer); + let balance = + self.get_balance(&wrapper.fee.token, &wrapper.fee_payer()); // In testnets with a faucet, tx is allowed to skip fees if // it includes a valid PoW @@ -1225,7 +1220,7 @@ mod test_utils { }, &keypair, Epoch(0), - 0.into(), + 1.into(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1443,8 +1438,7 @@ mod test_mempool_validate { /// transactions #[test] fn test_replay_attack() { - let (mut shell, _) = TestShell::new(); - + let (mut shell, _) = test_utils::setup(1); let keypair = super::test_utils::gen_keypair(); let tx = Tx::new( @@ -1461,7 +1455,7 @@ mod test_mempool_validate { }, &keypair, Epoch(0), - 0.into(), + 1.into(), tx, Default::default(), #[cfg(not(feature = "mainnet"))] diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 77aac7e32f..560952f90c 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -559,18 +559,9 @@ where .write(&wrapper_hash_key, vec![]) .expect("Couldn't write wrapper tx hash to write log"); - // If the public key corresponds to the MASP sentinel - // transaction key, then the fee payer is effectively - // the MASP, otherwise derive - // they payer from public key. - let fee_payer = if wrapper.pk != masp_tx_key().ref_to() { - wrapper.fee_payer() - } else { - masp() - }; - // check that the fee payer has sufficient balance - let balance = - self.get_balance(&wrapper.fee.token, &fee_payer); + // check that the fee payer has sufficient balance + let balance = + self.get_balance(&wrapper.fee.token, &wrapper.fee_payer()); // In testnets, tx is allowed to skip fees if it // includes a valid PoW diff --git a/benches/mod.rs b/benches/mod.rs index ec9655fad8..21f8cf8854 100644 --- a/benches/mod.rs +++ b/benches/mod.rs @@ -590,12 +590,7 @@ impl BenchShieldedCtx { &[], )); let shielded = async_runtime - .block_on(gen_shielded_transfer( - &mut self.ctx, - &self.shell, - &args, - false, - )) + .block_on(gen_shielded_transfer(&mut self.ctx, &self.shell, &args)) .unwrap() .map(|x| x.0); diff --git a/core/src/types/address.rs b/core/src/types/address.rs index 20e3e6f372..c7530e8236 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -575,17 +575,6 @@ pub fn masp() -> Address { Address::decode("atest1v4ehgw36xaryysfsx5unvve4g5my2vjz89p52sjxxgenzd348yuyyv3hg3pnjs35g5unvde4ca36y5").expect("The token address decoding shouldn't fail") } -/// Sentinel secret key to indicate a MASP source -pub fn masp_tx_key() -> crate::types::key::common::SecretKey { - use crate::types::key::common; - let bytes = [ - 0, 27, 238, 157, 32, 131, 242, 184, 142, 146, 189, 24, 249, 68, 165, - 205, 71, 213, 158, 25, 253, 52, 217, 87, 52, 171, 225, 110, 131, 238, - 58, 94, 56, - ]; - common::SecretKey::try_from_slice(bytes.as_ref()).unwrap() -} - /// Temporary helper for testing, a hash map of tokens addresses with their /// MASP XAN incentive schedules. If the reward is (a, b) then a rewarded tokens /// are dispensed for every b possessed tokens. From 0b906ef744f050e3d2fc7dee7fc0dcf041b8bceb Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 23 Mar 2023 17:05:59 +0100 Subject: [PATCH 05/23] Fixes gas for unit tests Date: Thu Mar 23 17:05:59 2023 +0100 --- apps/src/lib/cli.rs | 2 +- .../lib/node/ledger/shell/finalize_block.rs | 18 +++-- apps/src/lib/node/ledger/shell/mod.rs | 35 +++++----- .../lib/node/ledger/shell/prepare_proposal.rs | 16 +++-- .../lib/node/ledger/shell/process_proposal.rs | 65 ++++++++++++------- benches/Cargo.toml | 1 - benches/native_vps.rs | 17 +++-- benches/txs.rs | 16 +++-- core/src/ledger/storage/wl_storage.rs | 2 +- core/src/types/transaction/wrapper.rs | 15 ----- shared/src/ledger/ibc/vp/mod.rs | 11 ++-- shared/src/ledger/protocol/mod.rs | 3 + wasm/gas.json | 5 +- 13 files changed, 111 insertions(+), 95 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 458e963c7e..a249acd1da 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -3067,7 +3067,7 @@ pub mod args { address joined with a number.", )) .arg(FEE_AMOUNT.def().about( - "The amount being paid for the inclusion of this transaction", + "The amount being paid, per gas unit, for the inclusion of this transaction", )) .arg(FEE_TOKEN.def().about("The token for paying the gas")) .arg( diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 1dfcfd68a6..8bf5e8b19a 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -82,7 +82,9 @@ where {new_epoch}." ); let gas_table: BTreeMap = self - .read_storage_key(¶meters::storage::get_gas_table_storage_key()) + .wl_storage + .read(¶meters::storage::get_gas_table_storage_key()) + .expect("Error while reading from storage") .expect("Missing gas table in storage"); if new_epoch { @@ -197,11 +199,13 @@ where .storage .delete(&tx_hash_key) .expect("Error while deleting tx hash from storage"); - } else if let TxType::Wrapper(wrapper) = &tx_type { - // Charge fee if needed - if ErrorCodes::from_u32(processed_tx.result.code) - .unwrap() - .charges_fee() + } + + #[cfg(not(feature = "abcipp"))] + if let TxType::Wrapper(wrapper) = &tx_type { + // Charge fee if wrapper transaction went out of gas + if ErrorCodes::from_u32(processed_tx.result.code).unwrap() + == ErrorCodes::TxGasLimit { #[cfg(not(feature = "mainnet"))] let has_valid_pow = @@ -213,6 +217,7 @@ where ); } } + continue; } @@ -1833,7 +1838,6 @@ mod test_finalize_block { assert_eq!(code, String::from(ErrorCodes::WasmRuntimeError).as_str()); assert!(!shell - .shell .wl_storage .has_key(&inner_hash_key) .expect("Test failed")) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 8fea607d4d..6bf39c1505 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -166,13 +166,6 @@ impl ErrorCodes { } } -impl ErrorCodes { - /// Whether to charge fees depending on the exit code of a transaction - pub fn charges_fee(&self) -> bool { - matches!(self, Self::Ok | Self::WasmRuntimeError | Self::TxGasLimit) - } -} - impl From for u32 { fn from(code: ErrorCodes) -> u32 { code.to_u32().unwrap() @@ -728,7 +721,9 @@ where // Max block gas let block_gas_limit: u64 = self - .read_storage_key(¶meters::storage::get_max_block_gas_key()) + .wl_storage + .read(¶meters::storage::get_max_block_gas_key()) + .expect("Error while reading from storage") .expect("Missing max_block_gas parameter in storage"); let mut block_gas_meter = BlockGasMeter::new(block_gas_limit); if let Err(_) = block_gas_meter.finalize_transaction(gas_meter) { @@ -812,18 +807,18 @@ where #[allow(dead_code)] /// Simulate validation and application of a transaction. fn dry_run_tx(&self, tx_bytes: &[u8]) -> response::Query { - //FIXME: should we simulate also the wrapper part for gas? let mut response = response::Query::default(); let gas_table: BTreeMap = self - .read_storage_key(¶meters::storage::get_gas_table_storage_key()) + .wl_storage + .read(¶meters::storage::get_gas_table_storage_key()) + .expect("Error while reading from storage") .expect("Missing gas table in storage"); - let mut gas_meter = - TxGasMeter::new( - self.read_storage_key( - ¶meters::storage::get_max_block_gas_key(), - ) - .expect("Missing parameter in storage"), - ); + let mut gas_meter = TxGasMeter::new( + self.wl_storage + .read(¶meters::storage::get_max_block_gas_key()) + .expect("Error while reading from storage") + .expect("Missing max_block_gas parameter in storage"), + ); let mut write_log = WriteLog::default(); let mut vp_wasm_cache = self.vp_wasm_cache.read_only(); let mut tx_wasm_cache = self.tx_wasm_cache.read_only(); @@ -1155,6 +1150,8 @@ mod test_utils { }, num_validators, ); + test.commit(); + (test, receiver) } @@ -1603,7 +1600,9 @@ mod test_mempool_validate { let (shell, _) = test_utils::setup(1); let block_gas_limit: u64 = shell - .read_storage_key(¶meters::storage::get_max_block_gas_key()) + .wl_storage + .read(¶meters::storage::get_max_block_gas_key()) + .expect("Error while reading from storage") .expect("Missing max_block_gas parameter in storage"); let keypair = super::test_utils::gen_keypair(); diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 77b64c0750..ca6b37f3e7 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -6,6 +6,7 @@ use namada::core::ledger::parameters; use namada::ledger::gas::BlockGasMeter; use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::proof_of_stake::pos_queries::PosQueries; +use namada::ledger::storage_api::StorageRead; use namada::proto::Tx; use namada::types::internal::WrapperTxInQueue; use namada::types::time::DateTimeUtc; @@ -131,12 +132,11 @@ where // valid because of mempool check TryInto::::try_into(block_time).ok() }); - let mut temp_block_gas_meter = - BlockGasMeter::new( - self.read_storage_key( - ¶meters::storage::get_max_block_gas_key(), - ) - .expect("Missing max_block_gas parameter in storage"), + let mut temp_block_gas_meter = BlockGasMeter::new( + self.wl_storage + .read(¶meters::storage::get_max_block_gas_key()) + .expect("Error while reading from storage") + .expect("Missing max_block_gas parameter in storage"), ); let txs = txs @@ -488,7 +488,9 @@ mod test_prepare_proposal { let (shell, _) = test_utils::setup(1); let block_gas_limit: u64 = shell - .read_storage_key(¶meters::storage::get_max_block_gas_key()) + .wl_storage + .read(¶meters::storage::get_max_block_gas_key()) + .expect("Error while reading from storage") .expect("Missing max_block_gas parameter in storage"); let keypair = gen_keypair(); diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 560952f90c..0d66ea4a52 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -142,15 +142,16 @@ where let mut tx_queue_iter = self.wl_storage.storage.tx_queue.iter(); let mut temp_wl_storage = TempWlStorage::new(&self.wl_storage.storage); let mut metadata = ValidationMeta::from(&self.wl_storage); - let mut temp_block_gas_meter = - BlockGasMeter::new( - self.read_storage_key( - ¶meters::storage::get_max_block_gas_key(), - ) + let mut temp_block_gas_meter = BlockGasMeter::new( + self.wl_storage + .read(¶meters::storage::get_max_block_gas_key()) + .expect("Error while reading from storage") .expect("Missing max_block_gas parameter in storage"), - ); + ); let gas_table: BTreeMap = self - .read_storage_key(¶meters::storage::get_gas_table_storage_key()) + .wl_storage + .read(¶meters::storage::get_gas_table_storage_key()) + .expect("Error while reading from storage") .expect("Missing gas table in storage"); let mut wrapper_index = 0; @@ -373,13 +374,21 @@ where }; let tx_gas = match gas_table.get(&tx_hash.to_string().to_ascii_lowercase()) { Some(gas) => gas.to_owned(), - #[cfg(any(test, feature = "testing"))] - None => 1000, - #[cfg(not(any(test, feature = "testing")))] - None => return TxResult { - // Tx is not whitelisted - code: ErrorCodes::Undecryptable.into(), - info: "Tx is not whitelisted".to_string() + #[cfg(any(test, feature = "testing"))] + None => 1_000, + #[cfg(not(any( + test, + feature = "testing" + )))] + None => { + return TxResult { + // Tx is not whitelisted + code: + ErrorCodes::DecryptedTxGasLimit + .into(), + info: "Tx is not whitelisted" + .to_string(), + }; } }; let inner_tx_gas_limit = temp_wl_storage.storage.tx_queue.get(tx_index).map_or(0, |wrapper| wrapper.gas); @@ -416,12 +425,17 @@ where }, }} TxType::Wrapper(wrapper) => { - // Account for gas. This is done even if the transaction is later deemed invalid, to incentivize the proposer to - // include only valid transaction and avoid wasting block gas limit - let mut tx_gas_meter = TxGasMeter::new(u64::from(&wrapper.gas_limit)); - if let Err(_) = tx_gas_meter.add_tx_size_gas(tx_bytes.len()) { - // Add the declared tx gas limit to the block gas meter even in case of an error - let _ = temp_block_gas_meter.finalize_transaction(tx_gas_meter); + // Account for gas. This is done even if the transaction is + // later deemed invalid, to incentivize the proposer to + // include only valid transaction and avoid wasting block + // gas limit (ABCI) + let mut tx_gas_meter = + TxGasMeter::new(u64::from(&wrapper.gas_limit)); + if tx_gas_meter.add_tx_size_gas(tx_bytes.len()).is_err() { + // Add the declared tx gas limit to the block gas meter + // even in case of an error + let _ = temp_block_gas_meter + .finalize_transaction(tx_gas_meter); return TxResult { code: ErrorCodes::TxGasLimit.into(), @@ -612,6 +626,7 @@ where mod test_process_proposal { use borsh::BorshDeserialize; use namada::ledger::parameters::storage::get_wrapper_tx_fees_key; + use namada::ledger::storage_api::StorageWrite; use namada::proto::SignedTxData; use namada::types::hash::Hash; use namada::types::key::*; @@ -791,8 +806,7 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; ); shell .wl_storage - .storage - .write(&balance_key, Amount::whole(99).try_to_vec().unwrap()) + .write(&balance_key, Amount::whole(99)) .unwrap(); let tx = Tx::new( @@ -851,8 +865,7 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; ); shell .wl_storage - .write_log - .write(&balance_key, Amount::whole(99).try_to_vec().unwrap()) + .write(&balance_key, Amount::whole(99)) .unwrap(); shell .wl_storage @@ -1785,7 +1798,9 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; let (mut shell, _) = test_utils::setup(1); let block_gas_limit: u64 = shell - .read_storage_key(¶meters::storage::get_max_block_gas_key()) + .wl_storage + .read(¶meters::storage::get_max_block_gas_key()) + .expect("Error while reading from storage") .expect("Missing max_block_gas parameter in storage"); let keypair = super::test_utils::gen_keypair(); diff --git a/benches/Cargo.toml b/benches/Cargo.toml index c61e367947..8d03356a4b 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -44,7 +44,6 @@ masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6af namada = {path = "../shared" } namada_apps = {path = "../apps", features = ["dev"]} namada_test_utils = {path = "../test_utils"} -namada_tx_prelude = {path = "../tx_prelude", default-features = false} prost = "0.9.0" rand = "0.8" rand_core = "0.6" diff --git a/benches/native_vps.rs b/benches/native_vps.rs index d44a1edcea..0085e484d1 100644 --- a/benches/native_vps.rs +++ b/benches/native_vps.rs @@ -21,6 +21,7 @@ use namada::ledger::ibc::vp::{Ibc, IbcToken}; use namada::ledger::native_vp::replay_protection::ReplayProtectionVp; use namada::ledger::native_vp::slash_fund::SlashFundVp; use namada::ledger::native_vp::{Ctx, NativeVp}; +use namada::ledger::storage_api::StorageRead; use namada::proto::Tx; use namada::types::address::InternalAddress; use namada::types::chain::ChainId; @@ -132,10 +133,18 @@ fn governance(c: &mut Criterion) { governance::storage::get_max_proposal_code_size_key(); let max_proposal_content_key = governance::storage::get_max_proposal_content_key(); - let max_code_size = - shell.read_storage_key(&max_code_size_key).unwrap(); - let max_proposal_content_size = - shell.read_storage_key(&max_proposal_content_key).unwrap(); + let max_code_size = shell + .wl_storage + .read(&max_code_size_key) + .expect("Error while reading from storage") + .expect("Missing max_code_size parameter in storage"); + let max_proposal_content_size = shell + .wl_storage + .read(&max_proposal_content_key) + .expect("Error while reading from storage") + .expect( + "Missing max_proposal_content parameter in storage", + ); generate_tx( TX_INIT_PROPOSAL_WASM, diff --git a/benches/txs.rs b/benches/txs.rs index 9e59af3e4a..71c1ab7438 100644 --- a/benches/txs.rs +++ b/benches/txs.rs @@ -14,6 +14,7 @@ use namada::types::masp::{TransferSource, TransferTarget}; use namada::types::transaction::governance::{ InitProposalData, ProposalType, VoteProposalData, }; +use namada::ledger::storage_api::StorageRead; use namada::types::transaction::pos::{Bond, CommissionChange, Withdraw}; use namada::types::transaction::EllipticCurve; use namada::types::transaction::{InitAccount, InitValidator, UpdateVp}; @@ -365,12 +366,15 @@ fn init_proposal(c: &mut Criterion) { governance::storage::get_max_proposal_code_size_key(); let max_proposal_content_key = governance::storage::get_max_proposal_content_key(); - let max_code_size = shell - .read_storage_key(&max_code_size_key) - .unwrap(); - let max_proposal_content_size = shell - .read_storage_key(&max_proposal_content_key) - .unwrap(); + let max_code_size = + shell.wl_storage + .read(&max_code_size_key) + .expect("Error while reading from storage") + .expect("Missing max_code_size parameter in storage"); + let max_proposal_content_size = shell.wl_storage + .read(&max_proposal_content_key) + .expect("Error while reading from storage") + .expect("Missing max_proposal_content parameter in storage"); generate_tx( TX_INIT_PROPOSAL_WASM, diff --git a/core/src/ledger/storage/wl_storage.rs b/core/src/ledger/storage/wl_storage.rs index 99df4534e4..588bea9c88 100644 --- a/core/src/ledger/storage/wl_storage.rs +++ b/core/src/ledger/storage/wl_storage.rs @@ -507,7 +507,7 @@ mod tests { })] #[test] fn test_prefix_iters( - key_vals in arb_key_vals(50), + key_vals in arb_key_vals(30), ) { test_prefix_iters_aux(key_vals) } diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index 8eda482e8b..8b74acf05f 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -124,14 +124,6 @@ pub mod wrapper_tx { } } - /// Round the input number up to the next highest multiple - /// of GAS_LIMIT_RESOLUTION - impl From for GasLimit { - fn from(amount: Amount) -> GasLimit { - GasLimit::from(u64::from(amount)) - } - } - /// Get back the gas limit as a raw number impl From<&GasLimit> for u64 { fn from(limit: &GasLimit) -> u64 { @@ -146,13 +138,6 @@ pub mod wrapper_tx { } } - /// Get back the gas limit as a raw number, viewed as an Amount - impl From for Amount { - fn from(limit: GasLimit) -> Amount { - Amount::from(limit.multiplier * GAS_LIMIT_RESOLUTION) - } - } - impl FromStr for GasLimit { type Err = ParseIntError; diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 243e0835ca..a0c2595dd7 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -745,9 +745,6 @@ mod tests { #[test] fn test_create_client_fail() { let mut wl_storage = TestWlStorage::default(); - let gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); - let (vp_wasm_cache, _vp_cache_dir) = - wasm::compilation_cache::common::testing::cache(); let mut keys_changed = BTreeSet::new(); @@ -934,7 +931,7 @@ mod tests { None, ) .sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); + let gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1259,7 +1256,7 @@ mod tests { None, ) .sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); + let gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2028,7 +2025,7 @@ mod tests { None, ) .sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); + let gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2201,7 +2198,7 @@ mod tests { None, ) .sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); + let gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 842718389c..586730fe08 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -551,6 +551,9 @@ fn add_precomputed_gas( ) -> Result<()> { let vp_gas_required = match gas_table.get(vp) { Some(gas) => gas.to_owned(), + #[cfg(any(test, feature = "testing"))] + None => 1_000, + #[cfg(not(any(test, feature = "testing")))] None => return Err(Error::MissingGasCost(vp.to_owned())), }; diff --git a/wasm/gas.json b/wasm/gas.json index e01e679acf..d6b28b3456 100644 --- a/wasm/gas.json +++ b/wasm/gas.json @@ -16,6 +16,5 @@ "vp_testnet_faucet.wasm": 0, "vp_token.wasm": 1, "vp_user.wasm": 1, - "vp_validator.wasm": 1, - "single_signature": 1 -} + "vp_validator.wasm": 1 +} \ No newline at end of file From 964d224d53516308f95235f905903b8502f1abba Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 24 Mar 2023 18:50:17 +0100 Subject: [PATCH 06/23] Corrects gas in e2e tests Date: Fri Mar 24 18:50:17 2023 +0100 --- apps/src/lib/config/genesis.rs | 21 +- .../lib/node/ledger/shell/process_proposal.rs | 22 +- apps/src/lib/wasm_loader/mod.rs | 20 -- benches/mod.rs | 1 + benches/txs.rs | 1 + core/src/ledger/gas.rs | 4 +- core/src/ledger/parameters/mod.rs | 4 + core/src/ledger/storage/write_log.rs | 18 +- genesis/e2e-tests-single-node.toml | 2 +- shared/src/ledger/protocol/mod.rs | 14 +- tests/src/e2e/ibc_tests.rs | 14 +- tests/src/e2e/ledger_tests.rs | 190 +++++++----------- tests/src/e2e/multitoken_tests/helpers.rs | 8 +- tests/src/e2e/setup.rs | 9 +- wasm/checksums.py | 10 +- wasm/gas_checksums.json | 20 ++ 16 files changed, 173 insertions(+), 185 deletions(-) create mode 100644 wasm/gas_checksums.json diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index b61e93086d..1dfe36b4de 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -273,7 +273,7 @@ pub mod genesis_config { /// Fix wrapper tx fees pub wrapper_tx_fees: Option, /// Gas table - pub gas_table: BTreeMap, + pub gas_table: Option>, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -597,6 +597,7 @@ pub mod genesis_config { let min_duration: i64 = 60 * 60 * 24 * 365 / (parameters.epochs_per_year as i64); + let parameters = Parameters { epoch_duration: EpochDuration { min_num_of_blocks: parameters.min_num_of_blocks, @@ -622,9 +623,25 @@ pub mod genesis_config { staked_ratio: Decimal::ZERO, pos_inflation_amount: 0, wrapper_tx_fees: parameters.wrapper_tx_fees, - gas_table: parameters.gas_table, + gas_table: parameters.gas_table.unwrap_or_default(), }; + // Check validity of gas table + if parameters.gas_table.len() + != parameters.tx_whitelist.len() + parameters.vp_whitelist.len() + { + panic!("Mismatching length of gas table and txs/vps whitelists"); + } + for hash in parameters + .tx_whitelist + .iter() + .chain(parameters.vp_whitelist.iter()) + { + if !parameters.gas_table.contains_key(&hash.to_lowercase()) { + panic!("Missing gas cost for hash {}", hash); + } + } + let GovernanceParamsConfig { min_proposal_fund, max_proposal_code_size, diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 0d66ea4a52..8f9a6f053c 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -365,7 +365,7 @@ where match Hash::try_from(tx.code_or_hash.as_slice()) { Ok(hash) => hash, Err(_) => return TxResult { - code: ErrorCodes::Undecryptable.into(), + code: ErrorCodes::DecryptedTxGasLimit.into(), info: format!("Failed conversion of transaction's hash") } } @@ -374,22 +374,12 @@ where }; let tx_gas = match gas_table.get(&tx_hash.to_string().to_ascii_lowercase()) { Some(gas) => gas.to_owned(), - #[cfg(any(test, feature = "testing"))] + #[cfg(test)] None => 1_000, - #[cfg(not(any( - test, - feature = "testing" - )))] - None => { - return TxResult { - // Tx is not whitelisted - code: - ErrorCodes::DecryptedTxGasLimit - .into(), - info: "Tx is not whitelisted" - .to_string(), - }; - } + #[cfg(not( + test + ))] + None => 0, // VPs will rejected the non-whitelisted tx }; let inner_tx_gas_limit = temp_wl_storage.storage.tx_queue.get(tx_index).map_or(0, |wrapper| wrapper.gas); let mut tx_gas_meter = TxGasMeter::new(inner_tx_gas_limit); diff --git a/apps/src/lib/wasm_loader/mod.rs b/apps/src/lib/wasm_loader/mod.rs index 4eadf9d565..9a075fbcf8 100644 --- a/apps/src/lib/wasm_loader/mod.rs +++ b/apps/src/lib/wasm_loader/mod.rs @@ -326,23 +326,3 @@ async fn download_wasm(url: String) -> Result, Error> { Err(e) => Err(Error::Download(url, e)), } } - -/// Read the json file containing the gas costs for the whitelisted vps and txsi from -/// the default "gas.json" file in the given directory -pub fn read_gas_file(wasm_directory: impl AsRef) -> HashMap { - let gas_path = wasm_directory.as_ref().join("gas.json"); - - match fs::File::open(&gas_path) { - Ok(file) => match serde_json::from_reader(file) { - Ok(result) => result, - Err(_) => { - eprintln!("Can't read gas from {}", gas_path.to_string_lossy()); - safe_exit(1); - } - }, - Err(_) => { - eprintln!("Can't find gas at {}", gas_path.to_string_lossy()); - safe_exit(1); - } - } -} diff --git a/benches/mod.rs b/benches/mod.rs index 21f8cf8854..eb33bbfc33 100644 --- a/benches/mod.rs +++ b/benches/mod.rs @@ -195,6 +195,7 @@ impl BenchShell { bench_shell.wl_storage.commit_tx(); bench_shell.commit(); + // Advance epoch for pos benches for _ in 0..=12 { bench_shell.advance_epoch(); } diff --git a/benches/txs.rs b/benches/txs.rs index 71c1ab7438..1d52af4073 100644 --- a/benches/txs.rs +++ b/benches/txs.rs @@ -245,6 +245,7 @@ fn withdraw(c: &mut Criterion) { }; shell.execute_tx(&unbond_tx); + shell.wl_storage.commit_tx(); // Advance Epoch for pipeline and unbonding length let params = diff --git a/core/src/ledger/gas.rs b/core/src/ledger/gas.rs index 746260e93f..f4414651b4 100644 --- a/core/src/ledger/gas.rs +++ b/core/src/ledger/gas.rs @@ -22,9 +22,11 @@ const PARALLEL_GAS_DIVIDER: u64 = 10; /// The minimum gas cost for accessing the storage pub const MIN_STORAGE_GAS: u64 = 1; /// The gas cost for verifying the signature of a transaction -pub const VERIFY_TX_SIG_GAS_COST: u64 = 1000; +pub const VERIFY_TX_SIG_GAS_COST: u64 = 1_000; /// The gas cost for validating wasm vp code pub const WASM_VALIDATION_GAS_PER_BYTE: u64 = 1; +/// The cost for writing a byte to storage +pub const STORAGE_WRITE_GAS_PER_BYTE: u64 = 100; /// Gas module result for functions that may fail pub type Result = std::result::Result; diff --git a/core/src/ledger/parameters/mod.rs b/core/src/ledger/parameters/mod.rs index e256a6d8a4..d1bef59b9d 100644 --- a/core/src/ledger/parameters/mod.rs +++ b/core/src/ledger/parameters/mod.rs @@ -147,6 +147,10 @@ impl Parameters { // write gas table let gas_table_key = storage::get_gas_table_storage_key(); + let gas_table = gas_table + .iter() + .map(|(k, v)| (k.to_lowercase(), *v)) + .collect::>(); storage.write(&gas_table_key, gas_table)?; // write vp whitelist parameter diff --git a/core/src/ledger/storage/write_log.rs b/core/src/ledger/storage/write_log.rs index bf4881aab0..861f83bbe8 100644 --- a/core/src/ledger/storage/write_log.rs +++ b/core/src/ledger/storage/write_log.rs @@ -7,6 +7,7 @@ use itertools::Itertools; use thiserror::Error; use crate::ledger; +use crate::ledger::gas::STORAGE_WRITE_GAS_PER_BYTE; use crate::ledger::storage::{Storage, StorageHasher}; use crate::types::address::{Address, EstablishedAddressGen}; use crate::types::hash::Hash; @@ -191,7 +192,7 @@ impl WriteLog { // the previous value exists on the storage None => len as i64, }; - Ok((gas as _, size_diff)) + Ok((gas as u64 * STORAGE_WRITE_GAS_PER_BYTE, size_diff)) } /// Write a key and a value. @@ -557,7 +558,10 @@ mod tests { // insert a value let inserted = "inserted".as_bytes().to_vec(); let (gas, diff) = write_log.write(&key, inserted.clone()).unwrap(); - assert_eq!(gas, (key.len() + inserted.len()) as u64); + assert_eq!( + gas, + (key.len() + inserted.len()) as u64 * STORAGE_WRITE_GAS_PER_BYTE + ); assert_eq!(diff, inserted.len() as i64); // read the value @@ -573,7 +577,10 @@ mod tests { // update the value let updated = "updated".as_bytes().to_vec(); let (gas, diff) = write_log.write(&key, updated.clone()).unwrap(); - assert_eq!(gas, (key.len() + updated.len()) as u64); + assert_eq!( + gas, + (key.len() + updated.len()) as u64 * STORAGE_WRITE_GAS_PER_BYTE + ); assert_eq!(diff, updated.len() as i64 - inserted.len() as i64); // delete the key @@ -597,7 +604,10 @@ mod tests { // insert again let reinserted = "reinserted".as_bytes().to_vec(); let (gas, diff) = write_log.write(&key, reinserted.clone()).unwrap(); - assert_eq!(gas, (key.len() + reinserted.len()) as u64); + assert_eq!( + gas, + (key.len() + reinserted.len()) as u64 * STORAGE_WRITE_GAS_PER_BYTE + ); assert_eq!(diff, reinserted.len() as i64); } diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 76e0a8abd0..ff4a2e53e1 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -156,7 +156,7 @@ max_expected_time_per_block = 30 # Max payload size, in bytes, for a tx batch proposal. max_proposal_bytes = 22020096 # Max amount of gas per block FIXME: adjust value -max_block_gas = 1000000 +max_block_gas = 1000000000 # Gas table gas_table = {} # vp whitelist diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 586730fe08..94c7b97f5f 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -68,8 +68,6 @@ pub enum Error { ), #[error("Access to an internal address {0} is forbidden")] AccessForbidden(InternalAddress), - #[error("The gas cost for the tx/vp {0} was not found in storage")] - MissingGasCost(String), #[error("Error while converting the transaction code's hash")] TxCodeHashConversion, #[error("Could not retrieve wasm code from storage for hash {0}")] @@ -198,10 +196,10 @@ where let tx_hash = tx_hash.to_string().to_ascii_lowercase(); let tx_gas_required = match gas_table.get(tx_hash.as_str()) { Some(gas) => gas.to_owned(), - #[cfg(any(test, feature = "testing"))] + #[cfg(test)] None => 1_000, - #[cfg(not(any(test, feature = "testing")))] - None => return Err(Error::MissingGasCost(tx_hash)), + #[cfg(not(test))] + None => 0, // VPs will reject the non-whitelisted tx }; tx_gas_meter.add(tx_gas_required).map_err(Error::GasError)?; @@ -551,10 +549,10 @@ fn add_precomputed_gas( ) -> Result<()> { let vp_gas_required = match gas_table.get(vp) { Some(gas) => gas.to_owned(), - #[cfg(any(test, feature = "testing"))] + #[cfg(test)] None => 1_000, - #[cfg(not(any(test, feature = "testing")))] - None => return Err(Error::MissingGasCost(vp.to_owned())), + #[cfg(not(test))] + None => 0, }; gas_meter.add(vp_gas_required).map_err(Error::GasError) diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index ea6261aed1..a573017cd1 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -765,11 +765,11 @@ fn transfer_received_token( &sub_prefix, "--amount", "50000", - "--gas-amount", + "--fee-amount", "0", "--gas-limit", - "0", - "--gas-token", + "20", + "--fee-token", NAM, "--node", &rpc, @@ -982,11 +982,11 @@ fn submit_ibc_tx( &data_path, "--signer", signer, - "--gas-amount", + "--fee-amount", "0", "--gas-limit", - "0", - "--gas-token", + "20", + "--fee-token", NAM, "--node", &rpc @@ -1030,6 +1030,8 @@ fn transfer( &channel_id, "--port-id", &port_id, + "--gas-limit", + "20", "--node", &rpc, ]; diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index a8f9adfb48..6cac4041a5 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -32,7 +32,6 @@ use serde_json::json; use setup::constants::*; use super::helpers::{get_height, is_debug_mode, wait_for_block_height}; -use super::setup::get_all_wasms_hashes; use crate::e2e::helpers::{ epoch_sleep, find_address, find_bonded_stake, get_actor_rpc, get_epoch, }; @@ -113,12 +112,8 @@ fn test_node_connectivity_and_consensus() -> Result<()> { NAM, "--amount", "10.1", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "20", "--node", &validator_one_rpc, ]; @@ -388,12 +383,8 @@ fn ledger_txs_and_queries() -> Result<()> { NAM, "--amount", "10.1", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "2", "--node", &validator_one_rpc, ], @@ -408,12 +399,8 @@ fn ledger_txs_and_queries() -> Result<()> { NAM, "--amount", "10.1", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "2", "--node", &validator_one_rpc, ], @@ -425,12 +412,8 @@ fn ledger_txs_and_queries() -> Result<()> { BERTHA, "--code-path", VP_USER_WASM, - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "30", "--node", &validator_one_rpc, ], @@ -443,12 +426,8 @@ fn ledger_txs_and_queries() -> Result<()> { TX_TRANSFER_WASM, "--data-path", &tx_data_path, - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "2", "--node", &validator_one_rpc ], @@ -464,12 +443,8 @@ fn ledger_txs_and_queries() -> Result<()> { VP_USER_WASM, "--alias", "Test-Account", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "2", "--node", &validator_one_rpc, ], @@ -488,6 +463,8 @@ fn ledger_txs_and_queries() -> Result<()> { // Faucet withdrawal requires an explicit signer "--signer", ALBERT, + "--gas-limit", + "2", "--node", &validator_one_rpc, ], @@ -625,6 +602,8 @@ fn masp_txs_and_queries() -> Result<()> { BTC, "--amount", "10", + "--gas-limit", + "2", "--node", &validator_one_rpc, ], @@ -642,6 +621,8 @@ fn masp_txs_and_queries() -> Result<()> { BTC, "--amount", "15", + "--gas-limit", + "2", "--node", &validator_one_rpc, ], @@ -659,6 +640,8 @@ fn masp_txs_and_queries() -> Result<()> { BTC, "--amount", "20", + "--gas-limit", + "2", "--node", &validator_one_rpc, ], @@ -678,6 +661,8 @@ fn masp_txs_and_queries() -> Result<()> { "10", "--signer", ALBERT, + "--gas-limit", + "2", "--node", &validator_one_rpc, ], @@ -697,6 +682,8 @@ fn masp_txs_and_queries() -> Result<()> { "7", "--signer", ALBERT, + "--gas-limit", + "2", "--node", &validator_one_rpc, ], @@ -716,6 +703,8 @@ fn masp_txs_and_queries() -> Result<()> { "7", "--signer", ALBERT, + "--gas-limit", + "2", "--node", &validator_one_rpc, ], @@ -735,6 +724,8 @@ fn masp_txs_and_queries() -> Result<()> { "7", "--signer", ALBERT, + "--gas-limit", + "2", "--node", &validator_one_rpc, ], @@ -754,6 +745,8 @@ fn masp_txs_and_queries() -> Result<()> { "6", "--signer", ALBERT, + "--gas-limit", + "2", "--node", &validator_one_rpc, ], @@ -810,6 +803,8 @@ fn masp_txs_and_queries() -> Result<()> { "20", "--signer", BERTHA, + "--gas-limit", + "2", "--node", &validator_one_rpc, ], @@ -1757,12 +1752,8 @@ fn invalid_transactions() -> Result<()> { NAM, "--amount", "1", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "2", "--node", &validator_one_rpc, ]; @@ -1771,7 +1762,7 @@ fn invalid_transactions() -> Result<()> { client.exp_string("Transaction accepted")?; client.exp_string("Transaction applied")?; client.exp_string("Transaction is invalid")?; - client.exp_string(r#""code": "4"#)?; + client.exp_string(r#""code": "5"#)?; client.assert_success(); let mut ledger = bg_ledger.foreground(); @@ -1812,12 +1803,8 @@ fn invalid_transactions() -> Result<()> { BERTHA, "--amount", "1_000_000.1", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "1", // Force to ignore client check that fails on the balance check of the // source address "--force", @@ -1831,7 +1818,7 @@ fn invalid_transactions() -> Result<()> { client.exp_string("Error trying to apply a transaction")?; - client.exp_string(r#""code": "3"#)?; + client.exp_string(r#""code": "4"#)?; client.assert_success(); Ok(()) @@ -2238,16 +2225,29 @@ fn test_bond_queries() -> Result<()> { "bond", "--validator", validator_alias, + "--amount", + "100", + "--gas-limit", + "20", + "--ledger-address", + &validator_one_rpc, + ]; + let mut client = + run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 3. Submit a delegation to the genesis validator + let tx_args = vec![ + "bond", + "--validator", + "validator-0", "--source", BERTHA, "--amount", "200", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "20", "--ledger-address", &validator_one_rpc, ]; @@ -2277,12 +2277,8 @@ fn test_bond_queries() -> Result<()> { BERTHA, "--amount", "300", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "20", "--ledger-address", &validator_one_rpc, ]; @@ -2299,12 +2295,8 @@ fn test_bond_queries() -> Result<()> { BERTHA, "--amount", "412", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "20", "--ledger-address", &validator_one_rpc, ]; @@ -2394,12 +2386,8 @@ fn pos_init_validator() -> Result<()> { "--source", BERTHA, "--unsafe-dont-encrypt", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "20", "--commission-rate", "0.05", "--max-commission-rate-change", @@ -2423,12 +2411,8 @@ fn pos_init_validator() -> Result<()> { NAM, "--amount", "0.5", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "20", "--node", &validator_one_rpc, ]; @@ -2444,12 +2428,8 @@ fn pos_init_validator() -> Result<()> { BERTHA, "--amount", "1000.5", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "20", "--node", &validator_one_rpc, ]; @@ -2468,12 +2448,8 @@ fn pos_init_validator() -> Result<()> { NAM, "--amount", "10999.5", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "20", "--node", &validator_one_rpc, ]; @@ -2488,12 +2464,8 @@ fn pos_init_validator() -> Result<()> { new_validator, "--amount", "10000", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "20", "--node", &validator_one_rpc, ]; @@ -2562,12 +2534,8 @@ fn ledger_many_txs_in_a_block() -> Result<()> { NAM, "--amount", "1.01", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "20", "--node", ]); @@ -2619,8 +2587,6 @@ fn ledger_many_txs_in_a_block() -> Result<()> { /// 13. Check governance address funds are 0 #[test] fn proposal_submission() -> Result<()> { - let working_dir = setup::working_dir(); - let test = setup::network( |genesis| { let parameters = ParametersConfig { @@ -2628,16 +2594,6 @@ fn proposal_submission() -> Result<()> { max_proposal_bytes: Default::default(), min_num_of_blocks: 4, max_expected_time_per_block: 1, - vp_whitelist: Some(get_all_wasms_hashes( - &working_dir, - Some("vp_"), - )), - // Enable tx whitelist to test the execution of a - // non-whitelisted tx by governance - tx_whitelist: Some(get_all_wasms_hashes( - &working_dir, - Some("tx_"), - )), ..genesis.parameters }; @@ -2674,12 +2630,8 @@ fn proposal_submission() -> Result<()> { BERTHA, "--amount", "900", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "20", "--node", &validator_one_rpc, ]; @@ -2706,6 +2658,8 @@ fn proposal_submission() -> Result<()> { "--data-path", valid_proposal_json_path.to_str().unwrap(), "--node", + "--gas-limit", + "20", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; @@ -2807,6 +2761,8 @@ fn proposal_submission() -> Result<()> { "--data-path", invalid_proposal_json_path.to_str().unwrap(), "--node", + "--gas-limit", + "20", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; @@ -2861,6 +2817,8 @@ fn proposal_submission() -> Result<()> { "--signer", "validator-0", "--node", + "--gas-limit", + "1", &validator_one_rpc, ]; @@ -2883,6 +2841,8 @@ fn proposal_submission() -> Result<()> { "--signer", BERTHA, "--node", + "--gas-limit", + "1", &validator_one_rpc, ]; @@ -2901,6 +2861,8 @@ fn proposal_submission() -> Result<()> { "--signer", ALBERT, "--node", + "--gas-limit", + "1", &validator_one_rpc, ]; @@ -3528,12 +3490,8 @@ fn proposal_offline() -> Result<()> { ALBERT, "--amount", "900", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "1", "--node", &validator_one_rpc, ]; @@ -3986,12 +3944,8 @@ fn test_genesis_validators() -> Result<()> { NAM, "--amount", "10.1", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "20", "--node", &validator_one_rpc, ]; @@ -4162,12 +4116,8 @@ fn double_signing_gets_slashed() -> Result<()> { NAM, "--amount", "10.1", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "20", "--node", &validator_one_rpc, ]; diff --git a/tests/src/e2e/multitoken_tests/helpers.rs b/tests/src/e2e/multitoken_tests/helpers.rs index 7008910b5e..4561424059 100644 --- a/tests/src/e2e/multitoken_tests/helpers.rs +++ b/tests/src/e2e/multitoken_tests/helpers.rs @@ -46,11 +46,11 @@ pub fn init_multitoken_vp(test: &Test, rpc_addr: &str) -> Result { &multitoken_vp_wasm_path, "--alias", multitoken_alias, - "--gas-amount", + "--fee-amount", "0", "--gas-limit", - "0", - "--gas-token", + "20", + "--fee-token", NAM, "--ledger-address", rpc_addr, @@ -153,6 +153,8 @@ pub fn attempt_red_tokens_transfer( signer, "--amount", &amount, + "--gas-limit", + "20", "--ledger-address", rpc_addr, ]; diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 8066910f81..1272189de0 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::ffi::OsStr; use std::fmt::Display; use std::fs::{File, OpenOptions}; @@ -127,6 +127,7 @@ pub fn network( Some(get_all_wasms_hashes(&working_dir, Some("vp_"))); genesis.parameters.tx_whitelist = Some(get_all_wasms_hashes(&working_dir, Some("tx_"))); + genesis.parameters.gas_table = Some(get_gas_checksums(&working_dir)); // Run the provided function on it let genesis = update_genesis(genesis); @@ -907,3 +908,9 @@ pub fn get_all_wasms_hashes( }) .collect() } + +pub fn get_gas_checksums(working_dir: &Path) -> BTreeMap { + let gas_checksums_path = working_dir.join("wasm/gas_checksums.json"); + serde_json::from_reader(fs::File::open(gas_checksums_path).unwrap()) + .unwrap() +} diff --git a/wasm/checksums.py b/wasm/checksums.py index 5657b5c0f1..96011c9de1 100644 --- a/wasm/checksums.py +++ b/wasm/checksums.py @@ -5,6 +5,7 @@ import sys gas = json.load(open("wasm/gas.json")) +gas_checksums = {} checksums = {} for wasm in sorted(glob.glob("wasm/*.wasm")): @@ -15,15 +16,17 @@ else os.path.splitext(basename)[0].split(".")[0] ) file_key = "{}.wasm".format(file_name) - checksums[file_key] = "{}.{}.wasm".format( - file_name, hashlib.sha256(open(wasm, "rb").read()).hexdigest() - ) + file_hash = hashlib.sha256(open(wasm, "rb").read()).hexdigest() + checksums[file_key] = "{}.{}.wasm".format(file_name, file_hash) # Check gas in whitelist if file_key not in gas: print("{} doesn't have an associated gas cost in gas.json".format(file_key)) sys.exit(1) + # Add gas to checksum gas + gas_checksums[file_hash] = gas[file_key] + os.rename(wasm, "wasm/{}".format(checksums[file_key])) # Prune unused gas entries if needed (in case of a tx/vp removal) @@ -32,6 +35,7 @@ del gas[k] json.dump(gas, open("wasm/gas.json", "w"), indent=4, sort_keys=True) +json.dump(gas_checksums, open("wasm/gas_checksums.json", "w"), indent=4, sort_keys=True) updated_wasms = list(checksums.values()) diff --git a/wasm/gas_checksums.json b/wasm/gas_checksums.json new file mode 100644 index 0000000000..3c61a1c577 --- /dev/null +++ b/wasm/gas_checksums.json @@ -0,0 +1,20 @@ +{ + "2b543ec54932a8a5a9f402913f857406dd1e52255d692515f70bc1c393bc85e2": 1, + "462af49196f257d271f75d613e063750c934df1133b4cf3eb44d46c4ce5be668": 1, + "50444d2a47159773cbbe01c26b9a54732de8bbab4133e61a3937f0a1b8723dd4": 0, + "5574a272dbac031e4e14109fabcd864ab3069a0d21ae3b782cce2d91893d1999": 1, + "69f92cb21b49b6b0a2fec8a3e6b24ceb4016b37fe064e913f6fcfe9eb719381b": 1, + "70175441c32efc3cdc0bf33790857ab0a56aabc3309dc39b0e8d5c331a7ad7fe": 1, + "7bc74c63fe03776ed26aa5b07eac4416ac36840f2dd39336c42ae1619babe911": 1, + "7f8357d4554450ba52dc5c7aa6a2ef3ce40cc7c92a6eba79bcd163715e96eee1": 1, + "885702764693e40408baa6fa82530d4b0693d63880187eef2fe8abfd27497e7b": 1, + "8faa70660152a6fc4998a845ac310f92c04cb89692c5ad1940f031ab7e935470": 1, + "94bc8a4bf27f12f8c0cd102ee1a63be0320f5e1d2f540c0dfd510c9324dc3842": 1, + "cd261fd4b5d67db72ace5eb281d6a9e58f1b00cde6c96cd0b0a8bcf5f38eaf41": 1, + "d014ccb0df538af3ffba99bcde354d2117a95fa6906aa96b1873ca29628e20c5": 1, + "d0b03343e585666300d7f01ada1cc51bb5a74fc25005b9b26daef213ca73e00f": 1, + "d2e59b592e11f4c25cb5035fdf69089c84a968702e6e325775c1a9bf23db7c89": 1, + "db724ffa2345002de0dea3b3dc219e98c6f27f9a980002e93f6cef2c892d34ca": 1, + "e208af69e7c52beb29ce53334015dc0ce84167d3d943667755bad7c6bc2f2535": 1, + "f22219a40cc0208ac13f4529ebbf75f55ef3113ae3384ccb16c7c1e646f86462": 1 +} \ No newline at end of file From 39493a5cd46c00cb052e7eb0497df6f44d6c1241 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 29 Mar 2023 18:31:41 +0200 Subject: [PATCH 07/23] Updates cargo lock for benchmarks Date: Wed Mar 29 18:31:41 2023 +0200 --- Cargo.lock | 181 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 178 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e8189c9780..49741e263c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,6 +67,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "ansi_term" version = "0.12.1" @@ -1100,6 +1106,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.0.73" @@ -1173,6 +1185,33 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" +[[package]] +name = "ciborium" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde 1.0.145", +] + +[[package]] +name = "ciborium-io" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" + +[[package]] +name = "ciborium-ll" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.3.0" @@ -1211,14 +1250,35 @@ dependencies = [ "bitflags", "indexmap", "lazy_static", - "os_str_bytes", + "os_str_bytes 2.4.0", "strsim", "termcolor", - "textwrap", + "textwrap 0.12.1", "unicode-width", "vec_map", ] +[[package]] +name = "clap" +version = "3.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +dependencies = [ + "bitflags", + "clap_lex", + "indexmap", + "textwrap 0.16.0", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes 6.5.0", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -1431,6 +1491,42 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "criterion" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" +dependencies = [ + "anes", + "atty", + "cast", + "ciborium", + "clap 3.2.23", + "criterion-plot", + "itertools", + "lazy_static", + "num-traits 0.2.15", + "oorandom", + "plotters", + "rayon", + "regex", + "serde 1.0.145", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + [[package]] name = "crossbeam-channel" version = "0.4.4" @@ -3958,7 +4054,7 @@ dependencies = [ "borsh 0.9.4", "byte-unit", "byteorder", - "clap", + "clap 3.0.0-beta.2", "color-eyre", "config", "data-encoding", @@ -4031,6 +4127,29 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "namada_benchmarks" +version = "0.15.0" +dependencies = [ + "async-trait", + "borsh 0.9.4", + "criterion", + "ferveo-common", + "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", + "ibc-relayer", + "ibc-relayer-types", + "masp_primitives", + "namada", + "namada_apps", + "namada_test_utils", + "prost", + "rand 0.8.5", + "rand_core 0.6.4", + "rust_decimal", + "tempfile", + "tokio", +] + [[package]] name = "namada_core" version = "0.15.2" @@ -4485,6 +4604,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + [[package]] name = "opaque-debug" version = "0.2.3" @@ -4587,6 +4712,12 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85" +[[package]] +name = "os_str_bytes" +version = "6.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" + [[package]] name = "output_vt100" version = "0.1.3" @@ -4857,6 +4988,34 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +[[package]] +name = "plotters" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" +dependencies = [ + "num-traits 0.2.15", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" + +[[package]] +name = "plotters-svg" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" +dependencies = [ + "plotters-backend", +] + [[package]] name = "polling" version = "2.3.0" @@ -6809,6 +6968,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + [[package]] name = "thiserror" version = "1.0.38" @@ -6919,6 +7084,16 @@ dependencies = [ "url 2.3.1", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde 1.0.145", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.6.0" From 6b44bb23e90b709f47bc5cc5c9641a2e0df65fb8 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 29 Mar 2023 18:49:23 +0200 Subject: [PATCH 08/23] Misc fixes to gas and checksum file Date: Wed Mar 29 18:49:23 2023 +0200 --- apps/src/lib/cli.rs | 2 +- apps/src/lib/client/utils.rs | 2 +- apps/src/lib/config/genesis.rs | 2 +- .../lib/node/ledger/shell/finalize_block.rs | 11 ++- apps/src/lib/node/ledger/shell/init_chain.rs | 10 +- apps/src/lib/wasm_loader/mod.rs | 11 ++- core/src/ledger/gas.rs | 2 +- genesis/dev.toml | 2 +- genesis/e2e-tests-single-node.toml | 2 +- shared/src/ledger/queries/mod.rs | 35 +------ tests/src/e2e/ibc_tests.rs | 6 +- tests/src/e2e/ledger_tests.rs | 99 +++++++++++-------- tests/src/e2e/multitoken_tests/helpers.rs | 4 +- tests/src/e2e/setup.rs | 32 ++++-- tests/src/vm_host_env/tx.rs | 2 +- wasm/checksums.py | 28 ++---- wasm/gas.json | 34 +++---- wasm/gas_checksums.json | 20 ---- 18 files changed, 137 insertions(+), 167 deletions(-) delete mode 100644 wasm/gas_checksums.json diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index a249acd1da..bdb15db023 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1705,7 +1705,7 @@ pub mod args { const FEE_AMOUNT: ArgDefault = arg_default("fee-amount", DefaultFn(|| token::Amount::from(0))); const GAS_LIMIT: ArgDefault = - arg_default("gas-limit", DefaultFn(|| GasLimit::from(10))); //FIXME: fix this default value + arg_default("gas-limit", DefaultFn(|| GasLimit::from(10))); const FEE_TOKEN: ArgDefaultFromCtx = arg_default_from_ctx("fee-token", DefaultFn(|| "NAM".into())); const GENESIS_PATH: Arg = arg("genesis-path"); diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index a73df375b1..4eb483c12d 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -423,7 +423,7 @@ pub fn init_network( // Find the sha256 from checksums.json let name = format!("{}.wasm", name); // Full name in format `{name}.{sha256}.wasm` - let full_name = checksums.0.get(&name).unwrap(); + let full_name = checksums.0.get(&name).unwrap().get("hash").unwrap(); let hash = full_name .split_once('.') .unwrap() diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 1dfe36b4de..f54a4d2b41 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -978,7 +978,7 @@ pub fn genesis(num_validators: u64) -> Genesis { }, max_expected_time_per_block: namada::types::time::DurationSecs(30), max_proposal_bytes: Default::default(), - max_block_gas: 100_000_000, //FIXME: adjust this value + max_block_gas: 100_000_000, vp_whitelist: vec![], tx_whitelist: vec![], implicit_vp_code_path: vp_implicit_path.into(), diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 8bf5e8b19a..e01517da8e 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -296,9 +296,14 @@ where }, ); ( - tx_event, - None, - TxGasMeter::new(spare_gas), // This is just for logging/events purposes, no more gas is actually used by the wrapper + tx_event, None, + gas_meter, + /* This is just for + * logging/events + * purposes, no more + * gas is actually + * used by the + * wrapper */ ) } TxType::Decrypted(inner) => { diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 4e79d1f097..ed2541804e 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -111,14 +111,16 @@ where // Store wasm codes into storage let checksums = wasm_loader::Checksums::read_checksums(&self.wasm_dir); - for (name, full_name) in checksums.0.iter() { + for (name, info) in checksums.0.iter() { let code = wasm_loader::read_wasm(&self.wasm_dir, name) .map_err(Error::ReadingWasm)?; let code_hash = CodeHash::sha256(&code); - let elements = full_name.split('.').collect::>(); - let checksum = elements.get(1).ok_or_else(|| { - Error::LoadingWasm(format!("invalid full name: {}", full_name)) + let checksum = info.get("hash").ok_or_else(|| { + Error::LoadingWasm(format!( + "Missing wasm hash for tx: {}", + name + )) })?; assert_eq!( code_hash.to_string(), diff --git a/apps/src/lib/wasm_loader/mod.rs b/apps/src/lib/wasm_loader/mod.rs index 9a075fbcf8..2c78bf6f8c 100644 --- a/apps/src/lib/wasm_loader/mod.rs +++ b/apps/src/lib/wasm_loader/mod.rs @@ -31,7 +31,7 @@ pub enum Error { /// including SHA256 hash #[derive(Debug, Serialize, Deserialize)] #[serde(transparent)] -pub struct Checksums(pub HashMap); +pub struct Checksums(pub HashMap>); const S3_URL: &str = "https://namada-wasm-master.s3.eu-west-1.amazonaws.com"; @@ -132,8 +132,9 @@ pub async fn pre_fetch_wasm(wasm_directory: impl AsRef) { // load json with wasm hashes let checksums = Checksums::read_checksums_async(&wasm_directory).await; - join_all(checksums.0.into_iter().map(|(name, full_name)| { + join_all(checksums.0.into_iter().map(|(name, map)| { let wasm_directory = wasm_directory.as_ref().to_owned(); + let full_name = map.get("hash").unwrap().to_owned(); // Async check and download (if needed) each file tokio::spawn(async move { @@ -267,8 +268,10 @@ pub fn read_wasm( if let Some(os_name) = file_path.as_ref().file_name() { if let Some(name) = os_name.to_str() { let wasm_path = match checksums.0.get(name) { - Some(wasm_filename) => { - wasm_directory.as_ref().join(wasm_filename) + Some(map) => { + wasm_directory.as_ref().join(map.get("hash").ok_or_else( + || eyre!("Missing hash field in checksum"), + )?) } None => { if !file_path.as_ref().is_absolute() { diff --git a/core/src/ledger/gas.rs b/core/src/ledger/gas.rs index f4414651b4..11e6365c9d 100644 --- a/core/src/ledger/gas.rs +++ b/core/src/ledger/gas.rs @@ -22,7 +22,7 @@ const PARALLEL_GAS_DIVIDER: u64 = 10; /// The minimum gas cost for accessing the storage pub const MIN_STORAGE_GAS: u64 = 1; /// The gas cost for verifying the signature of a transaction -pub const VERIFY_TX_SIG_GAS_COST: u64 = 1_000; +pub const VERIFY_TX_SIG_GAS_COST: u64 = 10; /// The gas cost for validating wasm vp code pub const WASM_VALIDATION_GAS_PER_BYTE: u64 = 1; /// The cost for writing a byte to storage diff --git a/genesis/dev.toml b/genesis/dev.toml index 6973aae34a..fb6c5eaaf0 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -148,7 +148,7 @@ max_expected_time_per_block = 30 epochs_per_year = 525_600 # Max payload size, in bytes, for a tx batch proposal. max_proposal_bytes = 22020096 -# Max amount of gas per block FIXME: adjust value +# Max amount of gas per block max_block_gas = 1000000 # Proof of stake parameters. diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index ff4a2e53e1..57f2d51936 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -155,7 +155,7 @@ min_num_of_blocks = 4 max_expected_time_per_block = 30 # Max payload size, in bytes, for a tx batch proposal. max_proposal_bytes = 22020096 -# Max amount of gas per block FIXME: adjust value +# Max amount of gas per block max_block_gas = 1000000000 # Gas table gas_table = {} diff --git a/shared/src/ledger/queries/mod.rs b/shared/src/ledger/queries/mod.rs index 9c75edbada..18c0b4f970 100644 --- a/shared/src/ledger/queries/mod.rs +++ b/shared/src/ledger/queries/mod.rs @@ -197,39 +197,8 @@ mod testing { // Initialize the `TestClient` let mut wl_storage = TestWlStorage::default(); - // Initialize gas table - let checksums: BTreeMap = serde_json::from_slice( - &std::fs::read("../wasm/checksums.json").unwrap(), - ) - .unwrap(); - - let gas_file: BTreeMap = serde_json::from_slice( - &std::fs::read("../wasm/gas.json").unwrap(), - ) - .unwrap(); - - let mut gas_table = BTreeMap::::new(); - - for id in checksums.keys().chain(gas_file.keys()){ - // Get tx/vp hash (or name if native) - let hash = match checksums.get(id.as_str()) { - Some(v) => { -v - .split_once('.') - .unwrap() - .1 - .split_once('.') - .unwrap() - .0.to_owned() - } - None => { - id.to_owned() - } - }; - let gas = gas_file.get(id).unwrap_or(&1_000).to_owned(); - gas_table.insert(hash, gas); - } - + // Initialize mock gas table and gas limit + let gas_table: BTreeMap = BTreeMap::default(); let gas_table_key = namada_core::ledger::parameters::storage::get_gas_table_storage_key(); wl_storage diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index a573017cd1..1022a2cb73 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -768,7 +768,7 @@ fn transfer_received_token( "--fee-amount", "0", "--gas-limit", - "20", + "100", "--fee-token", NAM, "--node", @@ -985,7 +985,7 @@ fn submit_ibc_tx( "--fee-amount", "0", "--gas-limit", - "20", + "100", "--fee-token", NAM, "--node", @@ -1031,7 +1031,7 @@ fn transfer( "--port-id", &port_id, "--gas-limit", - "20", + "100", "--node", &rpc, ]; diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 6cac4041a5..6aa8a11a65 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -113,7 +113,7 @@ fn test_node_connectivity_and_consensus() -> Result<()> { "--amount", "10.1", "--gas-limit", - "20", + "100", "--node", &validator_one_rpc, ]; @@ -384,7 +384,7 @@ fn ledger_txs_and_queries() -> Result<()> { "--amount", "10.1", "--gas-limit", - "2", + "100", "--node", &validator_one_rpc, ], @@ -400,7 +400,7 @@ fn ledger_txs_and_queries() -> Result<()> { "--amount", "10.1", "--gas-limit", - "2", + "100", "--node", &validator_one_rpc, ], @@ -413,7 +413,7 @@ fn ledger_txs_and_queries() -> Result<()> { "--code-path", VP_USER_WASM, "--gas-limit", - "30", + "100", "--node", &validator_one_rpc, ], @@ -427,7 +427,7 @@ fn ledger_txs_and_queries() -> Result<()> { "--data-path", &tx_data_path, "--gas-limit", - "2", + "100", "--node", &validator_one_rpc ], @@ -444,7 +444,7 @@ fn ledger_txs_and_queries() -> Result<()> { "--alias", "Test-Account", "--gas-limit", - "2", + "100", "--node", &validator_one_rpc, ], @@ -464,7 +464,7 @@ fn ledger_txs_and_queries() -> Result<()> { "--signer", ALBERT, "--gas-limit", - "2", + "100", "--node", &validator_one_rpc, ], @@ -603,7 +603,7 @@ fn masp_txs_and_queries() -> Result<()> { "--amount", "10", "--gas-limit", - "2", + "100", "--node", &validator_one_rpc, ], @@ -622,7 +622,7 @@ fn masp_txs_and_queries() -> Result<()> { "--amount", "15", "--gas-limit", - "2", + "100", "--node", &validator_one_rpc, ], @@ -641,7 +641,7 @@ fn masp_txs_and_queries() -> Result<()> { "--amount", "20", "--gas-limit", - "2", + "100", "--node", &validator_one_rpc, ], @@ -662,7 +662,7 @@ fn masp_txs_and_queries() -> Result<()> { "--signer", ALBERT, "--gas-limit", - "2", + "100", "--node", &validator_one_rpc, ], @@ -683,7 +683,7 @@ fn masp_txs_and_queries() -> Result<()> { "--signer", ALBERT, "--gas-limit", - "2", + "100", "--node", &validator_one_rpc, ], @@ -704,7 +704,7 @@ fn masp_txs_and_queries() -> Result<()> { "--signer", ALBERT, "--gas-limit", - "2", + "100", "--node", &validator_one_rpc, ], @@ -725,7 +725,7 @@ fn masp_txs_and_queries() -> Result<()> { "--signer", ALBERT, "--gas-limit", - "2", + "100", "--node", &validator_one_rpc, ], @@ -746,7 +746,7 @@ fn masp_txs_and_queries() -> Result<()> { "--signer", ALBERT, "--gas-limit", - "2", + "100", "--node", &validator_one_rpc, ], @@ -804,7 +804,7 @@ fn masp_txs_and_queries() -> Result<()> { "--signer", BERTHA, "--gas-limit", - "2", + "100", "--node", &validator_one_rpc, ], @@ -929,6 +929,8 @@ fn masp_pinned_txs() -> Result<()> { BTC, "--amount", "20", + "--gas-limit", + "100", "--node", &validator_one_rpc ], @@ -1055,6 +1057,8 @@ fn masp_incentives() -> Result<()> { BTC, "--amount", "20", + "--gas-limit", + "100", "--node", &validator_one_rpc ], @@ -1246,7 +1250,7 @@ fn masp_incentives() -> Result<()> { "--token", ETH, "--amount", - "30", + "100", "--node", &validator_one_rpc ], @@ -1375,6 +1379,8 @@ fn masp_incentives() -> Result<()> { "30", "--signer", BERTHA, + "--gas-limit", + "100", "--node", &validator_one_rpc ], @@ -1467,6 +1473,8 @@ fn masp_incentives() -> Result<()> { "20", "--signer", ALBERT, + "--gas-limit", + "100", "--node", &validator_one_rpc ], @@ -1625,6 +1633,8 @@ fn masp_incentives() -> Result<()> { &((amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)).to_string(), "--signer", BERTHA, + "--gas-limit", + "100", "--node", &validator_one_rpc ], @@ -1652,6 +1662,8 @@ fn masp_incentives() -> Result<()> { &((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)).to_string(), "--signer", ALBERT, + "--gas-limit", + "100", "--node", &validator_one_rpc ], @@ -1753,7 +1765,7 @@ fn invalid_transactions() -> Result<()> { "--amount", "1", "--gas-limit", - "2", + "100", "--node", &validator_one_rpc, ]; @@ -1804,7 +1816,7 @@ fn invalid_transactions() -> Result<()> { "--amount", "1_000_000.1", "--gas-limit", - "1", + "100", // Force to ignore client check that fails on the balance check of the // source address "--force", @@ -2228,7 +2240,7 @@ fn test_bond_queries() -> Result<()> { "--amount", "100", "--gas-limit", - "20", + "100", "--ledger-address", &validator_one_rpc, ]; @@ -2247,7 +2259,7 @@ fn test_bond_queries() -> Result<()> { "--amount", "200", "--gas-limit", - "20", + "100", "--ledger-address", &validator_one_rpc, ]; @@ -2278,7 +2290,7 @@ fn test_bond_queries() -> Result<()> { "--amount", "300", "--gas-limit", - "20", + "100", "--ledger-address", &validator_one_rpc, ]; @@ -2296,7 +2308,7 @@ fn test_bond_queries() -> Result<()> { "--amount", "412", "--gas-limit", - "20", + "100", "--ledger-address", &validator_one_rpc, ]; @@ -2387,7 +2399,7 @@ fn pos_init_validator() -> Result<()> { BERTHA, "--unsafe-dont-encrypt", "--gas-limit", - "20", + "100", "--commission-rate", "0.05", "--max-commission-rate-change", @@ -2412,7 +2424,7 @@ fn pos_init_validator() -> Result<()> { "--amount", "0.5", "--gas-limit", - "20", + "100", "--node", &validator_one_rpc, ]; @@ -2429,7 +2441,7 @@ fn pos_init_validator() -> Result<()> { "--amount", "1000.5", "--gas-limit", - "20", + "100", "--node", &validator_one_rpc, ]; @@ -2449,7 +2461,7 @@ fn pos_init_validator() -> Result<()> { "--amount", "10999.5", "--gas-limit", - "20", + "100", "--node", &validator_one_rpc, ]; @@ -2465,7 +2477,7 @@ fn pos_init_validator() -> Result<()> { "--amount", "10000", "--gas-limit", - "20", + "100", "--node", &validator_one_rpc, ]; @@ -2535,8 +2547,9 @@ fn ledger_many_txs_in_a_block() -> Result<()> { "--amount", "1.01", "--gas-limit", - "20", + "100", "--node", + &validator_one_rpc, ]); // 2. Spawn threads each submitting token transfer tx @@ -2631,7 +2644,7 @@ fn proposal_submission() -> Result<()> { "--amount", "900", "--gas-limit", - "20", + "100", "--node", &validator_one_rpc, ]; @@ -2657,9 +2670,9 @@ fn proposal_submission() -> Result<()> { "init-proposal", "--data-path", valid_proposal_json_path.to_str().unwrap(), - "--node", "--gas-limit", - "20", + "100", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; @@ -2760,9 +2773,9 @@ fn proposal_submission() -> Result<()> { "init-proposal", "--data-path", invalid_proposal_json_path.to_str().unwrap(), - "--node", "--gas-limit", - "20", + "100", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; @@ -2816,9 +2829,9 @@ fn proposal_submission() -> Result<()> { "yay", "--signer", "validator-0", - "--node", "--gas-limit", - "1", + "100", + "--node", &validator_one_rpc, ]; @@ -2840,9 +2853,9 @@ fn proposal_submission() -> Result<()> { "nay", "--signer", BERTHA, - "--node", "--gas-limit", - "1", + "100", + "--node", &validator_one_rpc, ]; @@ -2860,9 +2873,9 @@ fn proposal_submission() -> Result<()> { "yay", "--signer", ALBERT, - "--node", "--gas-limit", - "1", + "100", + "--node", &validator_one_rpc, ]; @@ -3491,7 +3504,7 @@ fn proposal_offline() -> Result<()> { "--amount", "900", "--gas-limit", - "1", + "100", "--node", &validator_one_rpc, ]; @@ -3945,7 +3958,7 @@ fn test_genesis_validators() -> Result<()> { "--amount", "10.1", "--gas-limit", - "20", + "100", "--node", &validator_one_rpc, ]; @@ -4117,7 +4130,7 @@ fn double_signing_gets_slashed() -> Result<()> { "--amount", "10.1", "--gas-limit", - "20", + "100", "--node", &validator_one_rpc, ]; diff --git a/tests/src/e2e/multitoken_tests/helpers.rs b/tests/src/e2e/multitoken_tests/helpers.rs index 4561424059..2d600a2b2c 100644 --- a/tests/src/e2e/multitoken_tests/helpers.rs +++ b/tests/src/e2e/multitoken_tests/helpers.rs @@ -49,7 +49,7 @@ pub fn init_multitoken_vp(test: &Test, rpc_addr: &str) -> Result { "--fee-amount", "0", "--gas-limit", - "20", + "100", "--fee-token", NAM, "--ledger-address", @@ -154,7 +154,7 @@ pub fn attempt_red_tokens_transfer( "--amount", &amount, "--gas-limit", - "20", + "100", "--ledger-address", rpc_addr, ]; diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 1272189de0..4332ff331a 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -127,7 +127,7 @@ pub fn network( Some(get_all_wasms_hashes(&working_dir, Some("vp_"))); genesis.parameters.tx_whitelist = Some(get_all_wasms_hashes(&working_dir, Some("tx_"))); - genesis.parameters.gas_table = Some(get_gas_checksums(&working_dir)); + genesis.parameters.gas_table = Some(get_all_wasms_gas(&working_dir)); // Run the provided function on it let genesis = update_genesis(genesis); @@ -890,15 +890,16 @@ pub fn get_all_wasms_hashes( ) -> Vec { let checksums_path = working_dir.join("wasm/checksums.json"); let checksums_content = fs::read_to_string(checksums_path).unwrap(); - let checksums: HashMap = + let mut checksums: HashMap> = serde_json::from_str(&checksums_content).unwrap(); let filter_prefix = filter.unwrap_or_default(); checksums - .values() + .values_mut() .filter_map(|wasm| { - if wasm.contains(filter_prefix) { + let hash = wasm.get_mut("hash").expect("Missing hash in checksum"); + if hash.contains(filter_prefix) { Some( - wasm.split('.').collect::>()[1] + hash.split('.').collect::>()[1] .to_owned() .to_lowercase(), ) @@ -909,8 +910,21 @@ pub fn get_all_wasms_hashes( .collect() } -pub fn get_gas_checksums(working_dir: &Path) -> BTreeMap { - let gas_checksums_path = working_dir.join("wasm/gas_checksums.json"); - serde_json::from_reader(fs::File::open(gas_checksums_path).unwrap()) - .unwrap() +pub fn get_all_wasms_gas(working_dir: &Path) -> BTreeMap { + let checksums_path = working_dir.join("wasm/checksums.json"); + let checksums: HashMap> = + serde_json::from_reader(fs::File::open(checksums_path).unwrap()) + .unwrap(); + + checksums + .values() + .map(|map| { + ( + map.get("hash").unwrap().split('.').collect::>()[1] + .to_owned() + .to_lowercase(), + map.get("gas").unwrap().parse::().unwrap(), + ) + }) + .collect() } diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 771e8e587e..b5d6789f04 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -71,7 +71,7 @@ impl Default for TestTxEnv { Self { wl_storage, iterators: PrefixIterators::default(), - gas_meter: TxGasMeter::new(10_000_000), + gas_meter: TxGasMeter::new(100_000_000), tx_index: TxIndex::default(), verifiers: BTreeSet::default(), result_buffer: None, diff --git a/wasm/checksums.py b/wasm/checksums.py index 96011c9de1..31590cb639 100644 --- a/wasm/checksums.py +++ b/wasm/checksums.py @@ -2,10 +2,8 @@ import glob import hashlib import os -import sys gas = json.load(open("wasm/gas.json")) -gas_checksums = {} checksums = {} for wasm in sorted(glob.glob("wasm/*.wasm")): @@ -16,28 +14,14 @@ else os.path.splitext(basename)[0].split(".")[0] ) file_key = "{}.wasm".format(file_name) - file_hash = hashlib.sha256(open(wasm, "rb").read()).hexdigest() - checksums[file_key] = "{}.{}.wasm".format(file_name, file_hash) - - # Check gas in whitelist - if file_key not in gas: - print("{} doesn't have an associated gas cost in gas.json".format(file_key)) - sys.exit(1) - - # Add gas to checksum gas - gas_checksums[file_hash] = gas[file_key] - - os.rename(wasm, "wasm/{}".format(checksums[file_key])) - -# Prune unused gas entries if needed (in case of a tx/vp removal) -for k in list(gas.keys()): - if k not in checksums: - del gas[k] + extended_file_name = "{}.{}.wasm".format( + file_name, hashlib.sha256(open(wasm, "rb").read()).hexdigest() + ) + checksums[file_key] = {"hash": extended_file_name, "gas": str(gas[file_key])} -json.dump(gas, open("wasm/gas.json", "w"), indent=4, sort_keys=True) -json.dump(gas_checksums, open("wasm/gas_checksums.json", "w"), indent=4, sort_keys=True) + os.rename(wasm, "wasm/{}".format(checksums[file_key]["hash"])) -updated_wasms = list(checksums.values()) +updated_wasms = [value["hash"] for value in checksums.values()] for wasm in sorted(glob.glob("wasm/*.wasm")): basename = os.path.basename(wasm) diff --git a/wasm/gas.json b/wasm/gas.json index d6b28b3456..7ab4018276 100644 --- a/wasm/gas.json +++ b/wasm/gas.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": 1, - "tx_change_validator_commission.wasm": 1, - "tx_ibc.wasm": 1, - "tx_init_account.wasm": 1, - "tx_init_proposal.wasm": 1, - "tx_init_validator.wasm": 1, - "tx_reveal_pk.wasm": 1, - "tx_transfer.wasm": 1, - "tx_unbond.wasm": 1, - "tx_update_vp.wasm": 1, - "tx_vote_proposal.wasm": 1, - "tx_withdraw.wasm": 1, - "vp_implicit.wasm": 1, - "vp_masp.wasm": 1, + "tx_bond.wasm": 160, + "tx_change_validator_commission.wasm": 220, + "tx_ibc.wasm": 1240, + "tx_init_account.wasm": 230, + "tx_init_proposal.wasm": 40, + "tx_init_validator.wasm": 730, + "tx_reveal_pk.wasm": 170, + "tx_transfer.wasm": 110, + "tx_unbond.wasm": 430, + "tx_update_vp.wasm": 140, + "tx_vote_proposal.wasm": 120, + "tx_withdraw.wasm": 260, + "vp_implicit.wasm": 40, + "vp_masp.wasm": 8030, "vp_testnet_faucet.wasm": 0, - "vp_token.wasm": 1, - "vp_user.wasm": 1, - "vp_validator.wasm": 1 + "vp_token.wasm": 30, + "vp_user.wasm": 60, + "vp_validator.wasm": 50 } \ No newline at end of file diff --git a/wasm/gas_checksums.json b/wasm/gas_checksums.json deleted file mode 100644 index 3c61a1c577..0000000000 --- a/wasm/gas_checksums.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "2b543ec54932a8a5a9f402913f857406dd1e52255d692515f70bc1c393bc85e2": 1, - "462af49196f257d271f75d613e063750c934df1133b4cf3eb44d46c4ce5be668": 1, - "50444d2a47159773cbbe01c26b9a54732de8bbab4133e61a3937f0a1b8723dd4": 0, - "5574a272dbac031e4e14109fabcd864ab3069a0d21ae3b782cce2d91893d1999": 1, - "69f92cb21b49b6b0a2fec8a3e6b24ceb4016b37fe064e913f6fcfe9eb719381b": 1, - "70175441c32efc3cdc0bf33790857ab0a56aabc3309dc39b0e8d5c331a7ad7fe": 1, - "7bc74c63fe03776ed26aa5b07eac4416ac36840f2dd39336c42ae1619babe911": 1, - "7f8357d4554450ba52dc5c7aa6a2ef3ce40cc7c92a6eba79bcd163715e96eee1": 1, - "885702764693e40408baa6fa82530d4b0693d63880187eef2fe8abfd27497e7b": 1, - "8faa70660152a6fc4998a845ac310f92c04cb89692c5ad1940f031ab7e935470": 1, - "94bc8a4bf27f12f8c0cd102ee1a63be0320f5e1d2f540c0dfd510c9324dc3842": 1, - "cd261fd4b5d67db72ace5eb281d6a9e58f1b00cde6c96cd0b0a8bcf5f38eaf41": 1, - "d014ccb0df538af3ffba99bcde354d2117a95fa6906aa96b1873ca29628e20c5": 1, - "d0b03343e585666300d7f01ada1cc51bb5a74fc25005b9b26daef213ca73e00f": 1, - "d2e59b592e11f4c25cb5035fdf69089c84a968702e6e325775c1a9bf23db7c89": 1, - "db724ffa2345002de0dea3b3dc219e98c6f27f9a980002e93f6cef2c892d34ca": 1, - "e208af69e7c52beb29ce53334015dc0ce84167d3d943667755bad7c6bc2f2535": 1, - "f22219a40cc0208ac13f4529ebbf75f55ef3113ae3384ccb16c7c1e646f86462": 1 -} \ No newline at end of file From 2384328928436c0afc58d794c11d3762788acd56 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 14 Apr 2023 23:57:16 +0200 Subject: [PATCH 09/23] Misc fixes to docs and e2e tests Date: Fri Apr 14 23:57:16 2023 +0200 --- Makefile | 7 +- apps/src/lib/cli.rs | 12 +- apps/src/lib/cli/context.rs | 3 +- apps/src/lib/client/utils.rs | 10 +- .../ledger/shell/block_space_alloc/states.rs | 28 +- .../lib/node/ledger/shell/finalize_block.rs | 76 +++--- apps/src/lib/node/ledger/shell/governance.rs | 3 +- apps/src/lib/node/ledger/shell/mod.rs | 21 +- .../lib/node/ledger/shell/prepare_proposal.rs | 56 ++-- .../lib/node/ledger/shell/process_proposal.rs | 252 ++++++++++-------- apps/src/lib/wasm_loader/mod.rs | 17 +- benches/Cargo.toml | 10 +- benches/host_env.rs | 7 +- benches/mod.rs | 73 ++--- benches/native_vps.rs | 78 +++--- benches/process_wrapper.rs | 11 +- benches/txs.rs | 58 ++-- benches/vps.rs | 179 +++++++------ core/src/ledger/gas.rs | 24 +- core/src/ledger/parameters/mod.rs | 2 +- .../storage_api/collections/lazy_map.rs | 10 +- core/src/ledger/testnet_pow.rs | 18 +- core/src/types/internal.rs | 6 +- shared/src/ledger/protocol/mod.rs | 9 +- shared/src/ledger/queries/mod.rs | 13 +- shared/src/ledger/queries/shell.rs | 3 +- shared/src/vm/host_env.rs | 2 +- tests/src/e2e/ledger_tests.rs | 67 ++--- tests/src/e2e/setup.rs | 20 +- tests/src/native_vp/pos.rs | 48 ++-- tests/src/vm_host_env/mod.rs | 34 ++- wasm/checksums.py | 11 +- 32 files changed, 605 insertions(+), 563 deletions(-) diff --git a/Makefile b/Makefile index bbb2553b11..696189ef57 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ wasm_templates := wasm/tx_template wasm/vp_template audit-ignores += RUSTSEC-2021-0076 build: - $(cargo) build + $(cargo) build --workspace --exclude namada_benchmarks build-test: $(cargo) +$(nightly) build --tests -Z unstable-options @@ -36,7 +36,8 @@ package: build-release check-wasm = $(cargo) check --target wasm32-unknown-unknown --manifest-path $(wasm)/Cargo.toml check: - $(cargo) check && \ + $(cargo) check --workspace --exclude namada_benchmarks && \ + $(cargo) +$(nightly) check --benches -Z unstable-options && \ make -C $(wasms) check && \ make -C $(wasms_for_tests) check && \ $(foreach wasm,$(wasm_templates),$(check-wasm) && ) true @@ -270,4 +271,4 @@ test-miri: MIRIFLAGS="-Zmiri-disable-isolation" $(cargo) +$(nightly) miri test -.PHONY : build check build-release clippy install run-ledger run-gossip reset-ledger test test-debug fmt watch clean build-doc doc build-wasm-scripts-docker debug-wasm-scripts-docker build-wasm-scripts debug-wasm-scripts clean-wasm-scripts dev-deps test-miri test-unit test-unit-abcipp clippy-abcipp bench-gas +.PHONY : build check build-release clippy install run-ledger run-gossip reset-ledger test test-debug fmt watch clean build-doc doc build-wasm-scripts-docker debug-wasm-scripts-docker build-wasm-scripts debug-wasm-scripts clean-wasm-scripts dev-deps test-miri test-unit test-unit-abcipp clippy-abcipp bench diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index bdb15db023..c08a7ed2d3 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -3067,14 +3067,14 @@ pub mod args { address joined with a number.", )) .arg(FEE_AMOUNT.def().about( - "The amount being paid, per gas unit, for the inclusion of this transaction", + "The amount being paid, per gas unit, for the inclusion of \ + this transaction", )) .arg(FEE_TOKEN.def().about("The token for paying the gas")) - .arg( - GAS_LIMIT.def().about( - "The multiplier of the gas limit resolution definying the maximum amount of gas needed to run transaction", - ), - ) + .arg(GAS_LIMIT.def().about( + "The multiplier of the gas limit resolution defining the \ + maximum amount of gas needed to run transaction", + )) .arg(EXPIRATION_OPT.def().about( "The expiration datetime of the transaction, after which the \ tx won't be accepted anymore. All of these examples are \ diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 7f449658f8..686a348151 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -14,9 +14,8 @@ use namada::types::masp::*; use super::args; use crate::client::tx::ShieldedContext; -use crate::config::genesis; use crate::config::global::GlobalConfig; -use crate::config::{self, Config}; +use crate::config::{self, genesis, Config}; use crate::wallet::{AddressVpType, Wallet}; use crate::wasm_loader; diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 4eb483c12d..fb85d8d5ec 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -422,15 +422,7 @@ pub fn init_network( config.wasm.iter_mut().for_each(|(name, config)| { // Find the sha256 from checksums.json let name = format!("{}.wasm", name); - // Full name in format `{name}.{sha256}.wasm` - let full_name = checksums.0.get(&name).unwrap().get("hash").unwrap(); - let hash = full_name - .split_once('.') - .unwrap() - .1 - .split_once('.') - .unwrap() - .0; + let hash = checksums.0.get(&name).unwrap().get("hash").unwrap(); config.sha256 = Some(genesis_config::HexString(hash.to_owned())); }); diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc/states.rs b/apps/src/lib/node/ledger/shell/block_space_alloc/states.rs index 18712998e3..84292049a4 100644 --- a/apps/src/lib/node/ledger/shell/block_space_alloc/states.rs +++ b/apps/src/lib/node/ledger/shell/block_space_alloc/states.rs @@ -39,44 +39,26 @@ pub enum EncryptedTxBatchAllocator { /// The leader of the current Tendermint round is building /// a new batch of DKG decrypted transactions. -/// -/// For more info, read the module docs of -/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. pub enum BuildingDecryptedTxBatch {} /// The leader of the current Tendermint round is building /// a new batch of Namada protocol transactions. -/// -/// For more info, read the module docs of -/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. pub enum BuildingProtocolTxBatch {} /// The leader of the current Tendermint round is building /// a new batch of DKG encrypted transactions. -/// -/// For more info, read the module docs of -/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. pub struct BuildingEncryptedTxBatch { /// One of [`WithEncryptedTxs`] and [`WithoutEncryptedTxs`]. _mode: Mode, } /// Allow block proposals to include encrypted txs. -/// -/// For more info, read the module docs of -/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. pub enum WithEncryptedTxs {} /// Prohibit block proposals from including encrypted txs. -/// -/// For more info, read the module docs of -/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. pub enum WithoutEncryptedTxs {} /// Try to allocate a new transaction on a [`BlockSpaceAllocator`] state. -/// -/// For more info, read the module docs of -/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. pub trait TryAlloc { /// Try to allocate space for a new transaction. fn try_alloc(&mut self, tx: &[u8]) -> Result<(), AllocFailure>; @@ -85,11 +67,8 @@ pub trait TryAlloc { /// Represents a state transition in the [`BlockSpaceAllocator`] state machine. /// /// This trait should not be used directly. Instead, consider using one of -/// [`NextState`], [`NextStateWithEncryptedTxs`] or -/// [`NextStateWithoutEncryptedTxs`]. -/// -/// For more info, read the module docs of -/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. +/// [`NextState`], [`WithEncryptedTxs`] or +/// [`WithoutEncryptedTxs`]. pub trait NextStateImpl { /// The next state in the [`BlockSpaceAllocator`] state machine. type Next; @@ -101,9 +80,6 @@ pub trait NextStateImpl { /// Convenience extension of [`NextStateImpl`], to transition to a new /// state with a null transition function. -/// -/// For more info, read the module docs of -/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. pub trait NextState: NextStateImpl { /// Transition to the next state in the [`BlockSpaceAllocator`] state, /// using a null transiiton function. diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index e01517da8e..ce0d296b56 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1,10 +1,8 @@ //! Implementation of the `FinalizeBlock` ABCI++ method for the Shell -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use data_encoding::HEXUPPER; -use std::collections::BTreeMap; - use namada::ledger::gas::TxGasMeter; use namada::ledger::parameters::storage as params_storage; use namada::ledger::pos::types::{decimal_mult_u64, into_tm_voting_power}; @@ -298,12 +296,12 @@ where ( tx_event, None, gas_meter, - /* This is just for - * logging/events - * purposes, no more - * gas is actually - * used by the - * wrapper */ + // This is just for + // logging/events + // purposes, no more + // gas is actually + // used by the + // wrapper ) } TxType::Decrypted(inner) => { @@ -329,9 +327,9 @@ where ); } DecryptedTx::Undecryptable(_) => { - event["log"] = - "Transaction could not be decrypted." - .into(); + event["log"] = "Transaction could not be \ + decrypted." + .into(); event["code"] = ErrorCodes::Undecryptable.into(); } @@ -346,14 +344,14 @@ where TxType::Raw(_) => { tracing::error!( "Internal logic error: FinalizeBlock received a \ - TxType::Raw transaction" + TxType::Raw transaction" ); continue; } TxType::Protocol(_) => { tracing::error!( "Internal logic error: FinalizeBlock received a \ - TxType::Protocol transaction" + TxType::Protocol transaction" ); continue; } @@ -448,8 +446,9 @@ where .storage .delete(&tx_hash_key) .expect( - "Error while deleting tx hash key from storage", - ); + "Error while deleting tx hash key from \ + storage", + ); } } @@ -1599,9 +1598,11 @@ mod test_finalize_block { // won't receive votes from TM since we receive votes at a 1-block // delay, so votes will be empty here next_block_for_inflation(&mut shell, pkh1.clone(), vec![]); - assert!(rewards_accumulator_handle() - .is_empty(&shell.wl_storage) - .unwrap()); + assert!( + rewards_accumulator_handle() + .is_empty(&shell.wl_storage) + .unwrap() + ); // FINALIZE BLOCK 2. Tell Namada that val1 is the block proposer. // Include votes that correspond to block 1. Make val2 the next block's @@ -1611,9 +1612,11 @@ mod test_finalize_block { assert!(rewards_prod_2.is_empty(&shell.wl_storage).unwrap()); assert!(rewards_prod_3.is_empty(&shell.wl_storage).unwrap()); assert!(rewards_prod_4.is_empty(&shell.wl_storage).unwrap()); - assert!(!rewards_accumulator_handle() - .is_empty(&shell.wl_storage) - .unwrap()); + assert!( + !rewards_accumulator_handle() + .is_empty(&shell.wl_storage) + .unwrap() + ); // Val1 was the proposer, so its reward should be larger than all // others, which should themselves all be equal let acc_sum = get_rewards_sum(&shell.wl_storage); @@ -1714,9 +1717,11 @@ mod test_finalize_block { ); next_block_for_inflation(&mut shell, pkh1.clone(), votes.clone()); } - assert!(rewards_accumulator_handle() - .is_empty(&shell.wl_storage) - .unwrap()); + assert!( + rewards_accumulator_handle() + .is_empty(&shell.wl_storage) + .unwrap() + ); let rp1 = rewards_prod_1 .get(&shell.wl_storage, &Epoch::default()) .unwrap() @@ -1842,14 +1847,16 @@ mod test_finalize_block { let code = event.attributes.get("code").expect("Testfailed").as_str(); assert_eq!(code, String::from(ErrorCodes::WasmRuntimeError).as_str()); - assert!(!shell - .wl_storage - .has_key(&inner_hash_key) - .expect("Test failed")) + assert!( + !shell + .wl_storage + .has_key(&inner_hash_key) + .expect("Test failed") + ) } - /// Test that a wrapper transaction rejected by [`process_proposal`] because of gas, - /// still pays the fee + /// Test that a wrapper transaction rejected by [`process_proposal`] because + /// of gas, still pays the fee #[test] fn test_rejected_wrapper_for_gas_pays_fee() { let (mut shell, _) = setup(1); @@ -1875,7 +1882,7 @@ mod test_finalize_block { &keypair, Epoch(0), 1.into(), - raw_tx.clone(), + raw_tx, Default::default(), #[cfg(not(feature = "mainnet"))] None, @@ -1901,7 +1908,7 @@ mod test_finalize_block { .write(&balance_key, initial_balance.try_to_vec().unwrap()) .unwrap(); - //FIXME: uncomment when variable fees + // FIXME: uncomment when variable fees // let event = &shell // .finalize_block(FinalizeBlock { // txs: vec![processed_tx], @@ -1910,7 +1917,8 @@ mod test_finalize_block { // .expect("Test failed")[0]; // assert_eq!(event.event_type.to_string(), String::from("accepted")); - // let code = event.attributes.get("code").expect("Testfailed").as_str(); + // let code = + // event.attributes.get("code").expect("Testfailed").as_str(); // assert_eq!(code, String::from(ErrorCodes::TxGasLimit).as_str()); // assert_eq!( diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index e3409d19f8..b590f17183 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -173,7 +173,8 @@ where let tx_result = protocol::apply_tx( tx_type, TxIndex::default(), - &mut TxGasMeter::new(u64::MAX), // No gas limit for governance proposals + &mut TxGasMeter::new(u64::MAX), /* No gas limit for + * governance proposals */ gas_table, &mut shell.wl_storage.write_log, &shell.wl_storage.storage, diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 6bf39c1505..64c6918782 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -5,11 +5,11 @@ //! and [`Shell::process_proposal`] must be also reverted //! (unless we can simply overwrite them in the next block). //! More info in . -mod block_space_alloc; +pub mod block_space_alloc; mod finalize_block; mod governance; mod init_chain; -mod prepare_proposal; +pub mod prepare_proposal; pub mod process_proposal; mod queries; mod stats; @@ -42,14 +42,13 @@ use namada::types::internal::WrapperTxInQueue; use namada::types::key::*; use namada::types::storage::{BlockHeight, Key, TxIndex}; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; -use namada::types::token; #[cfg(not(feature = "mainnet"))] use namada::types::transaction::MIN_FEE; use namada::types::transaction::{ hash_tx, process_tx, verify_decrypted_correctly, AffineCurve, DecryptedTx, EllipticCurve, PairingEngine, TxType, }; -use namada::types::{address, hash}; +use namada::types::{address, hash, token}; use namada::vm::wasm::{TxCache, VpCache}; use namada::vm::WasmCacheRwAccess; use num_derive::{FromPrimitive, ToPrimitive}; @@ -712,7 +711,7 @@ where if let TxType::Wrapper(wrapper) = tx_type { // Tx gas limit let mut gas_meter = TxGasMeter::new(u64::from(&wrapper.gas_limit)); - if let Err(_) = gas_meter.add_tx_size_gas(tx_bytes.len()) { + if gas_meter.add_tx_size_gas(tx_bytes.len()).is_err() { response.code = ErrorCodes::TxGasLimit.into(); response.log = "Wrapper transactions exceeds its gas limit".to_string(); @@ -726,11 +725,11 @@ where .expect("Error while reading from storage") .expect("Missing max_block_gas parameter in storage"); let mut block_gas_meter = BlockGasMeter::new(block_gas_limit); - if let Err(_) = block_gas_meter.finalize_transaction(gas_meter) { + if block_gas_meter.finalize_transaction(gas_meter).is_err() { response.code = ErrorCodes::BlockGasLimit.into(); - response.log = - "Wrapper transaction exceeds the maximum block gas limit" - .to_string(); + response.log = "Wrapper transaction exceeds the maximum block \ + gas limit" + .to_string(); return response; } @@ -1115,8 +1114,8 @@ mod test_utils { } /// Add a wrapper tx to the queue of txs to be decrypted - /// in the current block proposal. Takes the length of the encoded wrapper - /// as parameter. + /// in the current block proposal. Takes the length of the encoded + /// wrapper as parameter. #[cfg(test)] pub fn enqueue_tx(&mut self, wrapper: WrapperTx, inner_tx_gas: u64) { self.shell diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index ca6b37f3e7..56c97bd20c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -5,8 +5,8 @@ use namada::core::ledger::gas::TxGasMeter; use namada::core::ledger::parameters; use namada::ledger::gas::BlockGasMeter; use namada::ledger::storage::{DBIter, StorageHasher, DB}; -use namada::proof_of_stake::pos_queries::PosQueries; use namada::ledger::storage_api::StorageRead; +use namada::proof_of_stake::pos_queries::PosQueries; use namada::proto::Tx; use namada::types::internal::WrapperTxInQueue; use namada::types::time::DateTimeUtc; @@ -15,8 +15,6 @@ use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; use super::super::*; -#[allow(unused_imports)] -use super::block_space_alloc; use super::block_space_alloc::states::{ BuildingDecryptedTxBatch, BuildingProtocolTxBatch, EncryptedTxBatchAllocator, NextState, TryAlloc, @@ -36,8 +34,8 @@ where { /// Begin a new block. /// - /// Block construction is documented in [`block_space_alloc`] - /// and [`block_space_alloc::states`]. + /// Block construction is documented in [`super::block_space_alloc`] + /// and [`super::block_space_alloc::states`]. /// /// INVARIANT: Any changes applied in this method must be reverted if /// the proposal is rejected (unless we can simply overwrite @@ -132,12 +130,12 @@ where // valid because of mempool check TryInto::::try_into(block_time).ok() }); - let mut temp_block_gas_meter = BlockGasMeter::new( - self.wl_storage - .read(¶meters::storage::get_max_block_gas_key()) - .expect("Error while reading from storage") - .expect("Missing max_block_gas parameter in storage"), - ); + let mut temp_block_gas_meter = BlockGasMeter::new( + self.wl_storage + .read(¶meters::storage::get_max_block_gas_key()) + .expect("Error while reading from storage") + .expect("Missing max_block_gas parameter in storage"), + ); let txs = txs .iter() @@ -226,7 +224,7 @@ where .map( |WrapperTxInQueue { tx, - gas: _, + gas: _, #[cfg(not(feature = "mainnet"))] has_valid_pow, }| { @@ -482,7 +480,8 @@ mod test_prepare_proposal { assert!(result.txs.is_empty()); } - /// Check that a tx requiring more gas than the block limit is not included in the block + /// Check that a tx requiring more gas than the block limit is not included + /// in the block #[test] fn test_exceeding_max_block_gas_tx() { let (shell, _) = test_utils::setup(1); @@ -523,20 +522,13 @@ mod test_prepare_proposal { time: None, ..Default::default() }; - #[cfg(feature = "abcipp")] - assert_eq!( - shell.prepare_proposal(req).tx_records, - vec![record::remove(wrapper.to_bytes())] - ); - #[cfg(not(feature = "abcipp"))] - { - let result = shell.prepare_proposal(req); - eprintln!("Proposal: {:?}", result.txs); - assert!(result.txs.is_empty()); - } + let result = shell.prepare_proposal(req); + eprintln!("Proposal: {:?}", result.txs); + assert!(result.txs.is_empty()); } - // Check that a wrapper requiring more gas than its limit is not included in the block + // Check that a wrapper requiring more gas than its limit is not included in + // the block #[test] fn test_exceeding_gas_limit_wrapper() { let (shell, _) = test_utils::setup(1); @@ -571,16 +563,8 @@ mod test_prepare_proposal { time: None, ..Default::default() }; - #[cfg(feature = "abcipp")] - assert_eq!( - shell.prepare_proposal(req).tx_records, - vec![record::remove(wrapper.to_bytes())] - ); - #[cfg(not(feature = "abcipp"))] - { - let result = shell.prepare_proposal(req); - eprintln!("Proposal: {:?}", result.txs); - assert!(result.txs.is_empty()); - } + let result = shell.prepare_proposal(req); + eprintln!("Proposal: {:?}", result.txs); + assert!(result.txs.is_empty()); } } diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 8f9a6f053c..2059d8892e 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1,15 +1,15 @@ //! Implementation of the ['VerifyHeader`], [`ProcessProposal`], //! and [`RevertProposal`] ABCI++ methods for the Shell +use std::collections::BTreeMap; + use data_encoding::HEXUPPER; use namada::core::hints; use namada::core::ledger::storage::WlStorage; -use namada::types::hash::HASH_LENGTH; -use std::collections::BTreeMap; - use namada::core::types::hash::Hash; use namada::ledger::storage::TempWlStorage; use namada::proof_of_stake::pos_queries::PosQueries; +use namada::types::hash::HASH_LENGTH; use namada::types::internal::WrapperTxInQueue; use super::*; @@ -167,7 +167,7 @@ where &mut temp_block_gas_meter, block_time, &gas_table, - &mut wrapper_index + &mut wrapper_index, ); if let ErrorCodes::Ok = ErrorCodes::from_u32(result.code).unwrap() @@ -206,6 +206,7 @@ where /// INVARIANT: Any changes applied in this method must be reverted if the /// proposal is rejected (unless we can simply overwrite them in the /// next block). + #[allow(clippy::too_many_arguments)] pub fn process_single_tx<'a>( &self, tx_bytes: &[u8], @@ -309,32 +310,32 @@ where .into(), } } - TxType::Decrypted(tx) => { - // Increase wrapper index - let tx_index = *wrapper_index; - *wrapper_index += 1; - - match tx_queue_iter.next() { - Some(wrapper) => { - if wrapper.tx.tx_hash != tx.hash_commitment() { - TxResult { - code: ErrorCodes::InvalidOrder.into(), - info: "Process proposal rejected a \ - decrypted transaction that \ - violated the tx order determined \ - in the previous block" - .into(), - } - } else if verify_decrypted_correctly(&tx, privkey) { - if let DecryptedTx::Decrypted { - tx, - has_valid_pow: _, - } = tx - { - // Tx chain id - if tx.chain_id != self.chain_id { - return TxResult { - code: ErrorCodes::InvalidDecryptedChainId + TxType::Decrypted(tx) => { + // Increase wrapper index + let tx_index = *wrapper_index; + *wrapper_index += 1; + + match tx_queue_iter.next() { + Some(wrapper) => { + if wrapper.tx.tx_hash != tx.hash_commitment() { + TxResult { + code: ErrorCodes::InvalidOrder.into(), + info: "Process proposal rejected a decrypted \ + transaction that violated the tx order \ + determined in the previous block" + .into(), + } + } else if verify_decrypted_correctly(&tx, privkey) { + if let DecryptedTx::Decrypted { + tx, + has_valid_pow: _, + } = tx + { + // Tx chain id + if tx.chain_id != self.chain_id { + return TxResult { + code: + ErrorCodes::InvalidDecryptedChainId .into(), info: format!( "Decrypted tx carries a wrong \ @@ -360,8 +361,10 @@ where } } - // Tx gas (partial check) - let tx_hash = if tx.code_or_hash.len() == HASH_LENGTH { + // Tx gas (partial check) + let tx_hash = if tx.code_or_hash.len() + == HASH_LENGTH + { match Hash::try_from(tx.code_or_hash.as_slice()) { Ok(hash) => hash, Err(_) => return TxResult { @@ -372,20 +375,24 @@ where } else { Hash(tx.code_hash()) }; - let tx_gas = match gas_table.get(&tx_hash.to_string().to_ascii_lowercase()) { - Some(gas) => gas.to_owned(), - #[cfg(test)] - None => 1_000, - #[cfg(not( - test - ))] - None => 0, // VPs will rejected the non-whitelisted tx - }; - let inner_tx_gas_limit = temp_wl_storage.storage.tx_queue.get(tx_index).map_or(0, |wrapper| wrapper.gas); - let mut tx_gas_meter = TxGasMeter::new(inner_tx_gas_limit); - if let Err(e) = tx_gas_meter.add(tx_gas) { - - return TxResult { + let tx_gas = match gas_table.get( + &tx_hash.to_string().to_ascii_lowercase(), + ) { + Some(gas) => gas.to_owned(), + #[cfg(test)] + None => 1_000, + #[cfg(not(test))] + None => 0, // VPs will rejected the non-whitelisted tx + }; + let inner_tx_gas_limit = temp_wl_storage + .storage + .tx_queue + .get(tx_index) + .map_or(0, |wrapper| wrapper.gas); + let mut tx_gas_meter = + TxGasMeter::new(inner_tx_gas_limit); + if let Err(e) = tx_gas_meter.add(tx_gas) { + return TxResult { code: ErrorCodes::DecryptedTxGasLimit.into(), info: format!("Decrypted transaction gas error: {}", e) }; @@ -413,36 +420,41 @@ where info: "Received more decrypted txs than expected" .into(), }, - }} - TxType::Wrapper(wrapper) => { - // Account for gas. This is done even if the transaction is - // later deemed invalid, to incentivize the proposer to - // include only valid transaction and avoid wasting block - // gas limit (ABCI) - let mut tx_gas_meter = - TxGasMeter::new(u64::from(&wrapper.gas_limit)); - if tx_gas_meter.add_tx_size_gas(tx_bytes.len()).is_err() { - // Add the declared tx gas limit to the block gas meter - // even in case of an error - let _ = temp_block_gas_meter - .finalize_transaction(tx_gas_meter); + } + } + TxType::Wrapper(wrapper) => { + // Account for gas. This is done even if the transaction is + // later deemed invalid, to incentivize the proposer to + // include only valid transaction and avoid wasting block + // gas limit (ABCI) + let mut tx_gas_meter = + TxGasMeter::new(u64::from(&wrapper.gas_limit)); + if tx_gas_meter.add_tx_size_gas(tx_bytes.len()).is_err() { + // Add the declared tx gas limit to the block gas meter + // even in case of an error + let _ = + temp_block_gas_meter.finalize_transaction(tx_gas_meter); + + return TxResult { + code: ErrorCodes::TxGasLimit.into(), + info: "Wrapper transactions exceeds its gas limit" + .to_string(), + }; + } + + if temp_block_gas_meter + .finalize_transaction(tx_gas_meter) + .is_err() + { + return TxResult { + code: ErrorCodes::BlockGasLimit.into(), + + info: "Wrapper transaction exceeds the maximum block \ + gas limit" + .to_string(), + }; + } - return TxResult { - code: ErrorCodes::TxGasLimit.into(), - info: "Wrapper transactions exceeds its gas limit".to_string(), - }; - } - - if let Err(_) = temp_block_gas_meter.finalize_transaction(tx_gas_meter){ - return TxResult { - code: ErrorCodes::BlockGasLimit.into(), - - info: - "Wrapper transaction exceeds the maximum block gas limit" - .to_string() - }; - } - // decrypted txs shouldn't show up before wrapper txs if metadata.has_decrypted_txs { return TxResult { @@ -490,7 +502,7 @@ where }; } - // Tx expiration + // Tx expiration if let Some(exp) = tx_expiration { if block_time > exp { return TxResult { @@ -503,7 +515,6 @@ where } } - // validate the ciphertext via Ferveo if !wrapper.validate_ciphertext() { TxResult { @@ -563,9 +574,9 @@ where .write(&wrapper_hash_key, vec![]) .expect("Couldn't write wrapper tx hash to write log"); - // check that the fee payer has sufficient balance - let balance = - self.get_balance(&wrapper.fee.token, &wrapper.fee_payer()); + // check that the fee payer has sufficient balance + let balance = self + .get_balance(&wrapper.fee.token, &wrapper.fee_payer()); // In testnets, tx is allowed to skip fees if it // includes a valid PoW @@ -631,7 +642,7 @@ mod test_process_proposal { self, gen_keypair, ProcessProposal, TestError, }; -const GAS_LIMIT_MULTIPLIER: u64 = 1; + const GAS_LIMIT_MULTIPLIER: u64 = 1; /// Test that if a wrapper tx is not signed, the block is rejected /// by [`process_proposal`]. @@ -789,7 +800,7 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; ) .unwrap(); let keypair = gen_keypair(); -// reduce address balance to match the 100 token min fee + // reduce address balance to match the 100 token min fee let balance_key = token::balance_key( &shell.wl_storage.storage.native_token, &Address::from(&keypair.ref_to()), @@ -937,9 +948,13 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; #[cfg(not(feature = "mainnet"))] None, ); - let signed_wrapper = wrapper.sign(&keypair, shell.chain_id.clone(), None).unwrap().to_bytes(); - let gas_limit = u64::from(&wrapper.gas_limit) - signed_wrapper.len() as u64; - shell.enqueue_tx(wrapper, gas_limit); + let signed_wrapper = wrapper + .sign(&keypair, shell.chain_id.clone(), None) + .unwrap() + .to_bytes(); + let gas_limit = + u64::from(&wrapper.gas_limit) - signed_wrapper.len() as u64; + shell.enqueue_tx(wrapper, gas_limit); let mut decrypted_tx = Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { tx, @@ -1002,8 +1017,14 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; #[cfg(not(feature = "mainnet"))] None, ); - let signed_wrapper = wrapper.sign(&keypair, shell.chain_id.clone(), None).unwrap().to_bytes(); - shell.enqueue_tx(wrapper.clone(), u64::from(&wrapper.gas_limit) - signed_wrapper.len() as u64); + let signed_wrapper = wrapper + .sign(&keypair, shell.chain_id.clone(), None) + .unwrap() + .to_bytes(); + shell.enqueue_tx( + wrapper.clone(), + u64::from(&wrapper.gas_limit) - signed_wrapper.len() as u64, + ); let mut tx = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable(wrapper))); @@ -1059,9 +1080,15 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; None, ); wrapper.tx_hash = Hash([0; 32]); - let signed_wrapper = wrapper.sign(&keypair, shell.chain_id.clone(), None).unwrap().to_bytes(); + let signed_wrapper = wrapper + .sign(&keypair, shell.chain_id.clone(), None) + .unwrap() + .to_bytes(); - shell.enqueue_tx(wrapper.clone(), u64::from(&wrapper.gas_limit) - signed_wrapper.len() as u64); + shell.enqueue_tx( + wrapper.clone(), + u64::from(&wrapper.gas_limit) - signed_wrapper.len() as u64, + ); let mut tx = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( #[allow(clippy::redundant_clone)] wrapper.clone(), @@ -1108,8 +1135,14 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; pow_solution: None, }; - let signed_wrapper = wrapper.sign(&keypair, shell.chain_id.clone(), None).unwrap().to_bytes(); - shell.enqueue_tx(wrapper.clone(), u64::from(&wrapper.gas_limit) - signed_wrapper.len() as u64); + let signed_wrapper = wrapper + .sign(&keypair, shell.chain_id.clone(), None) + .unwrap() + .to_bytes(); + shell.enqueue_tx( + wrapper.clone(), + u64::from(&wrapper.gas_limit) - signed_wrapper.len() as u64, + ); let mut signed = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( #[allow(clippy::redundant_clone)] @@ -1593,8 +1626,12 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; #[cfg(not(feature = "mainnet"))] None, ); - let signed_wrapper = wrapper.sign(&keypair, shell.chain_id.clone(), None).unwrap().to_bytes(); - let gas_limit = u64::from(&wrapper.gas_limit) - signed_wrapper.len() as u64; + let signed_wrapper = wrapper + .sign(&keypair, shell.chain_id.clone(), None) + .unwrap() + .to_bytes(); + let gas_limit = + u64::from(&wrapper.gas_limit) - signed_wrapper.len() as u64; let wrapper_in_queue = WrapperTxInQueue { tx: wrapper, gas: gas_limit, @@ -1702,8 +1739,12 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; #[cfg(not(feature = "mainnet"))] None, ); - let signed_wrapper_tx = wrapper.sign(&keypair, shell.chain_id.clone(), None).unwrap().to_bytes(); - let gas_limit = u64::from(&wrapper.gas_limit) - signed_wrapper_tx.len() as u64; + let signed_wrapper_tx = wrapper + .sign(&keypair, shell.chain_id.clone(), None) + .unwrap() + .to_bytes(); + let gas_limit = + u64::from(&wrapper.gas_limit) - signed_wrapper_tx.len() as u64; let wrapper_in_queue = WrapperTxInQueue { tx: wrapper, gas: gas_limit, @@ -1726,9 +1767,8 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; } } - - /// Test that a decrypted transaction requiring more gas than the limit imposed - /// by its wrapper is rejected. + /// Test that a decrypted transaction requiring more gas than the limit + /// imposed by its wrapper is rejected. #[test] fn test_decrypted_gas_limit() { let (mut shell, _) = test_utils::setup(1); @@ -1738,7 +1778,7 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; "wasm_code".as_bytes().to_owned(), Some("new transaction data".as_bytes().to_owned()), shell.chain_id.clone(), - None + None, ); let decrypted: Tx = DecryptedTx::Decrypted { tx: tx.clone(), @@ -1759,7 +1799,7 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; #[cfg(not(feature = "mainnet"))] None, ); - let gas = u64::from(&wrapper.gas_limit) ; + let gas = u64::from(&wrapper.gas_limit); let wrapper_in_queue = WrapperTxInQueue { tx: wrapper, gas, @@ -1782,7 +1822,8 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; } } - /// Check that a tx requiring more gas than the block limit causes a block rejection + /// Check that a tx requiring more gas than the block limit causes a block + /// rejection #[test] fn test_exceeding_max_block_gas_tx() { let (mut shell, _) = test_utils::setup(1); @@ -1821,7 +1862,7 @@ const GAS_LIMIT_MULTIPLIER: u64 = 1; let request = ProcessProposal { txs: vec![wrapper.to_bytes()], }; -match shell.process_proposal(request) { + match shell.process_proposal(request) { Ok(_) => panic!("Test failed"), Err(TestError::RejectProposal(response)) => { assert_eq!( @@ -1832,7 +1873,8 @@ match shell.process_proposal(request) { } } -// Check that a wrapper requiring more gas than its limit causes a block rejection + // Check that a wrapper requiring more gas than its limit causes a block + // rejection #[test] fn test_exceeding_gas_limit_wrapper() { let (mut shell, _) = test_utils::setup(1); @@ -1865,7 +1907,7 @@ match shell.process_proposal(request) { let request = ProcessProposal { txs: vec![wrapper.to_bytes()], }; -match shell.process_proposal(request) { + match shell.process_proposal(request) { Ok(_) => panic!("Test failed"), Err(TestError::RejectProposal(response)) => { assert_eq!( @@ -1874,5 +1916,5 @@ match shell.process_proposal(request) { ); } } - } + } } diff --git a/apps/src/lib/wasm_loader/mod.rs b/apps/src/lib/wasm_loader/mod.rs index 2c78bf6f8c..8c6dedc7c6 100644 --- a/apps/src/lib/wasm_loader/mod.rs +++ b/apps/src/lib/wasm_loader/mod.rs @@ -134,7 +134,8 @@ pub async fn pre_fetch_wasm(wasm_directory: impl AsRef) { join_all(checksums.0.into_iter().map(|(name, map)| { let wasm_directory = wasm_directory.as_ref().to_owned(); - let full_name = map.get("hash").unwrap().to_owned(); + let full_name = + name.replace(".", &format!(".{}.", map.get("hash").unwrap())); // Async check and download (if needed) each file tokio::spawn(async move { @@ -268,11 +269,15 @@ pub fn read_wasm( if let Some(os_name) = file_path.as_ref().file_name() { if let Some(name) = os_name.to_str() { let wasm_path = match checksums.0.get(name) { - Some(map) => { - wasm_directory.as_ref().join(map.get("hash").ok_or_else( - || eyre!("Missing hash field in checksum"), - )?) - } + Some(map) => wasm_directory.as_ref().join(name.replace( + ".", + &format!( + ".{}.", + map.get("hash").ok_or_else(|| eyre!( + "Missing hash field in checksum" + ))? + ), + )), None => { if !file_path.as_ref().is_absolute() { wasm_directory.as_ref().join(file_path.as_ref()) diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 8d03356a4b..86e8c95b73 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_benchmarks" resolver = "2" -version = "0.14.2" +version = "0.15.0" [lib] name = "namada_benches" @@ -39,11 +39,11 @@ path = "host_env.rs" [dependencies] async-trait = "0.1.51" borsh = "0.9.0" -ferveo-common = {git = "https://github.com/anoma/ferveo"} +ferveo-common = { git = "https://github.com/anoma/ferveo" } masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } -namada = {path = "../shared" } -namada_apps = {path = "../apps", features = ["dev"]} -namada_test_utils = {path = "../test_utils"} +namada = { path = "../shared" } +namada_apps = { path = "../apps" } +namada_test_utils = { path = "../test_utils" } prost = "0.9.0" rand = "0.8" rand_core = "0.6" diff --git a/benches/host_env.rs b/benches/host_env.rs index f9af56b159..f936dd8145 100644 --- a/benches/host_env.rs +++ b/benches/host_env.rs @@ -1,13 +1,12 @@ use borsh::BorshDeserialize; use criterion::{criterion_group, criterion_main, Criterion}; +use namada::core::proto::SignedTxData; use namada::core::types::address; +use namada::core::types::key::RefTo; use namada::core::types::token::{Amount, Transfer}; use namada_apps::wallet::defaults; use namada_benches::{generate_tx, TX_TRANSFER_WASM}; -use namada::core::proto::SignedTxData; -use namada::core::types::key::RefTo; - fn tx_signature_validation(c: &mut Criterion) { let tx = generate_tx( TX_TRANSFER_WASM, @@ -24,7 +23,7 @@ fn tx_signature_validation(c: &mut Criterion) { ); let SignedTxData { data: _, ref sig } = - SignedTxData::try_from_slice(&tx.data.as_ref().unwrap()).unwrap(); + SignedTxData::try_from_slice(tx.data.as_ref().unwrap()).unwrap(); c.bench_function("tx_signature_validation", |b| { b.iter(|| { diff --git a/benches/mod.rs b/benches/mod.rs index eb33bbfc33..ce5804d8a2 100644 --- a/benches/mod.rs +++ b/benches/mod.rs @@ -1,15 +1,20 @@ -//! Benchmarks module based on [`criterion`]. +//! Benchmarks module based on criterion. //! //! Measurements are taken on the elapsed wall-time. //! -//! The benchmarks only focus on sucessfull transactions and vps: in case of failure, -//! the bench function shall panic to avoid timing incomplete execution paths. +//! The benchmarks only focus on sucessfull transactions and vps: in case of +//! failure, the bench function shall panic to avoid timing incomplete execution +//! paths. //! -//! In addition, this module also contains benchmarks for [`WrapperTx`][`namada::core::types::transaction::wrapper::WrapperTx`] validation and -//! [`host_env`][`namada::vm::host_env`] exposed functions that define the gas constants of [`gas`][`namada::core::ledger::gas`]. +//! In addition, this module also contains benchmarks for +//! [`WrapperTx`][`namada::core::types::transaction::wrapper::WrapperTx`] +//! validation and [`host_env`][`namada::vm::host_env`] exposed functions that +//! define the gas constants of [`gas`][`namada::core::ledger::gas`]. //! -//! For more realistic results these benchmarks should be run on all the combination of -//! supported OS/architecture. +//! For more realistic results these benchmarks should be run on all the +//! combination of supported OS/architecture. + +use std::ops::{Deref, DerefMut}; use borsh::BorshSerialize; use masp_primitives::zip32::ExtendedFullViewingKey; @@ -19,8 +24,9 @@ use namada::core::types::key::common::SecretKey; use namada::core::types::storage::Key; use namada::core::types::token::{Amount, Transfer}; use namada::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; -use namada::ibc::clients::ics07_tendermint::client_state::AllowUpdate; -use namada::ibc::clients::ics07_tendermint::client_state::ClientState; +use namada::ibc::clients::ics07_tendermint::client_state::{ + AllowUpdate, ClientState, +}; use namada::ibc::clients::ics07_tendermint::consensus_state::ConsensusState; use namada::ibc::core::ics02_client::client_consensus::AnyConsensusState; use namada::ibc::core::ics02_client::client_state::AnyClientState; @@ -36,9 +42,8 @@ use namada::ibc::core::ics04_channel::channel::{ use namada::ibc::core::ics04_channel::Version as ChannelVersion; use namada::ibc::core::ics23_commitment::commitment::CommitmentRoot; use namada::ibc::core::ics23_commitment::specs::ProofSpecs; -use namada::ibc::core::ics24_host::identifier::ChainId as IbcChainId; use namada::ibc::core::ics24_host::identifier::{ - ChannelId, ClientId, ConnectionId, PortId, + ChainId as IbcChainId, ChannelId, ClientId, ConnectionId, PortId, }; use namada::ibc::core::ics24_host::path::{ChannelEndsPath, ConnectionsPath}; use namada::ibc::core::ics24_host::Path as IbcPath; @@ -48,9 +53,8 @@ use namada::ibc::tx_msg::Msg; use namada::ibc::Height as IbcHeight; use namada::ibc_proto::cosmos::base::v1beta1::Coin; use namada::ledger::gas::TxGasMeter; -use namada::ledger::queries::RPC; use namada::ledger::queries::{ - Client, EncodedResponseQuery, RequestCtx, RequestQuery, Router, + Client, EncodedResponseQuery, RequestCtx, RequestQuery, Router, RPC, }; use namada::proof_of_stake; use namada::proto::Tx; @@ -63,16 +67,14 @@ use namada::types::masp::{ }; use namada::types::storage::{BlockHeight, KeySeg, TxIndex}; use namada::types::time::DateTimeUtc; -use namada::types::transaction::governance::InitProposalData; -use namada::types::transaction::governance::ProposalType; +use namada::types::transaction::governance::{InitProposalData, ProposalType}; use namada::types::transaction::pos::Bond; use namada::types::transaction::GasLimit; use namada::vm::wasm::run; use namada_apps::cli::args::{Tx as TxArgs, TxTransfer}; use namada_apps::cli::context::FromContext; use namada_apps::cli::Context; -use namada_apps::client::tx::find_valid_diversifier; -use namada_apps::client::tx::gen_shielded_transfer; +use namada_apps::client::tx; use namada_apps::config::TendermintMode; use namada_apps::facade::tendermint_config::net::Address as TendermintAddress; use namada_apps::facade::tendermint_proto::abci::RequestInitChain; @@ -82,7 +84,6 @@ use namada_apps::wallet::defaults; use namada_apps::{config, wasm_loader}; use namada_test_utils::tx_data::TxWriteData; use rand_core::OsRng; -use std::ops::{Deref, DerefMut}; use tempfile::TempDir; pub const WASM_DIR: &str = "../wasm"; @@ -104,7 +105,8 @@ const BERTHA_SPENDING_KEY: &str = "bertha_spending"; pub struct BenchShell { pub inner: Shell, - /// NOTE: Temporary directory should be dropped last since Shell need to flush data on drop + /// NOTE: Temporary directory should be dropped last since Shell need to + /// flush data on drop tempdir: TempDir, } @@ -122,8 +124,8 @@ impl DerefMut for BenchShell { } } -impl BenchShell { - pub fn new() -> Self { +impl Default for BenchShell { + fn default() -> Self { let (sender, _) = tokio::sync::mpsc::unbounded_channel(); let tempdir = tempfile::tempdir().unwrap(); let path = tempdir.path().canonicalize().unwrap(); @@ -162,11 +164,8 @@ impl BenchShell { amount: Amount::whole(1000), source: Some(defaults::albert_address()), }; - let signed_tx = generate_tx( - TX_BOND_WASM, - bond.clone(), - &defaults::albert_keypair(), - ); + let signed_tx = + generate_tx(TX_BOND_WASM, bond, &defaults::albert_keypair()); let mut bench_shell = BenchShell { inner: shell, @@ -202,7 +201,9 @@ impl BenchShell { bench_shell } +} +impl BenchShell { pub fn execute_tx(&mut self, tx: &Tx) { run::tx( &self.inner.wl_storage.storage, @@ -291,7 +292,7 @@ impl BenchShell { .write(&cap_key, PortId::transfer().as_bytes()) .unwrap(); - //Set Channel open + // Set Channel open let counterparty = ChannelCounterparty::new( PortId::transfer(), Some(ChannelId::new(5)), @@ -364,7 +365,7 @@ impl BenchShell { let consensus_state = ConsensusState { timestamp: now, - root: CommitmentRoot::from_bytes(&vec![]), + root: CommitmentRoot::from_bytes(&[]), next_validators_hash: Hash::Sha256([0u8; 32]), }; @@ -487,9 +488,9 @@ impl Client for BenchShell { } } -impl BenchShieldedCtx { - pub fn new() -> Self { - let mut shell = BenchShell::new(); +impl Default for BenchShieldedCtx { + fn default() -> Self { + let mut shell = BenchShell::default(); let mut ctx = Context::new(namada_apps::cli::args::Global { chain_id: None, @@ -523,7 +524,7 @@ impl BenchShieldedCtx { ExtendedFullViewingKey::from(ctx.get_cached(&viewing_key)) .fvk .vk; - let (div, _g_d) = find_valid_diversifier(&mut OsRng); + let (div, _g_d) = tx::find_valid_diversifier(&mut OsRng); let payment_addr = viewing_key.to_payment_address(div).unwrap(); let _ = ctx .wallet @@ -542,7 +543,9 @@ impl BenchShieldedCtx { Self { ctx, shell } } +} +impl BenchShieldedCtx { pub fn generate_masp_tx( &mut self, amount: Amount, @@ -591,7 +594,11 @@ impl BenchShieldedCtx { &[], )); let shielded = async_runtime - .block_on(gen_shielded_transfer(&mut self.ctx, &self.shell, &args)) + .block_on(tx::gen_shielded_transfer( + &mut self.ctx, + &self.shell, + &args, + )) .unwrap() .map(|x| x.0); diff --git a/benches/native_vps.rs b/benches/native_vps.rs index 0085e484d1..a528e74de6 100644 --- a/benches/native_vps.rs +++ b/benches/native_vps.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeSet; + use criterion::{criterion_group, criterion_main, Criterion}; use namada::core::ledger::ibc::actions; use namada::core::types::address::{self, Address}; @@ -27,23 +29,20 @@ use namada::types::address::InternalAddress; use namada::types::chain::ChainId; use namada::types::governance::{ProposalVote, VoteType}; use namada::types::storage::TxIndex; -use namada::types::transaction::governance::ProposalType; +use namada::types::transaction::governance::{ + InitProposalData, ProposalType, VoteProposalData, +}; +use namada_apps::wallet::defaults; use namada_apps::wasm_loader; use namada_benches::{ generate_foreign_key_tx, generate_ibc_transfer_tx, generate_tx, BenchShell, TX_IBC_WASM, TX_INIT_PROPOSAL_WASM, TX_VOTE_PROPOSAL_WASM, WASM_DIR, }; -use std::collections::BTreeSet; - -use namada::types::transaction::governance::{ - InitProposalData, VoteProposalData, -}; -use namada_apps::wallet::defaults; fn replay_protection(c: &mut Criterion) { // Write a random key under the replay protection subspace let tx = generate_foreign_key_tx(&defaults::albert_keypair()); - let mut shell = BenchShell::new(); + let mut shell = BenchShell::default(); shell.execute_tx(&tx); let (verifiers, keys_changed) = shell @@ -67,7 +66,8 @@ fn replay_protection(c: &mut Criterion) { c.bench_function("vp_replay_protection", |b| { b.iter(|| { - // NOTE: thiv VP will always fail when triggered so don't assert here + // NOTE: thiv VP will always fail when triggered so don't assert + // here replay_protection .validate_tx( tx.data.as_ref().unwrap(), @@ -89,7 +89,7 @@ fn governance(c: &mut Criterion) { "minimal_proposal", "complete_proposal", ] { - let mut shell = BenchShell::new(); + let mut shell = BenchShell::default(); let signed_tx = match bench_name { "foreign_key_write" => { @@ -190,13 +190,15 @@ fn governance(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!(governance - .validate_tx( - signed_tx.data.as_ref().unwrap(), - governance.ctx.keys_changed, - governance.ctx.verifiers, - ) - .unwrap()) + assert!( + governance + .validate_tx( + signed_tx.data.as_ref().unwrap(), + governance.ctx.keys_changed, + governance.ctx.verifiers, + ) + .unwrap() + ) }) }); } @@ -229,7 +231,7 @@ fn slash_fund(c: &mut Criterion) { .into_iter() .zip(["foreign_key_write", "governance_proposal"]) { - let mut shell = BenchShell::new(); + let mut shell = BenchShell::default(); // Run the tx to validate shell.execute_tx(&tx); @@ -255,13 +257,15 @@ fn slash_fund(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!(slash_fund - .validate_tx( - tx.data.as_ref().unwrap(), - slash_fund.ctx.keys_changed, - slash_fund.ctx.verifiers, - ) - .unwrap()) + assert!( + slash_fund + .validate_tx( + tx.data.as_ref().unwrap(), + slash_fund.ctx.keys_changed, + slash_fund.ctx.verifiers, + ) + .unwrap() + ) }) }); } @@ -313,7 +317,7 @@ fn ibc(c: &mut Criterion) { ); let msg = MsgChannelOpenInit { port_id: PortId::transfer(), - channel: channel.clone(), + channel, signer: Signer::new(defaults::albert_address()), }; @@ -347,7 +351,7 @@ fn ibc(c: &mut Criterion) { "open_channel", "outgoing_transfer", ]) { - let mut shell = BenchShell::new(); + let mut shell = BenchShell::default(); shell.init_ibc_channel(); shell.execute_tx(signed_tx); @@ -372,13 +376,14 @@ fn ibc(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!(ibc - .validate_tx( + assert!( + ibc.validate_tx( signed_tx.data.as_ref().unwrap(), ibc.ctx.keys_changed, ibc.ctx.verifiers, ) - .unwrap()) + .unwrap() + ) }) }); } @@ -397,10 +402,10 @@ fn ibc_token(c: &mut Criterion) { .iter() .zip(["foreign_key_write", "outgoing_transfer"]) { - let mut shell = BenchShell::new(); + let mut shell = BenchShell::default(); shell.init_ibc_channel(); - shell.execute_tx(&signed_tx); + shell.execute_tx(signed_tx); let (verifiers, keys_changed) = shell .wl_storage @@ -420,7 +425,7 @@ fn ibc_token(c: &mut Criterion) { &internal_address, &shell.wl_storage.storage, &shell.wl_storage.write_log, - &signed_tx, + signed_tx, &TxIndex(0), VpGasMeter::new(u64::MAX, 0), &keys_changed, @@ -431,13 +436,14 @@ fn ibc_token(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!(ibc - .validate_tx( + assert!( + ibc.validate_tx( signed_tx.data.as_ref().unwrap(), ibc.ctx.keys_changed, ibc.ctx.verifiers, ) - .unwrap()) + .unwrap() + ) }) }); } diff --git a/benches/process_wrapper.rs b/benches/process_wrapper.rs index 29482810a1..0cc3895725 100644 --- a/benches/process_wrapper.rs +++ b/benches/process_wrapper.rs @@ -1,19 +1,22 @@ +use std::collections::BTreeMap; + use criterion::{criterion_group, criterion_main, Criterion}; use namada::core::types::address; use namada::core::types::token::{Amount, Transfer}; use namada::ledger::gas::BlockGasMeter; use namada::ledger::storage::TempWlStorage; use namada::types::chain::ChainId; +use namada::types::storage::BlockHeight; use namada::types::time::DateTimeUtc; -use std::collections::BTreeMap; - use namada::types::transaction::{Fee, WrapperTx}; use namada_apps::node::ledger::shell::process_proposal::ValidationMeta; use namada_apps::wallet::defaults; use namada_benches::{generate_tx, BenchShell, TX_TRANSFER_WASM}; fn process_tx(c: &mut Criterion) { - let shell = BenchShell::new(); + let mut shell = BenchShell::default(); + // Advance chain height to allow the inclusion of wrapper txs by the block space allocator + shell.wl_storage.storage.last_height = BlockHeight(2); let tx = generate_tx( TX_TRANSFER_WASM, Transfer { @@ -55,7 +58,7 @@ fn process_tx(c: &mut Criterion) { // Prevent block out of gas and replay protection TempWlStorage::new(&shell.wl_storage.storage), BlockGasMeter::new(u64::MAX), - ValidationMeta::default(), + ValidationMeta::from(&shell.wl_storage), ) }, |( diff --git a/benches/txs.rs b/benches/txs.rs index 1d52af4073..c749be9a21 100644 --- a/benches/txs.rs +++ b/benches/txs.rs @@ -5,6 +5,7 @@ use namada::core::types::key::{ }; use namada::core::types::token::Amount; use namada::ledger::governance; +use namada::ledger::storage_api::StorageRead; use namada::proof_of_stake; use namada::proto::Tx; use namada::types::chain::ChainId; @@ -14,10 +15,10 @@ use namada::types::masp::{TransferSource, TransferTarget}; use namada::types::transaction::governance::{ InitProposalData, ProposalType, VoteProposalData, }; -use namada::ledger::storage_api::StorageRead; use namada::types::transaction::pos::{Bond, CommissionChange, Withdraw}; -use namada::types::transaction::EllipticCurve; -use namada::types::transaction::{InitAccount, InitValidator, UpdateVp}; +use namada::types::transaction::{ + EllipticCurve, InitAccount, InitValidator, UpdateVp, +}; use namada_apps::wallet::defaults; use namada_apps::wasm_loader; use namada_benches::{ @@ -43,7 +44,7 @@ fn transfer(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter_batched_ref( || { - let mut shielded_ctx = BenchShieldedCtx::new(); + let mut shielded_ctx = BenchShieldedCtx::default(); let albert_spending_key = shielded_ctx .ctx @@ -105,7 +106,7 @@ fn transfer(c: &mut Criterion) { (shielded_ctx, signed_tx) }, |(shielded_ctx, signed_tx)| { - shielded_ctx.shell.execute_tx(&signed_tx); + shielded_ctx.shell.execute_tx(signed_tx); }, criterion::BatchSize::LargeInput, ) @@ -143,7 +144,7 @@ fn bond(c: &mut Criterion) { { group.bench_function(bench_name, |b| { b.iter_batched_ref( - BenchShell::new, + BenchShell::default, |shell| shell.execute_tx(signed_tx), criterion::BatchSize::LargeInput, ) @@ -181,7 +182,7 @@ fn unbond(c: &mut Criterion) { { group.bench_function(bench_name, |b| { b.iter_batched_ref( - BenchShell::new, + BenchShell::default, |shell| shell.execute_tx(signed_tx), criterion::BatchSize::LargeInput, ) @@ -219,7 +220,7 @@ fn withdraw(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter_batched_ref( || { - let mut shell = BenchShell::new(); + let mut shell = BenchShell::default(); // Unbond funds let unbond_tx = match bench_name { @@ -285,7 +286,7 @@ fn reveal_pk(c: &mut Criterion) { c.bench_function("reveal_pk", |b| { b.iter_batched_ref( - BenchShell::new, + BenchShell::default, |shell| shell.execute_tx(&tx), criterion::BatchSize::LargeInput, ) @@ -307,7 +308,7 @@ fn update_vp(c: &mut Criterion) { c.bench_function("update_vp", |b| { b.iter_batched_ref( - BenchShell::new, + BenchShell::default, |shell| shell.execute_tx(&signed_tx), criterion::BatchSize::LargeInput, ) @@ -332,7 +333,7 @@ fn init_account(c: &mut Criterion) { c.bench_function("init_account", |b| { b.iter_batched_ref( - BenchShell::new, + BenchShell::default, |shell| shell.execute_tx(&signed_tx), criterion::BatchSize::LargeInput, ) @@ -346,7 +347,7 @@ fn init_proposal(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter_batched_ref( || { - let shell = BenchShell::new(); + let shell = BenchShell::default(); let signed_tx = match bench_name { "minimal_proposal" => generate_tx( @@ -367,15 +368,22 @@ fn init_proposal(c: &mut Criterion) { governance::storage::get_max_proposal_code_size_key(); let max_proposal_content_key = governance::storage::get_max_proposal_content_key(); - let max_code_size = - shell.wl_storage - .read(&max_code_size_key) - .expect("Error while reading from storage") - .expect("Missing max_code_size parameter in storage"); - let max_proposal_content_size = shell.wl_storage - .read(&max_proposal_content_key) - .expect("Error while reading from storage") - .expect("Missing max_proposal_content parameter in storage"); + let max_code_size = shell + .wl_storage + .read(&max_code_size_key) + .expect("Error while reading from storage") + .expect( + "Missing max_code_size parameter in \ + storage", + ); + let max_proposal_content_size = shell + .wl_storage + .read(&max_proposal_content_key) + .expect("Error while reading from storage") + .expect( + "Missing max_proposal_content parameter \ + in storage", + ); generate_tx( TX_INIT_PROPOSAL_WASM, @@ -437,7 +445,7 @@ fn vote_proposal(c: &mut Criterion) { { group.bench_function(bench_name, |b| { b.iter_batched_ref( - BenchShell::new, + BenchShell::default, |shell| shell.execute_tx(signed_tx), criterion::BatchSize::LargeInput, ) @@ -486,7 +494,7 @@ fn init_validator(c: &mut Criterion) { c.bench_function("init_validator", |b| { b.iter_batched_ref( - BenchShell::new, + BenchShell::default, |shell| shell.execute_tx(&signed_tx), criterion::BatchSize::LargeInput, ) @@ -505,7 +513,7 @@ fn change_validator_commission(c: &mut Criterion) { c.bench_function("change_validator_commission", |b| { b.iter_batched_ref( - BenchShell::new, + BenchShell::default, |shell| shell.execute_tx(&signed_tx), criterion::BatchSize::LargeInput, ) @@ -518,7 +526,7 @@ fn ibc(c: &mut Criterion) { c.bench_function("ibc_transfer", |b| { b.iter_batched_ref( || { - let mut shell = BenchShell::new(); + let mut shell = BenchShell::default(); shell.init_ibc_channel(); shell diff --git a/benches/vps.rs b/benches/vps.rs index e1df633948..a3b874e278 100644 --- a/benches/vps.rs +++ b/benches/vps.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeSet; + use borsh::BorshSerialize; use criterion::{criterion_group, criterion_main, Criterion}; use namada::core::types::address::{self, Address}; @@ -12,7 +14,11 @@ use namada::types::governance::{ProposalVote, VoteType}; use namada::types::key::ed25519; use namada::types::masp::{TransferSource, TransferTarget}; use namada::types::storage::TxIndex; +use namada::types::transaction::governance::VoteProposalData; +use namada::types::transaction::pos::{Bond, CommissionChange}; +use namada::types::transaction::UpdateVp; use namada::vm::wasm::run; +use namada_apps::wallet::defaults; use namada_apps::wasm_loader; use namada_benches::{ generate_foreign_key_tx, generate_tx, BenchShell, BenchShieldedCtx, @@ -22,12 +28,6 @@ use namada_benches::{ WASM_DIR, }; use rust_decimal::Decimal; -use std::collections::BTreeSet; - -use namada::types::transaction::governance::VoteProposalData; -use namada::types::transaction::pos::{Bond, CommissionChange}; -use namada::types::transaction::UpdateVp; -use namada_apps::wallet::defaults; const VP_USER_WASM: &str = "vp_user.wasm"; const VP_TOKEN_WASM: &str = "vp_token.wasm"; @@ -120,8 +120,8 @@ fn vp_user(c: &mut Criterion) { "pos", "vp", ]) { - let mut shell = BenchShell::new(); - shell.execute_tx(&signed_tx); + let mut shell = BenchShell::default(); + shell.execute_tx(signed_tx); let (verifiers, keys_changed) = shell .wl_storage .write_log @@ -129,20 +129,22 @@ fn vp_user(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!(run::vp( - &vp_code, - signed_tx, - &TxIndex(0), - &defaults::albert_address(), - &shell.wl_storage.storage, - &shell.wl_storage.write_log, - &mut VpGasMeter::new(u64::MAX, 0), - &keys_changed, - &verifiers, - shell.vp_wasm_cache.clone(), - false, - ) - .unwrap()); + assert!( + run::vp( + &vp_code, + signed_tx, + &TxIndex(0), + &defaults::albert_address(), + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + &mut VpGasMeter::new(u64::MAX, 0), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + false, + ) + .unwrap() + ); }) }); } @@ -214,7 +216,8 @@ fn vp_implicit(c: &mut Criterion) { id: 0, vote: ProposalVote::Yay(VoteType::Default), voter: Address::from(&implicit_account.to_public()), - delegations: vec![], //NOTE: no need to bond tokens because the implicit vp doesn't check that + delegations: vec![], /* NOTE: no need to bond tokens because the + * implicit vp doesn't check that */ }, &implicit_account, ); @@ -236,7 +239,7 @@ fn vp_implicit(c: &mut Criterion) { "pos", "governance_vote", ]) { - let mut shell = BenchShell::new(); + let mut shell = BenchShell::default(); if bench_name != "reveal_pk" { // Reveal publick key @@ -261,20 +264,22 @@ fn vp_implicit(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!(run::vp( - &vp_code, - tx, - &TxIndex(0), - &Address::from(&implicit_account.to_public()), - &shell.wl_storage.storage, - &shell.wl_storage.write_log, - &mut VpGasMeter::new(u64::MAX, 0), - &keys_changed, - &verifiers, - shell.vp_wasm_cache.clone(), - false, + assert!( + run::vp( + &vp_code, + tx, + &TxIndex(0), + &Address::from(&implicit_account.to_public()), + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + &mut VpGasMeter::new(u64::MAX, 0), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + false, + ) + .unwrap() ) - .unwrap()) }) }); } @@ -378,9 +383,9 @@ fn vp_validator(c: &mut Criterion) { "commission_rate", "vp", ]) { - let mut shell = BenchShell::new(); + let mut shell = BenchShell::default(); - shell.execute_tx(&signed_tx); + shell.execute_tx(signed_tx); let (verifiers, keys_changed) = shell .wl_storage .write_log @@ -388,20 +393,22 @@ fn vp_validator(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!(run::vp( - &vp_code, - signed_tx, - &TxIndex(0), - &defaults::validator_address(), - &shell.wl_storage.storage, - &shell.wl_storage.write_log, - &mut VpGasMeter::new(u64::MAX, 0), - &keys_changed, - &verifiers, - shell.vp_wasm_cache.clone(), - false, - ) - .unwrap()); + assert!( + run::vp( + &vp_code, + signed_tx, + &TxIndex(0), + &defaults::validator_address(), + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + &mut VpGasMeter::new(u64::MAX, 0), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + false, + ) + .unwrap() + ); }) }); } @@ -434,8 +441,8 @@ fn vp_token(c: &mut Criterion) { .iter() .zip(["foreign_key_write", "transfer"]) { - let mut shell = BenchShell::new(); - shell.execute_tx(&signed_tx); + let mut shell = BenchShell::default(); + shell.execute_tx(signed_tx); let (verifiers, keys_changed) = shell .wl_storage .write_log @@ -443,20 +450,22 @@ fn vp_token(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!(run::vp( - &vp_code, - signed_tx, - &TxIndex(0), - &defaults::albert_address(), - &shell.wl_storage.storage, - &shell.wl_storage.write_log, - &mut VpGasMeter::new(u64::MAX, 0), - &keys_changed, - &verifiers, - shell.vp_wasm_cache.clone(), - false, - ) - .unwrap()); + assert!( + run::vp( + &vp_code, + signed_tx, + &TxIndex(0), + &defaults::albert_address(), + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + &mut VpGasMeter::new(u64::MAX, 0), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + false, + ) + .unwrap() + ); }) }); } @@ -470,7 +479,7 @@ fn vp_masp(c: &mut Criterion) { for bench_name in ["shielding", "unshielding", "shielded"] { group.bench_function(bench_name, |b| { - let mut shielded_ctx = BenchShieldedCtx::new(); + let mut shielded_ctx = BenchShieldedCtx::default(); let albert_spending_key = shielded_ctx .ctx @@ -527,20 +536,22 @@ fn vp_masp(c: &mut Criterion) { .verifiers_and_changed_keys(&BTreeSet::default()); b.iter(|| { - assert!(run::vp( - &vp_code, - &signed_tx, - &TxIndex(0), - &defaults::validator_address(), - &shielded_ctx.shell.wl_storage.storage, - &shielded_ctx.shell.wl_storage.write_log, - &mut VpGasMeter::new(u64::MAX, 0), - &keys_changed, - &verifiers, - shielded_ctx.shell.vp_wasm_cache.clone(), - false, - ) - .unwrap()); + assert!( + run::vp( + &vp_code, + &signed_tx, + &TxIndex(0), + &defaults::validator_address(), + &shielded_ctx.shell.wl_storage.storage, + &shielded_ctx.shell.wl_storage.write_log, + &mut VpGasMeter::new(u64::MAX, 0), + &keys_changed, + &verifiers, + shielded_ctx.shell.vp_wasm_cache.clone(), + false, + ) + .unwrap() + ); }) }); } diff --git a/core/src/ledger/gas.rs b/core/src/ledger/gas.rs index 11e6365c9d..4895b3bd92 100644 --- a/core/src/ledger/gas.rs +++ b/core/src/ledger/gas.rs @@ -31,11 +31,12 @@ pub const STORAGE_WRITE_GAS_PER_BYTE: u64 = 100; /// Gas module result for functions that may fail pub type Result = std::result::Result; -/// Gas metering in a block. The amount of gas consumed in a block is based on the -/// tx_gas_limit declared by each [`TxGasMeter`] +/// Gas metering in a block. The amount of gas consumed in a block is based on +/// the tx_gas_limit declared by each [`TxGasMeter`] #[derive(Debug, Clone)] pub struct BlockGasMeter { - /// The max amount of gas allowed per block, defined by the protocol parameter + /// The max amount of gas allowed per block, defined by the protocol + /// parameter pub block_gas_limit: u64, block_gas: u64, } @@ -79,7 +80,8 @@ impl BlockGasMeter { /// Add the transaction gas limit to the block's total gas. It will return /// error when the consumed gas exceeds the block gas limit, but the state - /// will still be updated. This function consumes the [`TxGasMeter`] which shouldn't be updated after this point. + /// will still be updated. This function consumes the [`TxGasMeter`] which + /// shouldn't be updated after this point. pub fn finalize_transaction( &mut self, tx_gas_meter: TxGasMeter, @@ -96,7 +98,9 @@ impl BlockGasMeter { } /// Tries to add the transaction gas limit to the block's total gas. - /// If the operation returns an error, propagates this errors without updating the state. This function consumes the [`TxGasMeter`] which shouldn't be updated after this point. + /// If the operation returns an error, propagates this errors without + /// updating the state. This function consumes the [`TxGasMeter`] which + /// shouldn't be updated after this point. pub fn try_finalize_transaction( &mut self, tx_gas_meter: TxGasMeter, @@ -117,7 +121,8 @@ impl BlockGasMeter { } impl TxGasMeter { - /// Initialize a new Tx gas meter. Requires the gas limit for the specific transaction + /// Initialize a new Tx gas meter. Requires the gas limit for the specific + /// transaction pub fn new(tx_gas_limit: u64) -> Self { Self { tx_gas_limit, @@ -126,8 +131,8 @@ impl TxGasMeter { } /// Add gas cost for the current transaction. It will return error when the - /// consumed gas exceeds the provided transaction gas limit, but the state will still - /// be updated. + /// consumed gas exceeds the provided transaction gas limit, but the state + /// will still be updated. pub fn add(&mut self, gas: u64) -> Result<()> { self.transaction_gas = self .transaction_gas @@ -203,7 +208,8 @@ impl VpGasMeter { } impl VpsGas { - /// Set the gas cost from a single VP run. It consumes the [`VpGasMeter`] instance which shouldn't be accessed passed this point. + /// Set the gas cost from a single VP run. It consumes the [`VpGasMeter`] + /// instance which shouldn't be accessed passed this point. pub fn set(&mut self, vp_gas_meter: VpGasMeter) -> Result<()> { debug_assert_eq!(self.max, None); debug_assert!(self.rest.is_empty()); diff --git a/core/src/ledger/parameters/mod.rs b/core/src/ledger/parameters/mod.rs index d1bef59b9d..df4ca4e41e 100644 --- a/core/src/ledger/parameters/mod.rs +++ b/core/src/ledger/parameters/mod.rs @@ -9,7 +9,7 @@ use thiserror::Error; use super::storage::types; use super::storage_api::{self, ResultExt, StorageRead, StorageWrite}; -use crate::ledger::storage::{self as ledger_storage}; +use crate::ledger::storage as ledger_storage; use crate::types::address::{Address, InternalAddress}; use crate::types::chain::ProposalBytes; use crate::types::hash::Hash; diff --git a/core/src/ledger/storage_api/collections/lazy_map.rs b/core/src/ledger/storage_api/collections/lazy_map.rs index 9c9603e851..c1e8ae6dbf 100644 --- a/core/src/ledger/storage_api/collections/lazy_map.rs +++ b/core/src/ledger/storage_api/collections/lazy_map.rs @@ -405,11 +405,11 @@ where storage: &'iter impl StorageRead, ) -> Result< impl Iterator< - Item = Result<( - ::SubKey, - ::Value, - )>, - > + 'iter, + Item = Result<( + ::SubKey, + ::Value, + )>, + > + 'iter, > { let iter = storage_api::iter_prefix_with_filter( storage, diff --git a/core/src/ledger/testnet_pow.rs b/core/src/ledger/testnet_pow.rs index 4eccaab40f..bed2273a70 100644 --- a/core/src/ledger/testnet_pow.rs +++ b/core/src/ledger/testnet_pow.rs @@ -297,10 +297,14 @@ pub fn is_counter_key<'a>( faucet_address: &Address, ) -> Option<&'a Address> { match &key.segments[..] { - [DbKeySeg::AddressSeg(address), DbKeySeg::StringSeg(sub_key), DbKeySeg::StringSeg(data), DbKeySeg::AddressSeg(owner)] - if address == faucet_address - && sub_key.as_str() == Keys::VALUES.counters - && data.as_str() == lazy_map::DATA_SUBKEY => + [ + DbKeySeg::AddressSeg(address), + DbKeySeg::StringSeg(sub_key), + DbKeySeg::StringSeg(data), + DbKeySeg::AddressSeg(owner), + ] if address == faucet_address + && sub_key.as_str() == Keys::VALUES.counters + && data.as_str() == lazy_map::DATA_SUBKEY => { Some(owner) } @@ -413,11 +417,7 @@ pub struct Difficulty(u8); impl Difficulty { /// The value must be between `0..=9` (inclusive upper bound). pub fn try_new(raw: u8) -> Option { - if raw > 9 { - None - } else { - Some(Self(raw)) - } + if raw > 9 { None } else { Some(Self(raw)) } } } diff --git a/core/src/types/internal.rs b/core/src/types/internal.rs index 4534832c69..3987b18e95 100644 --- a/core/src/types/internal.rs +++ b/core/src/types/internal.rs @@ -40,11 +40,7 @@ impl HostEnvResult { impl From for HostEnvResult { fn from(success: bool) -> Self { - if success { - Self::Success - } else { - Self::Fail - } + if success { Self::Success } else { Self::Fail } } } diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 94c7b97f5f..119156fd6f 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -149,6 +149,7 @@ where } /// Execute a transaction code. Returns verifiers requested by the transaction. +#[allow(clippy::too_many_arguments)] fn execute_tx( tx: &Tx, tx_index: &TxIndex, @@ -329,7 +330,7 @@ where }; gas_meter - .add_compiling_gas(vp_code_hash.len()) + .add_compiling_gas(vp_code.len()) .map_err(Error::GasError)?; add_precomputed_gas( @@ -338,8 +339,10 @@ where &vp_code_hash.to_string().to_ascii_lowercase(), )?; - // NOTE: because of the whitelisted gas and the gas metering for the exposed vm env functions, - // the first signature verification (if any) is accounted twice + // NOTE: because of the whitelisted gas and the gas metering + // for the exposed vm env functions, + // the first signature verification (if any) is accounted + // twice wasm::run::vp( &vp_code_hash, tx, diff --git a/shared/src/ledger/queries/mod.rs b/shared/src/ledger/queries/mod.rs index 18c0b4f970..b608572acb 100644 --- a/shared/src/ledger/queries/mod.rs +++ b/shared/src/ledger/queries/mod.rs @@ -193,7 +193,7 @@ mod testing { { #[allow(dead_code)] /// Initialize a test client for the given root RPC router - pub fn new(rpc: RPC) -> Self { + pub fn new(rpc: RPC) -> Self { // Initialize the `TestClient` let mut wl_storage = TestWlStorage::default(); @@ -203,11 +203,14 @@ mod testing { namada_core::ledger::parameters::storage::get_gas_table_storage_key(); wl_storage .storage - .write(&gas_table_key, - namada_core::ledger::storage::types::encode(&gas_table)) + .write( + &gas_table_key, + namada_core::ledger::storage::types::encode(&gas_table), + ) .expect( - "Gas table parameter must be initialized in the genesis block", - ); + "Gas table parameter must be initialized in the genesis \ + block", + ); let max_block_gas_key = namada_core::ledger::parameters::storage::get_max_block_gas_key( diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 4757a6c43e..4c9f1ea258 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -5,7 +5,6 @@ use masp_primitives::sapling::Node; use namada_core::types::address::Address; use namada_core::types::hash::Hash; use namada_core::types::storage::BlockResults; -use std::collections::BTreeMap; use crate::ledger::events::log::dumb_queries; use crate::ledger::events::Event; @@ -70,6 +69,8 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { + use std::collections::BTreeMap; + use namada_core::ledger::gas::TxGasMeter; use namada_core::ledger::parameters; diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index d22b68ff00..9592affd01 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -1786,7 +1786,7 @@ where let tx = unsafe { env.ctx.tx.get() }; Ok(HostEnvResult::from(vp_host_fns::verify_tx_signature( - gas_meter, &tx, &pk, &sig, + gas_meter, tx, &pk, &sig, )?) .to_i64()) } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 6aa8a11a65..9d92ad4b7c 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1250,6 +1250,8 @@ fn masp_incentives() -> Result<()> { "--token", ETH, "--amount", + "30", + "--gas-limit", "100", "--node", &validator_one_rpc @@ -1889,12 +1891,8 @@ fn pos_bonds() -> Result<()> { "validator-0", "--amount", "10000.0", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "100", "--node", &validator_one_rpc, ]; @@ -1912,12 +1910,8 @@ fn pos_bonds() -> Result<()> { BERTHA, "--amount", "5000.0", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "100", "--node", &validator_one_rpc, ]; @@ -1932,12 +1926,8 @@ fn pos_bonds() -> Result<()> { "validator-0", "--amount", "5100.0", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "100", "--node", &validator_one_rpc, ]; @@ -1955,12 +1945,8 @@ fn pos_bonds() -> Result<()> { BERTHA, "--amount", "3200.", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "100", "--node", &validator_one_rpc, ]; @@ -1999,12 +1985,8 @@ fn pos_bonds() -> Result<()> { "withdraw", "--validator", "validator-0", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "100", "--node", &validator_one_rpc, ]; @@ -2020,12 +2002,8 @@ fn pos_bonds() -> Result<()> { "validator-0", "--source", BERTHA, - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "100", "--node", &validator_one_rpc, ]; @@ -2549,7 +2527,6 @@ fn ledger_many_txs_in_a_block() -> Result<()> { "--gas-limit", "100", "--node", - &validator_one_rpc, ]); // 2. Spawn threads each submitting token transfer tx @@ -3002,12 +2979,8 @@ fn eth_governance_proposal() -> Result<()> { BERTHA, "--amount", "900", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "100", "--ledger-address", &validator_one_rpc, ]; @@ -3025,6 +2998,8 @@ fn eth_governance_proposal() -> Result<()> { "init-proposal", "--data-path", valid_proposal_json_path.to_str().unwrap(), + "--gas-limit", + "100", "--ledger-address", &validator_one_rpc, ]; @@ -3103,6 +3078,8 @@ fn eth_governance_proposal() -> Result<()> { &vote_arg, "--signer", BERTHA, + "--gas-limit", + "100", "--ledger-address", &validator_one_rpc, ]; @@ -3124,6 +3101,8 @@ fn eth_governance_proposal() -> Result<()> { &vote_arg, "--signer", "validator-0", + "--gas-limit", + "100", "--ledger-address", &validator_one_rpc, ]; @@ -3227,12 +3206,8 @@ fn pgf_governance_proposal() -> Result<()> { BERTHA, "--amount", "900", - "--gas-amount", - "0", "--gas-limit", - "0", - "--gas-token", - NAM, + "100", "--ledger-address", &validator_one_rpc, ]; @@ -3250,6 +3225,8 @@ fn pgf_governance_proposal() -> Result<()> { "init-proposal", "--data-path", valid_proposal_json_path.to_str().unwrap(), + "--gas-limit", + "100", "--ledger-address", &validator_one_rpc, ]; @@ -3266,6 +3243,8 @@ fn pgf_governance_proposal() -> Result<()> { "init-proposal", "--data-path", valid_proposal_json_path.to_str().unwrap(), + "--gas-limit", + "100", "--ledger-address", &validator_one_rpc, ]; @@ -3348,6 +3327,8 @@ fn pgf_governance_proposal() -> Result<()> { &arg_vote, "--signer", "validator-0", + "--gas-limit", + "100", "--ledger-address", &validator_one_rpc, ]; @@ -3374,6 +3355,8 @@ fn pgf_governance_proposal() -> Result<()> { &different_vote, "--signer", BERTHA, + "--gas-limit", + "100", "--ledger-address", &validator_one_rpc, ]; @@ -3393,6 +3376,8 @@ fn pgf_governance_proposal() -> Result<()> { &different_vote, "--signer", BERTHA, + "--gas-limit", + "100", "--ledger-address", &validator_one_rpc, ]; diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 4332ff331a..61485593e2 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -890,18 +890,18 @@ pub fn get_all_wasms_hashes( ) -> Vec { let checksums_path = working_dir.join("wasm/checksums.json"); let checksums_content = fs::read_to_string(checksums_path).unwrap(); - let mut checksums: HashMap> = + let checksums: HashMap> = serde_json::from_str(&checksums_content).unwrap(); let filter_prefix = filter.unwrap_or_default(); + checksums - .values_mut() - .filter_map(|wasm| { - let hash = wasm.get_mut("hash").expect("Missing hash in checksum"); - if hash.contains(filter_prefix) { + .iter() + .filter_map(|(name, info)| { + if name.contains(filter_prefix) { Some( - hash.split('.').collect::>()[1] - .to_owned() - .to_lowercase(), + info.get("hash") + .expect("Missing hash in checksum") + .to_owned(), ) } else { None @@ -920,9 +920,7 @@ pub fn get_all_wasms_gas(working_dir: &Path) -> BTreeMap { .values() .map(|map| { ( - map.get("hash").unwrap().split('.').collect::>()[1] - .to_owned() - .to_lowercase(), + map.get("hash").unwrap().to_lowercase(), map.get("gas").unwrap().parse::().unwrap(), ) }) diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index e591e281bf..7cea4d53b4 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -1523,29 +1523,31 @@ pub mod testing { let arb_delta = prop_oneof![(-(u32::MAX as i128)..0), (1..=u32::MAX as i128),]; - prop_oneof![( - arb_address_or_validator.clone(), - arb_address_or_validator, - arb_offset, - arb_delta, - ) - .prop_map(|(validator, owner, offset, delta)| { - vec![ - // We have to ensure that the addresses exists - PosStorageChange::SpawnAccount { - address: validator.clone(), - }, - PosStorageChange::SpawnAccount { - address: owner.clone(), - }, - PosStorageChange::Bond { - owner, - validator, - delta, - offset, - }, - ] - })] + prop_oneof![ + ( + arb_address_or_validator.clone(), + arb_address_or_validator, + arb_offset, + arb_delta, + ) + .prop_map(|(validator, owner, offset, delta)| { + vec![ + // We have to ensure that the addresses exists + PosStorageChange::SpawnAccount { + address: validator.clone(), + }, + PosStorageChange::SpawnAccount { + address: owner.clone(), + }, + PosStorageChange::Bond { + owner, + validator, + delta, + offset, + }, + ] + }) + ] } impl InvalidPosAction { diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 1a9474c6cb..1ad4b22599 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -133,11 +133,13 @@ mod tests { // Trying to delete a validity predicate should fail let key = storage::Key::validity_predicate(&test_account); - assert!(panic::catch_unwind(|| { tx::ctx().delete(&key).unwrap() }) - .err() - .map(|a| a.downcast_ref::().cloned().unwrap()) - .unwrap() - .contains("CannotDeleteVp")); + assert!( + panic::catch_unwind(|| { tx::ctx().delete(&key).unwrap() }) + .err() + .map(|a| a.downcast_ref::().cloned().unwrap()) + .unwrap() + .contains("CannotDeleteVp") + ); } #[test] @@ -468,17 +470,21 @@ mod tests { .expect("decoding signed data we just signed") }); assert_eq!(&signed_tx_data.data, data); - assert!(vp::CTX - .verify_tx_signature(&pk, &signed_tx_data.sig) - .unwrap()); + assert!( + vp::CTX + .verify_tx_signature(&pk, &signed_tx_data.sig) + .unwrap() + ); let other_keypair = key::testing::keypair_2(); - assert!(!vp::CTX - .verify_tx_signature( - &other_keypair.ref_to(), - &signed_tx_data.sig - ) - .unwrap()); + assert!( + !vp::CTX + .verify_tx_signature( + &other_keypair.ref_to(), + &signed_tx_data.sig + ) + .unwrap() + ); } } diff --git a/wasm/checksums.py b/wasm/checksums.py index 31590cb639..aa5a94164d 100644 --- a/wasm/checksums.py +++ b/wasm/checksums.py @@ -5,6 +5,7 @@ gas = json.load(open("wasm/gas.json")) checksums = {} +updated_wasms = [] for wasm in sorted(glob.glob("wasm/*.wasm")): basename = os.path.basename(wasm) @@ -14,14 +15,14 @@ else os.path.splitext(basename)[0].split(".")[0] ) file_key = "{}.wasm".format(file_name) + file_hash = hashlib.sha256(open(wasm, "rb").read()).hexdigest() + checksums[file_key] = {"hash": file_hash, "gas": str(gas[file_key])} + extended_file_name = "{}.{}.wasm".format( file_name, hashlib.sha256(open(wasm, "rb").read()).hexdigest() ) - checksums[file_key] = {"hash": extended_file_name, "gas": str(gas[file_key])} - - os.rename(wasm, "wasm/{}".format(checksums[file_key]["hash"])) - -updated_wasms = [value["hash"] for value in checksums.values()] + os.rename(wasm, "wasm/{}".format(extended_file_name)) + updated_wasms.append(extended_file_name) for wasm in sorted(glob.glob("wasm/*.wasm")): basename = os.path.basename(wasm) From 44cf55fdab6c37b5a7cb40f1cbe31c9b14681f07 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Sat, 13 May 2023 17:59:50 +0200 Subject: [PATCH 10/23] Fixes benchmarks Date: Sat May 13 17:59:50 2023 +0200 --- benches/Cargo.toml | 7 +- benches/mod.rs | 95 +++++++++++---------- benches/native_vps.rs | 106 ++++++++++------------- benches/txs.rs | 30 ++++--- benches/vps.rs | 190 +++++++++++++++++++++--------------------- 5 files changed, 212 insertions(+), 216 deletions(-) diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 86e8c95b73..5acf2042ad 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -39,12 +39,15 @@ path = "host_env.rs" [dependencies] async-trait = "0.1.51" borsh = "0.9.0" -ferveo-common = { git = "https://github.com/anoma/ferveo" } +ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} +ibc-relayer = { version = "0.22.0", default-features = false } +ibc-relayer-types = { version = "0.22.0", default-features = false } +ibc-proto = { version = "0.26.0", default-features = false } masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } namada = { path = "../shared" } namada_apps = { path = "../apps" } namada_test_utils = { path = "../test_utils" } -prost = "0.9.0" +prost = "0.11.6" rand = "0.8" rand_core = "0.6" rust_decimal = "1.26.1" diff --git a/benches/mod.rs b/benches/mod.rs index ce5804d8a2..3e5ef9d436 100644 --- a/benches/mod.rs +++ b/benches/mod.rs @@ -17,19 +17,18 @@ use std::ops::{Deref, DerefMut}; use borsh::BorshSerialize; +use ibc_proto::google::protobuf::Any; use masp_primitives::zip32::ExtendedFullViewingKey; -use namada::core::ledger::ibc::actions; +use namada::core::ledger::ibc::storage::port_key; use namada::core::types::address::{self, Address}; use namada::core::types::key::common::SecretKey; use namada::core::types::storage::Key; use namada::core::types::token::{Amount, Transfer}; -use namada::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; +use namada::ibc::applications::transfer::msgs::transfer::MsgTransfer; use namada::ibc::clients::ics07_tendermint::client_state::{ AllowUpdate, ClientState, }; use namada::ibc::clients::ics07_tendermint::consensus_state::ConsensusState; -use namada::ibc::core::ics02_client::client_consensus::AnyConsensusState; -use namada::ibc::core::ics02_client::client_state::AnyClientState; use namada::ibc::core::ics02_client::client_type::ClientType; use namada::ibc::core::ics02_client::trust_threshold::TrustThreshold; use namada::ibc::core::ics03_connection::connection::{ @@ -39,27 +38,31 @@ use namada::ibc::core::ics03_connection::version::Version; use namada::ibc::core::ics04_channel::channel::{ ChannelEnd, Counterparty as ChannelCounterparty, Order, State, }; +use namada::ibc::core::ics04_channel::timeout::TimeoutHeight; use namada::ibc::core::ics04_channel::Version as ChannelVersion; -use namada::ibc::core::ics23_commitment::commitment::CommitmentRoot; +use namada::ibc::core::ics23_commitment::commitment::{ + CommitmentPrefix, CommitmentRoot, +}; use namada::ibc::core::ics23_commitment::specs::ProofSpecs; use namada::ibc::core::ics24_host::identifier::{ - ChainId as IbcChainId, ChannelId, ClientId, ConnectionId, PortId, + ChainId as IbcChainId, ChannelId, ClientId, ConnectionId, PortChannelId, + PortId, }; -use namada::ibc::core::ics24_host::path::{ChannelEndsPath, ConnectionsPath}; use namada::ibc::core::ics24_host::Path as IbcPath; use namada::ibc::signer::Signer; use namada::ibc::timestamp::Timestamp as IbcTimestamp; use namada::ibc::tx_msg::Msg; use namada::ibc::Height as IbcHeight; use namada::ibc_proto::cosmos::base::v1beta1::Coin; +use namada::ibc_proto::protobuf::Protobuf; use namada::ledger::gas::TxGasMeter; +use namada::ledger::ibc::storage::{channel_key, connection_key}; use namada::ledger::queries::{ Client, EncodedResponseQuery, RequestCtx, RequestQuery, Router, RPC, }; use namada::proof_of_stake; use namada::proto::Tx; use namada::tendermint::Hash; -use namada::tendermint_proto::Protobuf; use namada::types::address::InternalAddress; use namada::types::chain::ChainId; use namada::types::masp::{ @@ -84,6 +87,7 @@ use namada_apps::wallet::defaults; use namada_apps::{config, wasm_loader}; use namada_test_utils::tx_data::TxWriteData; use rand_core::OsRng; +use std::str::FromStr; use tempfile::TempDir; pub const WASM_DIR: &str = "../wasm"; @@ -97,6 +101,7 @@ pub const TX_REVEAL_PK_WASM: &str = "tx_reveal_pk.wasm"; pub const TX_CHANGE_VALIDATOR_COMMISSION_WASM: &str = "tx_change_validator_commission.wasm"; pub const TX_IBC_WASM: &str = "tx_ibc.wasm"; +pub const VP_VALIDATOR_WASM: &str = "vp_validator.wasm"; pub const ALBERT_PAYMENT_ADDRESS: &str = "albert_payment"; pub const ALBERT_SPENDING_KEY: &str = "albert_spending"; @@ -210,7 +215,7 @@ impl BenchShell { &mut self.inner.wl_storage.write_log, &mut TxGasMeter::new(u64::MAX), &TxIndex(0), - &tx.code, + &tx.code_or_hash, tx.data.as_ref().unwrap(), &mut self.inner.vp_wasm_cache, &mut self.inner.tx_wasm_cache, @@ -240,14 +245,16 @@ impl BenchShell { pub fn init_ibc_channel(&mut self) { // Set connection open - let client_id = ClientId::new(ClientType::Tendermint, 1).unwrap(); + let client_id = + ClientId::new(ClientType::new("01-tendermint".to_string()), 1) + .unwrap(); let connection = ConnectionEnd::new( ConnectionState::Open, client_id.clone(), Counterparty::new( client_id, Some(ConnectionId::new(1)), - actions::commitment_prefix(), + CommitmentPrefix::try_from(b"ibc".to_vec()).unwrap(), ), vec![Version::default()], std::time::Duration::new(100, 0), @@ -256,24 +263,14 @@ impl BenchShell { let addr_key = Key::from(Address::Internal(InternalAddress::Ibc).to_db_key()); - let path = IbcPath::Connections(ConnectionsPath(ConnectionId::new(1))); - let connection_key = - addr_key.join(&Key::parse(path.to_string()).unwrap()); - + let connection_key = connection_key(&ConnectionId::new(1)); self.wl_storage .storage .write(&connection_key, connection.encode_vec().unwrap()) .unwrap(); // Set port - let path = Key::parse( - IbcPath::Ports(namada::ibc::core::ics24_host::path::PortsPath( - PortId::transfer(), - )) - .to_string(), - ) - .unwrap(); - let port_key = addr_key.join(&path); + let port_key = port_key(&PortId::transfer()); let index_key = addr_key .join(&Key::from("capabilities/index".to_string().to_db_key())); @@ -302,20 +299,21 @@ impl BenchShell { Order::Unordered, counterparty, vec![ConnectionId::new(1)], - ChannelVersion::ics20(), + ChannelVersion::new("ics20-1".to_string()), ); - let path = IbcPath::ChannelEnds(ChannelEndsPath( - PortId::transfer(), + let channel_key = channel_key(&PortChannelId::new( ChannelId::new(5), + PortId::transfer(), )); - let channel_key = addr_key.join(&Key::parse(path.to_string()).unwrap()); self.wl_storage .storage .write(&channel_key, channel.encode_vec().unwrap()) .unwrap(); // Set client state - let client_id = ClientId::new(ClientType::Tendermint, 1).unwrap(); + let client_id = + ClientId::new(ClientType::new("01-tendermint".to_string()), 1) + .unwrap(); let client_state_key = addr_key.join(&Key::from( IbcPath::ClientState( namada::ibc::core::ics24_host::path::ClientStatePath( @@ -331,21 +329,23 @@ impl BenchShell { std::time::Duration::new(1, 0), std::time::Duration::new(2, 0), std::time::Duration::new(1, 0), - IbcHeight::new(0, 1), + IbcHeight::new(0, 1).unwrap(), ProofSpecs::cosmos(), vec![], AllowUpdate { after_expiry: true, after_misbehaviour: true, }, + None, ) .unwrap(); - let bytes = AnyClientState::Tendermint(client_state) - .encode_vec() - .expect("encoding failed"); self.wl_storage .storage - .write(&client_state_key, bytes) + .write( + &client_state_key, + >::encode_vec(&client_state) + .unwrap(), + ) .expect("write failed"); // Set consensus state @@ -369,12 +369,13 @@ impl BenchShell { next_validators_hash: Hash::Sha256([0u8; 32]), }; - let bytes = AnyConsensusState::Tendermint(consensus_state) - .encode_vec() - .unwrap(); self.wl_storage .storage - .write(&consensus_key, bytes) + .write( + &consensus_key, + >::encode_vec(&consensus_state) + .unwrap(), + ) .unwrap(); } } @@ -415,25 +416,27 @@ pub fn generate_foreign_key_tx(signer: &SecretKey) -> Tx { } pub fn generate_ibc_transfer_tx() -> Tx { - let token = Some(Coin { + let token = Coin { denom: address::nam().to_string(), amount: Amount::whole(1000).to_string(), - }); + }; - let timeout_height = IbcHeight::new(0, 100); + let timeout_height = TimeoutHeight::At(IbcHeight::new(0, 100).unwrap()); let now: namada::tendermint::Time = DateTimeUtc::now().try_into().unwrap(); let now: IbcTimestamp = now.into(); let timeout_timestamp = (now + std::time::Duration::new(3600, 0)).unwrap(); let msg = MsgTransfer { - source_port: PortId::transfer(), - source_channel: ChannelId::new(5), + port_id_on_a: PortId::transfer(), + chan_id_on_a: ChannelId::new(5), token, - sender: Signer::new(defaults::albert_address()), - receiver: Signer::new(defaults::bertha_address()), - timeout_height, - timeout_timestamp, + sender: Signer::from_str(&defaults::albert_address().to_string()) + .unwrap(), + receiver: Signer::from_str(&defaults::bertha_address().to_string()) + .unwrap(), + timeout_height_on_b: timeout_height, + timeout_timestamp_on_b: timeout_timestamp, }; let any_msg = msg.to_any(); let mut data = vec![]; diff --git a/benches/native_vps.rs b/benches/native_vps.rs index a528e74de6..92a3799f6a 100644 --- a/benches/native_vps.rs +++ b/benches/native_vps.rs @@ -1,17 +1,16 @@ use std::collections::BTreeSet; +use std::str::FromStr; use criterion::{criterion_group, criterion_main, Criterion}; -use namada::core::ledger::ibc::actions; use namada::core::types::address::{self, Address}; use namada::ibc::core::ics02_client::client_type::ClientType; use namada::ibc::core::ics03_connection::connection::Counterparty; use namada::ibc::core::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; use namada::ibc::core::ics03_connection::version::Version; -use namada::ibc::core::ics04_channel::channel::{ - ChannelEnd, Counterparty as ChannelCounterparty, Order, State, -}; +use namada::ibc::core::ics04_channel::channel::Order; use namada::ibc::core::ics04_channel::msgs::chan_open_init::MsgChannelOpenInit; use namada::ibc::core::ics04_channel::Version as ChannelVersion; +use namada::ibc::core::ics23_commitment::commitment::CommitmentPrefix; use namada::ibc::core::ics24_host::identifier::{ ChannelId, ClientId, ConnectionId, PortId, }; @@ -190,15 +189,13 @@ fn governance(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!( - governance - .validate_tx( - signed_tx.data.as_ref().unwrap(), - governance.ctx.keys_changed, - governance.ctx.verifiers, - ) - .unwrap() - ) + assert!(governance + .validate_tx( + signed_tx.data.as_ref().unwrap(), + governance.ctx.keys_changed, + governance.ctx.verifiers, + ) + .unwrap()) }) }); } @@ -257,15 +254,13 @@ fn slash_fund(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!( - slash_fund - .validate_tx( - tx.data.as_ref().unwrap(), - slash_fund.ctx.keys_changed, - slash_fund.ctx.verifiers, - ) - .unwrap() - ) + assert!(slash_fund + .validate_tx( + tx.data.as_ref().unwrap(), + slash_fund.ctx.keys_changed, + slash_fund.ctx.verifiers, + ) + .unwrap()) }) }); } @@ -276,20 +271,22 @@ fn slash_fund(c: &mut Criterion) { fn ibc(c: &mut Criterion) { let mut group = c.benchmark_group("vp_ibc"); - let foreign_key_write = - generate_foreign_key_tx(&defaults::albert_keypair()); - // Connection handshake let msg = MsgConnectionOpenInit { - client_id: ClientId::new(ClientType::Tendermint, 1).unwrap(), + client_id_on_a: ClientId::new( + ClientType::new("01-tendermint".to_string()), + 1, + ) + .unwrap(), counterparty: Counterparty::new( - ClientId::new(ClientType::Tendermint, 1).unwrap(), + ClientId::from_str(&"01-tendermint-1").unwrap(), Some(ConnectionId::new(1)), - actions::commitment_prefix(), + CommitmentPrefix::try_from(b"ibc".to_vec()).unwrap(), ), version: Some(Version::default()), delay_period: std::time::Duration::new(100, 0), - signer: Signer::new(defaults::albert_address()), + signer: Signer::from_str(&defaults::albert_address().to_string()) + .unwrap(), }; let any_msg = msg.to_any(); let mut data = vec![]; @@ -306,19 +303,14 @@ fn ibc(c: &mut Criterion) { .sign(&defaults::albert_keypair()); // Channel handshake - let counterparty = - ChannelCounterparty::new(PortId::transfer(), Some(ChannelId::new(5))); - let channel = ChannelEnd::new( - State::Init, - Order::Unordered, - counterparty, - vec![ConnectionId::new(1)], - ChannelVersion::ics20(), - ); let msg = MsgChannelOpenInit { - port_id: PortId::transfer(), - channel, - signer: Signer::new(defaults::albert_address()), + port_id_on_a: PortId::transfer(), + connection_hops_on_a: vec![ConnectionId::new(1)], + port_id_on_b: PortId::transfer(), + ordering: Order::Unordered, + signer: Signer::from_str(&defaults::albert_address().to_string()) + .unwrap(), + version_proposal: ChannelVersion::new("ics20-1".to_string()), }; let any_msg = msg.to_any(); @@ -338,19 +330,11 @@ fn ibc(c: &mut Criterion) { // Ibc transfer let outgoing_transfer = generate_ibc_transfer_tx(); - for (signed_tx, bench_name) in [ - foreign_key_write, - open_connection, - open_channel, - outgoing_transfer, - ] - .iter() - .zip([ - "foreign_key_write", - "open_connection", - "open_channel", - "outgoing_transfer", - ]) { + for (signed_tx, bench_name) in + [open_connection, open_channel, outgoing_transfer] + .iter() + .zip(["open_connection", "open_channel", "outgoing_transfer"]) + { let mut shell = BenchShell::default(); shell.init_ibc_channel(); @@ -376,14 +360,13 @@ fn ibc(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!( - ibc.validate_tx( + assert!(ibc + .validate_tx( signed_tx.data.as_ref().unwrap(), ibc.ctx.keys_changed, ibc.ctx.verifiers, ) - .unwrap() - ) + .unwrap()) }) }); } @@ -436,14 +419,13 @@ fn ibc_token(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!( - ibc.validate_tx( + assert!(ibc + .validate_tx( signed_tx.data.as_ref().unwrap(), ibc.ctx.keys_changed, ibc.ctx.verifiers, ) - .unwrap() - ) + .unwrap()) }) }); } diff --git a/benches/txs.rs b/benches/txs.rs index c749be9a21..ae05465835 100644 --- a/benches/txs.rs +++ b/benches/txs.rs @@ -10,8 +10,10 @@ use namada::proof_of_stake; use namada::proto::Tx; use namada::types::chain::ChainId; use namada::types::governance::{ProposalVote, VoteType}; +use namada::types::hash::Hash; use namada::types::key::{ed25519, secp256k1}; use namada::types::masp::{TransferSource, TransferTarget}; +use namada::types::storage::Key; use namada::types::transaction::governance::{ InitProposalData, ProposalType, VoteProposalData, }; @@ -26,7 +28,7 @@ use namada_benches::{ ALBERT_PAYMENT_ADDRESS, ALBERT_SPENDING_KEY, BERTHA_PAYMENT_ADDRESS, TX_BOND_WASM, TX_CHANGE_VALIDATOR_COMMISSION_WASM, TX_INIT_PROPOSAL_WASM, TX_REVEAL_PK_WASM, TX_UNBOND_WASM, TX_UPDATE_VP_WASM, - TX_VOTE_PROPOSAL_WASM, WASM_DIR, + TX_VOTE_PROPOSAL_WASM, VP_VALIDATOR_WASM, WASM_DIR, }; use rand::rngs::StdRng; use rand::SeedableRng; @@ -294,14 +296,15 @@ fn reveal_pk(c: &mut Criterion) { } fn update_vp(c: &mut Criterion) { + let shell = BenchShell::default(); + let vp_code_hash: Hash = shell + .read_storage_key(&Key::wasm_hash(VP_VALIDATOR_WASM)) + .unwrap(); let signed_tx = generate_tx( TX_UPDATE_VP_WASM, UpdateVp { addr: defaults::albert_address(), - vp_code: wasm_loader::read_wasm_or_exit( - WASM_DIR, - "vp_validator.wasm", - ), + vp_code_hash, }, &defaults::albert_keypair(), ); @@ -322,13 +325,17 @@ fn init_account(c: &mut Criterion) { .try_to_sk() .unwrap(); + let shell = BenchShell::default(); + let vp_code_hash: Hash = shell + .read_storage_key(&Key::wasm_hash(VP_VALIDATOR_WASM)) + .unwrap(); let signed_tx = generate_tx( TX_INIT_ACCOUNT_WASM, InitAccount { public_key: new_account.to_public(), - vp_code: wasm_loader::read_wasm_or_exit(WASM_DIR, "vp_user.wasm"), + vp_code_hash, }, - &new_account, + &defaults::albert_keypair(), ); c.bench_function("init_account", |b| { @@ -475,6 +482,10 @@ fn init_validator(c: &mut Criterion) { .public() .into(); + let shell = BenchShell::default(); + let validator_vp_code_hash: Hash = shell + .read_storage_key(&Key::wasm_hash(VP_VALIDATOR_WASM)) + .unwrap(); let signed_tx = generate_tx( TX_INIT_VALIDATOR_WASM, InitValidator { @@ -484,10 +495,7 @@ fn init_validator(c: &mut Criterion) { dkg_key, commission_rate: Decimal::default(), max_commission_rate_change: Decimal::default(), - validator_vp_code: wasm_loader::read_wasm_or_exit( - WASM_DIR, - "vp_validator.wasm", - ), + validator_vp_code_hash, }, &defaults::albert_keypair(), ); diff --git a/benches/vps.rs b/benches/vps.rs index a3b874e278..4c0ea460c6 100644 --- a/benches/vps.rs +++ b/benches/vps.rs @@ -11,9 +11,10 @@ use namada::ledger::gas::VpGasMeter; use namada::proto::Tx; use namada::types::chain::ChainId; use namada::types::governance::{ProposalVote, VoteType}; +use namada::types::hash::Hash; use namada::types::key::ed25519; use namada::types::masp::{TransferSource, TransferTarget}; -use namada::types::storage::TxIndex; +use namada::types::storage::{Key, TxIndex}; use namada::types::transaction::governance::VoteProposalData; use namada::types::transaction::pos::{Bond, CommissionChange}; use namada::types::transaction::UpdateVp; @@ -25,18 +26,16 @@ use namada_benches::{ ALBERT_PAYMENT_ADDRESS, ALBERT_SPENDING_KEY, BERTHA_PAYMENT_ADDRESS, TX_BOND_WASM, TX_CHANGE_VALIDATOR_COMMISSION_WASM, TX_REVEAL_PK_WASM, TX_TRANSFER_WASM, TX_UNBOND_WASM, TX_UPDATE_VP_WASM, TX_VOTE_PROPOSAL_WASM, - WASM_DIR, + VP_VALIDATOR_WASM, WASM_DIR, }; use rust_decimal::Decimal; const VP_USER_WASM: &str = "vp_user.wasm"; const VP_TOKEN_WASM: &str = "vp_token.wasm"; const VP_IMPLICIT_WASM: &str = "vp_implicit.wasm"; -const VP_VALIDATOR_WASM: &str = "vp_validator.wasm"; const VP_MASP_WASM: &str = "vp_masp.wasm"; fn vp_user(c: &mut Criterion) { - let vp_code = wasm_loader::read_wasm_or_exit(WASM_DIR, VP_USER_WASM); let mut group = c.benchmark_group("vp_user"); let foreign_key_write = @@ -70,14 +69,15 @@ fn vp_user(c: &mut Criterion) { &defaults::bertha_keypair(), ); + let shell = BenchShell::default(); + let vp_validator_hash = shell + .read_storage_key(&Key::wasm_hash(VP_VALIDATOR_WASM)) + .unwrap(); let vp = generate_tx( TX_UPDATE_VP_WASM, UpdateVp { addr: defaults::albert_address(), - vp_code: wasm_loader::read_wasm_or_exit( - WASM_DIR, - "vp_validator.wasm", - ), + vp_code_hash: vp_validator_hash, }, &defaults::albert_keypair(), ); @@ -121,6 +121,9 @@ fn vp_user(c: &mut Criterion) { "vp", ]) { let mut shell = BenchShell::default(); + let vp_code_hash: Hash = shell + .read_storage_key(&Key::wasm_hash(VP_USER_WASM)) + .unwrap(); shell.execute_tx(signed_tx); let (verifiers, keys_changed) = shell .wl_storage @@ -129,22 +132,20 @@ fn vp_user(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!( - run::vp( - &vp_code, - signed_tx, - &TxIndex(0), - &defaults::albert_address(), - &shell.wl_storage.storage, - &shell.wl_storage.write_log, - &mut VpGasMeter::new(u64::MAX, 0), - &keys_changed, - &verifiers, - shell.vp_wasm_cache.clone(), - false, - ) - .unwrap() - ); + assert!(run::vp( + &vp_code_hash, + signed_tx, + &TxIndex(0), + &defaults::albert_address(), + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + &mut VpGasMeter::new(u64::MAX, 0), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + false, + ) + .unwrap()); }) }); } @@ -153,7 +154,6 @@ fn vp_user(c: &mut Criterion) { } fn vp_implicit(c: &mut Criterion) { - let vp_code = wasm_loader::read_wasm_or_exit(WASM_DIR, VP_IMPLICIT_WASM); let mut group = c.benchmark_group("vp_implicit"); let mut csprng = rand::rngs::OsRng {}; @@ -240,6 +240,9 @@ fn vp_implicit(c: &mut Criterion) { "governance_vote", ]) { let mut shell = BenchShell::default(); + let vp_code_hash: Hash = shell + .read_storage_key(&Key::wasm_hash(VP_IMPLICIT_WASM)) + .unwrap(); if bench_name != "reveal_pk" { // Reveal publick key @@ -264,22 +267,20 @@ fn vp_implicit(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!( - run::vp( - &vp_code, - tx, - &TxIndex(0), - &Address::from(&implicit_account.to_public()), - &shell.wl_storage.storage, - &shell.wl_storage.write_log, - &mut VpGasMeter::new(u64::MAX, 0), - &keys_changed, - &verifiers, - shell.vp_wasm_cache.clone(), - false, - ) - .unwrap() + assert!(run::vp( + &vp_code_hash, + tx, + &TxIndex(0), + &Address::from(&implicit_account.to_public()), + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + &mut VpGasMeter::new(u64::MAX, 0), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + false, ) + .unwrap()) }) }); } @@ -288,7 +289,10 @@ fn vp_implicit(c: &mut Criterion) { } fn vp_validator(c: &mut Criterion) { - let vp_code = wasm_loader::read_wasm_or_exit(WASM_DIR, VP_VALIDATOR_WASM); + let shell = BenchShell::default(); + let vp_code_hash: Hash = shell + .read_storage_key(&Key::wasm_hash(VP_VALIDATOR_WASM)) + .unwrap(); let mut group = c.benchmark_group("vp_validator"); let foreign_key_write = @@ -326,10 +330,7 @@ fn vp_validator(c: &mut Criterion) { TX_UPDATE_VP_WASM, UpdateVp { addr: defaults::validator_address(), - vp_code: wasm_loader::read_wasm_or_exit( - WASM_DIR, - "vp_validator.wasm", - ), + vp_code_hash: vp_code_hash.clone(), }, &defaults::validator_keypair(), ); @@ -393,22 +394,20 @@ fn vp_validator(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!( - run::vp( - &vp_code, - signed_tx, - &TxIndex(0), - &defaults::validator_address(), - &shell.wl_storage.storage, - &shell.wl_storage.write_log, - &mut VpGasMeter::new(u64::MAX, 0), - &keys_changed, - &verifiers, - shell.vp_wasm_cache.clone(), - false, - ) - .unwrap() - ); + assert!(run::vp( + &vp_code_hash, + signed_tx, + &TxIndex(0), + &defaults::validator_address(), + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + &mut VpGasMeter::new(u64::MAX, 0), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + false, + ) + .unwrap()); }) }); } @@ -417,7 +416,6 @@ fn vp_validator(c: &mut Criterion) { } fn vp_token(c: &mut Criterion) { - let vp_code = wasm_loader::read_wasm_or_exit(WASM_DIR, VP_TOKEN_WASM); let mut group = c.benchmark_group("vp_token"); let foreign_key_write = @@ -442,6 +440,9 @@ fn vp_token(c: &mut Criterion) { .zip(["foreign_key_write", "transfer"]) { let mut shell = BenchShell::default(); + let vp_code_hash: Hash = shell + .read_storage_key(&Key::wasm_hash(VP_TOKEN_WASM)) + .unwrap(); shell.execute_tx(signed_tx); let (verifiers, keys_changed) = shell .wl_storage @@ -450,29 +451,26 @@ fn vp_token(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!( - run::vp( - &vp_code, - signed_tx, - &TxIndex(0), - &defaults::albert_address(), - &shell.wl_storage.storage, - &shell.wl_storage.write_log, - &mut VpGasMeter::new(u64::MAX, 0), - &keys_changed, - &verifiers, - shell.vp_wasm_cache.clone(), - false, - ) - .unwrap() - ); + assert!(run::vp( + &vp_code_hash, + signed_tx, + &TxIndex(0), + &defaults::albert_address(), + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + &mut VpGasMeter::new(u64::MAX, 0), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + false, + ) + .unwrap()); }) }); } } fn vp_masp(c: &mut Criterion) { - let vp_code = wasm_loader::read_wasm_or_exit(WASM_DIR, VP_MASP_WASM); let mut group = c.benchmark_group("vp_masp"); let amount = Amount::whole(500); @@ -480,6 +478,10 @@ fn vp_masp(c: &mut Criterion) { for bench_name in ["shielding", "unshielding", "shielded"] { group.bench_function(bench_name, |b| { let mut shielded_ctx = BenchShieldedCtx::default(); + let vp_code_hash: Hash = shielded_ctx + .shell + .read_storage_key(&Key::wasm_hash(VP_MASP_WASM)) + .unwrap(); let albert_spending_key = shielded_ctx .ctx @@ -536,22 +538,20 @@ fn vp_masp(c: &mut Criterion) { .verifiers_and_changed_keys(&BTreeSet::default()); b.iter(|| { - assert!( - run::vp( - &vp_code, - &signed_tx, - &TxIndex(0), - &defaults::validator_address(), - &shielded_ctx.shell.wl_storage.storage, - &shielded_ctx.shell.wl_storage.write_log, - &mut VpGasMeter::new(u64::MAX, 0), - &keys_changed, - &verifiers, - shielded_ctx.shell.vp_wasm_cache.clone(), - false, - ) - .unwrap() - ); + assert!(run::vp( + &vp_code_hash, + &signed_tx, + &TxIndex(0), + &defaults::validator_address(), + &shielded_ctx.shell.wl_storage.storage, + &shielded_ctx.shell.wl_storage.write_log, + &mut VpGasMeter::new(u64::MAX, 0), + &keys_changed, + &verifiers, + shielded_ctx.shell.vp_wasm_cache.clone(), + false, + ) + .unwrap()); }) }); } From b7bf1571ce4cc87eab4cc15c8c445a59edae650f Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 15 May 2023 17:44:19 +0200 Subject: [PATCH 11/23] Removes mock gas meter in tx execution --- shared/src/ledger/protocol/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 119156fd6f..a74faa2520 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -206,13 +206,10 @@ where let empty = vec![]; let tx_data = tx.data.as_ref().unwrap_or(&empty); - // Mock gas meter to ignore runtime gas metering while we - // rely on the gas table - let mut mock_gas_meter = TxGasMeter::new(u64::MAX); wasm::run::tx( storage, write_log, - &mut mock_gas_meter, + tx_gas_meter, tx_index, &tx.code_or_hash, tx_data, From 36d81b3c96db813e0f63c7cfead88b12efa9fcec Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 16 May 2023 17:38:42 +0200 Subject: [PATCH 12/23] Optimizes gas accounting for tx hash --- apps/src/lib/node/ledger/shell/init_chain.rs | 5 + benches/mod.rs | 2 + core/src/ledger/gas.rs | 57 +++++++-- core/src/types/storage.rs | 11 ++ shared/src/ledger/protocol/mod.rs | 122 +++++++------------ shared/src/vm/wasm/run.rs | 79 ++++++++++-- tests/src/vm_host_env/tx.rs | 3 +- 7 files changed, 182 insertions(+), 97 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index ed2541804e..fa6fa3314e 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -115,6 +115,8 @@ where let code = wasm_loader::read_wasm(&self.wasm_dir, name) .map_err(Error::ReadingWasm)?; let code_hash = CodeHash::sha256(&code); + let code_len = u64::try_from(code.len()) + .map_err(|e| Error::LoadingWasm(e.to_string()))?; let checksum = info.get("hash").ok_or_else(|| { Error::LoadingWasm(format!( @@ -143,6 +145,9 @@ where let code_key = Key::wasm_code(&code_hash); self.wl_storage.write_bytes(&code_key, code)?; + let code_len_key = Key::wasm_code_len(&code_hash); + self.wl_storage.write(&code_len_key, code_len)?; + let hash_key = Key::wasm_hash(name); self.wl_storage.write_bytes(&hash_key, code_hash)?; } else { diff --git a/benches/mod.rs b/benches/mod.rs index 3e5ef9d436..7444c2b958 100644 --- a/benches/mod.rs +++ b/benches/mod.rs @@ -14,6 +14,7 @@ //! For more realistic results these benchmarks should be run on all the //! combination of supported OS/architecture. +use std::collections::BTreeMap; use std::ops::{Deref, DerefMut}; use borsh::BorshSerialize; @@ -214,6 +215,7 @@ impl BenchShell { &self.inner.wl_storage.storage, &mut self.inner.wl_storage.write_log, &mut TxGasMeter::new(u64::MAX), + &BTreeMap::default(), &TxIndex(0), &tx.code_or_hash, tx.data.as_ref().unwrap(), diff --git a/core/src/ledger/gas.rs b/core/src/ledger/gas.rs index 4895b3bd92..50fe28e8ec 100644 --- a/core/src/ledger/gas.rs +++ b/core/src/ledger/gas.rs @@ -13,9 +13,11 @@ pub enum Error { BlockGasExceeded, #[error("Overflow during gas operations")] GasOverflow, + #[error("Error converting to u64")] + ConversionError, } -const TX_SIZE_GAS_PER_BYTE: u64 = 1; +const TX_SIZE_GAS_PER_BYTE: u64 = 10; const COMPILE_GAS_PER_BYTE: u64 = 1; const PARALLEL_GAS_DIVIDER: u64 = 10; @@ -147,13 +149,38 @@ impl TxGasMeter { } /// Add the compiling cost proportionate to the code length - pub fn add_compiling_gas(&mut self, bytes_len: usize) -> Result<()> { - self.add(bytes_len as u64 * COMPILE_GAS_PER_BYTE) + pub fn add_compiling_gas(&mut self, bytes_len: u64) -> Result<()> { + self.add( + bytes_len + .checked_mul(COMPILE_GAS_PER_BYTE) + .ok_or(Error::GasOverflow)?, + ) } /// Add the gas for the space that the transaction requires in the block - pub fn add_tx_size_gas(&mut self, bytes_len: usize) -> Result<()> { - self.add(bytes_len as u64 * TX_SIZE_GAS_PER_BYTE) + pub fn add_tx_size_gas( + &mut self, + bytes_len: impl TryInto, + ) -> Result<()> { + let bytes_len = + bytes_len.try_into().map_err(|_| Error::ConversionError)?; + self.add( + bytes_len + .checked_mul(TX_SIZE_GAS_PER_BYTE) + .ok_or(Error::GasOverflow)?, + ) + } + + /// Add the gas for loading the transaction's code from storage + pub fn add_tx_load_from_storage_gas( + &mut self, + bytes_len: u64, + ) -> Result<()> { + self.add( + bytes_len + .checked_mul(MIN_STORAGE_GAS) + .ok_or(Error::GasOverflow)?, + ) } /// Add the gas cost used in validity predicates to the current transaction. @@ -202,8 +229,24 @@ impl VpGasMeter { } /// Add the compiling cost proportionate to the code length - pub fn add_compiling_gas(&mut self, bytes_len: usize) -> Result<()> { - self.add(bytes_len as u64 * COMPILE_GAS_PER_BYTE) + pub fn add_compiling_gas(&mut self, bytes_len: u64) -> Result<()> { + self.add( + bytes_len + .checked_mul(COMPILE_GAS_PER_BYTE) + .ok_or(Error::GasOverflow)?, + ) + } + + /// Add the gas for loading the non-native vp's code from storage + pub fn add_vp_load_from_storage_gas( + &mut self, + bytes_len: u64, + ) -> Result<()> { + self.add( + bytes_len + .checked_mul(MIN_STORAGE_GAS) + .ok_or(Error::GasOverflow)?, + ) } } diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index 9ceddecf15..9064463139 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -58,6 +58,8 @@ pub const RESERVED_VP_KEY: &str = "?"; pub const WASM_KEY_PREFIX: &str = "wasm"; /// The reserved storage key prefix for wasm codes pub const WASM_CODE_PREFIX: &str = "code"; +/// The reserved storage key prefix for wasm codes' length +pub const WASM_CODE_LEN_PREFIX: &str = "len"; /// The reserved storage key prefix for wasm code hashes pub const WASM_HASH_PREFIX: &str = "hash"; @@ -549,6 +551,15 @@ impl Key { Key { segments } } + /// Returns a key of the wasm code's length of the given hash + pub fn wasm_code_len(code_hash: &Hash) -> Self { + let mut segments = + Self::from(WASM_KEY_PREFIX.to_owned().to_db_key()).segments; + segments.push(DbKeySeg::StringSeg(WASM_CODE_LEN_PREFIX.to_owned())); + segments.push(DbKeySeg::StringSeg(code_hash.to_string())); + Key { segments } + } + /// Returns a key of the wasm code hash of the given code path pub fn wasm_hash(code_path: impl AsRef) -> Self { let mut segments = diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index a74faa2520..87df7e5e86 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -2,9 +2,10 @@ use std::collections::{BTreeMap, BTreeSet}; use std::panic; +use borsh::BorshDeserialize; use namada_core::ledger::gas::TxGasMeter; use namada_core::ledger::storage::write_log::StorageModification; -use namada_core::types::hash::{Hash, HASH_LENGTH}; +use namada_core::types::hash::Hash; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use thiserror::Error; @@ -72,6 +73,8 @@ pub enum Error { TxCodeHashConversion, #[error("Could not retrieve wasm code from storage for hash {0}")] MissingWasmCodeInStorage(Hash), + #[error("Failed type conversion: {0}")] + ConversionError(String), } /// Result of applying a transaction @@ -165,51 +168,13 @@ where H: 'static + StorageHasher + Sync, CA: 'static + WasmCacheAccess + Sync, { - // Always account for compilation gas cost - let (tx_hash, tx_code) = if tx.code_or_hash.len() == HASH_LENGTH { - // we assume that there is no wasm code with HASH_LENGTH - let code_hash = Hash::try_from(tx.code_or_hash.as_slice()) - .map_err(|_| Error::TxCodeHashConversion)?; - - let key = storage::Key::wasm_code(&code_hash); - let tx_code = match write_log.read(&key).0 { - Some(StorageModification::Write { value }) => value.clone(), - _ => storage - .read(&key) - .map_err(Error::StorageError)? - .0 - .ok_or_else(|| { - Error::MissingWasmCodeInStorage(code_hash.clone()) - })?, - }; - - (code_hash, std::borrow::Cow::Owned(tx_code)) - } else { - ( - Hash(tx.code_hash()), - std::borrow::Cow::Borrowed(&tx.code_or_hash), - ) - }; - - tx_gas_meter - .add_compiling_gas(tx_code.len()) - .map_err(Error::GasError)?; - let tx_hash = tx_hash.to_string().to_ascii_lowercase(); - let tx_gas_required = match gas_table.get(tx_hash.as_str()) { - Some(gas) => gas.to_owned(), - #[cfg(test)] - None => 1_000, - #[cfg(not(test))] - None => 0, // VPs will reject the non-whitelisted tx - }; - tx_gas_meter.add(tx_gas_required).map_err(Error::GasError)?; - let empty = vec![]; let tx_data = tx.data.as_ref().unwrap_or(&empty); wasm::run::tx( storage, write_log, tx_gas_meter, + gas_table, tx_index, &tx.code_or_hash, tx_data, @@ -307,34 +272,46 @@ where } }; - // Always account for compilation gas cost - let vp_code = { - let key = storage::Key::wasm_code(&vp_code_hash); - match write_log.read(&key).0 { - Some(StorageModification::Write { value }) => { - value.clone() - } - _ => storage - .read(&key) - .map_err(Error::StorageError)? - .0 - .ok_or_else(|| { - Error::MissingWasmCodeInStorage( - vp_code_hash.clone(), - ) - })?, + //FIXME: move gas inside vp? Yes but also change eval_vp to call vp instead of run_vp + // Compilation gas even if the compiled module is in cache + let key = storage::Key::wasm_code_len(&vp_code_hash); + let vp_code_len = match write_log.read(&key).0 { + Some(StorageModification::Write { value }) => { + u64::try_from_slice(value).map_err(|e| { + Error::ConversionError(e.to_string()) + }) } - }; - + _ => match storage + .read(&key) + .map_err(Error::StorageError)? + .0 + { + Some(v) => u64::try_from_slice(&v).map_err(|e| { + Error::ConversionError(e.to_string()) + }), + None => Err(Error::MissingWasmCodeInStorage( + vp_code_hash.clone(), + )), + }, + }?; gas_meter - .add_compiling_gas(vp_code.len()) + .add_compiling_gas(vp_code_len) + .map_err(Error::GasError)?; + gas_meter + .add_vp_load_from_storage_gas(vp_code_len) .map_err(Error::GasError)?; - add_precomputed_gas( - &mut gas_meter, - gas_table, - &vp_code_hash.to_string().to_ascii_lowercase(), - )?; + let vp_gas_required = match gas_table + .get(&vp_code_hash.to_string().to_ascii_lowercase()) + { + Some(gas) => gas.to_owned(), + #[cfg(test)] + None => 1_000, + #[cfg(not(test))] + None => 0, + }; + + gas_meter.add(vp_gas_required).map_err(Error::GasError)?; // NOTE: because of the whitelisted gas and the gas metering // for the exposed vm env functions, @@ -540,20 +517,3 @@ fn merge_vp_results( errors, }) } - -/// Add the precomputed cost for the vp -fn add_precomputed_gas( - gas_meter: &mut VpGasMeter, - gas_table: &BTreeMap, - vp: &str, -) -> Result<()> { - let vp_gas_required = match gas_table.get(vp) { - Some(gas) => gas.to_owned(), - #[cfg(test)] - None => 1_000, - #[cfg(not(test))] - None => 0, - }; - - gas_meter.add(vp_gas_required).map_err(Error::GasError) -} diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index 011e30a538..44ad175f65 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -1,9 +1,11 @@ //! Wasm runners -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use std::marker::PhantomData; -use namada_core::ledger::gas::TxGasMeter; +use borsh::BorshDeserialize; +use namada_core::ledger::gas::{self, TxGasMeter}; +use namada_core::ledger::storage::write_log::StorageModification; use parity_wasm::elements; use pwasm_utils::{self, rules}; use thiserror::Error; @@ -37,7 +39,7 @@ const WASM_STACK_LIMIT: u32 = u16::MAX as u32; pub enum Error { #[error("Memory error: {0}")] MemoryError(memory::Error), - #[error("Unable to inject gas meter")] + #[error("Unable to inject stack limiter")] StackLimiterInjection, #[error("Wasm deserialization error: {0}")] DeserializationError(elements::Error), @@ -72,6 +74,10 @@ pub enum Error { LoadWasmCode(String), #[error("Unable to find compiled wasm code")] NoCompiledWasmCode, + #[error("Error while accounting for gas: {0}")] + GasError(#[from] gas::Error), + #[error("Failed type conversion: {0}")] + ConversionError(String), } /// Result for functions that may fail @@ -84,6 +90,7 @@ pub fn tx( storage: &Storage, write_log: &mut WriteLog, gas_meter: &mut TxGasMeter, + gas_table: &BTreeMap, tx_index: &TxIndex, tx_code: impl AsRef<[u8]>, tx_data: impl AsRef<[u8]>, @@ -95,18 +102,66 @@ where H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - let (module, store) = if tx_code.as_ref().len() == HASH_LENGTH { + let (module, store, tx_hash) = if tx_code.as_ref().len() == HASH_LENGTH { // we assume that there is no wasm code with HASH_LENGTH let code_hash = Hash::try_from(tx_code.as_ref()).map_err(Error::CodeHash)?; - fetch_or_compile(tx_wasm_cache, &code_hash, write_log, storage)? + + // Compilation gas even if the compiled module is in cache + let key = Key::wasm_code_len(&code_hash); + let tx_len = match write_log.read(&key).0 { + Some(StorageModification::Write { value }) => { + u64::try_from_slice(value) + .map_err(|e| Error::ConversionError(e.to_string())) + } + _ => match storage + .read(&key) + .map_err(|e| { + Error::LoadWasmCode(format!( + "Read wasm code length failed from storage: key {}, \ + error {}", + key, e + )) + })? + .0 + { + Some(v) => u64::try_from_slice(&v) + .map_err(|e| Error::ConversionError(e.to_string())), + None => Err(Error::LoadWasmCode(format!( + "No wasm code length in storage: key {}", + key + ))), + }, + }?; + gas_meter.add_compiling_gas(tx_len)?; + gas_meter.add_tx_load_from_storage_gas(tx_len)?; + + let (module, store) = + fetch_or_compile(tx_wasm_cache, &code_hash, write_log, storage)?; + (module, store, code_hash) } else { - match tx_wasm_cache.compile_or_fetch(tx_code)? { - Some((module, store)) => (module, store), + // Compilation gas even if the compiled module is in cache + gas_meter.add_compiling_gas( + u64::try_from(tx_code.as_ref().len()) + .map_err(|e| Error::ConversionError(e.to_string()))?, + )?; + + match tx_wasm_cache.compile_or_fetch(tx_code.as_ref())? { + Some((module, store)) => (module, store, Hash::sha256(tx_code)), None => return Err(Error::NoCompiledWasmCode), } }; + let tx_gas_required = + match gas_table.get(&tx_hash.to_string().to_ascii_lowercase()) { + Some(gas) => gas.to_owned(), + #[cfg(test)] + None => 1_000, + #[cfg(not(test))] + None => 0, // VPs will reject the non-whitelisted tx + }; + gas_meter.add(tx_gas_required)?; + let mut iterators: PrefixIterators<'_, DB> = PrefixIterators::default(); let mut verifiers = BTreeSet::new(); let mut result_buffer: Option> = None; @@ -429,7 +484,6 @@ where CN: 'static + CacheName, CA: 'static + WasmCacheAccess, { - use crate::core::ledger::storage::write_log::StorageModification; match wasm_cache.fetch(code_hash)? { Some((module, store)) => Ok((module, store)), None => { @@ -535,6 +589,7 @@ mod tests { let storage = TestStorage::default(); let mut write_log = WriteLog::default(); let mut gas_meter = TxGasMeter::new(TX_GAS_LIMIT); + let gas_table = BTreeMap::default(); let tx_index = TxIndex::default(); // This code will allocate memory of the given size @@ -558,6 +613,7 @@ mod tests { &storage, &mut write_log, &mut gas_meter, + &gas_table, &tx_index, &code_hash, tx_data, @@ -573,6 +629,7 @@ mod tests { &storage, &mut write_log, &mut gas_meter, + &gas_table, &tx_index, &code_hash, tx_data, @@ -746,6 +803,7 @@ mod tests { let storage = TestStorage::default(); let mut write_log = WriteLog::default(); let mut gas_meter = TxGasMeter::new(TX_GAS_LIMIT); + let gas_table = BTreeMap::default(); let tx_index = TxIndex::default(); let tx_no_op = TestWasms::TxNoOp.read_bytes(); @@ -769,6 +827,7 @@ mod tests { &storage, &mut write_log, &mut gas_meter, + &gas_table, &tx_index, code_hash, tx_data, @@ -864,6 +923,7 @@ mod tests { let mut storage = TestStorage::default(); let mut write_log = WriteLog::default(); let mut gas_meter = TxGasMeter::new(TX_GAS_LIMIT); + let gas_table = BTreeMap::default(); let tx_index = TxIndex::default(); let tx_read_key = TestWasms::TxReadStorageKey.read_bytes(); @@ -892,6 +952,7 @@ mod tests { &storage, &mut write_log, &mut gas_meter, + &gas_table, &tx_index, code_hash, tx_data, @@ -1059,6 +1120,7 @@ mod tests { let storage = TestStorage::default(); let mut write_log = WriteLog::default(); let mut gas_meter = TxGasMeter::new(TX_GAS_LIMIT); + let gas_table = BTreeMap::default(); let (mut vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let (mut tx_cache, _) = @@ -1072,6 +1134,7 @@ mod tests { &storage, &mut write_log, &mut gas_meter, + &gas_table, &tx_index, code_hash, tx_data, diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index b5d6789f04..de70c14b54 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -1,5 +1,5 @@ use std::borrow::Borrow; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use namada::ledger::gas::TxGasMeter; use namada::ledger::parameters::{self, EpochDuration}; @@ -212,6 +212,7 @@ impl TestTxEnv { &self.wl_storage.storage, &mut self.wl_storage.write_log, &mut self.gas_meter, + &BTreeMap::default(), &self.tx_index, &self.tx.code_or_hash, self.tx.data.as_ref().unwrap_or(&empty_data), From 3da5e807a11556bff2919ede5f2a7d8ffbbd2bb7 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 16 May 2023 18:46:26 +0200 Subject: [PATCH 13/23] Optimizes vp gas metering --- benches/vps.rs | 7 ++- shared/src/ledger/protocol/mod.rs | 44 +------------- shared/src/vm/wasm/run.rs | 98 ++++++++++++++++++++++++++++++- 3 files changed, 102 insertions(+), 47 deletions(-) diff --git a/benches/vps.rs b/benches/vps.rs index 4c0ea460c6..57f69494f0 100644 --- a/benches/vps.rs +++ b/benches/vps.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use borsh::BorshSerialize; use criterion::{criterion_group, criterion_main, Criterion}; @@ -140,6 +140,7 @@ fn vp_user(c: &mut Criterion) { &shell.wl_storage.storage, &shell.wl_storage.write_log, &mut VpGasMeter::new(u64::MAX, 0), + &BTreeMap::default(), &keys_changed, &verifiers, shell.vp_wasm_cache.clone(), @@ -275,6 +276,7 @@ fn vp_implicit(c: &mut Criterion) { &shell.wl_storage.storage, &shell.wl_storage.write_log, &mut VpGasMeter::new(u64::MAX, 0), + &BTreeMap::default(), &keys_changed, &verifiers, shell.vp_wasm_cache.clone(), @@ -402,6 +404,7 @@ fn vp_validator(c: &mut Criterion) { &shell.wl_storage.storage, &shell.wl_storage.write_log, &mut VpGasMeter::new(u64::MAX, 0), + &BTreeMap::default(), &keys_changed, &verifiers, shell.vp_wasm_cache.clone(), @@ -459,6 +462,7 @@ fn vp_token(c: &mut Criterion) { &shell.wl_storage.storage, &shell.wl_storage.write_log, &mut VpGasMeter::new(u64::MAX, 0), + &BTreeMap::default(), &keys_changed, &verifiers, shell.vp_wasm_cache.clone(), @@ -546,6 +550,7 @@ fn vp_masp(c: &mut Criterion) { &shielded_ctx.shell.wl_storage.storage, &shielded_ctx.shell.wl_storage.write_log, &mut VpGasMeter::new(u64::MAX, 0), + &BTreeMap::default(), &keys_changed, &verifiers, shielded_ctx.shell.vp_wasm_cache.clone(), diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 87df7e5e86..ea2d4c9529 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -2,9 +2,7 @@ use std::collections::{BTreeMap, BTreeSet}; use std::panic; -use borsh::BorshDeserialize; use namada_core::ledger::gas::TxGasMeter; -use namada_core::ledger::storage::write_log::StorageModification; use namada_core::types::hash::Hash; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use thiserror::Error; @@ -272,47 +270,6 @@ where } }; - //FIXME: move gas inside vp? Yes but also change eval_vp to call vp instead of run_vp - // Compilation gas even if the compiled module is in cache - let key = storage::Key::wasm_code_len(&vp_code_hash); - let vp_code_len = match write_log.read(&key).0 { - Some(StorageModification::Write { value }) => { - u64::try_from_slice(value).map_err(|e| { - Error::ConversionError(e.to_string()) - }) - } - _ => match storage - .read(&key) - .map_err(Error::StorageError)? - .0 - { - Some(v) => u64::try_from_slice(&v).map_err(|e| { - Error::ConversionError(e.to_string()) - }), - None => Err(Error::MissingWasmCodeInStorage( - vp_code_hash.clone(), - )), - }, - }?; - gas_meter - .add_compiling_gas(vp_code_len) - .map_err(Error::GasError)?; - gas_meter - .add_vp_load_from_storage_gas(vp_code_len) - .map_err(Error::GasError)?; - - let vp_gas_required = match gas_table - .get(&vp_code_hash.to_string().to_ascii_lowercase()) - { - Some(gas) => gas.to_owned(), - #[cfg(test)] - None => 1_000, - #[cfg(not(test))] - None => 0, - }; - - gas_meter.add(vp_gas_required).map_err(Error::GasError)?; - // NOTE: because of the whitelisted gas and the gas metering // for the exposed vm env functions, // the first signature verification (if any) is accounted @@ -325,6 +282,7 @@ where storage, write_log, &mut gas_meter, + &gas_table, &keys_changed, &verifiers, vp_wasm_cache.clone(), diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index 44ad175f65..0b113e8136 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -133,8 +133,8 @@ where ))), }, }?; - gas_meter.add_compiling_gas(tx_len)?; gas_meter.add_tx_load_from_storage_gas(tx_len)?; + gas_meter.add_compiling_gas(tx_len)?; let (module, store) = fetch_or_compile(tx_wasm_cache, &code_hash, write_log, storage)?; @@ -233,6 +233,7 @@ pub fn vp( storage: &Storage, write_log: &WriteLog, gas_meter: &mut VpGasMeter, + gas_table: &BTreeMap, keys_changed: &BTreeSet, verifiers: &BTreeSet
, mut vp_wasm_cache: VpCache, @@ -285,21 +286,69 @@ where run_vp( module, imports, + vp_code_hash, input_data, address, keys_changed, verifiers, + storage, + write_log, + gas_meter, + gas_table, ) } -fn run_vp( +fn run_vp( module: wasmer::Module, vp_imports: wasmer::ImportObject, + vp_code_hash: &Hash, input_data: &[u8], address: &Address, keys_changed: &BTreeSet, verifiers: &BTreeSet
, -) -> Result { + storage: &Storage, + write_log: &WriteLog, + gas_meter: &mut VpGasMeter, + gas_table: &BTreeMap, +) -> Result +where + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + StorageHasher, +{ + // Compilation gas even if the compiled module is in cache + let key = Key::wasm_code_len(&vp_code_hash); + let vp_code_len = match write_log.read(&key).0 { + Some(StorageModification::Write { value }) => { + u64::try_from_slice(value) + .map_err(|e| Error::ConversionError(e.to_string())) + } + _ => match storage.read(&key).map_err(|e| Error::LoadWasmCode(format!("Read wasm vp code length from storage failed: key {}, error {}", key, e)))?.0 { + Some(v) => u64::try_from_slice(&v) + .map_err(|e| Error::ConversionError(e.to_string())), + None => Err(Error::LoadWasmCode(format!( + "No wasm code length in storage: key {}", + key + ))), + }, + }?; + gas_meter + .add_vp_load_from_storage_gas(vp_code_len) + .map_err(Error::GasError)?; + gas_meter + .add_compiling_gas(vp_code_len) + .map_err(Error::GasError)?; + + let vp_gas_required = + match gas_table.get(&vp_code_hash.to_string().to_ascii_lowercase()) { + Some(gas) => gas.to_owned(), + #[cfg(test)] + None => 1_000, + #[cfg(not(test))] + None => 0, + }; + + gas_meter.add(vp_gas_required).map_err(Error::GasError)?; + let input: VpInput = VpInput { addr: address, data: input_data, @@ -423,10 +472,34 @@ where let vp_wasm_cache = unsafe { ctx.vp_wasm_cache.get() }; let write_log = unsafe { ctx.write_log.get() }; let storage = unsafe { ctx.storage.get() }; + let gas_meter = unsafe { ctx.gas_meter.get() }; let env = VpVmEnv { memory: WasmMemory::default(), ctx, }; + let gas_table_key = &namada_core::ledger::parameters::storage::get_gas_table_storage_key(); + let gas_table = match write_log.read(&gas_table_key).0 { + Some(StorageModification::Write { value }) => { + BTreeMap::try_from_slice(value) + .map_err(|e| Error::ConversionError(e.to_string())) + } + _ => match storage + .read(&gas_table_key) + .map_err(|e| { + Error::LoadWasmCode(format!( + "Read gas table from storage failed: {}", + e + )) + })? + .0 + { + Some(v) => BTreeMap::try_from_slice(&v) + .map_err(|e| Error::ConversionError(e.to_string())), + None => Err(Error::LoadWasmCode( + "No gas table in storage".to_string(), + )), + }, + }?; // Compile the wasm module let (module, store) = @@ -440,10 +513,15 @@ where run_vp( module, imports, + &vp_code_hash, &input_data[..], address, keys_changed, verifiers, + storage, + write_log, + gas_meter, + &gas_table, ) } } @@ -650,6 +728,7 @@ mod tests { let addr = storage.address_gen.generate_address("rng seed"); let write_log = WriteLog::default(); let mut gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); + let gas_table = BTreeMap::default(); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); let tx_index = TxIndex::default(); @@ -690,6 +769,7 @@ mod tests { &storage, &write_log, &mut gas_meter, + &gas_table, &keys_changed, &verifiers, vp_cache.clone(), @@ -719,6 +799,7 @@ mod tests { &storage, &write_log, &mut gas_meter, + &gas_table, &keys_changed, &verifiers, vp_cache, @@ -738,6 +819,7 @@ mod tests { let addr = storage.address_gen.generate_address("rng seed"); let write_log = WriteLog::default(); let mut gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); + let gas_table = BTreeMap::default(); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); let tx_index = TxIndex::default(); @@ -765,6 +847,7 @@ mod tests { &storage, &write_log, &mut gas_meter, + &gas_table, &keys_changed, &verifiers, vp_cache.clone(), @@ -785,6 +868,7 @@ mod tests { &storage, &write_log, &mut gas_meter, + &gas_table, &keys_changed, &verifiers, vp_cache, @@ -861,6 +945,7 @@ mod tests { let addr = storage.address_gen.generate_address("rng seed"); let write_log = WriteLog::default(); let mut gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); + let gas_table = BTreeMap::default(); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); let tx_index = TxIndex::default(); @@ -888,6 +973,7 @@ mod tests { &storage, &write_log, &mut gas_meter, + &gas_table, &keys_changed, &verifiers, vp_cache, @@ -973,6 +1059,7 @@ mod tests { let addr = storage.address_gen.generate_address("rng seed"); let write_log = WriteLog::default(); let mut gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); + let gas_table = BTreeMap::default(); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); let tx_index = TxIndex::default(); @@ -1005,6 +1092,7 @@ mod tests { &storage, &write_log, &mut gas_meter, + &gas_table, &keys_changed, &verifiers, vp_cache, @@ -1026,6 +1114,7 @@ mod tests { let addr = storage.address_gen.generate_address("rng seed"); let write_log = WriteLog::default(); let mut gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); + let gas_table = BTreeMap::default(); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); let tx_index = TxIndex::default(); @@ -1070,6 +1159,7 @@ mod tests { &storage, &write_log, &mut gas_meter, + &gas_table, &keys_changed, &verifiers, vp_cache, @@ -1178,6 +1268,7 @@ mod tests { let addr = storage.address_gen.generate_address("rng seed"); let write_log = WriteLog::default(); let mut gas_meter = VpGasMeter::new(TX_GAS_LIMIT, 0); + let gas_table = BTreeMap::default(); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); @@ -1194,6 +1285,7 @@ mod tests { &storage, &write_log, &mut gas_meter, + &gas_table, &keys_changed, &verifiers, vp_cache, From 5b8e41d3565d84a4bd7bb7d531474726bb0cd62c Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 16 May 2023 18:46:58 +0200 Subject: [PATCH 14/23] Renames `vp_eval` parameters --- shared/src/vm/host_env.rs | 14 +++++++------- tests/src/vm_host_env/vp.rs | 2 +- vm_env/src/lib.rs | 4 ++-- vp_prelude/src/lib.rs | 10 +++++++--- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 9592affd01..ceb38a915e 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -287,7 +287,7 @@ pub trait VpEvaluator { fn eval( &self, ctx: VpCtx<'static, Self::Db, Self::H, Self::Eval, Self::CA>, - vp_code: Vec, + vp_code_hash: Vec, input_data: Vec, ) -> HostEnvResult; } @@ -1884,8 +1884,8 @@ where /// Evaluate a validity predicate with the given input data. pub fn vp_eval( env: &VpVmEnv<'static, MEM, DB, H, EVAL, CA>, - vp_code_ptr: u64, - vp_code_len: u64, + vp_code_hash_ptr: u64, + vp_code_hash_len: u64, input_data_ptr: u64, input_data_len: u64, ) -> vp_host_fns::EnvResult @@ -1896,10 +1896,10 @@ where EVAL: VpEvaluator, CA: WasmCacheAccess, { - let (vp_code, gas) = - env.memory - .read_bytes(vp_code_ptr, vp_code_len as _) - .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; + let (vp_code, gas) = env + .memory + .read_bytes(vp_code_hash_ptr, vp_code_hash_len as _) + .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; let gas_meter = unsafe { env.ctx.gas_meter.get() }; vp_host_fns::add_gas(gas_meter, gas)?; diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index f030cb8c51..db66247b62 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -233,7 +233,7 @@ mod native_vp_host_env { fn eval( &self, _ctx: VpCtx<'static, Self::Db, Self::H, Self::Eval, Self::CA>, - _vp_code: Vec, + _vp_code_hash: Vec, _input_data: Vec, ) -> namada::types::internal::HostEnvResult { unimplemented!( diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index 9df39d6271..3b67a3c944 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -196,8 +196,8 @@ pub mod vp { pub fn namada_vp_log_string(str_ptr: u64, str_len: u64); pub fn namada_vp_eval( - vp_code_ptr: u64, - vp_code_len: u64, + vp_code_hash_ptr: u64, + vp_code_hash_len: u64, input_data_ptr: u64, input_data_len: u64, ) -> i64; diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 57ac5a388f..b487fe8d54 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -272,11 +272,15 @@ impl<'view> VpEnv<'view> for Ctx { iter_prefix_pre_impl(prefix) } - fn eval(&self, vp_code: Hash, input_data: Vec) -> Result { + fn eval( + &self, + vp_code_hash: Hash, + input_data: Vec, + ) -> Result { let result = unsafe { namada_vp_eval( - vp_code.as_ptr() as _, - vp_code.len() as _, + vp_code_hash.as_ptr() as _, + vp_code_hash.len() as _, input_data.as_ptr() as _, input_data.len() as _, ) From ec08858c7405f88e14a23b4cee9f505e88282d0d Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 17 May 2023 17:18:47 +0200 Subject: [PATCH 15/23] Fixes unit tests and wrong error handling --- .../lib/node/ledger/shell/finalize_block.rs | 34 +++++-------- apps/src/lib/node/ledger/shell/mod.rs | 2 - shared/src/ledger/protocol/mod.rs | 8 ++- shared/src/ledger/queries/shell.rs | 8 ++- shared/src/vm/wasm/run.rs | 40 +++++++++++++++ tests/src/vm_host_env/mod.rs | 50 +++++++++++-------- 6 files changed, 97 insertions(+), 45 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index ce0d296b56..9e743d3b29 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1598,11 +1598,9 @@ mod test_finalize_block { // won't receive votes from TM since we receive votes at a 1-block // delay, so votes will be empty here next_block_for_inflation(&mut shell, pkh1.clone(), vec![]); - assert!( - rewards_accumulator_handle() - .is_empty(&shell.wl_storage) - .unwrap() - ); + assert!(rewards_accumulator_handle() + .is_empty(&shell.wl_storage) + .unwrap()); // FINALIZE BLOCK 2. Tell Namada that val1 is the block proposer. // Include votes that correspond to block 1. Make val2 the next block's @@ -1612,11 +1610,9 @@ mod test_finalize_block { assert!(rewards_prod_2.is_empty(&shell.wl_storage).unwrap()); assert!(rewards_prod_3.is_empty(&shell.wl_storage).unwrap()); assert!(rewards_prod_4.is_empty(&shell.wl_storage).unwrap()); - assert!( - !rewards_accumulator_handle() - .is_empty(&shell.wl_storage) - .unwrap() - ); + assert!(!rewards_accumulator_handle() + .is_empty(&shell.wl_storage) + .unwrap()); // Val1 was the proposer, so its reward should be larger than all // others, which should themselves all be equal let acc_sum = get_rewards_sum(&shell.wl_storage); @@ -1717,11 +1713,9 @@ mod test_finalize_block { ); next_block_for_inflation(&mut shell, pkh1.clone(), votes.clone()); } - assert!( - rewards_accumulator_handle() - .is_empty(&shell.wl_storage) - .unwrap() - ); + assert!(rewards_accumulator_handle() + .is_empty(&shell.wl_storage) + .unwrap()); let rp1 = rewards_prod_1 .get(&shell.wl_storage, &Epoch::default()) .unwrap() @@ -1847,12 +1841,10 @@ mod test_finalize_block { let code = event.attributes.get("code").expect("Testfailed").as_str(); assert_eq!(code, String::from(ErrorCodes::WasmRuntimeError).as_str()); - assert!( - !shell - .wl_storage - .has_key(&inner_hash_key) - .expect("Test failed") - ) + assert!(!shell + .wl_storage + .has_key(&inner_hash_key) + .expect("Test failed")) } /// Test that a wrapper transaction rejected by [`process_proposal`] because diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 64c6918782..ead7a55417 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -95,8 +95,6 @@ pub enum Error { TxDecoding(proto::Error), #[error("Error trying to apply a transaction: {0}")] TxApply(protocol::Error), - #[error("Gas limit exceeding while applying transactions in block")] - GasOverflow, #[error("{0}")] Tendermint(tendermint_node::Error), #[error("Server error: {0}")] diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index ea2d4c9529..ff67d19271 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -179,7 +179,13 @@ where vp_wasm_cache, tx_wasm_cache, ) - .map_err(Error::TxRunnerError) + .map_err(|e| { + if let wasm::run::Error::GasError(gas_error) = e { + Error::GasError(gas_error) + } else { + Error::TxRunnerError(e) + } + }) } /// Check the acceptance of a transaction by validity predicates diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 4c9f1ea258..b2c329e1ce 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -358,7 +358,7 @@ where #[cfg(test)] mod test { - use borsh::BorshDeserialize; + use borsh::{BorshDeserialize, BorshSerialize}; use namada_test_utils::TestWasms; use crate::ledger::queries::testing::TestClient; @@ -399,7 +399,13 @@ mod test { let tx_no_op = TestWasms::TxNoOp.read_bytes(); let tx_hash = Hash::sha256(&tx_no_op); let key = Key::wasm_code(&tx_hash); + let len_key = Key::wasm_code_len(&tx_hash); client.wl_storage.storage.write(&key, &tx_no_op).unwrap(); + client + .wl_storage + .storage + .write(&len_key, (tx_no_op.len() as u64).try_to_vec().unwrap()) + .unwrap(); // Request last committed epoch let read_epoch = RPC.shell().epoch(&client).await.unwrap(); diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index 0b113e8136..d222e50da8 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -675,7 +675,10 @@ mod tests { // store the wasm code let code_hash = Hash::sha256(&tx_code); let key = Key::wasm_code(&code_hash); + let len_key = Key::wasm_code_len(&code_hash); + let code_len = (tx_code.len() as u64).try_to_vec().unwrap(); write_log.write(&key, tx_code).unwrap(); + write_log.write(&len_key, code_len).unwrap(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::TX_MEMORY_MAX_PAGES, 200); @@ -738,13 +741,23 @@ mod tests { // store the wasm code let code_hash = Hash::sha256(&vp_eval); let key = Key::wasm_code(&code_hash); + let len_key = Key::wasm_code_len(&code_hash); + let code_len = (vp_eval.len() as u64).try_to_vec().unwrap(); storage.write(&key, vp_eval).unwrap(); + storage.write(&len_key, code_len).unwrap(); // This code will allocate memory of the given size let vp_memory_limit = TestWasms::VpMemoryLimit.read_bytes(); // store the wasm code let limit_code_hash = Hash::sha256(&vp_memory_limit); let key = Key::wasm_code(&limit_code_hash); + let len_key = Key::wasm_code_len(&limit_code_hash); + let code_len = (vp_memory_limit.len() as u64).try_to_vec().unwrap(); storage.write(&key, vp_memory_limit).unwrap(); + storage.write(&len_key, code_len).unwrap(); + let gas_table_key = &namada_core::ledger::parameters::storage::get_gas_table_storage_key(); + storage + .write(&gas_table_key, gas_table.try_to_vec().unwrap()) + .unwrap(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::VP_MEMORY_MAX_PAGES, 200); @@ -828,8 +841,11 @@ mod tests { let vp_code = TestWasms::VpMemoryLimit.read_bytes(); // store the wasm code let code_hash = Hash::sha256(&vp_code); + let code_len = (vp_code.len() as u64).try_to_vec().unwrap(); let key = Key::wasm_code(&code_hash); + let len_key = Key::wasm_code_len(&code_hash); storage.write(&key, vp_code).unwrap(); + storage.write(&len_key, code_len).unwrap(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::VP_MEMORY_MAX_PAGES, 200); @@ -894,7 +910,10 @@ mod tests { // store the wasm code let code_hash = Hash::sha256(&tx_no_op); let key = Key::wasm_code(&code_hash); + let len_key = Key::wasm_code_len(&code_hash); + let code_len = (tx_no_op.len() as u64).try_to_vec().unwrap(); write_log.write(&key, tx_no_op).unwrap(); + write_log.write(&len_key, code_len).unwrap(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::TX_MEMORY_MAX_PAGES, 200); @@ -954,7 +973,10 @@ mod tests { // store the wasm code let code_hash = Hash::sha256(&vp_code); let key = Key::wasm_code(&code_hash); + let len_key = Key::wasm_code_len(&code_hash); + let code_len = (vp_code.len() as u64).try_to_vec().unwrap(); storage.write(&key, vp_code).unwrap(); + storage.write(&len_key, code_len).unwrap(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::VP_MEMORY_MAX_PAGES, 200); @@ -1015,8 +1037,11 @@ mod tests { let tx_read_key = TestWasms::TxReadStorageKey.read_bytes(); // store the wasm code let code_hash = Hash::sha256(&tx_read_key); + let code_len = (tx_read_key.len() as u64).try_to_vec().unwrap(); let key = Key::wasm_code(&code_hash); + let len_key = Key::wasm_code_len(&code_hash); write_log.write(&key, tx_read_key).unwrap(); + write_log.write(&len_key, code_len).unwrap(); // Allocating `2^24` (16 MiB) for a value in storage that the tx // attempts to read should be above the memory limit and should @@ -1067,8 +1092,11 @@ mod tests { let vp_read_key = TestWasms::VpReadStorageKey.read_bytes(); // store the wasm code let code_hash = Hash::sha256(&vp_read_key); + let code_len = (vp_read_key.len() as u64).try_to_vec().unwrap(); let key = Key::wasm_code(&code_hash); + let len_key = Key::wasm_code_len(&code_hash); storage.write(&key, vp_read_key).unwrap(); + storage.write(&len_key, code_len).unwrap(); // Allocating `2^24` (16 MiB) for a value in storage that the tx // attempts to read should be above the memory limit and should @@ -1123,14 +1151,20 @@ mod tests { let vp_eval = TestWasms::VpEval.read_bytes(); // store the wasm code let code_hash = Hash::sha256(&vp_eval); + let code_len = (vp_eval.len() as u64).try_to_vec().unwrap(); let key = Key::wasm_code(&code_hash); + let len_key = Key::wasm_code_len(&code_hash); storage.write(&key, vp_eval).unwrap(); + storage.write(&len_key, code_len).unwrap(); // This code will read value from the storage let vp_read_key = TestWasms::VpReadStorageKey.read_bytes(); // store the wasm code let read_code_hash = Hash::sha256(&vp_read_key); + let code_len = (vp_read_key.len() as u64).try_to_vec().unwrap(); let key = Key::wasm_code(&read_code_hash); + let len_key = Key::wasm_code_len(&read_code_hash); storage.write(&key, vp_read_key).unwrap(); + storage.write(&len_key, code_len).unwrap(); // Allocating `2^24` (16 MiB) for a value in storage that the tx // attempts to read should be above the memory limit and should @@ -1217,8 +1251,11 @@ mod tests { wasm::compilation_cache::common::testing::cache(); // store the tx code let code_hash = Hash::sha256(&tx_code); + let code_len = (tx_code.len() as u64).try_to_vec().unwrap(); let key = Key::wasm_code(&code_hash); + let len_key = Key::wasm_code_len(&code_hash); write_log.write(&key, tx_code).unwrap(); + write_log.write(&len_key, code_len).unwrap(); tx( &storage, @@ -1274,8 +1311,11 @@ mod tests { let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); // store the vp code let code_hash = Hash::sha256(&vp_code); + let code_len = (vp_code.len() as u64).try_to_vec().unwrap(); let key = Key::wasm_code(&code_hash); + let len_key = Key::wasm_code_len(&code_hash); storage.write(&key, vp_code).unwrap(); + storage.write(&len_key, code_len).unwrap(); vp( &code_hash, diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 1ad4b22599..5acc692d2d 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -18,6 +18,7 @@ pub mod vp; #[cfg(test)] mod tests { + use std::collections::BTreeMap; use std::panic; use itertools::Itertools; @@ -133,13 +134,11 @@ mod tests { // Trying to delete a validity predicate should fail let key = storage::Key::validity_predicate(&test_account); - assert!( - panic::catch_unwind(|| { tx::ctx().delete(&key).unwrap() }) - .err() - .map(|a| a.downcast_ref::().cloned().unwrap()) - .unwrap() - .contains("CannotDeleteVp") - ); + assert!(panic::catch_unwind(|| { tx::ctx().delete(&key).unwrap() }) + .err() + .map(|a| a.downcast_ref::().cloned().unwrap()) + .unwrap() + .contains("CannotDeleteVp")); } #[test] @@ -470,21 +469,17 @@ mod tests { .expect("decoding signed data we just signed") }); assert_eq!(&signed_tx_data.data, data); - assert!( - vp::CTX - .verify_tx_signature(&pk, &signed_tx_data.sig) - .unwrap() - ); + assert!(vp::CTX + .verify_tx_signature(&pk, &signed_tx_data.sig) + .unwrap()); let other_keypair = key::testing::keypair_2(); - assert!( - !vp::CTX - .verify_tx_signature( - &other_keypair.ref_to(), - &signed_tx_data.sig - ) - .unwrap() - ); + assert!(!vp::CTX + .verify_tx_signature( + &other_keypair.ref_to(), + &signed_tx_data.sig + ) + .unwrap()); } } @@ -539,12 +534,20 @@ mod tests { assert!(!result); // evaluating the VP template which always returns `true` should pass + let gas_table: BTreeMap = BTreeMap::default(); let code = TestWasms::VpAlwaysTrue.read_bytes(); let code_hash = Hash::sha256(&code); + let code_len = (code.len() as u64).try_to_vec().unwrap(); vp_host_env::with(|env| { // store wasm codes let key = Key::wasm_code(&code_hash); + let len_key = Key::wasm_code_len(&code_hash); env.wl_storage.storage.write(&key, code.clone()).unwrap(); + env.wl_storage + .storage + .write(&len_key, code_len.clone()) + .unwrap(); + env.wl_storage.storage.write(&namada::ledger::parameters::storage::get_gas_table_storage_key(), gas_table.clone().try_to_vec().unwrap()).unwrap(); }); let input_data = vec![]; let result = vp::CTX.eval(code_hash, input_data).unwrap(); @@ -554,10 +557,17 @@ mod tests { // pass let code = TestWasms::VpAlwaysFalse.read_bytes(); let code_hash = Hash::sha256(&code); + let code_len = (code.len() as u64).try_to_vec().unwrap(); vp_host_env::with(|env| { // store wasm codes let key = Key::wasm_code(&code_hash); + let len_key = Key::wasm_code_len(&code_hash); env.wl_storage.storage.write(&key, code.clone()).unwrap(); + env.wl_storage + .storage + .write(&len_key, code_len.clone()) + .unwrap(); + env.wl_storage.storage.write(&namada::ledger::parameters::storage::get_gas_table_storage_key(), gas_table.clone().try_to_vec().unwrap()).unwrap(); }); let input_data = vec![]; let result = vp::CTX.eval(code_hash, input_data).unwrap(); From c2570bf78d944af36e9c69aa69c263ae70258b4f Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 17 May 2023 18:17:27 +0200 Subject: [PATCH 16/23] Short circuit vp evaluation on error --- shared/src/ledger/protocol/mod.rs | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index ff67d19271..dd3a26e988 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -424,27 +424,14 @@ where }; // Returning error from here will short-circuit the VP parallel - // execution. It's important that we only short-circuit gas - // errors to get deterministic gas costs + // execution. result.gas_used.set(gas_meter).map_err(Error::GasError)?; - match accept { - Ok(accepted) => { - if !accepted { - result.rejected_vps.insert(addr.clone()); - } else { - result.accepted_vps.insert(addr.clone()); - } - Ok(result) - } - Err(err) => match err { - Error::GasError(_) => Err(err), - _ => { - result.rejected_vps.insert(addr.clone()); - result.errors.push((addr.clone(), err.to_string())); - Ok(result) - } - }, + if accept? { + result.accepted_vps.insert(addr.clone()); + } else { + result.rejected_vps.insert(addr.clone()); } + Ok(result) }) .try_reduce(VpsResult::default, |a, b| { merge_vp_results(a, b, tx_gas_limit, initial_gas) From 216508406b071c84cdcbb22b674c1abd056e03e1 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 18 May 2023 13:48:31 +0200 Subject: [PATCH 17/23] Refactors gas meters api into trait --- .../lib/node/ledger/shell/process_proposal.rs | 1 + core/src/ledger/gas.rs | 125 ++++++++---------- shared/src/ledger/protocol/mod.rs | 2 +- shared/src/ledger/vp_host_fns.rs | 2 +- shared/src/vm/host_env.rs | 2 +- shared/src/vm/wasm/run.rs | 7 +- 6 files changed, 61 insertions(+), 78 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 2059d8892e..12ef115843 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -7,6 +7,7 @@ use data_encoding::HEXUPPER; use namada::core::hints; use namada::core::ledger::storage::WlStorage; use namada::core::types::hash::Hash; +use namada::ledger::gas::TxVpGasMetering; use namada::ledger::storage::TempWlStorage; use namada::proof_of_stake::pos_queries::PosQueries; use namada::types::hash::HASH_LENGTH; diff --git a/core/src/ledger/gas.rs b/core/src/ledger/gas.rs index 50fe28e8ec..5578c86d67 100644 --- a/core/src/ledger/gas.rs +++ b/core/src/ledger/gas.rs @@ -19,7 +19,7 @@ pub enum Error { const TX_SIZE_GAS_PER_BYTE: u64 = 10; const COMPILE_GAS_PER_BYTE: u64 = 1; -const PARALLEL_GAS_DIVIDER: u64 = 10; +const PARALLEL_GAS_DIVIDER: u64 = 10; //FIXME: should this depend on the length of the vector of rest? /// The minimum gas cost for accessing the storage pub const MIN_STORAGE_GAS: u64 = 1; @@ -33,6 +33,32 @@ pub const STORAGE_WRITE_GAS_PER_BYTE: u64 = 100; /// Gas module result for functions that may fail pub type Result = std::result::Result; +/// Trait to share gas operations for transactions and validity predicates +pub trait TxVpGasMetering { + /// Add gas cost. It will return error when the + /// consumed gas exceeds the provided transaction gas limit, but the state + /// will still be updated + fn add(&mut self, gas: u64) -> Result<()>; + + /// Add the compiling cost proportionate to the code length + fn add_compiling_gas(&mut self, bytes_len: u64) -> Result<()> { + self.add( + bytes_len + .checked_mul(COMPILE_GAS_PER_BYTE) + .ok_or(Error::GasOverflow)?, + ) + } + + /// Add the gas for loading the wasm code from storage + fn add_wasm_load_from_storage_gas(&mut self, bytes_len: u64) -> Result<()> { + self.add( + bytes_len + .checked_mul(MIN_STORAGE_GAS) + .ok_or(Error::GasOverflow)?, + ) + } +} + /// Gas metering in a block. The amount of gas consumed in a block is based on /// the tx_gas_limit declared by each [`TxGasMeter`] #[derive(Debug, Clone)] @@ -122,20 +148,8 @@ impl BlockGasMeter { } } -impl TxGasMeter { - /// Initialize a new Tx gas meter. Requires the gas limit for the specific - /// transaction - pub fn new(tx_gas_limit: u64) -> Self { - Self { - tx_gas_limit, - transaction_gas: 0, - } - } - - /// Add gas cost for the current transaction. It will return error when the - /// consumed gas exceeds the provided transaction gas limit, but the state - /// will still be updated. - pub fn add(&mut self, gas: u64) -> Result<()> { +impl TxVpGasMetering for TxGasMeter { + fn add(&mut self, gas: u64) -> Result<()> { self.transaction_gas = self .transaction_gas .checked_add(gas) @@ -147,20 +161,22 @@ impl TxGasMeter { Ok(()) } +} - /// Add the compiling cost proportionate to the code length - pub fn add_compiling_gas(&mut self, bytes_len: u64) -> Result<()> { - self.add( - bytes_len - .checked_mul(COMPILE_GAS_PER_BYTE) - .ok_or(Error::GasOverflow)?, - ) +impl TxGasMeter { + /// Initialize a new Tx gas meter. Requires the gas limit for the specific + /// transaction + pub fn new(tx_gas_limit: u64) -> Self { + Self { + tx_gas_limit, + transaction_gas: 0, + } } /// Add the gas for the space that the transaction requires in the block pub fn add_tx_size_gas( &mut self, - bytes_len: impl TryInto, + bytes_len: impl TryInto, //FIXME: no generic here ) -> Result<()> { let bytes_len = bytes_len.try_into().map_err(|_| Error::ConversionError)?; @@ -171,18 +187,6 @@ impl TxGasMeter { ) } - /// Add the gas for loading the transaction's code from storage - pub fn add_tx_load_from_storage_gas( - &mut self, - bytes_len: u64, - ) -> Result<()> { - self.add( - bytes_len - .checked_mul(MIN_STORAGE_GAS) - .ok_or(Error::GasOverflow)?, - ) - } - /// Add the gas cost used in validity predicates to the current transaction. pub fn add_vps_gas(&mut self, vps_gas: &VpsGas) -> Result<()> { self.add(vps_gas.get_current_gas()?) @@ -194,28 +198,13 @@ impl TxGasMeter { } } -impl VpGasMeter { - /// Initialize a new VP gas meter, starting with the gas consumed in the - /// transaction so far. Also requires the transaction gas limit. - pub fn new(tx_gas_limit: u64, initial_gas: u64) -> Self { - Self { - tx_gas_limit, - initial_gas, - current_gas: 0, - } - } - - /// Consume gas in a validity predicate. It will return error when the - /// consumed gas exceeds the transaction gas limit, but the state will still - /// be updated. - pub fn add(&mut self, gas: u64) -> Result<()> { - let gas = self +impl TxVpGasMetering for VpGasMeter { + fn add(&mut self, gas: u64) -> Result<()> { + self.current_gas = self .current_gas .checked_add(gas) .ok_or(Error::GasOverflow)?; - self.current_gas = gas; - let current_total = self .initial_gas .checked_add(self.current_gas) @@ -227,26 +216,18 @@ impl VpGasMeter { Ok(()) } +} - /// Add the compiling cost proportionate to the code length - pub fn add_compiling_gas(&mut self, bytes_len: u64) -> Result<()> { - self.add( - bytes_len - .checked_mul(COMPILE_GAS_PER_BYTE) - .ok_or(Error::GasOverflow)?, - ) - } - - /// Add the gas for loading the non-native vp's code from storage - pub fn add_vp_load_from_storage_gas( - &mut self, - bytes_len: u64, - ) -> Result<()> { - self.add( - bytes_len - .checked_mul(MIN_STORAGE_GAS) - .ok_or(Error::GasOverflow)?, - ) +impl VpGasMeter { + /// Initialize a new VP gas meter, starting with the gas consumed in the + /// transaction so far. Also requires the transaction gas limit. + pub fn new(tx_gas_limit: u64, initial_gas: u64) -> Self { + //FIXME: should this take the reference to the txgasmeter? + Self { + tx_gas_limit, + initial_gas, + current_gas: 0, + } } } diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index dd3a26e988..157ced4bdf 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -8,7 +8,7 @@ use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use thiserror::Error; use crate::ledger::eth_bridge::vp::EthBridge; -use crate::ledger::gas::{self, VpGasMeter}; +use crate::ledger::gas::{self, TxVpGasMetering, VpGasMeter}; use crate::ledger::ibc::vp::{Ibc, IbcToken}; use crate::ledger::native_vp::governance::GovernanceVp; use crate::ledger::native_vp::parameters::{self, ParametersVp}; diff --git a/shared/src/ledger/vp_host_fns.rs b/shared/src/ledger/vp_host_fns.rs index 14bd8cfa56..fe927a3e7f 100644 --- a/shared/src/ledger/vp_host_fns.rs +++ b/shared/src/ledger/vp_host_fns.rs @@ -13,7 +13,7 @@ use thiserror::Error; use super::gas::MIN_STORAGE_GAS; use crate::ledger::gas; -use crate::ledger::gas::VpGasMeter; +use crate::ledger::gas::{TxVpGasMetering, VpGasMeter}; use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::{self, write_log, Storage, StorageHasher}; use crate::proto::Tx; diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index ceb38a915e..cebd9d007c 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -5,7 +5,7 @@ use std::convert::TryInto; use std::num::TryFromIntError; use borsh::{BorshDeserialize, BorshSerialize}; -use namada_core::ledger::gas::TxGasMeter; +use namada_core::ledger::gas::{TxGasMeter, TxVpGasMetering}; use namada_core::types::internal::KeyVal; use thiserror::Error; diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index d222e50da8..a3cca9595b 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -4,7 +4,7 @@ use std::collections::{BTreeMap, BTreeSet}; use std::marker::PhantomData; use borsh::BorshDeserialize; -use namada_core::ledger::gas::{self, TxGasMeter}; +use namada_core::ledger::gas::{self, TxGasMeter, TxVpGasMetering}; use namada_core::ledger::storage::write_log::StorageModification; use parity_wasm::elements; use pwasm_utils::{self, rules}; @@ -133,7 +133,7 @@ where ))), }, }?; - gas_meter.add_tx_load_from_storage_gas(tx_len)?; + gas_meter.add_wasm_load_from_storage_gas(tx_len)?; gas_meter.add_compiling_gas(tx_len)?; let (module, store) = @@ -332,7 +332,7 @@ where }, }?; gas_meter - .add_vp_load_from_storage_gas(vp_code_len) + .add_wasm_load_from_storage_gas(vp_code_len) .map_err(Error::GasError)?; gas_meter .add_compiling_gas(vp_code_len) @@ -502,6 +502,7 @@ where }?; // Compile the wasm module + //FIXME: need to account gas for loading and compilation! let (module, store) = fetch_or_compile(vp_wasm_cache, &vp_code_hash, write_log, storage)?; From e1ac8d156aaa3def23f69bfe7068344452505b4e Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 19 May 2023 08:53:09 +0200 Subject: [PATCH 18/23] Moves compilation gas into `fetch_or_compile` --- core/src/ledger/gas.rs | 5 +- shared/src/vm/wasm/run.rs | 226 ++++++++++++++++++++------------------ 2 files changed, 119 insertions(+), 112 deletions(-) diff --git a/core/src/ledger/gas.rs b/core/src/ledger/gas.rs index 5578c86d67..4b93cee35d 100644 --- a/core/src/ledger/gas.rs +++ b/core/src/ledger/gas.rs @@ -19,7 +19,7 @@ pub enum Error { const TX_SIZE_GAS_PER_BYTE: u64 = 10; const COMPILE_GAS_PER_BYTE: u64 = 1; -const PARALLEL_GAS_DIVIDER: u64 = 10; //FIXME: should this depend on the length of the vector of rest? +const PARALLEL_GAS_DIVIDER: u64 = 10; /// The minimum gas cost for accessing the storage pub const MIN_STORAGE_GAS: u64 = 1; @@ -176,7 +176,7 @@ impl TxGasMeter { /// Add the gas for the space that the transaction requires in the block pub fn add_tx_size_gas( &mut self, - bytes_len: impl TryInto, //FIXME: no generic here + bytes_len: impl TryInto, ) -> Result<()> { let bytes_len = bytes_len.try_into().map_err(|_| Error::ConversionError)?; @@ -222,7 +222,6 @@ impl VpGasMeter { /// Initialize a new VP gas meter, starting with the gas consumed in the /// transaction so far. Also requires the transaction gas limit. pub fn new(tx_gas_limit: u64, initial_gas: u64) -> Self { - //FIXME: should this take the reference to the txgasmeter? Self { tx_gas_limit, initial_gas, diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index a3cca9595b..740b4996ff 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -83,6 +83,11 @@ pub enum Error { /// Result for functions that may fail pub type Result = std::result::Result; +enum WasmPayload<'fetch> { + Hash(&'fetch Hash), + Code(&'fetch [u8]), +} + /// Execute a transaction code. Returns the set verifiers addresses requested by /// the transaction. #[allow(clippy::too_many_arguments)] @@ -102,56 +107,30 @@ where H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - let (module, store, tx_hash) = if tx_code.as_ref().len() == HASH_LENGTH { + let (tx_hash, code) = if tx_code.as_ref().len() == HASH_LENGTH { // we assume that there is no wasm code with HASH_LENGTH - let code_hash = + let tx_hash = Hash::try_from(tx_code.as_ref()).map_err(Error::CodeHash)?; - // Compilation gas even if the compiled module is in cache - let key = Key::wasm_code_len(&code_hash); - let tx_len = match write_log.read(&key).0 { - Some(StorageModification::Write { value }) => { - u64::try_from_slice(value) - .map_err(|e| Error::ConversionError(e.to_string())) - } - _ => match storage - .read(&key) - .map_err(|e| { - Error::LoadWasmCode(format!( - "Read wasm code length failed from storage: key {}, \ - error {}", - key, e - )) - })? - .0 - { - Some(v) => u64::try_from_slice(&v) - .map_err(|e| Error::ConversionError(e.to_string())), - None => Err(Error::LoadWasmCode(format!( - "No wasm code length in storage: key {}", - key - ))), - }, - }?; - gas_meter.add_wasm_load_from_storage_gas(tx_len)?; - gas_meter.add_compiling_gas(tx_len)?; - - let (module, store) = - fetch_or_compile(tx_wasm_cache, &code_hash, write_log, storage)?; - (module, store, code_hash) + (tx_hash, None) } else { - // Compilation gas even if the compiled module is in cache - gas_meter.add_compiling_gas( - u64::try_from(tx_code.as_ref().len()) - .map_err(|e| Error::ConversionError(e.to_string()))?, - )?; + let tx_hash = Hash::sha256(tx_code.as_ref()); + (tx_hash, Some(tx_code.as_ref())) + }; - match tx_wasm_cache.compile_or_fetch(tx_code.as_ref())? { - Some((module, store)) => (module, store, Hash::sha256(tx_code)), - None => return Err(Error::NoCompiledWasmCode), - } + let code_or_hash = match code { + Some(code) => WasmPayload::Code(code), + None => WasmPayload::Hash(&tx_hash), }; + let (module, store) = fetch_or_compile( + tx_wasm_cache, + code_or_hash, + write_log, + storage, + gas_meter, + )?; + let tx_gas_required = match gas_table.get(&tx_hash.to_string().to_ascii_lowercase()) { Some(gas) => gas.to_owned(), @@ -250,8 +229,13 @@ where }; // Compile the wasm module - let (module, store) = - fetch_or_compile(&mut vp_wasm_cache, vp_code_hash, write_log, storage)?; + let (module, store) = fetch_or_compile( + &mut vp_wasm_cache, + WasmPayload::Hash(vp_code_hash), + write_log, + storage, + gas_meter, + )?; let mut iterators: PrefixIterators<'_, DB> = PrefixIterators::default(); let mut result_buffer: Option> = None; @@ -291,14 +275,12 @@ where address, keys_changed, verifiers, - storage, - write_log, gas_meter, gas_table, ) } -fn run_vp( +fn run_vp( module: wasmer::Module, vp_imports: wasmer::ImportObject, vp_code_hash: &Hash, @@ -306,38 +288,9 @@ fn run_vp( address: &Address, keys_changed: &BTreeSet, verifiers: &BTreeSet
, - storage: &Storage, - write_log: &WriteLog, gas_meter: &mut VpGasMeter, gas_table: &BTreeMap, -) -> Result -where - DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, - H: 'static + StorageHasher, -{ - // Compilation gas even if the compiled module is in cache - let key = Key::wasm_code_len(&vp_code_hash); - let vp_code_len = match write_log.read(&key).0 { - Some(StorageModification::Write { value }) => { - u64::try_from_slice(value) - .map_err(|e| Error::ConversionError(e.to_string())) - } - _ => match storage.read(&key).map_err(|e| Error::LoadWasmCode(format!("Read wasm vp code length from storage failed: key {}, error {}", key, e)))?.0 { - Some(v) => u64::try_from_slice(&v) - .map_err(|e| Error::ConversionError(e.to_string())), - None => Err(Error::LoadWasmCode(format!( - "No wasm code length in storage: key {}", - key - ))), - }, - }?; - gas_meter - .add_wasm_load_from_storage_gas(vp_code_len) - .map_err(Error::GasError)?; - gas_meter - .add_compiling_gas(vp_code_len) - .map_err(Error::GasError)?; - +) -> Result { let vp_gas_required = match gas_table.get(&vp_code_hash.to_string().to_ascii_lowercase()) { Some(gas) => gas.to_owned(), @@ -502,9 +455,13 @@ where }?; // Compile the wasm module - //FIXME: need to account gas for loading and compilation! - let (module, store) = - fetch_or_compile(vp_wasm_cache, &vp_code_hash, write_log, storage)?; + let (module, store) = fetch_or_compile( + vp_wasm_cache, + WasmPayload::Hash(&vp_code_hash), + write_log, + storage, + gas_meter, + )?; let initial_memory = memory::prepare_vp_memory(&store).map_err(Error::MemoryError)?; @@ -519,8 +476,6 @@ where address, keys_changed, verifiers, - storage, - write_log, gas_meter, &gas_table, ) @@ -550,12 +505,13 @@ pub fn prepare_wasm_code>(code: T) -> Result> { elements::serialize(module).map_err(Error::SerializationError) } -// Fetch or compile a WASM code from the cache or storage +// Fetch or compile a WASM code from the cache or storage. Account for the loading and code compilation gas costs. fn fetch_or_compile( wasm_cache: &mut Cache, - code_hash: &Hash, + code_or_hash: WasmPayload, write_log: &WriteLog, storage: &Storage, + gas_meter: &mut dyn TxVpGasMetering, ) -> Result<(Module, Store)> where DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, @@ -563,35 +519,87 @@ where CN: 'static + CacheName, CA: 'static + WasmCacheAccess, { - match wasm_cache.fetch(code_hash)? { - Some((module, store)) => Ok((module, store)), - None => { - let key = Key::wasm_code(code_hash); - let code = match write_log.read(&key).0 { - Some(StorageModification::Write { value }) => value.clone(), - _ => match storage - .read(&key) - .map_err(|e| { - Error::LoadWasmCode(format!( - "Read wasm code failed from storage: key {}, \ + match code_or_hash { + WasmPayload::Hash(code_hash) => { + let (module, store, tx_len) = match wasm_cache.fetch(code_hash)? { + Some((module, store)) => { + // Gas accounting even if the compiled module is in cache + let key = Key::wasm_code_len(&code_hash); + let tx_len = match write_log.read(&key).0 { + Some(StorageModification::Write { value }) => { + u64::try_from_slice(value).map_err(|e| { + Error::ConversionError(e.to_string()) + }) + } + _ => match storage + .read(&key) + .map_err(|e| { + Error::LoadWasmCode(format!( + "Read wasm code length failed from storage: key {}, \ + error {}", + key, e + )) + })? + .0 + { + Some(v) => u64::try_from_slice(&v).map_err(|e| { + Error::ConversionError(e.to_string()) + }), + None => Err(Error::LoadWasmCode(format!( + "No wasm code length in storage: key {}", + key + ))), + }, + }?; + + (module, store, tx_len) + } + None => { + let key = Key::wasm_code(code_hash); + let code = match write_log.read(&key).0 { + Some(StorageModification::Write { value }) => { + value.clone() + } + _ => match storage + .read(&key) + .map_err(|e| { + Error::LoadWasmCode(format!( + "Read wasm code failed from storage: key {}, \ error {}", - key, e - )) - })? - .0 - { - Some(v) => v, - None => { - return Err(Error::LoadWasmCode(format!( - "No wasm code in storage: key {}", - key - ))); + key, e + )) + })? + .0 + { + Some(v) => v, + None => { + return Err(Error::LoadWasmCode(format!( + "No wasm code in storage: key {}", + key + ))); + } + }, + }; + let tx_len = u64::try_from(code.len()) + .map_err(|e| Error::ConversionError(e.to_string()))?; + + match wasm_cache.compile_or_fetch(code)? { + Some((module, store)) => (module, store, tx_len), + None => return Err(Error::NoCompiledWasmCode), } - }, + } }; + gas_meter.add_wasm_load_from_storage_gas(tx_len)?; + gas_meter.add_compiling_gas(tx_len)?; + Ok((module, store)) + } + WasmPayload::Code(code) => { + gas_meter.add_compiling_gas( + u64::try_from(code.as_ref().len()) + .map_err(|e| Error::ConversionError(e.to_string()))?, + )?; validate_untrusted_wasm(&code).map_err(Error::ValidationError)?; - match wasm_cache.compile_or_fetch(code)? { Some((module, store)) => Ok((module, store)), None => Err(Error::NoCompiledWasmCode), From 980ab099fcc8439096cfc406ebc3750e2f0bd631 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Sat, 20 May 2023 19:06:16 +0200 Subject: [PATCH 19/23] Fixes e2e tests --- tests/src/e2e/ledger_tests.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 9d92ad4b7c..e7f86b9196 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -804,7 +804,7 @@ fn masp_txs_and_queries() -> Result<()> { "--signer", BERTHA, "--gas-limit", - "100", + "150", "--node", &validator_one_rpc, ], @@ -1382,7 +1382,7 @@ fn masp_incentives() -> Result<()> { "--signer", BERTHA, "--gas-limit", - "100", + "150", "--node", &validator_one_rpc ], @@ -1476,7 +1476,7 @@ fn masp_incentives() -> Result<()> { "--signer", ALBERT, "--gas-limit", - "100", + "150", "--node", &validator_one_rpc ], @@ -1636,7 +1636,7 @@ fn masp_incentives() -> Result<()> { "--signer", BERTHA, "--gas-limit", - "100", + "150", "--node", &validator_one_rpc ], From f02c6e72789cf3c2bfa51716edcab12e7ed30123 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 23 May 2023 17:39:33 +0200 Subject: [PATCH 20/23] Adjusts conditional compilation for fee payment --- apps/src/lib/node/ledger/shell/finalize_block.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 9e743d3b29..b0e44fece8 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -199,7 +199,7 @@ where .expect("Error while deleting tx hash from storage"); } - #[cfg(not(feature = "abcipp"))] + #[cfg(not(any(feature = "abciplus", feature = "abcipp")))] if let TxType::Wrapper(wrapper) = &tx_type { // Charge fee if wrapper transaction went out of gas if ErrorCodes::from_u32(processed_tx.result.code).unwrap() From 7b89045d90baaa19c199e4f14a47057d6b8658a3 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Sat, 20 May 2023 19:34:03 +0200 Subject: [PATCH 21/23] Clippy + fmt --- .../lib/node/ledger/shell/finalize_block.rs | 34 ++-- .../lib/node/ledger/shell/process_proposal.rs | 39 ++-- apps/src/lib/wasm_loader/mod.rs | 4 +- benches/mod.rs | 2 +- benches/native_vps.rs | 48 ++--- benches/process_wrapper.rs | 3 +- benches/vps.rs | 158 ++++++++-------- shared/src/ledger/ibc/vp/mod.rs | 169 +++++++++++++----- shared/src/ledger/protocol/mod.rs | 2 +- shared/src/vm/wasm/run.rs | 30 ++-- tests/src/vm_host_env/mod.rs | 34 ++-- 11 files changed, 321 insertions(+), 202 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index b0e44fece8..cfeaa93b15 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1598,9 +1598,11 @@ mod test_finalize_block { // won't receive votes from TM since we receive votes at a 1-block // delay, so votes will be empty here next_block_for_inflation(&mut shell, pkh1.clone(), vec![]); - assert!(rewards_accumulator_handle() - .is_empty(&shell.wl_storage) - .unwrap()); + assert!( + rewards_accumulator_handle() + .is_empty(&shell.wl_storage) + .unwrap() + ); // FINALIZE BLOCK 2. Tell Namada that val1 is the block proposer. // Include votes that correspond to block 1. Make val2 the next block's @@ -1610,9 +1612,11 @@ mod test_finalize_block { assert!(rewards_prod_2.is_empty(&shell.wl_storage).unwrap()); assert!(rewards_prod_3.is_empty(&shell.wl_storage).unwrap()); assert!(rewards_prod_4.is_empty(&shell.wl_storage).unwrap()); - assert!(!rewards_accumulator_handle() - .is_empty(&shell.wl_storage) - .unwrap()); + assert!( + !rewards_accumulator_handle() + .is_empty(&shell.wl_storage) + .unwrap() + ); // Val1 was the proposer, so its reward should be larger than all // others, which should themselves all be equal let acc_sum = get_rewards_sum(&shell.wl_storage); @@ -1713,9 +1717,11 @@ mod test_finalize_block { ); next_block_for_inflation(&mut shell, pkh1.clone(), votes.clone()); } - assert!(rewards_accumulator_handle() - .is_empty(&shell.wl_storage) - .unwrap()); + assert!( + rewards_accumulator_handle() + .is_empty(&shell.wl_storage) + .unwrap() + ); let rp1 = rewards_prod_1 .get(&shell.wl_storage, &Epoch::default()) .unwrap() @@ -1841,10 +1847,12 @@ mod test_finalize_block { let code = event.attributes.get("code").expect("Testfailed").as_str(); assert_eq!(code, String::from(ErrorCodes::WasmRuntimeError).as_str()); - assert!(!shell - .wl_storage - .has_key(&inner_hash_key) - .expect("Test failed")) + assert!( + !shell + .wl_storage + .has_key(&inner_hash_key) + .expect("Test failed") + ) } /// Test that a wrapper transaction rejected by [`process_proposal`] because diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 12ef115843..692f94eacd 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -363,19 +363,24 @@ where } // Tx gas (partial check) - let tx_hash = if tx.code_or_hash.len() - == HASH_LENGTH - { - match Hash::try_from(tx.code_or_hash.as_slice()) { + let tx_hash = + if tx.code_or_hash.len() == HASH_LENGTH { + match Hash::try_from( + tx.code_or_hash.as_slice(), + ) { Ok(hash) => hash, Err(_) => return TxResult { - code: ErrorCodes::DecryptedTxGasLimit.into(), - info: format!("Failed conversion of transaction's hash") - } + code: + ErrorCodes::DecryptedTxGasLimit + .into(), + info: "Failed conversion of \ + transaction's hash" + .to_string(), + }, } - } else { - Hash(tx.code_hash()) - }; + } else { + Hash(tx.code_hash()) + }; let tx_gas = match gas_table.get( &tx_hash.to_string().to_ascii_lowercase(), ) { @@ -383,7 +388,8 @@ where #[cfg(test)] None => 1_000, #[cfg(not(test))] - None => 0, // VPs will rejected the non-whitelisted tx + None => 0, /* VPs will rejected the + * non-whitelisted tx */ }; let inner_tx_gas_limit = temp_wl_storage .storage @@ -394,9 +400,14 @@ where TxGasMeter::new(inner_tx_gas_limit); if let Err(e) = tx_gas_meter.add(tx_gas) { return TxResult { - code: ErrorCodes::DecryptedTxGasLimit.into(), - info: format!("Decrypted transaction gas error: {}", e) - }; + code: ErrorCodes::DecryptedTxGasLimit + .into(), + info: format!( + "Decrypted transaction gas error: \ + {}", + e + ), + }; } } diff --git a/apps/src/lib/wasm_loader/mod.rs b/apps/src/lib/wasm_loader/mod.rs index 8c6dedc7c6..743e9d7b4e 100644 --- a/apps/src/lib/wasm_loader/mod.rs +++ b/apps/src/lib/wasm_loader/mod.rs @@ -135,7 +135,7 @@ pub async fn pre_fetch_wasm(wasm_directory: impl AsRef) { join_all(checksums.0.into_iter().map(|(name, map)| { let wasm_directory = wasm_directory.as_ref().to_owned(); let full_name = - name.replace(".", &format!(".{}.", map.get("hash").unwrap())); + name.replace('.', &format!(".{}.", map.get("hash").unwrap())); // Async check and download (if needed) each file tokio::spawn(async move { @@ -270,7 +270,7 @@ pub fn read_wasm( if let Some(name) = os_name.to_str() { let wasm_path = match checksums.0.get(name) { Some(map) => wasm_directory.as_ref().join(name.replace( - ".", + '.', &format!( ".{}.", map.get("hash").ok_or_else(|| eyre!( diff --git a/benches/mod.rs b/benches/mod.rs index 7444c2b958..6c43b403d4 100644 --- a/benches/mod.rs +++ b/benches/mod.rs @@ -16,6 +16,7 @@ use std::collections::BTreeMap; use std::ops::{Deref, DerefMut}; +use std::str::FromStr; use borsh::BorshSerialize; use ibc_proto::google::protobuf::Any; @@ -88,7 +89,6 @@ use namada_apps::wallet::defaults; use namada_apps::{config, wasm_loader}; use namada_test_utils::tx_data::TxWriteData; use rand_core::OsRng; -use std::str::FromStr; use tempfile::TempDir; pub const WASM_DIR: &str = "../wasm"; diff --git a/benches/native_vps.rs b/benches/native_vps.rs index 92a3799f6a..dea8585d13 100644 --- a/benches/native_vps.rs +++ b/benches/native_vps.rs @@ -189,13 +189,15 @@ fn governance(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!(governance - .validate_tx( - signed_tx.data.as_ref().unwrap(), - governance.ctx.keys_changed, - governance.ctx.verifiers, - ) - .unwrap()) + assert!( + governance + .validate_tx( + signed_tx.data.as_ref().unwrap(), + governance.ctx.keys_changed, + governance.ctx.verifiers, + ) + .unwrap() + ) }) }); } @@ -254,13 +256,15 @@ fn slash_fund(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!(slash_fund - .validate_tx( - tx.data.as_ref().unwrap(), - slash_fund.ctx.keys_changed, - slash_fund.ctx.verifiers, - ) - .unwrap()) + assert!( + slash_fund + .validate_tx( + tx.data.as_ref().unwrap(), + slash_fund.ctx.keys_changed, + slash_fund.ctx.verifiers, + ) + .unwrap() + ) }) }); } @@ -279,7 +283,7 @@ fn ibc(c: &mut Criterion) { ) .unwrap(), counterparty: Counterparty::new( - ClientId::from_str(&"01-tendermint-1").unwrap(), + ClientId::from_str("01-tendermint-1").unwrap(), Some(ConnectionId::new(1)), CommitmentPrefix::try_from(b"ibc".to_vec()).unwrap(), ), @@ -360,13 +364,14 @@ fn ibc(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!(ibc - .validate_tx( + assert!( + ibc.validate_tx( signed_tx.data.as_ref().unwrap(), ibc.ctx.keys_changed, ibc.ctx.verifiers, ) - .unwrap()) + .unwrap() + ) }) }); } @@ -419,13 +424,14 @@ fn ibc_token(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!(ibc - .validate_tx( + assert!( + ibc.validate_tx( signed_tx.data.as_ref().unwrap(), ibc.ctx.keys_changed, ibc.ctx.verifiers, ) - .unwrap()) + .unwrap() + ) }) }); } diff --git a/benches/process_wrapper.rs b/benches/process_wrapper.rs index 0cc3895725..ee64f992ec 100644 --- a/benches/process_wrapper.rs +++ b/benches/process_wrapper.rs @@ -15,7 +15,8 @@ use namada_benches::{generate_tx, BenchShell, TX_TRANSFER_WASM}; fn process_tx(c: &mut Criterion) { let mut shell = BenchShell::default(); - // Advance chain height to allow the inclusion of wrapper txs by the block space allocator + // Advance chain height to allow the inclusion of wrapper txs by the block + // space allocator shell.wl_storage.storage.last_height = BlockHeight(2); let tx = generate_tx( TX_TRANSFER_WASM, diff --git a/benches/vps.rs b/benches/vps.rs index 57f69494f0..e11d8ebc69 100644 --- a/benches/vps.rs +++ b/benches/vps.rs @@ -132,21 +132,23 @@ fn vp_user(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!(run::vp( - &vp_code_hash, - signed_tx, - &TxIndex(0), - &defaults::albert_address(), - &shell.wl_storage.storage, - &shell.wl_storage.write_log, - &mut VpGasMeter::new(u64::MAX, 0), - &BTreeMap::default(), - &keys_changed, - &verifiers, - shell.vp_wasm_cache.clone(), - false, - ) - .unwrap()); + assert!( + run::vp( + &vp_code_hash, + signed_tx, + &TxIndex(0), + &defaults::albert_address(), + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + &mut VpGasMeter::new(u64::MAX, 0), + &BTreeMap::default(), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + false, + ) + .unwrap() + ); }) }); } @@ -268,21 +270,23 @@ fn vp_implicit(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!(run::vp( - &vp_code_hash, - tx, - &TxIndex(0), - &Address::from(&implicit_account.to_public()), - &shell.wl_storage.storage, - &shell.wl_storage.write_log, - &mut VpGasMeter::new(u64::MAX, 0), - &BTreeMap::default(), - &keys_changed, - &verifiers, - shell.vp_wasm_cache.clone(), - false, + assert!( + run::vp( + &vp_code_hash, + tx, + &TxIndex(0), + &Address::from(&implicit_account.to_public()), + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + &mut VpGasMeter::new(u64::MAX, 0), + &BTreeMap::default(), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + false, + ) + .unwrap() ) - .unwrap()) }) }); } @@ -396,21 +400,23 @@ fn vp_validator(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!(run::vp( - &vp_code_hash, - signed_tx, - &TxIndex(0), - &defaults::validator_address(), - &shell.wl_storage.storage, - &shell.wl_storage.write_log, - &mut VpGasMeter::new(u64::MAX, 0), - &BTreeMap::default(), - &keys_changed, - &verifiers, - shell.vp_wasm_cache.clone(), - false, - ) - .unwrap()); + assert!( + run::vp( + &vp_code_hash, + signed_tx, + &TxIndex(0), + &defaults::validator_address(), + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + &mut VpGasMeter::new(u64::MAX, 0), + &BTreeMap::default(), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + false, + ) + .unwrap() + ); }) }); } @@ -454,21 +460,23 @@ fn vp_token(c: &mut Criterion) { group.bench_function(bench_name, |b| { b.iter(|| { - assert!(run::vp( - &vp_code_hash, - signed_tx, - &TxIndex(0), - &defaults::albert_address(), - &shell.wl_storage.storage, - &shell.wl_storage.write_log, - &mut VpGasMeter::new(u64::MAX, 0), - &BTreeMap::default(), - &keys_changed, - &verifiers, - shell.vp_wasm_cache.clone(), - false, - ) - .unwrap()); + assert!( + run::vp( + &vp_code_hash, + signed_tx, + &TxIndex(0), + &defaults::albert_address(), + &shell.wl_storage.storage, + &shell.wl_storage.write_log, + &mut VpGasMeter::new(u64::MAX, 0), + &BTreeMap::default(), + &keys_changed, + &verifiers, + shell.vp_wasm_cache.clone(), + false, + ) + .unwrap() + ); }) }); } @@ -542,21 +550,23 @@ fn vp_masp(c: &mut Criterion) { .verifiers_and_changed_keys(&BTreeSet::default()); b.iter(|| { - assert!(run::vp( - &vp_code_hash, - &signed_tx, - &TxIndex(0), - &defaults::validator_address(), - &shielded_ctx.shell.wl_storage.storage, - &shielded_ctx.shell.wl_storage.write_log, - &mut VpGasMeter::new(u64::MAX, 0), - &BTreeMap::default(), - &keys_changed, - &verifiers, - shielded_ctx.shell.vp_wasm_cache.clone(), - false, - ) - .unwrap()); + assert!( + run::vp( + &vp_code_hash, + &signed_tx, + &TxIndex(0), + &defaults::validator_address(), + &shielded_ctx.shell.wl_storage.storage, + &shielded_ctx.shell.wl_storage.write_log, + &mut VpGasMeter::new(u64::MAX, 0), + &BTreeMap::default(), + &keys_changed, + &verifiers, + shielded_ctx.shell.vp_wasm_cache.clone(), + false, + ) + .unwrap() + ); }) }); } diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index a0c2595dd7..9f11914b21 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -224,8 +224,8 @@ pub fn get_dummy_header() -> crate::types::storage::Header { /// A dummy validator used for testing #[cfg(any(feature = "test", feature = "testing"))] -pub fn get_dummy_genesis_validator( -) -> namada_proof_of_stake::types::GenesisValidator { +pub fn get_dummy_genesis_validator() +-> namada_proof_of_stake::types::GenesisValidator { use rust_decimal::prelude::Decimal; use crate::core::types::address::testing::established_address_1; @@ -737,9 +737,14 @@ mod tests { let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -949,9 +954,14 @@ mod tests { ); let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1054,9 +1064,14 @@ mod tests { ); let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1274,9 +1289,14 @@ mod tests { ); let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1379,9 +1399,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1462,9 +1487,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1581,9 +1611,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1701,9 +1736,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1805,9 +1845,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1907,9 +1952,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } // skip test_close_init_channel() and test_close_confirm_channel() since it @@ -2042,9 +2092,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -2215,9 +2270,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -2357,9 +2417,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -2504,9 +2569,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -2651,8 +2721,13 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } } diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 157ced4bdf..a52f1ce9c6 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -288,7 +288,7 @@ where storage, write_log, &mut gas_meter, - &gas_table, + gas_table, &keys_changed, &verifiers, vp_wasm_cache.clone(), diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index 740b4996ff..fa22e3295e 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -280,6 +280,7 @@ where ) } +#[allow(clippy::too_many_arguments)] fn run_vp( module: wasmer::Module, vp_imports: wasmer::ImportObject, @@ -431,13 +432,13 @@ where ctx, }; let gas_table_key = &namada_core::ledger::parameters::storage::get_gas_table_storage_key(); - let gas_table = match write_log.read(&gas_table_key).0 { + let gas_table = match write_log.read(gas_table_key).0 { Some(StorageModification::Write { value }) => { BTreeMap::try_from_slice(value) .map_err(|e| Error::ConversionError(e.to_string())) } _ => match storage - .read(&gas_table_key) + .read(gas_table_key) .map_err(|e| { Error::LoadWasmCode(format!( "Read gas table from storage failed: {}", @@ -505,7 +506,8 @@ pub fn prepare_wasm_code>(code: T) -> Result> { elements::serialize(module).map_err(Error::SerializationError) } -// Fetch or compile a WASM code from the cache or storage. Account for the loading and code compilation gas costs. +// Fetch or compile a WASM code from the cache or storage. Account for the +// loading and code compilation gas costs. fn fetch_or_compile( wasm_cache: &mut Cache, code_or_hash: WasmPayload, @@ -524,7 +526,7 @@ where let (module, store, tx_len) = match wasm_cache.fetch(code_hash)? { Some((module, store)) => { // Gas accounting even if the compiled module is in cache - let key = Key::wasm_code_len(&code_hash); + let key = Key::wasm_code_len(code_hash); let tx_len = match write_log.read(&key).0 { Some(StorageModification::Write { value }) => { u64::try_from_slice(value).map_err(|e| { @@ -535,10 +537,10 @@ where .read(&key) .map_err(|e| { Error::LoadWasmCode(format!( - "Read wasm code length failed from storage: key {}, \ - error {}", - key, e - )) + "Read wasm code length failed from \ + storage: key {}, error {}", + key, e + )) })? .0 { @@ -564,10 +566,10 @@ where .read(&key) .map_err(|e| { Error::LoadWasmCode(format!( - "Read wasm code failed from storage: key {}, \ - error {}", - key, e - )) + "Read wasm code failed from storage: key \ + {}, error {}", + key, e + )) })? .0 { @@ -599,7 +601,7 @@ where u64::try_from(code.as_ref().len()) .map_err(|e| Error::ConversionError(e.to_string()))?, )?; - validate_untrusted_wasm(&code).map_err(Error::ValidationError)?; + validate_untrusted_wasm(code).map_err(Error::ValidationError)?; match wasm_cache.compile_or_fetch(code)? { Some((module, store)) => Ok((module, store)), None => Err(Error::NoCompiledWasmCode), @@ -765,7 +767,7 @@ mod tests { storage.write(&len_key, code_len).unwrap(); let gas_table_key = &namada_core::ledger::parameters::storage::get_gas_table_storage_key(); storage - .write(&gas_table_key, gas_table.try_to_vec().unwrap()) + .write(gas_table_key, gas_table.try_to_vec().unwrap()) .unwrap(); // Assuming 200 pages, 12.8 MiB limit diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 5acc692d2d..6ba6ab2f39 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -134,11 +134,13 @@ mod tests { // Trying to delete a validity predicate should fail let key = storage::Key::validity_predicate(&test_account); - assert!(panic::catch_unwind(|| { tx::ctx().delete(&key).unwrap() }) - .err() - .map(|a| a.downcast_ref::().cloned().unwrap()) - .unwrap() - .contains("CannotDeleteVp")); + assert!( + panic::catch_unwind(|| { tx::ctx().delete(&key).unwrap() }) + .err() + .map(|a| a.downcast_ref::().cloned().unwrap()) + .unwrap() + .contains("CannotDeleteVp") + ); } #[test] @@ -469,17 +471,21 @@ mod tests { .expect("decoding signed data we just signed") }); assert_eq!(&signed_tx_data.data, data); - assert!(vp::CTX - .verify_tx_signature(&pk, &signed_tx_data.sig) - .unwrap()); + assert!( + vp::CTX + .verify_tx_signature(&pk, &signed_tx_data.sig) + .unwrap() + ); let other_keypair = key::testing::keypair_2(); - assert!(!vp::CTX - .verify_tx_signature( - &other_keypair.ref_to(), - &signed_tx_data.sig - ) - .unwrap()); + assert!( + !vp::CTX + .verify_tx_signature( + &other_keypair.ref_to(), + &signed_tx_data.sig + ) + .unwrap() + ); } } From 6478fc30b8dff1ab460e7d8d390f0af496e9bd5d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 19 May 2023 17:36:45 +0000 Subject: [PATCH 22/23] [ci] wasm checksums update --- wasm/checksums.json | 90 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 72 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 09154499f0..90e6f150b4 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,74 @@ { - "tx_bond.wasm": "tx_bond.1fac5f79b23232f25370160cc71f3ae9c6e6b35a929d79922826d0853288be6a.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.fc6b3d798cbda1833e1ca9be03fe0cab998048e0a829ae127ac1282b300e7f0e.wasm", - "tx_ibc.wasm": "tx_ibc.9a8291d8a249f19d17608d8c1ff5d13a8eea25b9cf1629299707553121ff78fc.wasm", - "tx_init_account.wasm": "tx_init_account.016e2ff541a09b2e4709d7d1b8d36d336412ccb57ab7390fdfb5726324dec8a5.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.32b350ea3377b679bb81035bb19be9f0dd7d678e4de4e8ddb24f8050c5564ba5.wasm", - "tx_init_validator.wasm": "tx_init_validator.c400691869282c6302a2772e11ab086798bbbdcc60d497c3dc279096bd2ad4bd.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.1981f185eae27de9135244b8238fd388206268e69dd511f246751aeaf8137b2a.wasm", - "tx_transfer.wasm": "tx_transfer.54cee6bef28d0ff33281788c124eaabf49486414a0a3eba1a66bfba33347c824.wasm", - "tx_unbond.wasm": "tx_unbond.486ed4ed2324c783a1715b94dc8dcda6fbb3ab7827d66945f8d9179de743fd79.wasm", - "tx_update_vp.wasm": "tx_update_vp.bfcf8672a62187858551fc914b675a9dbfd581d65daffbbbebcedcc28f7aa339.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.9a170b3265c9d6e58dc4f7889c7426698f3f72a3797bbe4c5cbb21f24dfffb70.wasm", - "tx_withdraw.wasm": "tx_withdraw.b77e8e47d787dfc7a311f4bb4c4772573064e5cdd0251af2c4bc6b807ef4f362.wasm", - "vp_implicit.wasm": "vp_implicit.835dc08ccebdb356d0d757cd9813a56a40a98bc688f12c6f8fed74310b2c6d13.wasm", - "vp_masp.wasm": "vp_masp.5cd63d431ffde6c33f0e1fb2a85b940721a924656998e187fb10dc2064676b1d.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.cf0b5c725807c847a271f72711aa4e1d059539cbc6e2d569d5e6e9fcc14253f1.wasm", - "vp_token.wasm": "vp_token.0b0152e2974bfa3f15d3fc30ee8d9687b5418055c67314cedbf50ce972b763c5.wasm", - "vp_user.wasm": "vp_user.0ed07e207a6e1f29fdf113a39f9032d0cbec04a6e92a040970d96bb9c3ddbed8.wasm", - "vp_validator.wasm": "vp_validator.bfa5e9a46f6d722a16f2ded4bb6c05dde382e6de6e882847714720804b2c7661.wasm" + "tx_bond.wasm": { + "gas": "160", + "hash": "2c7bee49a52513a403fb35694ebfd238b9d4b96aeaa6d43cc67f5a81d378e080" + }, + "tx_change_validator_commission.wasm": { + "gas": "220", + "hash": "149ad6f2ad711282b043f36727fe4af9c0d3a51887e683c2b3032fe4ce34281b" + }, + "tx_ibc.wasm": { + "gas": "1240", + "hash": "74c8041170ef6944c9e2fa3bf87cdccdd1c80108e6f7108277c521f25e422aba" + }, + "tx_init_account.wasm": { + "gas": "230", + "hash": "4d975d0f4c7959a4906c47f98d257d003a034074f6af52791e737bfb3b469ef2" + }, + "tx_init_proposal.wasm": { + "gas": "40", + "hash": "508b7acdb8e4031d339adc70cca707fc8318481a2f63178b1c865d28d153c82d" + }, + "tx_init_validator.wasm": { + "gas": "730", + "hash": "eb8a6c44d87c330f78cb552fd3a6778831964d76c2780f9bfe52fd52be27a71f" + }, + "tx_reveal_pk.wasm": { + "gas": "170", + "hash": "be878e819c5a50a057818ca36f9f803967f2750e9defeb1122c6a07292c4f10b" + }, + "tx_transfer.wasm": { + "gas": "110", + "hash": "22e7ee45debf9e826058090665373a77d6df6d4739116692c86a2f7e4d879703" + }, + "tx_unbond.wasm": { + "gas": "430", + "hash": "a1ccdf15828ed0de597528e0cedfcc1572f824310b60cbd564ec4ff13f8c16b1" + }, + "tx_update_vp.wasm": { + "gas": "140", + "hash": "000833cf971f5e9d5d7842ad474b6ae939e46d20b18924af116b54af7433cd64" + }, + "tx_vote_proposal.wasm": { + "gas": "120", + "hash": "636e971ecbd6ed708ee6e69d15020b2f08a6794d8b2ddabf7c64c7406be235ca" + }, + "tx_withdraw.wasm": { + "gas": "260", + "hash": "a88f0fe4fbcad6dc0ae27fba8b5fd6fb415e074252f778aa93306a58041e865f" + }, + "vp_implicit.wasm": { + "gas": "40", + "hash": "186f8912945cb9c5f36dbda8402bcef0073e714bf89e229a9e2ed26de8401cb9" + }, + "vp_masp.wasm": { + "gas": "8030", + "hash": "e50ca2a72df71bb71d0e405a003aa4f7210f3c336f7d3310089cf4e87ebbea30" + }, + "vp_testnet_faucet.wasm": { + "gas": "0", + "hash": "a26ae96c2700063c043fc180aaf6114a3f2621759b3518193f374ac5beebe6c4" + }, + "vp_token.wasm": { + "gas": "30", + "hash": "f5dd59c0c21e4e0bbfa22a87a556a0d0178b1ec33aa357641ab25215d2fd8813" + }, + "vp_user.wasm": { + "gas": "60", + "hash": "ea9589a9ef40fd0f94dfc0b081e37efc0d9e91b6f7ba34153d61f02f372c4650" + }, + "vp_validator.wasm": { + "gas": "50", + "hash": "98c2ded76ae76eb0ec25980a7fbd9c827a8a2e2186532f1c03edb4a469eff671" + } } \ No newline at end of file From 6349f96a357562f10501ca09af7beb69b6d65d86 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 31 Mar 2023 18:35:03 +0200 Subject: [PATCH 23/23] changelog: add #1242 --- .changelog/unreleased/1242-whitelist-gas-accounting.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/1242-whitelist-gas-accounting.md diff --git a/.changelog/unreleased/1242-whitelist-gas-accounting.md b/.changelog/unreleased/1242-whitelist-gas-accounting.md new file mode 100644 index 0000000000..7387faab86 --- /dev/null +++ b/.changelog/unreleased/1242-whitelist-gas-accounting.md @@ -0,0 +1,3 @@ +- Implemented a first gas metering system based on the + runtime cost of whitelisted transactions and VPs. + ([#1242](https://github.com/anoma/namada/pull/1242)) \ No newline at end of file