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 storage optimization #1977

Merged
merged 17 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@
- Reduced the storage consumption of replay protection.
([\#1977](https://github.com/anoma/namada/pull/1977))
315 changes: 174 additions & 141 deletions apps/src/lib/node/ledger/shell/finalize_block.rs

Large diffs are not rendered by default.

75 changes: 32 additions & 43 deletions apps/src/lib/node/ledger/shell/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@ use namada::ledger::protocol::{
apply_wasm_tx, get_fee_unshielding_transaction,
get_transfer_hash_from_storage, ShellParams,
};
use namada::ledger::storage::wl_storage::WriteLogAndStorage;
use namada::ledger::storage::write_log::WriteLog;
use namada::ledger::storage::{
DBIter, Sha256Hasher, Storage, StorageHasher, TempWlStorage, WlStorage, DB,
EPOCH_SWITCH_BLOCKS_DELAY,
};
use namada::ledger::storage_api::{self, StorageRead};
use namada::ledger::{parameters, pos, protocol, replay_protection};
use namada::ledger::{parameters, pos, protocol};
use namada::proof_of_stake::{self, process_slashes, read_pos_params, slash};
use namada::proto::{self, Section, Tx};
use namada::types::address::Address;
Expand All @@ -63,7 +64,7 @@ use namada::types::transaction::{
hash_tx, verify_decrypted_correctly, AffineCurve, DecryptedTx,
EllipticCurve, PairingEngine, TxType, WrapperTx,
};
use namada::types::{address, hash, token};
use namada::types::{address, token};
use namada::vm::wasm::{TxCache, VpCache};
use namada::vm::{WasmCacheAccess, WasmCacheRwAccess};
use num_derive::{FromPrimitive, ToPrimitive};
Expand Down Expand Up @@ -929,51 +930,40 @@ where
pub fn replay_protection_checks(
&self,
wrapper: &Tx,
tx_bytes: &[u8],
temp_wl_storage: &mut TempWlStorage<D, H>,
) -> Result<()> {
let inner_tx_hash =
wrapper.clone().update_header(TxType::Raw).header_hash();
let inner_hash_key =
replay_protection::get_replay_protection_key(&inner_tx_hash);
let wrapper_hash = wrapper.header_hash();
if temp_wl_storage
.has_key(&inner_hash_key)
.expect("Error while checking inner tx hash key in storage")
.has_replay_protection_entry(&wrapper_hash)
.expect("Error while checking wrapper tx hash key in storage")
{
return Err(Error::ReplayAttempt(format!(
"Inner transaction hash {} already in storage",
&inner_tx_hash,
"Wrapper transaction hash {} already in storage",
wrapper_hash
)));
}

// Write inner hash to tx WAL
// Write wrapper hash to tx WAL
temp_wl_storage
.write_log
.write(&inner_hash_key, vec![])
.expect("Couldn't write inner transaction hash to write log");
.write_tx_hash(wrapper_hash)
.map_err(|e| Error::ReplayAttempt(e.to_string()))?;

let tx =
Tx::try_from(tx_bytes).expect("Deserialization shouldn't fail");
let wrapper_hash = tx.header_hash();
let wrapper_hash_key =
replay_protection::get_replay_protection_key(&wrapper_hash);
let inner_tx_hash =
wrapper.clone().update_header(TxType::Raw).header_hash();
if temp_wl_storage
.has_key(&wrapper_hash_key)
.expect("Error while checking wrapper tx hash key in storage")
.has_replay_protection_entry(&inner_tx_hash)
.expect("Error while checking inner tx hash key in storage")
{
return Err(Error::ReplayAttempt(format!(
"Wrapper transaction hash {} already in storage",
wrapper_hash
"Inner transaction hash {} already in storage",
&inner_tx_hash,
)));
}

// Write wrapper hash to tx WAL
// Write inner hash to tx WAL
temp_wl_storage
.write_log
.write(&wrapper_hash_key, vec![])
.expect("Couldn't write wrapper tx hash to write log");

Ok(())
.write_tx_hash(inner_tx_hash)
.map_err(|e| Error::ReplayAttempt(e.to_string()))
}

/// If a handle to an Ethereum oracle was provided to the [`Shell`], attempt
Expand Down Expand Up @@ -1266,14 +1256,11 @@ where
let mut inner_tx = tx;
inner_tx.update_header(TxType::Raw);
let inner_tx_hash = &inner_tx.header_hash();
let inner_hash_key =
replay_protection::get_replay_protection_key(inner_tx_hash);
if self
.wl_storage
.storage
.has_key(&inner_hash_key)
.has_replay_protection_entry(inner_tx_hash)
.expect("Error while checking inner tx hash key in storage")
.0
{
response.code = ErrorCodes::ReplayTx.into();
response.log = format!(
Expand All @@ -1286,17 +1273,14 @@ where

let tx = Tx::try_from(tx_bytes)
.expect("Deserialization shouldn't fail");
let wrapper_hash = hash::Hash(tx.header_hash().0);
let wrapper_hash_key =
replay_protection::get_replay_protection_key(&wrapper_hash);
let wrapper_hash = &tx.header_hash();
if self
.wl_storage
.storage
.has_key(&wrapper_hash_key)
.has_replay_protection_entry(wrapper_hash)
.expect(
"Error while checking wrapper tx hash key in storage",
)
.0
{
response.code = ErrorCodes::ReplayTx.into();
response.log = format!(
Expand Down Expand Up @@ -2323,6 +2307,7 @@ mod abciplus_mempool_tests {

#[cfg(test)]
mod tests {
use namada::ledger::replay_protection;
use namada::proof_of_stake::Epoch;
use namada::proto::{Code, Data, Section, Signature, Tx};
use namada::types::transaction::{Fee, WrapperTx};
Expand Down Expand Up @@ -2464,13 +2449,15 @@ mod tests {
)));

// Write wrapper hash to storage
let mut batch =
namada::core::ledger::storage::testing::TestStorage::batch();
let wrapper_hash = wrapper.header_hash();
let wrapper_hash_key =
replay_protection::get_replay_protection_key(&wrapper_hash);
replay_protection::get_replay_protection_last_subkey(&wrapper_hash);
shell
.wl_storage
.storage
.write(&wrapper_hash_key, wrapper_hash)
.write_replay_protection_entry(&mut batch, &wrapper_hash_key)
.expect("Test failed");

// Try wrapper tx replay attack
Expand Down Expand Up @@ -2506,11 +2493,13 @@ mod tests {
wrapper.clone().update_header(TxType::Raw).header_hash();
// Write inner hash in storage
let inner_hash_key =
replay_protection::get_replay_protection_key(&inner_tx_hash);
replay_protection::get_replay_protection_last_subkey(
&inner_tx_hash,
);
shell
.wl_storage
.storage
.write(&inner_hash_key, inner_tx_hash)
.write_replay_protection_entry(&mut batch, &inner_hash_key)
.expect("Test failed");

// Try inner tx replay attack
Expand Down
16 changes: 10 additions & 6 deletions apps/src/lib/node/ledger/shell/prepare_proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,11 @@ 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
self.replay_protection_checks(&tx, tx_bytes, temp_wl_storage)
// 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
self.replay_protection_checks(&tx, temp_wl_storage)
.map_err(|_| ())?;

// Check fees
Expand Down Expand Up @@ -1188,7 +1191,7 @@ mod test_prepare_proposal {

// Write wrapper hash to storage
let wrapper_unsigned_hash = wrapper.header_hash();
let hash_key = replay_protection::get_replay_protection_key(
let hash_key = replay_protection::get_replay_protection_last_key(
&wrapper_unsigned_hash,
);
shell
Expand Down Expand Up @@ -1283,8 +1286,9 @@ mod test_prepare_proposal {
wrapper.clone().update_header(TxType::Raw).header_hash();

// Write inner hash to storage
let hash_key =
replay_protection::get_replay_protection_key(&inner_unsigned_hash);
let hash_key = replay_protection::get_replay_protection_last_key(
&inner_unsigned_hash,
);
shell
.wl_storage
.storage
Expand Down Expand Up @@ -1313,7 +1317,7 @@ mod test_prepare_proposal {
let (shell, _recv, _, _) = test_utils::setup();

let keypair = crate::wallet::defaults::daewon_keypair();
let keypair_2 = crate::wallet::defaults::daewon_keypair();
let keypair_2 = crate::wallet::defaults::albert_keypair();
let mut wrapper =
Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new(
Fee {
Expand Down
33 changes: 17 additions & 16 deletions apps/src/lib/node/ledger/shell/process_proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -878,11 +878,9 @@ where
}
} else {
// Replay protection checks
if let Err(e) = self.replay_protection_checks(
&tx,
tx_bytes,
temp_wl_storage,
) {
if let Err(e) =
self.replay_protection_checks(&tx, temp_wl_storage)
{
return TxResult {
code: ErrorCodes::ReplayTx.into(),
info: e.to_string(),
Expand Down Expand Up @@ -988,6 +986,7 @@ mod test_process_proposal {

#[cfg(feature = "abcipp")]
use assert_matches::assert_matches;
use namada::ledger::replay_protection;
use namada::ledger::storage_api::StorageWrite;
use namada::proto::{
Code, Data, Section, SignableEthMessage, Signature, Signed,
Expand Down Expand Up @@ -2131,14 +2130,16 @@ mod test_process_proposal {
)));

// Write wrapper hash to storage
let mut batch =
namada::core::ledger::storage::testing::TestStorage::batch();
let wrapper_unsigned_hash = wrapper.header_hash();
let hash_key = replay_protection::get_replay_protection_key(
let hash_key = replay_protection::get_replay_protection_last_subkey(
&wrapper_unsigned_hash,
);
shell
.wl_storage
.storage
.write(&hash_key, vec![])
.write_replay_protection_entry(&mut batch, &hash_key)
.expect("Test failed");

// Run validation
Expand Down Expand Up @@ -2224,12 +2225,9 @@ mod test_process_proposal {
assert_eq!(
response[1].result.info,
format!(
"Transaction replay attempt: Inner transaction hash \
"Transaction replay attempt: Wrapper transaction hash \
{} already in storage",
wrapper
.clone()
.update_header(TxType::Raw)
.header_hash(),
wrapper.header_hash(),
)
);
}
Expand Down Expand Up @@ -2267,12 +2265,15 @@ mod test_process_proposal {
wrapper.clone().update_header(TxType::Raw).header_hash();

// Write inner hash to storage
let hash_key =
replay_protection::get_replay_protection_key(&inner_unsigned_hash);
let mut batch =
namada::core::ledger::storage::testing::TestStorage::batch();
let hash_key = replay_protection::get_replay_protection_last_subkey(
&inner_unsigned_hash,
);
shell
.wl_storage
.storage
.write(&hash_key, vec![])
.write_replay_protection_entry(&mut batch, &hash_key)
.expect("Test failed");

// Run validation
Expand Down Expand Up @@ -2305,7 +2306,7 @@ mod test_process_proposal {
let (shell, _recv, _, _) = test_utils::setup();

let keypair = crate::wallet::defaults::daewon_keypair();
let keypair_2 = crate::wallet::defaults::daewon_keypair();
let keypair_2 = crate::wallet::defaults::albert_keypair();

let mut wrapper =
Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new(
Expand Down
Loading
Loading