Skip to content

Commit 55fb8e0

Browse files
rotcanshekhirin
authored andcommitted
feat(engine): Compare sorted bundle states in witness invalid block hook (paradigmxyz#15689)
Co-authored-by: Alexey Shekhirin <[email protected]>
1 parent ff0bd4c commit 55fb8e0

File tree

3 files changed

+109
-4
lines changed

3 files changed

+109
-4
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/engine/invalid-block-hooks/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ workspace = true
1212

1313
[dependencies]
1414
# reth
15+
revm-bytecode.workspace = true
1516
reth-chainspec.workspace = true
17+
revm-database.workspace = true
1618
reth-engine-primitives.workspace = true
1719
reth-evm.workspace = true
1820
reth-primitives-traits.workspace = true

crates/engine/invalid-block-hooks/src/witness.rs

Lines changed: 105 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,114 @@
11
use alloy_consensus::BlockHeader;
2-
use alloy_primitives::{keccak256, B256};
2+
use alloy_primitives::{keccak256, Address, B256, U256};
33
use alloy_rpc_types_debug::ExecutionWitness;
44
use pretty_assertions::Comparison;
55
use reth_chainspec::{EthChainSpec, EthereumHardforks};
66
use reth_engine_primitives::InvalidBlockHook;
77
use reth_evm::execute::{BlockExecutorProvider, Executor};
88
use reth_primitives_traits::{NodePrimitives, RecoveredBlock, SealedHeader};
99
use reth_provider::{BlockExecutionOutput, ChainSpecProvider, StateProviderFactory};
10-
use reth_revm::database::StateProviderDatabase;
10+
use reth_revm::{database::StateProviderDatabase, db::BundleState, state::AccountInfo};
1111
use reth_rpc_api::DebugApiClient;
1212
use reth_tracing::tracing::warn;
1313
use reth_trie::{updates::TrieUpdates, HashedStorage};
14+
use revm_bytecode::Bytecode;
15+
use revm_database::states::{
16+
reverts::{AccountInfoRevert, RevertToSlot},
17+
AccountStatus, StorageSlot,
18+
};
1419
use serde::Serialize;
15-
use std::{fmt::Debug, fs::File, io::Write, path::PathBuf};
20+
use std::{collections::BTreeMap, fmt::Debug, fs::File, io::Write, path::PathBuf};
21+
22+
#[derive(Debug, PartialEq, Eq)]
23+
struct AccountRevertSorted {
24+
pub account: AccountInfoRevert,
25+
pub storage: BTreeMap<U256, RevertToSlot>,
26+
pub previous_status: AccountStatus,
27+
pub wipe_storage: bool,
28+
}
29+
30+
#[derive(Debug, PartialEq, Eq)]
31+
struct BundleAccountSorted {
32+
pub info: Option<AccountInfo>,
33+
pub original_info: Option<AccountInfo>,
34+
/// Contains both original and present state.
35+
/// When extracting changeset we compare if original value is different from present value.
36+
/// If it is different we add it to changeset.
37+
/// If Account was destroyed we ignore original value and compare present state with
38+
/// `U256::ZERO`.
39+
pub storage: BTreeMap<U256, StorageSlot>,
40+
/// Account status.
41+
pub status: AccountStatus,
42+
}
43+
44+
#[derive(Debug, PartialEq, Eq)]
45+
struct BundleStateSorted {
46+
/// Account state
47+
pub state: BTreeMap<Address, BundleAccountSorted>,
48+
/// All created contracts in this block.
49+
pub contracts: BTreeMap<B256, Bytecode>,
50+
/// Changes to revert
51+
///
52+
/// **Note**: Inside vector is *not* sorted by address.
53+
///
54+
/// But it is unique by address.
55+
pub reverts: Vec<Vec<(Address, AccountRevertSorted)>>,
56+
/// The size of the plain state in the bundle state
57+
pub state_size: usize,
58+
/// The size of reverts in the bundle state
59+
pub reverts_size: usize,
60+
}
61+
62+
impl BundleStateSorted {
63+
fn from_bundle_state(bundle_state: &BundleState) -> Self {
64+
let state = bundle_state
65+
.state
66+
.clone()
67+
.into_iter()
68+
.map(|(address, account)| {
69+
{
70+
(
71+
address,
72+
BundleAccountSorted {
73+
info: account.info,
74+
original_info: account.original_info,
75+
status: account.status,
76+
storage: BTreeMap::from_iter(account.storage),
77+
},
78+
)
79+
}
80+
})
81+
.collect();
82+
83+
let contracts = BTreeMap::from_iter(bundle_state.contracts.clone());
84+
85+
let reverts = bundle_state
86+
.reverts
87+
.iter()
88+
.map(|block| {
89+
block
90+
.iter()
91+
.map(|(address, account_revert)| {
92+
(
93+
*address,
94+
AccountRevertSorted {
95+
account: account_revert.account.clone(),
96+
previous_status: account_revert.previous_status,
97+
wipe_storage: account_revert.wipe_storage,
98+
storage: BTreeMap::from_iter(account_revert.storage.clone()),
99+
},
100+
)
101+
})
102+
.collect()
103+
})
104+
.collect();
105+
106+
let state_size = bundle_state.state_size;
107+
let reverts_size = bundle_state.reverts_size;
108+
109+
Self { state, contracts, reverts, state_size, reverts_size }
110+
}
111+
}
16112

17113
/// Generates a witness for the given block and saves it to a file.
18114
#[derive(Debug)]
@@ -184,7 +280,12 @@ where
184280
)?;
185281

186282
let filename = format!("{}_{}.bundle_state.diff", block.number(), block.hash());
187-
let diff_path = self.save_diff(filename, &bundle_state, &output.state)?;
283+
// Convert bundle state to sorted struct which has BTreeMap instead of HashMap to
284+
// have deterministric ordering
285+
let bundle_state_sorted = BundleStateSorted::from_bundle_state(&bundle_state);
286+
let output_state_sorted = BundleStateSorted::from_bundle_state(&output.state);
287+
288+
let diff_path = self.save_diff(filename, &bundle_state_sorted, &output_state_sorted)?;
188289

189290
warn!(
190291
target: "engine::invalid_block_hooks::witness",

0 commit comments

Comments
 (0)