diff --git a/.changelog/unreleased/improvements/2838-runtime-gas-meter.md b/.changelog/unreleased/improvements/2838-runtime-gas-meter.md new file mode 100644 index 0000000000..204fb01605 --- /dev/null +++ b/.changelog/unreleased/improvements/2838-runtime-gas-meter.md @@ -0,0 +1,2 @@ +- Improved the gas metering system to track gas at runtime in wasm. + ([\#2838](https://github.com/anoma/namada/pull/2838)) \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index de68c16eb6..54f9182d01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4478,6 +4478,7 @@ dependencies = [ "borsh 1.2.1", "borsh-ext", "criterion", + "lazy_static", "masp_primitives", "namada", "namada_apps", @@ -4486,6 +4487,10 @@ dependencies = [ "rand_core 0.6.4", "sha2 0.9.9", "tempfile", + "wasm-instrument", + "wasmer", + "wasmer-compiler-singlepass", + "wasmer-engine-universal", ] [[package]] @@ -4802,6 +4807,7 @@ dependencies = [ "namada_account", "namada_core", "namada_ethereum_bridge", + "namada_gas", "namada_governance", "namada_ibc", "namada_macros", diff --git a/Cargo.toml b/Cargo.toml index 466485643c..6f04afa587 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -180,6 +180,10 @@ tracing-appender = "0.2.2" tracing-log = "0.1.2" tracing-subscriber = {version = "0.3.7", default-features = false, features = ["env-filter", "fmt"]} wasmparser = "0.107.0" +wasm-instrument = {version = "0.4.0", features = ["sign_ext"]} +wasmer = {git = "https://github.com/heliaxdev/wasmer", rev = "255054f7f58b7b4a525f2fee6b9b86422d1ca15b"} +wasmer-compiler-singlepass = { git = "https://github.com/heliaxdev/wasmer", rev = "255054f7f58b7b4a525f2fee6b9b86422d1ca15b" } +wasmer-engine-universal = { git = "https://github.com/heliaxdev/wasmer", rev = "255054f7f58b7b4a525f2fee6b9b86422d1ca15b" } winapi = "0.3.9" yansi = "0.5.1" zeroize = { version = "1.5.5", features = ["zeroize_derive"] } diff --git a/Makefile b/Makefile index 3a11c99756..ecd9658e83 100644 --- a/Makefile +++ b/Makefile @@ -109,8 +109,10 @@ check-crates: clippy-wasm = $(cargo) +$(nightly) clippy --manifest-path $(wasm)/Cargo.toml --all-targets -- -D warnings +# Need a separate command for benchmarks to prevent the "testing" feature flag from being activated clippy: - $(cargo) +$(nightly) clippy $(jobs) --all-targets -- -D warnings && \ + $(cargo) +$(nightly) clippy $(jobs) --all-targets --workspace --exclude namada_benchmarks -- -D warnings && \ + $(cargo) +$(nightly) clippy $(jobs) --all-targets --package namada_benchmarks -- -D warnings && \ make -C $(wasms) clippy && \ make -C $(wasms_for_tests) clippy && \ $(foreach wasm,$(wasm_templates),$(clippy-wasm) && ) true @@ -262,7 +264,7 @@ clean: $(cargo) clean bench: - $(cargo) bench --package namada_benchmarks + $(cargo) bench --package namada_benchmarks build-doc: $(cargo) doc --no-deps diff --git a/crates/apps/Cargo.toml b/crates/apps/Cargo.toml index b8c1f810df..e5e8dc2458 100644 --- a/crates/apps/Cargo.toml +++ b/crates/apps/Cargo.toml @@ -56,7 +56,7 @@ mainnet = [ ] # for integration tests and test utilities testing = ["namada_test_utils"] -benches = ["testing", "namada_test_utils"] +benches = ["namada_test_utils"] integration = [] jemalloc = ["rocksdb/jemalloc"] migrations = [ diff --git a/crates/apps/src/lib/bench_utils.rs b/crates/apps/src/lib/bench_utils.rs index 72ce301cb2..651e8f6761 100644 --- a/crates/apps/src/lib/bench_utils.rs +++ b/crates/apps/src/lib/bench_utils.rs @@ -573,6 +573,11 @@ impl BenchShell { .unwrap(); self.inner.commit(); + self.inner + .state + .in_mem_mut() + .set_header(get_dummy_header()) + .unwrap(); } // Commit a masp transaction and cache the tx and the changed keys for @@ -1094,4 +1099,68 @@ impl BenchShieldedCtx { }; (ctx, tx) } + + pub fn generate_shielded_action( + self, + amount: Amount, + source: TransferSource, + target: TransferTarget, + ) -> (Self, Tx) { + let (ctx, tx) = self.generate_masp_tx( + amount, + source.clone(), + TransferTarget::Address(Address::Internal(InternalAddress::Ibc)), + ); + + let token = PrefixedCoin { + denom: address::testing::nam().to_string().parse().unwrap(), + amount: amount + .to_string_native() + .split('.') + .next() + .unwrap() + .to_string() + .parse() + .unwrap(), + }; + let timeout_height = TimeoutHeight::At(IbcHeight::new(0, 100).unwrap()); + + #[allow(clippy::disallowed_methods)] + let now: namada::tendermint::Time = + DateTimeUtc::now().try_into().unwrap(); + let now: IbcTimestamp = now.into(); + let timeout_timestamp = + (now + std::time::Duration::new(3600, 0)).unwrap(); + let msg = IbcMsgTransfer { + port_id_on_a: PortId::transfer(), + chan_id_on_a: ChannelId::new(5), + packet_data: PacketData { + token, + sender: source.effective_address().to_string().into(), + receiver: target.effective_address().to_string().into(), + memo: "".parse().unwrap(), + }, + timeout_height_on_b: timeout_height, + timeout_timestamp_on_b: timeout_timestamp, + }; + + let transfer = + Transfer::deserialize(&mut tx.data().unwrap().as_slice()).unwrap(); + let masp_tx = tx + .get_section(&transfer.shielded.unwrap()) + .unwrap() + .masp_tx() + .unwrap(); + let msg = MsgTransfer { + message: msg, + transfer: Some(transfer), + }; + + let mut ibc_tx = ctx + .shell + .generate_ibc_tx(TX_IBC_WASM, msg.serialize_to_vec()); + ibc_tx.add_masp_tx_section(masp_tx); + + (ctx, ibc_tx) + } } diff --git a/crates/apps/src/lib/mod.rs b/crates/apps/src/lib/mod.rs index 9bfb5be0ef..22cb4d78d5 100644 --- a/crates/apps/src/lib/mod.rs +++ b/crates/apps/src/lib/mod.rs @@ -5,7 +5,7 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] -#[cfg(feature = "testing")] +#[cfg(feature = "benches")] pub mod bench_utils; pub mod cli; pub mod client; diff --git a/crates/apps/src/lib/node/ledger/mod.rs b/crates/apps/src/lib/node/ledger/mod.rs index 28abdc0567..3ce18adb47 100644 --- a/crates/apps/src/lib/node/ledger/mod.rs +++ b/crates/apps/src/lib/node/ledger/mod.rs @@ -102,7 +102,11 @@ impl Shell { tracing::debug!("Request InitChain"); self.init_chain( init, - #[cfg(any(test, feature = "testing"))] + #[cfg(any( + test, + feature = "testing", + feature = "benches" + ))] 1, ) .map(Response::InitChain) diff --git a/crates/apps/src/lib/node/ledger/shell/finalize_block.rs b/crates/apps/src/lib/node/ledger/shell/finalize_block.rs index 17c3100430..9d54e3702d 100644 --- a/crates/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/crates/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -767,7 +767,7 @@ mod test_finalize_block { FinalizeBlock, ProcessedTx, }; - const GAS_LIMIT_MULTIPLIER: u64 = 100_000_000; + const WRAPPER_GAS_LIMIT: u64 = 20_000; /// Make a wrapper tx and a processed tx from the wrapped tx that can be /// added to `FinalizeBlock` request. @@ -784,7 +784,7 @@ mod test_finalize_block { }, keypair.ref_to(), Epoch(0), - GAS_LIMIT_MULTIPLIER.into(), + WRAPPER_GAS_LIMIT.into(), None, )))); wrapper_tx.header.chain_id = shell.chain_id.clone(); @@ -2459,7 +2459,7 @@ mod test_finalize_block { }, keypair.ref_to(), Epoch(0), - GAS_LIMIT_MULTIPLIER.into(), + WRAPPER_GAS_LIMIT.into(), None, )))); wrapper.header.chain_id = shell.chain_id.clone(); @@ -2474,7 +2474,7 @@ mod test_finalize_block { }, keypair_2.ref_to(), Epoch(0), - GAS_LIMIT_MULTIPLIER.into(), + WRAPPER_GAS_LIMIT.into(), None, )))); new_wrapper.add_section(Section::Signature(Signature::new( @@ -2590,7 +2590,7 @@ mod test_finalize_block { }, keypair.ref_to(), Epoch(0), - GAS_LIMIT_MULTIPLIER.into(), + WRAPPER_GAS_LIMIT.into(), None, )))); unsigned_wrapper.header.chain_id = shell.chain_id.clone(); @@ -2816,7 +2816,7 @@ mod test_finalize_block { }, keypair.ref_to(), Epoch(0), - GAS_LIMIT_MULTIPLIER.into(), + WRAPPER_GAS_LIMIT.into(), None, )))); wrapper.header.chain_id = shell.chain_id.clone(); diff --git a/crates/apps/src/lib/node/ledger/shell/init_chain.rs b/crates/apps/src/lib/node/ledger/shell/init_chain.rs index 5968a99c5b..1669a5b32d 100644 --- a/crates/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/crates/apps/src/lib/node/ledger/shell/init_chain.rs @@ -87,7 +87,8 @@ where pub fn init_chain( &mut self, init: request::InitChain, - #[cfg(any(test, feature = "testing"))] _num_validators: u64, + #[cfg(any(test, feature = "testing", feature = "benches"))] + _num_validators: u64, ) -> Result { let mut response = response::InitChain::default(); let chain_id = self.state.in_mem().chain_id.as_str(); diff --git a/crates/benches/Cargo.toml b/crates/benches/Cargo.toml index a0089b0650..688b6db601 100644 --- a/crates/benches/Cargo.toml +++ b/crates/benches/Cargo.toml @@ -37,17 +37,28 @@ name = "host_env" harness = false path = "host_env.rs" +[[bench]] +name = "wasm_opcodes" +harness = false +path = "wasm_opcodes.rs" + [dependencies] +# NOTE: this crate MUST NOT import any dependency with testing features to prevent benchmarking non-production code [dev-dependencies] -namada = { path = "../namada", features = ["rand", "testing"] } +namada = { path = "../namada", features = ["rand", "benches"] } namada_apps = { path = "../apps", features = ["benches"] } masp_primitives.workspace = true borsh.workspace = true borsh-ext.workspace = true criterion = { version = "0.5", features = ["html_reports"] } +lazy_static.workspace= true prost.workspace = true rand_core.workspace = true rand.workspace = true tempfile.workspace = true sha2.workspace = true +wasm-instrument.workspace = true +wasmer-compiler-singlepass.workspace = true +wasmer-engine-universal.workspace = true +wasmer.workspace = true diff --git a/crates/benches/native_vps.rs b/crates/benches/native_vps.rs index 50c7f1490f..a8da2cda2b 100644 --- a/crates/benches/native_vps.rs +++ b/crates/benches/native_vps.rs @@ -1,10 +1,13 @@ use std::cell::RefCell; use std::collections::BTreeSet; +use std::ops::Deref; use std::rc::Rc; use std::str::FromStr; -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use masp_primitives::sapling::Node; +use masp_primitives::transaction::sighash::{signature_hash, SignableInput}; +use masp_primitives::transaction::txid::TxIdDigester; use namada::core::address::{self, Address, InternalAddress}; use namada::core::collections::HashMap; use namada::core::eth_bridge_pool::{GasFee, PendingTransfer}; @@ -42,9 +45,13 @@ use namada::ledger::pgf::PgfVp; use namada::ledger::pos::PosVP; use namada::proof_of_stake; use namada::proof_of_stake::KeySeg; -use namada::sdk::masp::verify_shielded_tx; +use namada::sdk::masp::{ + check_convert, check_output, check_spend, partial_deauthorize, + preload_verifying_keys, PVKs, +}; use namada::sdk::masp_primitives::merkle_tree::CommitmentTree; use namada::sdk::masp_primitives::transaction::Transaction; +use namada::sdk::masp_proofs::sapling::SaplingVerificationContext; use namada::state::{Epoch, StorageRead, StorageWrite, TxIndex}; use namada::token::{Amount, Transfer}; use namada::tx::{Code, Section, Tx}; @@ -306,72 +313,115 @@ fn governance(c: &mut Criterion) { // group.finish(); // } +fn prepare_ibc_tx_and_ctx(bench_name: &str) -> (BenchShieldedCtx, Tx) { + match bench_name { + "open_connection" => { + let mut shielded_ctx = BenchShieldedCtx::default(); + let _ = shielded_ctx.shell.init_ibc_client_state( + namada::core::storage::Key::from( + Address::Internal(InternalAddress::Ibc).to_db_key(), + ), + ); + let msg = MsgConnectionOpenInit { + client_id_on_a: ClientId::new("07-tendermint", 1).unwrap(), + counterparty: Counterparty::new( + ClientId::from_str("07-tendermint-1").unwrap(), + None, + CommitmentPrefix::try_from(b"ibc".to_vec()).unwrap(), + ), + version: Some(Version::default()), + delay_period: std::time::Duration::new(100, 0), + signer: defaults::albert_address().to_string().into(), + }; + let mut data = vec![]; + prost::Message::encode(&msg.to_any(), &mut data).unwrap(); + let open_connection = + shielded_ctx.shell.generate_ibc_tx(TX_IBC_WASM, data); + + (shielded_ctx, open_connection) + } + "open_channel" => { + let mut shielded_ctx = BenchShieldedCtx::default(); + let _ = shielded_ctx.shell.init_ibc_connection(); + // Channel handshake + let msg = MsgChannelOpenInit { + port_id_on_a: PortId::transfer(), + connection_hops_on_a: vec![ConnectionId::new(1)], + port_id_on_b: PortId::transfer(), + ordering: Order::Unordered, + signer: defaults::albert_address().to_string().into(), + version_proposal: ChannelVersion::new("ics20-1".to_string()), + }; + + // Avoid serializing the data again with borsh + let mut data = vec![]; + prost::Message::encode(&msg.to_any(), &mut data).unwrap(); + let open_channel = + shielded_ctx.shell.generate_ibc_tx(TX_IBC_WASM, data); + + (shielded_ctx, open_channel) + } + "outgoing_transfer" => { + let mut shielded_ctx = BenchShieldedCtx::default(); + shielded_ctx.shell.init_ibc_channel(); + shielded_ctx.shell.enable_ibc_transfer(); + let outgoing_transfer = + shielded_ctx.shell.generate_ibc_transfer_tx(); + + (shielded_ctx, outgoing_transfer) + } + "outgoing_shielded_action" => { + let mut shielded_ctx = BenchShieldedCtx::default(); + shielded_ctx.shell.init_ibc_channel(); + shielded_ctx.shell.enable_ibc_transfer(); + + let albert_payment_addr = shielded_ctx + .wallet + .find_payment_addr(ALBERT_PAYMENT_ADDRESS) + .unwrap() + .to_owned(); + let albert_spending_key = shielded_ctx + .wallet + .find_spending_key(ALBERT_SPENDING_KEY, None) + .unwrap() + .to_owned(); + // Shield some tokens for Albert + let (mut shielded_ctx, shield_tx) = shielded_ctx.generate_masp_tx( + Amount::native_whole(500), + TransferSource::Address(defaults::albert_address()), + TransferTarget::PaymentAddress(albert_payment_addr), + ); + shielded_ctx.shell.execute_tx(&shield_tx); + shielded_ctx.shell.commit_masp_tx(shield_tx); + shielded_ctx.shell.commit_block(); + shielded_ctx.generate_shielded_action( + Amount::native_whole(10), + TransferSource::ExtendedSpendingKey(albert_spending_key), + TransferTarget::Address(defaults::bertha_address()), + ) + } + _ => panic!("Unexpected bench test"), + } +} + fn ibc(c: &mut Criterion) { let mut group = c.benchmark_group("vp_ibc"); - let shell = BenchShell::default(); // NOTE: Ibc encompass a variety of different messages that can be executed, // here we only benchmark a few of those Connection handshake - let msg = MsgConnectionOpenInit { - client_id_on_a: ClientId::new("07-tendermint", 1).unwrap(), - counterparty: Counterparty::new( - ClientId::from_str("07-tendermint-1").unwrap(), - None, - CommitmentPrefix::try_from(b"ibc".to_vec()).unwrap(), - ), - version: Some(Version::default()), - delay_period: std::time::Duration::new(100, 0), - signer: defaults::albert_address().to_string().into(), - }; - let mut data = vec![]; - prost::Message::encode(&msg.to_any(), &mut data).unwrap(); - let open_connection = shell.generate_ibc_tx(TX_IBC_WASM, data); - - // Channel handshake - let msg = MsgChannelOpenInit { - port_id_on_a: PortId::transfer(), - connection_hops_on_a: vec![ConnectionId::new(1)], - port_id_on_b: PortId::transfer(), - ordering: Order::Unordered, - signer: defaults::albert_address().to_string().into(), - version_proposal: ChannelVersion::new("ics20-1".to_string()), - }; - - // Avoid serializing the data again with borsh - let mut data = vec![]; - prost::Message::encode(&msg.to_any(), &mut data).unwrap(); - let open_channel = shell.generate_ibc_tx(TX_IBC_WASM, data); - // Ibc transfer - let outgoing_transfer = shell.generate_ibc_transfer_tx(); - - for (signed_tx, bench_name) in - [open_connection, open_channel, outgoing_transfer] - .iter() - .zip(["open_connection", "open_channel", "outgoing_transfer"]) - { - let mut shell = BenchShell::default(); + for bench_name in [ + "open_connection", + "open_channel", + "outgoing_transfer", + "outgoing_shielded_action", + ] { // Initialize the state according to the target tx - match bench_name { - "open_connection" => { - let _ = shell.init_ibc_client_state( - namada::core::storage::Key::from( - Address::Internal(InternalAddress::Ibc).to_db_key(), - ), - ); - } - "open_channel" => { - let _ = shell.init_ibc_connection(); - } - "outgoing_transfer" => { - shell.init_ibc_channel(); - shell.enable_ibc_transfer(); - } - _ => panic!("Unexpected bench test"), - } + let (mut shielded_ctx, signed_tx) = prepare_ibc_tx_and_ctx(bench_name); - shell.execute_tx(signed_tx); - let (verifiers, keys_changed) = shell + shielded_ctx.shell.execute_tx(&signed_tx); + let (verifiers, keys_changed) = shielded_ctx + .shell .state .write_log() .verifiers_and_changed_keys(&BTreeSet::default()); @@ -383,14 +433,14 @@ fn ibc(c: &mut Criterion) { let ibc = Ibc { ctx: Ctx::new( &Address::Internal(InternalAddress::Ibc), - &shell.state, - signed_tx, + &shielded_ctx.shell.state, + &signed_tx, &TxIndex(0), &gas_meter, &sentinel, &keys_changed, &verifiers, - shell.vp_wasm_cache.clone(), + shielded_ctx.shell.vp_wasm_cache.clone(), ), }; @@ -398,7 +448,7 @@ fn ibc(c: &mut Criterion) { b.iter(|| { assert!( ibc.validate_tx( - signed_tx, + &signed_tx, ibc.ctx.keys_changed, ibc.ctx.verifiers, ) @@ -590,32 +640,186 @@ fn masp(c: &mut Criterion) { group.finish(); } -fn masp_verify_shielded_tx(c: &mut Criterion) { - let mut group = c.benchmark_group("vp_masp_verify_shielded_tx"); +fn masp_check_spend(c: &mut Criterion) { + let spend_vk = &preload_verifying_keys().spend_vk; + + c.bench_function("vp_masp_check_spend", |b| { + b.iter_batched_ref( + || { + let (_, signed_tx) = + setup_storage_for_masp_verification("shielded"); + + let transaction = signed_tx + .sections + .into_iter() + .filter_map(|section| match section { + Section::MaspTx(transaction) => Some(transaction), + _ => None, + }) + .collect::>() + .first() + .unwrap() + .to_owned(); + let spend = transaction + .sapling_bundle() + .unwrap() + .shielded_spends + .first() + .unwrap() + .to_owned(); + let ctx = SaplingVerificationContext::new(true); + let tx_data = transaction.deref(); + // Partially deauthorize the transparent bundle + let unauth_tx_data = partial_deauthorize(tx_data).unwrap(); + let txid_parts = unauth_tx_data.digest(TxIdDigester); + let sighash = signature_hash( + &unauth_tx_data, + &SignableInput::Shielded, + &txid_parts, + ); - for bench_name in ["shielding", "unshielding", "shielded"] { - group.bench_function(bench_name, |b| { - let (_, signed_tx) = - setup_storage_for_masp_verification(bench_name); + (ctx, spend, sighash) + }, + |(ctx, spend, sighash)| { + assert!(check_spend(spend, sighash.as_ref(), ctx, spend_vk)); + }, + BatchSize::SmallInput, + ) + }); +} - let transaction = signed_tx - .sections - .into_iter() - .filter_map(|section| match section { - Section::MaspTx(transaction) => Some(transaction), - _ => None, - }) - .collect::>() - .first() - .unwrap() - .to_owned(); - b.iter(|| { - assert!(verify_shielded_tx(&transaction)); - }) - }); - } +fn masp_check_convert(c: &mut Criterion) { + let convert_vk = &preload_verifying_keys().convert_vk; + + c.bench_function("vp_masp_check_convert", |b| { + b.iter_batched_ref( + || { + let (_, signed_tx) = + setup_storage_for_masp_verification("shielded"); + + let transaction = signed_tx + .sections + .into_iter() + .filter_map(|section| match section { + Section::MaspTx(transaction) => Some(transaction), + _ => None, + }) + .collect::>() + .first() + .unwrap() + .to_owned(); + let convert = transaction + .sapling_bundle() + .unwrap() + .shielded_converts + .first() + .unwrap() + .to_owned(); + let ctx = SaplingVerificationContext::new(true); - group.finish(); + (ctx, convert) + }, + |(ctx, convert)| { + assert!(check_convert(convert, ctx, convert_vk)); + }, + BatchSize::SmallInput, + ) + }); +} + +fn masp_check_output(c: &mut Criterion) { + let output_vk = &preload_verifying_keys().output_vk; + + c.bench_function("masp_vp_check_output", |b| { + b.iter_batched_ref( + || { + let (_, signed_tx) = + setup_storage_for_masp_verification("shielded"); + + let transaction = signed_tx + .sections + .into_iter() + .filter_map(|section| match section { + Section::MaspTx(transaction) => Some(transaction), + _ => None, + }) + .collect::>() + .first() + .unwrap() + .to_owned(); + let output = transaction + .sapling_bundle() + .unwrap() + .shielded_outputs + .first() + .unwrap() + .to_owned(); + let ctx = SaplingVerificationContext::new(true); + + (ctx, output) + }, + |(ctx, output)| { + assert!(check_output(output, ctx, output_vk)); + }, + BatchSize::SmallInput, + ) + }); +} + +fn masp_final_check(c: &mut Criterion) { + let PVKs { + spend_vk, + convert_vk, + output_vk, + } = preload_verifying_keys(); + + let (_, signed_tx) = setup_storage_for_masp_verification("shielded"); + + let transaction = signed_tx + .sections + .into_iter() + .filter_map(|section| match section { + Section::MaspTx(transaction) => Some(transaction), + _ => None, + }) + .collect::>() + .first() + .unwrap() + .to_owned(); + let sapling_bundle = transaction.sapling_bundle().unwrap(); + let mut ctx = SaplingVerificationContext::new(true); + // Partially deauthorize the transparent bundle + let unauth_tx_data = partial_deauthorize(transaction.deref()).unwrap(); + let txid_parts = unauth_tx_data.digest(TxIdDigester); + let sighash = + signature_hash(&unauth_tx_data, &SignableInput::Shielded, &txid_parts); + + // Check spends, converts and outputs before the final check + assert!(sapling_bundle.shielded_spends.iter().all(|spend| { + check_spend(spend, sighash.as_ref(), &mut ctx, spend_vk) + })); + assert!( + sapling_bundle + .shielded_converts + .iter() + .all(|convert| check_convert(convert, &mut ctx, convert_vk)) + ); + assert!( + sapling_bundle + .shielded_outputs + .iter() + .all(|output| check_output(output, &mut ctx, output_vk)) + ); + + c.bench_function("vp_masp_final_check", |b| { + b.iter(|| { + assert!(ctx.final_check( + sapling_bundle.value_balance.clone(), + sighash.as_ref(), + sapling_bundle.authorization.binding_sig + )) + }) + }); } fn pgf(c: &mut Criterion) { @@ -1091,67 +1295,19 @@ fn pos(c: &mut Criterion) { fn ibc_vp_validate_action(c: &mut Criterion) { let mut group = c.benchmark_group("vp_ibc_validate_action"); - let shell = BenchShell::default(); - - // Connection handshake - let msg = MsgConnectionOpenInit { - client_id_on_a: ClientId::new("07-tendermint", 1).unwrap(), - counterparty: Counterparty::new( - ClientId::from_str("07-tendermint-1").unwrap(), - None, - CommitmentPrefix::try_from(b"ibc".to_vec()).unwrap(), - ), - version: Some(Version::default()), - delay_period: std::time::Duration::new(100, 0), - signer: defaults::albert_address().to_string().into(), - }; - let mut data = vec![]; - prost::Message::encode(&msg.to_any(), &mut data).unwrap(); - let open_connection = shell.generate_ibc_tx(TX_IBC_WASM, data); - - // Channel handshake - let msg = MsgChannelOpenInit { - port_id_on_a: PortId::transfer(), - connection_hops_on_a: vec![ConnectionId::new(1)], - port_id_on_b: PortId::transfer(), - ordering: Order::Unordered, - signer: defaults::albert_address().to_string().into(), - version_proposal: ChannelVersion::new("ics20-1".to_string()), - }; - - // Avoid serializing the data again with borsh - let mut data = vec![]; - prost::Message::encode(&msg.to_any(), &mut data).unwrap(); - let open_channel = shell.generate_ibc_tx(TX_IBC_WASM, data); - - // Ibc transfer - let outgoing_transfer = shell.generate_ibc_transfer_tx(); - for (signed_tx, bench_name) in - [open_connection, open_channel, outgoing_transfer] - .iter() - .zip(["open_connection", "open_channel", "outgoing_transfer"]) - { - let mut shell = BenchShell::default(); - // Initialize the state according to the target tx - match bench_name { - "open_connection" => { - let _ = shell.init_ibc_client_state( - namada::core::storage::Key::from( - Address::Internal(InternalAddress::Ibc).to_db_key(), - ), - ); - } - "open_channel" => { - let _ = shell.init_ibc_connection(); - } - "outgoing_transfer" => shell.init_ibc_channel(), - _ => panic!("Unexpected bench test"), - } + for bench_name in [ + "open_connection", + "open_channel", + "outgoing_transfer", + "outgoing_shielded_action", + ] { + let (mut shielded_ctx, signed_tx) = prepare_ibc_tx_and_ctx(bench_name); - shell.execute_tx(signed_tx); + shielded_ctx.shell.execute_tx(&signed_tx); let tx_data = signed_tx.data().unwrap(); - let (verifiers, keys_changed) = shell + let (verifiers, keys_changed) = shielded_ctx + .shell .state .write_log() .verifiers_and_changed_keys(&BTreeSet::default()); @@ -1163,14 +1319,14 @@ fn ibc_vp_validate_action(c: &mut Criterion) { let ibc = Ibc { ctx: Ctx::new( &Address::Internal(InternalAddress::Ibc), - &shell.state, - signed_tx, + &shielded_ctx.shell.state, + &signed_tx, &TxIndex(0), &gas_meter, &sentinel, &keys_changed, &verifiers, - shell.vp_wasm_cache.clone(), + shielded_ctx.shell.vp_wasm_cache.clone(), ), }; let exec_ctx = PseudoExecutionContext::new(ibc.ctx.pre()); @@ -1193,67 +1349,19 @@ fn ibc_vp_validate_action(c: &mut Criterion) { fn ibc_vp_execute_action(c: &mut Criterion) { let mut group = c.benchmark_group("vp_ibc_execute_action"); - let shell = BenchShell::default(); - - // Connection handshake - let msg = MsgConnectionOpenInit { - client_id_on_a: ClientId::new("07-tendermint", 1).unwrap(), - counterparty: Counterparty::new( - ClientId::from_str("07-tendermint-1").unwrap(), - None, - CommitmentPrefix::try_from(b"ibc".to_vec()).unwrap(), - ), - version: Some(Version::default()), - delay_period: std::time::Duration::new(100, 0), - signer: defaults::albert_address().to_string().into(), - }; - let mut data = vec![]; - prost::Message::encode(&msg.to_any(), &mut data).unwrap(); - let open_connection = shell.generate_ibc_tx(TX_IBC_WASM, data); - - // Channel handshake - let msg = MsgChannelOpenInit { - port_id_on_a: PortId::transfer(), - connection_hops_on_a: vec![ConnectionId::new(1)], - port_id_on_b: PortId::transfer(), - ordering: Order::Unordered, - signer: defaults::albert_address().to_string().into(), - version_proposal: ChannelVersion::new("ics20-1".to_string()), - }; - - // Avoid serializing the data again with borsh - let mut data = vec![]; - prost::Message::encode(&msg.to_any(), &mut data).unwrap(); - let open_channel = shell.generate_ibc_tx(TX_IBC_WASM, data); - // Ibc transfer - let outgoing_transfer = shell.generate_ibc_transfer_tx(); - - for (signed_tx, bench_name) in - [open_connection, open_channel, outgoing_transfer] - .iter() - .zip(["open_connection", "open_channel", "outgoing_transfer"]) - { - let mut shell = BenchShell::default(); - // Initialize the state according to the target tx - match bench_name { - "open_connection" => { - let _ = shell.init_ibc_client_state( - namada::core::storage::Key::from( - Address::Internal(InternalAddress::Ibc).to_db_key(), - ), - ); - } - "open_channel" => { - let _ = shell.init_ibc_connection(); - } - "outgoing_transfer" => shell.init_ibc_channel(), - _ => panic!("Unexpected bench test"), - } + for bench_name in [ + "open_connection", + "open_channel", + "outgoing_transfer", + "outgoing_shielded_action", + ] { + let (mut shielded_ctx, signed_tx) = prepare_ibc_tx_and_ctx(bench_name); - shell.execute_tx(signed_tx); + shielded_ctx.shell.execute_tx(&signed_tx); let tx_data = signed_tx.data().unwrap(); - let (verifiers, keys_changed) = shell + let (verifiers, keys_changed) = shielded_ctx + .shell .state .write_log() .verifiers_and_changed_keys(&BTreeSet::default()); @@ -1265,14 +1373,14 @@ fn ibc_vp_execute_action(c: &mut Criterion) { let ibc = Ibc { ctx: Ctx::new( &Address::Internal(InternalAddress::Ibc), - &shell.state, - signed_tx, + &shielded_ctx.shell.state, + &signed_tx, &TxIndex(0), &gas_meter, &sentinel, &keys_changed, &verifiers, - shell.vp_wasm_cache.clone(), + shielded_ctx.shell.vp_wasm_cache.clone(), ), }; let exec_ctx = PseudoExecutionContext::new(ibc.ctx.pre()); @@ -1299,7 +1407,10 @@ criterion_group!( // slash_fund, ibc, masp, - masp_verify_shielded_tx, + masp_check_spend, + masp_check_convert, + masp_check_output, + masp_final_check, vp_multitoken, pgf, eth_bridge_nut, diff --git a/crates/benches/txs.rs b/crates/benches/txs.rs index 9e7cda965b..4a74673167 100644 --- a/crates/benches/txs.rs +++ b/crates/benches/txs.rs @@ -704,7 +704,7 @@ fn change_consensus_key(c: &mut Criterion) { b.iter_batched_ref( BenchShell::default, |shell| shell.execute_tx(&signed_tx), - criterion::BatchSize::LargeInput, + criterion::BatchSize::SmallInput, ) }); } @@ -734,78 +734,107 @@ fn change_validator_metadata(c: &mut Criterion) { b.iter_batched_ref( BenchShell::default, |shell| shell.execute_tx(&signed_tx), - criterion::BatchSize::LargeInput, + criterion::BatchSize::SmallInput, ) }); } fn ibc(c: &mut Criterion) { let mut group = c.benchmark_group("tx_ibc"); - let shell = BenchShell::default(); - - // Connection handshake - let msg = MsgConnectionOpenInit { - client_id_on_a: ClientId::new("07-tendermint", 1).unwrap(), - counterparty: Counterparty::new( - ClientId::from_str("07-tendermint-1").unwrap(), - None, - CommitmentPrefix::try_from(b"ibc".to_vec()).unwrap(), - ), - version: Some(Version::default()), - delay_period: std::time::Duration::new(100, 0), - signer: defaults::albert_address().to_string().into(), - }; - let mut data = vec![]; - prost::Message::encode(&msg.to_any(), &mut data).unwrap(); - let open_connection = shell.generate_ibc_tx(TX_IBC_WASM, data); - - // Channel handshake - let msg = MsgChannelOpenInit { - port_id_on_a: PortId::transfer(), - connection_hops_on_a: vec![ConnectionId::new(1)], - port_id_on_b: PortId::transfer(), - ordering: Order::Unordered, - signer: defaults::albert_address().to_string().into(), - version_proposal: ChannelVersion::new("ics20-1".to_string()), - }; - - // Avoid serializing the data again with borsh - let mut data = vec![]; - prost::Message::encode(&msg.to_any(), &mut data).unwrap(); - let open_channel = shell.generate_ibc_tx(TX_IBC_WASM, data); - - // Ibc transfer - let outgoing_transfer = shell.generate_ibc_transfer_tx(); // NOTE: Ibc encompass a variety of different messages that can be executed, // here we only benchmark a few of those - for (signed_tx, bench_name) in - [open_connection, open_channel, outgoing_transfer] - .iter() - .zip(["open_connection", "open_channel", "outgoing_transfer"]) - { + for bench_name in [ + "open_connection", + "open_channel", + "outgoing_transfer", + "outgoing_shielded_action", + ] { group.bench_function(bench_name, |b| { b.iter_batched_ref( || { - let mut shell = BenchShell::default(); + let mut shielded_ctx = BenchShieldedCtx::default(); // Initialize the state according to the target tx - match bench_name { + let (shielded_ctx, signed_tx) = match bench_name { "open_connection" => { - let _ = shell.init_ibc_client_state( + let _ = shielded_ctx.shell.init_ibc_client_state( namada::core::storage::Key::from( Address::Internal(namada::core::address::InternalAddress::Ibc).to_db_key(), ), ); + // Connection handshake + let msg = MsgConnectionOpenInit { + client_id_on_a: ClientId::new("07-tendermint", 1).unwrap(), + counterparty: Counterparty::new( + ClientId::from_str("07-tendermint-1").unwrap(), + None, + CommitmentPrefix::try_from(b"ibc".to_vec()).unwrap(), + ), + version: Some(Version::default()), + delay_period: std::time::Duration::new(100, 0), + signer: defaults::albert_address().to_string().into(), + }; + let mut data = vec![]; + prost::Message::encode(&msg.to_any(), &mut data).unwrap(); + let open_connection = shielded_ctx.shell.generate_ibc_tx(TX_IBC_WASM, data); + (shielded_ctx, open_connection) } "open_channel" => { - let _ = shell.init_ibc_connection(); + let _ = shielded_ctx.shell.init_ibc_connection(); + // Channel handshake + let msg = MsgChannelOpenInit { + port_id_on_a: PortId::transfer(), + connection_hops_on_a: vec![ConnectionId::new(1)], + port_id_on_b: PortId::transfer(), + ordering: Order::Unordered, + signer: defaults::albert_address().to_string().into(), + version_proposal: ChannelVersion::new("ics20-1".to_string()), + }; + + // Avoid serializing the data again with borsh + let mut data = vec![]; + prost::Message::encode(&msg.to_any(), &mut data).unwrap(); + let open_channel = shielded_ctx.shell.generate_ibc_tx(TX_IBC_WASM, data); + (shielded_ctx, open_channel) + } + "outgoing_transfer" => { + shielded_ctx.shell.init_ibc_channel(); + let outgoing_transfer = shielded_ctx.shell.generate_ibc_transfer_tx(); + (shielded_ctx, outgoing_transfer) + } + "outgoing_shielded_action" => { + shielded_ctx.shell.init_ibc_channel(); + let albert_payment_addr = shielded_ctx + .wallet + .find_payment_addr(ALBERT_PAYMENT_ADDRESS) + .unwrap() + .to_owned(); + let albert_spending_key = shielded_ctx + .wallet + .find_spending_key(ALBERT_SPENDING_KEY, None) + .unwrap() + .to_owned(); + // Shield some tokens for Albert + let (mut shielded_ctx, shield_tx) = shielded_ctx.generate_masp_tx( + Amount::native_whole(500), + TransferSource::Address(defaults::albert_address()), + TransferTarget::PaymentAddress(albert_payment_addr), + ); + shielded_ctx.shell.execute_tx(&shield_tx); + shielded_ctx.shell.commit_masp_tx(shield_tx); + shielded_ctx.shell.commit_block(); + + shielded_ctx.generate_shielded_action( + Amount::native_whole(10), + TransferSource::ExtendedSpendingKey(albert_spending_key), + TransferTarget::Address(defaults::bertha_address()), + ) } - "outgoing_transfer" => shell.init_ibc_channel(), _ => panic!("Unexpected bench test"), - } - shell + }; + (shielded_ctx, signed_tx) }, - |shell| shell.execute_tx(signed_tx), + |(shielded_ctx, signed_tx)| shielded_ctx.shell.execute_tx(signed_tx), criterion::BatchSize::SmallInput, ) }); @@ -972,7 +1001,7 @@ fn deactivate_validator(c: &mut Criterion) { b.iter_batched_ref( BenchShell::default, |shell| shell.execute_tx(&signed_tx), - criterion::BatchSize::LargeInput, + criterion::BatchSize::SmallInput, ) }); } @@ -1012,7 +1041,7 @@ fn reactivate_validator(c: &mut Criterion) { shell }, |shell| shell.execute_tx(&signed_tx), - criterion::BatchSize::LargeInput, + criterion::BatchSize::SmallInput, ) }); } @@ -1065,7 +1094,7 @@ fn claim_rewards(c: &mut Criterion) { shell }, |shell| shell.execute_tx(signed_tx), - criterion::BatchSize::LargeInput, + criterion::BatchSize::SmallInput, ) }); } diff --git a/crates/benches/vps.rs b/crates/benches/vps.rs index 48e9229443..a1d6385490 100644 --- a/crates/benches/vps.rs +++ b/crates/benches/vps.rs @@ -27,150 +27,6 @@ use sha2::Digest; const VP_IMPLICIT_WASM: &str = "vp_implicit.wasm"; -fn vp_user(c: &mut Criterion) { - let mut group = c.benchmark_group("vp_user"); - let shell = BenchShell::default(); - let vp_code_hash: Hash = shell - .read_storage_key(&Key::wasm_hash(VP_USER_WASM)) - .unwrap(); - - let foreign_key_write = - generate_foreign_key_tx(&defaults::albert_keypair()); - - let transfer = shell.generate_tx( - TX_TRANSFER_WASM, - Transfer { - source: defaults::albert_address(), - target: defaults::bertha_address(), - token: address::testing::nam(), - amount: Amount::native_whole(1000).native_denominated(), - key: None, - shielded: None, - }, - None, - None, - vec![&defaults::albert_keypair()], - ); - - let received_transfer = shell.generate_tx( - TX_TRANSFER_WASM, - Transfer { - source: defaults::bertha_address(), - target: defaults::albert_address(), - token: address::testing::nam(), - amount: Amount::native_whole(1000).native_denominated(), - key: None, - shielded: None, - }, - None, - None, - vec![&defaults::bertha_keypair()], - ); - - let vp_validator_hash = shell - .read_storage_key(&Key::wasm_hash(VP_USER_WASM)) - .unwrap(); - let extra_section = Section::ExtraData(Code::from_hash( - vp_validator_hash, - Some(VP_USER_WASM.to_string()), - )); - let data = UpdateAccount { - addr: defaults::albert_address(), - vp_code_hash: Some(Hash( - extra_section - .hash(&mut sha2::Sha256::new()) - .finalize_reset() - .into(), - )), - public_keys: vec![defaults::albert_keypair().to_public()], - threshold: None, - }; - let vp = shell.generate_tx( - TX_UPDATE_ACCOUNT_WASM, - data, - None, - Some(vec![extra_section]), - vec![&defaults::albert_keypair()], - ); - - let vote = shell.generate_tx( - TX_VOTE_PROPOSAL_WASM, - VoteProposalData { - id: 0, - vote: ProposalVote::Yay, - voter: defaults::albert_address(), - delegations: vec![defaults::validator_address()], - }, - None, - None, - vec![&defaults::albert_keypair()], - ); - - let pos = shell.generate_tx( - TX_UNBOND_WASM, - Bond { - validator: defaults::validator_address(), - amount: Amount::native_whole(1000), - source: Some(defaults::albert_address()), - }, - None, - None, - vec![&defaults::albert_keypair()], - ); - - for (signed_tx, bench_name) in [ - foreign_key_write, - transfer, - received_transfer, - vote, - pos, - vp, - ] - .iter() - .zip([ - "foreign_key_write", - "transfer", - "received_transfer", - "governance_vote", - "pos", - "vp", - ]) { - let mut shell = BenchShell::default(); - shell.execute_tx(signed_tx); - let (verifiers, keys_changed) = shell - .state - .write_log() - .verifiers_and_changed_keys(&BTreeSet::default()); - - group.bench_function(bench_name, |b| { - b.iter(|| { - let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(u64::MAX.into()), - )); - assert!( - // NOTE: the wasm code is always in cache so we don't - // include here the cost to read and compile the vp code - run::vp( - vp_code_hash, - signed_tx, - &TxIndex(0), - &defaults::albert_address(), - &shell.state, - &gas_meter, - &keys_changed, - &verifiers, - shell.vp_wasm_cache.clone(), - ) - .unwrap(), - "VP \"{bench_name}\" bench call failed" - ); - }) - }); - } - - group.finish(); -} - fn vp_implicit(c: &mut Criterion) { let mut group = c.benchmark_group("vp_implicit"); @@ -290,11 +146,11 @@ fn vp_implicit(c: &mut Criterion) { .write_log() .verifiers_and_changed_keys(&BTreeSet::default()); + let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( + &TxGasMeter::new_from_sub_limit(u64::MAX.into()), + )); group.bench_function(bench_name, |b| { b.iter(|| { - let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(u64::MAX.into()), - )); assert!( run::vp( vp_code_hash, @@ -316,12 +172,12 @@ fn vp_implicit(c: &mut Criterion) { group.finish(); } -fn vp_validator(c: &mut Criterion) { +fn vp_user(c: &mut Criterion) { let shell = BenchShell::default(); let vp_code_hash: Hash = shell .read_storage_key(&Key::wasm_hash(VP_USER_WASM)) .unwrap(); - let mut group = c.benchmark_group("vp_validator"); + let mut group = c.benchmark_group("vp_user"); let foreign_key_write = generate_foreign_key_tx(&defaults::validator_account_keypair()); @@ -442,11 +298,13 @@ fn vp_validator(c: &mut Criterion) { .write_log() .verifiers_and_changed_keys(&BTreeSet::default()); + let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( + &TxGasMeter::new_from_sub_limit(u64::MAX.into()), + )); group.bench_function(bench_name, |b| { b.iter(|| { - let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(u64::MAX.into()), - )); + // NOTE: the wasm code is always in cache so we don't + // include here the cost to read and compile the vp code assert!( run::vp( vp_code_hash, @@ -468,5 +326,5 @@ fn vp_validator(c: &mut Criterion) { group.finish(); } -criterion_group!(allowed_vps, vp_user, vp_implicit, vp_validator,); +criterion_group!(allowed_vps, vp_user, vp_implicit); criterion_main!(allowed_vps); diff --git a/crates/benches/wasm_opcodes.rs b/crates/benches/wasm_opcodes.rs new file mode 100644 index 0000000000..b9ce3bfdb2 --- /dev/null +++ b/crates/benches/wasm_opcodes.rs @@ -0,0 +1,767 @@ +//! Module to benchmark the wasm instructions. To do so we: +//! - Generate a benchmark for an empty module to serve as a reference since +//! we expect the function call itself to represent the majority of the +//! cost +//! - All instruction (expect the empty function call) must be repeated a +//! certain amount of time because the default iteratrions of criterion +//! don't apply in this case +//! - Some operations require some other instructions to run correctly, in +//! this case we need to subtract these costs +//! - From all operations we must subtract the cost of the empy function call + +use std::fmt::Display; + +use criterion::{criterion_group, criterion_main, Criterion}; +use lazy_static::lazy_static; +use wasm_instrument::parity_wasm::elements::Instruction::*; +use wasm_instrument::parity_wasm::elements::{ + BlockType, BrTableData, SignExtInstruction, +}; +use wasmer::{imports, Instance, Module, Store, Value}; + +// Don't reduce this value too much or it will be impossible to see the +// differences in execution times between the diffent instructions +const ITERATIONS: u64 = 10_000; +const ENTRY_POINT: &str = "op"; + +lazy_static! { + static ref WASM_OPTS: Vec = vec![ + // Unreachable unconditionally traps, so no need to divide its cost by ITERATIONS because we only execute it once + Unreachable, + Nop, + Block(BlockType::NoResult), + Loop(BlockType::NoResult), + // remove the cost of i32.const and nop + If(BlockType::NoResult), + // Use 0 to exit the current block (branching in a block goes to the end of it, i.e. exits). Remove the cost of block + Br(0u32), + // Use 0 to exit the current block. Remove the cost of block and i32.const + BrIf(0u32), + // If 0 on top of the stack exit the current block. Remove the cost of block and i32.const: + BrTable(Box::new(BrTableData { + table: Box::new([1, 0]), + default: 0u32, + })), + // remove cost of call, i32.const and drop + Return, + // remove the cost of nop + Call(0u32), + // remove cost of i32.const + CallIndirect(0u32, 0u8), + // remove cost of i32.const + Drop, + // remove cost of three i32.const and a drop + Select, + // remove cost of drop + GetLocal(0u32), + // remove the cost of i32.const + SetLocal(0u32), + // remove the cost of i32.const and drop + TeeLocal(0u32), + // remove cost of drop + GetGlobal(0u32), + // remove cost of i32.const + SetGlobal(0u32), + // remove the cost of i32.const and drop + I32Load(0u32, 0u32), + // remove the cost of i32.const and drop + I64Load(0u32, 0u32), + // remove the cost of i32.const and drop + F32Load(0u32, 0u32), + // remove the cost of i32.const and drop + F64Load(0u32, 0u32), + // remove the cost of i32.const and drop + I32Load8S(0u32, 0u32), + // remove the cost of i32.const and drop + I32Load8U(0u32, 0u32), + // remove the cost of i32.const and drop + I32Load16S(0u32, 0u32), + // remove the cost of i32.const and drop + I32Load16U(0u32, 0u32), + // remove the cost of i32.const and drop + I64Load8S(0u32, 0u32), + // remove the cost of i32.const and drop + I64Load8U(0u32, 0u32), + // remove the cost of i32.const and drop + I64Load16S(0u32, 0u32), + // remove the cost of i32.const and drop + I64Load16U(0u32, 0u32), + // remove the cost of i32.const and drop + I64Load32S(0u32, 0u32), + // remove the cost of i32.const and drop + I64Load32U(0u32, 0u32), + // remove the cost of two i32.const + I32Store(0u32, 0u32), + // remove the cost of a i32.const and a i64.const + I64Store(0u32, 0u32), + // remove the cost of a i32.const and a f32.const + F32Store(0u32, 0u32), + // remove the cost of a i32.const and a f64.const + F64Store(0u32, 0u32), + // remove the cost of two i32.const + I32Store8(0u32, 0u32), + // remove the cost of two i32.const + I32Store16(0u32, 0u32), + // remove the cost of a i32.const and a i64.const + I64Store8(0u32, 0u32), + // remove the cost of a i32.const and a i64.const + I64Store16(0u32, 0u32), + // remove the cost of a i32.const and a i64.const + I64Store32(0u32, 0u32), + // remove cost of a drop + CurrentMemory(0u8), + // remove the cost of a i32.const and a drop + GrowMemory(0u8), + // remove the cost of a drop + I32Const(0i32), + // remove the cost of a drop + I64Const(0i64), + // remove the cost of a drop + F32Const(0u32), + // remove the cost of a drop + F64Const(0u64), + // remove the cost of a i32.const and a drop + I32Eqz, + // remove the cost of two i32.const and a drop + I32Eq, + // remove the cost of two i32.const and a drop + I32Ne, + // remove the cost of two i32.const and a drop + I32LtS, + // remove the cost of two i32.const and a drop + I32LtU, + // remove the cost of two i32.const and a drop + I32GtS, + // remove the cost of two i32.const and a drop + I32GtU, + // remove the cost of two i32.const and a drop + I32LeS, + // remove the cost of two i32.const and a drop + I32LeU, + // remove the cost of two i32.const and a drop + I32GeS, + // remove the cost of two i32.const and a drop + I32GeU, + // remove the cost of a i64.const and a drop + I64Eqz, + // remove the cost of two i64.const and a drop + I64Eq, + // remove the cost of two i64.const and a drop + I64Ne, + // remove the cost of two i64.const and a drop + I64LtS, + // remove the cost of two i64.const and a drop + I64LtU, + // remove the cost of two i64.const and a drop + I64GtS, + // remove the cost of two i64.const and a drop + I64GtU, + // remove the cost of two i64.const and a drop + I64LeS, + // remove the cost of two i64.const and a drop + I64LeU, + // remove the cost of two i64.const and a drop + I64GeS, + // remove the cost of two i64.const and a drop + I64GeU, + // remove the cost of two f32.const and a drop + F32Eq, + // remove the cost of two f32.const and a drop + F32Ne, + // remove the cost of two f32.const and a drop + F32Lt, + // remove the cost of two f32.const and a drop + F32Gt, + // remove the cost of two f32.const and a drop + F32Le, + // remove the cost of two f32.const and a drop + F32Ge, + // remove the cost of two f64.const and a drop + F64Eq, + // remove the cost of two f64.const and a drop + F64Ne, + // remove the cost of two f64.const and a drop + F64Lt, + // remove the cost of two f64.const and a drop + F64Gt, + // remove the cost of two f64.const and a drop + F64Le, + // remove the cost of two f64.const and a drop + F64Ge, + // remove the cost of i32.const and a drop + I32Clz, + // remove the cost of i32.const and a drop + I32Ctz, + // remove the cost of i32.const and a drop + I32Popcnt, + // remove the cost of two i32.const and a drop + I32Add, + // remove the cost of two i32.const and a drop + I32Sub, + // remove the cost of two i32.const and a drop + I32Mul, + // remove the cost of two i32.const and a drop + I32DivS, + // remove the cost of two i32.const and a drop + I32DivU, + // remove the cost of two i32.const and a drop + I32RemS, + // remove the cost of two i32.const and a drop + I32RemU, + // remove the cost of two i32.const and a drop + I32And, + // remove the cost of two i32.const and a drop + I32Or, + // remove the cost of two i32.const and a drop + I32Xor, + // remove the cost of two i32.const and a drop + I32Shl, + // remove the cost of two i32.const and a drop + I32ShrS, + // remove the cost of two i32.const and a drop + I32ShrU, + // remove the cost of two i32.const and a drop + I32Rotl, + // remove the cost of two i32.const and a drop + I32Rotr, + // remove cost of i64.const and a drop + I64Clz, + // remove cost of i64.const and a drop + I64Ctz, + // remove cost of i64.const and a drop + I64Popcnt, + // remove cost of two i64.const and a drop + I64Add, + // remove cost of two i64.const and a drop + I64Sub, + // remove cost of two i64.const and a drop + I64Mul, + // remove cost of two i64.const and a drop + I64DivS, + // remove cost of two i64.const and a drop + I64DivU, + // remove cost of two i64.const and a drop + I64RemS, + // remove cost of two i64.const and a drop + I64RemU, + // remove cost of two i64.const and a drop + I64And, + // remove cost of two i64.const and a drop + I64Or, + // remove cost of two i64.const and a drop + I64Xor, + // remove cost of two i64.const and a drop + I64Shl, + // remove cost of two i64.const and a drop + I64ShrS, + // remove cost of two i64.const and a drop + I64ShrU, + // remove cost of two i64.const and a drop + I64Rotl, + // remove cost of two i64.const and a drop + I64Rotr, + // remove cost of a f32.const and a drop + F32Abs, + // remove cost of a f32.const and a drop + F32Neg, + // remove cost of a f32.const and a drop + F32Ceil, + // remove cost of a f32.const and a drop + F32Floor, + // remove cost of a f32.const and a drop + F32Trunc, + // remove cost of a f32.const and a drop + F32Nearest, + // remove cost of a f32.const and a drop + F32Sqrt, + // remove cost of two f32.const and a drop + F32Add, + // remove cost of two f32.const and a drop + F32Sub, + // remove cost of two f32.const and a drop + F32Mul, + // remove cost of two f32.const and a drop + F32Div, + // remove cost of two f32.const and a drop + F32Min, + // remove cost of two f32.const and a drop + F32Max, + // remove cost of two f32.const and a drop + F32Copysign, + // remove cost of a f64.const and a drop + F64Abs, + // remove cost of a f64.const and a drop + F64Neg, + // remove cost of a f64.const and a drop + F64Ceil, + // remove cost of a f64.const and a drop + F64Floor, + // remove cost of a f64.const and a drop + F64Trunc, + // remove cost of a f64.const and a drop + F64Nearest, + // remove cost of a f64.const and a drop + F64Sqrt, + // remove cost of two f64.const and a drop + F64Add, + // remove cost of two f64.const and a drop + F64Sub, + // remove cost of two f64.const and a drop + F64Mul, + // remove cost of two f64.const and a drop + F64Div, + // remove cost of two f64.const and a drop + F64Min, + // remove cost of two f64.const and a drop + F64Max, + // remove cost of two f64.const and a drop + F64Copysign, + // remove the cost of a i64.const and a drop + I32WrapI64, + // remove the cost of a f32.const and a drop + I32TruncSF32, + // remove the cost of a f32.const and a drop + I32TruncUF32, + // remove the cost of a f64.const and a drop + I32TruncSF64, + // remove the cost of a f64.const and a drop + I32TruncUF64, + // remove the cost of a i32.const and a drop + I64ExtendSI32, + // remove the cost of a i32.const and a drop + I64ExtendUI32, + // remove the cost of a f32.const and a drop + I64TruncSF32, + // remove the cost of a f32.const and a drop + I64TruncUF32, + // remove the cost of a f64.const and a drop + I64TruncSF64, + // remove the cost of a f64.const and a drop + I64TruncUF64, + // remove the cost of a i32.const and a drop + F32ConvertSI32, + // remove the cost of a i32.const and a drop + F32ConvertUI32, + // remove the cost of a i64.const and a drop + F32ConvertSI64, + // remove the cost of a i64.const and a drop + F32ConvertUI64, + // remove the cost of a f64.const and a drop + F32DemoteF64, + // remove the cost of a i32.const and a drop + F64ConvertSI32, + // remove the cost of a i32.const and a drop + F64ConvertUI32, + // remove the cost of a i64.const and a drop + F64ConvertSI64, + // remove the cost of a i64.const and a drop + F64ConvertUI64, + // remove the cost of a f32.const and a drop + F64PromoteF32, + // remove the cost of a f32.const and a drop + I32ReinterpretF32, + // remove the cost of a f64.const and a drop + I64ReinterpretF64, + // remove the cost of a i32.const and a drop + F32ReinterpretI32, + // remove the cost of a i64.const and a drop + F64ReinterpretI64, + // remove the cost of a i32.load8_s, a i32.const and a drop + SignExt(SignExtInstruction::I32Extend8S), + // remove the cost of a i32.load16_s, a i32.const and a drop + SignExt(SignExtInstruction::I32Extend16S), + // remove the cost of a i64.load8_s, a i32.const and a drop + SignExt(SignExtInstruction::I64Extend8S), + // remove the cost of a i64.load16_s, a i32.cons and a drop + SignExt(SignExtInstruction::I64Extend16S), + // remove the cost of a i64.load32_s, a i32.const and a drop + SignExt(SignExtInstruction::I64Extend32S), +]; + } + +struct WatBuilder { + wat: String, + instruction: wasm_instrument::parity_wasm::elements::Instruction, +} + +impl Display for WatBuilder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!( + f, + r#" + (module + (func $f0 nop) + (func $f1 (result i32) i32.const 1 return) + (table 1 funcref) + (elem (i32.const 0) $f0) + (global $iter (mut i32) (i32.const 0)) + (memory 1) + (func (export "{ENTRY_POINT}") (param $local_var i32)"# + )?; + + for _ in 0..ITERATIONS { + writeln!(f, r#"{}"#, self.wat)?; + } + write!(f, r#"))"#) + } +} + +// Use singlepass compiler (the same one used in protocol) to prevent +// optimizations that would compile out the benchmarks since most of them are +// trivial operations +fn get_wasm_store() -> Store { + wasmer::Store::new( + &wasmer_engine_universal::Universal::new( + wasmer_compiler_singlepass::Singlepass::default(), + ) + .engine(), + ) +} + +// An empty wasm module to serve as the base reference for all the other +// instructions since the bigger part of the cost is the function call itself +fn empty_module(c: &mut Criterion) { + let module_wat = format!( + r#" + (module + (func (export "{ENTRY_POINT}") (param $local_var i32)) + ) + "#, + ); + let module = Module::new(&get_wasm_store(), module_wat).unwrap(); + let instance = Instance::new(&module, &imports! {}).unwrap(); + let function = instance.exports.get_function(ENTRY_POINT).unwrap(); + + c.bench_function("empty_module", |b| { + b.iter(|| function.call(&[Value::I32(0)]).unwrap()); + }); +} + +fn ops(c: &mut Criterion) { + let mut group = c.benchmark_group("wasm_opts"); + + for builder in bench_functions() { + let module = + Module::new(&get_wasm_store(), builder.to_string()).unwrap(); + let instance = Instance::new(&module, &imports! {}).unwrap(); + let function = instance.exports.get_function(ENTRY_POINT).unwrap(); + + group.bench_function(format!("{}", builder.instruction), |b| { + if let Unreachable = builder.instruction { + b.iter(|| function.call(&[Value::I32(0)]).unwrap_err()); + } else { + b.iter(|| function.call(&[Value::I32(0)]).unwrap()); + } + }); + } + + group.finish(); +} + +fn bench_functions() -> Vec { + let instructions = + WASM_OPTS + .clone() + .into_iter() + .map(|instruction| match instruction { + Unreachable | Nop => WatBuilder { + wat: format!(r#"{instruction}"#), + instruction, + }, + Block(_) | Loop(_) => WatBuilder { + wat: format!(r#"({instruction})"#), + instruction, + }, + If(_) => WatBuilder { + wat: format!( + r#" + i32.const 1 + ({instruction} + (then + nop + ) + )"# + ), + instruction, + }, + Br(_) => WatBuilder { + wat: format!( + r#" + (block {instruction}) + "# + ), + instruction, + }, + BrIf(_) | BrTable(_) => WatBuilder { + wat: format!( + r#" + (block + i32.const 1 + {instruction} + ) + "# + ), + instruction, + }, + Return => { + // To benchmark the return opcode we need to call a function + // that returns something and then subtract the cost of + // everything. This way we can run the return + // opcode ITERATIONS times + WatBuilder { + wat: r#" + call $f1 + drop + "# + .to_string(), + instruction, + } + } + Call(_) => WatBuilder { + wat: r#" + call $f0 + "# + .to_string(), + instruction, + }, + CallIndirect(_, _) | Drop => WatBuilder { + wat: format!( + r#" + i32.const 0 + {instruction} + "# + ), + instruction, + }, + Select => WatBuilder { + wat: format!( + r#" + i32.const 10 + i32.const 20 + i32.const 0 + {instruction} + drop + "# + ), + instruction, + }, + GetLocal(_) | GetGlobal(_) | CurrentMemory(_) | I32Const(_) + | I64Const(_) | F32Const(_) | F64Const(_) => WatBuilder { + wat: format!( + r#" + {instruction} + drop + "# + ), + instruction, + }, + SetLocal(_) | SetGlobal(_) => WatBuilder { + wat: format!( + r#" + i32.const 10 + {instruction} + "# + ), + instruction, + }, + I32Load(_, _) + | I64Load(_, _) + | F32Load(_, _) + | F64Load(_, _) + | I32Load8S(_, _) + | I32Load8U(_, _) + | I32Load16S(_, _) + | I32Load16U(_, _) + | I64Load8S(_, _) + | I64Load8U(_, _) + | I64Load16S(_, _) + | I64Load16U(_, _) + | I64Load32S(_, _) + | I64Load32U(_, _) + | TeeLocal(_) + | GrowMemory(_) => WatBuilder { + wat: format!( + r#" + i32.const 1 + {instruction} + drop + "# + ), + instruction, + }, + I32Store(_, _) + | I64Store(_, _) + | F32Store(_, _) + | F64Store(_, _) + | I32Store8(_, _) + | I32Store16(_, _) + | I64Store8(_, _) + | I64Store16(_, _) + | I64Store32(_, _) => { + let ty = match instruction { + I32Store(_, _) | I32Store8(_, _) | I32Store16(_, _) => { + "i32" + } + I64Store(_, _) + | I64Store8(_, _) + | I64Store16(_, _) + | I64Store32(_, _) => "i64", + F32Store(_, _) => "f32", + F64Store(_, _) => "f64", + _ => unreachable!(), + }; + + WatBuilder { + wat: format!( + r#" + i32.const 0 + {ty}.const 10000 + {instruction} + "# + ), + instruction, + } + } + I32Eqz | I64Eqz | I32Clz | I32Ctz | I32Popcnt | I64Clz + | I64Ctz | I64Popcnt | F32Abs | F64Abs | F32Neg | F32Ceil + | F32Floor | F32Trunc | F32Nearest | F32Sqrt | F64Neg + | F64Ceil | F64Floor | F64Trunc | F64Nearest | F64Sqrt + | I32WrapI64 | I32TruncSF32 | I32TruncUF32 | I32TruncSF64 + | I32TruncUF64 | I64ExtendSI32 | I64ExtendUI32 + | I64TruncSF32 | I64TruncUF32 | I64TruncSF64 | I64TruncUF64 + | F32ConvertSI32 | F32ConvertUI32 | F32ConvertSI64 + | F32ConvertUI64 | F32DemoteF64 | F64ConvertSI32 + | F64ConvertUI32 | F64ConvertSI64 | F64ConvertUI64 + | F64PromoteF32 | I32ReinterpretF32 | I64ReinterpretF64 + | F32ReinterpretI32 | F64ReinterpretI64 => { + let ty = + match instruction { + I32Eqz | I32Clz | I32Ctz | I32Popcnt => "i32", + I64Eqz | I32WrapI64 => "i64", + I64Clz | I64Ctz | I64Popcnt => "i64", + F32Abs | F32Neg | F32Ceil | F32Floor | F32Trunc + | F32Nearest | F32Sqrt => "f32", + F64Abs | F64Neg | F64Ceil | F64Floor | F64Trunc + | F64Nearest | F64Sqrt => "f64", + I32TruncSF32 | I32TruncUF32 | I32ReinterpretF32 => { + "f32" + } + I32TruncSF64 | I32TruncUF64 => "f64", + I64ExtendSI32 | I64ExtendUI32 => "i32", + I64TruncSF32 | I64TruncUF32 => "f32", + I64TruncSF64 | I64TruncUF64 | I64ReinterpretF64 => { + "f64" + } + F32ConvertSI32 | F32ConvertUI32 + | F32ReinterpretI32 => "i32", + F32ConvertSI64 | F32ConvertUI64 => "i64", + F32DemoteF64 => "f64", + F64ConvertSI32 | F64ConvertUI32 => "i32", + F64ConvertSI64 | F64ConvertUI64 + | F64ReinterpretI64 => "i64", + F64PromoteF32 => "f32", + _ => unreachable!(), + }; + WatBuilder { + wat: format!( + r#" + {ty}.const 1000 + {instruction} + drop + "# + ), + instruction, + } + } + I32Eq | I64Eq | F32Eq | F64Eq | I32Ne | I64Ne | F32Ne + | F64Ne | I32LtS | I64LtS | F32Lt | F64Lt | I32LtU | I32GtS + | I32GtU | I32LeS | I32LeU | I32GeS | I32GeU | I64LtU + | I64GtS | I64GtU | I64LeS | I64LeU | I64GeS | I64GeU + | F32Gt | F32Le | F32Ge | F64Gt | F64Le | F64Ge | I32Add + | I64Add | I32Sub | I64Sub | I32Mul | I32DivS | I32DivU + | I32RemS | I32RemU | I32And | I32Or | I32Xor | I32Shl + | I32ShrS | I32ShrU | I32Rotl | I32Rotr | I64Mul | I64DivS + | I64DivU | I64RemS | I64RemU | I64And | I64Or | I64Xor + | I64Shl | I64ShrS | I64ShrU | I64Rotl | I64Rotr | F32Add + | F32Sub | F32Mul | F32Div | F32Min | F32Max | F32Copysign + | F64Add | F64Sub | F64Mul | F64Div | F64Min | F64Max + | F64Copysign => { + let ty = match instruction { + I32Eq | I32Ne | I32LtS | I32LtU | I32GtS | I32GtU + | I32LeS | I32LeU | I32GeS | I32GeU | I32Add + | I32Sub | I32Mul | I32DivS | I32DivU | I32RemS + | I32RemU | I32And | I32Or | I32Xor | I32Shl + | I32ShrS | I32ShrU | I32Rotl | I32Rotr => "i32", + I64Eq | I64Ne | I64LtS | I64LtU | I64GtS | I64GtU + | I64LeS | I64LeU | I64GeS | I64GeU => "i64", + F32Eq | F32Ne | F32Lt | F32Gt | F32Le | F32Ge => "f32", + F64Eq | F64Ne | F64Lt | F64Gt | F64Le | F64Ge => "f64", + I64Add | I64Sub | I64Mul | I64DivS | I64DivU + | I64RemS | I64RemU | I64And | I64Or | I64Xor + | I64Shl | I64ShrS | I64ShrU | I64Rotl | I64Rotr => { + "i64" + } + F32Add | F32Sub | F32Mul | F32Div | F32Min | F32Max + | F32Copysign => "f32", + F64Add | F64Sub | F64Mul | F64Div | F64Min | F64Max + | F64Copysign => "f64", + _ => unreachable!(), + }; + WatBuilder { + wat: format!( + r#" + {ty}.const 2000 + {ty}.const 1000 + {instruction} + drop + "# + ), + instruction, + } + } + + SignExt(SignExtInstruction::I32Extend8S) + | SignExt(SignExtInstruction::I32Extend16S) + | SignExt(SignExtInstruction::I64Extend8S) + | SignExt(SignExtInstruction::I64Extend16S) + | SignExt(SignExtInstruction::I64Extend32S) => { + let load = match instruction { + SignExt(SignExtInstruction::I32Extend8S) => { + "i32.load8_s" + } + SignExt(SignExtInstruction::I32Extend16S) => { + "i32.load16_s" + } + SignExt(SignExtInstruction::I64Extend8S) => { + "i64.load8_s" + } + SignExt(SignExtInstruction::I64Extend16S) => { + "i64.load16_s" + } + SignExt(SignExtInstruction::I64Extend32S) => { + "i64.load32_s" + } + _ => unreachable!(), + }; + WatBuilder { + wat: format!( + r#" + i32.const 1000 + {load} + {instruction} + drop + "# + ), + instruction, + } + } + _ => { + panic!("Found an instruction not covered by the benchmarks") + } + }); + + instructions.collect() +} + +criterion_group!(wasm_opcodes, ops, empty_module); +criterion_main!(wasm_opcodes); diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 84fdb8ebb6..14f29989d8 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -29,6 +29,7 @@ migrations = [ "namada_migrations", "linkme", ] +benches = ["proptest"] [dependencies] namada_macros = {path = "../macros"} diff --git a/crates/core/src/address.rs b/crates/core/src/address.rs index 028d2ea047..6c03a428c5 100644 --- a/crates/core/src/address.rs +++ b/crates/core/src/address.rs @@ -687,7 +687,7 @@ pub fn gen_deterministic_established_address(seed: impl AsRef) -> Address { } /// Helpers for testing with addresses. -#[cfg(any(test, feature = "testing"))] +#[cfg(any(test, feature = "testing", feature = "benches"))] pub mod testing { use proptest::prelude::*; diff --git a/crates/core/src/ethereum_events.rs b/crates/core/src/ethereum_events.rs index 61d42c6f15..ec6984ac42 100644 --- a/crates/core/src/ethereum_events.rs +++ b/crates/core/src/ethereum_events.rs @@ -453,7 +453,7 @@ pub mod tests { #[allow(missing_docs)] /// Test helpers -#[cfg(any(test, feature = "testing"))] +#[cfg(any(test, feature = "testing", feature = "benches"))] pub mod testing { use proptest::prop_compose; diff --git a/crates/core/src/key/mod.rs b/crates/core/src/key/mod.rs index 23af1c05a6..fdfe0195d5 100644 --- a/crates/core/src/key/mod.rs +++ b/crates/core/src/key/mod.rs @@ -487,7 +487,7 @@ impl SignableBytes for &crate::keccak::KeccakHash { } /// Helpers for testing with keys. -#[cfg(any(test, feature = "testing"))] +#[cfg(any(test, feature = "testing", feature = "benches"))] pub mod testing { use proptest::prelude::*; use rand::prelude::{StdRng, ThreadRng}; diff --git a/crates/gas/src/lib.rs b/crates/gas/src/lib.rs index f950209b96..0d42dc95fb 100644 --- a/crates/gas/src/lib.rs +++ b/crates/gas/src/lib.rs @@ -22,47 +22,56 @@ pub enum Error { GasOverflow, } -const COMPILE_GAS_PER_BYTE: u64 = 24; +const COMPILE_GAS_PER_BYTE: u64 = 1_955; const PARALLEL_GAS_DIVIDER: u64 = 10; -const WASM_CODE_VALIDATION_GAS_PER_BYTE: u64 = 1; -const WRAPPER_TX_VALIDATION_GAS: u64 = 58_371; +const WASM_CODE_VALIDATION_GAS_PER_BYTE: u64 = 67; +const WRAPPER_TX_VALIDATION_GAS: u64 = 3_245_500; const STORAGE_OCCUPATION_GAS_PER_BYTE: u64 = - 100 + PHYSICAL_STORAGE_LATENCY_PER_BYTE; + 100_000 + PHYSICAL_STORAGE_LATENCY_PER_BYTE; // NOTE: this accounts for the latency of a physical drive access. For read // accesses we have no way to tell if data was in cache or in storage. Moreover, // the latency shouldn't really be accounted per single byte but rather per // storage blob but this would make it more tedious to compute gas in the // codebase. For these two reasons we just set an arbitrary value (based on // actual SSDs latency) per byte here -const PHYSICAL_STORAGE_LATENCY_PER_BYTE: u64 = 75; +const PHYSICAL_STORAGE_LATENCY_PER_BYTE: u64 = 1_000_000; // This is based on the global average bandwidth -const NETWORK_TRANSMISSION_GAS_PER_BYTE: u64 = 13; +const NETWORK_TRANSMISSION_GAS_PER_BYTE: u64 = 848; /// The cost of accessing data from memory (both read and write mode), per byte -pub const MEMORY_ACCESS_GAS_PER_BYTE: u64 = 2; +pub const MEMORY_ACCESS_GAS_PER_BYTE: u64 = 104; /// The cost of accessing data from storage, per byte pub const STORAGE_ACCESS_GAS_PER_BYTE: u64 = - 3 + PHYSICAL_STORAGE_LATENCY_PER_BYTE; + 163 + PHYSICAL_STORAGE_LATENCY_PER_BYTE; /// The cost of writing data to storage, per byte pub const STORAGE_WRITE_GAS_PER_BYTE: u64 = - MEMORY_ACCESS_GAS_PER_BYTE + 848 + STORAGE_OCCUPATION_GAS_PER_BYTE; + MEMORY_ACCESS_GAS_PER_BYTE + 69_634 + STORAGE_OCCUPATION_GAS_PER_BYTE; /// The cost of verifying a single signature of a transaction -pub const VERIFY_TX_SIG_GAS: u64 = 9_793; +pub const VERIFY_TX_SIG_GAS: u64 = 594_290; /// The cost for requesting one more page in wasm (64KiB) pub const WASM_MEMORY_PAGE_GAS: u32 = MEMORY_ACCESS_GAS_PER_BYTE as u32 * 64 * 1_024; /// The cost to validate an Ibc action -pub const IBC_ACTION_VALIDATE_GAS: u64 = 7_511; +pub const IBC_ACTION_VALIDATE_GAS: u64 = 1_472_023; /// The cost to execute an Ibc action -pub const IBC_ACTION_EXECUTE_GAS: u64 = 47_452; -/// The cost to execute a masp tx verification -pub const MASP_VERIFY_SHIELDED_TX_GAS: u64 = 62_381_957; +pub const IBC_ACTION_EXECUTE_GAS: u64 = 3_678_745; +/// The cost to execute an ibc transaction TODO: remove once ibc tx goes back to +/// wasm +pub const IBC_TX_GAS: u64 = 111_825_500; +/// The cost to verify a masp spend note +pub const MASP_VERIFY_SPEND_GAS: u64 = 66_822_000; +/// The cost to verify a masp convert note +pub const MASP_VERIFY_CONVERT_GAS: u64 = 45_240_000; +/// The cost to verify a masp output note +pub const MASP_VERIFY_OUTPUT_GAS: u64 = 55_023_000; +/// The cost to run the final masp verification +pub const MASP_VERIFY_FINAL_GAS: u64 = 3_475_200; /// Gas module result for functions that may fail pub type Result = std::result::Result; /// Decimal scale of Gas units -const SCALE: u64 = 10_000; +const SCALE: u64 = 100_000_000; /// Representation of gas in sub-units. This effectively decouples gas metering /// from fee payment, allowing higher resolution when accounting for gas while, diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 0fd7147598..7ef97a36f5 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -11,17 +11,10 @@ use proc_macro2::{Span as Span2, Span, TokenStream as TokenStream2}; use quote::{quote, ToTokens}; use sha2::Digest; use syn::punctuated::Punctuated; -use syn::{ - parse_macro_input, ExprAssign, FnArg, ItemEnum, ItemFn, ItemStruct, - LitByte, Pat, -}; +use syn::{parse_macro_input, ItemEnum, ItemFn, ItemStruct, LitByte}; /// Generate WASM binding for a transaction main entrypoint function. /// -/// It expects an attribute in the form: `gas = u64`, so that a call to the gas -/// meter can be injected as the first instruction of the transaction to account -/// for the whitelisted gas amount. -/// /// This macro expects a function with signature: /// /// ```compiler_fail @@ -31,38 +24,15 @@ use syn::{ /// ) -> TxResult /// ``` #[proc_macro_attribute] -pub fn transaction(attr: TokenStream, input: TokenStream) -> TokenStream { +pub fn transaction(_attr: TokenStream, input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as ItemFn); - let ItemFn { - attrs, - vis, - sig, - block, - } = ast; - let stmts = &block.stmts; - let ident = &sig.ident; - let attr_ast = parse_macro_input!(attr as ExprAssign); - let gas = attr_ast.right; - let ctx = match sig.inputs.first() { - Some(FnArg::Typed(pat_type)) => { - if let Pat::Ident(pat_ident) = pat_type.pat.as_ref() { - &pat_ident.ident - } else { - panic!("Unexpected token, expected ctx ident") - } - } - _ => panic!("Unexpected token, expected ctx ident"), - }; + let ident = &ast.sig.ident; let gen = quote! { // Use `wee_alloc` as the global allocator. #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; - #(#attrs)* #vis #sig { - // Consume the whitelisted gas - #ctx.charge_gas(#gas)?; - #(#stmts)* - } + #ast // The module entrypoint callable by wasm runtime #[no_mangle] @@ -94,10 +64,6 @@ pub fn transaction(attr: TokenStream, input: TokenStream) -> TokenStream { /// Generate WASM binding for validity predicate main entrypoint function. /// -/// It expects an attribute in the form: `gas = u64`, so that a call to the gas -/// meter can be injected as the first instruction of the validity predicate to -/// account for the whitelisted gas amount. -/// /// This macro expects a function with signature: /// /// ```compiler_fail @@ -111,40 +77,17 @@ pub fn transaction(attr: TokenStream, input: TokenStream) -> TokenStream { /// ``` #[proc_macro_attribute] pub fn validity_predicate( - attr: TokenStream, + _attr: TokenStream, input: TokenStream, ) -> TokenStream { let ast = parse_macro_input!(input as ItemFn); - let ItemFn { - attrs, - vis, - sig, - block, - } = ast; - let stmts = &block.stmts; - let ident = &sig.ident; - let attr_ast = parse_macro_input!(attr as ExprAssign); - let gas = attr_ast.right; - let ctx = match sig.inputs.first() { - Some(FnArg::Typed(pat_type)) => { - if let Pat::Ident(pat_ident) = pat_type.pat.as_ref() { - &pat_ident.ident - } else { - panic!("Unexpected token, expected ctx ident") - } - } - _ => panic!("Unexpected token, expected ctx ident"), - }; + let ident = &ast.sig.ident; let gen = quote! { // Use `wee_alloc` as the global allocator. #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; - #(#attrs)* #vis #sig { - // Consume the whitelisted gas - #ctx.charge_gas(#gas)?; - #(#stmts)* - } + #ast // The module entrypoint callable by wasm runtime #[no_mangle] diff --git a/crates/namada/Cargo.toml b/crates/namada/Cargo.toml index c09842abb3..0f5e6b2050 100644 --- a/crates/namada/Cargo.toml +++ b/crates/namada/Cargo.toml @@ -77,6 +77,7 @@ migrations = [ "namada_migrations", "linkme", ] +benches = ["namada_core/benches", "namada_state/benches"] [dependencies] namada_account = { path = "../account" } @@ -141,14 +142,12 @@ tiny-bip39.workspace = true tiny-hderive.workspace = true toml.workspace = true tracing.workspace = true -wasm-instrument = { version = "0.4.0", features = [ - "sign_ext", -], optional = true } -wasmer = { git = "https://github.com/heliaxdev/wasmer", rev = "255054f7f58b7b4a525f2fee6b9b86422d1ca15b", optional = true } +wasm-instrument = { workspace = true, optional = true } +wasmer = { workspace = true, optional = true } wasmer-cache = { git = "https://github.com/heliaxdev/wasmer", rev = "255054f7f58b7b4a525f2fee6b9b86422d1ca15b", optional = true } -wasmer-compiler-singlepass = { git = "https://github.com/heliaxdev/wasmer", rev = "255054f7f58b7b4a525f2fee6b9b86422d1ca15b", optional = true } +wasmer-compiler-singlepass = { workspace = true, optional = true } wasmer-engine-dylib = { git = "https://github.com/heliaxdev/wasmer", rev = "255054f7f58b7b4a525f2fee6b9b86422d1ca15b", optional = true } -wasmer-engine-universal = { git = "https://github.com/heliaxdev/wasmer", rev = "255054f7f58b7b4a525f2fee6b9b86422d1ca15b", optional = true } +wasmer-engine-universal = { workspace = true, optional = true } wasmer-vm = { git = "https://github.com/heliaxdev/wasmer", rev = "255054f7f58b7b4a525f2fee6b9b86422d1ca15b", optional = true } # Greater versions break in `test_tx_stack_limiter` and `test_vp_stack_limiter` wat = "=1.0.71" diff --git a/crates/namada/src/ledger/governance/mod.rs b/crates/namada/src/ledger/governance/mod.rs index 1151b299b9..e5b40d53aa 100644 --- a/crates/namada/src/ledger/governance/mod.rs +++ b/crates/namada/src/ledger/governance/mod.rs @@ -897,8 +897,6 @@ mod test { use crate::ledger::pos; use crate::vm::wasm; - const TX_GAS_LIMIT: u64 = 1_000_000; - fn init_storage() -> TestState { let mut state = TestState::default(); @@ -928,7 +926,7 @@ mod test { let keys_changed = BTreeSet::new(); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), + &TxGasMeter::new_from_sub_limit(u64::MAX.into()), )); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1153,7 +1151,7 @@ mod test { let keys_changed = get_proposal_keys(proposal_id, grace_epoch); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), + &TxGasMeter::new_from_sub_limit(u64::MAX.into()), )); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1250,7 +1248,7 @@ mod test { let keys_changed = get_proposal_keys(proposal_id, grace_epoch); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), + &TxGasMeter::new_from_sub_limit(u64::MAX.into()), )); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1350,7 +1348,7 @@ mod test { let keys_changed = get_proposal_keys(proposal_id, grace_epoch); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), + &TxGasMeter::new_from_sub_limit(u64::MAX.into()), )); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1449,7 +1447,7 @@ mod test { let keys_changed = get_proposal_keys(proposal_id, grace_epoch); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), + &TxGasMeter::new_from_sub_limit(u64::MAX.into()), )); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1528,7 +1526,7 @@ mod test { let keys_changed = get_proposal_keys(proposal_id, grace_epoch); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), + &TxGasMeter::new_from_sub_limit(u64::MAX.into()), )); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1607,7 +1605,7 @@ mod test { let keys_changed = get_proposal_keys(proposal_id, grace_epoch); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), + &TxGasMeter::new_from_sub_limit(u64::MAX.into()), )); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1704,7 +1702,7 @@ mod test { ]); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), + &TxGasMeter::new_from_sub_limit(u64::MAX.into()), )); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1801,7 +1799,7 @@ mod test { ]); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), + &TxGasMeter::new_from_sub_limit(u64::MAX.into()), )); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1880,7 +1878,7 @@ mod test { let mut keys_changed = get_proposal_keys(proposal_id, grace_epoch); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), + &TxGasMeter::new_from_sub_limit(u64::MAX.into()), )); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2004,7 +2002,7 @@ mod test { let mut keys_changed = get_proposal_keys(proposal_id, grace_epoch); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), + &TxGasMeter::new_from_sub_limit(u64::MAX.into()), )); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2128,7 +2126,7 @@ mod test { let mut keys_changed = get_proposal_keys(proposal_id, grace_epoch); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), + &TxGasMeter::new_from_sub_limit(u64::MAX.into()), )); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2252,7 +2250,7 @@ mod test { let mut keys_changed = get_proposal_keys(proposal_id, grace_epoch); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), + &TxGasMeter::new_from_sub_limit(u64::MAX.into()), )); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2393,7 +2391,7 @@ mod test { let mut keys_changed = get_proposal_keys(proposal_id, grace_epoch); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), + &TxGasMeter::new_from_sub_limit(u64::MAX.into()), )); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2534,7 +2532,7 @@ mod test { let mut keys_changed = get_proposal_keys(proposal_id, grace_epoch); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), + &TxGasMeter::new_from_sub_limit(u64::MAX.into()), )); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); diff --git a/crates/namada/src/ledger/native_vp/ibc/context.rs b/crates/namada/src/ledger/native_vp/ibc/context.rs index 957fdb40e9..1c44b638ff 100644 --- a/crates/namada/src/ledger/native_vp/ibc/context.rs +++ b/crates/namada/src/ledger/native_vp/ibc/context.rs @@ -5,8 +5,10 @@ use std::collections::BTreeSet; use borsh_ext::BorshSerializeExt; use namada_core::collections::{HashMap, HashSet}; use namada_core::storage::Epochs; +use namada_gas::MEMORY_ACCESS_GAS_PER_BYTE; use namada_ibc::{IbcCommonContext, IbcStorageContext}; use namada_state::{StateRead, StorageError, StorageRead, StorageWrite}; +use namada_vp_env::VpEnv; use crate::address::{Address, InternalAddress}; use crate::ibc::IbcEvent; @@ -76,9 +78,18 @@ where fn read_bytes(&self, key: &Key) -> Result>> { match self.store.get(key) { Some(StorageModification::Write { ref value }) => { + let gas = key.len() + value.len(); + self.ctx + .ctx + .charge_gas(gas as u64 * MEMORY_ACCESS_GAS_PER_BYTE)?; Ok(Some(value.clone())) } - Some(StorageModification::Delete) => Ok(None), + Some(StorageModification::Delete) => { + self.ctx.ctx.charge_gas( + key.len() as u64 * MEMORY_ACCESS_GAS_PER_BYTE, + )?; + Ok(None) + } Some(StorageModification::Temp { .. }) => { Err(StorageError::new_const( "Temp shouldn't be inserted in an IBC transaction", @@ -87,7 +98,12 @@ where Some(StorageModification::InitAccount { .. }) => Err( StorageError::new_const("InitAccount shouldn't be inserted"), ), - None => self.ctx.read_bytes(key), + None => { + self.ctx.ctx.charge_gas( + key.len() as u64 * MEMORY_ACCESS_GAS_PER_BYTE, + )?; + self.ctx.read_bytes(key) + } } } @@ -154,18 +170,20 @@ where key: &Key, value: impl AsRef<[u8]>, ) -> Result<()> { - self.store.insert( - key.clone(), - StorageModification::Write { - value: value.as_ref().to_vec(), - }, - ); - Ok(()) + let value = value.as_ref().to_vec(); + let gas = key.len() + value.len(); + self.store + .insert(key.clone(), StorageModification::Write { value }); + self.ctx + .ctx + .charge_gas(gas as u64 * MEMORY_ACCESS_GAS_PER_BYTE) } fn delete(&mut self, key: &Key) -> Result<()> { self.store.insert(key.clone(), StorageModification::Delete); - Ok(()) + self.ctx + .ctx + .charge_gas(key.len() as u64 * MEMORY_ACCESS_GAS_PER_BYTE) } } diff --git a/crates/namada/src/ledger/native_vp/ibc/mod.rs b/crates/namada/src/ledger/native_vp/ibc/mod.rs index fbc5bac1a2..a5255bbe0e 100644 --- a/crates/namada/src/ledger/native_vp/ibc/mod.rs +++ b/crates/namada/src/ledger/native_vp/ibc/mod.rs @@ -322,7 +322,7 @@ impl From for Error { } /// A dummy header used for testing -#[cfg(any(test, feature = "testing"))] +#[cfg(any(test, feature = "testing", feature = "benches"))] pub fn get_dummy_header() -> crate::storage::Header { use crate::tendermint::time::Time as TmTime; crate::storage::Header { @@ -502,7 +502,7 @@ mod tests { const ADDRESS: Address = Address::Internal(InternalAddress::Ibc); const COMMITMENT_PREFIX: &[u8] = b"ibc"; - const TX_GAS_LIMIT: u64 = 1_000_000; + const TX_GAS_LIMIT: u64 = 10_000_000_000; fn get_client_id() -> ClientId { let id = format!("{}-0", MOCK_CLIENT_TYPE); diff --git a/crates/namada/src/ledger/native_vp/masp.rs b/crates/namada/src/ledger/native_vp/masp.rs index 81fe9bae88..ea08279b82 100644 --- a/crates/namada/src/ledger/native_vp/masp.rs +++ b/crates/namada/src/ledger/native_vp/masp.rs @@ -14,7 +14,6 @@ use namada_core::address::InternalAddress::Masp; use namada_core::collections::{HashMap, HashSet}; use namada_core::masp::encode_asset_type; use namada_core::storage::{IndexedTx, Key}; -use namada_gas::MASP_VERIFY_SHIELDED_TX_GAS; use namada_sdk::masp::verify_shielded_tx; use namada_state::{OptionExt, ResultExt, StateRead}; use namada_token::read_denom; @@ -728,11 +727,9 @@ where _ => {} } - // Verify the proofs and charge the gas for the expensive execution - self.ctx - .charge_gas(MASP_VERIFY_SHIELDED_TX_GAS) - .map_err(Error::NativeVpError)?; - Ok(verify_shielded_tx(&shielded_tx)) + // Verify the proofs + verify_shielded_tx(&shielded_tx, |gas| self.ctx.charge_gas(gas)) + .map_err(Error::NativeVpError) } } diff --git a/crates/namada/src/ledger/native_vp/mod.rs b/crates/namada/src/ledger/native_vp/mod.rs index 1700ec8548..4503b68861 100644 --- a/crates/namada/src/ledger/native_vp/mod.rs +++ b/crates/namada/src/ledger/native_vp/mod.rs @@ -98,7 +98,7 @@ where S: StateRead, CA: WasmCacheAccess, { - ctx: &'view Ctx<'a, S, CA>, + pub(crate) ctx: &'view Ctx<'a, S, CA>, } /// Read access to the posterior storage (state after tx execution) via diff --git a/crates/namada/src/ledger/protocol/mod.rs b/crates/namada/src/ledger/protocol/mod.rs index 056cdf618b..76e4267eec 100644 --- a/crates/namada/src/ledger/protocol/mod.rs +++ b/crates/namada/src/ledger/protocol/mod.rs @@ -815,11 +815,6 @@ where return Err(Error::MissingAddress(addr.clone())); }; - // NOTE: because of the whitelisted gas and the gas - // metering for the exposed vm - // env functions, the first - // signature verification (if any) is accounted - // twice wasm::run::vp( vp_code_hash, tx, @@ -1164,4 +1159,85 @@ mod tests { Ok(()) } + + #[test] + fn test_native_vp_out_of_gas() { + let (mut state, _validators) = test_utils::setup_default_storage(); + + // some random token address + let token_address = Address::Established([0xff; 20].into()); + + let src_address = Address::Established([0xab; 20].into()); + let dst_address = Address::Established([0xba; 20].into()); + + // supply an address with 1000 of said token + namada_token::credit_tokens( + &mut state, + &token_address, + &src_address, + 1000.into(), + ) + .unwrap(); + + // commit storage changes. this will act as the + // initial state of the chain + state.commit_tx(); + state.commit_block().unwrap(); + + // "execute" a dummy tx, by manually performing its state changes + let (dummy_tx, changed_keys, verifiers) = { + let mut tx = Tx::from_type(TxType::Raw); + tx.set_code(namada_tx::Code::new(vec![], None)); + tx.set_data(namada_tx::Data::new(vec![])); + + // transfer half of the supply of src to dst + namada_token::transfer( + &mut state, + &token_address, + &src_address, + &dst_address, + 500.into(), + ) + .unwrap(); + + let changed_keys = { + let mut set = BTreeSet::new(); + set.insert(namada_token::storage_key::balance_key( + &token_address, + &src_address, + )); + set.insert(namada_token::storage_key::balance_key( + &token_address, + &dst_address, + )); + set + }; + + let verifiers = { + let mut set = BTreeSet::new(); + set.insert(Address::Internal(InternalAddress::Multitoken)); + set + }; + + (tx, changed_keys, verifiers) + }; + + // temp vp cache + let (mut vp_cache, _) = + wasm::compilation_cache::common::testing::cache(); + + // gas meter with no gas left + let gas_meter = TxGasMeter::new(0); + + let result = execute_vps( + verifiers, + changed_keys, + &dummy_tx, + &TxIndex::default(), + &state, + &gas_meter, + &mut vp_cache, + ); + assert!(matches!(result.unwrap_err(), Error::GasError(_))); + } } diff --git a/crates/namada/src/vm/host_env.rs b/crates/namada/src/vm/host_env.rs index 10ecc255db..0ab7796923 100644 --- a/crates/namada/src/vm/host_env.rs +++ b/crates/namada/src/vm/host_env.rs @@ -7,6 +7,7 @@ use std::num::TryFromIntError; use borsh::BorshDeserialize; use borsh_ext::BorshSerializeExt; +use gas::IBC_TX_GAS; use masp_primitives::transaction::Transaction; use namada_core::address::ESTABLISHED_ADDRESS_BYTES_LEN; use namada_core::internal::KeyVal; @@ -1932,10 +1933,8 @@ where Ok(len) } -/// Verify a transaction signature -/// TODO: this is just a warkaround to track gas for multiple signature -/// verifications. When the runtime gas meter is implemented, this function can -/// be removed +/// Verify a transaction signature in the host environment for better +/// performance #[allow(clippy::too_many_arguments)] pub fn vp_verify_tx_section_signature( env: &VpVmEnv, @@ -2056,6 +2055,8 @@ where use namada_ibc::{IbcActions, NftTransferModule, TransferModule}; + tx_charge_gas::(env, IBC_TX_GAS)?; + let tx = unsafe { env.ctx.tx.get() }; let tx_data = tx.data().ok_or_else(|| { let sentinel = unsafe { env.ctx.sentinel.get() }; diff --git a/crates/namada/src/vm/wasm/host_env.rs b/crates/namada/src/vm/wasm/host_env.rs index 58655037f6..1f53c8b8eb 100644 --- a/crates/namada/src/vm/wasm/host_env.rs +++ b/crates/namada/src/vm/wasm/host_env.rs @@ -61,8 +61,6 @@ where "memory" => initial_memory, // Wasm middleware gas injection hook "gas" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_charge_gas), - // Whitelisted gas exposed function, we need two different functions just because of colliding names in the vm_host_env macro to generate implementations - "namada_tx_charge_gas" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_charge_gas), "namada_tx_read" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_read), "namada_tx_result_buffer" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_result_buffer), "namada_tx_has_key" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_has_key), @@ -112,8 +110,6 @@ where "memory" => initial_memory, // Wasm middleware gas injection hook "gas" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_charge_gas), - // Whitelisted gas exposed function, we need two different functions just because of colliding names in the vm_host_env macro to generate implementations - "namada_vp_charge_gas" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_charge_gas), "namada_vp_read_pre" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_read_pre), "namada_vp_read_post" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_read_post), "namada_vp_read_temp" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_read_temp), diff --git a/crates/namada/src/vm/wasm/run.rs b/crates/namada/src/vm/wasm/run.rs index 7e3ff8be15..2aa06f0188 100644 --- a/crates/namada/src/vm/wasm/run.rs +++ b/crates/namada/src/vm/wasm/run.rs @@ -4,6 +4,7 @@ use std::cell::RefCell; use std::collections::BTreeSet; use std::fmt::Debug; use std::marker::PhantomData; +use std::num::NonZeroU32; use borsh::BorshDeserialize; use namada_core::validity_predicate::VpSentinel; @@ -12,7 +13,8 @@ use namada_state::write_log::StorageModification; use namada_state::{DBIter, State, StateRead, StorageHasher, StorageRead, DB}; use namada_tx::data::{TxSentinel, TxType}; use namada_tx::{Commitment, Section, Tx}; -use parity_wasm::elements; +use parity_wasm::elements::Instruction::*; +use parity_wasm::elements::{self, SignExtInstruction}; use thiserror::Error; use wasmer::{BaseTunables, Module, Store}; @@ -510,7 +512,7 @@ pub fn prepare_wasm_code>(code: T) -> Result> { wasm_instrument::gas_metering::host_function::Injector::new( "env", "gas", ), - &get_gas_rules(), + &GasRules, ) .map_err(|_original_module| Error::GasMeterInjection)?; let module = @@ -633,18 +635,217 @@ where } } -/// Get the gas rules used to meter wasm operations -fn get_gas_rules() -> wasm_instrument::gas_metering::ConstantCostRules { - // NOTE: costs set to 0 don't actually trigger the injection of a call to - // the gas host function (no useless instructions are injected) - let instruction_cost = 0; - let memory_grow_cost = WASM_MEMORY_PAGE_GAS; - let call_per_local_cost = 0; - wasm_instrument::gas_metering::ConstantCostRules::new( - instruction_cost, - memory_grow_cost, - call_per_local_cost, - ) +struct GasRules; + +impl wasm_instrument::gas_metering::Rules for GasRules { + fn instruction_cost( + &self, + instruction: &wasm_instrument::parity_wasm::elements::Instruction, + ) -> Option { + // NOTE: costs set to 0 don't actually trigger the injection of a call + // to the gas host function (no useless instructions are + // injected) + // NOTE: these costs are taken from the benchmarks crate. None of them + // should be zero + let gas = match instruction { + Unreachable => 129_358, + // Just a flag, aribitrary cost of 1 + End => 1, + // Just a flag, aribitrary cost of 1 + Else => 1, + Nop => 1, + Block(_) => 1, + Loop(_) => 1, + If(_) => 4, + Br(_) => 27, + BrIf(_) => 36, + BrTable(_) => 70, + Return => 7, + Call(_) => 43, + CallIndirect(_, _) => 140, + Drop => 1, + Select => 37, + GetLocal(_) => 2, + SetLocal(_) => 2, + TeeLocal(_) => 2, + GetGlobal(_) => 3, + SetGlobal(_) => 4, + I32Load(_, _) => 5, + I64Load(_, _) => 5, + F32Load(_, _) => 6, + F64Load(_, _) => 6, + I32Load8S(_, _) => 5, + I32Load8U(_, _) => 5, + I32Load16S(_, _) => 5, + I32Load16U(_, _) => 5, + I64Load8S(_, _) => 5, + I64Load8U(_, _) => 5, + I64Load16S(_, _) => 5, + I64Load16U(_, _) => 5, + I64Load32S(_, _) => 5, + I64Load32U(_, _) => 5, + I32Store(_, _) => 5, + I64Store(_, _) => 7, + F32Store(_, _) => 5, + F64Store(_, _) => 6, + I32Store8(_, _) => 5, + I32Store16(_, _) => 15, + I64Store8(_, _) => 5, + I64Store16(_, _) => 15, + I64Store32(_, _) => 6, + CurrentMemory(_) => 108, + GrowMemory(_) => 394, + I32Const(_) => 1, + I64Const(_) => 1, + F32Const(_) => 1, + F64Const(_) => 1, + I32Eqz => 6, + I32Eq => 6, + I32Ne => 6, + I32LtS => 6, + I32LtU => 6, + I32GtS => 6, + I32GtU => 6, + I32LeS => 6, + I32LeU => 6, + I32GeS => 6, + I32GeU => 6, + I64Eqz => 7, + I64Eq => 7, + I64Ne => 7, + I64LtS => 7, + I64LtU => 7, + I64GtS => 7, + I64GtU => 7, + I64LeS => 7, + I64LeU => 7, + I64GeS => 7, + I64GeU => 7, + F32Eq => 8, + F32Ne => 8, + F32Lt => 8, + F32Gt => 8, + F32Le => 8, + F32Ge => 8, + F64Eq => 10, + F64Ne => 10, + F64Lt => 9, + F64Gt => 9, + F64Le => 9, + F64Ge => 9, + I32Clz => 35, + I32Ctz => 34, + I32Popcnt => 3, + I32Add => 3, + I32Sub => 3, + I32Mul => 5, + I32DivS => 17, + I32DivU => 17, + I32RemS => 41, + I32RemU => 17, + I32And => 3, + I32Or => 3, + I32Xor => 3, + I32Shl => 3, + I32ShrS => 3, + I32ShrU => 3, + I32Rotl => 3, + I32Rotr => 3, + I64Clz => 35, + I64Ctz => 34, + I64Popcnt => 3, + I64Add => 5, + I64Sub => 5, + I64Mul => 6, + I64DivS => 28, + I64DivU => 28, + I64RemS => 46, + I64RemU => 28, + I64And => 5, + I64Or => 5, + I64Xor => 5, + I64Shl => 4, + I64ShrS => 4, + I64ShrU => 4, + I64Rotl => 4, + I64Rotr => 4, + F32Abs => 4, + F32Neg => 3, + F32Ceil => 6, + F32Floor => 6, + F32Trunc => 6, + F32Nearest => 6, + F32Sqrt => 9, + F32Add => 6, + F32Sub => 6, + F32Mul => 6, + F32Div => 9, + F32Min => 50, + F32Max => 47, + F32Copysign => 6, + F64Abs => 6, + F64Neg => 4, + F64Ceil => 7, + F64Floor => 7, + F64Trunc => 7, + F64Nearest => 7, + F64Sqrt => 17, + F64Add => 7, + F64Sub => 7, + F64Mul => 7, + F64Div => 12, + F64Min => 52, + F64Max => 49, + F64Copysign => 11, + I32WrapI64 => 2, + I32TruncSF32 => 54, + I32TruncUF32 => 54, + I32TruncSF64 => 57, + I32TruncUF64 => 57, + I64ExtendSI32 => 2, + I64ExtendUI32 => 2, + I64TruncSF32 => 73, + I64TruncUF32 => 70, + I64TruncSF64 => 89, + I64TruncUF64 => 70, + F32ConvertSI32 => 12, + F32ConvertUI32 => 6, + F32ConvertSI64 => 6, + F32ConvertUI64 => 39, + F32DemoteF64 => 9, + F64ConvertSI32 => 12, + F64ConvertUI32 => 12, + F64ConvertSI64 => 12, + F64ConvertUI64 => 39, + F64PromoteF32 => 9, + I32ReinterpretF32 => 2, + I64ReinterpretF64 => 2, + F32ReinterpretI32 => 3, + F64ReinterpretI64 => 3, + SignExt(SignExtInstruction::I32Extend8S) => 1, + SignExt(SignExtInstruction::I32Extend16S) => 1, + SignExt(SignExtInstruction::I64Extend8S) => 1, + SignExt(SignExtInstruction::I64Extend16S) => 1, + SignExt(SignExtInstruction::I64Extend32S) => 1, + }; + + // We always return a cost, forbidden instructions should be rejected at + // validation time not here + Some(gas) + } + + fn memory_grow_cost( + &self, + ) -> wasm_instrument::gas_metering::MemoryGrowCost { + wasm_instrument::gas_metering::MemoryGrowCost::Linear( + NonZeroU32::new(WASM_MEMORY_PAGE_GAS) + .expect("Memory grow gas cost should be non-zero"), + ) + } + + fn call_per_local_cost(&self) -> u32 { + 1 + } } #[cfg(test)] @@ -667,7 +868,8 @@ mod tests { use crate::vm::host_env::TxRuntimeError; use crate::vm::wasm; - const TX_GAS_LIMIT: u64 = 10_000_000_000; + const TX_GAS_LIMIT: u64 = 10_000_000_000_000; + const OUT_OF_GAS_LIMIT: u64 = 10_000; /// Test that we sanitize accesses to invalid addresses in wasm memory. #[test] @@ -1382,6 +1584,169 @@ mod tests { } } + /// Test that when a function runs out of gas in guest, the execution is + /// aborted + #[test] + fn test_tx_out_of_gas_in_guest() { + let mut state = TestState::default(); + let gas_meter = RefCell::new(TxGasMeter::new_from_sub_limit( + OUT_OF_GAS_LIMIT.into(), + )); + let tx_index = TxIndex::default(); + + // This code will charge gas in a host function indefinetely + let tx_code = TestWasms::TxInfiniteGuestGas.read_bytes(); + // store the wasm code + let code_hash = Hash::sha256(&tx_code); + let key = Key::wasm_code(&code_hash); + let len_key = Key::wasm_code_len(&code_hash); + let code_len = (tx_code.len() as u64).serialize_to_vec(); + state.write_log_mut().write(&key, tx_code.clone()).unwrap(); + state.write_log_mut().write(&len_key, code_len).unwrap(); + + let (mut vp_cache, _) = + wasm::compilation_cache::common::testing::cache(); + let (mut tx_cache, _) = + wasm::compilation_cache::common::testing::cache(); + let mut outer_tx = Tx::from_type(TxType::Raw); + outer_tx.set_code(Code::new(tx_code.clone(), None)); + outer_tx.set_data(Data::new(vec![])); + let result = tx( + &mut state, + &gas_meter, + &tx_index, + &outer_tx, + &mut vp_cache, + &mut tx_cache, + ); + + assert!(matches!(result.unwrap_err(), Error::GasError(_))); + } + + /// Test that when a function runs out of gas in host, the execution is + /// aborted from the host env (no cooperation required by the guest). + #[test] + fn test_tx_out_of_gas_in_host() { + let mut state = TestState::default(); + let gas_meter = RefCell::new(TxGasMeter::new_from_sub_limit( + OUT_OF_GAS_LIMIT.into(), + )); + let tx_index = TxIndex::default(); + + // This code will charge gas in a host function indefinetely + let tx_code = TestWasms::TxInfiniteHostGas.read_bytes(); + // store the wasm code + let code_hash = Hash::sha256(&tx_code); + let key = Key::wasm_code(&code_hash); + let len_key = Key::wasm_code_len(&code_hash); + let code_len = (tx_code.len() as u64).serialize_to_vec(); + state.write_log_mut().write(&key, tx_code.clone()).unwrap(); + state.write_log_mut().write(&len_key, code_len).unwrap(); + + let (mut vp_cache, _) = + wasm::compilation_cache::common::testing::cache(); + let (mut tx_cache, _) = + wasm::compilation_cache::common::testing::cache(); + let mut outer_tx = Tx::from_type(TxType::Raw); + outer_tx.set_code(Code::new(tx_code.clone(), None)); + outer_tx.set_data(Data::new(vec![])); + let result = tx( + &mut state, + &gas_meter, + &tx_index, + &outer_tx, + &mut vp_cache, + &mut tx_cache, + ); + + assert!(matches!(result.unwrap_err(), Error::GasError(_))); + } + + /// Test that when a vp runs out of gas in guest, the execution is aborted + #[test] + fn test_vp_out_of_gas_in_guest() { + let mut state = TestState::default(); + let tx_index = TxIndex::default(); + + let addr = state.in_mem_mut().address_gen.generate_address("rng seed"); + let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( + &TxGasMeter::new_from_sub_limit(OUT_OF_GAS_LIMIT.into()), + )); + let keys_changed = BTreeSet::new(); + let verifiers = BTreeSet::new(); + + // This code will charge gas in a host function indefinetely + let tx_code = TestWasms::VpInfiniteGuestGas.read_bytes(); + // store the wasm code + let code_hash = Hash::sha256(&tx_code); + let key = Key::wasm_code(&code_hash); + let len_key = Key::wasm_code_len(&code_hash); + let code_len = (tx_code.len() as u64).serialize_to_vec(); + state.write_log_mut().write(&key, tx_code.clone()).unwrap(); + state.write_log_mut().write(&len_key, code_len).unwrap(); + + let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); + let mut outer_tx = Tx::from_type(TxType::Raw); + outer_tx.set_code(Code::new(tx_code.clone(), None)); + outer_tx.set_data(Data::new(vec![])); + let result = vp( + code_hash, + &outer_tx, + &tx_index, + &addr, + &state, + &gas_meter, + &keys_changed, + &verifiers, + vp_cache.clone(), + ); + + assert!(matches!(result.unwrap_err(), Error::GasError(_))); + } + + /// Test that when a vp runs out of gas in host, the execution is aborted + /// from the host env (no cooperation required by the guest). + #[test] + fn test_vp_out_of_gas_in_host() { + let mut state = TestState::default(); + let tx_index = TxIndex::default(); + + let addr = state.in_mem_mut().address_gen.generate_address("rng seed"); + let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( + &TxGasMeter::new_from_sub_limit(OUT_OF_GAS_LIMIT.into()), + )); + let keys_changed = BTreeSet::new(); + let verifiers = BTreeSet::new(); + + // This code will charge gas in a host function indefinetely + let tx_code = TestWasms::VpInfiniteHostGas.read_bytes(); + // store the wasm code + let code_hash = Hash::sha256(&tx_code); + let key = Key::wasm_code(&code_hash); + let len_key = Key::wasm_code_len(&code_hash); + let code_len = (tx_code.len() as u64).serialize_to_vec(); + state.write_log_mut().write(&key, tx_code.clone()).unwrap(); + state.write_log_mut().write(&len_key, code_len).unwrap(); + + let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); + let mut outer_tx = Tx::from_type(TxType::Raw); + outer_tx.set_code(Code::new(tx_code.clone(), None)); + outer_tx.set_data(Data::new(vec![])); + let result = vp( + code_hash, + &outer_tx, + &tx_index, + &addr, + &state, + &gas_meter, + &keys_changed, + &verifiers, + vp_cache.clone(), + ); + + assert!(matches!(result.unwrap_err(), Error::GasError(_))); + } + fn execute_tx_with_code(tx_code: Vec) -> Result> { let tx_data = vec![]; let tx_index = TxIndex::default(); diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index f4d05e74aa..1937671913 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -71,6 +71,7 @@ migrations = [ namada_account = { path = "../account" } namada_core = { path = "../core" } namada_ethereum_bridge = { path = "../ethereum_bridge", default-features = false } +namada_gas = { path = "../gas" } namada_governance = { path = "../governance" } namada_ibc = { path = "../ibc" } namada_macros = { path = "../macros" } diff --git a/crates/sdk/src/masp.rs b/crates/sdk/src/masp.rs index 5d3e21c414..089f8fa653 100644 --- a/crates/sdk/src/masp.rs +++ b/crates/sdk/src/masp.rs @@ -163,11 +163,11 @@ struct ExtractedMaspTx { /// MASP verifying keys pub struct PVKs { /// spend verifying key - spend_vk: PreparedVerifyingKey, + pub spend_vk: PreparedVerifyingKey, /// convert verifying key - convert_vk: PreparedVerifyingKey, + pub convert_vk: PreparedVerifyingKey, /// output verifying key - output_vk: PreparedVerifyingKey, + pub output_vk: PreparedVerifyingKey, } lazy_static! { @@ -297,7 +297,7 @@ impl Authorization for PartialAuthorized { } /// Partially deauthorize the transparent bundle -fn partial_deauthorize( +pub fn partial_deauthorize( tx_data: &TransactionData, ) -> Option> { let transp = tx_data.transparent_bundle().and_then(|x| { @@ -330,20 +330,26 @@ fn partial_deauthorize( } /// Verify a shielded transaction. -pub fn verify_shielded_tx(transaction: &Transaction) -> bool { +pub fn verify_shielded_tx( + transaction: &Transaction, + mut consume_verify_gas: F, +) -> Result +where + F: FnMut(u64) -> std::result::Result<(), namada_state::StorageError>, +{ tracing::info!("entered verify_shielded_tx()"); let sapling_bundle = if let Some(bundle) = transaction.sapling_bundle() { bundle } else { - return false; + return Ok(false); }; let tx_data = transaction.deref(); // Partially deauthorize the transparent bundle let unauth_tx_data = match partial_deauthorize(tx_data) { Some(tx_data) => tx_data, - None => return false, + None => return Ok(false), }; let txid_parts = unauth_tx_data.digest(TxIdDigester); @@ -365,21 +371,23 @@ pub fn verify_shielded_tx(transaction: &Transaction) -> bool { let mut ctx = SaplingVerificationContext::new(true); #[cfg(feature = "testing")] let mut ctx = testing::MockSaplingVerificationContext::new(true); - let spends_valid = sapling_bundle - .shielded_spends - .iter() - .all(|spend| check_spend(spend, sighash.as_ref(), &mut ctx, spend_vk)); - let converts_valid = sapling_bundle - .shielded_converts - .iter() - .all(|convert| check_convert(convert, &mut ctx, convert_vk)); - let outputs_valid = sapling_bundle - .shielded_outputs - .iter() - .all(|output| check_output(output, &mut ctx, output_vk)); - - if !(spends_valid && outputs_valid && converts_valid) { - return false; + for spend in &sapling_bundle.shielded_spends { + consume_verify_gas(namada_gas::MASP_VERIFY_SPEND_GAS)?; + if !check_spend(spend, sighash.as_ref(), &mut ctx, spend_vk) { + return Ok(false); + } + } + for convert in &sapling_bundle.shielded_converts { + consume_verify_gas(namada_gas::MASP_VERIFY_CONVERT_GAS)?; + if !check_convert(convert, &mut ctx, convert_vk) { + return Ok(false); + } + } + for output in &sapling_bundle.shielded_outputs { + consume_verify_gas(namada_gas::MASP_VERIFY_OUTPUT_GAS)?; + if !check_output(output, &mut ctx, output_vk) { + return Ok(false); + } } tracing::info!("passed spend/output verification"); @@ -391,13 +399,14 @@ pub fn verify_shielded_tx(transaction: &Transaction) -> bool { assets_and_values.components().len() ); + consume_verify_gas(namada_gas::MASP_VERIFY_FINAL_GAS)?; let result = ctx.final_check( assets_and_values, sighash.as_ref(), sapling_bundle.authorization.binding_sig, ); tracing::info!("final check result {result}"); - result + Ok(result) } /// Get the path to MASP parameters from [`ENV_VAR_MASP_PARAMS_DIR`] env var or diff --git a/crates/state/Cargo.toml b/crates/state/Cargo.toml index c0e4e7e55d..174407218f 100644 --- a/crates/state/Cargo.toml +++ b/crates/state/Cargo.toml @@ -21,6 +21,7 @@ migrations = [ "namada_migrations", "linkme", ] +benches = [] [dependencies] namada_core = { path = "../core", default-features = false } diff --git a/crates/state/src/wl_state.rs b/crates/state/src/wl_state.rs index 0942a5f87e..6965db4daa 100644 --- a/crates/state/src/wl_state.rs +++ b/crates/state/src/wl_state.rs @@ -543,7 +543,7 @@ where // For convenience in tests, fill-in a header if it's missing. // Normally, the header is added in `FinalizeBlock`. - #[cfg(any(test, feature = "testing"))] + #[cfg(any(test, feature = "testing", feature = "benches"))] { if self.in_mem.header.is_none() { self.in_mem.header = Some(storage::Header { diff --git a/crates/state/src/write_log.rs b/crates/state/src/write_log.rs index 632d50ddbd..c68d0e9f59 100644 --- a/crates/state/src/write_log.rs +++ b/crates/state/src/write_log.rs @@ -218,6 +218,11 @@ impl WriteLog { } StorageModification::Delete => len as i64, StorageModification::InitAccount { .. } => { + // NOTE: errors from host functions force a shudown of the + // wasm environment without the need for cooperation from + // the wasm code (tx or vp), so there's no need to return + // gas in case of an error because execution will terminate + // anyway and this cannot be exploited to run the vm forever return Err(Error::UpdateVpOfNewAccount); } StorageModification::Temp { .. } => { diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index 9f9a3ce75f..29ce624c23 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs @@ -20,16 +20,20 @@ pub enum TestWasms { TxMemoryLimit, TxNoOp, TxInvalidData, + TxInfiniteGuestGas, + TxInfiniteHostGas, TxProposalCode, + TxProposalMaspRewards, + TxProposalIbcTokenInflation, TxReadStorageKey, TxWriteStorageKey, VpAlwaysFalse, VpAlwaysTrue, VpEval, + VpInfiniteGuestGas, + VpInfiniteHostGas, VpMemoryLimit, VpReadStorageKey, - TxProposalMaspRewards, - TxProposalIbcTokenInflation, } impl TestWasms { @@ -40,18 +44,22 @@ impl TestWasms { TestWasms::TxMemoryLimit => "tx_memory_limit.wasm", TestWasms::TxNoOp => "tx_no_op.wasm", TestWasms::TxInvalidData => "tx_invalid_data.wasm", + TestWasms::TxInfiniteGuestGas => "tx_infinite_guest_gas.wasm", + TestWasms::TxInfiniteHostGas => "tx_infinite_host_gas.wasm", TestWasms::TxProposalCode => "tx_proposal_code.wasm", + TestWasms::TxProposalMaspRewards => "tx_proposal_masp_reward.wasm", + TestWasms::TxProposalIbcTokenInflation => { + "tx_proposal_ibc_token_inflation.wasm" + } TestWasms::TxReadStorageKey => "tx_read_storage_key.wasm", TestWasms::TxWriteStorageKey => "tx_write.wasm", TestWasms::VpAlwaysFalse => "vp_always_false.wasm", TestWasms::VpAlwaysTrue => "vp_always_true.wasm", TestWasms::VpEval => "vp_eval.wasm", + TestWasms::VpInfiniteGuestGas => "vp_infinite_guest_gas.wasm", + TestWasms::VpInfiniteHostGas => "vp_infinite_host_gas.wasm", TestWasms::VpMemoryLimit => "vp_memory_limit.wasm", TestWasms::VpReadStorageKey => "vp_read_storage_key.wasm", - TestWasms::TxProposalMaspRewards => "tx_proposal_masp_reward.wasm", - TestWasms::TxProposalIbcTokenInflation => { - "tx_proposal_ibc_token_inflation.wasm" - } }; let cwd = env::current_dir().expect("Couldn't get current working directory"); diff --git a/crates/tests/src/vm_host_env/ibc.rs b/crates/tests/src/vm_host_env/ibc.rs index 375f2d4687..a3febb03ae 100644 --- a/crates/tests/src/vm_host_env/ibc.rs +++ b/crates/tests/src/vm_host_env/ibc.rs @@ -149,7 +149,7 @@ pub fn validate_ibc_vp_from_tx<'a>( wasm::compilation_cache::common::testing::cache(); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(1_000_000.into()), + &TxGasMeter::new_from_sub_limit(10_000_000_000.into()), )); let sentinel = RefCell::new(VpSentinel::default()); let ctx = Ctx::new( @@ -189,7 +189,7 @@ pub fn validate_multitoken_vp_from_tx<'a>( wasm::compilation_cache::common::testing::cache(); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(1_000_000.into()), + &TxGasMeter::new_from_sub_limit(10_000_000_000.into()), )); let sentinel = RefCell::new(VpSentinel::default()); let ctx = Ctx::new( diff --git a/crates/tests/src/vm_host_env/tx.rs b/crates/tests/src/vm_host_env/tx.rs index 7ef5a5e418..b75c231f5b 100644 --- a/crates/tests/src/vm_host_env/tx.rs +++ b/crates/tests/src/vm_host_env/tx.rs @@ -72,7 +72,7 @@ impl Default for TestTxEnv { state, iterators: PrefixIterators::default(), gas_meter: RefCell::new(TxGasMeter::new_from_sub_limit( - 100_000_000.into(), + 100_000_000_000.into(), )), sentinel: RefCell::new(TxSentinel::default()), tx_index: TxIndex::default(), diff --git a/crates/tests/src/vm_host_env/vp.rs b/crates/tests/src/vm_host_env/vp.rs index d36e73dce9..07bf0c9f14 100644 --- a/crates/tests/src/vm_host_env/vp.rs +++ b/crates/tests/src/vm_host_env/vp.rs @@ -73,7 +73,7 @@ impl Default for TestVpEnv { state, iterators: PrefixIterators::default(), gas_meter: RefCell::new(VpGasMeter::new_from_tx_meter( - &TxGasMeter::new_from_sub_limit(10_000_000_000.into()), + &TxGasMeter::new_from_sub_limit(1_000_000_000_000.into()), )), sentinel: RefCell::new(VpSentinel::default()), tx, diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 735ce9d558..03b24f05b5 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3815,6 +3815,7 @@ dependencies = [ "namada_account", "namada_core", "namada_ethereum_bridge", + "namada_gas", "namada_governance", "namada_ibc", "namada_macros", diff --git a/wasm/tx_template/src/lib.rs b/wasm/tx_template/src/lib.rs index 4bedb51ecb..5b0f8fb501 100644 --- a/wasm/tx_template/src/lib.rs +++ b/wasm/tx_template/src/lib.rs @@ -1,6 +1,6 @@ use namada_tx_prelude::*; -#[transaction(gas = 1000)] +#[transaction] fn apply_tx(_ctx: &mut Ctx, tx_data: Tx) -> TxResult { log_string(format!("apply_tx called with data: {:#?}", tx_data)); Ok(()) diff --git a/wasm/vp_template/src/lib.rs b/wasm/vp_template/src/lib.rs index 3a42efc741..c4af9f31e0 100644 --- a/wasm/vp_template/src/lib.rs +++ b/wasm/vp_template/src/lib.rs @@ -1,6 +1,6 @@ use namada_vp_prelude::*; -#[validity_predicate(gas = 1000)] +#[validity_predicate] fn validate_tx( ctx: &Ctx, tx_data: Tx, diff --git a/wasm/wasm_source/src/tx_become_validator.rs b/wasm/wasm_source/src/tx_become_validator.rs index 4f959f6921..4887a14e30 100644 --- a/wasm/wasm_source/src/tx_become_validator.rs +++ b/wasm/wasm_source/src/tx_become_validator.rs @@ -4,7 +4,7 @@ use namada_tx_prelude::transaction::pos::BecomeValidator; use namada_tx_prelude::*; -#[transaction(gas = 4395397)] +#[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index b4b182034d..1a1bfb2b57 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -2,7 +2,7 @@ use namada_tx_prelude::*; -#[transaction(gas = 1342908)] +#[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { diff --git a/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs index 8d4277a22e..8d464d7818 100644 --- a/wasm/wasm_source/src/tx_bridge_pool.rs +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -7,7 +7,7 @@ use namada_tx_prelude::eth_bridge_pool::{ use namada_tx_prelude::parameters::native_erc20_key; use namada_tx_prelude::*; -#[transaction(gas = 1038546)] +#[transaction] fn apply_tx(ctx: &mut Ctx, signed: Tx) -> TxResult { let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { ctx.set_commitment_sentinel(); diff --git a/wasm/wasm_source/src/tx_change_consensus_key.rs b/wasm/wasm_source/src/tx_change_consensus_key.rs index 9d82f4d855..76c224153c 100644 --- a/wasm/wasm_source/src/tx_change_consensus_key.rs +++ b/wasm/wasm_source/src/tx_change_consensus_key.rs @@ -3,7 +3,7 @@ use namada_tx_prelude::transaction::pos::ConsensusKeyChange; use namada_tx_prelude::*; -#[transaction(gas = 220000)] // TODO: need to benchmark this gas +#[transaction] // TODO: need to benchmark this gas fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data")?; diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs index 039296e540..f33b84b611 100644 --- a/wasm/wasm_source/src/tx_change_validator_commission.rs +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -3,7 +3,7 @@ use namada_tx_prelude::transaction::pos::CommissionChange; use namada_tx_prelude::*; -#[transaction(gas = 1319787)] +#[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { diff --git a/wasm/wasm_source/src/tx_change_validator_metadata.rs b/wasm/wasm_source/src/tx_change_validator_metadata.rs index 0a0806fd2c..8a91e6da45 100644 --- a/wasm/wasm_source/src/tx_change_validator_metadata.rs +++ b/wasm/wasm_source/src/tx_change_validator_metadata.rs @@ -5,7 +5,7 @@ use namada_tx_prelude::transaction::pos::MetaDataChange; use namada_tx_prelude::*; // TODO: need to benchmark gas!!! -#[transaction(gas = 220000)] +#[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data")?; diff --git a/wasm/wasm_source/src/tx_claim_rewards.rs b/wasm/wasm_source/src/tx_claim_rewards.rs index 62207804af..5e2f1a01a1 100644 --- a/wasm/wasm_source/src/tx_claim_rewards.rs +++ b/wasm/wasm_source/src/tx_claim_rewards.rs @@ -3,7 +3,7 @@ use namada_tx_prelude::*; -#[transaction(gas = 260000)] // TODO: needs to be benchmarked +#[transaction] // TODO: needs to be benchmarked fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data")?; diff --git a/wasm/wasm_source/src/tx_deactivate_validator.rs b/wasm/wasm_source/src/tx_deactivate_validator.rs index cd62efc114..561a1e4e6d 100644 --- a/wasm/wasm_source/src/tx_deactivate_validator.rs +++ b/wasm/wasm_source/src/tx_deactivate_validator.rs @@ -2,7 +2,7 @@ use namada_tx_prelude::*; -#[transaction(gas = 340000)] +#[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data")?; diff --git a/wasm/wasm_source/src/tx_ibc.rs b/wasm/wasm_source/src/tx_ibc.rs index 5cc8bfe022..fbf408bf1d 100644 --- a/wasm/wasm_source/src/tx_ibc.rs +++ b/wasm/wasm_source/src/tx_ibc.rs @@ -5,7 +5,7 @@ use namada_tx_prelude::*; -#[transaction(gas = 585022)] +#[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; // let data = signed.data().ok_or_err_msg("Missing data").or_else(|err| { diff --git a/wasm/wasm_source/src/tx_init_account.rs b/wasm/wasm_source/src/tx_init_account.rs index 5b21855c4a..52525bc1c0 100644 --- a/wasm/wasm_source/src/tx_init_account.rs +++ b/wasm/wasm_source/src/tx_init_account.rs @@ -3,7 +3,7 @@ use namada_tx_prelude::*; -#[transaction(gas = 885069)] +#[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { diff --git a/wasm/wasm_source/src/tx_init_proposal.rs b/wasm/wasm_source/src/tx_init_proposal.rs index dd7018a399..c8aa9f8134 100644 --- a/wasm/wasm_source/src/tx_init_proposal.rs +++ b/wasm/wasm_source/src/tx_init_proposal.rs @@ -2,7 +2,7 @@ use namada_tx_prelude::*; -#[transaction(gas = 969395)] +#[transaction] fn apply_tx(ctx: &mut Ctx, tx: Tx) -> TxResult { let data = tx.data().ok_or_err_msg("Missing data").map_err(|err| { ctx.set_commitment_sentinel(); diff --git a/wasm/wasm_source/src/tx_reactivate_validator.rs b/wasm/wasm_source/src/tx_reactivate_validator.rs index 19bfd74648..dd4eaafc7c 100644 --- a/wasm/wasm_source/src/tx_reactivate_validator.rs +++ b/wasm/wasm_source/src/tx_reactivate_validator.rs @@ -2,7 +2,7 @@ use namada_tx_prelude::*; -#[transaction(gas = 340000)] +#[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data")?; diff --git a/wasm/wasm_source/src/tx_redelegate.rs b/wasm/wasm_source/src/tx_redelegate.rs index 8aeed5612c..85c4a3dbf3 100644 --- a/wasm/wasm_source/src/tx_redelegate.rs +++ b/wasm/wasm_source/src/tx_redelegate.rs @@ -3,7 +3,7 @@ use namada_tx_prelude::*; -#[transaction(gas = 2453242)] +#[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { diff --git a/wasm/wasm_source/src/tx_resign_steward.rs b/wasm/wasm_source/src/tx_resign_steward.rs index 10d8045895..2c39d24a57 100644 --- a/wasm/wasm_source/src/tx_resign_steward.rs +++ b/wasm/wasm_source/src/tx_resign_steward.rs @@ -2,7 +2,7 @@ use namada_tx_prelude::*; -#[transaction(gas = 1058710)] +#[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { diff --git a/wasm/wasm_source/src/tx_reveal_pk.rs b/wasm/wasm_source/src/tx_reveal_pk.rs index f5a23d0e1f..6d9a2b47da 100644 --- a/wasm/wasm_source/src/tx_reveal_pk.rs +++ b/wasm/wasm_source/src/tx_reveal_pk.rs @@ -6,7 +6,7 @@ use namada_tx_prelude::key::common; use namada_tx_prelude::*; -#[transaction(gas = 919818)] +#[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { diff --git a/wasm/wasm_source/src/tx_transfer.rs b/wasm/wasm_source/src/tx_transfer.rs index 5e43cd8bf2..5e66dba4aa 100644 --- a/wasm/wasm_source/src/tx_transfer.rs +++ b/wasm/wasm_source/src/tx_transfer.rs @@ -4,7 +4,7 @@ use namada_tx_prelude::*; -#[transaction(gas = 1703358)] +#[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index b1662649d1..20cc517149 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -3,7 +3,7 @@ use namada_tx_prelude::*; -#[transaction(gas = 2645941)] +#[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { diff --git a/wasm/wasm_source/src/tx_unjail_validator.rs b/wasm/wasm_source/src/tx_unjail_validator.rs index 7a11152876..ea4bc333e3 100644 --- a/wasm/wasm_source/src/tx_unjail_validator.rs +++ b/wasm/wasm_source/src/tx_unjail_validator.rs @@ -3,7 +3,7 @@ use namada_tx_prelude::*; -#[transaction(gas = 1641054)] +#[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { diff --git a/wasm/wasm_source/src/tx_update_account.rs b/wasm/wasm_source/src/tx_update_account.rs index 86038ab489..2f62b31954 100644 --- a/wasm/wasm_source/src/tx_update_account.rs +++ b/wasm/wasm_source/src/tx_update_account.rs @@ -4,7 +4,7 @@ use namada_tx_prelude::*; -#[transaction(gas = 968137)] +#[transaction] fn apply_tx(ctx: &mut Ctx, tx: Tx) -> TxResult { let signed = tx; let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { diff --git a/wasm/wasm_source/src/tx_update_steward_commission.rs b/wasm/wasm_source/src/tx_update_steward_commission.rs index 164414bcd8..33ad882051 100644 --- a/wasm/wasm_source/src/tx_update_steward_commission.rs +++ b/wasm/wasm_source/src/tx_update_steward_commission.rs @@ -3,7 +3,7 @@ use namada_tx_prelude::transaction::pgf::UpdateStewardCommission; use namada_tx_prelude::*; -#[transaction(gas = 1222239)] +#[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { diff --git a/wasm/wasm_source/src/tx_vote_proposal.rs b/wasm/wasm_source/src/tx_vote_proposal.rs index bd68cd0c73..ccd2959b84 100644 --- a/wasm/wasm_source/src/tx_vote_proposal.rs +++ b/wasm/wasm_source/src/tx_vote_proposal.rs @@ -2,7 +2,7 @@ use namada_tx_prelude::*; -#[transaction(gas = 840866)] +#[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 824ceafc1d..5e6aa476a9 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -3,7 +3,7 @@ use namada_tx_prelude::*; -#[transaction(gas = 1119469)] +#[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index bac1bf2997..70564e4937 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -66,7 +66,7 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { } } -#[validity_predicate(gas = 118452)] +#[validity_predicate] fn validate_tx( ctx: &Ctx, tx_data: Tx, diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 752f854cd4..1128fb6c69 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -77,7 +77,7 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { } } -#[validity_predicate(gas = 137325)] +#[validity_predicate] fn validate_tx( ctx: &Ctx, tx_data: Tx, diff --git a/wasm_for_tests/tx_infinite_guest_gas.wasm b/wasm_for_tests/tx_infinite_guest_gas.wasm new file mode 100755 index 0000000000..e04a0779b4 Binary files /dev/null and b/wasm_for_tests/tx_infinite_guest_gas.wasm differ diff --git a/wasm_for_tests/tx_infinite_host_gas.wasm b/wasm_for_tests/tx_infinite_host_gas.wasm new file mode 100755 index 0000000000..9129edbd28 Binary files /dev/null and b/wasm_for_tests/tx_infinite_host_gas.wasm differ diff --git a/wasm_for_tests/vp_infinite_guest_gas.wasm b/wasm_for_tests/vp_infinite_guest_gas.wasm new file mode 100755 index 0000000000..f66c513b73 Binary files /dev/null and b/wasm_for_tests/vp_infinite_guest_gas.wasm differ diff --git a/wasm_for_tests/vp_infinite_host_gas.wasm b/wasm_for_tests/vp_infinite_host_gas.wasm new file mode 100755 index 0000000000..01f2ce9968 Binary files /dev/null and b/wasm_for_tests/vp_infinite_host_gas.wasm differ diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index c606e7050b..5c1b1867c6 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -3770,6 +3770,7 @@ dependencies = [ "namada_account", "namada_core", "namada_ethereum_bridge", + "namada_gas", "namada_governance", "namada_ibc", "namada_macros", diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index 2f58ca8216..85bc53973d 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -12,20 +12,24 @@ crate-type = ["cdylib"] # The features should be used individually to build the selected wasm. # Newly added wasms should also be added into the Makefile `$(wasms)` list. [features] +tx_fail = [] +tx_infinite_guest_gas = [] +tx_infinite_host_gas = [] +tx_invalid_data = [] tx_memory_limit = [] tx_no_op = [] -tx_fail = [] +tx_proposal_code = [] +tx_proposal_ibc_token_inflation = [] +tx_proposal_masp_reward = [] tx_read_storage_key = [] tx_write = [] vp_always_false = [] vp_always_true = [] vp_eval = [] +vp_infinite_guest_gas = [] +vp_infinite_host_gas = [] vp_memory_limit = [] vp_read_storage_key = [] -tx_invalid_data = [] -tx_proposal_code = [] -tx_proposal_masp_reward = [] -tx_proposal_ibc_token_inflation = [] [dependencies] namada_test_utils = {path = "../../crates/test_utils"} diff --git a/wasm_for_tests/wasm_source/Makefile b/wasm_for_tests/wasm_source/Makefile index 8bf669a18e..d741e8f33a 100644 --- a/wasm_for_tests/wasm_source/Makefile +++ b/wasm_for_tests/wasm_source/Makefile @@ -8,17 +8,21 @@ nightly := $(shell cat ../../rust-nightly-version) wasms := tx_memory_limit wasms += tx_no_op wasms += tx_fail +wasms += tx_infinite_guest_gas +wasms += tx_infinite_host_gas +wasms += tx_invalid_data +wasms += tx_proposal_code +wasms += tx_proposal_masp_reward +wasms += tx_proposal_ibc_token_inflation wasms += tx_read_storage_key wasms += tx_write wasms += vp_always_false wasms += vp_always_true wasms += vp_eval +wasms += vp_infinite_guest_gas +wasms += vp_infinite_host_gas wasms += vp_memory_limit wasms += vp_read_storage_key -wasms += tx_invalid_data -wasms += tx_proposal_code -wasms += tx_proposal_masp_reward -wasms += tx_proposal_ibc_token_inflation # Build all wasms diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 86ac630133..c701ae1674 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -3,7 +3,7 @@ pub mod main { use namada_tx_prelude::*; - #[transaction(gas = 1000)] + #[transaction] fn apply_tx(_ctx: &mut Ctx, _tx_data: Tx) -> TxResult { Ok(()) } @@ -14,7 +14,7 @@ pub mod main { pub mod main { use namada_tx_prelude::*; - #[transaction(gas = 1000)] + #[transaction] fn apply_tx(_ctx: &mut Ctx, _tx_data: Tx) -> TxResult { Err(Error::SimpleMessage("failed tx")) } @@ -25,7 +25,7 @@ pub mod main { pub mod main { use namada_tx_prelude::*; - #[transaction(gas = 1000)] + #[transaction] fn apply_tx(_ctx: &mut Ctx, tx_data: Tx) -> TxResult { let len = usize::try_from_slice(&tx_data.data().as_ref().unwrap()[..]) .unwrap(); @@ -37,12 +37,39 @@ pub mod main { } } +/// A tx that endlessly charges gas from the host environment +#[cfg(feature = "tx_infinite_host_gas")] +pub mod main { + use namada_tx_prelude::*; + + #[transaction] + fn apply_tx(ctx: &mut Ctx, _tx_data: Tx) -> TxResult { + let target_key = parameters_storage::get_tx_allowlist_storage_key(); + loop { + // NOTE: don't propagate the error to verify that execution abortion is done in host and does not require guest cooperation + let _ = ctx.write(&target_key, vec!["hash"]); + } + } +} + +/// A tx that endlessly charges gas from the guest environment +#[cfg(feature = "tx_infinite_guest_gas")] +pub mod main { + use namada_tx_prelude::*; + + #[transaction] + fn apply_tx(_ctx: &mut Ctx, _tx_data: Tx) -> TxResult { + #[allow(clippy::empty_loop)] + loop {} + } +} + /// A tx to be used as proposal_code #[cfg(feature = "tx_proposal_code")] pub mod main { use namada_tx_prelude::*; - #[transaction(gas = 1000)] + #[transaction] fn apply_tx(ctx: &mut Ctx, _tx_data: Tx) -> TxResult { // governance let target_key = gov_storage::keys::get_min_proposal_grace_epoch_key(); @@ -63,7 +90,7 @@ pub mod main { use dec::Dec; use namada_tx_prelude::*; - #[transaction(gas = 1000)] + #[transaction] fn apply_tx(ctx: &mut Ctx, _tx_data: Tx) -> TxResult { let native_token = ctx.get_native_token()?; let shielded_rewards_key = @@ -145,7 +172,7 @@ pub mod main { pub mod main { use namada_tx_prelude::*; - #[transaction(gas = 1000)] + #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { // Allocates a memory of size given from the `tx_data (usize)` let key = @@ -163,7 +190,7 @@ pub mod main { use namada_test_utils::tx_data::TxWriteData; use namada_tx_prelude::{ log_string, transaction, BorshDeserialize, Ctx, StorageRead, - StorageWrite, Tx, TxEnv, TxResult, + StorageWrite, Tx, TxResult, }; const TX_NAME: &str = "tx_write"; @@ -182,7 +209,7 @@ pub mod main { panic!() } - #[transaction(gas = 1000)] + #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = match signed.data() { @@ -244,7 +271,7 @@ pub mod main { pub mod main { use namada_vp_prelude::*; - #[validity_predicate(gas = 1000)] + #[validity_predicate] fn validate_tx( _ctx: &Ctx, _tx_data: Tx, @@ -261,7 +288,7 @@ pub mod main { pub mod main { use namada_vp_prelude::*; - #[validity_predicate(gas = 1000)] + #[validity_predicate] fn validate_tx( _ctx: &Ctx, _tx_data: Tx, @@ -279,7 +306,7 @@ pub mod main { pub mod main { use namada_vp_prelude::*; - #[validity_predicate(gas = 1000)] + #[validity_predicate] fn validate_tx( ctx: &Ctx, tx_data: Tx, @@ -304,7 +331,7 @@ pub mod main { pub mod main { use namada_vp_prelude::*; - #[validity_predicate(gas = 1000)] + #[validity_predicate] fn validate_tx( _ctx: &Ctx, tx_data: Tx, @@ -328,7 +355,7 @@ pub mod main { pub mod main { use namada_vp_prelude::*; - #[validity_predicate(gas = 1000)] + #[validity_predicate] fn validate_tx( ctx: &Ctx, tx_data: Tx, @@ -345,3 +372,44 @@ pub mod main { accept() } } + +/// A vp that endlessly charges gas from the host environment +#[cfg(feature = "vp_infinite_host_gas")] +pub mod main { + use namada_vp_prelude::*; + + #[validity_predicate] + fn validate_tx( + ctx: &Ctx, + _tx_data: Tx, + _addr: Address, + _keys_changed: BTreeSet, + _verifiers: BTreeSet
, + ) -> VpResult { + let target_key = + namada_tx_prelude::parameters_storage::get_tx_allowlist_storage_key( + ); + loop { + // NOTE: don't propagate the error to verify that execution abortion is done in host and does not require guest cooperation + let _ = ctx.read_bytes_pre(&target_key); + } + } +} + +/// A vp that endlessly charges gas from the guest environment +#[cfg(feature = "vp_infinite_guest_gas")] +pub mod main { + use namada_vp_prelude::*; + + #[validity_predicate] + fn validate_tx( + _ctx: &Ctx, + _tx_data: Tx, + _addr: Address, + _keys_changed: BTreeSet, + _verifiers: BTreeSet
, + ) -> VpResult { + #[allow(clippy::empty_loop)] + loop {} + } +}