From 6df217cdbdb57c19438f0900d75135b2d45598ba Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 25 Oct 2023 17:32:07 +0200 Subject: [PATCH 01/10] Removes replay protection hash if undecryptable or invalid sig --- .../lib/node/ledger/shell/finalize_block.rs | 313 ++++++++++---- core/src/proto/types.rs | 16 +- core/src/types/key/mod.rs | 4 +- core/src/types/transaction/mod.rs | 2 + core/src/types/validity_predicate.rs | 35 ++ shared/src/ledger/native_vp/mod.rs | 45 +- shared/src/ledger/protocol/mod.rs | 391 +++++++++++------- shared/src/ledger/vp_host_fns.rs | 71 ++-- shared/src/vm/host_env.rs | 176 +++++--- shared/src/vm/wasm/run.rs | 74 +++- 10 files changed, 779 insertions(+), 348 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 2202f7a157..874baa0d63 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -226,7 +226,9 @@ where #[cfg(not(any(feature = "abciplus", feature = "abcipp")))] if let TxType::Wrapper(wrapper) = &tx_header.tx_type { // Charge fee if wrapper transaction went out of gas or - // failed because of fees + // failed because of fees (also write the wrapper hash to + // storage to prevent replays that would force the fee payer + // to pay more than once) let error_code = ErrorCodes::from_u32(processed_tx.result.code).unwrap(); if (error_code == ErrorCodes::TxGasLimit) @@ -238,7 +240,7 @@ where tx.get_section(hash) .map(|section| { if let Section::MaspTx(transaction) = - section + section.as_ref() { Some(transaction.to_owned()) } else { @@ -248,28 +250,30 @@ where .flatten() }) .flatten(); - if let Err(msg) = protocol::charge_fee( + if let Err(msg) = protocol::apply_wrapper_tx( wrapper, masp_transaction, + &processed_tx.tx, ShellParams::new( - TxGasMeter::new_from_sub_limit(u64::MAX), + &mut TxGasMeter::new_from_sub_limit( + u64::MAX.into(), + ), &mut self.wl_storage, &mut self.vp_wasm_cache, &mut self.tx_wasm_cache, ), Some(&native_block_proposer_address), - &mut BTreeSet::default(), ) { self.wl_storage.write_log.drop_tx(); tracing::error!( "Rejected wrapper tx {} could not pay fee: {}", - Hash::sha256( - tx::try_from(processed_tx.as_ref()) - .unwrap() - ), + hash::Hash::sha256(tx.header_hash()), msg ) } + self.wl_storage + .write_tx_hash(tx.header_hash()) + .expect("Error while writing tx hash to storage"); } } @@ -310,6 +314,9 @@ where "Tx with hash {} was un-decryptable", tx_in_queue.tx.header_hash() ); + // Remove inner tx hash from storage + self.allow_tx_replay(tx_in_queue.tx); + event["info"] = "Transaction is invalid.".into(); event["log"] = "Transaction could not be \ @@ -317,6 +324,7 @@ where .into(); event["code"] = ErrorCodes::Undecryptable.into(); + response.events.push(event); continue; } } @@ -487,6 +495,15 @@ where tx_event["hash"], result.vps_result.rejected_vps ); + + if let Some(wrapper) = embedding_wrapper { + if result.vps_result.invalid_sig { + // Invalid signature was found, remove the tx + // hash from storage to allow replay + self.allow_tx_replay(wrapper); + } + } + stats.increment_rejected_txs(); self.wl_storage.drop_tx(); tx_event["code"] = ErrorCodes::InvalidTx.into(); @@ -500,9 +517,7 @@ where tx_event["hash"], msg ); - stats.increment_errored_txs(); - self.wl_storage.drop_tx(); // If transaction type is Decrypted and failed because of // out of gas, remove its hash from storage to allow // rewrapping it @@ -521,6 +536,9 @@ where .expect("Error while writing tx hash to storage"); } + stats.increment_errored_txs(); + self.wl_storage.drop_tx(); + tx_event["gas_used"] = tx_gas_meter.get_tx_consumed_gas().to_string(); tx_event["info"] = msg.to_string(); @@ -933,7 +951,7 @@ where fn allow_tx_replay(&mut self, wrapper_tx: Tx) { self.wl_storage .write_tx_hash(wrapper_tx.header_hash()) - .expect("Error while deleting tx hash from storage"); + .expect("Error while writing tx hash to storage"); self.wl_storage .delete_tx_hash(wrapper_tx.raw_header_hash()) @@ -1025,6 +1043,7 @@ mod test_finalize_block { StorageProposalVote, VoteType, }; use namada::core::ledger::replay_protection; + use namada::core::types::storage::KeySeg; use namada::eth_bridge::storage::bridge_pool::{ self, get_key_from_hash, get_nonce_key, get_signed_root_key, }; @@ -1066,6 +1085,7 @@ mod test_finalize_block { use namada::types::uint::Uint; use namada::types::vote_extensions::ethereum_events; use namada_sdk::eth_bridge::MinimumConfirmations; + use namada_test_utils::tx_data::TxWriteData; use namada_test_utils::TestWasms; use test_log::test; @@ -1079,7 +1099,7 @@ mod test_finalize_block { FinalizeBlock, ProcessedTx, }; - const GAS_LIMIT_MULTIPLIER: u64 = 300_000; + const GAS_LIMIT_MULTIPLIER: u64 = 1_000_000; /// Make a wrapper tx and a processed tx from the wrapped tx that can be /// added to `FinalizeBlock` request. @@ -2287,7 +2307,10 @@ mod test_finalize_block { ); } - /// Test replay protection hash handling + /// Test that if a decrypted transaction fails because of out-of-gas, + /// undecryptable or invalid signature, its hash is removed from storage. + /// Also checks that a tx failing for other reason has its hash persisted to + /// storage. #[test] fn test_tx_hash_handling() { let (mut shell, _, _, _) = setup(); @@ -2295,47 +2318,72 @@ mod test_finalize_block { let mut batch = namada::core::ledger::storage::testing::TestStorage::batch(); - let (wrapper_tx, _) = mk_wrapper_tx(&shell, &keypair); - let (wrapper_tx_2, _) = mk_wrapper_tx(&shell, &keypair); - let mut invalid_wrapper_tx = + let (out_of_gas_wrapper, _) = mk_wrapper_tx(&shell, &keypair); + let (undecryptable_wrapper, _) = mk_wrapper_tx(&shell, &keypair); + let mut wasm_path = top_level_directory(); + // Write a key to trigger the vp to validate the signature + wasm_path.push("wasm_for_tests/tx_write.wasm"); + let tx_code = std::fs::read(wasm_path) + .expect("Expected a file at given code path"); + let mut unsigned_wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount_per_gas_unit: 0.into(), + amount_per_gas_unit: Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, keypair.ref_to(), Epoch(0), - 0.into(), + GAS_LIMIT_MULTIPLIER.into(), None, )))); - invalid_wrapper_tx.header.chain_id = shell.chain_id.clone(); - invalid_wrapper_tx - .set_code(Code::new("wasm_code".as_bytes().to_owned())); - invalid_wrapper_tx.set_data(Data::new( + unsigned_wrapper.header.chain_id = shell.chain_id.clone(); + let mut failing_wrapper = unsigned_wrapper.clone(); + unsigned_wrapper.set_code(Code::new(tx_code)); + let addr = Address::from(&keypair.to_public()); + let key = Key::from(addr.to_db_key()) + .join(&Key::from("test".to_string().to_db_key())); + unsigned_wrapper.set_data(Data::new( + borsh::to_vec(&TxWriteData { + key, + value: "test".as_bytes().to_owned(), + }) + .unwrap(), + )); + let mut wasm_path = top_level_directory(); + wasm_path.push("wasm_for_tests/tx_fail.wasm"); + let tx_code = std::fs::read(wasm_path) + .expect("Expected a file at given code path"); + failing_wrapper.set_code(Code::new(tx_code)); + failing_wrapper.set_data(Data::new( "Encrypted transaction data".as_bytes().to_owned(), )); - invalid_wrapper_tx.add_section(Section::Signature(Signature::new( - invalid_wrapper_tx.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); - - let wrapper_hash = wrapper_tx.header_hash(); - let wrapper_2_hash = wrapper_tx_2.header_hash(); - let invalid_wrapper_hash = invalid_wrapper_tx.header_hash(); - let mut decrypted_tx = wrapper_tx.clone(); - let mut decrypted_tx_2 = wrapper_tx_2.clone(); - decrypted_tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted)); - decrypted_tx_2.update_header(TxType::Decrypted(DecryptedTx::Decrypted)); - let decrypted_hash = wrapper_tx.raw_header_hash(); - let decrypted_2_hash = wrapper_tx_2.raw_header_hash(); - let decrypted_3_hash = invalid_wrapper_tx.raw_header_hash(); + let mut out_of_gas_inner = out_of_gas_wrapper.clone(); + let mut undecryptable_inner = undecryptable_wrapper.clone(); + let mut unsigned_inner = unsigned_wrapper.clone(); + let mut failing_inner = failing_wrapper.clone(); + + undecryptable_inner + .update_header(TxType::Decrypted(DecryptedTx::Undecryptable)); + for inner in [ + &mut out_of_gas_inner, + &mut unsigned_inner, + &mut failing_inner, + ] { + inner.update_header(TxType::Decrypted(DecryptedTx::Decrypted)); + } // Write inner hashes in storage - for hash in [&decrypted_hash, &decrypted_2_hash] { + for inner in [ + &out_of_gas_inner, + &undecryptable_inner, + &unsigned_inner, + &failing_inner, + ] { let hash_subkey = - replay_protection::get_replay_protection_last_subkey(hash); + replay_protection::get_replay_protection_last_subkey( + &inner.header_hash(), + ); shell .wl_storage .storage @@ -2343,35 +2391,29 @@ mod test_finalize_block { .expect("Test failed"); } - // Invalid wrapper tx that should lead to a commitment of the wrapper - // hash and no commitment of the inner hash - let mut processed_txs = vec![ProcessedTx { - tx: invalid_wrapper_tx.to_bytes(), - result: TxResult { - code: ErrorCodes::Ok.into(), - info: "".into(), - }, - }]; - // Out of gas error triggering inner hash removal and wrapper hash - // insert - processed_txs.push(ProcessedTx { - tx: decrypted_tx.to_bytes(), - result: TxResult { - code: ErrorCodes::Ok.into(), - info: "".into(), - }, - }); - // Wasm error that still leads to inner hash commitment and no wrapper - // hash insert - processed_txs.push(ProcessedTx { - tx: decrypted_tx_2.to_bytes(), - result: TxResult { - code: ErrorCodes::Ok.into(), - info: "".into(), - }, - }); - shell.enqueue_tx(wrapper_tx, Gas::default()); - shell.enqueue_tx(wrapper_tx_2, GAS_LIMIT_MULTIPLIER.into()); + let mut processed_txs: Vec = vec![]; + for inner in [ + &out_of_gas_inner, + &undecryptable_inner, + &unsigned_inner, + &failing_inner, + ] { + processed_txs.push(ProcessedTx { + tx: inner.to_bytes(), + result: TxResult { + code: ErrorCodes::Ok.into(), + info: "".into(), + }, + }) + } + + shell.enqueue_tx(out_of_gas_wrapper.clone(), Gas::default()); + shell.enqueue_tx( + undecryptable_wrapper.clone(), + GAS_LIMIT_MULTIPLIER.into(), + ); + shell.enqueue_tx(unsigned_wrapper.clone(), u64::MAX.into()); // Prevent out of gas which would still make the test pass + shell.enqueue_tx(failing_wrapper.clone(), GAS_LIMIT_MULTIPLIER.into()); // merkle tree root before finalize_block let root_pre = shell.shell.wl_storage.storage.block.tree.root(); @@ -2386,72 +2428,171 @@ mod test_finalize_block { let root_post = shell.shell.wl_storage.storage.block.tree.root(); assert_eq!(root_pre.0, root_post.0); - // Check first inner tx hash has been removed from storage but - // corresponding wrapper hash is still there Check second inner - // tx is still there and corresponding wrapper hash has been removed - // since useless - assert_eq!(event[0].event_type.to_string(), String::from("accepted")); + // Check inner tx hash has been removed from storage + assert_eq!(event[0].event_type.to_string(), String::from("applied")); let code = event[0] .attributes .get("code") - .expect("Test failed") + .expect("Testfailed") .as_str(); - assert_eq!(code, String::from(ErrorCodes::InvalidTx).as_str()); + assert_eq!(code, String::from(ErrorCodes::WasmRuntimeError).as_str()); assert_eq!(event[1].event_type.to_string(), String::from("applied")); let code = event[1] .attributes .get("code") - .expect("Test failed") + .expect("Testfailed") .as_str(); - assert_eq!(code, String::from(ErrorCodes::WasmRuntimeError).as_str()); + assert_eq!(code, String::from(ErrorCodes::Undecryptable).as_str()); assert_eq!(event[2].event_type.to_string(), String::from("applied")); let code = event[2] .attributes .get("code") - .expect("Test failed") + .expect("Testfailed") + .as_str(); + assert_eq!(code, String::from(ErrorCodes::InvalidTx).as_str()); + assert_eq!(event[3].event_type.to_string(), String::from("applied")); + let code = event[3] + .attributes + .get("code") + .expect("Testfailed") .as_str(); assert_eq!(code, String::from(ErrorCodes::WasmRuntimeError).as_str()); + assert!( + !shell + .wl_storage + .write_log + .has_replay_protection_entry(&out_of_gas_inner.header_hash()) + .unwrap_or_default() + ); assert!( shell .wl_storage .write_log - .has_replay_protection_entry(&invalid_wrapper_hash) + .has_replay_protection_entry(&out_of_gas_wrapper.header_hash()) .unwrap_or_default() ); assert!( !shell .wl_storage .write_log - .has_replay_protection_entry(&decrypted_3_hash) + .has_replay_protection_entry(&undecryptable_inner.header_hash()) + .unwrap_or_default() + ); + assert!( + shell + .wl_storage + .write_log + .has_replay_protection_entry( + &undecryptable_wrapper.header_hash() + ) .unwrap_or_default() ); assert!( !shell .wl_storage .write_log - .has_replay_protection_entry(&decrypted_hash) + .has_replay_protection_entry(&unsigned_inner.header_hash()) .unwrap_or_default() ); assert!( shell .wl_storage .write_log - .has_replay_protection_entry(&wrapper_hash) + .has_replay_protection_entry(&unsigned_wrapper.header_hash()) .unwrap_or_default() ); assert!( shell .wl_storage .storage - .has_replay_protection_entry(&decrypted_2_hash) + .has_replay_protection_entry(&failing_inner.header_hash()) .expect("test failed") ); assert!( !shell .wl_storage .write_log - .has_replay_protection_entry(&wrapper_2_hash) + .has_replay_protection_entry(&failing_wrapper.header_hash()) + .unwrap_or_default() + ); + } + + #[test] + /// Test that the hash of the wrapper transaction is committed to storage + /// even if the wrapper tx fails. The inner transaction hash must instead be + /// removed + fn test_commits_hash_if_wrapper_failure() { + let (mut shell, _, _, _) = setup(); + let keypair = gen_keypair(); + + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount_per_gas_unit: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + 0.into(), + None, + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.set_data(Data::new( + "Encrypted transaction data".as_bytes().to_owned(), + )); + wrapper.add_section(Section::Signature(Signature::new( + wrapper.sechashes(), + [(0, keypair)].into_iter().collect(), + None, + ))); + + let wrapper_hash = wrapper.header_hash(); + + // Invalid wrapper tx that should lead to a commitment of the wrapper + // hash and no commitment of the inner hash + let processed_txs = vec![ProcessedTx { + tx: wrapper.to_bytes(), + result: TxResult { + code: ErrorCodes::Ok.into(), + info: "".into(), + }, + }]; + // merkle tree root before finalize_block + let root_pre = shell.shell.wl_storage.storage.block.tree.root(); + + let event = &shell + .finalize_block(FinalizeBlock { + txs: processed_txs, + ..Default::default() + }) + .expect("Test failed"); + + // the merkle tree root should not change after finalize_block + let root_post = shell.shell.wl_storage.storage.block.tree.root(); + assert_eq!(root_pre.0, root_post.0); + + assert_eq!(event[0].event_type.to_string(), String::from("accepted")); + let code = event[0] + .attributes + .get("code") + .expect("Test failed") + .as_str(); + assert_eq!(code, String::from(ErrorCodes::InvalidTx).as_str()); + + assert!( + shell + .wl_storage + .write_log + .has_replay_protection_entry(&wrapper_hash) + .unwrap_or_default() + ); + assert!( + !shell + .wl_storage + .write_log + .has_replay_protection_entry(&wrapper.raw_header_hash()) .unwrap_or_default() ); } diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index b1d2b40cb5..c650162339 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -26,7 +26,9 @@ use sha2::{Digest, Sha256}; use thiserror::Error; use super::generated::types; -use crate::ledger::gas::{GasMetering, VpGasMeter, VERIFY_TX_SIG_GAS_COST}; +use crate::ledger::gas::{ + self, GasMetering, VpGasMeter, VERIFY_TX_SIG_GAS_COST, +}; use crate::ledger::storage::{KeccakHasher, Sha256Hasher, StorageHasher}; #[cfg(any(feature = "tendermint", feature = "tendermint-abcipp"))] use crate::tendermint_proto::abci::ResponseDeliverTx; @@ -74,8 +76,8 @@ pub enum Error { InvalidJSONDeserialization(String), #[error("The wrapper signature is invalid.")] InvalidWrapperSignature, - #[error("Signature verification went out of gas")] - OutOfGas, + #[error("Signature verification went out of gas: {0}")] + OutOfGas(gas::Error), } pub type Result = std::result::Result; @@ -593,7 +595,7 @@ impl Signature { if let Some(meter) = gas_meter { meter .consume(VERIFY_TX_SIG_GAS_COST) - .map_err(|_| VerifySigError::OutOfGas)?; + .map_err(VerifySigError::OutOfGas)?; } common::SigScheme::verify_signature( &pk, @@ -618,7 +620,7 @@ impl Signature { if let Some(meter) = gas_meter { meter .consume(VERIFY_TX_SIG_GAS_COST) - .map_err(|_| VerifySigError::OutOfGas)?; + .map_err(VerifySigError::OutOfGas)?; } common::SigScheme::verify_signature( pk, @@ -1448,8 +1450,8 @@ impl Tx { gas_meter, ) .map_err(|e| { - if let VerifySigError::OutOfGas = e { - Error::OutOfGas + if let VerifySigError::OutOfGas(inner) = e { + Error::OutOfGas(inner) } else { Error::InvalidSectionSignature( "found invalid signature.".to_string(), diff --git a/core/src/types/key/mod.rs b/core/src/types/key/mod.rs index 52af46d5dc..d05030ea5b 100644 --- a/core/src/types/key/mod.rs +++ b/core/src/types/key/mod.rs @@ -124,8 +124,8 @@ pub enum VerifySigError { MissingData, #[error("Signature belongs to a different scheme from the public key.")] MismatchedScheme, - #[error("Signature verification went out of gas")] - OutOfGas, + #[error("Signature verification went out of gas: {0}")] + OutOfGas(crate::ledger::gas::Error), } #[allow(missing_docs)] diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 3b50e55e05..1ddcb84175 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -80,6 +80,8 @@ pub struct VpsResult { pub gas_used: VpsGas, /// Errors occurred in any of the VPs, if any pub errors: Vec<(Address, String)>, + /// Sentinel to signal an invalid transaction signature + pub invalid_sig: bool, } impl fmt::Display for TxResult { diff --git a/core/src/types/validity_predicate.rs b/core/src/types/validity_predicate.rs index a40fc1f99d..60f73af912 100644 --- a/core/src/types/validity_predicate.rs +++ b/core/src/types/validity_predicate.rs @@ -17,3 +17,38 @@ pub struct EvalVp { /// The input for the `eval`ed VP pub input: Tx, } + +/// Sentinel used in validity predicates to signal events that require special +/// replay protection handling back to the protocol. +#[derive(Debug, Default)] +pub enum VpSentinel { + /// No action required + #[default] + None, + /// Exceeded gas limit + OutOfGas, + /// Found invalid transaction signature + InvalidSignature, +} + +impl VpSentinel { + /// Check if the Vp ran out of gas + pub fn is_out_of_gas(&self) -> bool { + matches!(self, Self::OutOfGas) + } + + /// Check if the Vp found an invalid signature + pub fn is_invalid_signature(&self) -> bool { + matches!(self, Self::InvalidSignature) + } + + /// Set the sentinel for an out of gas error + pub fn set_out_of_gas(&mut self) { + *self = Self::OutOfGas + } + + /// Set the sentinel for an invalid signature error + pub fn set_invalid_signature(&mut self) { + *self = Self::InvalidSignature + } +} diff --git a/shared/src/ledger/native_vp/mod.rs b/shared/src/ledger/native_vp/mod.rs index 39a6b65bb5..60c30daeb3 100644 --- a/shared/src/ledger/native_vp/mod.rs +++ b/shared/src/ledger/native_vp/mod.rs @@ -12,6 +12,7 @@ use std::collections::BTreeSet; use borsh::BorshDeserialize; use eyre::WrapErr; pub use namada_core::ledger::vp_env::VpEnv; +use namada_core::types::validity_predicate::VpSentinel; use super::storage_api::{self, ResultExt, StorageRead}; use super::vp_host_fns; @@ -66,6 +67,8 @@ where pub iterators: RefCell>, /// VP gas meter. pub gas_meter: RefCell, + /// Errors sentinel + pub sentinel: RefCell, /// Read-only access to the storage. pub storage: &'a Storage, /// Read-only access to the write log. @@ -136,6 +139,7 @@ where address, iterators: RefCell::new(PrefixIterators::default()), gas_meter: RefCell::new(gas_meter), + sentinel: RefCell::new(VpSentinel::default()), storage, write_log, tx, @@ -182,6 +186,7 @@ where self.ctx.storage, self.ctx.write_log, key, + &mut self.ctx.sentinel.borrow_mut(), ) .into_storage_result() } @@ -195,6 +200,7 @@ where self.ctx.storage, self.ctx.write_log, key, + &mut self.ctx.sentinel.borrow_mut(), ) .into_storage_result() } @@ -208,6 +214,7 @@ where self.ctx.write_log, self.ctx.storage, prefix, + &mut self.ctx.sentinel.borrow_mut(), ) .into_storage_result() } @@ -219,8 +226,12 @@ where &'iter self, iter: &mut Self::PrefixIter<'iter>, ) -> Result)>, storage_api::Error> { - vp_host_fns::iter_next::(&mut self.ctx.gas_meter.borrow_mut(), iter) - .into_storage_result() + vp_host_fns::iter_next::( + &mut self.ctx.gas_meter.borrow_mut(), + iter, + &mut self.ctx.sentinel.borrow_mut(), + ) + .into_storage_result() } fn get_chain_id(&self) -> Result { @@ -273,6 +284,7 @@ where self.ctx.storage, self.ctx.write_log, key, + &mut self.ctx.sentinel.borrow_mut(), ) .into_storage_result() } @@ -286,6 +298,7 @@ where self.ctx.storage, self.ctx.write_log, key, + &mut self.ctx.sentinel.borrow_mut(), ) .into_storage_result() } @@ -299,6 +312,7 @@ where self.ctx.write_log, self.ctx.storage, prefix, + &mut self.ctx.sentinel.borrow_mut(), ) .into_storage_result() } @@ -310,8 +324,12 @@ where &'iter self, iter: &mut Self::PrefixIter<'iter>, ) -> Result)>, storage_api::Error> { - vp_host_fns::iter_next::(&mut self.ctx.gas_meter.borrow_mut(), iter) - .into_storage_result() + vp_host_fns::iter_next::( + &mut self.ctx.gas_meter.borrow_mut(), + iter, + &mut self.ctx.sentinel.borrow_mut(), + ) + .into_storage_result() } fn get_chain_id(&self) -> Result { @@ -372,6 +390,7 @@ where &mut self.gas_meter.borrow_mut(), self.write_log, key, + &mut self.sentinel.borrow_mut(), ) .map(|data| data.and_then(|t| T::try_from_slice(&t[..]).ok())) .into_storage_result() @@ -385,6 +404,7 @@ where &mut self.gas_meter.borrow_mut(), self.write_log, key, + &mut self.sentinel.borrow_mut(), ) .into_storage_result() } @@ -393,6 +413,7 @@ where vp_host_fns::get_chain_id( &mut self.gas_meter.borrow_mut(), self.storage, + &mut self.sentinel.borrow_mut(), ) .into_storage_result() } @@ -401,6 +422,7 @@ where vp_host_fns::get_block_height( &mut self.gas_meter.borrow_mut(), self.storage, + &mut self.sentinel.borrow_mut(), ) .into_storage_result() } @@ -413,6 +435,7 @@ where &mut self.gas_meter.borrow_mut(), self.storage, height, + &mut self.sentinel.borrow_mut(), ) .into_storage_result() } @@ -421,6 +444,7 @@ where vp_host_fns::get_block_hash( &mut self.gas_meter.borrow_mut(), self.storage, + &mut self.sentinel.borrow_mut(), ) .into_storage_result() } @@ -429,6 +453,7 @@ where vp_host_fns::get_block_epoch( &mut self.gas_meter.borrow_mut(), self.storage, + &mut self.sentinel.borrow_mut(), ) .into_storage_result() } @@ -437,6 +462,7 @@ where vp_host_fns::get_tx_index( &mut self.gas_meter.borrow_mut(), self.tx_index, + &mut self.sentinel.borrow_mut(), ) .into_storage_result() } @@ -445,6 +471,7 @@ where vp_host_fns::get_native_token( &mut self.gas_meter.borrow_mut(), self.storage, + &mut self.sentinel.borrow_mut(), ) .into_storage_result() } @@ -470,6 +497,7 @@ where self.write_log, self.storage, prefix, + &mut self.sentinel.borrow_mut(), ) .into_storage_result() } @@ -501,6 +529,7 @@ where self.storage, self.write_log, &mut self.gas_meter.borrow_mut(), + &mut self.sentinel.borrow_mut(), self.tx, self.tx_index, &mut iterators, @@ -543,8 +572,12 @@ where } fn get_tx_code_hash(&self) -> Result, storage_api::Error> { - vp_host_fns::get_tx_code_hash(&mut self.gas_meter.borrow_mut(), self.tx) - .into_storage_result() + vp_host_fns::get_tx_code_hash( + &mut self.gas_meter.borrow_mut(), + self.tx, + &mut self.sentinel.borrow_mut(), + ) + .into_storage_result() } fn read_pre( diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index b8a9902ac5..314f8facae 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -16,7 +16,7 @@ use namada_core::types::transaction::WrapperTx; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use thiserror::Error; -use crate::ledger::gas::{self, GasMetering, VpGasMeter}; +use crate::ledger::gas::{GasMetering, VpGasMeter}; use crate::ledger::governance::GovernanceVp; use crate::ledger::native_vp::ethereum_bridge::bridge_pool_vp::BridgePoolVp; use crate::ledger::native_vp::ethereum_bridge::nut::NonUsableTokens; @@ -56,10 +56,12 @@ pub enum Error { TxTypeError, #[error("Fee ushielding error: {0}")] FeeUnshieldingError(crate::types::transaction::WrapperTxErr), - #[error("{0}")] - GasError(#[from] gas::Error), + #[error("Gas error: {0}")] + GasError(String), #[error("Error while processing transaction's fees: {0}")] FeeError(String), + #[error("Invalid transaction signature")] + InvalidTxSignature, #[error("Error executing VP for addresses: {0:?}")] VpRunnerError(vm::wasm::run::Error), #[error("The address {0} doesn't exist")] @@ -242,7 +244,10 @@ where )?; // Account for gas - shell_params.tx_gas_meter.add_tx_size_gas(tx_bytes)?; + shell_params + .tx_gas_meter + .add_tx_size_gas(tx_bytes) + .map_err(|err| Error::GasError(err.to_string()))?; // If wrapper was succesful, write inner tx hash to storage shell_params @@ -280,7 +285,7 @@ pub fn get_fee_unshielding_transaction( /// - Fee amount overflows /// - Not enough funds are available to pay the entire amount of the fee /// - The accumulated fee amount to be credited to the block proposer overflows -pub fn charge_fee<'a, D, H, CA, WLS>( +fn charge_fee<'a, D, H, CA, WLS>( wrapper: &WrapperTx, masp_transaction: Option, shell_params: &mut ShellParams<'a, CA, WLS>, @@ -708,12 +713,9 @@ where vp_wasm_cache, tx_wasm_cache, ) - .map_err(|e| { - if let wasm::run::Error::GasError(gas_error) = e { - Error::GasError(gas_error) - } else { - Error::TxRunnerError(e) - } + .map_err(|err| match err { + wasm::run::Error::GasError(msg) => Error::GasError(msg), + _ => Error::TxRunnerError(err), }) } @@ -765,7 +767,9 @@ where )?; tracing::debug!("Total VPs gas cost {:?}", vps_result.gas_used); - tx_gas_meter.add_vps_gas(&vps_result.gas_used)?; + tx_gas_meter + .add_vps_gas(&vps_result.gas_used) + .map_err(|err| Error::GasError(err.to_string()))?; Ok(vps_result) } @@ -787,7 +791,7 @@ where H: 'static + StorageHasher + Sync, CA: 'static + WasmCacheAccess + Sync, { - verifiers + let vps_result = verifiers .par_iter() .try_fold(VpsResult::default, |mut result, addr| { let mut gas_meter = VpGasMeter::new_from_tx_meter(tx_gas_meter); @@ -796,14 +800,17 @@ where let (vp_hash, gas) = storage .validity_predicate(addr) .map_err(Error::StorageError)?; - gas_meter.consume(gas).map_err(Error::GasError)?; + gas_meter + .consume(gas) + .map_err(|err| Error::GasError(err.to_string()))?; let Some(vp_code_hash) = vp_hash else { 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 + // 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, @@ -817,7 +824,13 @@ where &verifiers, vp_wasm_cache.clone(), ) - .map_err(Error::VpRunnerError) + .map_err(|err| match err { + wasm::run::Error::GasError(msg) => Error::GasError(msg), + wasm::run::Error::InvalidTxSignature => { + Error::InvalidTxSignature + } + _ => Error::VpRunnerError(err), + }) } Address::Internal(internal_addr) => { let ctx = native_vp::Ctx::new( @@ -832,139 +845,227 @@ where vp_wasm_cache.clone(), ); - let accepted: Result = match internal_addr { - InternalAddress::PoS => { - let pos = PosVP { ctx }; - let verifiers_addr_ref = &verifiers; - let pos_ref = &pos; - // TODO this is temporarily ran in a new thread to - // avoid crashing the ledger (required `UnwindSafe` - // and `RefUnwindSafe` in - // shared/src/ledger/pos/vp.rs) - let keys_changed_ref = &keys_changed; - let result = match panic::catch_unwind(move || { - pos_ref - .validate_tx( - tx, - keys_changed_ref, - verifiers_addr_ref, - ) - .map_err(Error::PosNativeVpError) - }) { - Ok(result) => result, - Err(err) => { - tracing::error!( - "PoS native VP failed with {:#?}", - err - ); - Err(Error::PosNativeVpRuntime) - } - }; - // Take the gas meter back out of the context - gas_meter = pos.ctx.gas_meter.into_inner(); - result - } - InternalAddress::Ibc => { - let ibc = Ibc { ctx }; - let result = ibc - .validate_tx(tx, &keys_changed, &verifiers) - .map_err(Error::IbcNativeVpError); - // Take the gas meter back out of the context - gas_meter = ibc.ctx.gas_meter.into_inner(); - result - } - InternalAddress::Parameters => { - let parameters = ParametersVp { ctx }; - let result = parameters - .validate_tx(tx, &keys_changed, &verifiers) - .map_err(Error::ParametersNativeVpError); - // Take the gas meter back out of the context - gas_meter = parameters.ctx.gas_meter.into_inner(); - result - } - InternalAddress::PosSlashPool => { - // Take the gas meter back out of the context - gas_meter = ctx.gas_meter.into_inner(); - Err(Error::AccessForbidden( - (*internal_addr).clone(), - )) - } - InternalAddress::Governance => { - let governance = GovernanceVp { ctx }; - let result = governance - .validate_tx(tx, &keys_changed, &verifiers) - .map_err(Error::GovernanceNativeVpError); - gas_meter = governance.ctx.gas_meter.into_inner(); - result + let (accepted, sentinel): (Result, _) = + match internal_addr { + InternalAddress::PoS => { + let pos = PosVP { ctx }; + let verifiers_addr_ref = &verifiers; + let pos_ref = &pos; + // TODO this is temporarily ran in a new thread + // to + // avoid crashing the ledger (required + // `UnwindSafe` + // and `RefUnwindSafe` in + // shared/src/ledger/pos/vp.rs) + let keys_changed_ref = &keys_changed; + let result = + match panic::catch_unwind(move || { + pos_ref + .validate_tx( + tx, + keys_changed_ref, + verifiers_addr_ref, + ) + .map_err(Error::PosNativeVpError) + }) { + Ok(result) => result, + Err(err) => { + tracing::error!( + "PoS native VP failed with \ + {:#?}", + err + ); + Err(Error::PosNativeVpRuntime) + } + }; + // Take the gas meter and sentinel + // back + // out of the context + gas_meter = pos.ctx.gas_meter.into_inner(); + (result, pos.ctx.sentinel.into_inner()) + } + InternalAddress::Ibc => { + let ibc = Ibc { ctx }; + let result = ibc + .validate_tx(tx, &keys_changed, &verifiers) + .map_err(Error::IbcNativeVpError); + // Take the gas meter and the sentinel + // back + // out of the context + gas_meter = ibc.ctx.gas_meter.into_inner(); + (result, ibc.ctx.sentinel.into_inner()) + } + InternalAddress::Parameters => { + let parameters = ParametersVp { ctx }; + let result = parameters + .validate_tx(tx, &keys_changed, &verifiers) + .map_err(Error::ParametersNativeVpError); + // Take the gas meter and the sentinel + // back + // out of the context + gas_meter = + parameters.ctx.gas_meter.into_inner(); + (result, parameters.ctx.sentinel.into_inner()) + } + InternalAddress::PosSlashPool => { + // Take the gas meter and the sentinel + // back + // out of the context + gas_meter = ctx.gas_meter.into_inner(); + ( + Err(Error::AccessForbidden( + (*internal_addr).clone(), + )), + ctx.sentinel.into_inner(), + ) + } + InternalAddress::Governance => { + let governance = GovernanceVp { ctx }; + let result = governance + .validate_tx(tx, &keys_changed, &verifiers) + .map_err(Error::GovernanceNativeVpError); + // Take the gas meter and the sentinel + // back + // out of the context + gas_meter = + governance.ctx.gas_meter.into_inner(); + (result, governance.ctx.sentinel.into_inner()) + } + InternalAddress::Multitoken => { + let multitoken = MultitokenVp { ctx }; + let result = multitoken + .validate_tx(tx, &keys_changed, &verifiers) + .map_err(Error::MultitokenNativeVpError); + // Take the gas meter and the sentinel + // back + // out of the context + gas_meter = + multitoken.ctx.gas_meter.into_inner(); + (result, multitoken.ctx.sentinel.into_inner()) + } + InternalAddress::EthBridge => { + let bridge = EthBridge { ctx }; + let result = bridge + .validate_tx(tx, &keys_changed, &verifiers) + .map_err(Error::EthBridgeNativeVpError); + // Take the gas meter and the sentinel + // back + // out of the context + gas_meter = bridge.ctx.gas_meter.into_inner(); + (result, bridge.ctx.sentinel.into_inner()) + } + InternalAddress::EthBridgePool => { + let bridge_pool = BridgePoolVp { ctx }; + let result = bridge_pool + .validate_tx(tx, &keys_changed, &verifiers) + .map_err(Error::BridgePoolNativeVpError); + // Take the gas meter and the sentinel + // back + // out of the context + gas_meter = + bridge_pool.ctx.gas_meter.into_inner(); + (result, bridge_pool.ctx.sentinel.into_inner()) + } + InternalAddress::Pgf => { + let pgf_vp = PgfVp { ctx }; + let result = pgf_vp + .validate_tx(tx, &keys_changed, &verifiers) + .map_err(Error::PgfNativeVpError); + // Take the gas meter and the sentinel + // back + // out of the context + gas_meter = pgf_vp.ctx.gas_meter.into_inner(); + (result, pgf_vp.ctx.sentinel.into_inner()) + } + InternalAddress::Nut(_) => { + let non_usable_tokens = NonUsableTokens { ctx }; + let result = non_usable_tokens + .validate_tx(tx, &keys_changed, &verifiers) + .map_err(Error::NutNativeVpError); + // Take the gas meter and the sentinel + // back + // out of the context + gas_meter = non_usable_tokens + .ctx + .gas_meter + .into_inner(); + ( + result, + non_usable_tokens.ctx.sentinel.into_inner(), + ) + } + InternalAddress::IbcToken(_) + | InternalAddress::Erc20(_) => { + // The address should be a part of a multitoken + // key + // Take the gas meter and the sentinel + // back + // out of the context + gas_meter = ctx.gas_meter.into_inner(); + ( + Ok(verifiers.contains(&Address::Internal( + InternalAddress::Multitoken, + ))), + ctx.sentinel.into_inner(), + ) + } + }; + + accepted.map_err(|err| { + // No need to check invalid sig because internal vps + // don't check the signature + if sentinel.is_out_of_gas() { + Error::GasError(err.to_string()) + } else { + err } - InternalAddress::Multitoken => { - let multitoken = MultitokenVp { ctx }; - let result = multitoken - .validate_tx(tx, &keys_changed, &verifiers) - .map_err(Error::MultitokenNativeVpError); - gas_meter = multitoken.ctx.gas_meter.into_inner(); - result - } - InternalAddress::EthBridge => { - let bridge = EthBridge { ctx }; - let result = bridge - .validate_tx(tx, &keys_changed, &verifiers) - .map_err(Error::EthBridgeNativeVpError); - gas_meter = bridge.ctx.gas_meter.into_inner(); - result - } - InternalAddress::EthBridgePool => { - let bridge_pool = BridgePoolVp { ctx }; - let result = bridge_pool - .validate_tx(tx, &keys_changed, &verifiers) - .map_err(Error::BridgePoolNativeVpError); - gas_meter = bridge_pool.ctx.gas_meter.into_inner(); - result - } - InternalAddress::Pgf => { - let pgf_vp = PgfVp { ctx }; - let result = pgf_vp - .validate_tx(tx, &keys_changed, &verifiers) - .map_err(Error::PgfNativeVpError); - gas_meter = pgf_vp.ctx.gas_meter.into_inner(); - result - } - InternalAddress::Nut(_) => { - let non_usable_tokens = NonUsableTokens { ctx }; - let result = non_usable_tokens - .validate_tx(tx, &keys_changed, &verifiers) - .map_err(Error::NutNativeVpError); - gas_meter = - non_usable_tokens.ctx.gas_meter.into_inner(); - result - } - InternalAddress::IbcToken(_) - | InternalAddress::Erc20(_) => { - // The address should be a part of a multitoken key - gas_meter = ctx.gas_meter.into_inner(); - Ok(verifiers.contains(&Address::Internal( - InternalAddress::Multitoken, - ))) - } - }; - - accepted + }) } }; - // Returning error from here will short-circuit the VP parallel - // execution. - result.gas_used.set(gas_meter).map_err(Error::GasError)?; - if accept? { - result.accepted_vps.insert(addr.clone()); - } else { - result.rejected_vps.insert(addr.clone()); + match accept { + Ok(accepted) => { + if accepted { + result.accepted_vps.insert(addr.clone()); + } else { + result.rejected_vps.insert(addr.clone()); + } + } + Err(err) => match err { + // Execution of VPs can (and must) be short-circuited + // only in case of a gas overflow to prevent the + // transaction from conuming resources that have not + // been acquired in the corresponding wrapper tx. For + // all the other errors we keep evaluating the vps. This + // allow to display a consistent VpsResult accross all + // nodes and find any invalid signatures + Error::GasError(_) => { + return Err(err); + } + Error::InvalidTxSignature => { + result.invalid_sig = true; + result.rejected_vps.insert(addr.clone()); + // Don't push the error since this is just a flag error + } + _ => { + result.rejected_vps.insert(addr.clone()); + result.errors.push((addr.clone(), err.to_string())); + } + }, } + + result + .gas_used + .set(gas_meter) + .map_err(|err| Error::GasError(err.to_string()))?; + Ok(result) }) .try_reduce(VpsResult::default, |a, b| { merge_vp_results(a, b, tx_gas_meter) - }) + })?; + + Ok(vps_result) } /// Merge VP results from parallel runs @@ -979,15 +1080,19 @@ fn merge_vp_results( rejected_vps.extend(b.rejected_vps); let mut errors = a.errors; errors.append(&mut b.errors); + let invalid_sig = a.invalid_sig || b.invalid_sig; let mut gas_used = a.gas_used; - gas_used.merge(b.gas_used, tx_gas_meter)?; + gas_used + .merge(b.gas_used, tx_gas_meter) + .map_err(|err| Error::GasError(err.to_string()))?; Ok(VpsResult { accepted_vps, rejected_vps, gas_used, errors, + invalid_sig, }) } diff --git a/shared/src/ledger/vp_host_fns.rs b/shared/src/ledger/vp_host_fns.rs index ef8831ae88..757b2d688b 100644 --- a/shared/src/ledger/vp_host_fns.rs +++ b/shared/src/ledger/vp_host_fns.rs @@ -7,6 +7,7 @@ use namada_core::types::hash::Hash; use namada_core::types::storage::{ BlockHash, BlockHeight, Epoch, Header, Key, TxIndex, }; +use namada_core::types::validity_predicate::VpSentinel; use thiserror::Error; use super::gas::STORAGE_ACCESS_GAS_PER_BYTE; @@ -45,12 +46,16 @@ pub enum RuntimeError { pub type EnvResult = std::result::Result; /// Add a gas cost incured in a validity predicate -pub fn add_gas(gas_meter: &mut VpGasMeter, used_gas: u64) -> EnvResult<()> { - let result = gas_meter.consume(used_gas).map_err(RuntimeError::OutOfGas); - if let Err(err) = &result { +pub fn add_gas( + gas_meter: &mut VpGasMeter, + used_gas: u64, + sentinel: &mut VpSentinel, +) -> EnvResult<()> { + gas_meter.consume(used_gas).map_err(|err| { + sentinel.set_out_of_gas(); tracing::info!("Stopping VP execution because of gas error: {}", err); - } - result + RuntimeError::OutOfGas(err) + }) } /// Storage read prior state (before tx execution). It will try to read from the @@ -60,13 +65,14 @@ pub fn read_pre( storage: &Storage, write_log: &WriteLog, key: &Key, + sentinel: &mut VpSentinel, ) -> EnvResult>> where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, { let (log_val, gas) = write_log.read_pre(key); - add_gas(gas_meter, gas)?; + add_gas(gas_meter, gas, sentinel)?; match log_val { Some(write_log::StorageModification::Write { ref value }) => { Ok(Some(value.clone())) @@ -88,7 +94,7 @@ where // When not found in write log, try to read from the storage let (value, gas) = storage.read(key).map_err(RuntimeError::StorageError)?; - add_gas(gas_meter, gas)?; + add_gas(gas_meter, gas, sentinel)?; Ok(value) } } @@ -101,6 +107,7 @@ pub fn read_post( storage: &Storage, write_log: &WriteLog, key: &Key, + sentinel: &mut VpSentinel, ) -> EnvResult>> where DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -108,7 +115,7 @@ where { // Try to read from the write log first let (log_val, gas) = write_log.read(key); - add_gas(gas_meter, gas)?; + add_gas(gas_meter, gas, sentinel)?; match log_val { Some(write_log::StorageModification::Write { ref value }) => { Ok(Some(value.clone())) @@ -130,7 +137,7 @@ where // When not found in write log, try to read from the storage let (value, gas) = storage.read(key).map_err(RuntimeError::StorageError)?; - add_gas(gas_meter, gas)?; + add_gas(gas_meter, gas, sentinel)?; Ok(value) } } @@ -142,10 +149,11 @@ pub fn read_temp( gas_meter: &mut VpGasMeter, write_log: &WriteLog, key: &Key, + sentinel: &mut VpSentinel, ) -> EnvResult>> { // Try to read from the write log first let (log_val, gas) = write_log.read(key); - add_gas(gas_meter, gas)?; + add_gas(gas_meter, gas, sentinel)?; match log_val { Some(write_log::StorageModification::Temp { ref value }) => { Ok(Some(value.clone())) @@ -162,6 +170,7 @@ pub fn has_key_pre( storage: &Storage, write_log: &WriteLog, key: &Key, + sentinel: &mut VpSentinel, ) -> EnvResult where DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -169,7 +178,7 @@ where { // Try to read from the write log first let (log_val, gas) = write_log.read_pre(key); - add_gas(gas_meter, gas)?; + add_gas(gas_meter, gas, sentinel)?; match log_val { Some(&write_log::StorageModification::Write { .. }) => Ok(true), Some(&write_log::StorageModification::Delete) => { @@ -182,7 +191,7 @@ where // When not found in write log, try to check the storage let (present, gas) = storage.has_key(key).map_err(RuntimeError::StorageError)?; - add_gas(gas_meter, gas)?; + add_gas(gas_meter, gas, sentinel)?; Ok(present) } } @@ -195,6 +204,7 @@ pub fn has_key_post( storage: &Storage, write_log: &WriteLog, key: &Key, + sentinel: &mut VpSentinel, ) -> EnvResult where DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -202,7 +212,7 @@ where { // Try to read from the write log first let (log_val, gas) = write_log.read(key); - add_gas(gas_meter, gas)?; + add_gas(gas_meter, gas, sentinel)?; match log_val { Some(&write_log::StorageModification::Write { .. }) => Ok(true), Some(&write_log::StorageModification::Delete) => { @@ -215,7 +225,7 @@ where // When not found in write log, try to check the storage let (present, gas) = storage.has_key(key).map_err(RuntimeError::StorageError)?; - add_gas(gas_meter, gas)?; + add_gas(gas_meter, gas, sentinel)?; Ok(present) } } @@ -225,13 +235,14 @@ where pub fn get_chain_id( gas_meter: &mut VpGasMeter, storage: &Storage, + sentinel: &mut VpSentinel, ) -> EnvResult where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, { let (chain_id, gas) = storage.get_chain_id(); - add_gas(gas_meter, gas)?; + add_gas(gas_meter, gas, sentinel)?; Ok(chain_id) } @@ -240,13 +251,14 @@ where pub fn get_block_height( gas_meter: &mut VpGasMeter, storage: &Storage, + sentinel: &mut VpSentinel, ) -> EnvResult where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, { let (height, gas) = storage.get_block_height(); - add_gas(gas_meter, gas)?; + add_gas(gas_meter, gas, sentinel)?; Ok(height) } @@ -255,6 +267,7 @@ pub fn get_block_header( gas_meter: &mut VpGasMeter, storage: &Storage, height: BlockHeight, + sentinel: &mut VpSentinel, ) -> EnvResult> where DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -263,7 +276,7 @@ where let (header, gas) = storage .get_block_header(Some(height)) .map_err(RuntimeError::StorageError)?; - add_gas(gas_meter, gas)?; + add_gas(gas_meter, gas, sentinel)?; Ok(header) } @@ -272,13 +285,14 @@ where pub fn get_block_hash( gas_meter: &mut VpGasMeter, storage: &Storage, + sentinel: &mut VpSentinel, ) -> EnvResult where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, { let (hash, gas) = storage.get_block_hash(); - add_gas(gas_meter, gas)?; + add_gas(gas_meter, gas, sentinel)?; Ok(hash) } @@ -287,12 +301,13 @@ where pub fn get_tx_code_hash( gas_meter: &mut VpGasMeter, tx: &Tx, + sentinel: &mut VpSentinel, ) -> EnvResult> { let hash = tx .get_section(tx.code_sechash()) .and_then(|x| Section::code_sec(x.as_ref())) .map(|x| x.code.hash()); - add_gas(gas_meter, STORAGE_ACCESS_GAS_PER_BYTE)?; + add_gas(gas_meter, STORAGE_ACCESS_GAS_PER_BYTE, sentinel)?; Ok(hash) } @@ -301,13 +316,14 @@ pub fn get_tx_code_hash( pub fn get_block_epoch( gas_meter: &mut VpGasMeter, storage: &Storage, + sentinel: &mut VpSentinel, ) -> EnvResult where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, { let (epoch, gas) = storage.get_current_epoch(); - add_gas(gas_meter, gas)?; + add_gas(gas_meter, gas, sentinel)?; Ok(epoch) } @@ -316,8 +332,9 @@ where pub fn get_tx_index( gas_meter: &mut VpGasMeter, tx_index: &TxIndex, + sentinel: &mut VpSentinel, ) -> EnvResult { - add_gas(gas_meter, STORAGE_ACCESS_GAS_PER_BYTE)?; + add_gas(gas_meter, STORAGE_ACCESS_GAS_PER_BYTE, sentinel)?; Ok(*tx_index) } @@ -325,12 +342,13 @@ pub fn get_tx_index( pub fn get_native_token( gas_meter: &mut VpGasMeter, storage: &Storage, + sentinel: &mut VpSentinel, ) -> EnvResult
where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, { - add_gas(gas_meter, STORAGE_ACCESS_GAS_PER_BYTE)?; + add_gas(gas_meter, STORAGE_ACCESS_GAS_PER_BYTE, sentinel)?; Ok(storage.native_token.clone()) } @@ -355,13 +373,14 @@ pub fn iter_prefix_pre<'a, DB, H>( write_log: &'a WriteLog, storage: &'a Storage, prefix: &Key, + sentinel: &mut VpSentinel, ) -> EnvResult> where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, { let (iter, gas) = storage::iter_prefix_pre(write_log, storage, prefix); - add_gas(gas_meter, gas)?; + add_gas(gas_meter, gas, sentinel)?; Ok(iter) } @@ -372,13 +391,14 @@ pub fn iter_prefix_post<'a, DB, H>( write_log: &'a WriteLog, storage: &'a Storage, prefix: &Key, + sentinel: &mut VpSentinel, ) -> EnvResult> where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, { let (iter, gas) = storage::iter_prefix_post(write_log, storage, prefix); - add_gas(gas_meter, gas)?; + add_gas(gas_meter, gas, sentinel)?; Ok(iter) } @@ -386,12 +406,13 @@ where pub fn iter_next( gas_meter: &mut VpGasMeter, iter: &mut storage::PrefixIter, + sentinel: &mut VpSentinel, ) -> EnvResult)>> where DB: storage::DB + for<'iter> storage::DBIter<'iter>, { if let Some((key, val, gas)) = iter.next() { - add_gas(gas_meter, gas)?; + add_gas(gas_meter, gas, sentinel)?; return Ok(Some((key, val))); } Ok(None) diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 5f701b7e1f..304fcf7285 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -9,6 +9,7 @@ use borsh_ext::BorshSerializeExt; use masp_primitives::transaction::Transaction; use namada_core::ledger::gas::{GasMetering, TxGasMeter}; use namada_core::types::internal::KeyVal; +use namada_core::types::validity_predicate::VpSentinel; use thiserror::Error; #[cfg(feature = "wasm-runtime")] @@ -100,6 +101,8 @@ where pub iterators: MutHostRef<'a, &'a PrefixIterators<'a, DB>>, /// Transaction gas meter. pub gas_meter: MutHostRef<'a, &'a TxGasMeter>, + /// Out-of-gas sentinel + pub out_of_gas: MutHostRef<'a, &'a bool>, /// The transaction code is used for signature verification pub tx: HostRef<'a, &'a Tx>, /// The transaction index is used to identify a shielded transaction's @@ -142,6 +145,7 @@ where write_log: &mut WriteLog, iterators: &mut PrefixIterators<'a, DB>, gas_meter: &mut TxGasMeter, + out_of_gas: &mut bool, tx: &Tx, tx_index: &TxIndex, verifiers: &mut BTreeSet
, @@ -153,6 +157,7 @@ where let write_log = unsafe { MutHostRef::new(write_log) }; let iterators = unsafe { MutHostRef::new(iterators) }; let gas_meter = unsafe { MutHostRef::new(gas_meter) }; + let out_of_gas = unsafe { MutHostRef::new(out_of_gas) }; let tx = unsafe { HostRef::new(tx) }; let tx_index = unsafe { HostRef::new(tx_index) }; let verifiers = unsafe { MutHostRef::new(verifiers) }; @@ -166,6 +171,7 @@ where write_log, iterators, gas_meter, + out_of_gas, tx, tx_index, verifiers, @@ -209,6 +215,7 @@ where write_log: self.write_log.clone(), iterators: self.iterators.clone(), gas_meter: self.gas_meter.clone(), + out_of_gas: self.out_of_gas.clone(), tx: self.tx.clone(), tx_index: self.tx_index.clone(), verifiers: self.verifiers.clone(), @@ -256,6 +263,8 @@ where pub iterators: MutHostRef<'a, &'a PrefixIterators<'a, DB>>, /// VP gas meter. pub gas_meter: MutHostRef<'a, &'a VpGasMeter>, + /// Errors sentinel + pub sentinel: MutHostRef<'a, &'a VpSentinel>, /// The transaction code is used for signature verification pub tx: HostRef<'a, &'a Tx>, /// The transaction index is used to identify a shielded transaction's @@ -324,6 +333,7 @@ where storage: &Storage, write_log: &WriteLog, gas_meter: &mut VpGasMeter, + sentinel: &mut VpSentinel, tx: &Tx, tx_index: &TxIndex, iterators: &mut PrefixIterators<'a, DB>, @@ -338,6 +348,7 @@ where storage, write_log, gas_meter, + sentinel, tx, tx_index, iterators, @@ -389,6 +400,7 @@ where storage: &Storage, write_log: &WriteLog, gas_meter: &mut VpGasMeter, + sentinel: &mut VpSentinel, tx: &Tx, tx_index: &TxIndex, iterators: &mut PrefixIterators<'a, DB>, @@ -405,6 +417,7 @@ where let tx_index = unsafe { HostRef::new(tx_index) }; let iterators = unsafe { MutHostRef::new(iterators) }; let gas_meter = unsafe { MutHostRef::new(gas_meter) }; + let sentinel = unsafe { MutHostRef::new(sentinel) }; let verifiers = unsafe { HostRef::new(verifiers) }; let result_buffer = unsafe { MutHostRef::new(result_buffer) }; let keys_changed = unsafe { HostRef::new(keys_changed) }; @@ -417,6 +430,7 @@ where write_log, iterators, gas_meter, + sentinel, tx, tx_index, eval_runner, @@ -445,6 +459,7 @@ where write_log: self.write_log.clone(), iterators: self.iterators.clone(), gas_meter: self.gas_meter.clone(), + sentinel: self.sentinel.clone(), tx: self.tx.clone(), tx_index: self.tx_index.clone(), eval_runner: self.eval_runner.clone(), @@ -472,16 +487,16 @@ where { let gas_meter = unsafe { env.ctx.gas_meter.get() }; // if we run out of gas, we need to stop the execution - let result = gas_meter - .consume(used_gas) - .map_err(TxRuntimeError::OutOfGas); - if let Err(err) = &result { + gas_meter.consume(used_gas).map_err(|err| { + let sentinel = unsafe { env.ctx.out_of_gas.get() }; + *sentinel = true; tracing::info!( "Stopping transaction execution because of gas error: {}", err ); - } - result + + TxRuntimeError::OutOfGas(err) + }) } /// Called from VP wasm to request to use the given gas amount @@ -497,7 +512,8 @@ where CA: WasmCacheAccess, { let gas_meter = unsafe { env.ctx.gas_meter.get() }; - vp_host_fns::add_gas(gas_meter, used_gas) + let sentinel = unsafe { env.ctx.sentinel.get() }; + vp_host_fns::add_gas(gas_meter, used_gas, sentinel) } /// Storage `has_key` function exposed to the wasm VM Tx environment. It will @@ -1030,14 +1046,16 @@ where .read_string(key_ptr, key_len as _) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; let gas_meter = unsafe { env.ctx.gas_meter.get() }; - vp_host_fns::add_gas(gas_meter, gas)?; + let sentinel = unsafe { env.ctx.sentinel.get() }; + vp_host_fns::add_gas(gas_meter, gas, sentinel)?; // try to read from the storage let key = Key::parse(key).map_err(vp_host_fns::RuntimeError::StorageDataError)?; let storage = unsafe { env.ctx.storage.get() }; let write_log = unsafe { env.ctx.write_log.get() }; - let value = vp_host_fns::read_pre(gas_meter, storage, write_log, &key)?; + let value = + vp_host_fns::read_pre(gas_meter, storage, write_log, &key, sentinel)?; tracing::debug!( "vp_read_pre addr {}, key {}, value {:?}", unsafe { env.ctx.address.get() }, @@ -1081,7 +1099,8 @@ where .read_string(key_ptr, key_len as _) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; let gas_meter = unsafe { env.ctx.gas_meter.get() }; - vp_host_fns::add_gas(gas_meter, gas)?; + let sentinel = unsafe { env.ctx.sentinel.get() }; + vp_host_fns::add_gas(gas_meter, gas, sentinel)?; tracing::debug!("vp_read_post {}, key {}", key, key_ptr,); @@ -1090,7 +1109,8 @@ where Key::parse(key).map_err(vp_host_fns::RuntimeError::StorageDataError)?; let storage = unsafe { env.ctx.storage.get() }; let write_log = unsafe { env.ctx.write_log.get() }; - let value = vp_host_fns::read_post(gas_meter, storage, write_log, &key)?; + let value = + vp_host_fns::read_post(gas_meter, storage, write_log, &key, sentinel)?; Ok(match value { Some(value) => { let len: i64 = value @@ -1127,7 +1147,8 @@ where .read_string(key_ptr, key_len as _) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; let gas_meter = unsafe { env.ctx.gas_meter.get() }; - vp_host_fns::add_gas(gas_meter, gas)?; + let sentinel = unsafe { env.ctx.sentinel.get() }; + vp_host_fns::add_gas(gas_meter, gas, sentinel)?; tracing::debug!("vp_read_temp {}, key {}", key, key_ptr); @@ -1135,7 +1156,7 @@ where let key = Key::parse(key).map_err(vp_host_fns::RuntimeError::StorageDataError)?; let write_log = unsafe { env.ctx.write_log.get() }; - let value = vp_host_fns::read_temp(gas_meter, write_log, &key)?; + let value = vp_host_fns::read_temp(gas_meter, write_log, &key, sentinel)?; Ok(match value { Some(value) => { let len: i64 = value @@ -1176,7 +1197,8 @@ where .write_bytes(result_ptr, value) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; let gas_meter = unsafe { env.ctx.gas_meter.get() }; - vp_host_fns::add_gas(gas_meter, gas) + let sentinel = unsafe { env.ctx.sentinel.get() }; + vp_host_fns::add_gas(gas_meter, gas, sentinel) } /// Storage `has_key` in prior state (before tx execution) function exposed to @@ -1198,7 +1220,8 @@ where .read_string(key_ptr, key_len as _) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; let gas_meter = unsafe { env.ctx.gas_meter.get() }; - vp_host_fns::add_gas(gas_meter, gas)?; + let sentinel = unsafe { env.ctx.sentinel.get() }; + vp_host_fns::add_gas(gas_meter, gas, sentinel)?; tracing::debug!("vp_has_key_pre {}, key {}", key, key_ptr,); @@ -1206,8 +1229,9 @@ where Key::parse(key).map_err(vp_host_fns::RuntimeError::StorageDataError)?; let storage = unsafe { env.ctx.storage.get() }; let write_log = unsafe { env.ctx.write_log.get() }; - let present = - vp_host_fns::has_key_pre(gas_meter, storage, write_log, &key)?; + let present = vp_host_fns::has_key_pre( + gas_meter, storage, write_log, &key, sentinel, + )?; Ok(HostEnvResult::from(present).to_i64()) } @@ -1231,7 +1255,8 @@ where .read_string(key_ptr, key_len as _) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; let gas_meter = unsafe { env.ctx.gas_meter.get() }; - vp_host_fns::add_gas(gas_meter, gas)?; + let sentinel = unsafe { env.ctx.sentinel.get() }; + vp_host_fns::add_gas(gas_meter, gas, sentinel)?; tracing::debug!("vp_has_key_post {}, key {}", key, key_ptr,); @@ -1239,8 +1264,9 @@ where Key::parse(key).map_err(vp_host_fns::RuntimeError::StorageDataError)?; let storage = unsafe { env.ctx.storage.get() }; let write_log = unsafe { env.ctx.write_log.get() }; - let present = - vp_host_fns::has_key_post(gas_meter, storage, write_log, &key)?; + let present = vp_host_fns::has_key_post( + gas_meter, storage, write_log, &key, sentinel, + )?; Ok(HostEnvResult::from(present).to_i64()) } @@ -1265,7 +1291,8 @@ where .read_string(prefix_ptr, prefix_len as _) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; let gas_meter = unsafe { env.ctx.gas_meter.get() }; - vp_host_fns::add_gas(gas_meter, gas)?; + let sentinel = unsafe { env.ctx.sentinel.get() }; + vp_host_fns::add_gas(gas_meter, gas, sentinel)?; tracing::debug!("vp_iter_prefix_pre {}", prefix); @@ -1274,8 +1301,9 @@ where let write_log = unsafe { env.ctx.write_log.get() }; let storage = unsafe { env.ctx.storage.get() }; - let iter = - vp_host_fns::iter_prefix_pre(gas_meter, write_log, storage, &prefix)?; + let iter = vp_host_fns::iter_prefix_pre( + gas_meter, write_log, storage, &prefix, sentinel, + )?; let iterators = unsafe { env.ctx.iterators.get() }; Ok(iterators.insert(iter).id()) @@ -1302,7 +1330,8 @@ where .read_string(prefix_ptr, prefix_len as _) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; let gas_meter = unsafe { env.ctx.gas_meter.get() }; - vp_host_fns::add_gas(gas_meter, gas)?; + let sentinel = unsafe { env.ctx.sentinel.get() }; + vp_host_fns::add_gas(gas_meter, gas, sentinel)?; tracing::debug!("vp_iter_prefix_post {}", prefix); @@ -1311,8 +1340,9 @@ where let write_log = unsafe { env.ctx.write_log.get() }; let storage = unsafe { env.ctx.storage.get() }; - let iter = - vp_host_fns::iter_prefix_post(gas_meter, write_log, storage, &prefix)?; + let iter = vp_host_fns::iter_prefix_post( + gas_meter, write_log, storage, &prefix, sentinel, + )?; let iterators = unsafe { env.ctx.iterators.get() }; Ok(iterators.insert(iter).id()) @@ -1340,7 +1370,10 @@ where let iter_id = PrefixIteratorId::new(iter_id); if let Some(iter) = iterators.get_mut(iter_id) { let gas_meter = unsafe { env.ctx.gas_meter.get() }; - if let Some((key, val)) = vp_host_fns::iter_next(gas_meter, iter)? { + let sentinel = unsafe { env.ctx.sentinel.get() }; + if let Some((key, val)) = + vp_host_fns::iter_next(gas_meter, iter, sentinel)? + { let key_val = borsh::to_vec(&KeyVal { key, val }) .map_err(vp_host_fns::RuntimeError::EncodingError)?; let len: i64 = key_val @@ -1530,8 +1563,9 @@ where CA: WasmCacheAccess, { let gas_meter = unsafe { env.ctx.gas_meter.get() }; + let sentinel = unsafe { env.ctx.sentinel.get() }; let tx_index = unsafe { env.ctx.tx_index.get() }; - let tx_idx = vp_host_fns::get_tx_index(gas_meter, tx_index)?; + let tx_idx = vp_host_fns::get_tx_index(gas_meter, tx_index, sentinel)?; Ok(tx_idx.0) } @@ -1641,13 +1675,14 @@ where CA: WasmCacheAccess, { let gas_meter = unsafe { env.ctx.gas_meter.get() }; + let sentinel = unsafe { env.ctx.sentinel.get() }; let storage = unsafe { env.ctx.storage.get() }; - let chain_id = vp_host_fns::get_chain_id(gas_meter, storage)?; + let chain_id = vp_host_fns::get_chain_id(gas_meter, storage, sentinel)?; let gas = env .memory .write_string(result_ptr, chain_id) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; - vp_host_fns::add_gas(gas_meter, gas) + vp_host_fns::add_gas(gas_meter, gas, sentinel) } /// Getting the block height function exposed to the wasm VM VP @@ -1664,8 +1699,9 @@ where CA: WasmCacheAccess, { let gas_meter = unsafe { env.ctx.gas_meter.get() }; + let sentinel = unsafe { env.ctx.sentinel.get() }; let storage = unsafe { env.ctx.storage.get() }; - let height = vp_host_fns::get_block_height(gas_meter, storage)?; + let height = vp_host_fns::get_block_height(gas_meter, storage, sentinel)?; Ok(height.0) } @@ -1682,11 +1718,12 @@ where CA: WasmCacheAccess, { let gas_meter = unsafe { env.ctx.gas_meter.get() }; + let sentinel = unsafe { env.ctx.sentinel.get() }; let storage = unsafe { env.ctx.storage.get() }; let (header, gas) = storage .get_block_header(Some(BlockHeight(height))) .map_err(vp_host_fns::RuntimeError::StorageError)?; - vp_host_fns::add_gas(gas_meter, gas)?; + vp_host_fns::add_gas(gas_meter, gas, sentinel)?; Ok(match header { Some(h) => { let value = h.serialize_to_vec(); @@ -1716,13 +1753,14 @@ where CA: WasmCacheAccess, { let gas_meter = unsafe { env.ctx.gas_meter.get() }; + let sentinel = unsafe { env.ctx.sentinel.get() }; let storage = unsafe { env.ctx.storage.get() }; - let hash = vp_host_fns::get_block_hash(gas_meter, storage)?; + let hash = vp_host_fns::get_block_hash(gas_meter, storage, sentinel)?; let gas = env .memory .write_bytes(result_ptr, hash.0) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; - vp_host_fns::add_gas(gas_meter, gas) + vp_host_fns::add_gas(gas_meter, gas, sentinel) } /// Getting the transaction hash function exposed to the wasm VM VP environment. @@ -1738,8 +1776,9 @@ where CA: WasmCacheAccess, { let gas_meter = unsafe { env.ctx.gas_meter.get() }; + let sentinel = unsafe { env.ctx.sentinel.get() }; let tx = unsafe { env.ctx.tx.get() }; - let hash = vp_host_fns::get_tx_code_hash(gas_meter, tx)?; + let hash = vp_host_fns::get_tx_code_hash(gas_meter, tx, sentinel)?; let mut result_bytes = vec![]; if let Some(hash) = hash { result_bytes.push(1); @@ -1751,7 +1790,7 @@ where .memory .write_bytes(result_ptr, result_bytes) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; - vp_host_fns::add_gas(gas_meter, gas) + vp_host_fns::add_gas(gas_meter, gas, sentinel) } /// Getting the block epoch function exposed to the wasm VM VP @@ -1768,8 +1807,9 @@ where CA: WasmCacheAccess, { let gas_meter = unsafe { env.ctx.gas_meter.get() }; + let sentinel = unsafe { env.ctx.sentinel.get() }; let storage = unsafe { env.ctx.storage.get() }; - let epoch = vp_host_fns::get_block_epoch(gas_meter, storage)?; + let epoch = vp_host_fns::get_block_epoch(gas_meter, storage, sentinel)?; Ok(epoch.0) } @@ -1791,7 +1831,8 @@ where .read_string(event_type_ptr, event_type_len as _) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; let gas_meter = unsafe { env.ctx.gas_meter.get() }; - vp_host_fns::add_gas(gas_meter, gas)?; + let sentinel = unsafe { env.ctx.sentinel.get() }; + vp_host_fns::add_gas(gas_meter, gas, sentinel)?; let write_log = unsafe { env.ctx.write_log.get() }; let events = vp_host_fns::get_ibc_events(gas_meter, write_log, event_type)?; @@ -1835,7 +1876,8 @@ where .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; let gas_meter = unsafe { env.ctx.gas_meter.get() }; - vp_host_fns::add_gas(gas_meter, gas)?; + let sentinel = unsafe { env.ctx.sentinel.get() }; + vp_host_fns::add_gas(gas_meter, gas, sentinel)?; let hashes = <[Hash; 1]>::try_from_slice(&hash_list) .map_err(vp_host_fns::RuntimeError::EncodingError)?; @@ -1843,7 +1885,7 @@ where .memory .read_bytes(public_keys_map_ptr, public_keys_map_len as _) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; - vp_host_fns::add_gas(gas_meter, gas)?; + vp_host_fns::add_gas(gas_meter, gas, sentinel)?; let public_keys_map = namada_core::types::account::AccountPublicKeysMap::try_from_slice( &public_keys_map, @@ -1854,7 +1896,7 @@ where .memory .read_bytes(signer_ptr, signer_len as _) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; - vp_host_fns::add_gas(gas_meter, gas)?; + vp_host_fns::add_gas(gas_meter, gas, sentinel)?; let signer = Address::try_from_slice(&signer) .map_err(vp_host_fns::RuntimeError::EncodingError)?; @@ -1862,24 +1904,32 @@ where .memory .read_bytes(max_signatures_ptr, max_signatures_len as _) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; - vp_host_fns::add_gas(gas_meter, gas)?; + vp_host_fns::add_gas(gas_meter, gas, sentinel)?; let max_signatures = Option::::try_from_slice(&max_signatures) .map_err(vp_host_fns::RuntimeError::EncodingError)?; let tx = unsafe { env.ctx.tx.get() }; - Ok(HostEnvResult::from( - tx.verify_signatures( - &hashes, - public_keys_map, - &Some(signer), - threshold, - max_signatures, - &mut Some(gas_meter), - ) - .is_ok(), - ) - .to_i64()) + match tx.verify_signatures( + &hashes, + public_keys_map, + &Some(signer), + threshold, + max_signatures, + &mut Some(gas_meter), + ) { + Ok(_) => Ok(HostEnvResult::Success.to_i64()), + Err(err) => match err { + namada_core::proto::Error::OutOfGas(inner) => { + sentinel.set_out_of_gas(); + Err(vp_host_fns::RuntimeError::OutOfGas(inner)) + } + _ => { + sentinel.set_invalid_signature(); + Ok(HostEnvResult::Fail.to_i64()) + } + }, + } } /// Verify a ShieldedTransaction. @@ -1896,11 +1946,12 @@ where CA: WasmCacheAccess, { let gas_meter = unsafe { env.ctx.gas_meter.get() }; + let sentinel = unsafe { env.ctx.sentinel.get() }; let (tx_bytes, gas) = env .memory .read_bytes(tx_ptr, tx_len as _) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; - vp_host_fns::add_gas(gas_meter, gas)?; + vp_host_fns::add_gas(gas_meter, gas, sentinel)?; let shielded: Transaction = BorshDeserialize::try_from_slice(tx_bytes.as_slice()) @@ -2017,13 +2068,14 @@ where .read_bytes(vp_code_hash_ptr, vp_code_hash_len as _) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; let gas_meter = unsafe { env.ctx.gas_meter.get() }; - vp_host_fns::add_gas(gas_meter, gas)?; + let sentinel = unsafe { env.ctx.sentinel.get() }; + vp_host_fns::add_gas(gas_meter, gas, sentinel)?; let (input_data, gas) = env .memory .read_bytes(input_data_ptr, input_data_len as _) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; - vp_host_fns::add_gas(gas_meter, gas)?; + vp_host_fns::add_gas(gas_meter, gas, sentinel)?; let input_data: Tx = BorshDeserialize::try_from_slice(&input_data) .map_err(vp_host_fns::RuntimeError::EncodingError)?; let vp_code_hash = Hash(vp_code_hash.try_into().map_err(|e| { @@ -2052,14 +2104,16 @@ where CA: WasmCacheAccess, { let gas_meter = unsafe { env.ctx.gas_meter.get() }; + let sentinel = unsafe { env.ctx.sentinel.get() }; let storage = unsafe { env.ctx.storage.get() }; - let native_token = vp_host_fns::get_native_token(gas_meter, storage)?; + let native_token = + vp_host_fns::get_native_token(gas_meter, storage, sentinel)?; let native_token_string = native_token.encode(); let gas = env .memory .write_string(result_ptr, native_token_string) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; - vp_host_fns::add_gas(gas_meter, gas) + vp_host_fns::add_gas(gas_meter, gas, sentinel) } /// Log a string from exposed to the wasm VM VP environment. The message will be @@ -2520,6 +2574,7 @@ pub mod testing { iterators: &mut PrefixIterators<'static, DB>, verifiers: &mut BTreeSet
, gas_meter: &mut TxGasMeter, + out_of_gas: &mut bool, tx: &Tx, tx_index: &TxIndex, result_buffer: &mut Option>, @@ -2537,6 +2592,7 @@ pub mod testing { write_log, iterators, gas_meter, + out_of_gas, tx, tx_index, verifiers, @@ -2556,6 +2612,7 @@ pub mod testing { write_log: &WriteLog, iterators: &mut PrefixIterators<'static, DB>, gas_meter: &mut VpGasMeter, + sentinel: &mut VpSentinel, tx: &Tx, tx_index: &TxIndex, verifiers: &BTreeSet
, @@ -2576,6 +2633,7 @@ pub mod testing { storage, write_log, gas_meter, + sentinel, tx, tx_index, iterators, diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index 9740469b95..9064704b3f 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -5,9 +5,10 @@ use std::marker::PhantomData; use borsh::BorshDeserialize; use namada_core::ledger::gas::{ - self, GasMetering, TxGasMeter, WASM_MEMORY_PAGE_GAS_COST, + GasMetering, TxGasMeter, WASM_MEMORY_PAGE_GAS_COST, }; use namada_core::ledger::storage::write_log::StorageModification; +use namada_core::types::validity_predicate::VpSentinel; use parity_wasm::elements; use thiserror::Error; use wasmer::{BaseTunables, Module, Store}; @@ -77,10 +78,12 @@ pub enum Error { LoadWasmCode(String), #[error("Unable to find compiled wasm code")] NoCompiledWasmCode, - #[error("Error while accounting for gas: {0}")] - GasError(#[from] gas::Error), + #[error("Gas error: {0}")] + GasError(String), #[error("Failed type conversion: {0}")] ConversionError(String), + #[error("Invalid transaction signature")] + InvalidTxSignature, } /// Result for functions that may fail @@ -120,12 +123,14 @@ where let mut verifiers = BTreeSet::new(); let mut result_buffer: Option> = None; + let mut out_of_gas = false; let env = TxVmEnv::new( WasmMemory::default(), storage, write_log, &mut iterators, gas_meter, + &mut out_of_gas, tx, tx_index, &mut verifiers, @@ -162,16 +167,14 @@ where entrypoint: TX_ENTRYPOINT, error, })?; - match apply_tx - .call(tx_data_ptr, tx_data_len) - .map_err(Error::RuntimeError) - { - Err(Error::RuntimeError(err)) => { - tracing::debug!("Tx WASM failed with {}", err); - Err(Error::RuntimeError(err)) + apply_tx.call(tx_data_ptr, tx_data_len).map_err(|err| { + tracing::debug!("Tx WASM failed with {}", err); + if out_of_gas { + Error::GasError(err.to_string()) + } else { + Error::RuntimeError(err) } - _ => Ok(()), - }?; + })?; Ok(verifiers) } @@ -214,12 +217,14 @@ where cache_access: PhantomData, }; + let mut sentinel = VpSentinel::default(); let env = VpVmEnv::new( WasmMemory::default(), address, storage, write_log, gas_meter, + &mut sentinel, tx, tx_index, &mut iterators, @@ -234,7 +239,7 @@ where memory::prepare_vp_memory(&store).map_err(Error::MemoryError)?; let imports = vp_imports(&store, initial_memory, env); - run_vp( + match run_vp( module, imports, &vp_code_hash, @@ -243,7 +248,22 @@ where keys_changed, verifiers, gas_meter, - ) + ) { + Ok(accept) => { + if !accept && sentinel.is_invalid_signature() { + Err(Error::InvalidTxSignature) + } else { + Ok(accept) + } + } + Err(err) => { + if sentinel.is_out_of_gas() { + Err(Error::GasError(err.to_string())) + } else { + Err(err) + } + } + } } #[allow(clippy::too_many_arguments)] @@ -520,15 +540,21 @@ where } }; - gas_meter.add_wasm_load_from_storage_gas(tx_len)?; - gas_meter.add_compiling_gas(tx_len)?; + gas_meter + .add_wasm_load_from_storage_gas(tx_len) + .map_err(|e| Error::GasError(e.to_string()))?; + gas_meter + .add_compiling_gas(tx_len) + .map_err(|e| Error::GasError(e.to_string()))?; Ok((module, store)) } Commitment::Id(code) => { - gas_meter.add_compiling_gas( - u64::try_from(code.len()) - .map_err(|e| Error::ConversionError(e.to_string()))?, - )?; + gas_meter + .add_compiling_gas( + u64::try_from(code.len()) + .map_err(|e| Error::ConversionError(e.to_string()))?, + ) + .map_err(|e| Error::GasError(e.to_string()))?; validate_untrusted_wasm(code).map_err(Error::ValidationError)?; match wasm_cache.compile_or_fetch(code)? { Some((module, store)) => Ok((module, store)), @@ -540,6 +566,8 @@ 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_COST; let call_per_local_cost = 0; @@ -690,6 +718,7 @@ mod tests { let mut gas_meter = VpGasMeter::new_from_tx_meter( &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), ); + let _invalid_sig = false; let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); let tx_index = TxIndex::default(); @@ -793,6 +822,7 @@ mod tests { let mut gas_meter = VpGasMeter::new_from_tx_meter( &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), ); + let _invalid_sig = false; let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); let tx_index = TxIndex::default(); @@ -925,6 +955,7 @@ mod tests { let mut gas_meter = VpGasMeter::new_from_tx_meter( &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), ); + let _invalid_sig = false; let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); let tx_index = TxIndex::default(); @@ -1046,6 +1077,7 @@ mod tests { let mut gas_meter = VpGasMeter::new_from_tx_meter( &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), ); + let _invalid_sig = false; let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); let tx_index = TxIndex::default(); @@ -1105,6 +1137,7 @@ mod tests { let mut gas_meter = VpGasMeter::new_from_tx_meter( &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), ); + let _invalid_sig = false; let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); let tx_index = TxIndex::default(); @@ -1274,6 +1307,7 @@ mod tests { let mut gas_meter = VpGasMeter::new_from_tx_meter( &TxGasMeter::new_from_sub_limit(TX_GAS_LIMIT.into()), ); + let _invalid_sig = false; let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); From 22c6ba262f58b8ef2ecd3a730a210fce7fddd54e Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 25 Oct 2023 17:32:53 +0200 Subject: [PATCH 02/10] Improves testing --- tests/src/native_vp/mod.rs | 1 + tests/src/vm_host_env/tx.rs | 6 ++++++ tests/src/vm_host_env/vp.rs | 28 ++++++++++++++++---------- wasm_for_tests/tx_fail.wasm | Bin 0 -> 448947 bytes wasm_for_tests/wasm_source/Cargo.toml | 1 + wasm_for_tests/wasm_source/Makefile | 1 + wasm_for_tests/wasm_source/src/lib.rs | 11 ++++++++++ 7 files changed, 37 insertions(+), 11 deletions(-) create mode 100755 wasm_for_tests/tx_fail.wasm diff --git a/tests/src/native_vp/mod.rs b/tests/src/native_vp/mod.rs index 00070380e6..77cf6b1458 100644 --- a/tests/src/native_vp/mod.rs +++ b/tests/src/native_vp/mod.rs @@ -54,6 +54,7 @@ impl TestNativeVpEnv { gas_meter: RefCell::new(VpGasMeter::new_from_tx_meter( &self.tx_env.gas_meter, )), + sentinel: Default::default(), storage: &self.tx_env.wl_storage.storage, write_log: &self.tx_env.wl_storage.write_log, tx: &self.tx_env.tx, diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index fdb5bd959e..dfbc23d7af 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -51,6 +51,7 @@ pub struct TestTxEnv { pub iterators: PrefixIterators<'static, MockDB>, pub verifiers: BTreeSet
, pub gas_meter: TxGasMeter, + pub out_of_gas: bool, pub tx_index: TxIndex, pub result_buffer: Option>, pub vp_wasm_cache: VpCache, @@ -75,6 +76,7 @@ impl Default for TestTxEnv { wl_storage, iterators: PrefixIterators::default(), gas_meter: TxGasMeter::new_from_sub_limit(100_000_000.into()), + out_of_gas: false, tx_index: TxIndex::default(), verifiers: BTreeSet::default(), result_buffer: None, @@ -344,6 +346,7 @@ mod native_tx_host_env { iterators, verifiers, gas_meter, + out_of_gas, result_buffer, tx_index, vp_wasm_cache, @@ -359,6 +362,7 @@ mod native_tx_host_env { iterators, verifiers, gas_meter, + out_of_gas, tx, tx_index, result_buffer, @@ -385,6 +389,7 @@ mod native_tx_host_env { iterators, verifiers, gas_meter, + out_of_gas, result_buffer, vp_wasm_cache, vp_cache_dir: _, @@ -399,6 +404,7 @@ mod native_tx_host_env { iterators, verifiers, gas_meter, + out_of_gas, tx, tx_index, result_buffer, diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index 69d7af9bb1..22b2562ed3 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -13,6 +13,7 @@ use namada::vm::prefix_iter::PrefixIterators; use namada::vm::wasm::{self, VpCache}; use namada::vm::{self, WasmCacheRwAccess}; use namada_core::ledger::gas::TxGasMeter; +use namada_tx_prelude::validity_predicate::VpSentinel; use namada_vp_prelude::Ctx; use tempfile::TempDir; @@ -44,6 +45,7 @@ pub struct TestVpEnv { pub wl_storage: WlStorage, pub iterators: PrefixIterators<'static, MockDB>, pub gas_meter: VpGasMeter, + pub sentinel: VpSentinel, pub tx: Tx, pub tx_index: TxIndex, pub keys_changed: BTreeSet, @@ -77,6 +79,7 @@ impl Default for TestVpEnv { gas_meter: VpGasMeter::new_from_tx_meter( &TxGasMeter::new_from_sub_limit(10_000_000.into()), ), + sentinel: VpSentinel::default(), tx, tx_index: TxIndex::default(), keys_changed: BTreeSet::default(), @@ -258,6 +261,7 @@ mod native_vp_host_env { wl_storage, iterators, gas_meter, + sentinel, tx, tx_index, keys_changed, @@ -274,6 +278,7 @@ mod native_vp_host_env { &wl_storage.write_log, iterators, gas_meter, + sentinel, tx, tx_index, verifiers, @@ -301,6 +306,7 @@ mod native_vp_host_env { wl_storage, iterators, gas_meter, + sentinel, tx, tx_index, keys_changed, @@ -317,6 +323,7 @@ mod native_vp_host_env { &wl_storage.write_log, iterators, gas_meter, + sentinel, tx, tx_index, verifiers, @@ -362,16 +369,15 @@ mod native_vp_host_env { ) -> i64); native_host_fn!(vp_log_string(str_ptr: u64, str_len: u64)); native_host_fn!(vp_verify_tx_section_signature( - hash_list_ptr: u64, - hash_list_len: u64, - public_keys_map_ptr: u64, - public_keys_map_len: u64, - signer_ptr: u64, - signer_len: u64, - threshold: u8, - max_signatures_ptr: u64, - max_signatures_len: u64,) - -> i64 - ); + hash_list_ptr: u64, + hash_list_len: u64, + public_keys_map_ptr: u64, + public_keys_map_len: u64, + signer_ptr: u64, + signer_len: u64, + threshold: u8, + max_signatures_ptr: u64, + max_signatures_len: u64, + ) -> i64); native_host_fn!(vp_charge_gas(used_gas: u64)); } diff --git a/wasm_for_tests/tx_fail.wasm b/wasm_for_tests/tx_fail.wasm new file mode 100755 index 0000000000000000000000000000000000000000..dce18fc1be7511c39dbdcc0a3c741f53f12fa991 GIT binary patch literal 448947 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­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≫;ThD&ZaPX|r)+X7^>cNB0My^Sa_}2Ku9fTdO?qI_V zpYbT~(i=RFKgkQkLU(s_b@=hJJ=;%JqcR(1FMg@0H$>!T#fx>O&D~jkw^1(}y!_5> jQBF~5LnJtjPuz7U$#BIXb$7~*KECS?$t8I=agF~Mx$cOU literal 0 HcmV?d00001 diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index 789571e27e..33eb8a5f60 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -14,6 +14,7 @@ crate-type = ["cdylib"] [features] tx_memory_limit = [] tx_no_op = [] +tx_fail = [] tx_read_storage_key = [] tx_write = [] vp_always_false = [] diff --git a/wasm_for_tests/wasm_source/Makefile b/wasm_for_tests/wasm_source/Makefile index f56ab0afb8..524d302f54 100644 --- a/wasm_for_tests/wasm_source/Makefile +++ b/wasm_for_tests/wasm_source/Makefile @@ -7,6 +7,7 @@ nightly := $(shell cat ../../rust-nightly-version) # Wasms can be added via the Cargo.toml `[features]` list. wasms := tx_memory_limit wasms += tx_no_op +wasms += tx_fail wasms += tx_read_storage_key wasms += tx_write wasms += vp_always_false diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 35198a1a87..d1c1b6696c 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -9,6 +9,17 @@ pub mod main { } } +/// A tx that fails everytime. +#[cfg(feature = "tx_fail")] +pub mod main { + use namada_tx_prelude::*; + + #[transaction(gas = 1000)] + fn apply_tx(_ctx: &mut Ctx, _tx_data: Tx) -> TxResult { + Err(Error::SimpleMessage("failed tx")) + } +} + /// A tx that allocates a memory of size given from the `tx_data: usize`. #[cfg(feature = "tx_memory_limit")] pub mod main { From e3930ad4e975be16acb8a85d05ce7d9da5ef1671 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 25 Oct 2023 18:04:22 +0200 Subject: [PATCH 03/10] Removes tx hash if invalid section commitment --- .../lib/node/ledger/shell/finalize_block.rs | 102 +++++++++--------- core/src/ledger/tx_env.rs | 3 + core/src/types/transaction/mod.rs | 25 +++++ shared/src/ledger/protocol/mod.rs | 5 +- shared/src/vm/host_env.rs | 40 ++++--- shared/src/vm/wasm/host_env.rs | 5 +- shared/src/vm/wasm/run.rs | 21 ++-- tests/src/vm_host_env/tx.rs | 57 ++++++++-- tx_prelude/src/lib.rs | 4 + vm_env/src/lib.rs | 3 + wasm/wasm_source/src/tx_bond.rs | 5 +- wasm/wasm_source/src/tx_bridge_pool.rs | 5 +- .../src/tx_change_validator_commission.rs | 5 +- wasm/wasm_source/src/tx_ibc.rs | 5 +- wasm/wasm_source/src/tx_init_account.rs | 17 ++- wasm/wasm_source/src/tx_init_proposal.rs | 29 ++++- wasm/wasm_source/src/tx_init_validator.rs | 17 ++- wasm/wasm_source/src/tx_redelegate.rs | 5 +- wasm/wasm_source/src/tx_resign_steward.rs | 5 +- wasm/wasm_source/src/tx_reveal_pk.rs | 5 +- wasm/wasm_source/src/tx_transfer.rs | 9 +- wasm/wasm_source/src/tx_unbond.rs | 5 +- wasm/wasm_source/src/tx_unjail_validator.rs | 5 +- wasm/wasm_source/src/tx_update_account.rs | 17 ++- .../src/tx_update_steward_commission.rs | 5 +- wasm/wasm_source/src/tx_update_vp.rs | 17 ++- wasm/wasm_source/src/tx_vote_proposal.rs | 5 +- wasm/wasm_source/src/tx_withdraw.rs | 5 +- 28 files changed, 317 insertions(+), 114 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 874baa0d63..e131c4e088 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -519,12 +519,16 @@ where ); // If transaction type is Decrypted and failed because of - // out of gas, remove its hash from storage to allow - // rewrapping it + // out of gas or invalid section commtiment, remove its hash + // from storage to allow rewrapping it if let Some(wrapper) = embedding_wrapper { - if let Error::TxApply(protocol::Error::GasError(_)) = - msg - { + if matches!( + msg, + Error::TxApply(protocol::Error::GasError(_)) + | Error::TxApply( + protocol::Error::MissingSection(_) + ) + ) { self.allow_tx_replay(wrapper); } } else if let Some(wrapper) = wrapper { @@ -2308,9 +2312,9 @@ mod test_finalize_block { } /// Test that if a decrypted transaction fails because of out-of-gas, - /// undecryptable or invalid signature, its hash is removed from storage. - /// Also checks that a tx failing for other reason has its hash persisted to - /// storage. + /// undecryptable, invalid signature or wrong section commitment, its hash + /// is removed from storage. Also checks that a tx failing for other + /// reason has its hash persisted to storage. #[test] fn test_tx_hash_handling() { let (mut shell, _, _, _) = setup(); @@ -2357,10 +2361,13 @@ mod test_finalize_block { failing_wrapper.set_data(Data::new( "Encrypted transaction data".as_bytes().to_owned(), )); + let mut wrong_commitment_wrapper = failing_wrapper.clone(); + wrong_commitment_wrapper.set_code_sechash(Hash::default()); let mut out_of_gas_inner = out_of_gas_wrapper.clone(); let mut undecryptable_inner = undecryptable_wrapper.clone(); let mut unsigned_inner = unsigned_wrapper.clone(); + let mut wrong_commitment_inner = wrong_commitment_wrapper.clone(); let mut failing_inner = failing_wrapper.clone(); undecryptable_inner @@ -2368,6 +2375,7 @@ mod test_finalize_block { for inner in [ &mut out_of_gas_inner, &mut unsigned_inner, + &mut wrong_commitment_inner, &mut failing_inner, ] { inner.update_header(TxType::Decrypted(DecryptedTx::Decrypted)); @@ -2378,6 +2386,7 @@ mod test_finalize_block { &out_of_gas_inner, &undecryptable_inner, &unsigned_inner, + &wrong_commitment_inner, &failing_inner, ] { let hash_subkey = @@ -2396,6 +2405,7 @@ mod test_finalize_block { &out_of_gas_inner, &undecryptable_inner, &unsigned_inner, + &wrong_commitment_inner, &failing_inner, ] { processed_txs.push(ProcessedTx { @@ -2413,6 +2423,10 @@ mod test_finalize_block { GAS_LIMIT_MULTIPLIER.into(), ); shell.enqueue_tx(unsigned_wrapper.clone(), u64::MAX.into()); // Prevent out of gas which would still make the test pass + shell.enqueue_tx( + wrong_commitment_wrapper.clone(), + GAS_LIMIT_MULTIPLIER.into(), + ); shell.enqueue_tx(failing_wrapper.clone(), GAS_LIMIT_MULTIPLIER.into()); // merkle tree root before finalize_block let root_pre = shell.shell.wl_storage.storage.block.tree.root(); @@ -2457,51 +2471,35 @@ mod test_finalize_block { .expect("Testfailed") .as_str(); assert_eq!(code, String::from(ErrorCodes::WasmRuntimeError).as_str()); + assert_eq!(event[4].event_type.to_string(), String::from("applied")); + let code = event[4] + .attributes + .get("code") + .expect("Testfailed") + .as_str(); + assert_eq!(code, String::from(ErrorCodes::WasmRuntimeError).as_str()); - assert!( - !shell - .wl_storage - .write_log - .has_replay_protection_entry(&out_of_gas_inner.header_hash()) - .unwrap_or_default() - ); - assert!( - shell - .wl_storage - .write_log - .has_replay_protection_entry(&out_of_gas_wrapper.header_hash()) - .unwrap_or_default() - ); - assert!( - !shell - .wl_storage - .write_log - .has_replay_protection_entry(&undecryptable_inner.header_hash()) - .unwrap_or_default() - ); - assert!( - shell - .wl_storage - .write_log - .has_replay_protection_entry( - &undecryptable_wrapper.header_hash() - ) - .unwrap_or_default() - ); - assert!( - !shell - .wl_storage - .write_log - .has_replay_protection_entry(&unsigned_inner.header_hash()) - .unwrap_or_default() - ); - assert!( - shell - .wl_storage - .write_log - .has_replay_protection_entry(&unsigned_wrapper.header_hash()) - .unwrap_or_default() - ); + for (invalid_inner, valid_wrapper) in [ + (out_of_gas_inner, out_of_gas_wrapper), + (undecryptable_inner, undecryptable_wrapper), + (unsigned_inner, unsigned_wrapper), + (wrong_commitment_inner, wrong_commitment_wrapper), + ] { + assert!( + !shell + .wl_storage + .write_log + .has_replay_protection_entry(&invalid_inner.header_hash()) + .unwrap_or_default() + ); + assert!( + shell + .wl_storage + .write_log + .has_replay_protection_entry(&valid_wrapper.header_hash()) + .unwrap_or_default() + ); + } assert!( shell .wl_storage diff --git a/core/src/ledger/tx_env.rs b/core/src/ledger/tx_env.rs index ad8b23e60c..4b87d97f7c 100644 --- a/core/src/ledger/tx_env.rs +++ b/core/src/ledger/tx_env.rs @@ -64,4 +64,7 @@ pub trait TxEnv: StorageRead + StorageWrite { &self, event_type: impl AsRef, ) -> Result, storage_api::Error>; + + /// Set the sentinel for an invalid section commitment + fn set_commitment_sentinel(&mut self); } diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 1ddcb84175..b6cdb0cd07 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -168,6 +168,31 @@ impl TxType { } } +/// Sentinel used in transactions to signal events that require special +/// replay protection handling back to the protocol. +#[derive(Debug, Default)] +pub enum TxSentinel { + /// No action required + #[default] + None, + /// Exceeded gas limit + OutOfGas, + /// Found invalid commtiment to one of the transaction's sections + InvalidCommitment, +} + +impl TxSentinel { + /// Set the sentinel for an out of gas error + pub fn set_out_of_gas(&mut self) { + *self = Self::OutOfGas + } + + /// Set the sentinel for an invalid section commitment error + pub fn set_invalid_commitment(&mut self) { + *self = Self::InvalidCommitment + } +} + #[cfg(test)] mod test_process_tx { use super::*; diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 314f8facae..e2292bd1a7 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -42,8 +42,8 @@ use crate::vm::{self, wasm, WasmCacheAccess}; #[allow(missing_docs)] #[derive(Error, Debug)] pub enum Error { - #[error("Missing wasm code error")] - MissingCode, + #[error("Missing tx section: {0}")] + MissingSection(String), #[error("Storage error: {0}")] StorageError(crate::ledger::storage::Error), #[error("Error decoding a transaction from bytes: {0}")] @@ -715,6 +715,7 @@ where ) .map_err(|err| match err { wasm::run::Error::GasError(msg) => Error::GasError(msg), + wasm::run::Error::MissingSection(msg) => Error::MissingSection(msg), _ => Error::TxRunnerError(err), }) } diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 304fcf7285..6c3dc4cd9a 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -9,6 +9,7 @@ use borsh_ext::BorshSerializeExt; use masp_primitives::transaction::Transaction; use namada_core::ledger::gas::{GasMetering, TxGasMeter}; use namada_core::types::internal::KeyVal; +use namada_core::types::transaction::TxSentinel; use namada_core::types::validity_predicate::VpSentinel; use thiserror::Error; @@ -101,8 +102,8 @@ where pub iterators: MutHostRef<'a, &'a PrefixIterators<'a, DB>>, /// Transaction gas meter. pub gas_meter: MutHostRef<'a, &'a TxGasMeter>, - /// Out-of-gas sentinel - pub out_of_gas: MutHostRef<'a, &'a bool>, + /// Transaction sentinel + pub sentinel: MutHostRef<'a, &'a TxSentinel>, /// The transaction code is used for signature verification pub tx: HostRef<'a, &'a Tx>, /// The transaction index is used to identify a shielded transaction's @@ -145,7 +146,7 @@ where write_log: &mut WriteLog, iterators: &mut PrefixIterators<'a, DB>, gas_meter: &mut TxGasMeter, - out_of_gas: &mut bool, + sentinel: &mut TxSentinel, tx: &Tx, tx_index: &TxIndex, verifiers: &mut BTreeSet
, @@ -157,7 +158,7 @@ where let write_log = unsafe { MutHostRef::new(write_log) }; let iterators = unsafe { MutHostRef::new(iterators) }; let gas_meter = unsafe { MutHostRef::new(gas_meter) }; - let out_of_gas = unsafe { MutHostRef::new(out_of_gas) }; + let sentinel = unsafe { MutHostRef::new(sentinel) }; let tx = unsafe { HostRef::new(tx) }; let tx_index = unsafe { HostRef::new(tx_index) }; let verifiers = unsafe { MutHostRef::new(verifiers) }; @@ -171,7 +172,7 @@ where write_log, iterators, gas_meter, - out_of_gas, + sentinel, tx, tx_index, verifiers, @@ -215,7 +216,7 @@ where write_log: self.write_log.clone(), iterators: self.iterators.clone(), gas_meter: self.gas_meter.clone(), - out_of_gas: self.out_of_gas.clone(), + sentinel: self.sentinel.clone(), tx: self.tx.clone(), tx_index: self.tx_index.clone(), verifiers: self.verifiers.clone(), @@ -488,8 +489,8 @@ where let gas_meter = unsafe { env.ctx.gas_meter.get() }; // if we run out of gas, we need to stop the execution gas_meter.consume(used_gas).map_err(|err| { - let sentinel = unsafe { env.ctx.out_of_gas.get() }; - *sentinel = true; + let sentinel = unsafe { env.ctx.sentinel.get() }; + sentinel.set_out_of_gas(); tracing::info!( "Stopping transaction execution because of gas error: {}", err @@ -2005,8 +2006,11 @@ where use namada_core::ledger::ibc::{IbcActions, TransferModule}; - let tx_data = unsafe { env.ctx.tx.get().data() } - .ok_or(TxRuntimeError::MissingTxData)?; + let tx_data = unsafe { env.ctx.tx.get().data() }.ok_or_else(|| { + let sentinel = unsafe { env.ctx.sentinel.get() }; + sentinel.set_invalid_commitment(); + TxRuntimeError::MissingTxData + })?; let ctx = Rc::new(RefCell::new(env.ctx.clone())); let mut actions = IbcActions::new(ctx.clone()); let module = TransferModule::new(ctx); @@ -2048,6 +2052,18 @@ where Ok(()) } +/// Set the sentinel for an invalid tx section commitment +pub fn tx_set_commitment_sentinel(env: &TxVmEnv) +where + MEM: VmMemory, + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + CA: WasmCacheAccess, +{ + let sentinel = unsafe { env.ctx.sentinel.get() }; + sentinel.set_invalid_commitment(); +} + /// Evaluate a validity predicate with the given input data. pub fn vp_eval( env: &VpVmEnv<'static, MEM, DB, H, EVAL, CA>, @@ -2574,7 +2590,7 @@ pub mod testing { iterators: &mut PrefixIterators<'static, DB>, verifiers: &mut BTreeSet
, gas_meter: &mut TxGasMeter, - out_of_gas: &mut bool, + sentinel: &mut TxSentinel, tx: &Tx, tx_index: &TxIndex, result_buffer: &mut Option>, @@ -2592,7 +2608,7 @@ pub mod testing { write_log, iterators, gas_meter, - out_of_gas, + sentinel, tx, tx_index, verifiers, diff --git a/shared/src/vm/wasm/host_env.rs b/shared/src/vm/wasm/host_env.rs index e9a63e631e..a7dc51cc6f 100644 --- a/shared/src/vm/wasm/host_env.rs +++ b/shared/src/vm/wasm/host_env.rs @@ -60,7 +60,7 @@ where // default namespace "env" => { "memory" => initial_memory, - // Wasm middleware gas injectiong hook + // 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), @@ -86,6 +86,7 @@ where "namada_tx_get_native_token" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_native_token), "namada_tx_log_string" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_log_string), "namada_tx_ibc_execute" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_ibc_execute), + "namada_tx_set_commitment_sentinel" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_set_commitment_sentinel) }, } } @@ -107,7 +108,7 @@ where // default namespace "env" => { "memory" => initial_memory, - // Wasm middleware gas injectiong hook + // 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), diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index 9064704b3f..b03b8530d5 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -8,6 +8,7 @@ use namada_core::ledger::gas::{ GasMetering, TxGasMeter, WASM_MEMORY_PAGE_GAS_COST, }; use namada_core::ledger::storage::write_log::StorageModification; +use namada_core::types::transaction::TxSentinel; use namada_core::types::validity_predicate::VpSentinel; use parity_wasm::elements; use thiserror::Error; @@ -39,8 +40,8 @@ const WASM_STACK_LIMIT: u32 = u16::MAX as u32; #[allow(missing_docs)] #[derive(Error, Debug)] pub enum Error { - #[error("Missing wasm code error")] - MissingCode, + #[error("Missing tx section: {0}")] + MissingSection(String), #[error("Memory error: {0}")] MemoryError(memory::Error), #[error("Unable to inject stack limiter")] @@ -109,7 +110,7 @@ where let tx_code = tx .get_section(tx.code_sechash()) .and_then(|x| Section::code_sec(x.as_ref())) - .ok_or(Error::MissingCode)?; + .ok_or(Error::MissingSection(tx.code_sechash().to_string()))?; let (module, store) = fetch_or_compile( tx_wasm_cache, @@ -123,14 +124,14 @@ where let mut verifiers = BTreeSet::new(); let mut result_buffer: Option> = None; - let mut out_of_gas = false; + let mut sentinel = TxSentinel::default(); let env = TxVmEnv::new( WasmMemory::default(), storage, write_log, &mut iterators, gas_meter, - &mut out_of_gas, + &mut sentinel, tx, tx_index, &mut verifiers, @@ -169,10 +170,12 @@ where })?; apply_tx.call(tx_data_ptr, tx_data_len).map_err(|err| { tracing::debug!("Tx WASM failed with {}", err); - if out_of_gas { - Error::GasError(err.to_string()) - } else { - Error::RuntimeError(err) + match sentinel { + TxSentinel::None => Error::RuntimeError(err), + TxSentinel::OutOfGas => Error::GasError(err.to_string()), + TxSentinel::InvalidCommitment => { + Error::MissingSection(err.to_string()) + } } })?; diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index dfbc23d7af..96c1b67cda 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -19,6 +19,7 @@ use namada::vm::wasm::run::Error; use namada::vm::wasm::{self, TxCache, VpCache}; use namada::vm::{self, WasmCacheRwAccess}; use namada_tx_prelude::borsh_ext::BorshSerializeExt; +use namada_tx_prelude::transaction::TxSentinel; use namada_tx_prelude::{storage_api, Ctx}; use namada_vp_prelude::key::common; use tempfile::TempDir; @@ -51,7 +52,7 @@ pub struct TestTxEnv { pub iterators: PrefixIterators<'static, MockDB>, pub verifiers: BTreeSet
, pub gas_meter: TxGasMeter, - pub out_of_gas: bool, + pub sentinel: TxSentinel, pub tx_index: TxIndex, pub result_buffer: Option>, pub vp_wasm_cache: VpCache, @@ -76,7 +77,7 @@ impl Default for TestTxEnv { wl_storage, iterators: PrefixIterators::default(), gas_meter: TxGasMeter::new_from_sub_limit(100_000_000.into()), - out_of_gas: false, + sentinel: TxSentinel::default(), tx_index: TxIndex::default(), verifiers: BTreeSet::default(), result_buffer: None, @@ -346,7 +347,7 @@ mod native_tx_host_env { iterators, verifiers, gas_meter, - out_of_gas, + sentinel, result_buffer, tx_index, vp_wasm_cache, @@ -362,7 +363,7 @@ mod native_tx_host_env { iterators, verifiers, gas_meter, - out_of_gas, + sentinel, tx, tx_index, result_buffer, @@ -389,7 +390,7 @@ mod native_tx_host_env { iterators, verifiers, gas_meter, - out_of_gas, + sentinel, result_buffer, vp_wasm_cache, vp_cache_dir: _, @@ -404,7 +405,7 @@ mod native_tx_host_env { iterators, verifiers, gas_meter, - out_of_gas, + sentinel, tx, tx_index, result_buffer, @@ -418,7 +419,48 @@ mod native_tx_host_env { }) } }); - } + }; + + // unit, non-result, return type + ( "non-result", $fn:ident ( $($arg:ident : $type:ty),* $(,)?) ) => { + concat_idents!(extern_fn_name = namada, _, $fn { + #[no_mangle] + extern "C" fn extern_fn_name( $($arg: $type),* ) { + with(|TestTxEnv { + wl_storage, + iterators, + verifiers, + gas_meter, + sentinel, + result_buffer, + tx_index, + vp_wasm_cache, + vp_cache_dir: _, + tx_wasm_cache, + tx_cache_dir: _, + tx, + }: &mut TestTxEnv| { + + let tx_env = vm::host_env::testing::tx_env( + &wl_storage.storage, + &mut wl_storage.write_log, + iterators, + verifiers, + gas_meter, + sentinel, + tx, + tx_index, + result_buffer, + vp_wasm_cache, + tx_wasm_cache, + ); + + // Call the `host_env` function + $fn( &tx_env, $($arg),* ) + }) + } + }); + }; } // Implement all the exported functions from @@ -464,4 +506,5 @@ mod native_tx_host_env { native_host_fn!(tx_get_native_token(result_ptr: u64)); native_host_fn!(tx_log_string(str_ptr: u64, str_len: u64)); native_host_fn!(tx_charge_gas(used_gas: u64)); + native_host_fn!("non-result", tx_set_commitment_sentinel()); } diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index d2a286d542..0bfefdec5e 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -350,6 +350,10 @@ impl TxEnv for Ctx { None => Ok(Vec::new()), } } + + fn set_commitment_sentinel(&mut self) { + unsafe { namada_tx_set_commitment_sentinel() } + } } /// Execute IBC tx. diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index baadd56182..c3497c5c27 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -114,6 +114,9 @@ pub mod tx { /// Execute IBC tx. // Temp. workaround for pub fn namada_tx_ibc_execute(); + + /// Set the sentinel for a wrong tx section commitment + pub fn namada_tx_set_commitment_sentinel(); } } diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 6b7263ce9b..9f0e71d005 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -5,7 +5,10 @@ use namada_tx_prelude::*; #[transaction(gas = 160000)] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; - let data = signed.data().ok_or_err_msg("Missing data")?; + let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { + ctx.set_commitment_sentinel(); + err + })?; let bond = transaction::pos::Bond::try_from_slice(&data[..]) .wrap_err("failed to decode Bond") .unwrap(); diff --git a/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs index b287f84a6a..364e597550 100644 --- a/wasm/wasm_source/src/tx_bridge_pool.rs +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -8,7 +8,10 @@ use namada_tx_prelude::*; #[transaction(gas = 100000)] fn apply_tx(ctx: &mut Ctx, signed: Tx) -> TxResult { - let data = signed.data().ok_or_err_msg("Missing data")?; + let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { + ctx.set_commitment_sentinel(); + err + })?; let transfer = PendingTransfer::try_from_slice(&data[..]) .map_err(|e| Error::wrap("Error deserializing PendingTransfer", e))?; log_string("Received transfer to add to pool."); diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs index 0923797e36..dd0a1be0c1 100644 --- a/wasm/wasm_source/src/tx_change_validator_commission.rs +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -6,7 +6,10 @@ use namada_tx_prelude::*; #[transaction(gas = 220000)] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; - let data = signed.data().ok_or_err_msg("Missing data")?; + let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { + ctx.set_commitment_sentinel(); + err + })?; let CommissionChange { validator, new_rate, diff --git a/wasm/wasm_source/src/tx_ibc.rs b/wasm/wasm_source/src/tx_ibc.rs index 5a8bac9d4e..2a6d2fcae3 100644 --- a/wasm/wasm_source/src/tx_ibc.rs +++ b/wasm/wasm_source/src/tx_ibc.rs @@ -8,7 +8,10 @@ use namada_tx_prelude::*; #[transaction(gas = 1240000)] fn apply_tx(_ctx: &mut Ctx, _tx_data: Tx) -> TxResult { // let signed = tx_data; - // let data = signed.data().ok_or_err_msg("Missing data")?; + // let data = signed.data().ok_or_err_msg("Missing data").or_else(|err| { + // ctx.set_commitment_sentinel(); + // Err(err) + // })?; // ibc::ibc_actions(ctx).execute(&data).into_storage_result() diff --git a/wasm/wasm_source/src/tx_init_account.rs b/wasm/wasm_source/src/tx_init_account.rs index 78a8edb018..da8ddc21f1 100644 --- a/wasm/wasm_source/src/tx_init_account.rs +++ b/wasm/wasm_source/src/tx_init_account.rs @@ -6,16 +6,27 @@ use namada_tx_prelude::*; #[transaction(gas = 230000)] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; - let data = signed.data().ok_or_err_msg("Missing data")?; + let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { + ctx.set_commitment_sentinel(); + err + })?; let tx_data = transaction::account::InitAccount::try_from_slice(&data[..]) .wrap_err("failed to decode InitAccount")?; debug_log!("apply_tx called to init a new established account"); let vp_code = signed .get_section(&tx_data.vp_code_hash) - .ok_or_err_msg("vp code section not found")? + .ok_or_err_msg("vp code section not found") + .map_err(|err| { + ctx.set_commitment_sentinel(); + err + })? .extra_data_sec() - .ok_or_err_msg("vp code section must be tagged as extra")? + .ok_or_err_msg("vp code section must be tagged as extra") + .map_err(|err| { + ctx.set_commitment_sentinel(); + err + })? .code .hash(); diff --git a/wasm/wasm_source/src/tx_init_proposal.rs b/wasm/wasm_source/src/tx_init_proposal.rs index 870d53331b..8c860d9d22 100644 --- a/wasm/wasm_source/src/tx_init_proposal.rs +++ b/wasm/wasm_source/src/tx_init_proposal.rs @@ -4,7 +4,10 @@ use namada_tx_prelude::*; #[transaction(gas = 40000)] fn apply_tx(ctx: &mut Ctx, tx: Tx) -> TxResult { - let data = tx.data().ok_or_err_msg("Missing data")?; + let data = tx.data().ok_or_err_msg("Missing data").map_err(|err| { + ctx.set_commitment_sentinel(); + err + })?; let tx_data = transaction::governance::InitProposalData::try_from_slice(&data[..]) .wrap_err("failed to decode InitProposalData")?; @@ -12,18 +15,34 @@ fn apply_tx(ctx: &mut Ctx, tx: Tx) -> TxResult { // Get the content from the referred to section let content = tx .get_section(&tx_data.content) - .ok_or_err_msg("Missing proposal content")? + .ok_or_err_msg("Missing proposal content") + .map_err(|err| { + ctx.set_commitment_sentinel(); + err + })? .extra_data() - .ok_or_err_msg("Missing full proposal content")?; + .ok_or_err_msg("Missing full proposal content") + .map_err(|err| { + ctx.set_commitment_sentinel(); + err + })?; // Get the code from the referred to section let code_hash = tx_data.get_section_code_hash(); let code = match code_hash { Some(hash) => Some( tx.get_section(&hash) - .ok_or_err_msg("Missing proposal code")? + .ok_or_err_msg("Missing proposal code") + .map_err(|err| { + ctx.set_commitment_sentinel(); + err + })? .extra_data() - .ok_or_err_msg("Missing full proposal code")?, + .ok_or_err_msg("Missing full proposal code") + .map_err(|err| { + ctx.set_commitment_sentinel(); + err + })?, ), None => None, }; diff --git a/wasm/wasm_source/src/tx_init_validator.rs b/wasm/wasm_source/src/tx_init_validator.rs index 5605764ba2..bea7ed0a9f 100644 --- a/wasm/wasm_source/src/tx_init_validator.rs +++ b/wasm/wasm_source/src/tx_init_validator.rs @@ -7,7 +7,10 @@ use namada_tx_prelude::*; #[transaction(gas = 730000)] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; - let data = signed.data().ok_or_err_msg("Missing data")?; + let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { + ctx.set_commitment_sentinel(); + err + })?; let init_validator = InitValidator::try_from_slice(&data[..]) .wrap_err("failed to decode InitValidator")?; debug_log!("apply_tx called to init a new validator account"); @@ -15,9 +18,17 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { // Get the validator vp code from the extra section let validator_vp_code_hash = signed .get_section(&init_validator.validator_vp_code_hash) - .ok_or_err_msg("validator vp section not found")? + .ok_or_err_msg("validator vp section not found") + .map_err(|err| { + ctx.set_commitment_sentinel(); + err + })? .extra_data_sec() - .ok_or_err_msg("validator vp section must be tagged as extra")? + .ok_or_err_msg("validator vp section must be tagged as extra") + .map_err(|err| { + ctx.set_commitment_sentinel(); + err + })? .code .hash(); diff --git a/wasm/wasm_source/src/tx_redelegate.rs b/wasm/wasm_source/src/tx_redelegate.rs index adae605a81..84f45093aa 100644 --- a/wasm/wasm_source/src/tx_redelegate.rs +++ b/wasm/wasm_source/src/tx_redelegate.rs @@ -6,7 +6,10 @@ use namada_tx_prelude::*; #[transaction(gas = 460000)] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; - let data = signed.data().ok_or_err_msg("Missing data")?; + let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { + ctx.set_commitment_sentinel(); + err + })?; let transaction::pos::Redelegation { src_validator, dest_validator, diff --git a/wasm/wasm_source/src/tx_resign_steward.rs b/wasm/wasm_source/src/tx_resign_steward.rs index f87aea4bd9..79ea99ab2f 100644 --- a/wasm/wasm_source/src/tx_resign_steward.rs +++ b/wasm/wasm_source/src/tx_resign_steward.rs @@ -5,7 +5,10 @@ use namada_tx_prelude::*; #[transaction(gas = 40000)] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; - let data = signed.data().ok_or_err_msg("Missing data")?; + let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { + ctx.set_commitment_sentinel(); + err + })?; let steward_address = Address::try_from_slice(&data[..]) .wrap_err("failed to decode an Address")?; diff --git a/wasm/wasm_source/src/tx_reveal_pk.rs b/wasm/wasm_source/src/tx_reveal_pk.rs index 189ef2f4b3..ba2078d806 100644 --- a/wasm/wasm_source/src/tx_reveal_pk.rs +++ b/wasm/wasm_source/src/tx_reveal_pk.rs @@ -9,7 +9,10 @@ use namada_tx_prelude::*; #[transaction(gas = 170000)] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; - let data = signed.data().ok_or_err_msg("Missing data")?; + let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { + ctx.set_commitment_sentinel(); + err + })?; let pk = common::PublicKey::try_from_slice(&data[..]) .wrap_err("failed to decode common::PublicKey from tx_data")?; debug_log!("tx_reveal_pk called with pk: {pk}"); diff --git a/wasm/wasm_source/src/tx_transfer.rs b/wasm/wasm_source/src/tx_transfer.rs index f36f52c74d..9164ff1656 100644 --- a/wasm/wasm_source/src/tx_transfer.rs +++ b/wasm/wasm_source/src/tx_transfer.rs @@ -7,7 +7,10 @@ use namada_tx_prelude::*; #[transaction(gas = 110000)] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; - let data = signed.data().ok_or_err_msg("Missing data")?; + let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { + ctx.set_commitment_sentinel(); + err + })?; let transfer = token::Transfer::try_from_slice(&data[..]) .wrap_err("failed to decode token::Transfer")?; debug_log!("apply_tx called with transfer: {:#?}", transfer); @@ -28,6 +31,10 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { .get_section(hash) .and_then(|x| x.as_ref().masp_tx()) .ok_or_err_msg("unable to find shielded section") + .map_err(|err| { + ctx.set_commitment_sentinel(); + err + }) }) .transpose()?; if let Some(shielded) = shielded { diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 7f66b7a338..a440692d43 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -6,7 +6,10 @@ use namada_tx_prelude::*; #[transaction(gas = 430000)] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; - let data = signed.data().ok_or_err_msg("Missing data")?; + let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { + ctx.set_commitment_sentinel(); + err + })?; let unbond = transaction::pos::Unbond::try_from_slice(&data[..]) .wrap_err("failed to decode Unbond")?; diff --git a/wasm/wasm_source/src/tx_unjail_validator.rs b/wasm/wasm_source/src/tx_unjail_validator.rs index b487c44f7b..9beeb8086a 100644 --- a/wasm/wasm_source/src/tx_unjail_validator.rs +++ b/wasm/wasm_source/src/tx_unjail_validator.rs @@ -6,7 +6,10 @@ use namada_tx_prelude::*; #[transaction(gas = 340000)] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; - let data = signed.data().ok_or_err_msg("Missing data")?; + let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { + ctx.set_commitment_sentinel(); + err + })?; let validator = Address::try_from_slice(&data[..]) .wrap_err("failed to decode an Address")?; ctx.unjail_validator(&validator) diff --git a/wasm/wasm_source/src/tx_update_account.rs b/wasm/wasm_source/src/tx_update_account.rs index d6315f9ace..a4279ca6a2 100644 --- a/wasm/wasm_source/src/tx_update_account.rs +++ b/wasm/wasm_source/src/tx_update_account.rs @@ -8,7 +8,10 @@ use namada_tx_prelude::*; #[transaction(gas = 140000)] fn apply_tx(ctx: &mut Ctx, tx: Tx) -> TxResult { let signed = tx; - let data = signed.data().ok_or_err_msg("Missing data")?; + let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { + ctx.set_commitment_sentinel(); + err + })?; let tx_data = transaction::account::UpdateAccount::try_from_slice(&data[..]) .wrap_err("failed to decode UpdateAccount")?; @@ -19,9 +22,17 @@ fn apply_tx(ctx: &mut Ctx, tx: Tx) -> TxResult { if let Some(hash) = tx_data.vp_code_hash { let vp_code_hash = signed .get_section(&hash) - .ok_or_err_msg("vp code section not found")? + .ok_or_err_msg("vp code section not found") + .map_err(|err| { + ctx.set_commitment_sentinel(); + err + })? .extra_data_sec() - .ok_or_err_msg("vp code section must be tagged as extra")? + .ok_or_err_msg("vp code section must be tagged as extra") + .map_err(|err| { + ctx.set_commitment_sentinel(); + err + })? .code .hash(); diff --git a/wasm/wasm_source/src/tx_update_steward_commission.rs b/wasm/wasm_source/src/tx_update_steward_commission.rs index a389e3d36a..0d98bd1ba1 100644 --- a/wasm/wasm_source/src/tx_update_steward_commission.rs +++ b/wasm/wasm_source/src/tx_update_steward_commission.rs @@ -6,7 +6,10 @@ use namada_tx_prelude::*; #[transaction(gas = 40000)] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; - let data = signed.data().ok_or_err_msg("Missing data")?; + let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { + ctx.set_commitment_sentinel(); + err + })?; let steward_commission = UpdateStewardCommission::try_from_slice(&data[..]) .wrap_err("failed to decode an UpdateStewardCommission")?; diff --git a/wasm/wasm_source/src/tx_update_vp.rs b/wasm/wasm_source/src/tx_update_vp.rs index 65c5a5b1da..7d372ee749 100644 --- a/wasm/wasm_source/src/tx_update_vp.rs +++ b/wasm/wasm_source/src/tx_update_vp.rs @@ -7,16 +7,27 @@ use namada_tx_prelude::*; #[transaction(gas = 40000)] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; - let data = signed.data().ok_or_err_msg("Missing data")?; + let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { + ctx.set_commitment_sentinel(); + err + })?; let update_vp = transaction::UpdateVp::try_from_slice(&data[..]) .wrap_err("failed to decode UpdateVp")?; debug_log!("update VP for: {:#?}", update_vp.addr); let vp_code_hash = signed .get_section(&update_vp.vp_code_hash) - .ok_or_err_msg("vp code section not found")? + .ok_or_err_msg("vp code section not found") + .map_err(|err| { + ctx.set_commitment_sentinel(); + err + })? .extra_data_sec() - .ok_or_err_msg("vp code section must be tagged as extra")? + .ok_or_err_msg("vp code section must be tagged as extra") + .map_err(|err| { + ctx.set_commitment_sentinel(); + err + })? .code .hash(); ctx.update_validity_predicate(&update_vp.addr, vp_code_hash) diff --git a/wasm/wasm_source/src/tx_vote_proposal.rs b/wasm/wasm_source/src/tx_vote_proposal.rs index 9bfd4d891b..d23820ab8a 100644 --- a/wasm/wasm_source/src/tx_vote_proposal.rs +++ b/wasm/wasm_source/src/tx_vote_proposal.rs @@ -5,7 +5,10 @@ use namada_tx_prelude::*; #[transaction(gas = 120000)] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; - let data = signed.data().ok_or_err_msg("Missing data")?; + let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { + ctx.set_commitment_sentinel(); + err + })?; let tx_data = transaction::governance::VoteProposalData::try_from_slice(&data[..]) .wrap_err("failed to decode VoteProposalData")?; diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 5bedf44c42..4ba555c9dc 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -6,7 +6,10 @@ use namada_tx_prelude::*; #[transaction(gas = 260000)] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; - let data = signed.data().ok_or_err_msg("Missing data")?; + let data = signed.data().ok_or_err_msg("Missing data").map_err(|err| { + ctx.set_commitment_sentinel(); + err + })?; let withdraw = transaction::pos::Withdraw::try_from_slice(&data[..]) .wrap_err("failed to decode Withdraw")?; From 81728771b8ef2490b84dc326a90958994c7caff7 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 25 Oct 2023 17:33:18 +0200 Subject: [PATCH 04/10] Changelog #1905 --- .../improvements/1905-replay-protection-improvements.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/1905-replay-protection-improvements.md diff --git a/.changelog/unreleased/improvements/1905-replay-protection-improvements.md b/.changelog/unreleased/improvements/1905-replay-protection-improvements.md new file mode 100644 index 0000000000..4e44cb732a --- /dev/null +++ b/.changelog/unreleased/improvements/1905-replay-protection-improvements.md @@ -0,0 +1,2 @@ +- Improved replay protection for invalid transactions. + ([\#1905](https://github.com/anoma/namada/pull/1905)) \ No newline at end of file From d36ceae0b0a3b263c09d5d364fd5e1a20aff9f66 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 6 Nov 2023 14:33:17 +0100 Subject: [PATCH 05/10] Check inner tx hash only against the storage --- apps/src/lib/node/ledger/shell/mod.rs | 9 ++++----- core/src/ledger/storage/wl_storage.rs | 8 ++++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 4c924400fc..2157e94586 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -950,8 +950,10 @@ where .map_err(|e| Error::ReplayAttempt(e.to_string()))?; let inner_tx_hash = wrapper.raw_header_hash(); + // Do the inner tx hash check only against the storage, skip the write + // log if temp_wl_storage - .has_replay_protection_entry(&inner_tx_hash) + .has_committed_replay_protection_entry(&inner_tx_hash) .expect("Error while checking inner tx hash key in storage") { return Err(Error::ReplayAttempt(format!( @@ -960,10 +962,7 @@ where ))); } - // Write inner hash to WAL - temp_wl_storage - .write_tx_hash(inner_tx_hash) - .map_err(|e| Error::ReplayAttempt(e.to_string())) + Ok(()) } /// If a handle to an Ethereum oracle was provided to the [`Shell`], attempt diff --git a/core/src/ledger/storage/wl_storage.rs b/core/src/ledger/storage/wl_storage.rs index 6d30e77c73..4d613365c0 100644 --- a/core/src/ledger/storage/wl_storage.rs +++ b/core/src/ledger/storage/wl_storage.rs @@ -69,6 +69,14 @@ where self.storage.has_replay_protection_entry(hash) } + + /// Check if the given tx hash has already been committed to storage + pub fn has_committed_replay_protection_entry( + &self, + hash: &Hash, + ) -> Result { + self.storage.has_replay_protection_entry(hash) + } } /// Common trait for [`WlStorage`] and [`TempWlStorage`], used to implement From e44ce6d6cc06afcc1a1b83cc07432ad546f06db2 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 6 Nov 2023 14:37:03 +0100 Subject: [PATCH 06/10] Delayed inner tx hash write --- .../lib/node/ledger/shell/finalize_block.rs | 53 ++++++++----------- shared/src/ledger/protocol/mod.rs | 37 +++++++------ 2 files changed, 44 insertions(+), 46 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index e131c4e088..b27333f4b6 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -210,17 +210,13 @@ where tx_event["gas_used"] = "0".into(); response.events.push(tx_event); // if the rejected tx was decrypted, remove it - // from the queue of txs to be processed, remove its hash - // from storage and write the hash of the corresponding wrapper + // from the queue of txs to be processed if let TxType::Decrypted(_) = &tx_header.tx_type { - let wrapper_tx = self - .wl_storage + self.wl_storage .storage .tx_queue .pop() - .expect("Missing wrapper tx in queue") - .tx; - self.allow_tx_replay(wrapper_tx); + .expect("Missing wrapper tx in queue"); } #[cfg(not(any(feature = "abciplus", feature = "abcipp")))] @@ -314,9 +310,6 @@ where "Tx with hash {} was un-decryptable", tx_in_queue.tx.header_hash() ); - // Remove inner tx hash from storage - self.allow_tx_replay(tx_in_queue.tx); - event["info"] = "Transaction is invalid.".into(); event["log"] = "Transaction could not be \ @@ -456,6 +449,9 @@ where result ); stats.increment_successful_txs(); + if let Some(wrapper) = embedding_wrapper { + self.commit_inner_tx_hash(wrapper); + } } self.wl_storage.commit_tx(); if !tx_event.contains_key("code") { @@ -497,10 +493,11 @@ where ); if let Some(wrapper) = embedding_wrapper { - if result.vps_result.invalid_sig { - // Invalid signature was found, remove the tx - // hash from storage to allow replay - self.allow_tx_replay(wrapper); + // If decrypted tx failed for any reason but invalid + // signature, commit its hash to storage, otherwise + // allow for a replay + if !result.vps_result.invalid_sig { + self.commit_inner_tx_hash(wrapper); } } @@ -518,26 +515,19 @@ where msg ); - // If transaction type is Decrypted and failed because of - // out of gas or invalid section commtiment, remove its hash - // from storage to allow rewrapping it + // If transaction type is Decrypted and didn't failed + // because of out of gas nor invalid + // section commitment, commit its hash to prevent replays if let Some(wrapper) = embedding_wrapper { - if matches!( + if !matches!( msg, Error::TxApply(protocol::Error::GasError(_)) | Error::TxApply( protocol::Error::MissingSection(_) ) ) { - self.allow_tx_replay(wrapper); + self.commit_inner_tx_hash(wrapper); } - } else if let Some(wrapper) = wrapper { - // If transaction type was Wrapper and failed, write its - // hash to storage to prevent - // replay - self.wl_storage - .write_tx_hash(wrapper.header_hash()) - .expect("Error while writing tx hash to storage"); } stats.increment_errored_txs(); @@ -950,15 +940,16 @@ where Ok(()) } - // Allow to replay a specific wasm transaction. Needs as argument the - // corresponding wrapper transaction to avoid replay of that in the process - fn allow_tx_replay(&mut self, wrapper_tx: Tx) { + // Write the inner tx hash to storage and remove the corresponding wrapper + // hash since it's redundant. Requires the wrapper transaction as argument + // to recover both the hashes. + fn commit_inner_tx_hash(&mut self, wrapper_tx: Tx) { self.wl_storage - .write_tx_hash(wrapper_tx.header_hash()) + .write_tx_hash(wrapper_tx.raw_header_hash()) .expect("Error while writing tx hash to storage"); self.wl_storage - .delete_tx_hash(wrapper_tx.raw_header_hash()) + .delete_tx_hash(wrapper_tx.header_hash()) .expect("Error while deleting tx hash from storage"); } } diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index e2292bd1a7..07430c1ccf 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -29,7 +29,7 @@ use crate::ledger::pgf::PgfVp; use crate::ledger::pos::{self, PosVP}; use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::{DBIter, Storage, StorageHasher, WlStorage, DB}; -use crate::ledger::{replay_protection, storage_api}; +use crate::ledger::storage_api; use crate::proto::{self, Tx}; use crate::types::address::{Address, InternalAddress}; use crate::types::storage; @@ -62,6 +62,10 @@ pub enum Error { FeeError(String), #[error("Invalid transaction signature")] InvalidTxSignature, + #[error( + "The decrypted transaction {0} has already been applied in this block" + )] + ReplayAttempt(Hash), #[error("Error executing VP for addresses: {0:?}")] VpRunnerError(vm::wasm::run::Error), #[error("The address {0} doesn't exist")] @@ -212,12 +216,11 @@ where } /// Performs the required operation on a wrapper transaction: +/// - replay protection /// - fee payment /// - gas accounting -/// - replay protection /// -/// Returns the set of changed storage keys. The caller should write the hash of -/// the wrapper header to storage in case of failure. +/// Returns the set of changed storage keys. pub(crate) fn apply_wrapper_tx<'a, D, H, CA, WLS>( tx: Tx, wrapper: &WrapperTx, @@ -234,6 +237,12 @@ where { let mut changed_keys = BTreeSet::default(); + // Write wrapper tx hash to storage + shell_params + .wl_storage + .write_tx_hash(tx.header_hash()) + .expect("Error while writing tx hash to storage"); + // Charge fee before performing any fallible operations charge_fee( wrapper, @@ -249,15 +258,6 @@ where .add_tx_size_gas(tx_bytes) .map_err(|err| Error::GasError(err.to_string()))?; - // If wrapper was succesful, write inner tx hash to storage - shell_params - .wl_storage - .write_tx_hash(tx.raw_header_hash()) - .expect("Error while writing tx hash to storage"); - changed_keys.insert(replay_protection::get_replay_protection_last_key( - &tx.raw_header_hash(), - )); - Ok(changed_keys) } @@ -578,6 +578,13 @@ where ) }; + let tx_hash = tx.raw_header_hash(); + if let Some(true) = write_log.has_replay_protection_entry(&tx_hash) { + // If the same transaction has already been applied in this block, skip + // execution and return + return Err(Error::ReplayAttempt(tx_hash)); + } + let verifiers = execute_tx( &tx, tx_index, @@ -1035,10 +1042,10 @@ where Err(err) => match err { // Execution of VPs can (and must) be short-circuited // only in case of a gas overflow to prevent the - // transaction from conuming resources that have not + // transaction from consuming resources that have not // been acquired in the corresponding wrapper tx. For // all the other errors we keep evaluating the vps. This - // allow to display a consistent VpsResult accross all + // allows to display a consistent VpsResult accross all // nodes and find any invalid signatures Error::GasError(_) => { return Err(err); From bb611dc711e68109354ca42bfdb950ab09ba45da Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 6 Nov 2023 16:47:51 +0100 Subject: [PATCH 07/10] Fixes and updates tests --- .../lib/node/ledger/shell/finalize_block.rs | 183 ++++++++++++++++-- .../lib/node/ledger/shell/prepare_proposal.rs | 46 ++--- .../lib/node/ledger/shell/process_proposal.rs | 21 +- 3 files changed, 178 insertions(+), 72 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index b27333f4b6..0dfe03b158 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -525,8 +525,22 @@ where | Error::TxApply( protocol::Error::MissingSection(_) ) + | Error::TxApply( + protocol::Error::ReplayAttempt(_) + ) ) { self.commit_inner_tx_hash(wrapper); + } else if let Error::TxApply( + protocol::Error::ReplayAttempt(_), + ) = msg + { + // Remove the wrapper hash but keep the inner tx + // hash + self.wl_storage + .delete_tx_hash(wrapper.header_hash()) + .expect( + "Error while deleting tx hash from storage", + ); } } @@ -2251,9 +2265,9 @@ mod test_finalize_block { let (wrapper_tx, processed_tx) = mk_wrapper_tx(&shell, &crate::wallet::defaults::albert_keypair()); - let decrypted_hash_key = + let wrapper_hash_key = replay_protection::get_replay_protection_last_key( - &wrapper_tx.raw_header_hash(), + &wrapper_tx.header_hash(), ); // merkle tree root before finalize_block @@ -2286,7 +2300,7 @@ mod test_finalize_block { .shell .wl_storage .write_log - .has_replay_protection_entry(&wrapper_tx.raw_header_hash()) + .has_replay_protection_entry(&wrapper_tx.header_hash()) .unwrap_or_default() ); // Check that the hash is present in the merkle tree @@ -2297,15 +2311,145 @@ mod test_finalize_block { .storage .block .tree - .has_key(&decrypted_hash_key) + .has_key(&wrapper_hash_key) .unwrap() ); } + /// Test that a decrypted tx that has already been applied in the same block + /// doesn't get reapplied + #[test] + fn test_duplicated_decrypted_tx_same_block() { + let (mut shell, _, _, _) = setup(); + let keypair = gen_keypair(); + let keypair_2 = gen_keypair(); + let mut batch = + namada::core::ledger::storage::testing::TestStorage::batch(); + + let tx_code = TestWasms::TxNoOp.read_bytes(); + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount_per_gas_unit: 1.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + GAS_LIMIT_MULTIPLIER.into(), + None, + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new(tx_code)); + wrapper.set_data(Data::new( + "Decrypted transaction data".as_bytes().to_owned(), + )); + + let mut new_wrapper = wrapper.clone(); + new_wrapper.update_header(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount_per_gas_unit: 1.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair_2.ref_to(), + Epoch(0), + GAS_LIMIT_MULTIPLIER.into(), + None, + )))); + new_wrapper.add_section(Section::Signature(Signature::new( + new_wrapper.sechashes(), + [(0, keypair_2)].into_iter().collect(), + None, + ))); + wrapper.add_section(Section::Signature(Signature::new( + wrapper.sechashes(), + [(0, keypair)].into_iter().collect(), + None, + ))); + + let mut inner = wrapper.clone(); + let mut new_inner = new_wrapper.clone(); + + for inner in [&mut inner, &mut new_inner] { + inner.update_header(TxType::Decrypted(DecryptedTx::Decrypted)); + } + + // Write wrapper hashes in storage + for tx in [&wrapper, &new_wrapper] { + let hash_subkey = + replay_protection::get_replay_protection_last_subkey( + &tx.header_hash(), + ); + shell + .wl_storage + .storage + .write_replay_protection_entry(&mut batch, &hash_subkey) + .expect("Test failed"); + } + + let mut processed_txs: Vec = vec![]; + for inner in [&inner, &new_inner] { + processed_txs.push(ProcessedTx { + tx: inner.to_bytes(), + result: TxResult { + code: ErrorCodes::Ok.into(), + info: "".into(), + }, + }) + } + + shell.enqueue_tx(wrapper.clone(), GAS_LIMIT_MULTIPLIER.into()); + shell.enqueue_tx(new_wrapper.clone(), GAS_LIMIT_MULTIPLIER.into()); + // merkle tree root before finalize_block + let root_pre = shell.shell.wl_storage.storage.block.tree.root(); + + let event = &shell + .finalize_block(FinalizeBlock { + txs: processed_txs, + ..Default::default() + }) + .expect("Test failed"); + + // the merkle tree root should not change after finalize_block + let root_post = shell.shell.wl_storage.storage.block.tree.root(); + assert_eq!(root_pre.0, root_post.0); + + assert_eq!(event[0].event_type.to_string(), String::from("applied")); + let code = event[0] + .attributes + .get("code") + .expect("Testfailed") + .as_str(); + assert_eq!(code, String::from(ErrorCodes::Ok).as_str()); + assert_eq!(event[1].event_type.to_string(), String::from("applied")); + let code = event[1] + .attributes + .get("code") + .expect("Testfailed") + .as_str(); + assert_eq!(code, String::from(ErrorCodes::WasmRuntimeError).as_str()); + + for (inner, wrapper) in [(inner, wrapper), (new_inner, new_wrapper)] { + assert!( + shell + .wl_storage + .write_log + .has_replay_protection_entry(&inner.raw_header_hash()) + .unwrap_or_default() + ); + assert!( + !shell + .wl_storage + .write_log + .has_replay_protection_entry(&wrapper.header_hash()) + .unwrap_or_default() + ); + } + } + /// Test that if a decrypted transaction fails because of out-of-gas, /// undecryptable, invalid signature or wrong section commitment, its hash - /// is removed from storage. Also checks that a tx failing for other - /// reason has its hash persisted to storage. + /// is not committed to storage. Also checks that a tx failing for other + /// reason has its hash written to storage. #[test] fn test_tx_hash_handling() { let (mut shell, _, _, _) = setup(); @@ -2372,17 +2516,17 @@ mod test_finalize_block { inner.update_header(TxType::Decrypted(DecryptedTx::Decrypted)); } - // Write inner hashes in storage - for inner in [ - &out_of_gas_inner, - &undecryptable_inner, - &unsigned_inner, - &wrong_commitment_inner, - &failing_inner, + // Write wrapper hashes in storage + for wrapper in [ + &out_of_gas_wrapper, + &undecryptable_wrapper, + &unsigned_wrapper, + &wrong_commitment_wrapper, + &failing_wrapper, ] { let hash_subkey = replay_protection::get_replay_protection_last_subkey( - &inner.header_hash(), + &wrapper.header_hash(), ); shell .wl_storage @@ -2433,7 +2577,6 @@ mod test_finalize_block { let root_post = shell.shell.wl_storage.storage.block.tree.root(); assert_eq!(root_pre.0, root_post.0); - // Check inner tx hash has been removed from storage assert_eq!(event[0].event_type.to_string(), String::from("applied")); let code = event[0] .attributes @@ -2480,13 +2623,15 @@ mod test_finalize_block { !shell .wl_storage .write_log - .has_replay_protection_entry(&invalid_inner.header_hash()) + .has_replay_protection_entry( + &invalid_inner.raw_header_hash() + ) .unwrap_or_default() ); assert!( shell .wl_storage - .write_log + .storage .has_replay_protection_entry(&valid_wrapper.header_hash()) .unwrap_or_default() ); @@ -2494,8 +2639,8 @@ mod test_finalize_block { assert!( shell .wl_storage - .storage - .has_replay_protection_entry(&failing_inner.header_hash()) + .write_log + .has_replay_protection_entry(&failing_inner.raw_header_hash()) .expect("test failed") ); assert!( diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 2df4b520c5..94e8190048 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -248,7 +248,7 @@ where // Check replay protection, safe to do here. Even if the tx is a // replay attempt, we can leave its hashes in the write log since, // having we already checked the signature, no other tx with the - // same hash can ba deemed valid + // same hash can be deemed valid self.replay_protection_checks(&tx, temp_wl_storage) .map_err(|_| ())?; @@ -1201,14 +1201,8 @@ mod test_prepare_proposal { ..Default::default() }; - let received = - shell.prepare_proposal(req).txs.into_iter().map(|tx_bytes| { - Tx::try_from(tx_bytes.as_slice()) - .expect("Test failed") - .data() - .expect("Test failed") - }); - assert_eq!(received.len(), 0); + let received_txs = shell.prepare_proposal(req).txs; + assert_eq!(received_txs.len(), 0); } /// Test that if two identical wrapper txs are proposed for this block, only @@ -1242,14 +1236,8 @@ mod test_prepare_proposal { txs: vec![wrapper.to_bytes(); 2], ..Default::default() }; - let received = - shell.prepare_proposal(req).txs.into_iter().map(|tx_bytes| { - Tx::try_from(tx_bytes.as_slice()) - .expect("Test failed") - .data() - .expect("Test failed") - }); - assert_eq!(received.len(), 1); + let received_txs = shell.prepare_proposal(req).txs; + assert_eq!(received_txs.len(), 1); } /// Test that if the unsigned inner tx hash is known (replay attack), the @@ -1295,18 +1283,12 @@ mod test_prepare_proposal { ..Default::default() }; - let received = - shell.prepare_proposal(req).txs.into_iter().map(|tx_bytes| { - Tx::try_from(tx_bytes.as_slice()) - .expect("Test failed") - .data() - .expect("Test failed") - }); - assert_eq!(received.len(), 0); + let received_txs = shell.prepare_proposal(req).txs; + assert_eq!(received_txs.len(), 0); } /// Test that if two identical decrypted txs are proposed for this block, - /// only one gets accepted + /// both get accepted #[test] fn test_inner_tx_hash_same_block() { let (shell, _recv, _, _) = test_utils::setup(); @@ -1347,7 +1329,7 @@ mod test_prepare_proposal { None, )))); new_wrapper.add_section(Section::Signature(Signature::new( - wrapper.sechashes(), + new_wrapper.sechashes(), [(0, keypair_2)].into_iter().collect(), None, ))); @@ -1356,14 +1338,8 @@ mod test_prepare_proposal { txs: vec![wrapper.to_bytes(), new_wrapper.to_bytes()], ..Default::default() }; - let received = - shell.prepare_proposal(req).txs.into_iter().map(|tx_bytes| { - Tx::try_from(tx_bytes.as_slice()) - .expect("Test failed") - .data() - .expect("Test failed") - }); - assert_eq!(received.len(), 1); + let received_txs = shell.prepare_proposal(req).txs; + assert_eq!(received_txs.len(), 2); } /// Test that expired wrapper transactions are not included in the block diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 4968faa050..e337a7b5af 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -2257,7 +2257,7 @@ mod test_process_proposal { } /// Test that a block containing two identical inner transactions is - /// rejected + /// accepted #[test] fn test_inner_tx_hash_same_block() { let (shell, _recv, _, _) = test_utils::setup(); @@ -2285,7 +2285,6 @@ mod test_process_proposal { [(0, keypair)].into_iter().collect(), None, ))); - let inner_unsigned_hash = wrapper.raw_header_hash(); new_wrapper.update_header(TxType::Wrapper(Box::new(WrapperTx::new( Fee { @@ -2308,22 +2307,8 @@ mod test_process_proposal { txs: vec![wrapper.to_bytes(), new_wrapper.to_bytes()], }; match shell.process_proposal(request) { - Ok(_) => panic!("Test failed"), - Err(TestError::RejectProposal(response)) => { - assert_eq!(response[0].result.code, u32::from(ErrorCodes::Ok)); - assert_eq!( - response[1].result.code, - u32::from(ErrorCodes::ReplayTx) - ); - assert_eq!( - response[1].result.info, - format!( - "Transaction replay attempt: Inner transaction hash \ - {} already in storage", - inner_unsigned_hash - ) - ); - } + Ok(received) => assert_eq!(received.len(), 2), + Err(_) => panic!("Test failed"), } } From 0cfe2fbc4e918acaac4b848e5eb973b4497034af Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 6 Nov 2023 16:58:54 +0100 Subject: [PATCH 08/10] Inverts the order of check for replay protection --- apps/src/lib/node/ledger/shell/mod.rs | 30 +++++++++---------- .../lib/node/ledger/shell/prepare_proposal.rs | 4 --- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 2157e94586..50d84dc807 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -933,6 +933,19 @@ where wrapper: &Tx, temp_wl_storage: &mut TempWlStorage, ) -> Result<()> { + let inner_tx_hash = wrapper.raw_header_hash(); + // Check the inner tx hash only against the storage, skip the write + // log + if temp_wl_storage + .has_committed_replay_protection_entry(&inner_tx_hash) + .expect("Error while checking inner tx hash key in storage") + { + return Err(Error::ReplayAttempt(format!( + "Inner transaction hash {} already in storage", + &inner_tx_hash, + ))); + } + let wrapper_hash = wrapper.header_hash(); if temp_wl_storage .has_replay_protection_entry(&wrapper_hash) @@ -947,22 +960,7 @@ where // Write wrapper hash to WAL temp_wl_storage .write_tx_hash(wrapper_hash) - .map_err(|e| Error::ReplayAttempt(e.to_string()))?; - - let inner_tx_hash = wrapper.raw_header_hash(); - // Do the inner tx hash check only against the storage, skip the write - // log - if temp_wl_storage - .has_committed_replay_protection_entry(&inner_tx_hash) - .expect("Error while checking inner tx hash key in storage") - { - return Err(Error::ReplayAttempt(format!( - "Inner transaction hash {} already in storage", - &inner_tx_hash, - ))); - } - - Ok(()) + .map_err(|e| Error::ReplayAttempt(e.to_string())) } /// If a handle to an Ethereum oracle was provided to the [`Shell`], attempt diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 94e8190048..0f652d5130 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -245,10 +245,6 @@ where let mut tx_gas_meter = TxGasMeter::new(wrapper.gas_limit); tx_gas_meter.add_tx_size_gas(tx_bytes).map_err(|_| ())?; - // Check replay protection, safe to do here. Even if the tx is a - // replay attempt, we can leave its hashes in the write log since, - // having we already checked the signature, no other tx with the - // same hash can be deemed valid self.replay_protection_checks(&tx, temp_wl_storage) .map_err(|_| ())?; From 47b062f7816cc3ba52cba9c3396f2e4011babeb5 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 7 Nov 2023 10:37:27 +0100 Subject: [PATCH 09/10] Improves comments on wrapper tx hash removal --- apps/src/lib/node/ledger/shell/finalize_block.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 0dfe03b158..5de09e508f 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -535,7 +535,9 @@ where ) = msg { // Remove the wrapper hash but keep the inner tx - // hash + // hash. A replay of the wrapper is impossible since + // the inner tx hash is committed to storage and + // we validate the wrapper against that hash too self.wl_storage .delete_tx_hash(wrapper.header_hash()) .expect( @@ -955,8 +957,9 @@ where } // Write the inner tx hash to storage and remove the corresponding wrapper - // hash since it's redundant. Requires the wrapper transaction as argument - // to recover both the hashes. + // hash since it's redundant (we check the inner tx hash too when validating + // the wrapper). Requires the wrapper transaction as argument to recover + // both the hashes. fn commit_inner_tx_hash(&mut self, wrapper_tx: Tx) { self.wl_storage .write_tx_hash(wrapper_tx.raw_header_hash()) From b9e4b7a88ffb1ec9406cc8b187bd9dbb57263f32 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 7 Nov 2023 13:48:52 +0100 Subject: [PATCH 10/10] Changelog #2104 --- .../improvements/2104-replay-protection-delay-write.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/2104-replay-protection-delay-write.md diff --git a/.changelog/unreleased/improvements/2104-replay-protection-delay-write.md b/.changelog/unreleased/improvements/2104-replay-protection-delay-write.md new file mode 100644 index 0000000000..8ab0ba0377 --- /dev/null +++ b/.changelog/unreleased/improvements/2104-replay-protection-delay-write.md @@ -0,0 +1,2 @@ +- Moved the inner transaction replay check at execution time. + ([\#2104](https://github.com/anoma/namada/pull/2104)) \ No newline at end of file