Skip to content

Commit

Permalink
Extends replay protection unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
grarco committed Sep 26, 2023
1 parent 037fbf7 commit 21d3385
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 35 deletions.
168 changes: 133 additions & 35 deletions apps/src/lib/node/ledger/shell/finalize_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ where
event["log"] =
"Transaction could not be decrypted.".into();
event["code"] = ErrorCodes::Undecryptable.into();
response.events.push(event);
continue;
}
}
Expand Down Expand Up @@ -1119,6 +1120,7 @@ mod test_finalize_block {
use namada::core::ledger::governance::storage::vote::{
StorageProposalVote, VoteType,
};
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,
};
Expand Down Expand Up @@ -1161,6 +1163,7 @@ mod test_finalize_block {
use namada::types::transaction::{Fee, WrapperTx};
use namada::types::uint::Uint;
use namada::types::vote_extensions::ethereum_events;
use namada_test_utils::tx_data::TxWriteData;
use namada_test_utils::TestWasms;
use test_log::test;

Expand All @@ -1174,7 +1177,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.
Expand Down Expand Up @@ -2426,18 +2429,23 @@ mod test_finalize_block {
);
}

/// Test that if a decrypted transaction fails because of out-of-gas, its
/// hash is removed from storage to allow rewrapping it
/// 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_remove_tx_hash() {
let (mut shell, _, _, _) = setup();
let keypair = gen_keypair();

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();
wasm_path.push("wasm_for_tests/tx_no_op.wasm");
// 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 wrapper_tx =
let mut unsigned_wrapper =
Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new(
Fee {
amount_per_gas_unit: Amount::zero(),
Expand All @@ -2450,57 +2458,147 @@ mod test_finalize_block {
None,
None,
))));
wrapper_tx.header.chain_id = shell.chain_id.clone();
wrapper_tx.set_code(Code::new(tx_code));
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(
TxWriteData {
key,
value: "test".as_bytes().to_owned(),
}
.try_to_vec()
.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(),
));
let mut decrypted_tx = wrapper_tx.clone();

decrypted_tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted {
#[cfg(not(feature = "mainnet"))]
has_valid_pow: false,
}));
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 {
#[cfg(not(feature = "mainnet"))]
has_valid_pow: false,
}));
}

// Write inner hash in storage
let inner_hash_key = replay_protection::get_replay_protection_key(
&wrapper_tx.clone().update_header(TxType::Raw).header_hash(),
);
shell
.wl_storage
.storage
.write(&inner_hash_key, vec![])
.expect("Test failed");
// Write inner hashes in storage
for inner in [
&out_of_gas_inner,
&undecryptable_inner,
&unsigned_inner,
&failing_inner,
] {
let inner_hash_key = replay_protection::get_replay_protection_key(
&inner.clone().update_header(TxType::Raw).header_hash(),
);
shell
.wl_storage
.storage
.write(&inner_hash_key, vec![])
.expect("Test failed");
}

let processed_tx = ProcessedTx {
tx: decrypted_tx.to_bytes(),
result: TxResult {
code: ErrorCodes::Ok.into(),
info: "".into(),
},
};
shell.enqueue_tx(wrapper_tx, Gas::default());
let mut processed_txs: Vec<ProcessedTx> = 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, Gas::default());
shell.enqueue_tx(undecryptable_wrapper, GAS_LIMIT_MULTIPLIER.into());
shell.enqueue_tx(unsigned_wrapper, GAS_LIMIT_MULTIPLIER.into());
shell.enqueue_tx(failing_wrapper, 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: vec![processed_tx],
txs: processed_txs,
..Default::default()
})
.expect("Test failed")[0];
.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);

// Check inner tx hash has been removed from storage
assert_eq!(event.event_type.to_string(), String::from("applied"));
let code = event.attributes.get("code").expect("Testfailed").as_str();
assert_eq!(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::WasmRuntimeError).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::Undecryptable).as_str());
assert_eq!(event[2].event_type.to_string(), String::from("applied"));
let code = event[2]
.attributes
.get("code")
.expect("Testfailed")
.as_str();
assert_eq!(code, String::from(ErrorCodes::WasmRuntimeError).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());

for mut inner in [out_of_gas_inner, undecryptable_inner, unsigned_inner]
{
let inner_hash_key = replay_protection::get_replay_protection_key(
&inner.update_header(TxType::Raw).header_hash(),
);
assert!(
!shell
.wl_storage
.has_key(&inner_hash_key)
.expect("Test failed")
)
}

// Check that hash of failing tx is persisted to storage
let inner_hash_key = replay_protection::get_replay_protection_key(
&failing_inner.update_header(TxType::Raw).header_hash(),
);
assert!(
!shell
shell
.wl_storage
.has_key(&inner_hash_key)
.expect("Test failed")
Expand Down
1 change: 1 addition & 0 deletions wasm_for_tests/wasm_source/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down
1 change: 1 addition & 0 deletions wasm_for_tests/wasm_source/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions wasm_for_tests/wasm_source/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 21d3385

Please sign in to comment.