Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replay protection #1017

Merged
merged 35 commits into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
1368c19
core: added `TempWlStorage` for ABCI++ prepare/process proposal
tzemanovic Jan 19, 2023
2bb8ead
changelog: add #1051
tzemanovic Feb 10, 2023
0b1aa74
Updates replay protection specs
grarco Dec 29, 2022
77e1627
Adds replay protection internal address and vp
grarco Dec 29, 2022
e028f9f
Updates replay protections specs with governance and unsigned inner hash
grarco Jan 4, 2023
14019da
Adds tx hash check in mempool validate
grarco Jan 4, 2023
c8710d7
Wrapper commit hash on unsigned inner tx
grarco Jan 9, 2023
78f1333
Unit test `mempool_validate`
grarco Jan 10, 2023
a5962d1
Refactors `unsigned_hash_tx`
grarco Jan 10, 2023
8cbccf7
Fixes replay protection specs
grarco Jan 10, 2023
5decfc9
Replay protection checks in `process_proposal`
grarco Jan 11, 2023
c795dce
Refactors `process_proposal`
grarco Jan 11, 2023
c00cb14
Fixes error codes
grarco Jan 11, 2023
b592344
Removes tx hash from storage in `finalize_block`
grarco Jan 11, 2023
f720b0a
Updates `process_proposal` unit tests
grarco Jan 12, 2023
a764bdb
Updates replay protection specs with protocol txs
grarco Jan 12, 2023
1f83434
Fixes `finalize_block` and adds unit test
grarco Jan 12, 2023
ada51c6
Updates `process_proposal` unit tests
grarco Jan 12, 2023
15eaf8d
Fmt
grarco Jan 12, 2023
fbb2bf5
Clippy
grarco Jan 13, 2023
ef04e1c
[ci] wasm checksums update
github-actions[bot] Jan 13, 2023
ec6a570
changelog: add #1017
grarco Jan 13, 2023
8391577
Fixes typos
grarco Jan 19, 2023
8b21bfd
Replay protection VP always rejects
grarco Jan 19, 2023
f3c3cdb
Fixes tx unsigned hash
grarco Jan 20, 2023
9f7ff17
Removes unnecessary clones
grarco Jan 20, 2023
906c742
Removes wal from replay protection specs
grarco Jan 20, 2023
b824f4e
Refactors replay protection logic
grarco Jan 23, 2023
8222615
Fmt
grarco Jan 23, 2023
e1f17ee
Fixes fee in unit tests
grarco Jan 24, 2023
519be43
[ci] wasm checksums update
github-actions[bot] Jan 24, 2023
b19e386
Fixes fee error code
grarco Feb 6, 2023
1d8e1da
Brings back sig check in `finalize_block`
grarco Feb 8, 2023
42e056d
Updates fees in replay protection specs
grarco Feb 8, 2023
48bcb8c
[ci] wasm checksums update
github-actions[bot] Feb 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Adds hash-based replay protection
([#1017](https://github.com/anoma/namada/pull/1017))
3 changes: 3 additions & 0 deletions .changelog/unreleased/improvements/1051-temp-wl-storage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- Added a TempWlStorage for storage_api::StorageRead/Write
in ABCI++ prepare/process proposal handler.
([#1051](https://github.com/anoma/namada/pull/1051))
151 changes: 142 additions & 9 deletions apps/src/lib/node/ledger/shell/finalize_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

use namada::ledger::pos::namada_proof_of_stake;
use namada::ledger::pos::types::into_tm_voting_power;
use namada::ledger::protocol;
use namada::ledger::storage_api::StorageRead;
use namada::ledger::{protocol, replay_protection};
use namada::types::hash;
use namada::types::storage::{BlockHash, BlockResults, Header};
use namada::types::token::Amount;

Expand Down Expand Up @@ -145,17 +146,49 @@ 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
// from the queue of txs to be processed and remove the hash
// from storage
if let TxType::Decrypted(_) = &tx_type {
self.wl_storage.storage.tx_queue.pop();
let tx_hash = self
.wl_storage
.storage
.tx_queue
.pop()
.expect("Missing wrapper tx in queue")
.tx
.tx_hash;
let tx_hash_key =
replay_protection::get_tx_hash_key(&tx_hash);
self.wl_storage
.storage
.delete(&tx_hash_key)
.expect("Error while deleting tx hash from storage");
}
continue;
}

let mut tx_event = match &tx_type {
let (mut tx_event, tx_unsigned_hash) = match &tx_type {
TxType::Wrapper(wrapper) => {
let mut tx_event = Event::new_tx_event(&tx_type, height.0);

// Writes both txs hash to storage
let tx = Tx::try_from(processed_tx.tx.as_ref()).unwrap();
let wrapper_tx_hash_key =
replay_protection::get_tx_hash_key(&hash::Hash(
tx.unsigned_hash(),
));
self.wl_storage
.storage
.write(&wrapper_tx_hash_key, vec![])
.expect("Error while writing tx hash to storage");

let inner_tx_hash_key =
replay_protection::get_tx_hash_key(&wrapper.tx_hash);
self.wl_storage
.storage
.write(&inner_tx_hash_key, vec![])
.expect("Error while writing tx hash to storage");

#[cfg(not(feature = "mainnet"))]
let has_valid_pow =
self.invalidate_pow_solution_if_valid(wrapper);
Expand Down Expand Up @@ -216,11 +249,18 @@ where
#[cfg(not(feature = "mainnet"))]
has_valid_pow,
});
tx_event
(tx_event, None)
}
TxType::Decrypted(inner) => {
// We remove the corresponding wrapper tx from the queue
self.wl_storage.storage.tx_queue.pop();
let wrapper_hash = self
.wl_storage
.storage
.tx_queue
.pop()
.expect("Missing wrapper tx in queue")
.tx
.tx_hash;
let mut event = Event::new_tx_event(&tx_type, height.0);

match inner {
Expand All @@ -239,8 +279,7 @@ where
event["code"] = ErrorCodes::Undecryptable.into();
}
}

event
(event, Some(wrapper_hash))
}
TxType::Raw(_) => {
tracing::error!(
Expand Down Expand Up @@ -333,6 +372,25 @@ where
msg
);
stats.increment_errored_txs();

// If transaction type is Decrypted and failed because of
// out of gas, remove its hash from storage to allow
// rewrapping it
if let Some(hash) = tx_unsigned_hash {
if let Error::TxApply(protocol::Error::GasError(namada::ledger::gas::Error::TransactionGasExceededError)) =
msg
{
let tx_hash_key =
replay_protection::get_tx_hash_key(&hash);
self.wl_storage
.storage
.delete(&tx_hash_key)
.expect(
"Error while deleting tx hash key from storage",
);
}
}

self.wl_storage.drop_tx();
tx_event["gas_used"] = self
.gas_meter
Expand Down Expand Up @@ -691,7 +749,7 @@ mod test_finalize_block {
let mut processed_txs = vec![];
let mut valid_txs = vec![];

// Add unshielded balance for fee paymenty
// Add unshielded balance for fee payment
let balance_key = token::balance_key(
&shell.wl_storage.storage.native_token,
&Address::from(&keypair.ref_to()),
Expand Down Expand Up @@ -915,4 +973,79 @@ mod test_finalize_block {
last_storage_state = store_block_state(&shell);
}
}

/// Test that if a decrypted transaction fails because of out-of-gas, its
/// hash is removed from storage to allow rewrapping it
#[test]
fn test_remove_tx_hash() {
let (mut shell, _) = setup();
let keypair = gen_keypair();

let mut wasm_path = top_level_directory();
wasm_path.push("wasm_for_tests/tx_no_op.wasm");
let tx_code = std::fs::read(wasm_path)
.expect("Expected a file at given code path");
let raw_tx = Tx::new(
tx_code,
Some("Encrypted transaction data".as_bytes().to_owned()),
);
let wrapper_tx = WrapperTx::new(
Fee {
amount: 0.into(),
token: shell.wl_storage.storage.native_token.clone(),
},
&keypair,
Epoch(0),
0.into(),
raw_tx.clone(),
Default::default(),
#[cfg(not(feature = "mainnet"))]
None,
);

// Write inner hash in storage
let inner_hash_key =
replay_protection::get_tx_hash_key(&wrapper_tx.tx_hash);
shell
.wl_storage
.storage
.write(&inner_hash_key, vec![])
.expect("Test failed");

let processed_tx = ProcessedTx {
tx: Tx::from(TxType::Decrypted(DecryptedTx::Decrypted {
tx: raw_tx,
#[cfg(not(feature = "mainnet"))]
has_valid_pow: false,
}))
.to_bytes(),
result: TxResult {
code: ErrorCodes::Ok.into(),
info: "".into(),
},
};
shell.enqueue_tx(wrapper_tx);

let _event = &shell
.finalize_block(FinalizeBlock {
txs: vec![processed_tx],
..Default::default()
})
.expect("Test failed")[0];

// FIXME: uncomment when proper gas metering is in place
// // Check inner tx hash has been removed from storage
// assert_eq!(event.event_type.to_string(), String::from("applied"));
// let code = event.attributes.get("code").expect("Test
// failed").as_str(); assert_eq!(code,
// String::from(ErrorCodes::WasmRuntimeError).as_str());

// assert!(
// !shell
// .storage
// .has_key(&inner_hash_key)
// .expect("Test failed")
// .0
// )
}
}
Loading