From bd37b18d8d56d81254c7e4b397c83f0d625651b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 22 May 2025 15:04:38 +0200 Subject: [PATCH 01/10] vm: allow to pick gas meter kind --- crates/node/src/bench_utils.rs | 1 + crates/node/src/dry_run_tx.rs | 1 + crates/node/src/protocol.rs | 20 ++++++++++++++++++++ crates/tests/src/vm_host_env/tx.rs | 1 + crates/vm/src/wasm/run.rs | 29 +++++++++++++++++++++++++++++ 5 files changed, 52 insertions(+) diff --git a/crates/node/src/bench_utils.rs b/crates/node/src/bench_utils.rs index 7cf954a68ee..644685d9299 100644 --- a/crates/node/src/bench_utils.rs +++ b/crates/node/src/bench_utils.rs @@ -283,6 +283,7 @@ impl BenchShellInner { batched_tx.cmt, &mut self.inner.vp_wasm_cache, &mut self.inner.tx_wasm_cache, + run::GasMeterKind::MutGlobal, ) .unwrap() } diff --git a/crates/node/src/dry_run_tx.rs b/crates/node/src/dry_run_tx.rs index 0dbc502c6cb..c24f615049e 100644 --- a/crates/node/src/dry_run_tx.rs +++ b/crates/node/src/dry_run_tx.rs @@ -106,6 +106,7 @@ where &mut state, &mut vp_wasm_cache, &mut tx_wasm_cache, + protocol::GasMeterKind::HostFn, ) .map_err(|err| err.error) .into_storage_result()?; diff --git a/crates/node/src/protocol.rs b/crates/node/src/protocol.rs index a929edb76a5..904a1b05ab8 100644 --- a/crates/node/src/protocol.rs +++ b/crates/node/src/protocol.rs @@ -37,6 +37,8 @@ use namada_sdk::validation::{ MultitokenVp, NativeVpCtx, ParametersVp, PgfVp, PosVp, }; use namada_sdk::{governance, parameters, state, storage, token}; +#[doc(inline)] +pub use namada_vm::wasm::run::GasMeterKind; use namada_vm::wasm::{TxCache, VpCache}; use namada_vm::{self, WasmCacheAccess, wasm}; use namada_vote_ext::EthereumTxData; @@ -249,6 +251,7 @@ where state, vp_wasm_cache, tx_wasm_cache, + GasMeterKind::MutGlobal, ) } else { // Governance proposal. We don't allow tx batches in this case, @@ -266,6 +269,7 @@ where vp_wasm_cache, tx_wasm_cache, }, + GasMeterKind::MutGlobal, ) .map_err(|e| Box::new(DispatchError::from(e)))?; @@ -343,6 +347,7 @@ pub(crate) fn dispatch_inner_txs<'a, S, D, H, CA>( state: &'a mut S, vp_wasm_cache: &'a mut VpCache, tx_wasm_cache: &'a mut TxCache, + gas_meter_kind: GasMeterKind, ) -> std::result::Result, Box> where S: 'static @@ -378,6 +383,7 @@ where vp_wasm_cache, tx_wasm_cache, }, + gas_meter_kind, ) { Err(Error::GasError(ref msg)) => { // Gas error aborts the execution of the entire batch @@ -826,6 +832,7 @@ where vp_wasm_cache, tx_wasm_cache, }, + GasMeterKind::MutGlobal, ) { Ok(result) => { // NOTE: do not commit yet cause this could be exploited to get @@ -1022,6 +1029,7 @@ fn apply_wasm_tx( batched_tx: &BatchedTxRef<'_>, tx_index: &TxIndex, shell_params: ShellParams<'_, S, D, H, CA>, + gas_meter_kind: GasMeterKind, ) -> Result where S: 'static + State + ReadConversionState + Sync, @@ -1044,6 +1052,7 @@ where tx_gas_meter, vp_wasm_cache, tx_wasm_cache, + gas_meter_kind, )?; let vps_result = check_vps(CheckVps { @@ -1053,6 +1062,7 @@ where tx_gas_meter: &mut tx_gas_meter.borrow_mut(), verifiers_from_tx: &verifiers, vp_wasm_cache, + gas_meter_kind, })?; let initialized_accounts = state.write_log().get_initialized_accounts(); @@ -1161,6 +1171,7 @@ fn execute_tx( tx_gas_meter: &RefCell, vp_wasm_cache: &mut VpCache, tx_wasm_cache: &mut TxCache, + gas_meter_kind: GasMeterKind, ) -> Result> where S: State, @@ -1177,6 +1188,7 @@ where batched_tx.cmt, vp_wasm_cache, tx_wasm_cache, + gas_meter_kind, ) .map_err(|err| match err { wasm::run::Error::GasError(msg) => Error::GasError(msg), @@ -1197,6 +1209,7 @@ where tx_gas_meter: &'a mut TxGasMeter, verifiers_from_tx: &'a BTreeSet
, vp_wasm_cache: &'a mut VpCache, + gas_meter_kind: GasMeterKind, } /// Check the acceptance of a transaction by validity predicates @@ -1208,6 +1221,7 @@ fn check_vps( tx_gas_meter, verifiers_from_tx, vp_wasm_cache, + gas_meter_kind, }: CheckVps<'_, S, CA>, ) -> Result where @@ -1226,6 +1240,7 @@ where state, tx_gas_meter, vp_wasm_cache, + gas_meter_kind, )?; tracing::debug!("Total VPs gas cost {:?}", vps_gas); @@ -1246,6 +1261,7 @@ fn execute_vps( state: &S, tx_gas_meter: &TxGasMeter, vp_wasm_cache: &mut VpCache, + gas_meter_kind: GasMeterKind, ) -> Result<(VpsResult, namada_sdk::gas::Gas)> where S: 'static + ReadConversionState + State + Sync, @@ -1281,6 +1297,7 @@ where &keys_changed, &verifiers, vp_wasm_cache.clone(), + gas_meter_kind, ) .map_err(|err| match err { wasm::run::Error::GasError(msg) => { @@ -1736,6 +1753,7 @@ mod tests { &state, &gas_meter, &mut vp_cache, + GasMeterKind::MutGlobal, ); assert!(matches!(result.unwrap_err(), Error::GasError(_))); } @@ -1788,6 +1806,7 @@ mod tests { &Default::default(), &Default::default(), vp_cache.clone(), + GasMeterKind::MutGlobal, ) .is_ok() ); @@ -1817,6 +1836,7 @@ mod tests { &Default::default(), &Default::default(), vp_cache.clone(), + GasMeterKind::MutGlobal, ) .is_err() ); diff --git a/crates/tests/src/vm_host_env/tx.rs b/crates/tests/src/vm_host_env/tx.rs index 2bc7f385f39..8b26c14d9c2 100644 --- a/crates/tests/src/vm_host_env/tx.rs +++ b/crates/tests/src/vm_host_env/tx.rs @@ -235,6 +235,7 @@ impl TestTxEnv { &self.batched_tx.cmt, &mut self.vp_wasm_cache, &mut self.tx_wasm_cache, + wasm::run::GasMeterKind::MutGlobal, ) .and(Ok(())) } diff --git a/crates/vm/src/wasm/run.rs b/crates/vm/src/wasm/run.rs index a94fbb3a509..1c24fc9d874 100644 --- a/crates/vm/src/wasm/run.rs +++ b/crates/vm/src/wasm/run.rs @@ -37,6 +37,16 @@ use crate::{ validate_untrusted_wasm, }; +/// Choose the gas mmeter used for WASM instructions +#[derive(Debug, Clone, Copy)] +pub enum GasMeterKind { + /// Gas accounting using a host env function. Suitable for unstructed code. + HostFn, + /// Global mutable variable accounted inside WASM. This should only be used + /// for trusted WASM code as a malicious code might modify the gas meter + MutGlobal, +} + const TX_ENTRYPOINT: &str = "_apply_tx"; const VP_ENTRYPOINT: &str = "_validate_tx"; const WASM_STACK_LIMIT: u32 = u16::MAX as u32; @@ -145,6 +155,7 @@ pub fn tx( cmt: &TxCommitments, vp_wasm_cache: &mut VpCache, tx_wasm_cache: &mut TxCache, + _gas_meter_kind: GasMeterKind, ) -> Result> where S: StateRead + State + StorageRead, @@ -321,6 +332,7 @@ pub fn vp( keys_changed: &BTreeSet, verifiers: &BTreeSet
, mut vp_wasm_cache: VpCache, + _gas_meter_kind: GasMeterKind, ) -> Result<()> where S: StateRead, @@ -1203,6 +1215,7 @@ mod tests { batched_tx.cmt, &mut vp_cache, &mut tx_cache, + GasMeterKind::MutGlobal, ); assert!(result.is_ok(), "Expected success, got {:?}", result); @@ -1222,6 +1235,7 @@ mod tests { batched_tx.cmt, &mut vp_cache, &mut tx_cache, + GasMeterKind::MutGlobal, ) .expect_err("Expected to run out of memory"); @@ -1294,6 +1308,7 @@ mod tests { &keys_changed, &verifiers, vp_cache.clone(), + GasMeterKind::MutGlobal, ) .is_ok() ); @@ -1326,6 +1341,7 @@ mod tests { &keys_changed, &verifiers, vp_cache, + GasMeterKind::MutGlobal, ) .is_err() ); @@ -1376,6 +1392,7 @@ mod tests { &keys_changed, &verifiers, vp_cache.clone(), + GasMeterKind::MutGlobal, ); assert!(result.is_ok(), "Expected success, got {:?}", result); @@ -1395,6 +1412,7 @@ mod tests { &keys_changed, &verifiers, vp_cache, + GasMeterKind::MutGlobal, ) .expect_err("Expected to run out of memory"); @@ -1445,6 +1463,7 @@ mod tests { batched_tx.cmt, &mut vp_cache, &mut tx_cache, + GasMeterKind::MutGlobal, ); // Depending on platform, we get a different error from the running out // of memory @@ -1510,6 +1529,7 @@ mod tests { &keys_changed, &verifiers, vp_cache, + GasMeterKind::MutGlobal, ); // Depending on platform, we get a different error from the running out // of memory @@ -1582,6 +1602,7 @@ mod tests { batched_tx.cmt, &mut vp_cache, &mut tx_cache, + GasMeterKind::MutGlobal, ) .expect_err("Expected to run out of memory"); @@ -1639,6 +1660,7 @@ mod tests { &keys_changed, &verifiers, vp_cache, + GasMeterKind::MutGlobal, ) .expect_err("Expected to run out of memory"); @@ -1716,6 +1738,7 @@ mod tests { &keys_changed, &verifiers, vp_cache, + GasMeterKind::MutGlobal, ) .is_err() ); @@ -1826,6 +1849,7 @@ mod tests { batched_tx.cmt, &mut vp_cache, &mut tx_cache, + GasMeterKind::MutGlobal, ); assert!(matches!(result.unwrap_err(), Error::GasError(_))); @@ -1867,6 +1891,7 @@ mod tests { batched_tx.cmt, &mut vp_cache, &mut tx_cache, + GasMeterKind::MutGlobal, ); assert!(matches!(result.unwrap_err(), Error::GasError(_))); @@ -1910,6 +1935,7 @@ mod tests { &keys_changed, &verifiers, vp_cache.clone(), + GasMeterKind::MutGlobal, ); assert!(matches!(result.unwrap_err(), Error::GasError(_))); @@ -1954,6 +1980,7 @@ mod tests { &keys_changed, &verifiers, vp_cache.clone(), + GasMeterKind::MutGlobal, ); assert!(matches!(result.unwrap_err(), Error::GasError(_))); @@ -2129,6 +2156,7 @@ mod tests { &keys_changed, &verifiers, vp_cache.clone(), + GasMeterKind::MutGlobal, ) } @@ -2175,6 +2203,7 @@ mod tests { batched_tx.cmt, vp_cache, tx_cache, + GasMeterKind::MutGlobal, ) } From e3300184eabda72836b0b1c42ff694b99dd144d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 22 May 2025 17:36:34 +0200 Subject: [PATCH 02/10] split wasm caches based on gas meter kind, inject the selected WASM meter --- crates/benches/host_env.rs | 3 +- crates/benches/native_vps.rs | 14 +- .../ethereum_bridge/src/vp/bridge_pool_vp.rs | 3 +- .../ethereum_bridge/src/vp/eth_bridge_vp.rs | 3 +- crates/ethereum_bridge/src/vp/nut_vp.rs | 3 +- crates/gas/src/lib.rs | 10 + crates/governance/src/vp/mod.rs | 27 +- crates/governance/src/vp/pgf.rs | 4 +- crates/ibc/src/vp/mod.rs | 21 +- crates/node/src/dry_run_tx.rs | 2 +- crates/node/src/protocol.rs | 1 + crates/node/src/shell/finalize_block.rs | 3 +- crates/node/src/shell/init_chain.rs | 10 +- crates/shielded_token/src/vp.rs | 5 +- crates/tests/src/native_vp/mod.rs | 4 +- crates/tests/src/vm_host_env/ibc.rs | 4 +- crates/trans_token/src/vp.rs | 17 +- crates/vm/src/host_env.rs | 19 +- .../vm/src/wasm/compilation_cache/common.rs | 479 +++++++++++++----- crates/vm/src/wasm/run.rs | 96 +++- crates/vp/src/native_vp.rs | 6 +- 21 files changed, 567 insertions(+), 167 deletions(-) diff --git a/crates/benches/host_env.rs b/crates/benches/host_env.rs index ecf2114c4c7..a8c76385a79 100644 --- a/crates/benches/host_env.rs +++ b/crates/benches/host_env.rs @@ -1,6 +1,7 @@ use criterion::{Criterion, criterion_group, criterion_main}; use namada_apps_lib::account::AccountPublicKeysMap; use namada_apps_lib::collections::{HashMap, HashSet}; +use namada_apps_lib::gas::GasMeterKind; use namada_apps_lib::storage::DB; use namada_apps_lib::token::{Amount, Transfer}; use namada_apps_lib::tx::Authorization; @@ -99,7 +100,7 @@ fn compile_wasm(c: &mut Criterion) { shell .write() .tx_wasm_cache - .compile_or_fetch(&wasm_code) + .compile_or_fetch(&wasm_code, GasMeterKind::MutGlobal) .unwrap() .unwrap() }, diff --git a/crates/benches/native_vps.rs b/crates/benches/native_vps.rs index 63ba6afebe2..7cab5f5261d 100644 --- a/crates/benches/native_vps.rs +++ b/crates/benches/native_vps.rs @@ -19,7 +19,7 @@ use namada_apps_lib::eth_bridge::read_native_erc20_address; use namada_apps_lib::eth_bridge::storage::eth_bridge_queries::is_bridge_comptime_enabled; use namada_apps_lib::eth_bridge::storage::whitelist; use namada_apps_lib::eth_bridge_pool::{GasFee, PendingTransfer}; -use namada_apps_lib::gas::{TxGasMeter, VpGasMeter}; +use namada_apps_lib::gas::{GasMeterKind, TxGasMeter, VpGasMeter}; use namada_apps_lib::governance::pgf::storage::steward::StewardDetail; use namada_apps_lib::governance::storage::proposal::ProposalType; use namada_apps_lib::governance::storage::vote::ProposalVote; @@ -231,6 +231,7 @@ fn governance(c: &mut Criterion) { &keys_changed, &verifiers, shell.vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); group.bench_function(bench_name, |b| { @@ -459,6 +460,7 @@ fn ibc(c: &mut Criterion) { &keys_changed, &verifiers, shell_read.vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, )); group.bench_function(bench_name, |b| { @@ -526,6 +528,7 @@ fn vp_multitoken(c: &mut Criterion) { &keys_changed, &verifiers, shell.vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); group.bench_function(bench_name, |b| { @@ -651,6 +654,7 @@ fn masp(c: &mut Criterion) { &keys_changed, &verifiers, shell_read.vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); b.iter(|| { @@ -1264,6 +1268,7 @@ fn pgf(c: &mut Criterion) { &keys_changed, &verifiers, shell.vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); group.bench_function(bench_name, |b| { @@ -1341,6 +1346,7 @@ fn eth_bridge_nut(c: &mut Criterion) { &keys_changed, &verifiers, shell.vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); c.bench_function("vp_eth_bridge_nut", |b| { @@ -1414,6 +1420,7 @@ fn eth_bridge(c: &mut Criterion) { &keys_changed, &verifiers, shell.vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); c.bench_function("vp_eth_bridge", |b| { @@ -1512,6 +1519,7 @@ fn eth_bridge_pool(c: &mut Criterion) { &keys_changed, &verifiers, shell.vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); c.bench_function("vp_eth_bridge_pool", |b| { @@ -1585,6 +1593,7 @@ fn parameters(c: &mut Criterion) { &keys_changed, &verifiers, shell.vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); group.bench_function(bench_name, |b| { @@ -1661,6 +1670,7 @@ fn pos(c: &mut Criterion) { &keys_changed, &verifiers, shell.vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); group.bench_function(bench_name, |b| { @@ -1714,6 +1724,7 @@ fn ibc_vp_validate_action(c: &mut Criterion) { &keys_changed, &verifiers, shell_read.vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, )); // Use an empty verifiers set placeholder for validation, this is only // needed in actual txs to addresses whose VPs should be triggered @@ -1777,6 +1788,7 @@ fn ibc_vp_execute_action(c: &mut Criterion) { &keys_changed, &verifiers, shell_read.vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, )); // Use an empty verifiers set placeholder for validation, this is only // needed in actual txs to addresses whose VPs should be triggered diff --git a/crates/ethereum_bridge/src/vp/bridge_pool_vp.rs b/crates/ethereum_bridge/src/vp/bridge_pool_vp.rs index c60509589d2..ffe10c8fde9 100644 --- a/crates/ethereum_bridge/src/vp/bridge_pool_vp.rs +++ b/crates/ethereum_bridge/src/vp/bridge_pool_vp.rs @@ -633,7 +633,7 @@ mod test_bridge_pool_vp { use namada_core::borsh::BorshSerializeExt; use namada_core::eth_bridge_pool::{GasFee, TransferToEthereum}; use namada_core::hash::Hash; - use namada_gas::{TxGasMeter, VpGasMeter}; + use namada_gas::{GasMeterKind, TxGasMeter, VpGasMeter}; use namada_state::testing::TestState; use namada_state::write_log::WriteLog; use namada_state::{StateRead, StorageWrite, TxIndex}; @@ -925,6 +925,7 @@ mod test_bridge_pool_vp { keys_changed, verifiers, VpCache::new(temp_dir(), 100usize), + GasMeterKind::MutGlobal, ) } diff --git a/crates/ethereum_bridge/src/vp/eth_bridge_vp.rs b/crates/ethereum_bridge/src/vp/eth_bridge_vp.rs index b251949ceca..2337382c664 100644 --- a/crates/ethereum_bridge/src/vp/eth_bridge_vp.rs +++ b/crates/ethereum_bridge/src/vp/eth_bridge_vp.rs @@ -156,7 +156,7 @@ mod tests { use namada_core::borsh::BorshSerializeExt; use namada_core::ethereum_events; use namada_core::ethereum_events::EthAddress; - use namada_gas::{TxGasMeter, VpGasMeter}; + use namada_gas::{GasMeterKind, TxGasMeter, VpGasMeter}; use namada_state::testing::TestState; use namada_state::{StateRead, StorageWrite, TxIndex}; use namada_trans_token::storage_key::{balance_key, minted_balance_key}; @@ -249,6 +249,7 @@ mod tests { keys_changed, verifiers, VpCache::new(temp_dir(), 100usize), + GasMeterKind::MutGlobal, ) } diff --git a/crates/ethereum_bridge/src/vp/nut_vp.rs b/crates/ethereum_bridge/src/vp/nut_vp.rs index e8d92183281..40f637acf68 100644 --- a/crates/ethereum_bridge/src/vp/nut_vp.rs +++ b/crates/ethereum_bridge/src/vp/nut_vp.rs @@ -108,7 +108,7 @@ mod test_nuts { use namada_core::borsh::BorshSerializeExt; use namada_core::ethereum_events::testing::DAI_ERC20_ETH_ADDRESS; use namada_core::storage::TxIndex; - use namada_gas::{TxGasMeter, VpGasMeter}; + use namada_gas::{GasMeterKind, TxGasMeter, VpGasMeter}; use namada_state::testing::TestState; use namada_state::{StateRead, StorageWrite}; use namada_trans_token::storage_key::balance_key; @@ -196,6 +196,7 @@ mod test_nuts { &keys_changed, &verifiers, VpCache::new(temp_dir(), 100usize), + GasMeterKind::MutGlobal, ); // print debug info in case we run into failures diff --git a/crates/gas/src/lib.rs b/crates/gas/src/lib.rs index e3bbf47c68f..56aa6953ff2 100644 --- a/crates/gas/src/lib.rs +++ b/crates/gas/src/lib.rs @@ -30,6 +30,16 @@ use namada_migrations::*; use serde::{Deserialize, Serialize}; use thiserror::Error; +/// Choose the gas mmeter used for WASM instructions +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum GasMeterKind { + /// Gas accounting using a host env function. Suitable for unstructed code. + HostFn, + /// Global mutable variable accounted inside WASM. This should only be used + /// for trusted WASM code as a malicious code might modify the gas meter + MutGlobal, +} + #[allow(missing_docs)] #[derive(Error, Debug, Clone, PartialEq, Eq)] pub enum Error { diff --git a/crates/governance/src/vp/mod.rs b/crates/governance/src/vp/mod.rs index 5885d648590..896edb95584 100644 --- a/crates/governance/src/vp/mod.rs +++ b/crates/governance/src/vp/mod.rs @@ -1178,7 +1178,7 @@ mod test { use namada_core::key::testing::keypair_1; use namada_core::parameters::Parameters; use namada_core::time::DateTimeUtc; - use namada_gas::{TxGasMeter, VpGasMeter}; + use namada_gas::{GasMeterKind, TxGasMeter, VpGasMeter}; use namada_proof_of_stake::bond_tokens; use namada_proof_of_stake::test_utils::get_dummy_genesis_validator; use namada_state::mockdb::MockDB; @@ -1285,6 +1285,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); // this should return true because state has been stored @@ -1553,6 +1554,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); // this should return true because state has been stored @@ -1657,6 +1659,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let result = GovernanceVp::validate_tx( @@ -1762,6 +1765,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let result = GovernanceVp::validate_tx( @@ -1867,6 +1871,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); // this should return true because state has been stored @@ -1953,6 +1958,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); // this should return true because state has been stored @@ -2039,6 +2045,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); // this should return true because state has been stored @@ -2143,6 +2150,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); // this should return true because state has been stored @@ -2247,6 +2255,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); // this should return true because state has been stored @@ -2333,6 +2342,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); // this should return true because state has been stored @@ -2388,6 +2398,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); assert_matches!( @@ -2473,6 +2484,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); // this should return true because state has been stored @@ -2528,6 +2540,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); assert_matches!( @@ -2613,6 +2626,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); // this should return true because state has been stored @@ -2668,6 +2682,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); assert_matches!( @@ -2753,6 +2768,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); assert_matches!( @@ -2825,6 +2841,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); assert_matches!( @@ -2910,6 +2927,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); assert_matches!( @@ -2982,6 +3000,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); assert_matches!( @@ -3067,6 +3086,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); assert_matches!( @@ -3139,6 +3159,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); assert_matches!( @@ -3225,6 +3246,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); let res = GovernanceVp::validate_tx( @@ -3315,6 +3337,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); let res = GovernanceVp::validate_tx( @@ -3400,6 +3423,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); let res = GovernanceVp::validate_tx( @@ -3490,6 +3514,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); let res = GovernanceVp::validate_tx( diff --git a/crates/governance/src/vp/pgf.rs b/crates/governance/src/vp/pgf.rs index 7ad14acaa20..43a75e6d7ff 100644 --- a/crates/governance/src/vp/pgf.rs +++ b/crates/governance/src/vp/pgf.rs @@ -281,7 +281,7 @@ mod test { use namada_core::key::RefTo; use namada_core::key::testing::keypair_1; use namada_core::token; - use namada_gas::{TxGasMeter, VpGasMeter}; + use namada_gas::{GasMeterKind, TxGasMeter, VpGasMeter}; use namada_proof_of_stake::test_utils::get_dummy_genesis_validator; use namada_state::testing::TestState; use namada_state::{BlockHeight, Epoch, State, StateRead, TxIndex}; @@ -415,6 +415,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); let res = @@ -501,6 +502,7 @@ mod test { &keys_changed, &verifiers, vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); let res = diff --git a/crates/ibc/src/vp/mod.rs b/crates/ibc/src/vp/mod.rs index b26cac8f831..441c31a6cab 100644 --- a/crates/ibc/src/vp/mod.rs +++ b/crates/ibc/src/vp/mod.rs @@ -494,7 +494,7 @@ mod tests { use namada_core::storage::TxIndex; use namada_core::tendermint::time::Time as TmTime; use namada_core::time::DurationSecs; - use namada_gas::{TxGasMeter, VpGasMeter}; + use namada_gas::{GasMeterKind, TxGasMeter, VpGasMeter}; use namada_governance::parameters::GovernanceParameters; use namada_parameters::EpochDuration; use namada_parameters::storage::get_epoch_duration_storage_key; @@ -1060,6 +1060,7 @@ mod tests { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let ibc = Ibc::new(ctx); @@ -1136,6 +1137,7 @@ mod tests { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let ibc = Ibc::new(ctx); @@ -1262,6 +1264,7 @@ mod tests { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let ibc = Ibc::new(ctx); // this should return true because state has been stored @@ -1371,6 +1374,7 @@ mod tests { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let ibc = Ibc::new(ctx); // this should return true because state has been stored @@ -1465,6 +1469,7 @@ mod tests { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let ibc = Ibc::new(ctx); // this should fail because no event @@ -1586,6 +1591,7 @@ mod tests { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let ibc = Ibc::new(ctx); // this should return true because state has been stored @@ -1696,6 +1702,7 @@ mod tests { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let ibc = Ibc::new(ctx); assert_matches!( @@ -1791,6 +1798,7 @@ mod tests { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let ibc = Ibc::new(ctx); assert_matches!( @@ -1914,6 +1922,7 @@ mod tests { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let ibc = Ibc::new(ctx); assert_matches!( @@ -2036,6 +2045,7 @@ mod tests { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let ibc = Ibc::new(ctx); assert_matches!( @@ -2143,6 +2153,7 @@ mod tests { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let ibc = Ibc::new(ctx); assert_matches!( @@ -2245,6 +2256,7 @@ mod tests { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let ibc = Ibc::new(ctx); assert_matches!( @@ -2401,6 +2413,7 @@ mod tests { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let ibc = Ibc::new(ctx); assert_matches!( @@ -2612,6 +2625,7 @@ mod tests { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let ibc = Ibc::new(ctx); assert_matches!( @@ -2767,6 +2781,7 @@ mod tests { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let ibc = Ibc::new(ctx); assert_matches!( @@ -2925,6 +2940,7 @@ mod tests { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let ibc = Ibc::new(ctx); assert_matches!( @@ -3083,6 +3099,7 @@ mod tests { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let ibc = Ibc::new(ctx); assert_matches!( @@ -3261,6 +3278,7 @@ mod tests { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let ibc = Ibc::new(ctx); assert_matches!( @@ -3495,6 +3513,7 @@ mod tests { &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let ibc = Ibc::new(ctx); assert_matches!( diff --git a/crates/node/src/dry_run_tx.rs b/crates/node/src/dry_run_tx.rs index c24f615049e..be04c1b7522 100644 --- a/crates/node/src/dry_run_tx.rs +++ b/crates/node/src/dry_run_tx.rs @@ -106,7 +106,7 @@ where &mut state, &mut vp_wasm_cache, &mut tx_wasm_cache, - protocol::GasMeterKind::HostFn, + protocol::GasMeterKind::MutGlobal, ) .map_err(|err| err.error) .into_storage_result()?; diff --git a/crates/node/src/protocol.rs b/crates/node/src/protocol.rs index 904a1b05ab8..27c002fd591 100644 --- a/crates/node/src/protocol.rs +++ b/crates/node/src/protocol.rs @@ -1320,6 +1320,7 @@ where &keys_changed, &verifiers, vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); match internal_addr { diff --git a/crates/node/src/shell/finalize_block.rs b/crates/node/src/shell/finalize_block.rs index db8eed3d1dc..998ad399735 100644 --- a/crates/node/src/shell/finalize_block.rs +++ b/crates/node/src/shell/finalize_block.rs @@ -1303,7 +1303,7 @@ mod test_finalize_block { use namada_sdk::ethereum_events::{EthAddress, Uint as ethUint}; use namada_sdk::events::Event; use namada_sdk::events::extend::Log; - use namada_sdk::gas::VpGasMeter; + use namada_sdk::gas::{GasMeterKind, VpGasMeter}; use namada_sdk::governance::storage::keys::get_proposal_execution_key; use namada_sdk::governance::storage::proposal::ProposalType; use namada_sdk::governance::{ @@ -5717,6 +5717,7 @@ mod test_finalize_block { &keys_changed, &verifiers, shell.vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); assert!( ParametersVp::validate_tx( diff --git a/crates/node/src/shell/init_chain.rs b/crates/node/src/shell/init_chain.rs index e14d0d7ac87..262d66efce1 100644 --- a/crates/node/src/shell/init_chain.rs +++ b/crates/node/src/shell/init_chain.rs @@ -410,9 +410,15 @@ where #[cfg(not(any(test, fuzzing)))] if name.starts_with("tx_") { - self.tx_wasm_cache.pre_compile(&code); + self.tx_wasm_cache.pre_compile( + &code, + namada_sdk::gas::GasMeterKind::MutGlobal, + ); } else if name.starts_with("vp_") { - self.vp_wasm_cache.pre_compile(&code); + self.vp_wasm_cache.pre_compile( + &code, + namada_sdk::gas::GasMeterKind::MutGlobal, + ); } let code_key = Key::wasm_code(&code_hash); diff --git a/crates/shielded_token/src/vp.rs b/crates/shielded_token/src/vp.rs index 1875937c5c6..1c3a7c561fc 100644 --- a/crates/shielded_token/src/vp.rs +++ b/crates/shielded_token/src/vp.rs @@ -957,7 +957,7 @@ mod shielded_token_tests { use namada_core::address::MASP; use namada_core::address::testing::nam; use namada_core::borsh::BorshSerializeExt; - use namada_gas::{TxGasMeter, VpGasMeter}; + use namada_gas::{GasMeterKind, TxGasMeter, VpGasMeter}; use namada_state::testing::{TestState, arb_account_storage_key, arb_key}; use namada_state::{StateRead, TxIndex}; use namada_trans_token::Amount; @@ -1044,6 +1044,7 @@ mod shielded_token_tests { &keys_changed, &verifiers, vp_vp_cache, + GasMeterKind::MutGlobal, ); // We don't care about the specific error so long as it fails @@ -1093,6 +1094,7 @@ mod shielded_token_tests { &keys_changed, &verifiers, vp_vp_cache, + GasMeterKind::MutGlobal, ); assert!(MaspVp::validate_tx( @@ -1142,6 +1144,7 @@ mod shielded_token_tests { &keys_changed, &verifiers, vp_vp_cache, + GasMeterKind::MutGlobal, ); assert!(matches!( diff --git a/crates/tests/src/native_vp/mod.rs b/crates/tests/src/native_vp/mod.rs index 739c6e4458d..e94344c3137 100644 --- a/crates/tests/src/native_vp/mod.rs +++ b/crates/tests/src/native_vp/mod.rs @@ -4,11 +4,11 @@ pub mod pos; use std::cell::RefCell; use std::collections::BTreeSet; -use namada_sdk::address::Address; use namada_sdk::gas::VpGasMeter; use namada_sdk::state::StateRead; use namada_sdk::state::testing::TestState; use namada_sdk::storage; +use namada_sdk::{address::Address, gas::GasMeterKind}; use namada_vm::WasmCacheRwAccess; use namada_vm::wasm::VpCache; use namada_vm::wasm::run::VpEvalWasm; @@ -71,6 +71,7 @@ impl TestNativeVpEnv { &self.keys_changed, &self.verifiers, self.tx_env.vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ); init_native_vp(ctx) } @@ -90,6 +91,7 @@ impl TestNativeVpEnv { &self.keys_changed, &self.verifiers, self.tx_env.vp_wasm_cache.clone(), + GasMeterKind::MutGlobal, ) } diff --git a/crates/tests/src/vm_host_env/ibc.rs b/crates/tests/src/vm_host_env/ibc.rs index a0f539843bb..534c6bdc316 100644 --- a/crates/tests/src/vm_host_env/ibc.rs +++ b/crates/tests/src/vm_host_env/ibc.rs @@ -9,7 +9,7 @@ use ibc_testkit::testapp::ibc::clients::mock::header::MockHeader; use namada_core::chain::testing::get_dummy_header; use namada_core::collections::HashMap; use namada_sdk::address::{self, Address, InternalAddress}; -use namada_sdk::gas::{TxGasMeter, VpGasMeter}; +use namada_sdk::gas::{GasMeterKind, TxGasMeter, VpGasMeter}; use namada_sdk::governance::parameters::GovernanceParameters; use namada_sdk::hash::Hash; use namada_sdk::ibc::apps::transfer::types::error::TokenTransferError; @@ -133,6 +133,7 @@ pub fn validate_ibc_vp_from_tx<'a>( &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); let ibc = IbcVp::new(ctx); @@ -172,6 +173,7 @@ pub fn validate_multitoken_vp_from_tx<'a>( &keys_changed, &verifiers, vp_wasm_cache, + GasMeterKind::MutGlobal, ); MultitokenVp::validate_tx(&ctx, batched_tx, ctx.keys_changed, ctx.verifiers) diff --git a/crates/trans_token/src/vp.rs b/crates/trans_token/src/vp.rs index 9cb85fa6d83..36eb7a0f33c 100644 --- a/crates/trans_token/src/vp.rs +++ b/crates/trans_token/src/vp.rs @@ -354,7 +354,7 @@ mod tests { }; use namada_core::borsh::BorshSerializeExt; use namada_core::key::testing::keypair_1; - use namada_gas::{TxGasMeter, VpGasMeter}; + use namada_gas::{GasMeterKind, TxGasMeter, VpGasMeter}; use namada_ibc::trace::ibc_token; use namada_parameters::storage::get_native_token_transferable_key; use namada_state::testing::TestState; @@ -468,6 +468,7 @@ mod tests { &keys_changed, &verifiers, vp_vp_cache, + GasMeterKind::MutGlobal, ); assert!( @@ -515,6 +516,7 @@ mod tests { &keys_changed, &verifiers, vp_vp_cache, + GasMeterKind::MutGlobal, ); assert!( @@ -587,6 +589,7 @@ mod tests { &keys_changed, &verifiers, vp_vp_cache, + GasMeterKind::MutGlobal, ); assert!( @@ -653,6 +656,7 @@ mod tests { &keys_changed, &verifiers, vp_vp_cache, + GasMeterKind::MutGlobal, ); assert!( @@ -712,6 +716,7 @@ mod tests { &keys_changed, &verifiers, vp_vp_cache, + GasMeterKind::MutGlobal, ); assert!( @@ -780,6 +785,7 @@ mod tests { &keys_changed, &verifiers, vp_vp_cache, + GasMeterKind::MutGlobal, ); assert!( @@ -828,6 +834,7 @@ mod tests { &keys_changed, &verifiers, vp_vp_cache, + GasMeterKind::MutGlobal, ); assert!( @@ -877,6 +884,7 @@ mod tests { &keys_changed, &verifiers, vp_vp_cache, + GasMeterKind::MutGlobal, ); assert!( @@ -921,6 +929,7 @@ mod tests { &keys_changed, &verifiers, vp_vp_cache, + GasMeterKind::MutGlobal, ); assert_matches!( @@ -973,6 +982,7 @@ mod tests { &keys_changed, &verifiers, vp_vp_cache, + GasMeterKind::MutGlobal, ); assert_matches!( @@ -1022,6 +1032,7 @@ mod tests { &keys_changed, &verifiers, vp_vp_cache, + GasMeterKind::MutGlobal, ); assert_matches!( @@ -1072,6 +1083,7 @@ mod tests { &keys_changed, &verifiers, vp_vp_cache, + GasMeterKind::MutGlobal, ); assert_matches!( @@ -1127,6 +1139,7 @@ mod tests { &keys_changed, &verifiers, vp_vp_cache, + GasMeterKind::MutGlobal, ); assert_matches!( @@ -1179,6 +1192,7 @@ mod tests { &keys_changed, &verifiers, vp_vp_cache.clone(), + GasMeterKind::MutGlobal, ); let err_msg = format!( @@ -1210,6 +1224,7 @@ mod tests { &keys_changed, &parties, vp_vp_cache, + GasMeterKind::MutGlobal, ); assert!( diff --git a/crates/vm/src/host_env.rs b/crates/vm/src/host_env.rs index 6a09231527f..f1ffbf2ee72 100644 --- a/crates/vm/src/host_env.rs +++ b/crates/vm/src/host_env.rs @@ -19,8 +19,8 @@ use namada_core::internal::{HostEnvResult, KeyVal}; use namada_core::storage::{Key, TX_INDEX_LENGTH, TxIndex}; use namada_events::{Event, EventTypeBuilder}; use namada_gas::{ - self as gas, Gas, GasMetering, MEMORY_ACCESS_GAS_PER_BYTE, TxGasMeter, - VpGasMeter, + self as gas, Gas, GasMeterKind, GasMetering, MEMORY_ACCESS_GAS_PER_BYTE, + TxGasMeter, VpGasMeter, }; use namada_state::prefix_iter::{PrefixIteratorId, PrefixIterators}; use namada_state::write_log::{self, WriteLog}; @@ -143,6 +143,8 @@ where /// To avoid unused parameter without "wasm-runtime" feature #[cfg(not(feature = "wasm-runtime"))] pub cache_access: std::marker::PhantomData, + /// WASM intructions gas meter kind + pub gas_meter_kind: GasMeterKind, } impl TxVmEnv @@ -176,6 +178,7 @@ where yielded_value: &mut Option>, #[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache, #[cfg(feature = "wasm-runtime")] tx_wasm_cache: &mut TxCache, + gas_meter_kind: GasMeterKind, ) -> Self { let write_log = unsafe { RwHostRef::new(write_log) }; let in_mem = unsafe { RoHostRef::new(in_mem) }; @@ -214,6 +217,7 @@ where tx_wasm_cache, #[cfg(not(feature = "wasm-runtime"))] cache_access: std::marker::PhantomData, + gas_meter_kind, }; Self { memory, ctx } @@ -299,6 +303,7 @@ where tx_wasm_cache: self.tx_wasm_cache, #[cfg(not(feature = "wasm-runtime"))] cache_access: std::marker::PhantomData, + gas_meter_kind: self.gas_meter_kind, } } } @@ -357,6 +362,8 @@ where /// To avoid unused parameter without "wasm-runtime" feature #[cfg(not(feature = "wasm-runtime"))] pub cache_access: std::marker::PhantomData, + /// WASM intructions gas meter kind + pub gas_meter_kind: GasMeterKind, } /// A Validity predicate runner for calls from the [`vp_eval`] function. @@ -415,6 +422,7 @@ where keys_changed: &BTreeSet, eval_runner: &EVAL, #[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache, + gas_meter_kind: GasMeterKind, ) -> Self { let ctx = VpCtx::new( address, @@ -433,6 +441,7 @@ where eval_runner, #[cfg(feature = "wasm-runtime")] vp_wasm_cache, + gas_meter_kind, ); Self { memory, ctx } @@ -491,6 +500,7 @@ where keys_changed: &BTreeSet, eval_runner: &EVAL, #[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache, + gas_meter_kind: GasMeterKind, ) -> Self { let address = unsafe { RoHostRef::new(address) }; let write_log = unsafe { RoHostRef::new(write_log) }; @@ -527,6 +537,7 @@ where vp_wasm_cache, #[cfg(not(feature = "wasm-runtime"))] cache_access: std::marker::PhantomData, + gas_meter_kind, } } @@ -578,6 +589,7 @@ where vp_wasm_cache: self.vp_wasm_cache, #[cfg(not(feature = "wasm-runtime"))] cache_access: std::marker::PhantomData, + gas_meter_kind: self.gas_meter_kind, } } } @@ -2392,6 +2404,7 @@ pub mod testing { vp_wasm_cache, #[cfg(feature = "wasm-runtime")] tx_wasm_cache, + GasMeterKind::MutGlobal, ) } @@ -2442,6 +2455,7 @@ pub mod testing { vp_wasm_cache, #[cfg(feature = "wasm-runtime")] tx_wasm_cache, + GasMeterKind::MutGlobal, ); env.memory.init_from(&wasm_memory); @@ -2488,6 +2502,7 @@ pub mod testing { eval_runner, #[cfg(feature = "wasm-runtime")] vp_wasm_cache, + GasMeterKind::MutGlobal, ) } } diff --git a/crates/vm/src/wasm/compilation_cache/common.rs b/crates/vm/src/wasm/compilation_cache/common.rs index 4b5331a63e6..402c38f7efb 100644 --- a/crates/vm/src/wasm/compilation_cache/common.rs +++ b/crates/vm/src/wasm/compilation_cache/common.rs @@ -16,6 +16,7 @@ use clru::{CLruCache, CLruCacheConfig, WeightScale}; use namada_core::collections::HashMap; use namada_core::control_flow::time::{ExponentialBackoff, SleepStrategy}; use namada_core::hash::Hash; +use namada_gas::GasMeterKind; use wasmer::{Module, Store}; use wasmer_cache::{FileSystemCache, Hash as CacheHash}; @@ -29,7 +30,7 @@ pub struct Cache { /// Cached files directory dir: PathBuf, /// Compilation progress - progress: Arc>>, + progress: Arc>>, /// In-memory LRU cache of compiled modules in_memory: Arc>, /// The cache's name @@ -53,8 +54,15 @@ pub trait CacheName: Clone + std::fmt::Debug { fn name() -> &'static str; } +/// WASM cache key consists of the code hash and gas meter kind. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct CacheKey { + code_hash: Hash, + gas_meter_kind: GasMeterKind, +} + /// In-memory LRU cache of compiled modules -type MemoryCache = CLruCache; +type MemoryCache = CLruCache; /// Compilation progress #[derive(Debug)] @@ -68,8 +76,8 @@ enum Compilation { #[derive(Debug)] struct ModuleCacheScale; -impl WeightScale for ModuleCacheScale { - fn weight(&self, _key: &Hash, _value: &Module) -> usize { +impl WeightScale for ModuleCacheScale { + fn weight(&self, _key: &CacheKey, _value: &Module) -> usize { 1 } } @@ -120,13 +128,14 @@ impl Cache { pub fn fetch( &mut self, code_hash: &Hash, + gas_meter_kind: GasMeterKind, ) -> Result, wasm::run::Error> { if A::is_read_write() { - let module = self.get(code_hash)?; + let module = self.get(code_hash, gas_meter_kind)?; Ok(module.map(|module| (module, store()))) } else { let store = store(); - let module = self.peek(code_hash, &store)?; + let module = self.peek(code_hash, gas_meter_kind, &store)?; Ok(module.map(|module| (module, store))) } } @@ -143,9 +152,17 @@ impl Cache { /// Get a WASM module from LRU cache, from a file or compile it and cache /// it. Updates the position in the LRU cache. - fn get(&mut self, hash: &Hash) -> Result, wasm::run::Error> { + fn get( + &mut self, + hash: &Hash, + gas_meter_kind: GasMeterKind, + ) -> Result, wasm::run::Error> { + let key = CacheKey { + code_hash: *hash, + gas_meter_kind, + }; let mut in_memory = self.in_memory.write().unwrap(); - if let Some(module) = in_memory.get(hash) { + if let Some(module) = in_memory.get(&key) { tracing::trace!( "{} found {} in cache.", N::name(), @@ -164,11 +181,11 @@ impl Cache { }; loop { let progress = self.progress.read().unwrap(); - match progress.get(hash) { + match progress.get(&key) { Some(Compilation::Done) => { drop(progress); let mut in_memory = self.in_memory.write().unwrap(); - if let Some(module) = in_memory.get(hash) { + if let Some(module) = in_memory.get(&key) { tracing::info!( "{} found {} in memory cache.", N::name(), @@ -177,17 +194,19 @@ impl Cache { return Ok(Some(module.clone())); } - if let Ok(module) = - file_load_module(&self.dir, hash, &self.store) - { + if let Ok(module) = file_load_module( + &self.dir, + hash, + gas_meter_kind, + &self.store, + ) { tracing::info!( "{} found {} in file cache.", N::name(), hash.to_string() ); // Put into cache, ignore result if it's full - let _ = - in_memory.put_with_weight(*hash, module.clone()); + let _ = in_memory.put_with_weight(key, module.clone()); return Ok(Some(module)); } else { @@ -211,15 +230,22 @@ impl Cache { } None => { drop(progress); - let module = if module_file_exists(&self.dir, hash) { + let module = if module_file_exists( + &self.dir, + hash, + gas_meter_kind, + ) { tracing::info!( "Trying to load {} {} from file.", N::name(), hash.to_string() ); - if let Ok(module) = - file_load_module(&self.dir, hash, &self.store) - { + if let Ok(module) = file_load_module( + &self.dir, + hash, + gas_meter_kind, + &self.store, + ) { module } else { return Ok(None); @@ -230,12 +256,12 @@ impl Cache { // Update progress let mut progress = self.progress.write().unwrap(); - progress.insert(*hash, Compilation::Done); + progress.insert(key, Compilation::Done); // Put into cache, ignore the result (fails if the module // cannot fit into the cache) let mut in_memory = self.in_memory.write().unwrap(); - let _ = in_memory.put_with_weight(*hash, module.clone()); + let _ = in_memory.put_with_weight(key, module.clone()); return Ok(Some(module)); } @@ -248,10 +274,15 @@ impl Cache { fn peek( &self, hash: &Hash, + gas_meter_kind: GasMeterKind, store: &Store, ) -> Result, wasm::run::Error> { + let key = CacheKey { + code_hash: *hash, + gas_meter_kind, + }; let in_memory = self.in_memory.read().unwrap(); - if let Some(module) = in_memory.peek(hash) { + if let Some(module) = in_memory.peek(&key) { tracing::info!( "{} found {} in cache.", N::name(), @@ -270,11 +301,11 @@ impl Cache { }; loop { let progress = self.progress.read().unwrap(); - match progress.get(hash) { + match progress.get(&key) { Some(Compilation::Done) => { drop(progress); let in_memory = self.in_memory.read().unwrap(); - if let Some(module) = in_memory.peek(hash) { + if let Some(module) = in_memory.peek(&key) { tracing::info!( "{} found {} in memory cache.", N::name(), @@ -283,7 +314,8 @@ impl Cache { return Ok(Some(module.clone())); } - if let Ok(module) = file_load_module(&self.dir, hash, store) + if let Ok(module) = + file_load_module(&self.dir, hash, gas_meter_kind, store) { tracing::info!( "{} found {} in file cache.", @@ -313,15 +345,22 @@ impl Cache { None => { drop(progress); - return if module_file_exists(&self.dir, hash) { + return if module_file_exists( + &self.dir, + hash, + gas_meter_kind, + ) { tracing::info!( "Trying to load {} {} from file.", N::name(), hash.to_string() ); - if let Ok(module) = - file_load_module(&self.dir, hash, store) - { + if let Ok(module) = file_load_module( + &self.dir, + hash, + gas_meter_kind, + store, + ) { return Ok(Some(module)); } else { return Ok(None); @@ -338,20 +377,26 @@ impl Cache { pub fn compile_or_fetch( &mut self, code: impl AsRef<[u8]>, + gas_meter_kind: GasMeterKind, ) -> Result, wasm::run::Error> { let hash = hash_of_code(&code); + let key = CacheKey { + code_hash: hash, + gas_meter_kind, + }; if !A::is_read_write() { // It doesn't update the cache and files let progress = self.progress.read().unwrap(); - match progress.get(&hash) { + match progress.get(&key) { Some(_) => { let store = store(); - let module = self.peek(&hash, &store)?; + let module = self.peek(&hash, gas_meter_kind, &store)?; return Ok(module.map(|module| (module, store))); } None => { - let code = wasm::run::prepare_wasm_code(code)?; + let code = + wasm::run::prepare_wasm_code(code, gas_meter_kind)?; let store = store(); let module = compile(code, &store)?; return Ok(Some((module, store))); @@ -360,28 +405,39 @@ impl Cache { } let mut progress = self.progress.write().unwrap(); - if progress.get(&hash).is_some() { + if progress.get(&key).is_some() { drop(progress); - return self.fetch(&hash); + return self.fetch(&hash, gas_meter_kind); } - progress.insert(hash, Compilation::Compiling); + progress.insert(key, Compilation::Compiling); drop(progress); tracing::info!("Compiling {} {}.", N::name(), hash.to_string()); - match wasm::run::prepare_wasm_code(code) { + match wasm::run::prepare_wasm_code(code, gas_meter_kind) { Ok(code) => match compile(code, &self.store) { Ok(module) => { // Write the file - file_write_module(&self.dir, &module, &hash); + file_write_module( + &self.dir, + &module, + &hash, + gas_meter_kind, + ); // Update progress let mut progress = self.progress.write().unwrap(); - progress.insert(hash, Compilation::Done); + progress.insert(key, Compilation::Done); // Put into cache, ignore result if it's full let mut in_memory = self.in_memory.write().unwrap(); - let _ = in_memory.put_with_weight(hash, module.clone()); + let _ = in_memory.put_with_weight( + CacheKey { + code_hash: hash, + gas_meter_kind, + }, + module.clone(), + ); Ok(Some((module, store()))) } @@ -392,7 +448,7 @@ impl Cache { err ); let mut progress = self.progress.write().unwrap(); - progress.swap_remove(&hash); + progress.swap_remove(&key); Err(err) } }, @@ -403,7 +459,7 @@ impl Cache { err ); let mut progress = self.progress.write().unwrap(); - progress.swap_remove(&hash); + progress.swap_remove(&key); Err(err) } } @@ -411,20 +467,28 @@ impl Cache { /// Pre-compile a WASM module to a file. The compilation runs in a new OS /// thread and the function returns immediately. - pub fn pre_compile(&mut self, code: impl AsRef<[u8]>) { + pub fn pre_compile( + &mut self, + code: impl AsRef<[u8]>, + gas_meter_kind: GasMeterKind, + ) { if A::is_read_write() { let hash = hash_of_code(&code); + let key = CacheKey { + code_hash: hash, + gas_meter_kind, + }; let mut progress = self.progress.write().unwrap(); - match progress.get(&hash) { + match progress.get(&key) { Some(_) => { // Already known, do nothing } None => { - if module_file_exists(&self.dir, &hash) { - progress.insert(hash, Compilation::Done); + if module_file_exists(&self.dir, &hash, gas_meter_kind) { + progress.insert(key, Compilation::Done); return; } - progress.insert(hash, Compilation::Compiling); + progress.insert(key, Compilation::Compiling); drop(progress); let progress = self.progress.clone(); let code = code.as_ref().to_vec(); @@ -433,18 +497,25 @@ impl Cache { std::thread::spawn(move || { tracing::info!("Compiling WASM {}.", hash.to_string()); - let _module = match wasm::run::prepare_wasm_code(code) { + let _module = match wasm::run::prepare_wasm_code( + code, + gas_meter_kind, + ) { Ok(code) => { match compile(code, &store) { Ok(module) => { // Write the file - file_write_module(&dir, &module, &hash); + file_write_module( + &dir, + &module, + &hash, + gas_meter_kind, + ); // Update progress let mut progress = progress.write().unwrap(); - progress - .insert(hash, Compilation::Done); + progress.insert(key, Compilation::Done); tracing::info!( "Finished compiling WASM {hash}." ); @@ -471,7 +542,7 @@ impl Cache { hash.to_string(), err ); - progress.swap_remove(&hash); + progress.swap_remove(&key); return Err(err); } } @@ -483,7 +554,7 @@ impl Cache { hash.to_string(), err ); - progress.swap_remove(&hash); + progress.swap_remove(&key); return Err(err); } }; @@ -532,19 +603,25 @@ pub(crate) fn store() -> Store { universal::store() } -fn file_write_module(dir: impl AsRef, module: &Module, hash: &Hash) { +fn file_write_module( + dir: impl AsRef, + module: &Module, + hash: &Hash, + gas_meter_kind: GasMeterKind, +) { use wasmer_cache::Cache; - let mut fs_cache = fs_cache(dir, hash); + let mut fs_cache = fs_cache(dir, hash, gas_meter_kind); fs_cache.store(CacheHash::new(hash.0), module).unwrap(); } fn file_load_module( dir: impl AsRef, hash: &Hash, + gas_meter_kind: GasMeterKind, store: &Store, ) -> Result { use wasmer_cache::Cache; - let fs_cache = fs_cache(dir, hash); + let fs_cache = fs_cache(dir, hash, gas_meter_kind); let hash = CacheHash::new(hash.0); let module = unsafe { fs_cache.load(store, hash) }; if let Err(err) = module.as_ref() { @@ -556,22 +633,42 @@ fn file_load_module( module } -fn fs_cache(dir: impl AsRef, hash: &Hash) -> FileSystemCache { - let path = dir.as_ref().join(hash.to_string().to_lowercase()); +fn fs_cache( + dir: impl AsRef, + hash: &Hash, + gas_meter_kind: GasMeterKind, +) -> FileSystemCache { + let kind = gas_meter_kind_dir(gas_meter_kind); + let path = dir + .as_ref() + .join(kind) + .join(hash.to_string().to_lowercase()); let mut fs_cache = FileSystemCache::new(path).unwrap(); fs_cache.set_cache_extension(Some(file_ext())); fs_cache } -fn module_file_exists(dir: impl AsRef, hash: &Hash) -> bool { - let file = - dir.as_ref() - .join(hash.to_string().to_lowercase()) - .join(format!( - "{}.{}", - hash.to_string().to_lowercase(), - file_ext() - )); +fn gas_meter_kind_dir(gas_meter_kind: GasMeterKind) -> &'static str { + match gas_meter_kind { + GasMeterKind::HostFn => "host_fn", + GasMeterKind::MutGlobal => "mut_global", + } +} + +fn module_file_exists( + dir: impl AsRef, + hash: &Hash, + gas_meter_kind: GasMeterKind, +) -> bool { + let file = dir + .as_ref() + .join(gas_meter_kind_dir(gas_meter_kind)) + .join(hash.to_string().to_lowercase()) + .join(format!( + "{}.{}", + hash.to_string().to_lowercase(), + file_ext() + )); file.exists() } @@ -652,7 +749,7 @@ mod test { // Load some WASMs and find their hashes and in-memory size let tx_read_storage_key = load_wasm(TestWasms::TxReadStorageKey.path()); let tx_no_op = load_wasm(TestWasms::TxNoOp.path()); - + let gas_meter_kind = GasMeterKind::MutGlobal; // Create a new cache with the limit set to // `max(tx_read_storage_key.size, tx_no_op.size) + 1` { @@ -668,15 +765,21 @@ mod test { // Fetch `tx_read_storage_key` { - let fetched = cache.fetch(&tx_read_storage_key.hash).unwrap(); + let fetched = cache + .fetch(&tx_read_storage_key.hash, gas_meter_kind) + .unwrap(); assert_matches!( fetched, None, "The module should not be in cache" ); - let fetched = - cache.compile_or_fetch(&tx_read_storage_key.code).unwrap(); + let fetched = cache + .compile_or_fetch( + &tx_read_storage_key.code, + GasMeterKind::MutGlobal, + ) + .unwrap(); assert_matches!( fetched, Some(_), @@ -685,20 +788,30 @@ mod test { let in_memory = cache.in_memory.read().unwrap(); assert_matches!( - in_memory.peek(&tx_read_storage_key.hash), + in_memory.peek(&CacheKey { + code_hash: tx_read_storage_key.hash, + gas_meter_kind + }), Some(_), "The module must be in memory" ); let progress = cache.progress.read().unwrap(); assert_matches!( - progress.get(&tx_read_storage_key.hash), + progress.get(&CacheKey { + code_hash: tx_read_storage_key.hash, + gas_meter_kind + }), Some(Compilation::Done), "The progress must be updated" ); assert!( - module_file_exists(&cache.dir, &tx_read_storage_key.hash), + module_file_exists( + &cache.dir, + &tx_read_storage_key.hash, + gas_meter_kind + ), "The file must be written" ); } @@ -706,14 +819,17 @@ mod test { // Fetch `tx_no_op`. Fetching another module should get us over the // limit, so the previous one should be popped from the cache { - let fetched = cache.fetch(&tx_no_op.hash).unwrap(); + let fetched = + cache.fetch(&tx_no_op.hash, gas_meter_kind).unwrap(); assert_matches!( fetched, None, "The module must not be in cache" ); - let fetched = cache.compile_or_fetch(&tx_no_op.code).unwrap(); + let fetched = cache + .compile_or_fetch(&tx_no_op.code, GasMeterKind::MutGlobal) + .unwrap(); assert_matches!( fetched, Some(_), @@ -722,31 +838,48 @@ mod test { let in_memory = cache.in_memory.read().unwrap(); assert_matches!( - in_memory.peek(&tx_no_op.hash), + in_memory.peek(&CacheKey { + code_hash: tx_no_op.hash, + gas_meter_kind + }), Some(_), "The module must be in memory" ); let progress = cache.progress.read().unwrap(); assert_matches!( - progress.get(&tx_no_op.hash), + progress.get(&CacheKey { + code_hash: tx_no_op.hash, + gas_meter_kind + }), Some(Compilation::Done), "The progress must be updated" ); assert!( - module_file_exists(&cache.dir, &tx_no_op.hash), + module_file_exists( + &cache.dir, + &tx_no_op.hash, + gas_meter_kind + ), "The file must be written" ); // The previous module's file should still exist assert!( - module_file_exists(&cache.dir, &tx_read_storage_key.hash), + module_file_exists( + &cache.dir, + &tx_read_storage_key.hash, + gas_meter_kind + ), "The file must be written" ); // But it should not be in-memory assert_matches!( - in_memory.peek(&tx_read_storage_key.hash), + in_memory.peek(&CacheKey { + code_hash: tx_read_storage_key.hash, + gas_meter_kind + }), None, "The module should have been popped from memory" ); @@ -763,7 +896,9 @@ mod test { cache.in_memory = in_memory; cache.progress = Default::default(); { - let fetched = cache.fetch(&tx_read_storage_key.hash).unwrap(); + let fetched = cache + .fetch(&tx_read_storage_key.hash, gas_meter_kind) + .unwrap(); assert_matches!( fetched, Some(_), @@ -772,31 +907,48 @@ mod test { let in_memory = cache.in_memory.read().unwrap(); assert_matches!( - in_memory.peek(&tx_read_storage_key.hash), + in_memory.peek(&CacheKey { + code_hash: tx_read_storage_key.hash, + gas_meter_kind + }), Some(_), "The module must be in memory" ); let progress = cache.progress.read().unwrap(); assert_matches!( - progress.get(&tx_read_storage_key.hash), + progress.get(&CacheKey { + code_hash: tx_read_storage_key.hash, + gas_meter_kind + }), Some(Compilation::Done), "The progress must be updated" ); assert!( - module_file_exists(&cache.dir, &tx_read_storage_key.hash), + module_file_exists( + &cache.dir, + &tx_read_storage_key.hash, + gas_meter_kind + ), "The file must be written" ); // The previous module's file should still exist assert!( - module_file_exists(&cache.dir, &tx_no_op.hash), + module_file_exists( + &cache.dir, + &tx_no_op.hash, + gas_meter_kind + ), "The file must be written" ); // But it should not be in-memory assert_matches!( - in_memory.peek(&tx_no_op.hash), + in_memory.peek(&CacheKey { + code_hash: tx_no_op.hash, + gas_meter_kind + }), None, "The module should have been popped from memory" ); @@ -804,7 +956,9 @@ mod test { // Fetch `tx_read_storage_key` again, now it should be in-memory { - let fetched = cache.fetch(&tx_read_storage_key.hash).unwrap(); + let fetched = cache + .fetch(&tx_read_storage_key.hash, gas_meter_kind) + .unwrap(); assert_matches!( fetched, Some(_), @@ -813,31 +967,48 @@ mod test { let in_memory = cache.in_memory.read().unwrap(); assert_matches!( - in_memory.peek(&tx_read_storage_key.hash), + in_memory.peek(&CacheKey { + code_hash: tx_read_storage_key.hash, + gas_meter_kind + }), Some(_), "The module must be in memory" ); let progress = cache.progress.read().unwrap(); assert_matches!( - progress.get(&tx_read_storage_key.hash), + progress.get(&CacheKey { + code_hash: tx_read_storage_key.hash, + gas_meter_kind + }), Some(Compilation::Done), "The progress must be updated" ); assert!( - module_file_exists(&cache.dir, &tx_read_storage_key.hash), + module_file_exists( + &cache.dir, + &tx_read_storage_key.hash, + gas_meter_kind + ), "The file must be written" ); // The previous module's file should still exist assert!( - module_file_exists(&cache.dir, &tx_no_op.hash), + module_file_exists( + &cache.dir, + &tx_no_op.hash, + gas_meter_kind + ), "The file must be written" ); // But it should not be in-memory assert_matches!( - in_memory.peek(&tx_no_op.hash), + in_memory.peek(&CacheKey { + code_hash: tx_no_op.hash, + gas_meter_kind + }), None, "The module should have been popped from memory" ); @@ -847,7 +1018,8 @@ mod test { { let mut cache = cache.read_only(); - let fetched = cache.fetch(&tx_no_op.hash).unwrap(); + let fetched = + cache.fetch(&tx_no_op.hash, gas_meter_kind).unwrap(); assert_matches!( fetched, Some(_), @@ -855,7 +1027,9 @@ mod test { ); // Fetching with read-only should not modify the in-memory cache - let fetched = cache.compile_or_fetch(&tx_no_op.code).unwrap(); + let fetched = cache + .compile_or_fetch(&tx_no_op.code, gas_meter_kind) + .unwrap(); assert_matches!( fetched, Some(_), @@ -864,14 +1038,20 @@ mod test { let in_memory = cache.in_memory.read().unwrap(); assert_matches!( - in_memory.peek(&tx_no_op.hash), + in_memory.peek(&CacheKey { + code_hash: tx_no_op.hash, + gas_meter_kind + }), None, "The module should not be added back to in-memory cache" ); let in_memory = cache.in_memory.read().unwrap(); assert_matches!( - in_memory.peek(&tx_read_storage_key.hash), + in_memory.peek(&CacheKey { + code_hash: tx_read_storage_key.hash, + gas_meter_kind + }), Some(_), "The previous module must still be in memory" ); @@ -884,26 +1064,37 @@ mod test { // Some random bytes let invalid_wasm = vec![1_u8, 0, 8, 10, 6, 1]; let hash = hash_of_code(&invalid_wasm); + let gas_meter_kind = GasMeterKind::HostFn; let (mut cache, _) = testing::cache::(); // Try to compile it let error = cache - .compile_or_fetch(&invalid_wasm) + .compile_or_fetch(&invalid_wasm, GasMeterKind::MutGlobal) .expect_err("Compilation should fail"); println!("Error: {}", error); let in_memory = cache.in_memory.read().unwrap(); assert_matches!( - in_memory.peek(&hash), + in_memory.peek(&CacheKey { + code_hash: hash, + gas_meter_kind + }), None, "There should be no entry for this hash in memory" ); let progress = cache.progress.read().unwrap(); - assert_matches!(progress.get(&hash), None, "Any progress is removed"); + assert_matches!( + progress.get(&CacheKey { + code_hash: hash, + gas_meter_kind + }), + None, + "Any progress is removed" + ); assert!( - !module_file_exists(&cache.dir, &hash), + !module_file_exists(&cache.dir, &hash, gas_meter_kind), "The file must not be written" ); } @@ -926,14 +1117,19 @@ mod test { max_bytes ); let (mut cache, _tmp_dir) = cache(max_bytes); + let gas_meter_kind = GasMeterKind::MutGlobal; // Pre-compile `vp_always_true` { - cache.pre_compile(&vp_always_true.code); + cache + .pre_compile(&vp_always_true.code, GasMeterKind::MutGlobal); let progress = cache.progress.read().unwrap(); assert_matches!( - progress.get(&vp_always_true.hash), + progress.get(&CacheKey { + code_hash: vp_always_true.hash, + gas_meter_kind + }), Some(Compilation::Done | Compilation::Compiling), "The progress must be updated" ); @@ -941,7 +1137,8 @@ mod test { // Now fetch it to wait for it finish compilation { - let fetched = cache.fetch(&vp_always_true.hash).unwrap(); + let fetched = + cache.fetch(&vp_always_true.hash, gas_meter_kind).unwrap(); assert_matches!( fetched, Some(_), @@ -950,20 +1147,30 @@ mod test { let in_memory = cache.in_memory.read().unwrap(); assert_matches!( - in_memory.peek(&vp_always_true.hash), + in_memory.peek(&CacheKey { + code_hash: vp_always_true.hash, + gas_meter_kind + }), Some(_), "The module must be in memory" ); let progress = cache.progress.read().unwrap(); assert_matches!( - progress.get(&vp_always_true.hash), + progress.get(&CacheKey { + code_hash: vp_always_true.hash, + gas_meter_kind + }), Some(Compilation::Done), "The progress must be updated" ); assert!( - module_file_exists(&cache.dir, &vp_always_true.hash), + module_file_exists( + &cache.dir, + &vp_always_true.hash, + gas_meter_kind + ), "The file must be written" ); } @@ -972,11 +1179,14 @@ mod test { // over the limit, so the previous one should be popped // from the cache { - cache.pre_compile(&vp_eval.code); + cache.pre_compile(&vp_eval.code, GasMeterKind::MutGlobal); let progress = cache.progress.read().unwrap(); assert_matches!( - progress.get(&vp_eval.hash), + progress.get(&CacheKey { + code_hash: vp_eval.hash, + gas_meter_kind + }), Some(Compilation::Done | Compilation::Compiling), "The progress must be updated" ); @@ -984,7 +1194,8 @@ mod test { // Now fetch it to wait for it finish compilation { - let fetched = cache.fetch(&vp_eval.hash).unwrap(); + let fetched = + cache.fetch(&vp_eval.hash, gas_meter_kind).unwrap(); assert_matches!( fetched, Some(_), @@ -993,24 +1204,38 @@ mod test { let in_memory = cache.in_memory.read().unwrap(); assert_matches!( - in_memory.peek(&vp_eval.hash), + in_memory.peek(&CacheKey { + code_hash: vp_eval.hash, + gas_meter_kind + }), Some(_), "The module must be in memory" ); assert!( - module_file_exists(&cache.dir, &vp_eval.hash), + module_file_exists( + &cache.dir, + &vp_eval.hash, + gas_meter_kind + ), "The file must be written" ); // The previous module's file should still exist assert!( - module_file_exists(&cache.dir, &vp_always_true.hash), + module_file_exists( + &cache.dir, + &vp_always_true.hash, + gas_meter_kind + ), "The file must be written" ); // But it should not be in-memory assert_matches!( - in_memory.peek(&vp_always_true.hash), + in_memory.peek(&CacheKey { + code_hash: vp_always_true.hash, + gas_meter_kind + }), None, "The module should have been popped from memory" ); @@ -1024,13 +1249,17 @@ mod test { let invalid_wasm = vec![1_u8]; let hash = hash_of_code(&invalid_wasm); let (mut cache, _) = testing::cache::(); + let gas_meter_kind = GasMeterKind::HostFn; // Try to pre-compile it { - cache.pre_compile(&invalid_wasm); + cache.pre_compile(&invalid_wasm, GasMeterKind::MutGlobal); let progress = cache.progress.read().unwrap(); assert_matches!( - progress.get(&hash), + progress.get(&CacheKey { + code_hash: hash, + gas_meter_kind + }), Some(Compilation::Done | Compilation::Compiling) | None, "The progress must be updated" ); @@ -1038,7 +1267,7 @@ mod test { // Now fetch it to wait for it finish compilation { - let fetched = cache.fetch(&hash).unwrap(); + let fetched = cache.fetch(&hash, gas_meter_kind).unwrap(); assert_matches!( fetched, None, @@ -1047,20 +1276,26 @@ mod test { let in_memory = cache.in_memory.read().unwrap(); assert_matches!( - in_memory.peek(&hash), + in_memory.peek(&CacheKey { + code_hash: hash, + gas_meter_kind + }), None, "There should be no entry for this hash in memory" ); let progress = cache.progress.read().unwrap(); assert_matches!( - progress.get(&hash), + progress.get(&CacheKey { + code_hash: hash, + gas_meter_kind + }), None, "Any progress is removed" ); assert!( - !module_file_exists(&cache.dir, &hash), + !module_file_exists(&cache.dir, &hash, gas_meter_kind), "The file must not be written" ); } @@ -1077,8 +1312,10 @@ mod test { // No in-memory cache needed, but must be non-zero 1, ); - let (_module, _store) = - cache.compile_or_fetch(&code).unwrap().unwrap(); + let (_module, _store) = cache + .compile_or_fetch(&code, GasMeterKind::MutGlobal) + .unwrap() + .unwrap(); 1 }; println!( diff --git a/crates/vm/src/wasm/run.rs b/crates/vm/src/wasm/run.rs index 1c24fc9d874..a2ae2b7bb65 100644 --- a/crates/vm/src/wasm/run.rs +++ b/crates/vm/src/wasm/run.rs @@ -1,5 +1,7 @@ //! Wasm runners +pub use namada_gas::GasMeterKind; + use std::cell::RefCell; use std::collections::BTreeSet; use std::error::Error as _; @@ -37,18 +39,9 @@ use crate::{ validate_untrusted_wasm, }; -/// Choose the gas mmeter used for WASM instructions -#[derive(Debug, Clone, Copy)] -pub enum GasMeterKind { - /// Gas accounting using a host env function. Suitable for unstructed code. - HostFn, - /// Global mutable variable accounted inside WASM. This should only be used - /// for trusted WASM code as a malicious code might modify the gas meter - MutGlobal, -} - const TX_ENTRYPOINT: &str = "_apply_tx"; const VP_ENTRYPOINT: &str = "_validate_tx"; +const MUT_GLOBAL_GAS_NAME: &str = "_namada_gas"; const WASM_STACK_LIMIT: u32 = u16::MAX as u32; /// The error type returned by transactions. @@ -155,7 +148,7 @@ pub fn tx( cmt: &TxCommitments, vp_wasm_cache: &mut VpCache, tx_wasm_cache: &mut TxCache, - _gas_meter_kind: GasMeterKind, + gas_meter_kind: GasMeterKind, ) -> Result> where S: StateRead + State + StorageRead, @@ -202,8 +195,13 @@ where } } - let (module, store) = - fetch_or_compile(tx_wasm_cache, &tx_code.code, state, gas_meter)?; + let (module, store) = fetch_or_compile( + tx_wasm_cache, + &tx_code.code, + state, + gas_meter, + gas_meter_kind, + )?; let store = Rc::new(RefCell::new(store)); let mut iterators: PrefixIterators<'_, ::D> = @@ -233,6 +231,7 @@ where &mut yielded_value, vp_wasm_cache, tx_wasm_cache, + gas_meter_kind, ); // Instantiate the wasm module @@ -243,6 +242,16 @@ where .map_err(|e| Error::InstantiationError(Box::new(e)))? }; + if let GasMeterKind::MutGlobal = gas_meter_kind { + let mut store = store.borrow_mut(); + instance + .exports + .get_global(MUT_GLOBAL_GAS_NAME) + .unwrap() + .set(&mut *store, wasmer::Value::I64(-1i64)) + .unwrap(); + } + // Fetch guest's main memory let guest_memory = instance .exports @@ -332,7 +341,7 @@ pub fn vp( keys_changed: &BTreeSet, verifiers: &BTreeSet
, mut vp_wasm_cache: VpCache, - _gas_meter_kind: GasMeterKind, + gas_meter_kind: GasMeterKind, ) -> Result<()> where S: StateRead, @@ -344,6 +353,7 @@ where &Commitment::Hash(vp_code_hash), state, gas_meter, + gas_meter_kind, )?; let store = Rc::new(RefCell::new(store)); @@ -375,6 +385,7 @@ where keys_changed, &eval_runner, &mut vp_wasm_cache, + gas_meter_kind, ); let yielded_value_borrow = env.ctx.yielded_value; @@ -395,6 +406,7 @@ where verifiers, yielded_value_borrow, |guest_memory| env.memory.init_from(guest_memory), + gas_meter_kind, ) } @@ -410,6 +422,7 @@ fn run_vp( verifiers: &BTreeSet
, yielded_value: HostRef>>, mut init_memory_callback: F, + gas_meter_kind: GasMeterKind, ) -> Result<()> where F: FnMut(&wasmer::Memory), @@ -428,6 +441,16 @@ where .map_err(|e| Error::InstantiationError(Box::new(e)))? }; + if let GasMeterKind::MutGlobal = gas_meter_kind { + let mut store = store.borrow_mut(); + instance + .exports + .get_global(MUT_GLOBAL_GAS_NAME) + .unwrap() + .set(&mut *store, wasmer::Value::I64(-1i64)) + .unwrap(); + } + // Fetch guest's main memory let guest_memory = instance .exports @@ -584,6 +607,7 @@ where ctx.keys_changed, &eval_runner, &mut vp_wasm_cache, + ctx.gas_meter_kind, ); eval_runner .eval_native_result(ctx, vp_code_hash, input_data) @@ -640,13 +664,14 @@ where let verifiers = unsafe { ctx.verifiers.get() }; let vp_wasm_cache = unsafe { ctx.vp_wasm_cache.get_mut() }; let gas_meter = unsafe { ctx.gas_meter.get() }; - + let gas_meter_kind = ctx.gas_meter_kind; // Compile the wasm module let (module, store) = fetch_or_compile( vp_wasm_cache, &Commitment::Hash(vp_code_hash), &ctx.state(), gas_meter, + gas_meter_kind, )?; let store = Rc::new(RefCell::new(store)); @@ -671,6 +696,7 @@ where verifiers, yielded_value_borrow, |guest_memory| env.memory.init_from(guest_memory), + gas_meter_kind, ) } } @@ -692,17 +718,30 @@ pub fn untrusted_wasm_store(limit: Limit) -> wasmer::Store { } /// Inject gas counter and stack-height limiter into the given wasm code -pub fn prepare_wasm_code>(code: T) -> Result> { +pub fn prepare_wasm_code>( + code: T, + gas_meter_kind: GasMeterKind, +) -> Result> { let module: elements::Module = elements::deserialize_buffer(code.as_ref()) .map_err(Error::DeserializationError)?; - let module = wasm_instrument::gas_metering::inject( - module, - wasm_instrument::gas_metering::host_function::Injector::new( - "env", "gas", - ), - &GasRules, - ) - .map_err(|_original_module| Error::GasMeterInjection)?; + let module = match gas_meter_kind { + GasMeterKind::HostFn => wasm_instrument::gas_metering::inject( + module, + wasm_instrument::gas_metering::host_function::Injector::new( + "env", "gas", + ), + &GasRules, + ) + .map_err(|_original_module| Error::GasMeterInjection)?, + GasMeterKind::MutGlobal => wasm_instrument::gas_metering::inject( + module, + wasm_instrument::gas_metering::mutable_global::Injector::new( + MUT_GLOBAL_GAS_NAME, + ), + &GasRules, + ) + .map_err(|_original_module| Error::GasMeterInjection)?, + }; let module = wasm_instrument::inject_stack_limiter(module, WASM_STACK_LIMIT) .map_err(|_original_module| Error::StackLimiterInjection)?; @@ -716,6 +755,7 @@ fn fetch_or_compile( code_or_hash: &Commitment, state: &S, gas_meter: &RefCell, + gas_meter_kind: GasMeterKind, ) -> Result<(Module, Store)> where S: StateRead, @@ -750,7 +790,9 @@ where .add_compiling_gas(tx_len) .map_err(|e| Error::GasError(e.to_string()))?; - let (module, store) = match wasm_cache.fetch(code_hash)? { + let (module, store) = match wasm_cache + .fetch(code_hash, gas_meter_kind)? + { Some((module, store)) => (module, store), None => { let key = Key::wasm_code(code_hash); @@ -767,7 +809,7 @@ where )) })?; - match wasm_cache.compile_or_fetch(code)? { + match wasm_cache.compile_or_fetch(code, gas_meter_kind)? { Some((module, store)) => (module, store), None => return Err(Error::NoCompiledWasmCode), } @@ -791,7 +833,7 @@ where .borrow_mut() .add_compiling_gas(tx_len) .map_err(|e| Error::GasError(e.to_string()))?; - match wasm_cache.compile_or_fetch(code)? { + match wasm_cache.compile_or_fetch(code, gas_meter_kind)? { Some((module, store)) => Ok((module, store)), None => Err(Error::NoCompiledWasmCode), } diff --git a/crates/vp/src/native_vp.rs b/crates/vp/src/native_vp.rs index 5812e6c1ab1..7bf42209101 100644 --- a/crates/vp/src/native_vp.rs +++ b/crates/vp/src/native_vp.rs @@ -10,7 +10,7 @@ use std::marker::PhantomData; use namada_core::borsh; use namada_core::borsh::BorshDeserialize; use namada_core::chain::{ChainId, Epochs}; -use namada_gas::{Gas, GasMetering, VpGasMeter}; +use namada_gas::{Gas, GasMeterKind, GasMetering, VpGasMeter}; use namada_state::{ConversionState, ReadConversionState}; use namada_tx::{BatchedTxRef, Tx, TxCommitments}; @@ -68,6 +68,8 @@ where pub vp_wasm_cache: CA, /// VP evaluator type pub eval: PhantomData, + /// WASM instructions gas meter kind + pub gas_meter_kind: GasMeterKind, } /// A Validity predicate runner for calls from the host env `vp_eval` function. @@ -127,6 +129,7 @@ where keys_changed: &'a BTreeSet, verifiers: &'a BTreeSet
, vp_wasm_cache: CA, + gas_meter_kind: GasMeterKind, ) -> Self { Self { address, @@ -140,6 +143,7 @@ where verifiers, vp_wasm_cache, eval: PhantomData, + gas_meter_kind, } } From 9b52155b4e5f12539fdea35a9c65c2604d3507e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 23 May 2025 14:50:51 +0200 Subject: [PATCH 03/10] vm: initialize wasm gas meter with available gas --- crates/gas/src/lib.rs | 9 +++++++++ crates/vm/src/wasm/run.rs | 18 ++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/crates/gas/src/lib.rs b/crates/gas/src/lib.rs index 56aa6953ff2..a392f6f689a 100644 --- a/crates/gas/src/lib.rs +++ b/crates/gas/src/lib.rs @@ -582,6 +582,15 @@ impl VpGasMeter { pub fn get_vp_consumed_gas(&self) -> Gas { self.current_gas.clone() } + + /// Get the amount of gas still available to the VP + pub fn get_available_gas(&self) -> Gas { + self.tx_gas_limit + .checked_sub(self.initial_gas.clone()) + .unwrap_or_default() + .checked_sub(self.current_gas.clone()) + .unwrap_or_default() + } } #[cfg(test)] diff --git a/crates/vm/src/wasm/run.rs b/crates/vm/src/wasm/run.rs index a2ae2b7bb65..61afeb264a1 100644 --- a/crates/vm/src/wasm/run.rs +++ b/crates/vm/src/wasm/run.rs @@ -243,12 +243,16 @@ where }; if let GasMeterKind::MutGlobal = gas_meter_kind { + #[allow(clippy::cast_possible_wrap)] + // intentional wrap around the value + let available_gas = + u64::from(gas_meter.borrow().get_available_gas()) as i64; let mut store = store.borrow_mut(); instance .exports .get_global(MUT_GLOBAL_GAS_NAME) .unwrap() - .set(&mut *store, wasmer::Value::I64(-1i64)) + .set(&mut *store, wasmer::Value::I64(available_gas)) .unwrap(); } @@ -395,6 +399,9 @@ where vp_imports(&mut *store, env.clone()) }; + #[allow(clippy::cast_possible_wrap)] // intentional wrap around the value + let available_gas = + u64::from(gas_meter.borrow().get_available_gas()) as i64; run_vp( store, module, @@ -406,6 +413,7 @@ where verifiers, yielded_value_borrow, |guest_memory| env.memory.init_from(guest_memory), + available_gas, gas_meter_kind, ) } @@ -422,6 +430,7 @@ fn run_vp( verifiers: &BTreeSet
, yielded_value: HostRef>>, mut init_memory_callback: F, + available_gas: i64, gas_meter_kind: GasMeterKind, ) -> Result<()> where @@ -447,7 +456,7 @@ where .exports .get_global(MUT_GLOBAL_GAS_NAME) .unwrap() - .set(&mut *store, wasmer::Value::I64(-1i64)) + .set(&mut *store, wasmer::Value::I64(available_gas)) .unwrap(); } @@ -685,6 +694,10 @@ where vp_imports(&mut *store, env.clone()) }; + #[allow(clippy::cast_possible_wrap)] + // intentional wrap around the value + let available_gas = + u64::from(gas_meter.borrow().get_available_gas()) as i64; run_vp( store, module, @@ -696,6 +709,7 @@ where verifiers, yielded_value_borrow, |guest_memory| env.memory.init_from(guest_memory), + available_gas, gas_meter_kind, ) } From a7c2658d7d155f2277f5fee7a375fb29c544c335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 23 May 2025 17:37:11 +0200 Subject: [PATCH 04/10] vm/tx: sync gas between wasm and host --- crates/gas/src/lib.rs | 20 +++ crates/tests/src/native_vp/mod.rs | 4 +- crates/tests/src/vm_host_env/tx.rs | 20 ++- crates/vm/src/host_env.rs | 204 ++++++++++++++++++++++++----- crates/vm/src/memory.rs | 9 ++ crates/vm/src/wasm/memory.rs | 4 + crates/vm/src/wasm/run.rs | 26 ++-- 7 files changed, 241 insertions(+), 46 deletions(-) diff --git a/crates/gas/src/lib.rs b/crates/gas/src/lib.rs index a392f6f689a..2c227d834c1 100644 --- a/crates/gas/src/lib.rs +++ b/crates/gas/src/lib.rs @@ -517,6 +517,26 @@ impl TxGasMeter { .checked_sub(self.transaction_gas.clone()) .unwrap_or_default() } + + /// Set the amount of gas still available to the transaction. + /// + /// WARNING: Utmost care must be taken when using this function! This must + /// never increase the amount available. + pub fn set_available_gas(&mut self, gas: impl Into) { + let gas: Gas = gas.into(); + let used_gas = self + .tx_gas_limit + .checked_sub(gas) + .unwrap_or_else(|| self.tx_gas_limit.clone()); + debug_assert!( + used_gas > self.transaction_gas, + "Used gas must not decrease from {:?}, but trying to set it to \ + {:?}", + self.transaction_gas, + used_gas + ); + self.transaction_gas = used_gas; + } } impl GasMetering for VpGasMeter { diff --git a/crates/tests/src/native_vp/mod.rs b/crates/tests/src/native_vp/mod.rs index e94344c3137..3fc7f93f863 100644 --- a/crates/tests/src/native_vp/mod.rs +++ b/crates/tests/src/native_vp/mod.rs @@ -4,11 +4,11 @@ pub mod pos; use std::cell::RefCell; use std::collections::BTreeSet; -use namada_sdk::gas::VpGasMeter; +use namada_sdk::address::Address; +use namada_sdk::gas::{GasMeterKind, VpGasMeter}; use namada_sdk::state::StateRead; use namada_sdk::state::testing::TestState; use namada_sdk::storage; -use namada_sdk::{address::Address, gas::GasMeterKind}; use namada_vm::WasmCacheRwAccess; use namada_vm::wasm::VpCache; use namada_vm::wasm::run::VpEvalWasm; diff --git a/crates/tests/src/vm_host_env/tx.rs b/crates/tests/src/vm_host_env/tx.rs index 8b26c14d9c2..c7ea99b0a04 100644 --- a/crates/tests/src/vm_host_env/tx.rs +++ b/crates/tests/src/vm_host_env/tx.rs @@ -63,6 +63,7 @@ pub struct TestTxEnv { pub tx_cache_dir: TempDir, pub batched_tx: BatchedTx, pub wasmer_store: Rc>, + pub gas_global: Option, } impl Default for TestTxEnv { fn default() -> Self { @@ -76,9 +77,13 @@ impl Default for TestTxEnv { tx.push_default_inner_tx(); let batched_tx = tx.batch_first_tx(); - let wasmer_store = Rc::new(RefCell::new( - wasm::compilation_cache::common::testing::store(), - )); + let (wasmer_store, gas_global) = { + let mut store = wasm::compilation_cache::common::testing::store(); + let global = + wasmer::Global::new_mut(&mut store, wasmer::Value::I64(0)); + + (Rc::new(RefCell::new(store)), Some(global)) + }; Self { state, @@ -95,6 +100,7 @@ impl Default for TestTxEnv { tx_cache_dir, batched_tx, wasmer_store, + gas_global, } } } @@ -357,6 +363,7 @@ mod native_tx_host_env { tx_cache_dir: _, batched_tx, wasmer_store: _, + gas_global, }: &mut TestTxEnv| { let mut tx_env = namada_vm::host_env::testing::tx_env( @@ -372,6 +379,7 @@ mod native_tx_host_env { yielded_value, vp_wasm_cache, tx_wasm_cache, + gas_global, ); // Call the `host_env` function and unwrap any @@ -402,6 +410,7 @@ mod native_tx_host_env { tx_cache_dir: _, batched_tx, wasmer_store: _, + gas_global, }: &mut TestTxEnv| { let mut tx_env = namada_vm::host_env::testing::tx_env( @@ -417,6 +426,7 @@ mod native_tx_host_env { yielded_value, vp_wasm_cache, tx_wasm_cache, + gas_global, ); // Call the `host_env` function and unwrap any @@ -447,6 +457,7 @@ mod native_tx_host_env { tx_cache_dir: _, batched_tx, wasmer_store: _, + gas_global, }: &mut TestTxEnv| { let mut tx_env = namada_vm::host_env::testing::tx_env( @@ -462,6 +473,7 @@ mod native_tx_host_env { yielded_value, vp_wasm_cache, tx_wasm_cache, + gas_global, ); // Call the `host_env` function @@ -767,6 +779,7 @@ mod tests { tx_cache_dir: _, batched_tx, wasmer_store, + gas_global, } = test_env; let mut tx_env = host_env::testing::tx_env_with_wasm_memory( @@ -783,6 +796,7 @@ mod tests { wasmer_store.clone(), vp_wasm_cache, tx_wasm_cache, + gas_global, ); if setup.write_to_memory { diff --git a/crates/vm/src/host_env.rs b/crates/vm/src/host_env.rs index f1ffbf2ee72..37d427b53d7 100644 --- a/crates/vm/src/host_env.rs +++ b/crates/vm/src/host_env.rs @@ -145,6 +145,9 @@ where pub cache_access: std::marker::PhantomData, /// WASM intructions gas meter kind pub gas_meter_kind: GasMeterKind, + /// Global mutable gas variable, only set when gas meter kind is + /// `MutGlobal` + pub gas_global: HostRef>, } impl TxVmEnv @@ -179,6 +182,7 @@ where #[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache, #[cfg(feature = "wasm-runtime")] tx_wasm_cache: &mut TxCache, gas_meter_kind: GasMeterKind, + gas_global: &Option, ) -> Self { let write_log = unsafe { RwHostRef::new(write_log) }; let in_mem = unsafe { RoHostRef::new(in_mem) }; @@ -197,6 +201,7 @@ where let vp_wasm_cache = unsafe { RwHostRef::new(vp_wasm_cache) }; #[cfg(feature = "wasm-runtime")] let tx_wasm_cache = unsafe { RwHostRef::new(tx_wasm_cache) }; + let gas_global = unsafe { RoHostRef::new(gas_global) }; let ctx = TxCtx { write_log, db, @@ -218,6 +223,7 @@ where #[cfg(not(feature = "wasm-runtime"))] cache_access: std::marker::PhantomData, gas_meter_kind, + gas_global, }; Self { memory, ctx } @@ -304,6 +310,7 @@ where #[cfg(not(feature = "wasm-runtime"))] cache_access: std::marker::PhantomData, gas_meter_kind: self.gas_meter_kind, + gas_global: self.gas_global, } } } @@ -608,37 +615,6 @@ where consume_tx_gas(env, used_gas.into()) } -// Internal funtion to charge gas for txs. Called by the other functions in this -// file while the public version is left to be used directly from wasm and as a -// hook for gas instrumentation -fn consume_tx_gas( - env: &mut TxVmEnv, - used_gas: Gas, -) -> Result<()> -where - MEM: VmMemory, - D: 'static + DB + for<'iter> DBIter<'iter>, - H: 'static + StorageHasher, - CA: WasmCacheAccess, -{ - let (gas_meter, sentinel) = env.ctx.gas_meter_and_sentinel(); - - // if we run out of gas, we need to stop the execution - gas_meter - .borrow_mut() - .consume(used_gas) - .map_err(|err| { - sentinel.borrow_mut().set_out_of_gas(); - tracing::info!( - "Stopping transaction execution because of gas error: {}", - err - ); - - TxRuntimeError::OutOfGas(err) - }) - .into_storage_result() -} - /// Called from VP wasm to request to use the given gas amount pub fn vp_charge_gas( env: &mut VpVmEnv, @@ -668,6 +644,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let (key, gas) = env .memory .read_string(key_ptr, key_len.try_into()?) @@ -700,6 +678,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let (key, gas) = env .memory .read_string(key_ptr, key_len.try_into()?) @@ -743,6 +723,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let (key, gas) = env .memory .read_string(key_ptr, key_len.try_into()?) @@ -788,6 +770,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let result_buffer = unsafe { env.ctx.result_buffer.get_mut() }; let value = result_buffer .take() @@ -813,6 +797,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let (prefix, gas) = env .memory .read_string(prefix_ptr, prefix_len.try_into()?) @@ -851,6 +837,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + tracing::debug!("tx_iter_next iter_id {}", iter_id,); let iterators = unsafe { env.ctx.iterators.get_mut() }; @@ -916,6 +904,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let (key, gas) = env .memory .read_string(key_ptr, key_len.try_into()?) @@ -956,6 +946,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let (key, gas) = env .memory .read_string(key_ptr, key_len.try_into()?) @@ -1037,6 +1029,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let (key, gas) = env .memory .read_string(key_ptr, key_len.try_into()?) @@ -1067,6 +1061,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let (event, gas) = env .memory .read_bytes(event_ptr, event_len.try_into()?) @@ -1102,6 +1098,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let (event_type, gas) = env .memory .read_string(event_type_ptr, event_type_len.try_into()?) @@ -1469,6 +1467,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let (addr, gas) = env .memory .read_string(addr_ptr, addr_len.try_into()?) @@ -1507,6 +1507,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let (addr, gas) = env .memory .read_string(addr_ptr, addr_len.try_into()?) @@ -1556,6 +1558,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let (code_hash, gas) = env .memory .read_bytes(code_hash_ptr, code_hash_len.try_into()?) @@ -1606,6 +1610,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let state = env.state(); let (chain_id, gas) = state.in_mem().get_chain_id(); consume_tx_gas::(env, gas)?; @@ -1628,6 +1634,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let state = env.state(); let (height, gas) = state.in_mem().get_block_height(); consume_tx_gas::(env, gas)?; @@ -1646,6 +1654,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + consume_tx_gas::( env, (TX_INDEX_LENGTH as u64) @@ -1688,6 +1698,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let state = env.state(); let (epoch, gas) = state.in_mem().get_current_epoch(); consume_tx_gas::(env, gas)?; @@ -1704,6 +1716,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let state = env.state(); let pred_epochs = state.in_mem().block.pred_epochs.clone(); let bytes = pred_epochs.serialize_to_vec(); @@ -1732,6 +1746,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + // Gas for getting the native token address from storage consume_tx_gas::( env, @@ -1761,6 +1777,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let state = env.state(); let (header, gas) = StateRead::get_block_header(&state, Some(BlockHeight(height)))?; @@ -2036,6 +2054,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let (str, _gas) = env .memory .read_string(str_ptr, str_len.try_into()?) @@ -2056,6 +2076,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let code_hash = Hash::try_from(code_hash) .map_err(|e| TxRuntimeError::InvalidVpCodeHash(e.to_string()))?; @@ -2131,6 +2153,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let (hash_list, gas) = env .memory .read_bytes(hash_list_ptr, hash_list_len.try_into()?) @@ -2152,7 +2176,7 @@ where let tx = unsafe { env.ctx.tx.get() }; let (gas_meter, sentinel) = env.ctx.gas_meter_and_sentinel(); - match tx.verify_signatures( + let result = match tx.verify_signatures( &HashSet::from_iter(hashes), public_keys_map, &None, @@ -2175,7 +2199,11 @@ where _ => Ok(HostEnvResult::Fail.to_i64()), }, } - .into_storage_result() + .into_storage_result(); + + tx_sync_gas_into_wasm(env)?; + + result } /// Appends the new note commitments to the tree in storage @@ -2190,6 +2218,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let (serialized_transaction, gas) = env .memory .read_bytes(transaction_ptr, transaction_len.try_into()?) @@ -2225,6 +2255,8 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { + tx_sync_gas_from_wasm(env)?; + let (value_to_yield, gas) = env .memory .read_bytes(buf_ptr, buf_len.try_into()?) @@ -2355,6 +2387,108 @@ where Ok(()) } +// Internal funtion to charge gas for txs. Called by the other functions in this +// file while the public version is left to be used directly from wasm and as a +// hook for gas instrumentation +fn consume_tx_gas( + env: &mut TxVmEnv, + used_gas: Gas, +) -> Result<()> +where + MEM: VmMemory, + D: 'static + DB + for<'iter> DBIter<'iter>, + H: 'static + StorageHasher, + CA: WasmCacheAccess, +{ + let (gas_meter, sentinel) = env.ctx.gas_meter_and_sentinel(); + + // if we run out of gas, we need to stop the execution + let result = gas_meter + .borrow_mut() + .consume(used_gas) + .map_err(|err| { + sentinel.borrow_mut().set_out_of_gas(); + tracing::info!( + "Stopping transaction execution because of gas error: {}", + err + ); + + TxRuntimeError::OutOfGas(err) + }) + .into_storage_result(); + + tx_sync_gas_into_wasm(env)?; + + result +} + +/// Sync gas meter from WASM into host env +fn tx_sync_gas_from_wasm( + env: &mut TxVmEnv, +) -> Result<()> +where + MEM: VmMemory, + D: 'static + DB + for<'iter> DBIter<'iter>, + H: 'static + StorageHasher, + CA: WasmCacheAccess, +{ + if let GasMeterKind::MutGlobal = env.ctx.gas_meter_kind { + let store = env + .memory + .store() + .upgrade() + .expect("Store must be accessible while the WASM is running"); + let mut store = store.borrow_mut(); + let gas_global = unsafe { env.ctx.gas_global.get() }; + let wasm_gas = gas_global.as_ref().unwrap().get(&mut store); + #[allow(clippy::cast_sign_loss)] + if let wasmer::Value::I64(available_gas) = wasm_gas { + // intentional wrap around the value + let available_gas = available_gas as u64; + let gas_meter = unsafe { env.ctx.gas_meter.get() }; + gas_meter.borrow_mut().set_available_gas(available_gas); + } else { + return Err(Error::new_const("Unexpected WASM gas value type")); + } + } + Ok(()) +} + +/// Sync gas meter from host env back into WASM +fn tx_sync_gas_into_wasm( + env: &mut TxVmEnv, +) -> Result<()> +where + MEM: VmMemory, + D: 'static + DB + for<'iter> DBIter<'iter>, + H: 'static + StorageHasher, + CA: WasmCacheAccess, +{ + if let GasMeterKind::MutGlobal = env.ctx.gas_meter_kind { + let store = env + .memory + .store() + .upgrade() + .expect("Store must be accessible while the WASM is running"); + let mut store = store.borrow_mut(); + let gas_meter = unsafe { env.ctx.gas_meter.get() }; + + // intentional wrap around the value + #[allow(clippy::cast_possible_wrap)] + let available_gas = + u64::from(gas_meter.borrow().get_available_gas()) as i64; + + let gas_global = unsafe { env.ctx.gas_global.get() }; + + gas_global + .as_ref() + .unwrap() + .set(&mut store, wasmer::Value::I64(available_gas)) + .unwrap(); + } + Ok(()) +} + /// A helper module for testing #[cfg(feature = "testing")] pub mod testing { @@ -2379,6 +2513,7 @@ pub mod testing { yielded_value: &mut Option>, #[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache, #[cfg(feature = "wasm-runtime")] tx_wasm_cache: &mut TxCache, + gas_global: &Option, ) -> TxVmEnv::D, ::H, CA> where S: State, @@ -2405,6 +2540,7 @@ pub mod testing { #[cfg(feature = "wasm-runtime")] tx_wasm_cache, GasMeterKind::MutGlobal, + gas_global, ) } @@ -2424,6 +2560,7 @@ pub mod testing { store: Rc>, #[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache, #[cfg(feature = "wasm-runtime")] tx_wasm_cache: &mut TxCache, + gas_global: &Option, ) -> TxVmEnv::D, ::H, CA> where S: State, @@ -2456,6 +2593,7 @@ pub mod testing { #[cfg(feature = "wasm-runtime")] tx_wasm_cache, GasMeterKind::MutGlobal, + gas_global, ); env.memory.init_from(&wasm_memory); diff --git a/crates/vm/src/memory.rs b/crates/vm/src/memory.rs index fc08464c398..1cb5fc1d7e8 100644 --- a/crates/vm/src/memory.rs +++ b/crates/vm/src/memory.rs @@ -1,6 +1,8 @@ //! Virtual machine's memory. +use std::cell::RefCell; use std::error::Error; +use std::rc; use namada_gas::Gas; @@ -36,6 +38,9 @@ pub trait VmMemory: Clone + Send + Sync { offset: u64, string: String, ) -> Result; + + /// Return a wasmer store associated with this memory. + fn store(&self) -> rc::Weak>; } /// Helper module for VM testing @@ -98,5 +103,9 @@ pub mod testing { target.clone_from_slice(bytes); Ok(Gas::default()) } + + fn store(&self) -> rc::Weak> { + unimplemented!() + } } } diff --git a/crates/vm/src/wasm/memory.rs b/crates/vm/src/wasm/memory.rs index 480ff03773b..3bad9e91a43 100644 --- a/crates/vm/src/wasm/memory.rs +++ b/crates/vm/src/wasm/memory.rs @@ -406,6 +406,10 @@ impl VmMemory for WasmMemory { fn write_string(&mut self, offset: u64, string: String) -> Result { self.write_bytes(offset, string.as_bytes()) } + + fn store(&self) -> rc::Weak> { + self.store.clone() + } } /// A custom [`Tunables`] to set a WASM memory limits. diff --git a/crates/vm/src/wasm/run.rs b/crates/vm/src/wasm/run.rs index 61afeb264a1..e11412cece3 100644 --- a/crates/vm/src/wasm/run.rs +++ b/crates/vm/src/wasm/run.rs @@ -1,7 +1,5 @@ //! Wasm runners -pub use namada_gas::GasMeterKind; - use std::cell::RefCell; use std::collections::BTreeSet; use std::error::Error as _; @@ -16,6 +14,7 @@ use namada_core::hash::{Error as TxHashError, Hash}; use namada_core::internal::HostEnvResult; use namada_core::storage::{Key, TxIndex}; use namada_core::validity_predicate::VpError; +pub use namada_gas::GasMeterKind; use namada_gas::{GasMetering, TxGasMeter, VpGasMeter, WASM_MEMORY_PAGE_GAS}; use namada_state::prefix_iter::PrefixIterators; use namada_state::{DB, DBIter, State, StateRead, StorageHasher, StorageRead}; @@ -74,6 +73,8 @@ pub enum Error { MissingModuleMemory(wasmer::ExportError), #[error("Missing wasm entrypoint: {0}")] MissingModuleEntrypoint(wasmer::ExportError), + #[error("Missing gas mutable global: {0}")] + MissingGasMutGlobal(wasmer::ExportError), #[error("Failed running wasm with: {0}")] RuntimeError(wasmer::RuntimeError), #[error("Failed instantiating wasm module with: {0}")] @@ -209,6 +210,7 @@ where let mut verifiers = BTreeSet::new(); let mut result_buffer: Option> = None; let mut yielded_value: Option> = None; + let mut gas_global: Option = None; let sentinel = RefCell::new(TxSentinel::default()); let (write_log, in_mem, db) = state.split_borrow(); @@ -232,6 +234,7 @@ where vp_wasm_cache, tx_wasm_cache, gas_meter_kind, + &gas_global, ); // Instantiate the wasm module @@ -243,17 +246,24 @@ where }; if let GasMeterKind::MutGlobal = gas_meter_kind { + let mut store = store.borrow_mut(); + let global = instance + .exports + .get_global(MUT_GLOBAL_GAS_NAME) + .map_err(Error::MissingGasMutGlobal)?; + #[allow(clippy::cast_possible_wrap)] // intentional wrap around the value let available_gas = u64::from(gas_meter.borrow().get_available_gas()) as i64; - let mut store = store.borrow_mut(); - instance - .exports - .get_global(MUT_GLOBAL_GAS_NAME) - .unwrap() + global .set(&mut *store, wasmer::Value::I64(available_gas)) - .unwrap(); + .map_err(Error::RuntimeError)?; + + #[allow(unused_assignments)] + { + gas_global = Some(global.clone()); + } } // Fetch guest's main memory From f1d7010e27f5c27e926f4257eab41b21f42cc5ce Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 28 May 2025 12:58:20 +0100 Subject: [PATCH 05/10] Use custom wasm gas metering backend --- crates/vm/src/wasm/run.rs | 46 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/crates/vm/src/wasm/run.rs b/crates/vm/src/wasm/run.rs index e11412cece3..7483a642d7f 100644 --- a/crates/vm/src/wasm/run.rs +++ b/crates/vm/src/wasm/run.rs @@ -759,9 +759,7 @@ pub fn prepare_wasm_code>( .map_err(|_original_module| Error::GasMeterInjection)?, GasMeterKind::MutGlobal => wasm_instrument::gas_metering::inject( module, - wasm_instrument::gas_metering::mutable_global::Injector::new( - MUT_GLOBAL_GAS_NAME, - ), + WasmMutGlobalGasBackend, &GasRules, ) .map_err(|_original_module| Error::GasMeterInjection)?, @@ -1084,6 +1082,48 @@ impl wasm_instrument::gas_metering::Rules for GasRules { } } +struct WasmMutGlobalGasBackend; + +impl wasm_instrument::gas_metering::Backend for WasmMutGlobalGasBackend { + fn gas_meter( + self, + module: &elements::Module, + _rules: &R, + ) -> wasm_instrument::gas_metering::GasMeter { + #[allow(clippy::cast_possible_truncation)] + let gas_global_idx = module.globals_space() as u32; + + let func_instructions = vec![ + // test if we still have gas + GetGlobal(gas_global_idx), + GetLocal(0), + I64GeU, + If(elements::BlockType::NoResult), + // we still have gas, decrement mut global + GetGlobal(gas_global_idx), + GetLocal(0), + I64Sub, + SetGlobal(gas_global_idx), + // out of gas, set sentinel and abort + Else, + I64Const(-1i64), + SetGlobal(gas_global_idx), + Unreachable, + // end if + End, + // end block + End, + ]; + + wasm_instrument::gas_metering::GasMeter::Internal { + global: MUT_GLOBAL_GAS_NAME, + func_instructions: elements::Instructions::new(func_instructions), + // we charge no gas for the gas function itself + cost: 0u64, + } + } +} + #[cfg(test)] mod tests { use std::collections::BTreeMap; From b5f49b643a50e4c1d8fbb7a2af0ca56cccd5ff3c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 2 Jun 2025 10:11:53 +0100 Subject: [PATCH 06/10] Move tx/vp host env defs to vm crate --- Cargo.lock | 1 + crates/state/src/lib.rs | 6 ------ crates/vm/Cargo.toml | 1 + crates/vm/src/host_env.rs | 5 ++++- .../host_env.rs => vm/src/host_env/state.rs} | 19 ++++++++++++++----- wasm/Cargo.lock | 1 + 6 files changed, 21 insertions(+), 12 deletions(-) rename crates/{state/src/host_env.rs => vm/src/host_env/state.rs} (84%) diff --git a/Cargo.lock b/Cargo.lock index 20c7d6eecd2..a122dca8d7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6448,6 +6448,7 @@ dependencies = [ "namada_gas", "namada_parameters", "namada_state", + "namada_storage", "namada_test_utils", "namada_token", "namada_tx", diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index d35ff52ba36..d565b7a83d0 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -17,7 +17,6 @@ clippy::print_stderr )] -mod host_env; mod in_memory; pub mod prefix_iter; mod wl_state; @@ -26,7 +25,6 @@ pub mod write_log; use std::fmt::Debug; use std::iter::Peekable; -pub use host_env::{TxHostEnvState, VpHostEnvState}; pub use in_memory::{ BlockStorage, InMemory, LastBlock, ProcessProposalCachedResult, }; @@ -428,10 +426,6 @@ impl_storage_write_by_protocol!(WlState); impl_storage_write_by_protocol!(TempWlState<'_, D, H>); impl_storage_write!(TxWlState<'_, D, H>); -impl_storage_read!(TxHostEnvState<'_, D, H>); -impl_storage_read!(VpHostEnvState<'_, D, H>); -impl_storage_write!(TxHostEnvState<'_, D, H>); - #[allow(missing_docs)] #[derive(Error, Debug)] pub enum StateError { diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index b5743cacba9..3a82700da7c 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -33,6 +33,7 @@ namada_events.workspace = true namada_gas.workspace = true namada_parameters.workspace = true namada_state.workspace = true +namada_storage.workspace = true namada_token.workspace = true namada_tx.workspace = true namada_vp.workspace = true diff --git a/crates/vm/src/host_env.rs b/crates/vm/src/host_env.rs index 37d427b53d7..54e6840572e 100644 --- a/crates/vm/src/host_env.rs +++ b/crates/vm/src/host_env.rs @@ -1,6 +1,8 @@ //! Virtual machine's host environment exposes functions that may be called from //! within a virtual machine. +pub mod state; + use std::borrow::Cow; use std::cell::RefCell; use std::collections::BTreeSet; @@ -26,7 +28,7 @@ use namada_state::prefix_iter::{PrefixIteratorId, PrefixIterators}; use namada_state::write_log::{self, WriteLog}; use namada_state::{ DB, DBIter, InMemory, OptionExt, ResultExt, State, StateRead, - StorageHasher, StorageRead, StorageWrite, TxHostEnvState, VpHostEnvState, + StorageHasher, StorageRead, StorageWrite, }; pub use namada_state::{Error, Result}; use namada_token::MaspTransaction; @@ -39,6 +41,7 @@ use namada_tx::{BatchedTx, BatchedTxRef, Tx, TxCommitments}; use namada_vp::vp_host_fns; use thiserror::Error; +use self::state::{TxHostEnvState, VpHostEnvState}; #[cfg(feature = "wasm-runtime")] use super::wasm::{TxCache, VpCache}; use crate::memory::VmMemory; diff --git a/crates/state/src/host_env.rs b/crates/vm/src/host_env/state.rs similarity index 84% rename from crates/state/src/host_env.rs rename to crates/vm/src/host_env/state.rs index b0444eee220..6f8a407abd1 100644 --- a/crates/state/src/host_env.rs +++ b/crates/vm/src/host_env/state.rs @@ -1,14 +1,23 @@ +//! Host environment state. + use std::cell::RefCell; +use namada_core::address::Address; +use namada_core::chain::{BlockHeader, BlockHeight, ChainId, Epoch, Epochs}; use namada_events::{EmitEvents, EventToEmit}; use namada_gas::{Gas, GasMetering, TxGasMeter, VpGasMeter}; +use namada_state::write_log::{self, WriteLog}; +use namada_state::{ + DB, DBIter, Error, InMemory, PrefixIter, Result, ResultExt, State, + StateError, StateRead, StorageHasher, StorageRead, StorageWrite, + impl_storage_read, impl_storage_write, iter_prefix_post, +}; +use namada_storage as storage; use namada_tx::data::TxSentinel; -use crate::in_memory::InMemory; -use crate::write_log::WriteLog; -use crate::{ - DB, DBIter, Error, Result, State, StateError, StateRead, StorageHasher, -}; +impl_storage_read!(TxHostEnvState<'_, D, H>); +impl_storage_read!(VpHostEnvState<'_, D, H>); +impl_storage_write!(TxHostEnvState<'_, D, H>); /// State with mutable write log and gas metering for tx host env. #[derive(Debug)] diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index da862097512..7ecfc4ba57d 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -4962,6 +4962,7 @@ dependencies = [ "namada_gas", "namada_parameters", "namada_state", + "namada_storage", "namada_token", "namada_tx", "namada_vp", From 59ef04c1fe929b147aa3a31ecbc9ff45c4fc354e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 2 Jun 2025 10:01:07 +0100 Subject: [PATCH 07/10] Switch to wasm global gas meter --- crates/gas/src/lib.rs | 153 ++++---- crates/node/src/dry_run_tx.rs | 2 +- crates/node/src/protocol.rs | 8 +- crates/node/src/shell/finalize_block.rs | 10 +- crates/node/src/shell/mod.rs | 4 +- crates/tests/src/native_vp/eth_bridge_pool.rs | 4 +- crates/tests/src/native_vp/pos.rs | 19 +- crates/tests/src/vm_host_env/tx.rs | 47 +-- crates/tests/src/vm_host_env/vp.rs | 10 +- crates/vm/src/host_env.rs | 177 +-------- crates/vm/src/host_env/gas_meter.rs | 161 ++++++++ crates/vm/src/host_env/state.rs | 6 +- crates/vm/src/lib.rs | 2 +- crates/vm/src/memory.rs | 9 - crates/vm/src/wasm/host_env.rs | 195 ++++++++++ crates/vm/src/wasm/memory.rs | 4 - crates/vm/src/wasm/run.rs | 366 ++++++++++++------ crates/vp/src/vp_host_fns.rs | 43 +- wasm/tx_become_validator/src/lib.rs | 4 +- wasm/tx_bond/src/lib.rs | 4 +- .../tx_change_validator_commission/src/lib.rs | 4 +- wasm/tx_redelegate/src/lib.rs | 4 +- wasm/tx_unbond/src/lib.rs | 4 +- wasm/tx_withdraw/src/lib.rs | 4 +- 24 files changed, 792 insertions(+), 452 deletions(-) create mode 100644 crates/vm/src/host_env/gas_meter.rs diff --git a/crates/gas/src/lib.rs b/crates/gas/src/lib.rs index 2c227d834c1..70df800bf9b 100644 --- a/crates/gas/src/lib.rs +++ b/crates/gas/src/lib.rs @@ -248,6 +248,11 @@ pub struct Gas { } impl Gas { + /// Initialize a new gas value from its sub units. + pub const fn new(sub_units: u64) -> Self { + Self { sub: sub_units } + } + /// Checked add of `Gas`. Returns `None` on overflow pub fn checked_add(&self, rhs: Self) -> Option { self.sub.checked_add(rhs.sub).map(|sub| Self { sub }) @@ -353,6 +358,29 @@ pub trait GasMetering { /// will still be updated fn consume(&mut self, gas: Gas) -> Result<()>; + /// Get the gas initially available to the gas meter + /// + /// This value will be equal to the gas limit minus some + /// gas that may have been consumed before the current + /// meter was initialized + fn get_initially_available_gas(&self) -> Gas; + + /// Get the gas consumed thus far + fn get_consumed_gas(&self) -> Gas; + + /// Get the gas limit + fn get_gas_limit(&self) -> Gas; + + /// Get the protocol gas scale + fn get_gas_scale(&self) -> u64; + + /// Get the amount of gas still available to the transaction + fn get_available_gas(&self) -> Gas { + self.get_gas_limit() + .checked_sub(self.get_consumed_gas()) + .unwrap_or_default() + } + /// Add the compiling cost proportionate to the code length fn add_compiling_gas(&mut self, bytes_len: u64) -> Result<()> { self.consume( @@ -383,56 +411,44 @@ pub trait GasMetering { ) } - /// Get the gas consumed by the tx - fn get_tx_consumed_gas(&self) -> Gas; - - /// Get the gas limit - fn get_gas_limit(&self) -> Gas; - - /// Get the protocol gas scale - fn get_gas_scale(&self) -> u64; - - /// Check if the vps went out of gas. Starts with the gas consumed by the - /// transaction. - fn check_vps_limit(&self, vps_gas: Gas) -> Result<()> { - let total = self - .get_tx_consumed_gas() - .checked_add(vps_gas) - .ok_or(Error::GasOverflow)?; - let gas_limit = self.get_gas_limit(); - if total > gas_limit { - return Err(Error::TransactionGasExceededError( - gas_limit.get_whole_gas_units(self.get_gas_scale()), - )); - } - - Ok(()) + /// Check if the meter ran out of gas. Starts with the initial gas. + fn check_limit(&self, gas: Gas) -> Result<()> { + self.get_initially_available_gas() + .checked_sub(gas) + .ok_or_else(|| { + Error::TransactionGasExceededError( + self.get_gas_limit() + .get_whole_gas_units(self.get_gas_scale()), + ) + }) + .and(Ok(())) } } /// Gas metering in a transaction -#[derive(Debug)] +#[derive(Debug, Default)] pub struct TxGasMeter { /// Track gas overflow gas_overflow: bool, - // The protocol gas scale + /// The protocol gas scale gas_scale: u64, /// The gas limit for a transaction - pub tx_gas_limit: Gas, + tx_gas_limit: Gas, + /// Gas consumption of the tx transaction_gas: Gas, } /// Gas metering in a validity predicate -#[derive(Debug)] +#[derive(Debug, Default)] pub struct VpGasMeter { /// Track gas overflow gas_overflow: bool, - // The protocol gas scale + /// The protocol gas scale gas_scale: u64, /// The transaction gas limit tx_gas_limit: Gas, /// The gas consumed by the transaction before the Vp - initial_gas: Gas, + prev_meter_consumed_gas: Gas, /// The current gas usage in the VP current_gas: Gas, } @@ -460,8 +476,12 @@ impl GasMetering for TxGasMeter { Ok(()) } - /// Get the entire gas used by the transaction up until this point - fn get_tx_consumed_gas(&self) -> Gas { + #[inline] + fn get_initially_available_gas(&self) -> Gas { + self.get_gas_limit() + } + + fn get_consumed_gas(&self) -> Gas { if !self.gas_overflow { self.transaction_gas.clone() } else { @@ -510,33 +530,6 @@ impl TxGasMeter { .into(), ) } - - /// Get the amount of gas still available to the transaction - pub fn get_available_gas(&self) -> Gas { - self.tx_gas_limit - .checked_sub(self.transaction_gas.clone()) - .unwrap_or_default() - } - - /// Set the amount of gas still available to the transaction. - /// - /// WARNING: Utmost care must be taken when using this function! This must - /// never increase the amount available. - pub fn set_available_gas(&mut self, gas: impl Into) { - let gas: Gas = gas.into(); - let used_gas = self - .tx_gas_limit - .checked_sub(gas) - .unwrap_or_else(|| self.tx_gas_limit.clone()); - debug_assert!( - used_gas > self.transaction_gas, - "Used gas must not decrease from {:?}, but trying to set it to \ - {:?}", - self.transaction_gas, - used_gas - ); - self.transaction_gas = used_gas; - } } impl GasMetering for VpGasMeter { @@ -554,7 +547,7 @@ impl GasMetering for VpGasMeter { })?; let current_total = self - .initial_gas + .prev_meter_consumed_gas .checked_add(self.current_gas.clone()) .ok_or(Error::GasOverflow)?; @@ -567,14 +560,16 @@ impl GasMetering for VpGasMeter { Ok(()) } - /// Get the gas consumed by the tx alone before the vps were executed - fn get_tx_consumed_gas(&self) -> Gas { - if !self.gas_overflow { - self.initial_gas.clone() - } else { - hints::cold(); - u64::MAX.into() - } + fn get_initially_available_gas(&self) -> Gas { + self.tx_gas_limit + .checked_sub(self.prev_meter_consumed_gas.clone()) + .unwrap_or_default() + } + + fn get_consumed_gas(&self) -> Gas { + self.prev_meter_consumed_gas + .checked_add(self.get_vp_consumed_gas()) + .unwrap_or_else(|| u64::MAX.into()) } fn get_gas_limit(&self) -> Gas { @@ -587,13 +582,18 @@ impl GasMetering for VpGasMeter { } impl VpGasMeter { - /// Initialize a new VP gas meter from the `TxGasMeter` + /// Initialize a new VP gas meter from the [`TxGasMeter`] pub fn new_from_tx_meter(tx_gas_meter: &TxGasMeter) -> Self { + Self::new_from_meter(tx_gas_meter) + } + + /// Initialize a new VP gas meter from the given generic gas meter + pub fn new_from_meter(gas_meter: &impl GasMetering) -> Self { Self { gas_overflow: false, - gas_scale: tx_gas_meter.gas_scale, - tx_gas_limit: tx_gas_meter.tx_gas_limit.clone(), - initial_gas: tx_gas_meter.transaction_gas.clone(), + gas_scale: gas_meter.get_gas_scale(), + tx_gas_limit: gas_meter.get_gas_limit(), + prev_meter_consumed_gas: gas_meter.get_consumed_gas(), current_gas: Gas::default(), } } @@ -602,15 +602,6 @@ impl VpGasMeter { pub fn get_vp_consumed_gas(&self) -> Gas { self.current_gas.clone() } - - /// Get the amount of gas still available to the VP - pub fn get_available_gas(&self) -> Gas { - self.tx_gas_limit - .checked_sub(self.initial_gas.clone()) - .unwrap_or_default() - .checked_sub(self.current_gas.clone()) - .unwrap_or_default() - } } #[cfg(test)] diff --git a/crates/node/src/dry_run_tx.rs b/crates/node/src/dry_run_tx.rs index be04c1b7522..508b82a9b38 100644 --- a/crates/node/src/dry_run_tx.rs +++ b/crates/node/src/dry_run_tx.rs @@ -115,7 +115,7 @@ where tx_result_string, tx_gas_meter .borrow() - .get_tx_consumed_gas() + .get_consumed_gas() .get_whole_gas_units(gas_scale), ); diff --git a/crates/node/src/protocol.rs b/crates/node/src/protocol.rs index 27c002fd591..c3b05ae310b 100644 --- a/crates/node/src/protocol.rs +++ b/crates/node/src/protocol.rs @@ -888,7 +888,7 @@ where tx_gas_meter .borrow_mut() - .consume(masp_gas_meter.borrow().get_tx_consumed_gas()) + .consume(masp_gas_meter.borrow().get_consumed_gas()) .map_err(|e| Error::GasError(e.to_string()))?; Ok(valid_batched_tx_result) @@ -1273,7 +1273,7 @@ where || (VpsResult::default(), Gas::from(0)), |(mut result, mut vps_gas), addr| { let gas_meter = - RefCell::new(VpGasMeter::new_from_tx_meter(tx_gas_meter)); + RefCell::new(VpGasMeter::new_from_meter(tx_gas_meter)); let tx_accepted = match &addr { Address::Implicit(_) | Address::Established(_) => { let (vp_hash, gas) = state @@ -1471,7 +1471,7 @@ where ))?; gas_meter .borrow() - .check_vps_limit(vps_gas.clone()) + .check_limit(vps_gas.clone()) .map_err(|err| Error::GasError(err.to_string()))?; Ok((result, vps_gas)) @@ -1503,7 +1503,7 @@ fn merge_vp_results( .checked_add(b_gas) .ok_or(Error::GasError(gas::Error::GasOverflow.to_string()))?; tx_gas_meter - .check_vps_limit(vps_gas.clone()) + .check_limit(vps_gas.clone()) .map_err(|err| Error::GasError(err.to_string()))?; Ok(( diff --git a/crates/node/src/shell/finalize_block.rs b/crates/node/src/shell/finalize_block.rs index 998ad399735..224a6444c69 100644 --- a/crates/node/src/shell/finalize_block.rs +++ b/crates/node/src/shell/finalize_block.rs @@ -413,7 +413,7 @@ where let gas_scale = tx_data.tx_gas_meter.get_gas_scale(); let scaled_gas = tx_data .tx_gas_meter - .get_tx_consumed_gas() + .get_consumed_gas() .get_whole_gas_units(gas_scale); tx_logs .tx_event @@ -446,7 +446,7 @@ where let gas_scale = tx_data.tx_gas_meter.get_gas_scale(); let scaled_gas = tx_data .tx_gas_meter - .get_tx_consumed_gas() + .get_consumed_gas() .get_whole_gas_units(gas_scale); tx_logs @@ -523,7 +523,7 @@ where let gas_scale = tx_data.tx_gas_meter.get_gas_scale(); let scaled_gas = tx_data .tx_gas_meter - .get_tx_consumed_gas() + .get_consumed_gas() .get_whole_gas_units(gas_scale); tx_logs @@ -806,7 +806,7 @@ where &mut self.state, ); let tx_gas_meter = tx_gas_meter.into_inner(); - let consumed_gas = tx_gas_meter.get_tx_consumed_gas(); + let consumed_gas = tx_gas_meter.get_consumed_gas(); // save the gas cost self.update_tx_gas(tx_hash, consumed_gas); @@ -880,7 +880,7 @@ where &mut self.state, ); let tx_gas_meter = tx_gas_meter.into_inner(); - let consumed_gas = tx_gas_meter.get_tx_consumed_gas(); + let consumed_gas = tx_gas_meter.get_consumed_gas(); // update the gas cost of the corresponding wrapper self.update_tx_gas(tx_hash, consumed_gas); diff --git a/crates/node/src/shell/mod.rs b/crates/node/src/shell/mod.rs index a6e47671f82..b0346b992ab 100644 --- a/crates/node/src/shell/mod.rs +++ b/crates/node/src/shell/mod.rs @@ -42,7 +42,7 @@ use namada_sdk::eth_bridge::protocol::validation::validator_set_update::validate use namada_sdk::eth_bridge::{EthBridgeQueries, EthereumOracleConfig}; use namada_sdk::ethereum_events::EthereumEvent; use namada_sdk::events::log::EventLog; -use namada_sdk::gas::{Gas, TxGasMeter}; +use namada_sdk::gas::{Gas, GasMetering, TxGasMeter}; use namada_sdk::hash::Hash; use namada_sdk::key::*; use namada_sdk::migrations::ScheduledMigration; @@ -1306,7 +1306,7 @@ where let block_gas_limit: Gas = Gas::from_whole_units(max_block_gas.into(), gas_scale) .expect("Gas limit from parameter must not overflow"); - if gas_meter.tx_gas_limit > block_gas_limit { + if gas_meter.get_gas_limit() > block_gas_limit { response.code = ResultCode::AllocationError.into(); response.log = format!( "{INVALID_MSG}: Wrapper transaction exceeds the \ diff --git a/crates/tests/src/native_vp/eth_bridge_pool.rs b/crates/tests/src/native_vp/eth_bridge_pool.rs index e1beb93e3d6..940bc8e1188 100644 --- a/crates/tests/src/native_vp/eth_bridge_pool.rs +++ b/crates/tests/src/native_vp/eth_bridge_pool.rs @@ -113,8 +113,8 @@ mod test_bridge_pool_vp { tx_host_env::set(env); let mut tx_env = tx_host_env::take(); tx_env.execute_tx().expect("Test failed."); - let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &tx_env.gas_meter.borrow(), + let gas_meter = RefCell::new(VpGasMeter::new_from_meter( + &*tx_env.gas_meter.borrow(), )); let vp_env = TestNativeVpEnv::from_tx_env(tx_env, BRIDGE_POOL_ADDRESS); diff --git a/crates/tests/src/native_vp/pos.rs b/crates/tests/src/native_vp/pos.rs index fa552e9883f..7c4bc23d264 100644 --- a/crates/tests/src/native_vp/pos.rs +++ b/crates/tests/src/native_vp/pos.rs @@ -445,8 +445,8 @@ mod tests { // Use the tx_env to run PoS VP let tx_env = tx_host_env::take(); - let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &tx_env.gas_meter.borrow(), + let gas_meter = RefCell::new(VpGasMeter::new_from_meter( + &*tx_env.gas_meter.borrow(), )); let vp_env = TestNativeVpEnv::from_tx_env(tx_env, address::POS); let ctx = vp_env.ctx(&gas_meter); @@ -597,7 +597,7 @@ pub mod testing { use itertools::Either; use namada_sdk::chain::Epoch; use namada_sdk::dec::Dec; - use namada_sdk::gas::TxGasMeter; + use namada_sdk::gas::{GasMetering, TxGasMeter}; use namada_sdk::key::RefTo; use namada_sdk::key::common::PublicKey; use namada_sdk::proof_of_stake::ADDRESS as POS_ADDRESS; @@ -611,6 +611,7 @@ pub mod testing { use namada_sdk::token::{Amount, Change}; use namada_sdk::{address, governance, key, token}; use namada_tx_prelude::{Address, StorageRead, StorageWrite}; + use namada_vm::host_env::gas_meter::GasMeter; use proptest::prelude::*; use crate::tx::{self, tx_host_env}; @@ -881,11 +882,13 @@ 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 - let gas_limit = env.gas_meter.borrow().tx_gas_limit.clone(); - env.gas_meter = RefCell::new(TxGasMeter::new( - gas_limit, - namada_sdk::parameters::get_gas_scale(tx::ctx()).unwrap(), - )); + let gas_limit = env.gas_meter.borrow().get_gas_limit(); + env.gas_meter = + RefCell::new(GasMeter::Native(TxGasMeter::new( + gas_limit, + namada_sdk::parameters::get_gas_scale(tx::ctx()) + .unwrap(), + ))); env.state.in_mem().block.epoch }); println!("Current epoch {}", current_epoch); diff --git a/crates/tests/src/vm_host_env/tx.rs b/crates/tests/src/vm_host_env/tx.rs index c7ea99b0a04..4e23ed2f858 100644 --- a/crates/tests/src/vm_host_env/tx.rs +++ b/crates/tests/src/vm_host_env/tx.rs @@ -18,6 +18,7 @@ use namada_sdk::{account, token}; use namada_tx_prelude::transaction::TxSentinel; use namada_tx_prelude::{BorshSerializeExt, Ctx}; use namada_vm::WasmCacheRwAccess; +use namada_vm::host_env::gas_meter::GasMeter; use namada_vm::wasm::run::Error; use namada_vm::wasm::{self, TxCache, VpCache}; use namada_vp_prelude::key::common; @@ -52,7 +53,7 @@ pub struct TestTxEnv { pub state: TestState, pub iterators: PrefixIterators<'static, MockDB>, pub verifiers: BTreeSet
, - pub gas_meter: RefCell, + pub gas_meter: RefCell>, pub sentinel: RefCell, pub tx_index: TxIndex, pub result_buffer: Option>, @@ -63,8 +64,8 @@ pub struct TestTxEnv { pub tx_cache_dir: TempDir, pub batched_tx: BatchedTx, pub wasmer_store: Rc>, - pub gas_global: Option, } + impl Default for TestTxEnv { fn default() -> Self { let (vp_wasm_cache, vp_cache_dir) = @@ -77,18 +78,17 @@ impl Default for TestTxEnv { tx.push_default_inner_tx(); let batched_tx = tx.batch_first_tx(); - let (wasmer_store, gas_global) = { - let mut store = wasm::compilation_cache::common::testing::store(); - let global = - wasmer::Global::new_mut(&mut store, wasmer::Value::I64(0)); - - (Rc::new(RefCell::new(store)), Some(global)) - }; + let wasmer_store = Rc::new(RefCell::new( + wasm::compilation_cache::common::testing::store(), + )); Self { state, iterators: PrefixIterators::default(), - gas_meter: RefCell::new(TxGasMeter::new(1_000_000_000_000, 1)), + gas_meter: RefCell::new(GasMeter::Native(TxGasMeter::new( + 1_000_000_000_000, + 1, + ))), sentinel: RefCell::new(TxSentinel::default()), tx_index: TxIndex::default(), verifiers: BTreeSet::default(), @@ -100,7 +100,6 @@ impl Default for TestTxEnv { tx_cache_dir, batched_tx, wasmer_store, - gas_global, } } } @@ -232,9 +231,17 @@ impl TestTxEnv { /// Apply the tx changes to the write log. pub fn execute_tx(&mut self) -> Result<(), Error> { - wasm::run::tx( + let gas_meter = RefCell::new( + if let GasMeter::Native(meter) = &mut *self.gas_meter.borrow_mut() { + std::mem::take(meter) + } else { + unreachable!() + }, + ); + + let res = wasm::run::tx( &mut self.state, - &self.gas_meter, + &gas_meter, None, &self.tx_index, &self.batched_tx.tx, @@ -243,7 +250,11 @@ impl TestTxEnv { &mut self.tx_wasm_cache, wasm::run::GasMeterKind::MutGlobal, ) - .and(Ok(())) + .and(Ok(())); + + *self.gas_meter.borrow_mut() = GasMeter::Native(gas_meter.take()); + + res } } @@ -363,7 +374,6 @@ mod native_tx_host_env { tx_cache_dir: _, batched_tx, wasmer_store: _, - gas_global, }: &mut TestTxEnv| { let mut tx_env = namada_vm::host_env::testing::tx_env( @@ -379,7 +389,6 @@ mod native_tx_host_env { yielded_value, vp_wasm_cache, tx_wasm_cache, - gas_global, ); // Call the `host_env` function and unwrap any @@ -410,7 +419,6 @@ mod native_tx_host_env { tx_cache_dir: _, batched_tx, wasmer_store: _, - gas_global, }: &mut TestTxEnv| { let mut tx_env = namada_vm::host_env::testing::tx_env( @@ -426,7 +434,6 @@ mod native_tx_host_env { yielded_value, vp_wasm_cache, tx_wasm_cache, - gas_global, ); // Call the `host_env` function and unwrap any @@ -457,7 +464,6 @@ mod native_tx_host_env { tx_cache_dir: _, batched_tx, wasmer_store: _, - gas_global, }: &mut TestTxEnv| { let mut tx_env = namada_vm::host_env::testing::tx_env( @@ -473,7 +479,6 @@ mod native_tx_host_env { yielded_value, vp_wasm_cache, tx_wasm_cache, - gas_global, ); // Call the `host_env` function @@ -779,7 +784,6 @@ mod tests { tx_cache_dir: _, batched_tx, wasmer_store, - gas_global, } = test_env; let mut tx_env = host_env::testing::tx_env_with_wasm_memory( @@ -796,7 +800,6 @@ mod tests { wasmer_store.clone(), vp_wasm_cache, tx_wasm_cache, - gas_global, ); if setup.write_to_memory { diff --git a/crates/tests/src/vm_host_env/vp.rs b/crates/tests/src/vm_host_env/vp.rs index 0be95da132b..bb0bae43338 100644 --- a/crates/tests/src/vm_host_env/vp.rs +++ b/crates/tests/src/vm_host_env/vp.rs @@ -11,6 +11,7 @@ use namada_sdk::tx::Tx; use namada_sdk::tx::data::TxType; use namada_tx_prelude::BatchedTx; use namada_vm::WasmCacheRwAccess; +use namada_vm::host_env::gas_meter::GasMeter; use namada_vm::wasm::{self, VpCache}; use namada_vp_prelude::Ctx; use tempfile::TempDir; @@ -44,7 +45,7 @@ pub struct TestVpEnv { pub addr: Address, pub state: TestState, pub iterators: PrefixIterators<'static, MockDB>, - pub gas_meter: RefCell, + pub gas_meter: RefCell>, pub batched_tx: BatchedTx, pub tx_index: TxIndex, pub keys_changed: BTreeSet, @@ -72,8 +73,11 @@ impl Default for TestVpEnv { addr: address::testing::established_address_1(), state, iterators: PrefixIterators::default(), - gas_meter: RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new(1_000_000_000_000, 1), + gas_meter: RefCell::new(GasMeter::Native( + VpGasMeter::new_from_tx_meter(&TxGasMeter::new( + 1_000_000_000_000, + 1, + )), )), batched_tx, tx_index: TxIndex::default(), diff --git a/crates/vm/src/host_env.rs b/crates/vm/src/host_env.rs index 54e6840572e..8151fef7e6d 100644 --- a/crates/vm/src/host_env.rs +++ b/crates/vm/src/host_env.rs @@ -1,6 +1,7 @@ //! Virtual machine's host environment exposes functions that may be called from //! within a virtual machine. +pub mod gas_meter; pub mod state; use std::borrow::Cow; @@ -21,8 +22,8 @@ use namada_core::internal::{HostEnvResult, KeyVal}; use namada_core::storage::{Key, TX_INDEX_LENGTH, TxIndex}; use namada_events::{Event, EventTypeBuilder}; use namada_gas::{ - self as gas, Gas, GasMeterKind, GasMetering, MEMORY_ACCESS_GAS_PER_BYTE, - TxGasMeter, VpGasMeter, + self as gas, Gas, GasMetering, MEMORY_ACCESS_GAS_PER_BYTE, TxGasMeter, + VpGasMeter, }; use namada_state::prefix_iter::{PrefixIteratorId, PrefixIterators}; use namada_state::write_log::{self, WriteLog}; @@ -117,7 +118,7 @@ where /// Storage prefix iterators. pub iterators: HostRef>, /// Transaction gas meter. In `RefCell` to charge gas in read-only fns. - pub gas_meter: HostRef>, + pub gas_meter: HostRef>>, /// Transaction sentinel. In `RefCell` to charge gas in read-only fns. pub sentinel: HostRef>, /// Hash of the wrapper transaction associated with @@ -146,11 +147,6 @@ where /// To avoid unused parameter without "wasm-runtime" feature #[cfg(not(feature = "wasm-runtime"))] pub cache_access: std::marker::PhantomData, - /// WASM intructions gas meter kind - pub gas_meter_kind: GasMeterKind, - /// Global mutable gas variable, only set when gas meter kind is - /// `MutGlobal` - pub gas_global: HostRef>, } impl TxVmEnv @@ -173,7 +169,7 @@ where in_mem: &InMemory, db: &D, iterators: &mut PrefixIterators<'static, D>, - gas_meter: &RefCell, + gas_meter: &RefCell>, sentinel: &RefCell, wrapper_hash: &Hash, tx: &Tx, @@ -184,8 +180,6 @@ where yielded_value: &mut Option>, #[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache, #[cfg(feature = "wasm-runtime")] tx_wasm_cache: &mut TxCache, - gas_meter_kind: GasMeterKind, - gas_global: &Option, ) -> Self { let write_log = unsafe { RwHostRef::new(write_log) }; let in_mem = unsafe { RoHostRef::new(in_mem) }; @@ -204,7 +198,6 @@ where let vp_wasm_cache = unsafe { RwHostRef::new(vp_wasm_cache) }; #[cfg(feature = "wasm-runtime")] let tx_wasm_cache = unsafe { RwHostRef::new(tx_wasm_cache) }; - let gas_global = unsafe { RoHostRef::new(gas_global) }; let ctx = TxCtx { write_log, db, @@ -225,8 +218,6 @@ where tx_wasm_cache, #[cfg(not(feature = "wasm-runtime"))] cache_access: std::marker::PhantomData, - gas_meter_kind, - gas_global, }; Self { memory, ctx } @@ -278,7 +269,10 @@ where /// Use gas meter and sentinel pub fn gas_meter_and_sentinel( &self, - ) -> (&RefCell, &RefCell) { + ) -> ( + &RefCell>, + &RefCell, + ) { let gas_meter = unsafe { self.gas_meter.get() }; let sentinel = unsafe { self.sentinel.get() }; (gas_meter, sentinel) @@ -312,8 +306,6 @@ where tx_wasm_cache: self.tx_wasm_cache, #[cfg(not(feature = "wasm-runtime"))] cache_access: std::marker::PhantomData, - gas_meter_kind: self.gas_meter_kind, - gas_global: self.gas_global, } } } @@ -347,7 +339,7 @@ where /// Storage prefix iterators. pub iterators: HostRef>, /// VP gas meter. In `RefCell` to charge gas in read-only fns. - pub gas_meter: HostRef>, + pub gas_meter: HostRef>>, /// The transaction code is used for signature verification pub tx: HostRef, /// The commitments inside the transaction @@ -372,8 +364,6 @@ where /// To avoid unused parameter without "wasm-runtime" feature #[cfg(not(feature = "wasm-runtime"))] pub cache_access: std::marker::PhantomData, - /// WASM intructions gas meter kind - pub gas_meter_kind: GasMeterKind, } /// A Validity predicate runner for calls from the [`vp_eval`] function. @@ -421,7 +411,7 @@ where write_log: &WriteLog, in_mem: &InMemory, db: &D, - gas_meter: &RefCell, + gas_meter: &RefCell>, tx: &Tx, cmt: &TxCommitments, tx_index: &TxIndex, @@ -432,7 +422,6 @@ where keys_changed: &BTreeSet, eval_runner: &EVAL, #[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache, - gas_meter_kind: GasMeterKind, ) -> Self { let ctx = VpCtx::new( address, @@ -451,7 +440,6 @@ where eval_runner, #[cfg(feature = "wasm-runtime")] vp_wasm_cache, - gas_meter_kind, ); Self { memory, ctx } @@ -499,7 +487,7 @@ where write_log: &WriteLog, in_mem: &InMemory, db: &D, - gas_meter: &RefCell, + gas_meter: &RefCell>, tx: &Tx, cmt: &TxCommitments, tx_index: &TxIndex, @@ -510,7 +498,6 @@ where keys_changed: &BTreeSet, eval_runner: &EVAL, #[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache, - gas_meter_kind: GasMeterKind, ) -> Self { let address = unsafe { RoHostRef::new(address) }; let write_log = unsafe { RoHostRef::new(write_log) }; @@ -547,7 +534,6 @@ where vp_wasm_cache, #[cfg(not(feature = "wasm-runtime"))] cache_access: std::marker::PhantomData, - gas_meter_kind, } } @@ -566,7 +552,7 @@ where } /// Use gas meter - pub fn gas_meter(&self) -> &RefCell { + pub fn gas_meter(&self) -> &RefCell> { let gas_meter = unsafe { self.gas_meter.get() }; gas_meter } @@ -599,7 +585,6 @@ where vp_wasm_cache: self.vp_wasm_cache, #[cfg(not(feature = "wasm-runtime"))] cache_access: std::marker::PhantomData, - gas_meter_kind: self.gas_meter_kind, } } } @@ -647,8 +632,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let (key, gas) = env .memory .read_string(key_ptr, key_len.try_into()?) @@ -681,8 +664,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let (key, gas) = env .memory .read_string(key_ptr, key_len.try_into()?) @@ -726,8 +707,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let (key, gas) = env .memory .read_string(key_ptr, key_len.try_into()?) @@ -773,8 +752,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let result_buffer = unsafe { env.ctx.result_buffer.get_mut() }; let value = result_buffer .take() @@ -800,8 +777,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let (prefix, gas) = env .memory .read_string(prefix_ptr, prefix_len.try_into()?) @@ -840,8 +815,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - tracing::debug!("tx_iter_next iter_id {}", iter_id,); let iterators = unsafe { env.ctx.iterators.get_mut() }; @@ -907,8 +880,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let (key, gas) = env .memory .read_string(key_ptr, key_len.try_into()?) @@ -949,8 +920,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let (key, gas) = env .memory .read_string(key_ptr, key_len.try_into()?) @@ -1032,8 +1001,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let (key, gas) = env .memory .read_string(key_ptr, key_len.try_into()?) @@ -1064,8 +1031,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let (event, gas) = env .memory .read_bytes(event_ptr, event_len.try_into()?) @@ -1101,8 +1066,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let (event_type, gas) = env .memory .read_string(event_type_ptr, event_type_len.try_into()?) @@ -1470,8 +1433,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let (addr, gas) = env .memory .read_string(addr_ptr, addr_len.try_into()?) @@ -1510,8 +1471,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let (addr, gas) = env .memory .read_string(addr_ptr, addr_len.try_into()?) @@ -1561,8 +1520,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let (code_hash, gas) = env .memory .read_bytes(code_hash_ptr, code_hash_len.try_into()?) @@ -1613,8 +1570,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let state = env.state(); let (chain_id, gas) = state.in_mem().get_chain_id(); consume_tx_gas::(env, gas)?; @@ -1637,8 +1592,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let state = env.state(); let (height, gas) = state.in_mem().get_block_height(); consume_tx_gas::(env, gas)?; @@ -1657,8 +1610,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - consume_tx_gas::( env, (TX_INDEX_LENGTH as u64) @@ -1701,8 +1652,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let state = env.state(); let (epoch, gas) = state.in_mem().get_current_epoch(); consume_tx_gas::(env, gas)?; @@ -1719,8 +1668,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let state = env.state(); let pred_epochs = state.in_mem().block.pred_epochs.clone(); let bytes = pred_epochs.serialize_to_vec(); @@ -1749,8 +1696,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - // Gas for getting the native token address from storage consume_tx_gas::( env, @@ -1780,8 +1725,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let state = env.state(); let (header, gas) = StateRead::get_block_header(&state, Some(BlockHeight(height)))?; @@ -2057,8 +2000,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let (str, _gas) = env .memory .read_string(str_ptr, str_len.try_into()?) @@ -2079,8 +2020,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let code_hash = Hash::try_from(code_hash) .map_err(|e| TxRuntimeError::InvalidVpCodeHash(e.to_string()))?; @@ -2156,8 +2095,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let (hash_list, gas) = env .memory .read_bytes(hash_list_ptr, hash_list_len.try_into()?) @@ -2204,8 +2141,6 @@ where } .into_storage_result(); - tx_sync_gas_into_wasm(env)?; - result } @@ -2221,8 +2156,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let (serialized_transaction, gas) = env .memory .read_bytes(transaction_ptr, transaction_len.try_into()?) @@ -2258,8 +2191,6 @@ where H: 'static + StorageHasher, CA: WasmCacheAccess, { - tx_sync_gas_from_wasm(env)?; - let (value_to_yield, gas) = env .memory .read_bytes(buf_ptr, buf_len.try_into()?) @@ -2420,78 +2351,9 @@ where }) .into_storage_result(); - tx_sync_gas_into_wasm(env)?; - result } -/// Sync gas meter from WASM into host env -fn tx_sync_gas_from_wasm( - env: &mut TxVmEnv, -) -> Result<()> -where - MEM: VmMemory, - D: 'static + DB + for<'iter> DBIter<'iter>, - H: 'static + StorageHasher, - CA: WasmCacheAccess, -{ - if let GasMeterKind::MutGlobal = env.ctx.gas_meter_kind { - let store = env - .memory - .store() - .upgrade() - .expect("Store must be accessible while the WASM is running"); - let mut store = store.borrow_mut(); - let gas_global = unsafe { env.ctx.gas_global.get() }; - let wasm_gas = gas_global.as_ref().unwrap().get(&mut store); - #[allow(clippy::cast_sign_loss)] - if let wasmer::Value::I64(available_gas) = wasm_gas { - // intentional wrap around the value - let available_gas = available_gas as u64; - let gas_meter = unsafe { env.ctx.gas_meter.get() }; - gas_meter.borrow_mut().set_available_gas(available_gas); - } else { - return Err(Error::new_const("Unexpected WASM gas value type")); - } - } - Ok(()) -} - -/// Sync gas meter from host env back into WASM -fn tx_sync_gas_into_wasm( - env: &mut TxVmEnv, -) -> Result<()> -where - MEM: VmMemory, - D: 'static + DB + for<'iter> DBIter<'iter>, - H: 'static + StorageHasher, - CA: WasmCacheAccess, -{ - if let GasMeterKind::MutGlobal = env.ctx.gas_meter_kind { - let store = env - .memory - .store() - .upgrade() - .expect("Store must be accessible while the WASM is running"); - let mut store = store.borrow_mut(); - let gas_meter = unsafe { env.ctx.gas_meter.get() }; - - // intentional wrap around the value - #[allow(clippy::cast_possible_wrap)] - let available_gas = - u64::from(gas_meter.borrow().get_available_gas()) as i64; - - let gas_global = unsafe { env.ctx.gas_global.get() }; - - gas_global - .as_ref() - .unwrap() - .set(&mut store, wasmer::Value::I64(available_gas)) - .unwrap(); - } - Ok(()) -} - /// A helper module for testing #[cfg(feature = "testing")] pub mod testing { @@ -2507,7 +2369,7 @@ pub mod testing { state: &mut S, iterators: &mut PrefixIterators<'static, ::D>, verifiers: &mut BTreeSet
, - gas_meter: &RefCell, + gas_meter: &RefCell>, sentinel: &RefCell, tx: &Tx, cmt: &TxCommitments, @@ -2516,7 +2378,6 @@ pub mod testing { yielded_value: &mut Option>, #[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache, #[cfg(feature = "wasm-runtime")] tx_wasm_cache: &mut TxCache, - gas_global: &Option, ) -> TxVmEnv::D, ::H, CA> where S: State, @@ -2542,8 +2403,6 @@ pub mod testing { vp_wasm_cache, #[cfg(feature = "wasm-runtime")] tx_wasm_cache, - GasMeterKind::MutGlobal, - gas_global, ) } @@ -2553,7 +2412,7 @@ pub mod testing { state: &mut S, iterators: &mut PrefixIterators<'static, ::D>, verifiers: &mut BTreeSet
, - gas_meter: &RefCell, + gas_meter: &RefCell>, sentinel: &RefCell, tx: &Tx, cmt: &TxCommitments, @@ -2563,7 +2422,6 @@ pub mod testing { store: Rc>, #[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache, #[cfg(feature = "wasm-runtime")] tx_wasm_cache: &mut TxCache, - gas_global: &Option, ) -> TxVmEnv::D, ::H, CA> where S: State, @@ -2595,8 +2453,6 @@ pub mod testing { vp_wasm_cache, #[cfg(feature = "wasm-runtime")] tx_wasm_cache, - GasMeterKind::MutGlobal, - gas_global, ); env.memory.init_from(&wasm_memory); @@ -2609,7 +2465,7 @@ pub mod testing { address: &Address, state: &S, iterators: &mut PrefixIterators<'static, ::D>, - gas_meter: &RefCell, + gas_meter: &RefCell>, tx: &Tx, cmt: &TxCommitments, tx_index: &TxIndex, @@ -2643,7 +2499,6 @@ pub mod testing { eval_runner, #[cfg(feature = "wasm-runtime")] vp_wasm_cache, - GasMeterKind::MutGlobal, ) } } diff --git a/crates/vm/src/host_env/gas_meter.rs b/crates/vm/src/host_env/gas_meter.rs new file mode 100644 index 00000000000..154005bcfbd --- /dev/null +++ b/crates/vm/src/host_env/gas_meter.rs @@ -0,0 +1,161 @@ +//! Gas meter used in the vm. + +use namada_gas::{Gas, GasMeterKind, GasMetering, TxGasMeter}; + +#[cfg(feature = "wasm-runtime")] +use crate::wasm::host_env::WasmGasMeter; + +/// Gas meter that stores used gas in native or wasm memory +#[derive(Debug)] +pub enum GasMeter { + /// Gas is stored in native memory + Native(N), + /// Gas is stored in wasm memory + #[cfg(feature = "wasm-runtime")] + Wasm(WasmGasMeter), +} + +impl Default for GasMeter { + fn default() -> Self { + Self::Native(N::default()) + } +} + +#[cfg(feature = "wasm-runtime")] +impl From for GasMeter { + #[inline] + fn from(meter: WasmGasMeter) -> Self { + Self::Wasm(meter) + } +} + +impl From for GasMeter { + #[inline] + fn from(meter: TxGasMeter) -> Self { + Self::Native(meter) + } +} + +impl GasMeter { + /// Return the [`GasMeterKind`] of this meter. + #[inline] + pub fn kind(&self) -> GasMeterKind { + match self { + Self::Native(_) => GasMeterKind::HostFn, + #[cfg(feature = "wasm-runtime")] + Self::Wasm(_) => GasMeterKind::MutGlobal, + } + } + + /// Get the inner wasm gas meter. + #[cfg(feature = "wasm-runtime")] + pub fn wasm(&self) -> Option<&WasmGasMeter> { + if let Self::Wasm(meter) = self { + Some(meter) + } else { + None + } + } + + /// Get the inner native gas meter. + pub fn native(&self) -> Option<&N> { + if let Self::Native(meter) = self { + Some(meter) + } else { + None + } + } +} + +#[cfg(feature = "wasm-runtime")] +impl GasMeter { + /// Create a new gas meter. + pub fn new( + kind: GasMeterKind, + native: NewNative, + wasm: NewWasm, + ) -> Self + where + NewNative: FnOnce() -> N, + NewWasm: FnOnce() -> WasmGasMeter, + { + match kind { + GasMeterKind::HostFn => Self::Native(native()), + GasMeterKind::MutGlobal => Self::Wasm(wasm()), + } + } + + /// Initialize the gas meter. + pub fn init( + &mut self, + init_native: InitNative, + init_wasm: InitWasm, + ) -> Result<(), E> + where + N: GasMetering, + InitNative: FnOnce(&mut N) -> Result<(), E>, + InitWasm: FnOnce(&mut WasmGasMeter) -> Result<(), E>, + { + match self { + Self::Native(meter) => init_native(meter), + Self::Wasm(meter) => init_wasm(meter), + } + } + + /// Flush the consumed gas to the provided meter. + #[inline] + pub fn flush_to_meter(self, native_meter: &mut N) -> namada_gas::Result<()> + where + N: GasMetering, + { + match self { + Self::Native(meter) => { + *native_meter = meter; + Ok(()) + } + Self::Wasm(meter) => meter.flush_to_meter(native_meter), + } + } +} + +impl GasMetering for GasMeter { + fn consume(&mut self, gas: Gas) -> namada_gas::Result<()> { + match self { + Self::Native(meter) => meter.consume(gas), + #[cfg(feature = "wasm-runtime")] + Self::Wasm(meter) => meter.consume(gas), + } + } + + fn get_initially_available_gas(&self) -> Gas { + match self { + Self::Native(meter) => meter.get_initially_available_gas(), + #[cfg(feature = "wasm-runtime")] + Self::Wasm(meter) => meter.get_initially_available_gas(), + } + } + + fn get_consumed_gas(&self) -> Gas { + match self { + Self::Native(meter) => meter.get_consumed_gas(), + #[cfg(feature = "wasm-runtime")] + Self::Wasm(meter) => meter.get_consumed_gas(), + } + } + + fn get_gas_limit(&self) -> Gas { + match self { + Self::Native(meter) => meter.get_gas_limit(), + #[cfg(feature = "wasm-runtime")] + Self::Wasm(meter) => meter.get_gas_limit(), + } + } + + fn get_gas_scale(&self) -> u64 { + match self { + Self::Native(meter) => meter.get_gas_scale(), + #[cfg(feature = "wasm-runtime")] + Self::Wasm(meter) => meter.get_gas_scale(), + } + } +} diff --git a/crates/vm/src/host_env/state.rs b/crates/vm/src/host_env/state.rs index 6f8a407abd1..3023489ed08 100644 --- a/crates/vm/src/host_env/state.rs +++ b/crates/vm/src/host_env/state.rs @@ -15,6 +15,8 @@ use namada_state::{ use namada_storage as storage; use namada_tx::data::TxSentinel; +use crate::host_env::gas_meter::GasMeter; + impl_storage_read!(TxHostEnvState<'_, D, H>); impl_storage_read!(VpHostEnvState<'_, D, H>); impl_storage_write!(TxHostEnvState<'_, D, H>); @@ -33,7 +35,7 @@ where /// State pub in_mem: &'a InMemory, /// Tx gas meter - pub gas_meter: &'a RefCell, + pub gas_meter: &'a RefCell>, /// Errors sentinel pub sentinel: &'a RefCell, } @@ -52,7 +54,7 @@ where /// State pub in_mem: &'a InMemory, /// VP gas meter - pub gas_meter: &'a RefCell, + pub gas_meter: &'a RefCell>, } impl StateRead for TxHostEnvState<'_, D, H> diff --git a/crates/vm/src/lib.rs b/crates/vm/src/lib.rs index f862b99f904..3e0e4aafdc8 100644 --- a/crates/vm/src/lib.rs +++ b/crates/vm/src/lib.rs @@ -100,7 +100,7 @@ pub enum RwAccess {} /// Reference to host environment data, to be used from wasm /// to implement host functions. #[derive(Debug)] -pub struct HostRef { +pub struct HostRef { data: NonNull, _access: PhantomData<*const ACCESS>, } diff --git a/crates/vm/src/memory.rs b/crates/vm/src/memory.rs index 1cb5fc1d7e8..fc08464c398 100644 --- a/crates/vm/src/memory.rs +++ b/crates/vm/src/memory.rs @@ -1,8 +1,6 @@ //! Virtual machine's memory. -use std::cell::RefCell; use std::error::Error; -use std::rc; use namada_gas::Gas; @@ -38,9 +36,6 @@ pub trait VmMemory: Clone + Send + Sync { offset: u64, string: String, ) -> Result; - - /// Return a wasmer store associated with this memory. - fn store(&self) -> rc::Weak>; } /// Helper module for VM testing @@ -103,9 +98,5 @@ pub mod testing { target.clone_from_slice(bytes); Ok(Gas::default()) } - - fn store(&self) -> rc::Weak> { - unimplemented!() - } } } diff --git a/crates/vm/src/wasm/host_env.rs b/crates/vm/src/wasm/host_env.rs index 4518096d540..755e458a09d 100644 --- a/crates/vm/src/wasm/host_env.rs +++ b/crates/vm/src/wasm/host_env.rs @@ -3,6 +3,11 @@ //! Here, we expose the host functions into wasm's //! imports, so they can be called from inside the wasm. +use std::cell::RefCell; +use std::rc; + +use namada_core::hints; +use namada_gas::{Gas, GasMetering}; use namada_state::{DB, DBIter, StorageHasher}; use wasmer::{Function, FunctionEnv, Imports}; @@ -10,6 +15,196 @@ use crate::host_env::{TxVmEnv, VpEvaluator, VpVmEnv}; use crate::wasm::memory::WasmMemory; use crate::{WasmCacheAccess, host_env}; +/// Wasm native gas meter +#[derive(Debug, Default)] +pub struct WasmGasMeter { + gas_scale: u64, + initial_gas: Gas, + tx_gas_limit: Gas, + wasm_transaction_gas_global: Option, + store: Option>>, +} + +impl WasmGasMeter { + /// Create an uninitialized wasm gas meter. + /// + /// The meter will only be initialized after a wasm instance + /// is built, populating the expected global variable that + /// will track gas usage. + pub const fn uninit() -> Self { + Self { + gas_scale: 0u64, + initial_gas: Gas::new(0u64), + tx_gas_limit: Gas::new(0u64), + wasm_transaction_gas_global: None, + store: None, + } + } + + /// Initialize the wasm gas meter. + pub fn init_from( + &mut self, + meter: &impl GasMetering, + gas_global: wasmer::Global, + store: rc::Weak>, + ) { + self.gas_scale = meter.get_gas_scale(); + self.tx_gas_limit = meter.get_gas_limit(); + self.initial_gas = meter.get_available_gas(); + self.wasm_transaction_gas_global = Some(gas_global); + self.store = Some(store); + + self.write_wasm_gas(self.initial_gas.clone(), None); + } + + /// Return the gas consumed while executing wasm code. + pub fn wasm_used_gas(&self) -> namada_gas::Result { + // initial := limit - + // variable := initial - wasm + // wasm = initial - variable + + let current_tx_gas = self.read_wasm_gas(None); + + if current_tx_gas == u64::MAX.into() { + return Err(namada_gas::Error::TransactionGasExceededError( + self.tx_gas_limit.get_whole_gas_units(self.gas_scale), + )); + } + + self.initial_gas + .checked_sub(current_tx_gas) + .ok_or(namada_gas::Error::GasOverflow) + } + + /// Flush the consumed gas to the provided meter. + #[inline] + pub fn flush_to_meter( + self, + meter: &mut impl GasMetering, + ) -> namada_gas::Result<()> { + // only increment meter by the gas consumption in wasm + meter.consume(self.wasm_used_gas()?) + } + + /// Return the gas initially available when this meter was first + /// initialized. + #[inline] + pub fn initial_gas(&self) -> Gas { + self.initial_gas.clone() + } + + fn write_wasm_gas( + &self, + gas: Gas, + store: Option>>, + ) { + let store = store.unwrap_or_else(|| { + self.store + .as_ref() + .expect("the wasm store must be set while running the vm") + .upgrade() + .expect("store must be accessible while the WASM is running") + }); + + #[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)] + let value_to_sync = u64::from(gas) as i64; + + self.wasm_transaction_gas_global + .as_ref() + .expect("the wasm gas global must be set while running the vm") + .set(&mut *store.borrow_mut(), wasmer::Value::I64(value_to_sync)) + .expect("setting the wasm global gas value shouldn't fail"); + } + + fn read_wasm_gas( + &self, + store: Option>>, + ) -> Gas { + let store = store.unwrap_or_else(|| { + self.store + .as_ref() + .expect("the wasm store must be set while running the vm") + .upgrade() + .expect("store must be accessible while the WASM is running") + }); + + let current_tx_gas = if let wasmer::Value::I64(available_gas) = self + .wasm_transaction_gas_global + .as_ref() + .expect("the wasm gas global must be set while running the vm") + .get(&mut *store.borrow_mut()) + { + #[allow(clippy::cast_sign_loss)] + { + namada_gas::Gas::from( + // Intentianally wrap around the value. The global + // should be interpreted as a u64. + available_gas as u64, + ) + } + } else { + unreachable!("unexpected wasm gas value type") + }; + + debug_assert!( + self.tx_gas_limit >= current_tx_gas + || current_tx_gas == Gas::new(u64::MAX), + "tx gas in wasm of {:?} mut not be greater than gas limit of {:?}", + current_tx_gas, + self.tx_gas_limit, + ); + + current_tx_gas + } +} + +impl GasMetering for WasmGasMeter { + fn consume(&mut self, gas: Gas) -> namada_gas::Result<()> { + let store = self + .store + .as_ref() + .expect("the wasm store must be set while running the vm") + .upgrade() + .expect("store must be accessible while the WASM is running"); + + let current_tx_gas = self.read_wasm_gas(Some(rc::Rc::clone(&store))); + + let current_tx_gas = + current_tx_gas.checked_sub(gas).ok_or_else(|| { + hints::cold(); + namada_gas::Error::TransactionGasExceededError( + self.tx_gas_limit.get_whole_gas_units(self.gas_scale), + ) + })?; + + self.write_wasm_gas(current_tx_gas, Some(store)); + + Ok(()) + } + + fn get_initially_available_gas(&self) -> Gas { + self.initial_gas.clone() + } + + fn get_consumed_gas(&self) -> Gas { + // total = limit - variable + + let current_tx_gas = self.read_wasm_gas(None); + + self.tx_gas_limit + .checked_sub(current_tx_gas) + .unwrap_or_default() + } + + fn get_gas_limit(&self) -> Gas { + self.tx_gas_limit.clone() + } + + fn get_gas_scale(&self) -> u64 { + self.gas_scale + } +} + /// Prepare imports (memory and host functions) exposed to the vm guest running /// transaction code #[allow(clippy::too_many_arguments)] diff --git a/crates/vm/src/wasm/memory.rs b/crates/vm/src/wasm/memory.rs index 3bad9e91a43..480ff03773b 100644 --- a/crates/vm/src/wasm/memory.rs +++ b/crates/vm/src/wasm/memory.rs @@ -406,10 +406,6 @@ impl VmMemory for WasmMemory { fn write_string(&mut self, offset: u64, string: String) -> Result { self.write_bytes(offset, string.as_bytes()) } - - fn store(&self) -> rc::Weak> { - self.store.clone() - } } /// A custom [`Tunables`] to set a WASM memory limits. diff --git a/crates/vm/src/wasm/run.rs b/crates/vm/src/wasm/run.rs index 7483a642d7f..5978dd232a2 100644 --- a/crates/vm/src/wasm/run.rs +++ b/crates/vm/src/wasm/run.rs @@ -29,15 +29,17 @@ use wasmer::{Engine, Module, NativeEngineExt, Store, Target}; use super::TxCache; use super::memory::{Limit, WasmMemory}; +use crate::host_env::gas_meter::GasMeter; use crate::host_env::{TxVmEnv, VpCtx, VpEvaluator, VpVmEnv}; use crate::types::VpInput; -use crate::wasm::host_env::{tx_imports, vp_imports}; +use crate::wasm::host_env::{WasmGasMeter, tx_imports, vp_imports}; use crate::wasm::{Cache, CacheName, VpCache, memory}; use crate::{ HostRef, RwAccess, WasmCacheAccess, WasmValidationError, validate_untrusted_wasm, }; +const GUEST_MEMORY: &str = "memory"; const TX_ENTRYPOINT: &str = "_apply_tx"; const VP_ENTRYPOINT: &str = "_validate_tx"; const MUT_GLOBAL_GAS_NAME: &str = "_namada_gas"; @@ -210,19 +212,35 @@ where let mut verifiers = BTreeSet::new(); let mut result_buffer: Option> = None; let mut yielded_value: Option> = None; - let mut gas_global: Option = None; let sentinel = RefCell::new(TxSentinel::default()); let (write_log, in_mem, db) = state.split_borrow(); const ZERO_HASH: Hash = Hash::zero(); let wrapper_hash = wrapper_hash.unwrap_or(&ZERO_HASH); + + let wasm_gas_meter = RefCell::new(GasMeter::new( + gas_meter_kind, + || { + // If gas metering is done through a host function, we + // take the provided gas meter, then restore it after + // we return from the wasm vm + TxGasMeter::default() + }, + || { + // If gas metering is done through a wasm function, we + // must provide a reference to the store, where we will + // look-up a mutable global with the gas count + WasmGasMeter::uninit() + }, + )); + let mut env = TxVmEnv::new( WasmMemory::new(Rc::downgrade(&store)), write_log, in_mem, db, &mut iterators, - gas_meter, + &wasm_gas_meter, &sentinel, wrapper_hash, tx, @@ -233,8 +251,6 @@ where &mut yielded_value, vp_wasm_cache, tx_wasm_cache, - gas_meter_kind, - &gas_global, ); // Instantiate the wasm module @@ -245,26 +261,27 @@ where .map_err(|e| Error::InstantiationError(Box::new(e)))? }; - if let GasMeterKind::MutGlobal = gas_meter_kind { - let mut store = store.borrow_mut(); - let global = instance - .exports - .get_global(MUT_GLOBAL_GAS_NAME) - .map_err(Error::MissingGasMutGlobal)?; - - #[allow(clippy::cast_possible_wrap)] - // intentional wrap around the value - let available_gas = - u64::from(gas_meter.borrow().get_available_gas()) as i64; - global - .set(&mut *store, wasmer::Value::I64(available_gas)) - .map_err(Error::RuntimeError)?; - - #[allow(unused_assignments)] - { - gas_global = Some(global.clone()); - } - } + wasm_gas_meter.borrow_mut().init( + |meter| { + *meter = gas_meter.take(); + + Ok(()) + }, + |meter| { + let global = instance + .exports + .get_global(MUT_GLOBAL_GAS_NAME) + .map_err(Error::MissingGasMutGlobal)?; + + meter.init_from( + &*gas_meter.borrow(), + global.clone(), + Rc::downgrade(&store), + ); + + Ok(()) + }, + )?; // Fetch guest's main memory let guest_memory = instance @@ -298,22 +315,29 @@ where error, })? }; - let ok = apply_tx - .call( - unsafe { &mut *RefCell::as_ptr(&*store) }, - tx_data_ptr, - tx_data_len, - ) - .map_err(|err| { - tracing::debug!("Tx WASM failed with {}", err); - match *sentinel.borrow() { - TxSentinel::None => Error::RuntimeError(err), - TxSentinel::OutOfGas => Error::GasError(err.to_string()), - TxSentinel::InvalidCommitment => { - Error::MissingSection(err.to_string()) - } + + let result = apply_tx.call( + unsafe { &mut *RefCell::as_ptr(&*store) }, + tx_data_ptr, + tx_data_len, + ); + + let wasm_gas_meter = RefCell::into_inner(wasm_gas_meter); + wasm_gas_meter + .flush_to_meter(&mut *gas_meter.borrow_mut()) + .map_err(|err| Error::GasError(err.to_string()))?; + + let ok = result.map_err(|err| { + tracing::debug!("Tx WASM failed with {}", err); + + match *sentinel.borrow() { + TxSentinel::None => Error::RuntimeError(err), + TxSentinel::OutOfGas => Error::GasError(err.to_string()), + TxSentinel::InvalidCommitment => { + Error::MissingSection(err.to_string()) } - })?; + } + })?; // NB: early drop this data to avoid memory errors _ = (instance, env); @@ -382,13 +406,20 @@ where cache_access: PhantomData, }; let BatchedTxRef { tx, cmt } = batched_tx; + + let wasm_gas_meter = RefCell::new(GasMeter::new( + gas_meter_kind, + VpGasMeter::default, + WasmGasMeter::uninit, + )); + let mut env = VpVmEnv::new( WasmMemory::new(Rc::downgrade(&store)), address, state.write_log(), state.in_mem(), state.db(), - gas_meter, + &wasm_gas_meter, tx, cmt, tx_index, @@ -399,7 +430,6 @@ where keys_changed, &eval_runner, &mut vp_wasm_cache, - gas_meter_kind, ); let yielded_value_borrow = env.ctx.yielded_value; @@ -409,9 +439,6 @@ where vp_imports(&mut *store, env.clone()) }; - #[allow(clippy::cast_possible_wrap)] // intentional wrap around the value - let available_gas = - u64::from(gas_meter.borrow().get_available_gas()) as i64; run_vp( store, module, @@ -422,14 +449,54 @@ where keys_changed, verifiers, yielded_value_borrow, - |guest_memory| env.memory.init_from(guest_memory), - available_gas, - gas_meter_kind, - ) + |instance: &wasmer::Instance, store: &Rc>| { + // Store ref to guest memory in host data structure + let guest_memory = instance + .exports + .get_memory(GUEST_MEMORY) + .map_err(Error::MissingModuleMemory)?; + + env.memory.init_from(guest_memory); + + // Initialize gas meter + wasm_gas_meter.borrow_mut().init( + |meter| { + *meter = gas_meter.take(); + + Ok(()) + }, + |meter| { + let global = instance + .exports + .get_global(MUT_GLOBAL_GAS_NAME) + .map_err(Error::MissingGasMutGlobal)?; + + meter.init_from( + &*gas_meter.borrow(), + global.clone(), + Rc::downgrade(store), + ); + + Ok(()) + }, + )?; + + Ok(guest_memory) + }, + || { + let wasm_gas_meter = wasm_gas_meter.take(); + + wasm_gas_meter + .flush_to_meter(&mut *gas_meter.borrow_mut()) + .map_err(|err| Error::GasError(err.to_string())) + }, + )?; + + Ok(()) } #[allow(clippy::too_many_arguments)] -fn run_vp( +fn run_vp( store: Rc>, module: wasmer::Module, vp_imports: wasmer::Imports, @@ -439,12 +506,15 @@ fn run_vp( keys_changed: &BTreeSet, verifiers: &BTreeSet
, yielded_value: HostRef>>, - mut init_memory_callback: F, - available_gas: i64, - gas_meter_kind: GasMeterKind, + init_ctx: Init, + finish_ctx: Fini, ) -> Result<()> where - F: FnMut(&wasmer::Memory), + Init: for<'wasm> FnOnce( + &'wasm wasmer::Instance, + &'wasm Rc>, + ) -> Result<&'wasm wasmer::Memory>, + Fini: FnOnce() -> Result<()>, { let input: VpInput<'_> = VpInput { addr: address, @@ -460,23 +530,7 @@ where .map_err(|e| Error::InstantiationError(Box::new(e)))? }; - if let GasMeterKind::MutGlobal = gas_meter_kind { - let mut store = store.borrow_mut(); - instance - .exports - .get_global(MUT_GLOBAL_GAS_NAME) - .unwrap() - .set(&mut *store, wasmer::Value::I64(available_gas)) - .unwrap(); - } - - // Fetch guest's main memory - let guest_memory = instance - .exports - .get_memory("memory") - .map_err(Error::MissingModuleMemory)?; - - init_memory_callback(guest_memory); + let guest_memory = init_ctx(&instance, &store)?; // Write the inputs in the memory exported from the wasm // module @@ -543,6 +597,9 @@ where }; downcasted_err().unwrap_or(Error::RuntimeError(rt_error)) })?; + + finish_ctx()?; + tracing::debug!( is_valid, %vp_code_hash, @@ -592,7 +649,7 @@ where CA: WasmCacheAccess, { fn eval( - ctx: &namada_vp::native_vp::Ctx<'a, S, VpCache, Self>, + native_ctx: &namada_vp::native_vp::Ctx<'a, S, VpCache, Self>, vp_code_hash: Hash, input_data: BatchedTxRef<'_>, ) -> namada_state::Result<()> { @@ -608,32 +665,74 @@ where PrefixIterators::default(); let mut result_buffer: Option> = None; let mut yielded_value: Option> = None; - let mut vp_wasm_cache = ctx.vp_wasm_cache.clone(); + let mut vp_wasm_cache = native_ctx.vp_wasm_cache.clone(); + + let wasm_gas_meter = RefCell::new(GasMeter::new( + GasMeterKind::MutGlobal, + VpGasMeter::default, + WasmGasMeter::uninit, + )); let ctx = VpCtx::new( - ctx.address, - ctx.state.write_log(), - ctx.state.in_mem(), - ctx.state.db(), - ctx.gas_meter, - ctx.tx, - ctx.cmt, - ctx.tx_index, + native_ctx.address, + native_ctx.state.write_log(), + native_ctx.state.in_mem(), + native_ctx.state.db(), + &wasm_gas_meter, + native_ctx.tx, + native_ctx.cmt, + native_ctx.tx_index, &mut iterators, - ctx.verifiers, + native_ctx.verifiers, &mut result_buffer, &mut yielded_value, - ctx.keys_changed, + native_ctx.keys_changed, &eval_runner, &mut vp_wasm_cache, - ctx.gas_meter_kind, ); + eval_runner - .eval_native_result(ctx, vp_code_hash, input_data) + .eval_native_result( + ctx, + vp_code_hash, + input_data, + |instance, store| { + wasm_gas_meter.borrow_mut().init( + |meter| { + *meter = native_ctx.gas_meter.take(); + + Ok(()) + }, + |meter| { + let global = instance + .exports + .get_global(MUT_GLOBAL_GAS_NAME) + .map_err(Error::MissingGasMutGlobal)?; + + meter.init_from( + &*native_ctx.gas_meter.borrow(), + global.clone(), + Rc::downgrade(store), + ); + + Ok(()) + }, + ) + }, + || { + let wasm_gas_meter = wasm_gas_meter.take(); + + wasm_gas_meter + .flush_to_meter(&mut *native_ctx.gas_meter.borrow_mut()) + .map_err(|err| Error::GasError(err.to_string())) + }, + ) .inspect_err(|err| { tracing::warn!("VP eval from a native VP failed with: {err}"); }) - .into_storage_result() + .into_storage_result()?; + + Ok(()) } } @@ -654,14 +753,38 @@ where vp_code_hash: Hash, input_data: BatchedTxRef<'_>, ) -> HostEnvResult { - self.eval_native_result(ctx, vp_code_hash, input_data) - .map_or_else( - |err| { - tracing::warn!("VP eval error {err}"); - HostEnvResult::Fail - }, - |()| HostEnvResult::Success, - ) + let mut new_ctx = ctx.clone(); + + let wasm_gas_meter = + RefCell::new(GasMeter::Native(VpGasMeter::new_from_meter( + &*unsafe { ctx.gas_meter.get() }.borrow(), + ))); + + new_ctx.gas_meter = unsafe { crate::RoHostRef::new(&wasm_gas_meter) }; + + self.eval_native_result( + new_ctx, + vp_code_hash, + input_data, + |_instance, _store| Ok(()), + || { + let wasm_gas_meter = wasm_gas_meter.take(); + + unsafe { ctx.gas_meter.get() } + .borrow_mut() + .consume( + wasm_gas_meter.native().unwrap().get_vp_consumed_gas(), + ) + .map_err(|err| Error::GasError(err.to_string())) + }, + ) + .map_or_else( + |err| { + tracing::info!("VP eval error {err}"); + HostEnvResult::Fail + }, + |()| HostEnvResult::Success, + ) } } @@ -672,18 +795,29 @@ where CA: WasmCacheAccess + 'static, { /// Evaluate the given VP. - pub fn eval_native_result( + /// + /// Returns the gas consumed in wasm. + pub fn eval_native_result( &self, ctx: VpCtx, vp_code_hash: Hash, input_data: BatchedTxRef<'_>, - ) -> Result<()> { + init_gas_meter: Init, + fini_gas_meter: Fini, + ) -> Result<()> + where + Init: for<'wasm> FnOnce( + &'wasm wasmer::Instance, + &'wasm Rc>, + ) -> Result<()>, + Fini: FnOnce() -> Result<()>, + { let address = unsafe { ctx.address.get() }; let keys_changed = unsafe { ctx.keys_changed.get() }; let verifiers = unsafe { ctx.verifiers.get() }; let vp_wasm_cache = unsafe { ctx.vp_wasm_cache.get_mut() }; let gas_meter = unsafe { ctx.gas_meter.get() }; - let gas_meter_kind = ctx.gas_meter_kind; + let gas_meter_kind = gas_meter.borrow().kind(); // Compile the wasm module let (module, store) = fetch_or_compile( vp_wasm_cache, @@ -704,10 +838,6 @@ where vp_imports(&mut *store, env.clone()) }; - #[allow(clippy::cast_possible_wrap)] - // intentional wrap around the value - let available_gas = - u64::from(gas_meter.borrow().get_available_gas()) as i64; run_vp( store, module, @@ -718,9 +848,20 @@ where keys_changed, verifiers, yielded_value_borrow, - |guest_memory| env.memory.init_from(guest_memory), - available_gas, - gas_meter_kind, + |instance, store| { + // Store ref to guest memory in host data structure + let guest_memory = instance + .exports + .get_memory(GUEST_MEMORY) + .map_err(Error::MissingModuleMemory)?; + + env.memory.init_from(guest_memory); + + init_gas_meter(instance, store)?; + + Ok(guest_memory) + }, + fini_gas_meter, ) } } @@ -1094,24 +1235,21 @@ impl wasm_instrument::gas_metering::Backend for WasmMutGlobalGasBackend { let gas_global_idx = module.globals_space() as u32; let func_instructions = vec![ - // test if we still have gas - GetGlobal(gas_global_idx), + // test if we ran out of gas GetLocal(0), + GetGlobal(gas_global_idx), I64GeU, If(elements::BlockType::NoResult), - // we still have gas, decrement mut global - GetGlobal(gas_global_idx), - GetLocal(0), - I64Sub, - SetGlobal(gas_global_idx), // out of gas, set sentinel and abort - Else, I64Const(-1i64), SetGlobal(gas_global_idx), Unreachable, - // end if End, - // end block + // we still have gas, decrement mut global + GetGlobal(gas_global_idx), + GetLocal(0), + I64Sub, + SetGlobal(gas_global_idx), End, ]; diff --git a/crates/vp/src/vp_host_fns.rs b/crates/vp/src/vp_host_fns.rs index c55d17d4fc6..9957f42c3bf 100644 --- a/crates/vp/src/vp_host_fns.rs +++ b/crates/vp/src/vp_host_fns.rs @@ -9,9 +9,7 @@ use namada_core::chain::{BlockHeader, BlockHeight, ChainId, Epoch, Epochs}; use namada_core::hash::{HASH_LENGTH, Hash}; use namada_core::storage::{Key, TX_INDEX_LENGTH, TxIndex}; use namada_events::{Event, EventTypeBuilder}; -use namada_gas::{ - self as gas, Gas, GasMetering, MEMORY_ACCESS_GAS_PER_BYTE, VpGasMeter, -}; +use namada_gas::{self as gas, Gas, GasMetering, MEMORY_ACCESS_GAS_PER_BYTE}; use namada_tx::{BatchedTxRef, Section}; use thiserror::Error; @@ -43,7 +41,10 @@ impl From for Error { pub type EnvResult = std::result::Result; /// Add a gas cost incured in a validity predicate -pub fn add_gas(gas_meter: &RefCell, used_gas: Gas) -> Result<()> { +pub fn add_gas( + gas_meter: &RefCell, + used_gas: Gas, +) -> Result<()> { gas_meter.borrow_mut().consume(used_gas).map_err(|err| { tracing::info!("Stopping VP execution because of gas error: {}", err); Error::new(RuntimeError::OutOfGas(err)) @@ -53,7 +54,7 @@ pub fn add_gas(gas_meter: &RefCell, used_gas: Gas) -> Result<()> { /// Storage read prior state (before tx execution). It will try to read from the /// storage. pub fn read_pre( - gas_meter: &RefCell, + gas_meter: &RefCell, state: &S, key: &Key, ) -> Result>> @@ -89,7 +90,7 @@ where /// Storage read posterior state (after tx execution). It will try to read from /// the write log first and if no entry found then from the storage. pub fn read_post( - gas_meter: &RefCell, + gas_meter: &RefCell, state: &S, key: &Key, ) -> Result>> @@ -124,7 +125,7 @@ where /// Storage read temporary state (after tx execution). It will try to read from /// only the write log. pub fn read_temp( - gas_meter: &RefCell, + gas_meter: &RefCell, state: &S, key: &Key, ) -> Result>> @@ -140,7 +141,7 @@ where /// Storage `has_key` in prior state (before tx execution). It will try to read /// from the storage. pub fn has_key_pre( - gas_meter: &RefCell, + gas_meter: &RefCell, state: &S, key: &Key, ) -> Result @@ -170,7 +171,7 @@ where /// Storage `has_key` in posterior state (after tx execution). It will try to /// check the write log first and if no entry found then the storage. pub fn has_key_post( - gas_meter: &RefCell, + gas_meter: &RefCell, state: &S, key: &Key, ) -> Result @@ -199,7 +200,7 @@ where /// Getting the chain ID. pub fn get_chain_id( - gas_meter: &RefCell, + gas_meter: &RefCell, state: &S, ) -> Result where @@ -213,7 +214,7 @@ where /// Getting the block height. The height is that of the block to which the /// current transaction is being applied. pub fn get_block_height( - gas_meter: &RefCell, + gas_meter: &RefCell, state: &S, ) -> Result where @@ -226,7 +227,7 @@ where /// Getting the block header. pub fn get_block_header( - gas_meter: &RefCell, + gas_meter: &RefCell, state: &S, height: BlockHeight, ) -> Result> @@ -241,7 +242,7 @@ where /// Getting the block hash. The height is that of the block to which the /// current transaction is being applied. pub fn get_tx_code_hash( - gas_meter: &RefCell, + gas_meter: &RefCell, batched_tx: &BatchedTxRef<'_>, ) -> Result> { add_gas( @@ -262,7 +263,7 @@ pub fn get_tx_code_hash( /// Getting the block epoch. The epoch is that of the block to which the /// current transaction is being applied. pub fn get_block_epoch( - gas_meter: &RefCell, + gas_meter: &RefCell, state: &S, ) -> Result where @@ -276,7 +277,7 @@ where /// Getting the block epoch. The epoch is that of the block to which the /// current transaction is being applied. pub fn get_tx_index( - gas_meter: &RefCell, + gas_meter: &RefCell, tx_index: &TxIndex, ) -> Result { add_gas( @@ -291,7 +292,7 @@ pub fn get_tx_index( /// Getting the native token's address. pub fn get_native_token( - gas_meter: &RefCell, + gas_meter: &RefCell, state: &S, ) -> Result
where @@ -309,7 +310,7 @@ where /// Given the information about predecessor block epochs pub fn get_pred_epochs( - gas_meter: &RefCell, + gas_meter: &RefCell, state: &S, ) -> Result where @@ -325,7 +326,7 @@ where /// Query events emitted by the current transaction. pub fn get_events( - _gas_meter: &RefCell, + _gas_meter: &RefCell, state: &S, event_type: String, ) -> Result> @@ -344,7 +345,7 @@ where /// Storage prefix iterator for prior state (before tx execution), ordered by /// storage keys. It will try to get an iterator from the storage. pub fn iter_prefix_pre<'a, D>( - gas_meter: &RefCell, + gas_meter: &RefCell, // We cannot use e.g. `&'a State`, because it doesn't live long // enough - the lifetime of the `PrefixIter` must depend on the lifetime of // references to the `WriteLog` and `DB`. @@ -363,7 +364,7 @@ where /// Storage prefix iterator for posterior state (after tx execution), ordered by /// storage keys. It will try to get an iterator from the storage. pub fn iter_prefix_post<'a, D>( - gas_meter: &RefCell, + gas_meter: &RefCell, // We cannot use e.g. `&'a State`, because it doesn't live long // enough - the lifetime of the `PrefixIter` must depend on the lifetime of // references to the `WriteLog` and `DB`. @@ -381,7 +382,7 @@ where /// Get the next item in a storage prefix iterator (pre or post). pub fn iter_next( - gas_meter: &RefCell, + gas_meter: &RefCell, iter: &mut PrefixIter<'_, DB>, ) -> Result)>> where diff --git a/wasm/tx_become_validator/src/lib.rs b/wasm/tx_become_validator/src/lib.rs index 41f4191e84b..30e45d2b138 100644 --- a/wasm/tx_become_validator/src/lib.rs +++ b/wasm/tx_become_validator/src/lib.rs @@ -277,8 +277,8 @@ mod tests { /// Use the `tx_host_env` to run PoS VP fn run_pos_vp() -> TxResult { let tx_env = tx_host_env::take(); - let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &tx_env.gas_meter.borrow(), + let gas_meter = RefCell::new(VpGasMeter::new_from_meter( + &*tx_env.gas_meter.borrow(), )); let vp_env = TestNativeVpEnv::from_tx_env(tx_env, address::POS); let ctx = vp_env.ctx(&gas_meter); diff --git a/wasm/tx_bond/src/lib.rs b/wasm/tx_bond/src/lib.rs index ac04eb7a8ab..09e20d66470 100644 --- a/wasm/tx_bond/src/lib.rs +++ b/wasm/tx_bond/src/lib.rs @@ -325,8 +325,8 @@ mod tests { // Use the tx_env to run PoS VP let tx_env = tx_host_env::take(); - let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &tx_env.gas_meter.borrow(), + let gas_meter = RefCell::new(VpGasMeter::new_from_meter( + &*tx_env.gas_meter.borrow(), )); let vp_env = TestNativeVpEnv::from_tx_env(tx_env, address::POS); let ctx = vp_env.ctx(&gas_meter); diff --git a/wasm/tx_change_validator_commission/src/lib.rs b/wasm/tx_change_validator_commission/src/lib.rs index a895e200760..e2217af69a4 100644 --- a/wasm/tx_change_validator_commission/src/lib.rs +++ b/wasm/tx_change_validator_commission/src/lib.rs @@ -150,8 +150,8 @@ mod tests { // Use the tx_env to run PoS VP let tx_env = tx_host_env::take(); - let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &tx_env.gas_meter.borrow(), + let gas_meter = RefCell::new(VpGasMeter::new_from_meter( + &*tx_env.gas_meter.borrow(), )); let vp_env = TestNativeVpEnv::from_tx_env(tx_env, address::POS); let ctx = vp_env.ctx(&gas_meter); diff --git a/wasm/tx_redelegate/src/lib.rs b/wasm/tx_redelegate/src/lib.rs index 0046c44fe06..66a6fd33122 100644 --- a/wasm/tx_redelegate/src/lib.rs +++ b/wasm/tx_redelegate/src/lib.rs @@ -361,8 +361,8 @@ mod tests { // Use the tx_env to run PoS VP let tx_env = tx_host_env::take(); - let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &tx_env.gas_meter.borrow(), + let gas_meter = RefCell::new(VpGasMeter::new_from_meter( + &*tx_env.gas_meter.borrow(), )); let vp_env = TestNativeVpEnv::from_tx_env(tx_env, address::POS); let ctx = vp_env.ctx(&gas_meter); diff --git a/wasm/tx_unbond/src/lib.rs b/wasm/tx_unbond/src/lib.rs index b5f42ea100c..46ce72db413 100644 --- a/wasm/tx_unbond/src/lib.rs +++ b/wasm/tx_unbond/src/lib.rs @@ -337,8 +337,8 @@ mod tests { // Use the tx_env to run PoS VP let tx_env = tx_host_env::take(); - let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &tx_env.gas_meter.borrow(), + let gas_meter = RefCell::new(VpGasMeter::new_from_meter( + &*tx_env.gas_meter.borrow(), )); let vp_env = TestNativeVpEnv::from_tx_env(tx_env, address::POS); let ctx = vp_env.ctx(&gas_meter); diff --git a/wasm/tx_withdraw/src/lib.rs b/wasm/tx_withdraw/src/lib.rs index b64b7af857b..c0fbe8a797a 100644 --- a/wasm/tx_withdraw/src/lib.rs +++ b/wasm/tx_withdraw/src/lib.rs @@ -223,8 +223,8 @@ mod tests { // Use the tx_env to run PoS VP let tx_env = tx_host_env::take(); - let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &tx_env.gas_meter.borrow(), + let gas_meter = RefCell::new(VpGasMeter::new_from_meter( + &*tx_env.gas_meter.borrow(), )); let vp_env = TestNativeVpEnv::from_tx_env(tx_env, address::POS); let ctx = vp_env.ctx(&gas_meter); From 4c3cd6e2dae05f984b5362b10df27d1f1b30d803 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 20 Jun 2025 13:05:56 +0100 Subject: [PATCH 08/10] Exempt wasm gas guest fn from getting instrumented by stack limiter --- Cargo.lock | 22 +++++++++++----------- Cargo.toml | 2 +- crates/vm/src/wasm/run.rs | 32 +++++++++++++++++++++++++++++--- wasm/Cargo.lock | 20 ++++++++++---------- 4 files changed, 51 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a122dca8d7e..3b0f1a30951 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5491,6 +5491,15 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "nam-wasm-instrument" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f6bbeb218db27d14ff0dd937675549f32ad2e74a044aa6c613291a1a33c83fa" +dependencies = [ + "parity-wasm", +] + [[package]] name = "namada_account" version = "0.150.0" @@ -5594,6 +5603,7 @@ dependencies = [ "lazy_static", "masp_primitives", "masp_proofs", + "nam-wasm-instrument", "namada_apps_lib", "namada_node", "namada_vm", @@ -5603,7 +5613,6 @@ dependencies = [ "rand_core", "sha2 0.10.8", "tempfile", - "wasm-instrument", "wasmer", "wasmer-compiler-singlepass", ] @@ -6442,6 +6451,7 @@ dependencies = [ "byte-unit", "clru", "itertools 0.14.0", + "nam-wasm-instrument", "namada_account", "namada_core", "namada_events", @@ -6460,7 +6470,6 @@ dependencies = [ "test-log", "thiserror 2.0.11", "tracing", - "wasm-instrument", "wasmer", "wasmer-cache", "wasmer-compiler", @@ -10323,15 +10332,6 @@ dependencies = [ "leb128", ] -[[package]] -name = "wasm-instrument" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a47ecb37b9734d1085eaa5ae1a81e60801fd8c28d4cabdd8aedb982021918bc" -dependencies = [ - "parity-wasm", -] - [[package]] name = "wasmer" version = "4.4.0" diff --git a/Cargo.toml b/Cargo.toml index e207175c8de..683ba70fbd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -263,7 +263,7 @@ typed-builder = "0.20" uint = "0.10" warp = "0.3" wasmparser = "0.121" -wasm-instrument = {version = "0.4.0", features = ["sign_ext"]} +wasm-instrument = { package = "nam-wasm-instrument", version = "0.5.2", features = ["sign_ext"] } wasmer = "4.3.5" wasmer-cache = "4.3.5" wasmer-compiler = "4.3.5" diff --git a/crates/vm/src/wasm/run.rs b/crates/vm/src/wasm/run.rs index 5978dd232a2..03c0a5f7792 100644 --- a/crates/vm/src/wasm/run.rs +++ b/crates/vm/src/wasm/run.rs @@ -905,9 +905,35 @@ pub fn prepare_wasm_code>( ) .map_err(|_original_module| Error::GasMeterInjection)?, }; - let module = - wasm_instrument::inject_stack_limiter(module, WASM_STACK_LIMIT) - .map_err(|_original_module| Error::StackLimiterInjection)?; + let stack_limiter_exempt_fn_ids = { + // cannot examine imported func ids, so we skip them + let mut exempt = wasm_instrument::utils::imported_function_ids(&module); + + // when using a mutable wasm global to track gas, we want to match the + // behavior of the gas host fn not getting instrumented + if let GasMeterKind::MutGlobal = gas_meter_kind { + let total_number_of_fns_in_wasm_module: u32 = module + .functions_space() + .try_into() + .expect("the number of wasm functions should fit in 32 bits"); + + // NB: the gas wasm fn is appended to the bottom of + // the module with [`wasm_instrument::gas_metering::inject`] + let wasm_gas_fn_id = total_number_of_fns_in_wasm_module + .checked_sub(1) + .expect("there should be at least one wasm fn in the module"); + + exempt.insert(wasm_gas_fn_id); + } + + exempt + }; + let module = wasm_instrument::inject_stack_limiter( + module, + WASM_STACK_LIMIT, + &stack_limiter_exempt_fn_ids, + ) + .map_err(|_original_module| Error::StackLimiterInjection)?; elements::serialize(module).map_err(Error::SerializationError) } diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 7ecfc4ba57d..28c3cc5df69 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -4383,6 +4383,15 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "nam-wasm-instrument" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f6bbeb218db27d14ff0dd937675549f32ad2e74a044aa6c613291a1a33c83fa" +dependencies = [ + "parity-wasm", +] + [[package]] name = "namada_account" version = "0.150.0" @@ -4956,6 +4965,7 @@ version = "0.150.0" dependencies = [ "borsh", "clru", + "nam-wasm-instrument", "namada_account", "namada_core", "namada_events", @@ -4972,7 +4982,6 @@ dependencies = [ "tempfile", "thiserror 2.0.11", "tracing", - "wasm-instrument", "wasmer", "wasmer-cache", "wasmer-compiler-singlepass", @@ -8419,15 +8428,6 @@ dependencies = [ "leb128", ] -[[package]] -name = "wasm-instrument" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a47ecb37b9734d1085eaa5ae1a81e60801fd8c28d4cabdd8aedb982021918bc" -dependencies = [ - "parity-wasm", -] - [[package]] name = "wasmer" version = "4.4.0" From 52c9d3f8488600cb1b2e58864a6f7fccbfeaf437 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 4 Jul 2025 13:44:34 +0100 Subject: [PATCH 09/10] Swap default gas meter impls with placeholder unsafe fns --- crates/gas/src/lib.rs | 35 +++++++++++++++++++++++++++-- crates/tests/src/vm_host_env/tx.rs | 6 +++-- crates/vm/src/host_env/gas_meter.rs | 32 ++++++++++++++++++++------ crates/vm/src/wasm/host_env.rs | 2 +- crates/vm/src/wasm/run.rs | 30 ++++++++++++++++--------- 5 files changed, 83 insertions(+), 22 deletions(-) diff --git a/crates/gas/src/lib.rs b/crates/gas/src/lib.rs index 70df800bf9b..58d35e3e6b7 100644 --- a/crates/gas/src/lib.rs +++ b/crates/gas/src/lib.rs @@ -426,7 +426,7 @@ pub trait GasMetering { } /// Gas metering in a transaction -#[derive(Debug, Default)] +#[derive(Debug)] pub struct TxGasMeter { /// Track gas overflow gas_overflow: bool, @@ -439,7 +439,7 @@ pub struct TxGasMeter { } /// Gas metering in a validity predicate -#[derive(Debug, Default)] +#[derive(Debug)] pub struct VpGasMeter { /// Track gas overflow gas_overflow: bool, @@ -500,6 +500,21 @@ impl GasMetering for TxGasMeter { } impl TxGasMeter { + /// Return a placeholder [`TxGasMeter`]. + /// + /// ## Safety + /// + /// This should only be used as an unitialized meter. Do + /// not perform gas metering with it. + pub const unsafe fn placeholder() -> Self { + Self { + gas_overflow: false, + gas_scale: 0u64, + tx_gas_limit: Gas::new(0u64), + transaction_gas: Gas::new(0u64), + } + } + /// Initialize a new Tx gas meter. Requires a gas limit for the specific /// wrapper transaction and the protocol's gas scale pub fn new(tx_gas_limit: impl Into, gas_scale: u64) -> Self { @@ -582,6 +597,22 @@ impl GasMetering for VpGasMeter { } impl VpGasMeter { + /// Return a placeholder [`VpGasMeter`]. + /// + /// ## Safety + /// + /// This should only be used as an unitialized meter. Do + /// not perform gas metering with it. + pub const unsafe fn placeholder() -> Self { + Self { + gas_overflow: false, + gas_scale: 0u64, + tx_gas_limit: Gas::new(0u64), + prev_meter_consumed_gas: Gas::new(0u64), + current_gas: Gas::new(0u64), + } + } + /// Initialize a new VP gas meter from the [`TxGasMeter`] pub fn new_from_tx_meter(tx_gas_meter: &TxGasMeter) -> Self { Self::new_from_meter(tx_gas_meter) diff --git a/crates/tests/src/vm_host_env/tx.rs b/crates/tests/src/vm_host_env/tx.rs index 4e23ed2f858..e8a5e71fbfb 100644 --- a/crates/tests/src/vm_host_env/tx.rs +++ b/crates/tests/src/vm_host_env/tx.rs @@ -233,7 +233,7 @@ impl TestTxEnv { pub fn execute_tx(&mut self) -> Result<(), Error> { let gas_meter = RefCell::new( if let GasMeter::Native(meter) = &mut *self.gas_meter.borrow_mut() { - std::mem::take(meter) + std::mem::replace(meter, unsafe { TxGasMeter::placeholder() }) } else { unreachable!() }, @@ -252,7 +252,9 @@ impl TestTxEnv { ) .and(Ok(())); - *self.gas_meter.borrow_mut() = GasMeter::Native(gas_meter.take()); + *self.gas_meter.borrow_mut() = GasMeter::Native( + gas_meter.replace_with(|_| unsafe { TxGasMeter::placeholder() }), + ); res } diff --git a/crates/vm/src/host_env/gas_meter.rs b/crates/vm/src/host_env/gas_meter.rs index 154005bcfbd..ab55fccfc87 100644 --- a/crates/vm/src/host_env/gas_meter.rs +++ b/crates/vm/src/host_env/gas_meter.rs @@ -1,6 +1,6 @@ //! Gas meter used in the vm. -use namada_gas::{Gas, GasMeterKind, GasMetering, TxGasMeter}; +use namada_gas::{Gas, GasMeterKind, GasMetering, TxGasMeter, VpGasMeter}; #[cfg(feature = "wasm-runtime")] use crate::wasm::host_env::WasmGasMeter; @@ -15,12 +15,6 @@ pub enum GasMeter { Wasm(WasmGasMeter), } -impl Default for GasMeter { - fn default() -> Self { - Self::Native(N::default()) - } -} - #[cfg(feature = "wasm-runtime")] impl From for GasMeter { #[inline] @@ -67,6 +61,30 @@ impl GasMeter { } } +impl GasMeter { + /// Return a placeholder [`GasMeter`] for tx gas metering. + /// + /// ## Safety + /// + /// This should only be used as an unitialized meter. Do + /// not perform gas metering with it. + pub const unsafe fn tx_placeholder() -> Self { + Self::Native(unsafe { TxGasMeter::placeholder() }) + } +} + +impl GasMeter { + /// Return a placeholder [`GasMeter`] for vp gas metering. + /// + /// ## Safety + /// + /// This should only be used as an unitialized meter. Do + /// not perform gas metering with it. + pub const unsafe fn vp_placeholder() -> Self { + Self::Native(unsafe { VpGasMeter::placeholder() }) + } +} + #[cfg(feature = "wasm-runtime")] impl GasMeter { /// Create a new gas meter. diff --git a/crates/vm/src/wasm/host_env.rs b/crates/vm/src/wasm/host_env.rs index 755e458a09d..f09ee295353 100644 --- a/crates/vm/src/wasm/host_env.rs +++ b/crates/vm/src/wasm/host_env.rs @@ -16,7 +16,7 @@ use crate::wasm::memory::WasmMemory; use crate::{WasmCacheAccess, host_env}; /// Wasm native gas meter -#[derive(Debug, Default)] +#[derive(Debug)] pub struct WasmGasMeter { gas_scale: u64, initial_gas: Gas, diff --git a/crates/vm/src/wasm/run.rs b/crates/vm/src/wasm/run.rs index 03c0a5f7792..70c3ba0265a 100644 --- a/crates/vm/src/wasm/run.rs +++ b/crates/vm/src/wasm/run.rs @@ -220,11 +220,11 @@ where let wasm_gas_meter = RefCell::new(GasMeter::new( gas_meter_kind, - || { + || unsafe { // If gas metering is done through a host function, we // take the provided gas meter, then restore it after // we return from the wasm vm - TxGasMeter::default() + TxGasMeter::placeholder() }, || { // If gas metering is done through a wasm function, we @@ -263,7 +263,8 @@ where wasm_gas_meter.borrow_mut().init( |meter| { - *meter = gas_meter.take(); + *meter = gas_meter + .replace_with(|_| unsafe { TxGasMeter::placeholder() }); Ok(()) }, @@ -409,7 +410,7 @@ where let wasm_gas_meter = RefCell::new(GasMeter::new( gas_meter_kind, - VpGasMeter::default, + || unsafe { VpGasMeter::placeholder() }, WasmGasMeter::uninit, )); @@ -461,7 +462,8 @@ where // Initialize gas meter wasm_gas_meter.borrow_mut().init( |meter| { - *meter = gas_meter.take(); + *meter = gas_meter + .replace_with(|_| unsafe { VpGasMeter::placeholder() }); Ok(()) }, @@ -484,7 +486,8 @@ where Ok(guest_memory) }, || { - let wasm_gas_meter = wasm_gas_meter.take(); + let wasm_gas_meter = wasm_gas_meter + .replace_with(|_| unsafe { GasMeter::vp_placeholder() }); wasm_gas_meter .flush_to_meter(&mut *gas_meter.borrow_mut()) @@ -669,7 +672,7 @@ where let wasm_gas_meter = RefCell::new(GasMeter::new( GasMeterKind::MutGlobal, - VpGasMeter::default, + || unsafe { VpGasMeter::placeholder() }, WasmGasMeter::uninit, )); @@ -699,7 +702,10 @@ where |instance, store| { wasm_gas_meter.borrow_mut().init( |meter| { - *meter = native_ctx.gas_meter.take(); + *meter = + native_ctx.gas_meter.replace_with(|_| unsafe { + VpGasMeter::placeholder() + }); Ok(()) }, @@ -720,7 +726,10 @@ where ) }, || { - let wasm_gas_meter = wasm_gas_meter.take(); + let wasm_gas_meter = + wasm_gas_meter.replace_with(|_| unsafe { + GasMeter::vp_placeholder() + }); wasm_gas_meter .flush_to_meter(&mut *native_ctx.gas_meter.borrow_mut()) @@ -768,7 +777,8 @@ where input_data, |_instance, _store| Ok(()), || { - let wasm_gas_meter = wasm_gas_meter.take(); + let wasm_gas_meter = wasm_gas_meter + .replace_with(|_| unsafe { GasMeter::vp_placeholder() }); unsafe { ctx.gas_meter.get() } .borrow_mut() From e1e296299c3b3b0d669c625d58b25ab26fccebc0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 11 Jun 2025 12:59:45 +0200 Subject: [PATCH 10/10] Changelog for #4685 --- .changelog/unreleased/improvements/4685-faster-gas.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/4685-faster-gas.md diff --git a/.changelog/unreleased/improvements/4685-faster-gas.md b/.changelog/unreleased/improvements/4685-faster-gas.md new file mode 100644 index 00000000000..d3b3a875550 --- /dev/null +++ b/.changelog/unreleased/improvements/4685-faster-gas.md @@ -0,0 +1,2 @@ +- Perform wasm gas accounting using mutable global, to avoid frequent host/guest + vm context switches. ([\#4685](https://github.com/anoma/namada/pull/4685)) \ No newline at end of file