From 8a557f44ae9a3998a295632d94c17b6d8678915f Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 4 Sep 2024 11:48:25 +0100 Subject: [PATCH 01/20] feat(engine, tree): witness invalid block hook --- Cargo.lock | 10 +++ crates/engine/invalid-block-hooks/Cargo.toml | 9 +++ crates/engine/invalid-block-hooks/src/lib.rs | 2 +- .../engine/invalid-block-hooks/src/witness.rs | 67 ++++++++++++++++--- crates/engine/primitives/Cargo.toml | 6 +- .../primitives/src/invalid_block_hook.rs | 37 ++++++++++ crates/engine/primitives/src/lib.rs | 3 + crates/engine/tree/Cargo.toml | 1 + .../tree/src/tree/invalid_block_hook.rs | 43 ++---------- crates/engine/tree/src/tree/mod.rs | 15 +++-- crates/node/builder/src/launch/common.rs | 8 ++- crates/node/core/src/dirs.rs | 7 ++ 12 files changed, 154 insertions(+), 54 deletions(-) create mode 100644 crates/engine/primitives/src/invalid_block_hook.rs diff --git a/Cargo.lock b/Cargo.lock index 3e3e9e3f97d0..b831b404c710 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6818,8 +6818,12 @@ dependencies = [ name = "reth-engine-primitives" version = "1.0.6" dependencies = [ + "eyre", "reth-chainspec", + "reth-execution-types", "reth-payload-primitives", + "reth-primitives", + "reth-trie", "serde", ] @@ -6860,6 +6864,7 @@ version = "1.0.6" dependencies = [ "alloy-rlp", "assert_matches", + "eyre", "futures", "metrics", "rand 0.8.5", @@ -7256,9 +7261,14 @@ dependencies = [ name = "reth-invalid-block-hooks" version = "1.0.6" dependencies = [ + "alloy-rlp", + "alloy-rpc-types-debug", + "eyre", + "reth-engine-primitives", "reth-primitives", "reth-provider", "reth-trie", + "serde_json", ] [[package]] diff --git a/crates/engine/invalid-block-hooks/Cargo.toml b/crates/engine/invalid-block-hooks/Cargo.toml index 404ff96ce68d..ecd1797a8901 100644 --- a/crates/engine/invalid-block-hooks/Cargo.toml +++ b/crates/engine/invalid-block-hooks/Cargo.toml @@ -12,6 +12,15 @@ workspace = true [dependencies] # reth +reth-engine-primitives.workspace = true reth-primitives.workspace = true reth-provider.workspace = true reth-trie.workspace = true + +# alloy +alloy-rlp.workspace = true +alloy-rpc-types-debug.workspace = true + +# misc +eyre.workspace = true +serde_json.workspace = true diff --git a/crates/engine/invalid-block-hooks/src/lib.rs b/crates/engine/invalid-block-hooks/src/lib.rs index 4a27e1fd1e42..271b43eb057d 100644 --- a/crates/engine/invalid-block-hooks/src/lib.rs +++ b/crates/engine/invalid-block-hooks/src/lib.rs @@ -2,4 +2,4 @@ mod witness; -pub use witness::witness; +pub use witness::Witness; diff --git a/crates/engine/invalid-block-hooks/src/witness.rs b/crates/engine/invalid-block-hooks/src/witness.rs index 75261ce5e18f..4b355a2db3e8 100644 --- a/crates/engine/invalid-block-hooks/src/witness.rs +++ b/crates/engine/invalid-block-hooks/src/witness.rs @@ -1,13 +1,60 @@ -use reth_primitives::{Receipt, SealedBlockWithSenders, SealedHeader, B256}; -use reth_provider::BlockExecutionOutput; -use reth_trie::updates::TrieUpdates; +use std::{collections::HashMap, path::PathBuf}; + +use alloy_rpc_types_debug::ExecutionWitness; +use reth_engine_primitives::InvalidBlockHook; +use reth_primitives::{keccak256, Receipt, SealedBlockWithSenders, SealedHeader, B256}; +use reth_provider::{BlockExecutionOutput, StateProviderFactory}; +use reth_trie::{updates::TrieUpdates, HashedPostState}; /// Generates a witness for the given block and saves it to a file. -pub fn witness( - _block: &SealedBlockWithSenders, - _header: &SealedHeader, - _output: &BlockExecutionOutput, - _trie_updates: Option<(&TrieUpdates, B256)>, -) { - unimplemented!("witness generation is not supported") +#[derive(Debug)] +pub struct Witness

{ + output_directory: PathBuf, + provider: P, +} + +impl

Witness

{ + /// Creates a new witness hook. + pub const fn new(output_directory: PathBuf, provider: P) -> Self { + Self { output_directory, provider } + } +} + +impl

InvalidBlockHook for Witness

+where + P: StateProviderFactory + Clone + Send + Sync + 'static, +{ + fn on_invalid_block( + &self, + _block: &SealedBlockWithSenders, + header: &SealedHeader, + output: &BlockExecutionOutput, + _trie_updates: Option<(&TrieUpdates, B256)>, + ) -> eyre::Result<()> { + // TODO(alexey): add accessed accounts and storage slots to the hashed state and state + // preimages + + let mut state_preimages = HashMap::new(); + + for (address, account) in &output.state.state { + let hashed_address = keccak256(address); + state_preimages.insert(hashed_address, alloy_rlp::encode(address).into()); + + for slot in account.storage.keys() { + let slot = B256::from(slot.to_be_bytes()); + let hashed_slot = keccak256(slot); + state_preimages.insert(hashed_slot, alloy_rlp::encode(slot).into()); + } + } + + let hashed_state = HashedPostState::from_bundle_state(&output.state.state); + let witness = self.provider.latest()?.witness(HashedPostState::default(), hashed_state)?; + + let path = self.output_directory.join(format!("{}_{}.json", header.number, header.hash())); + let response = ExecutionWitness { witness, state_preimages: Some(state_preimages) }; + + std::fs::write(path, serde_json::to_string(&response)?)?; + + Ok(()) + } } diff --git a/crates/engine/primitives/Cargo.toml b/crates/engine/primitives/Cargo.toml index b44a4a8aa4e7..76765be919a2 100644 --- a/crates/engine/primitives/Cargo.toml +++ b/crates/engine/primitives/Cargo.toml @@ -13,7 +13,11 @@ workspace = true [dependencies] # reth reth-chainspec.workspace = true +reth-execution-types.workspace = true reth-payload-primitives.workspace = true +reth-primitives.workspace = true +reth-trie.workspace = true # misc -serde.workspace = true \ No newline at end of file +eyre.workspace = true +serde.workspace = true diff --git a/crates/engine/primitives/src/invalid_block_hook.rs b/crates/engine/primitives/src/invalid_block_hook.rs new file mode 100644 index 000000000000..27059194de69 --- /dev/null +++ b/crates/engine/primitives/src/invalid_block_hook.rs @@ -0,0 +1,37 @@ +use reth_execution_types::BlockExecutionOutput; +use reth_primitives::{Receipt, SealedBlockWithSenders, SealedHeader, B256}; +use reth_trie::updates::TrieUpdates; + +/// A bad block hook. +pub trait InvalidBlockHook: Send + Sync { + /// Invoked when a bad block is encountered. + fn on_invalid_block( + &self, + block: &SealedBlockWithSenders, + header: &SealedHeader, + output: &BlockExecutionOutput, + trie_updates: Option<(&TrieUpdates, B256)>, + ) -> eyre::Result<()>; +} + +impl InvalidBlockHook for F +where + F: Fn( + &SealedBlockWithSenders, + &SealedHeader, + &BlockExecutionOutput, + Option<(&TrieUpdates, B256)>, + ) -> eyre::Result<()> + + Send + + Sync, +{ + fn on_invalid_block( + &self, + block: &SealedBlockWithSenders, + header: &SealedHeader, + output: &BlockExecutionOutput, + trie_updates: Option<(&TrieUpdates, B256)>, + ) -> eyre::Result<()> { + self(block, header, output, trie_updates) + } +} diff --git a/crates/engine/primitives/src/lib.rs b/crates/engine/primitives/src/lib.rs index 4b0db7c0a14c..284ed9f0fb02 100644 --- a/crates/engine/primitives/src/lib.rs +++ b/crates/engine/primitives/src/lib.rs @@ -8,6 +8,9 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +mod invalid_block_hook; +pub use invalid_block_hook::InvalidBlockHook; + use reth_chainspec::ChainSpec; pub use reth_payload_primitives::{ BuiltPayload, EngineApiMessageVersion, EngineObjectValidationError, PayloadOrAttributes, diff --git a/crates/engine/tree/Cargo.toml b/crates/engine/tree/Cargo.toml index c978acc107de..fbc398f52124 100644 --- a/crates/engine/tree/Cargo.toml +++ b/crates/engine/tree/Cargo.toml @@ -45,6 +45,7 @@ metrics.workspace = true reth-metrics = { workspace = true, features = ["common"] } # misc +eyre.workspace = true tracing.workspace = true # optional deps for test-utils diff --git a/crates/engine/tree/src/tree/invalid_block_hook.rs b/crates/engine/tree/src/tree/invalid_block_hook.rs index 9c32b781b12a..c0f3462ff5bb 100644 --- a/crates/engine/tree/src/tree/invalid_block_hook.rs +++ b/crates/engine/tree/src/tree/invalid_block_hook.rs @@ -1,40 +1,8 @@ +use reth_engine_primitives::InvalidBlockHook; use reth_primitives::{Receipt, SealedBlockWithSenders, SealedHeader, B256}; use reth_provider::BlockExecutionOutput; use reth_trie::updates::TrieUpdates; -/// A bad block hook. -pub trait InvalidBlockHook: Send + Sync { - /// Invoked when a bad block is encountered. - fn on_invalid_block( - &self, - block: &SealedBlockWithSenders, - header: &SealedHeader, - output: &BlockExecutionOutput, - trie_updates: Option<(&TrieUpdates, B256)>, - ); -} - -impl InvalidBlockHook for F -where - F: Fn( - &SealedBlockWithSenders, - &SealedHeader, - &BlockExecutionOutput, - Option<(&TrieUpdates, B256)>, - ) + Send - + Sync, -{ - fn on_invalid_block( - &self, - block: &SealedBlockWithSenders, - header: &SealedHeader, - output: &BlockExecutionOutput, - trie_updates: Option<(&TrieUpdates, B256)>, - ) { - self(block, header, output, trie_updates) - } -} - /// A no-op [`InvalidBlockHook`] that does nothing. #[derive(Debug, Default)] #[non_exhaustive] @@ -47,7 +15,8 @@ impl InvalidBlockHook for NoopInvalidBlockHook { _header: &SealedHeader, _output: &BlockExecutionOutput, _trie_updates: Option<(&TrieUpdates, B256)>, - ) { + ) -> eyre::Result<()> { + Ok(()) } } @@ -67,9 +36,11 @@ impl InvalidBlockHook for InvalidBlockHooks { header: &SealedHeader, output: &BlockExecutionOutput, trie_updates: Option<(&TrieUpdates, B256)>, - ) { + ) -> eyre::Result<()> { for hook in &self.0 { - hook.on_invalid_block(block, header, output, trie_updates); + hook.on_invalid_block(block, header, output, trie_updates)?; } + + Ok(()) } } diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 21ba30cbc5a4..4183b1391d63 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -62,7 +62,8 @@ mod invalid_block_hook; mod metrics; use crate::{engine::EngineApiRequest, tree::metrics::EngineApiMetrics}; pub use config::TreeConfig; -pub use invalid_block_hook::{InvalidBlockHook, InvalidBlockHooks, NoopInvalidBlockHook}; +pub use invalid_block_hook::{InvalidBlockHooks, NoopInvalidBlockHook}; +pub use reth_engine_primitives::InvalidBlockHook; /// Keeps track of the state of the tree. /// @@ -1956,12 +1957,14 @@ where PostExecutionInput::new(&output.receipts, &output.requests), ) { // call post-block hook - self.invalid_block_hook.on_invalid_block( + if let Err(hook_err) = self.invalid_block_hook.on_invalid_block( &block.seal_slow(), &parent_block, &output, None, - ); + ) { + error!(target: "engine::tree", %err, %hook_err, "Failed to invoke invalid block hook after execution"); + } return Err(err.into()) } @@ -1972,12 +1975,14 @@ where state_provider.state_root_with_updates(hashed_state.clone())?; if state_root != block.state_root { // call post-block hook - self.invalid_block_hook.on_invalid_block( + if let Err(hook_err) = self.invalid_block_hook.on_invalid_block( &block.clone().seal_slow(), &parent_block, &output, Some((&trie_output, state_root)), - ); + ) { + error!(target: "engine::tree", %hook_err, "Failed to invoke invalid block hook on state root mismatch"); + } return Err(ConsensusError::BodyStateRootDiff( GotExpected { got: state_root, expected: block.state_root }.into(), ) diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index d0ab6929feaa..eca5861f8839 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -844,13 +844,19 @@ where /// Returns the [`InvalidBlockHook`] to use for the node. pub fn invalid_block_hook(&self) -> eyre::Result> { Ok(if let Some(ref hook) = self.node_config().debug.invalid_block_hook { + let output_directory = self.data_dir().invalid_block_hooks(); let hooks = hook .iter() .copied() .map(|hook| { + let output_directory = output_directory.join(hook.to_string()); + Ok(match hook { reth_node_core::args::InvalidBlockHook::Witness => { - Box::new(reth_invalid_block_hooks::witness) as Box + Box::new(reth_invalid_block_hooks::Witness::new( + output_directory, + self.blockchain_db().clone(), + )) as Box } reth_node_core::args::InvalidBlockHook::PreState | reth_node_core::args::InvalidBlockHook::Opcode => { diff --git a/crates/node/core/src/dirs.rs b/crates/node/core/src/dirs.rs index 5922b6818cd9..c788f35da1d6 100644 --- a/crates/node/core/src/dirs.rs +++ b/crates/node/core/src/dirs.rs @@ -343,6 +343,13 @@ impl ChainPath { pub fn jwt(&self) -> PathBuf { self.data_dir().join("jwt.hex") } + + /// Returns the path to the invalid block hooks directory for this chain. + /// + /// `

//invalid_block_hooks` + pub fn invalid_block_hooks(&self) -> PathBuf { + self.data_dir().join("invalid_block_hooks") + } } impl AsRef for ChainPath { From 9018c2185e4bcf377d99b961a91efa853d7d7e9d Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 4 Sep 2024 14:02:50 +0100 Subject: [PATCH 02/20] ok proper witness --- Cargo.lock | 3 + crates/engine/invalid-block-hooks/Cargo.toml | 3 + .../engine/invalid-block-hooks/src/witness.rs | 137 +++++++++++++++--- .../primitives/src/invalid_block_hook.rs | 8 +- .../tree/src/tree/invalid_block_hook.rs | 6 +- crates/engine/tree/src/tree/mod.rs | 4 +- crates/node/builder/src/launch/common.rs | 1 + 7 files changed, 129 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b831b404c710..2ced98767b9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7264,9 +7264,12 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types-debug", "eyre", + "reth-chainspec", "reth-engine-primitives", + "reth-evm", "reth-primitives", "reth-provider", + "reth-revm", "reth-trie", "serde_json", ] diff --git a/crates/engine/invalid-block-hooks/Cargo.toml b/crates/engine/invalid-block-hooks/Cargo.toml index ecd1797a8901..9e5a97ce3904 100644 --- a/crates/engine/invalid-block-hooks/Cargo.toml +++ b/crates/engine/invalid-block-hooks/Cargo.toml @@ -12,9 +12,12 @@ workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-engine-primitives.workspace = true +reth-evm.workspace = true reth-primitives.workspace = true reth-provider.workspace = true +reth-revm.workspace = true reth-trie.workspace = true # alloy diff --git a/crates/engine/invalid-block-hooks/src/witness.rs b/crates/engine/invalid-block-hooks/src/witness.rs index 4b355a2db3e8..858565b61a14 100644 --- a/crates/engine/invalid-block-hooks/src/witness.rs +++ b/crates/engine/invalid-block-hooks/src/witness.rs @@ -1,58 +1,147 @@ use std::{collections::HashMap, path::PathBuf}; use alloy_rpc_types_debug::ExecutionWitness; +use eyre::OptionExt; +use reth_chainspec::ChainSpec; use reth_engine_primitives::InvalidBlockHook; -use reth_primitives::{keccak256, Receipt, SealedBlockWithSenders, SealedHeader, B256}; -use reth_provider::{BlockExecutionOutput, StateProviderFactory}; -use reth_trie::{updates::TrieUpdates, HashedPostState}; +use reth_evm::{ + system_calls::{pre_block_beacon_root_contract_call, pre_block_blockhashes_contract_call}, + ConfigureEvm, +}; +use reth_primitives::{keccak256, Receipt, SealedBlockWithSenders, SealedHeader, B256, U256}; +use reth_provider::{BlockExecutionOutput, ChainSpecProvider, StateProviderFactory}; +use reth_revm::{ + database::StateProviderDatabase, + db::states::bundle_state::BundleRetention, + primitives::{BlockEnv, CfgEnvWithHandlerCfg, Env, EnvWithHandlerCfg}, + DatabaseCommit, StateBuilder, +}; +use reth_trie::{updates::TrieUpdates, HashedPostState, HashedStorage}; /// Generates a witness for the given block and saves it to a file. #[derive(Debug)] -pub struct Witness

{ +pub struct Witness { output_directory: PathBuf, provider: P, + evm_config: EvmConfig, } -impl

Witness

{ +impl Witness { /// Creates a new witness hook. - pub const fn new(output_directory: PathBuf, provider: P) -> Self { - Self { output_directory, provider } + pub const fn new(output_directory: PathBuf, provider: P, evm_config: EvmConfig) -> Self { + Self { output_directory, provider, evm_config } } } -impl

InvalidBlockHook for Witness

+impl InvalidBlockHook for Witness where - P: StateProviderFactory + Clone + Send + Sync + 'static, + P: StateProviderFactory + ChainSpecProvider + Send + Sync + 'static, + EvmConfig: ConfigureEvm, { fn on_invalid_block( &self, - _block: &SealedBlockWithSenders, - header: &SealedHeader, - output: &BlockExecutionOutput, + _parent_header: &SealedHeader, + block: &SealedBlockWithSenders, + _output: &BlockExecutionOutput, _trie_updates: Option<(&TrieUpdates, B256)>, ) -> eyre::Result<()> { - // TODO(alexey): add accessed accounts and storage slots to the hashed state and state - // preimages + // Setup database. + let mut db = StateBuilder::new() + .with_database(StateProviderDatabase::new( + self.provider.state_by_block_hash(block.hash())?, + )) + .with_bundle_update() + .build(); + // Setup environment for the execution. + let mut cfg = CfgEnvWithHandlerCfg::new(Default::default(), Default::default()); + let mut block_env = BlockEnv::default(); + let evm_config = self.evm_config.clone(); + evm_config.fill_cfg_and_block_env( + &mut cfg, + &mut block_env, + &self.provider.chain_spec(), + block.header(), + U256::MAX, + ); + + // Apply pre-block system contract calls. + pre_block_beacon_root_contract_call( + &mut db, + &evm_config, + &self.provider.chain_spec(), + &cfg, + &block_env, + block.parent_beacon_block_root, + )?; + pre_block_blockhashes_contract_call( + &mut db, + &evm_config, + &self.provider.chain_spec(), + &cfg, + &block_env, + block.parent_hash, + )?; + + // Re-execute all of the transactions in the block to load all touched accounts into + // the cache DB. + for tx in block.transactions() { + let tx = tx.clone().into_ecrecovered().ok_or_eyre("failed to recover sender")?; + let env = EnvWithHandlerCfg { + env: Env::boxed(cfg.cfg_env.clone(), block_env.clone(), evm_config.tx_env(&tx)), + handler_cfg: cfg.handler_cfg, + }; + + let result = self.evm_config.evm_with_env(&mut db, env).transact()?; + db.commit(result.state); + } + + // Merge all state transitions + db.merge_transitions(BundleRetention::Reverts); + + // Take the bundle state + let bundle_state = db.take_bundle(); + + // Initialize a map of preimages. let mut state_preimages = HashMap::new(); - for (address, account) in &output.state.state { + // Grab all account proofs for the data accessed during block execution. + // + // Note: We grab *all* accounts in the cache here, as the `BundleState` prunes + // referenced accounts + storage slots. + let mut hashed_state = HashedPostState::from_bundle_state(&bundle_state.state); + for (address, account) in db.cache.accounts { let hashed_address = keccak256(address); - state_preimages.insert(hashed_address, alloy_rlp::encode(address).into()); + hashed_state + .accounts + .insert(hashed_address, account.account.as_ref().map(|a| a.info.clone().into())); + + let storage = hashed_state + .storages + .entry(hashed_address) + .or_insert_with(|| HashedStorage::new(account.status.was_destroyed())); + + if let Some(account) = account.account { + state_preimages.insert(hashed_address, alloy_rlp::encode(address).into()); - for slot in account.storage.keys() { - let slot = B256::from(slot.to_be_bytes()); - let hashed_slot = keccak256(slot); - state_preimages.insert(hashed_slot, alloy_rlp::encode(slot).into()); + for (slot, value) in account.storage { + let slot = B256::from(slot); + let hashed_slot = keccak256(slot); + storage.storage.insert(hashed_slot, value); + + state_preimages.insert(hashed_slot, alloy_rlp::encode(slot).into()); + } } } - let hashed_state = HashedPostState::from_bundle_state(&output.state.state); - let witness = self.provider.latest()?.witness(HashedPostState::default(), hashed_state)?; + // Generate an execution witness for the aggregated state of accessed accounts. + // Destruct the cache database to retrieve the state provider. + let state_provider = db.database.into_inner(); + let witness = state_provider.witness(HashedPostState::default(), hashed_state)?; - let path = self.output_directory.join(format!("{}_{}.json", header.number, header.hash())); + // Write the witness to the output directory. + let path = self.output_directory.join(format!("{}_{}.json", block.number, block.hash())); let response = ExecutionWitness { witness, state_preimages: Some(state_preimages) }; - std::fs::write(path, serde_json::to_string(&response)?)?; Ok(()) diff --git a/crates/engine/primitives/src/invalid_block_hook.rs b/crates/engine/primitives/src/invalid_block_hook.rs index 27059194de69..120114c2a773 100644 --- a/crates/engine/primitives/src/invalid_block_hook.rs +++ b/crates/engine/primitives/src/invalid_block_hook.rs @@ -7,8 +7,8 @@ pub trait InvalidBlockHook: Send + Sync { /// Invoked when a bad block is encountered. fn on_invalid_block( &self, + parent_header: &SealedHeader, block: &SealedBlockWithSenders, - header: &SealedHeader, output: &BlockExecutionOutput, trie_updates: Option<(&TrieUpdates, B256)>, ) -> eyre::Result<()>; @@ -17,8 +17,8 @@ pub trait InvalidBlockHook: Send + Sync { impl InvalidBlockHook for F where F: Fn( - &SealedBlockWithSenders, &SealedHeader, + &SealedBlockWithSenders, &BlockExecutionOutput, Option<(&TrieUpdates, B256)>, ) -> eyre::Result<()> @@ -27,11 +27,11 @@ where { fn on_invalid_block( &self, + parent_header: &SealedHeader, block: &SealedBlockWithSenders, - header: &SealedHeader, output: &BlockExecutionOutput, trie_updates: Option<(&TrieUpdates, B256)>, ) -> eyre::Result<()> { - self(block, header, output, trie_updates) + self(parent_header, block, output, trie_updates) } } diff --git a/crates/engine/tree/src/tree/invalid_block_hook.rs b/crates/engine/tree/src/tree/invalid_block_hook.rs index c0f3462ff5bb..d4366019a5a2 100644 --- a/crates/engine/tree/src/tree/invalid_block_hook.rs +++ b/crates/engine/tree/src/tree/invalid_block_hook.rs @@ -11,8 +11,8 @@ pub struct NoopInvalidBlockHook; impl InvalidBlockHook for NoopInvalidBlockHook { fn on_invalid_block( &self, + _parent_header: &SealedHeader, _block: &SealedBlockWithSenders, - _header: &SealedHeader, _output: &BlockExecutionOutput, _trie_updates: Option<(&TrieUpdates, B256)>, ) -> eyre::Result<()> { @@ -32,13 +32,13 @@ impl std::fmt::Debug for InvalidBlockHooks { impl InvalidBlockHook for InvalidBlockHooks { fn on_invalid_block( &self, + parent_header: &SealedHeader, block: &SealedBlockWithSenders, - header: &SealedHeader, output: &BlockExecutionOutput, trie_updates: Option<(&TrieUpdates, B256)>, ) -> eyre::Result<()> { for hook in &self.0 { - hook.on_invalid_block(block, header, output, trie_updates)?; + hook.on_invalid_block(parent_header, block, output, trie_updates)?; } Ok(()) diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 4183b1391d63..0067ddcaa158 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -1958,8 +1958,8 @@ where ) { // call post-block hook if let Err(hook_err) = self.invalid_block_hook.on_invalid_block( - &block.seal_slow(), &parent_block, + &block.seal_slow(), &output, None, ) { @@ -1976,8 +1976,8 @@ where if state_root != block.state_root { // call post-block hook if let Err(hook_err) = self.invalid_block_hook.on_invalid_block( - &block.clone().seal_slow(), &parent_block, + &block.clone().seal_slow(), &output, Some((&trie_output, state_root)), ) { diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index eca5861f8839..1526c21b6f2c 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -856,6 +856,7 @@ where Box::new(reth_invalid_block_hooks::Witness::new( output_directory, self.blockchain_db().clone(), + self.components().evm_config().clone(), )) as Box } reth_node_core::args::InvalidBlockHook::PreState | From fc92bb652eb954fd736b9e4884fc9f8384a38d6c Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 4 Sep 2024 14:12:09 +0100 Subject: [PATCH 03/20] query parent header state --- crates/engine/invalid-block-hooks/src/witness.rs | 8 +++----- crates/storage/storage-api/src/state.rs | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/engine/invalid-block-hooks/src/witness.rs b/crates/engine/invalid-block-hooks/src/witness.rs index 858565b61a14..f3e02a736291 100644 --- a/crates/engine/invalid-block-hooks/src/witness.rs +++ b/crates/engine/invalid-block-hooks/src/witness.rs @@ -1,7 +1,6 @@ use std::{collections::HashMap, path::PathBuf}; use alloy_rpc_types_debug::ExecutionWitness; -use eyre::OptionExt; use reth_chainspec::ChainSpec; use reth_engine_primitives::InvalidBlockHook; use reth_evm::{ @@ -40,7 +39,7 @@ where { fn on_invalid_block( &self, - _parent_header: &SealedHeader, + parent_header: &SealedHeader, block: &SealedBlockWithSenders, _output: &BlockExecutionOutput, _trie_updates: Option<(&TrieUpdates, B256)>, @@ -48,7 +47,7 @@ where // Setup database. let mut db = StateBuilder::new() .with_database(StateProviderDatabase::new( - self.provider.state_by_block_hash(block.hash())?, + self.provider.state_by_block_hash(parent_header.hash())?, )) .with_bundle_update() .build(); @@ -85,8 +84,7 @@ where // Re-execute all of the transactions in the block to load all touched accounts into // the cache DB. - for tx in block.transactions() { - let tx = tx.clone().into_ecrecovered().ok_or_eyre("failed to recover sender")?; + for tx in block.clone().into_transactions_ecrecovered() { let env = EnvWithHandlerCfg { env: Env::boxed(cfg.cfg_env.clone(), block_env.clone(), evm_config.tx_env(&tx)), handler_cfg: cfg.handler_cfg, diff --git a/crates/storage/storage-api/src/state.rs b/crates/storage/storage-api/src/state.rs index adf0601eb9a2..25da9ef4d2f0 100644 --- a/crates/storage/storage-api/src/state.rs +++ b/crates/storage/storage-api/src/state.rs @@ -140,7 +140,7 @@ pub trait StateProviderFactory: BlockIdReader + Send + Sync { /// Note: this only looks at historical blocks, not pending blocks. fn history_by_block_hash(&self, block: BlockHash) -> ProviderResult; - /// Returns _any_[StateProvider] with matching block hash. + /// Returns _any_ [StateProvider] with matching block hash. /// /// This will return a [StateProvider] for either a historical or pending block. fn state_by_block_hash(&self, block: BlockHash) -> ProviderResult; From 069df9ae0e8ac9fd668227e73fa48fa8162df119 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 4 Sep 2024 14:16:34 +0100 Subject: [PATCH 04/20] add a comment about unifying the logic with RPC --- crates/engine/invalid-block-hooks/src/witness.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/engine/invalid-block-hooks/src/witness.rs b/crates/engine/invalid-block-hooks/src/witness.rs index f3e02a736291..ff6b5dd9b6d5 100644 --- a/crates/engine/invalid-block-hooks/src/witness.rs +++ b/crates/engine/invalid-block-hooks/src/witness.rs @@ -44,6 +44,8 @@ where _output: &BlockExecutionOutput, _trie_updates: Option<(&TrieUpdates, B256)>, ) -> eyre::Result<()> { + // TODO(alexey): unify with `DebugApi::debug_execution_witness` + // Setup database. let mut db = StateBuilder::new() .with_database(StateProviderDatabase::new( From e232bdc322015f33806fd9ce8f081a5563d1851b Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 4 Sep 2024 14:21:50 +0100 Subject: [PATCH 05/20] bad block hook -> invalid block hook --- crates/engine/primitives/src/invalid_block_hook.rs | 4 ++-- crates/engine/tree/src/tree/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/engine/primitives/src/invalid_block_hook.rs b/crates/engine/primitives/src/invalid_block_hook.rs index 120114c2a773..51a30bd7eb42 100644 --- a/crates/engine/primitives/src/invalid_block_hook.rs +++ b/crates/engine/primitives/src/invalid_block_hook.rs @@ -2,9 +2,9 @@ use reth_execution_types::BlockExecutionOutput; use reth_primitives::{Receipt, SealedBlockWithSenders, SealedHeader, B256}; use reth_trie::updates::TrieUpdates; -/// A bad block hook. +/// An invalid block hook. pub trait InvalidBlockHook: Send + Sync { - /// Invoked when a bad block is encountered. + /// Invoked when an invalid block is encountered. fn on_invalid_block( &self, parent_header: &SealedHeader, diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 0067ddcaa158..9617f2db1ae5 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -526,7 +526,7 @@ pub struct EngineApiTreeHandler { config: TreeConfig, /// Metrics for the engine api. metrics: EngineApiMetrics, - /// A bad block hook. + /// An invalid block hook. invalid_block_hook: Box, } From 6f61a3ca91c24c4cc0cd493624fbdbd53ff7f3f1 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 4 Sep 2024 14:45:05 +0100 Subject: [PATCH 06/20] feat(engine): support `debug.etherscan` on experimental engine --- crates/node/builder/src/launch/engine.rs | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index e4ed58d2cb2b..d90fcee1ce46 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -7,6 +7,7 @@ use reth_beacon_consensus::{ }; use reth_blockchain_tree::BlockchainTreeConfig; use reth_chainspec::ChainSpec; +use reth_consensus_debug_client::{DebugConsensusClient, EtherscanBlockProvider}; use reth_engine_service::service::{ChainEvent, EngineService}; use reth_engine_tree::{ engine::{EngineApiRequest, EngineRequestHandler}, @@ -31,6 +32,7 @@ use reth_rpc_types::{engine::ClientVersionV1, WithOtherFields}; use reth_tasks::TaskExecutor; use reth_tokio_util::EventSender; use reth_tracing::tracing::{debug, error, info, warn}; +use std::sync::Arc; use tokio::sync::{mpsc::unbounded_channel, oneshot}; use tokio_stream::wrappers::UnboundedReceiverStream; @@ -285,6 +287,35 @@ where ) .await?; + if let Some(maybe_custom_etherscan_url) = ctx.node_config().debug.etherscan.clone() { + info!(target: "reth::cli", "Using etherscan as consensus client"); + + let chain = ctx.node_config().chain.chain; + let etherscan_url = maybe_custom_etherscan_url.map(Ok).unwrap_or_else(|| { + // If URL isn't provided, use default Etherscan URL for the chain if it is known + chain + .etherscan_urls() + .map(|urls| urls.0.to_string()) + .ok_or_else(|| eyre::eyre!("failed to get etherscan url for chain: {chain}")) + })?; + + let block_provider = EtherscanBlockProvider::new( + etherscan_url, + chain.etherscan_api_key().ok_or_else(|| { + eyre::eyre!( + "etherscan api key not found for rpc consensus client for chain: {chain}" + ) + })?, + ); + let rpc_consensus_client = DebugConsensusClient::new( + rpc_server_handles.auth.clone(), + Arc::new(block_provider), + ); + ctx.task_executor().spawn_critical("etherscan consensus client", async move { + rpc_consensus_client.run::().await + }); + } + // Run consensus engine to completion let initial_target = ctx.initial_backfill_target()?; let network_handle = ctx.components().network().clone(); From 59be1b330e8d24816479858c5f524a54f2726936 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 4 Sep 2024 14:54:44 +0100 Subject: [PATCH 07/20] add a todo note --- crates/node/builder/src/launch/engine.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index d90fcee1ce46..f3f9fb63e519 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -287,6 +287,7 @@ where ) .await?; + // TODO: migrate to devmode with https://github.com/paradigmxyz/reth/issues/10104 if let Some(maybe_custom_etherscan_url) = ctx.node_config().debug.etherscan.clone() { info!(target: "reth::cli", "Using etherscan as consensus client"); From 6c85323013de3ab818a677d5ac709ddb1e3270e4 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 4 Sep 2024 15:31:34 +0100 Subject: [PATCH 08/20] compare trie updates --- Cargo.lock | 1 + crates/engine/invalid-block-hooks/Cargo.toml | 3 ++- .../engine/invalid-block-hooks/src/witness.rs | 26 +++++++++++++++---- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2ced98767b9d..b181f7e854bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7270,6 +7270,7 @@ dependencies = [ "reth-primitives", "reth-provider", "reth-revm", + "reth-tracing", "reth-trie", "serde_json", ] diff --git a/crates/engine/invalid-block-hooks/Cargo.toml b/crates/engine/invalid-block-hooks/Cargo.toml index 9e5a97ce3904..97a87c753c7d 100644 --- a/crates/engine/invalid-block-hooks/Cargo.toml +++ b/crates/engine/invalid-block-hooks/Cargo.toml @@ -18,7 +18,8 @@ reth-evm.workspace = true reth-primitives.workspace = true reth-provider.workspace = true reth-revm.workspace = true -reth-trie.workspace = true +reth-tracing.workspace = true +reth-trie = { workspace = true, features = ["serde"] } # alloy alloy-rlp.workspace = true diff --git a/crates/engine/invalid-block-hooks/src/witness.rs b/crates/engine/invalid-block-hooks/src/witness.rs index ff6b5dd9b6d5..9e505cb2b8cc 100644 --- a/crates/engine/invalid-block-hooks/src/witness.rs +++ b/crates/engine/invalid-block-hooks/src/witness.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, path::PathBuf}; +use std::{collections::HashMap, fs::File, io::Write, path::PathBuf}; use alloy_rpc_types_debug::ExecutionWitness; use reth_chainspec::ChainSpec; @@ -15,6 +15,7 @@ use reth_revm::{ primitives::{BlockEnv, CfgEnvWithHandlerCfg, Env, EnvWithHandlerCfg}, DatabaseCommit, StateBuilder, }; +use reth_tracing::tracing::warn; use reth_trie::{updates::TrieUpdates, HashedPostState, HashedStorage}; /// Generates a witness for the given block and saves it to a file. @@ -42,7 +43,7 @@ where parent_header: &SealedHeader, block: &SealedBlockWithSenders, _output: &BlockExecutionOutput, - _trie_updates: Option<(&TrieUpdates, B256)>, + trie_updates: Option<(&TrieUpdates, B256)>, ) -> eyre::Result<()> { // TODO(alexey): unify with `DebugApi::debug_execution_witness` @@ -137,12 +138,27 @@ where // Generate an execution witness for the aggregated state of accessed accounts. // Destruct the cache database to retrieve the state provider. let state_provider = db.database.into_inner(); - let witness = state_provider.witness(HashedPostState::default(), hashed_state)?; + let witness = state_provider.witness(HashedPostState::default(), hashed_state.clone())?; // Write the witness to the output directory. - let path = self.output_directory.join(format!("{}_{}.json", block.number, block.hash())); + let mut file = File::options() + .create_new(true) + .open(self.output_directory.join(format!("{}_{}.jsonl", block.number, block.hash())))?; let response = ExecutionWitness { witness, state_preimages: Some(state_preimages) }; - std::fs::write(path, serde_json::to_string(&response)?)?; + writeln!(&mut file, "{}", serde_json::to_string(&response)?)?; + + // Calculate the state root and trie updates after re-execution. They should match + // the original ones. + let (state_root, trie_output) = state_provider.state_root_with_updates(hashed_state)?; + let new_trie_updates = (&trie_output, state_root); + match trie_updates { + Some(trie_updates) if new_trie_updates != trie_updates => { + warn!(target: "engine::invalid_block_hooks::witness", "Trie updates mismatch after re-execution"); + writeln!(&mut file, "{}", serde_json::to_string(&trie_updates)?)?; + writeln!(&mut file, "{}", serde_json::to_string(&new_trie_updates)?)?; + } + _ => {} + } Ok(()) } From eeedb637b06f66ca41a2da494ac74f6d31b58dd4 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Thu, 5 Sep 2024 09:13:07 +0100 Subject: [PATCH 09/20] sanity checks to compare outputs after re-execution --- Cargo.lock | 1 + .../engine/invalid-block-hooks/src/witness.rs | 23 ++++--- crates/node/builder/Cargo.toml | 61 ++++++++++--------- crates/node/builder/src/launch/common.rs | 2 + crates/node/builder/src/launch/engine.rs | 7 +-- crates/node/builder/src/launch/mod.rs | 6 +- 6 files changed, 50 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b181f7e854bd..f39804cecdad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7558,6 +7558,7 @@ dependencies = [ "reth-engine-util", "reth-evm", "reth-exex", + "reth-fs-util", "reth-invalid-block-hooks", "reth-network", "reth-network-api", diff --git a/crates/engine/invalid-block-hooks/src/witness.rs b/crates/engine/invalid-block-hooks/src/witness.rs index 9e505cb2b8cc..9b8cc8ee9709 100644 --- a/crates/engine/invalid-block-hooks/src/witness.rs +++ b/crates/engine/invalid-block-hooks/src/witness.rs @@ -42,7 +42,7 @@ where &self, parent_header: &SealedHeader, block: &SealedBlockWithSenders, - _output: &BlockExecutionOutput, + output: &BlockExecutionOutput, trie_updates: Option<(&TrieUpdates, B256)>, ) -> eyre::Result<()> { // TODO(alexey): unify with `DebugApi::debug_execution_witness` @@ -142,22 +142,27 @@ where // Write the witness to the output directory. let mut file = File::options() + .write(true) .create_new(true) - .open(self.output_directory.join(format!("{}_{}.jsonl", block.number, block.hash())))?; + .open(self.output_directory.join(format!("{}_{}.json", block.number, block.hash())))?; let response = ExecutionWitness { witness, state_preimages: Some(state_preimages) }; - writeln!(&mut file, "{}", serde_json::to_string(&response)?)?; + file.write_all(serde_json::to_string(&response)?.as_bytes())?; + + // The bundle state after re-execution should match the original one. + if bundle_state != output.state { + warn!(target: "engine::invalid_block_hooks::witness", "Bundle state mismatch after re-execution"); + } // Calculate the state root and trie updates after re-execution. They should match // the original ones. let (state_root, trie_output) = state_provider.state_root_with_updates(hashed_state)?; - let new_trie_updates = (&trie_output, state_root); - match trie_updates { - Some(trie_updates) if new_trie_updates != trie_updates => { + if let Some(trie_updates) = trie_updates { + if state_root != trie_updates.1 { + warn!(target: "engine::invalid_block_hooks::witness", "State root mismatch after re-execution"); + } + if &trie_output != trie_updates.0 { warn!(target: "engine::invalid_block_hooks::witness", "Trie updates mismatch after re-execution"); - writeln!(&mut file, "{}", serde_json::to_string(&trie_updates)?)?; - writeln!(&mut file, "{}", serde_json::to_string(&new_trie_updates)?)?; } - _ => {} } Ok(()) diff --git a/crates/node/builder/Cargo.toml b/crates/node/builder/Cargo.toml index 933c85af2287..93830ef7d947 100644 --- a/crates/node/builder/Cargo.toml +++ b/crates/node/builder/Cargo.toml @@ -13,48 +13,49 @@ workspace = true [dependencies] ## reth -reth-chainspec.workspace = true reth-auto-seal-consensus.workspace = true reth-beacon-consensus.workspace = true reth-blockchain-tree.workspace = true -reth-db-common.workspace = true -reth-exex.workspace = true -reth-evm.workspace = true -reth-provider.workspace = true +reth-chainspec.workspace = true +reth-cli-util.workspace = true +reth-config.workspace = true +reth-consensus-debug-client.workspace = true +reth-consensus.workspace = true reth-db = { workspace = true, features = ["mdbx"], optional = true } reth-db-api.workspace = true -reth-rpc-engine-api.workspace = true -reth-rpc.workspace = true -reth-rpc-builder.workspace = true -reth-rpc-layer.workspace = true +reth-db-common.workspace = true +reth-downloaders.workspace = true +reth-engine-service.workspace = true +reth-engine-tree.workspace = true +reth-engine-util.workspace = true +reth-evm.workspace = true +reth-exex.workspace = true +reth-fs-util.workspace = true +reth-invalid-block-hooks.workspace = true +reth-network-api.workspace = true +reth-network-p2p.workspace = true +reth-network.workspace = true reth-node-api.workspace = true reth-node-core.workspace = true +reth-node-events.workspace = true reth-node-metrics.workspace = true -reth-network.workspace = true -reth-primitives.workspace = true reth-payload-builder.workspace = true -reth-transaction-pool.workspace = true -reth-tasks.workspace = true -reth-tracing.workspace = true -reth-network-p2p.workspace = true -reth-static-file.workspace = true +reth-payload-validator.workspace = true +reth-primitives.workspace = true +reth-provider.workspace = true reth-prune.workspace = true -reth-stages.workspace = true -reth-config.workspace = true -reth-downloaders.workspace = true -reth-node-events.workspace = true -reth-consensus.workspace = true -reth-consensus-debug-client.workspace = true -reth-rpc-types.workspace = true -reth-engine-util.workspace = true -reth-cli-util.workspace = true +reth-rpc-builder.workspace = true +reth-rpc-engine-api.workspace = true reth-rpc-eth-types.workspace = true -reth-network-api.workspace = true -reth-payload-validator.workspace = true -reth-engine-service.workspace = true +reth-rpc-layer.workspace = true +reth-rpc-types.workspace = true +reth-rpc.workspace = true +reth-stages.workspace = true +reth-static-file.workspace = true +reth-tasks.workspace = true reth-tokio-util.workspace = true -reth-engine-tree.workspace = true -reth-invalid-block-hooks.workspace = true +reth-tracing.workspace = true +reth-transaction-pool.workspace = true ## ethereum alloy-network.workspace = true diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 1526c21b6f2c..3eedf8771055 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -17,6 +17,7 @@ use reth_db_common::init::{init_genesis, InitDatabaseError}; use reth_downloaders::{bodies::noop::NoopBodiesDownloader, headers::noop::NoopHeaderDownloader}; use reth_engine_tree::tree::{InvalidBlockHook, InvalidBlockHooks, NoopInvalidBlockHook}; use reth_evm::noop::NoopBlockExecutorProvider; +use reth_fs_util as fs; use reth_network_p2p::headers::client::HeadersClient; use reth_node_api::FullNodeTypes; use reth_node_core::{ @@ -850,6 +851,7 @@ where .copied() .map(|hook| { let output_directory = output_directory.join(hook.to_string()); + fs::create_dir_all(&output_directory)?; Ok(match hook { reth_node_core::args::InvalidBlockHook::Witness => { diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index f3f9fb63e519..3e0b2686d219 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -31,7 +31,7 @@ use reth_rpc_engine_api::{capabilities::EngineCapabilities, EngineApi}; use reth_rpc_types::{engine::ClientVersionV1, WithOtherFields}; use reth_tasks::TaskExecutor; use reth_tokio_util::EventSender; -use reth_tracing::tracing::{debug, error, info, warn}; +use reth_tracing::tracing::{debug, error, info}; use std::sync::Arc; use tokio::sync::{mpsc::unbounded_channel, oneshot}; use tokio_stream::wrappers::UnboundedReceiverStream; @@ -204,11 +204,6 @@ where let pruner_events = pruner.events(); info!(target: "reth::cli", prune_config=?ctx.prune_config().unwrap_or_default(), "Pruner initialized"); - // TODO: implement methods which convert this value into an actual function - if let Some(ref hook_type) = ctx.node_config().debug.invalid_block_hook { - warn!(target: "reth::cli", ?hook_type, "Invalid block hooks are not implemented yet! The `debug.invalid-block-hook` flag will do nothing for now."); - } - // Configure the consensus engine let mut eth_service = EngineService::new( ctx.consensus(), diff --git a/crates/node/builder/src/launch/mod.rs b/crates/node/builder/src/launch/mod.rs index 89a40a1a5369..c579de92eb2b 100644 --- a/crates/node/builder/src/launch/mod.rs +++ b/crates/node/builder/src/launch/mod.rs @@ -34,7 +34,7 @@ use reth_provider::providers::BlockchainProvider; use reth_rpc_engine_api::{capabilities::EngineCapabilities, EngineApi}; use reth_rpc_types::{engine::ClientVersionV1, WithOtherFields}; use reth_tasks::TaskExecutor; -use reth_tracing::tracing::{debug, info, warn}; +use reth_tracing::tracing::{debug, info}; use reth_transaction_pool::TransactionPool; use tokio::sync::{mpsc::unbounded_channel, oneshot}; use tokio_stream::wrappers::UnboundedReceiverStream; @@ -210,10 +210,6 @@ where let max_block = ctx.max_block(network_client.clone()).await?; let mut hooks = EngineHooks::new(); - if let Some(ref hook_type) = ctx.node_config().debug.invalid_block_hook { - warn!(target: "reth::cli", ?hook_type, "Bad block hooks are not implemented yet! The `debug.bad-block-hook` flag will do nothing for now."); - } - let static_file_producer = ctx.static_file_producer(); let static_file_producer_events = static_file_producer.lock().events(); hooks.add(StaticFileHook::new( From 6f321029ad30f4bb7b0d60a7ea3a5e9141ec1f01 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Thu, 5 Sep 2024 10:15:26 +0100 Subject: [PATCH 10/20] init evm in a more sane way --- Cargo.lock | 23 +++++ crates/engine/invalid-block-hooks/Cargo.toml | 2 + .../engine/invalid-block-hooks/src/witness.rs | 89 +++++++++++-------- 3 files changed, 77 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f39804cecdad..ee2da3708310 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2406,6 +2406,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.9.0" @@ -5521,6 +5527,16 @@ dependencies = [ "termtree", ] +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "prettyplease" version = "0.2.22" @@ -7264,6 +7280,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types-debug", "eyre", + "pretty_assertions", "reth-chainspec", "reth-engine-primitives", "reth-evm", @@ -11205,6 +11222,12 @@ dependencies = [ "tap", ] +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + [[package]] name = "yoke" version = "0.7.4" diff --git a/crates/engine/invalid-block-hooks/Cargo.toml b/crates/engine/invalid-block-hooks/Cargo.toml index 97a87c753c7d..d747a215426b 100644 --- a/crates/engine/invalid-block-hooks/Cargo.toml +++ b/crates/engine/invalid-block-hooks/Cargo.toml @@ -27,4 +27,6 @@ alloy-rpc-types-debug.workspace = true # misc eyre.workspace = true +pretty_assertions = "1.4" serde_json.workspace = true + diff --git a/crates/engine/invalid-block-hooks/src/witness.rs b/crates/engine/invalid-block-hooks/src/witness.rs index 9b8cc8ee9709..ff9faa979659 100644 --- a/crates/engine/invalid-block-hooks/src/witness.rs +++ b/crates/engine/invalid-block-hooks/src/witness.rs @@ -1,10 +1,11 @@ use std::{collections::HashMap, fs::File, io::Write, path::PathBuf}; use alloy_rpc_types_debug::ExecutionWitness; +use eyre::OptionExt; use reth_chainspec::ChainSpec; use reth_engine_primitives::InvalidBlockHook; use reth_evm::{ - system_calls::{pre_block_beacon_root_contract_call, pre_block_blockhashes_contract_call}, + system_calls::{apply_beacon_root_contract_call, apply_blockhashes_contract_call}, ConfigureEvm, }; use reth_primitives::{keccak256, Receipt, SealedBlockWithSenders, SealedHeader, B256, U256}; @@ -12,10 +13,9 @@ use reth_provider::{BlockExecutionOutput, ChainSpecProvider, StateProviderFactor use reth_revm::{ database::StateProviderDatabase, db::states::bundle_state::BundleRetention, - primitives::{BlockEnv, CfgEnvWithHandlerCfg, Env, EnvWithHandlerCfg}, + primitives::{BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg}, DatabaseCommit, StateBuilder, }; -use reth_tracing::tracing::warn; use reth_trie::{updates::TrieUpdates, HashedPostState, HashedStorage}; /// Generates a witness for the given block and saves it to a file. @@ -58,8 +58,7 @@ where // Setup environment for the execution. let mut cfg = CfgEnvWithHandlerCfg::new(Default::default(), Default::default()); let mut block_env = BlockEnv::default(); - let evm_config = self.evm_config.clone(); - evm_config.fill_cfg_and_block_env( + self.evm_config.fill_cfg_and_block_env( &mut cfg, &mut block_env, &self.provider.chain_spec(), @@ -67,36 +66,44 @@ where U256::MAX, ); - // Apply pre-block system contract calls. - pre_block_beacon_root_contract_call( + // Setup EVM + let mut evm = self.evm_config.evm_with_env( &mut db, - &evm_config, + EnvWithHandlerCfg::new_with_cfg_env(cfg, block_env, Default::default()), + ); + + // Apply pre-block system contract calls. + apply_beacon_root_contract_call( + &self.evm_config, &self.provider.chain_spec(), - &cfg, - &block_env, + block.timestamp, + block.number, block.parent_beacon_block_root, + &mut evm, )?; - pre_block_blockhashes_contract_call( - &mut db, - &evm_config, + apply_blockhashes_contract_call( + &self.evm_config, &self.provider.chain_spec(), - &cfg, - &block_env, + block.timestamp, + block.number, block.parent_hash, + &mut evm, )?; // Re-execute all of the transactions in the block to load all touched accounts into // the cache DB. - for tx in block.clone().into_transactions_ecrecovered() { - let env = EnvWithHandlerCfg { - env: Env::boxed(cfg.cfg_env.clone(), block_env.clone(), evm_config.tx_env(&tx)), - handler_cfg: cfg.handler_cfg, - }; - - let result = self.evm_config.evm_with_env(&mut db, env).transact()?; - db.commit(result.state); + for tx in block.transactions() { + self.evm_config.fill_tx_env( + evm.tx_mut(), + tx, + tx.recover_signer().ok_or_eyre("failed to recover sender")?, + ); + let result = evm.transact()?; + evm.db_mut().commit(result.state); } + drop(evm); + // Merge all state transitions db.merge_transitions(BundleRetention::Reverts); @@ -148,20 +155,28 @@ where let response = ExecutionWitness { witness, state_preimages: Some(state_preimages) }; file.write_all(serde_json::to_string(&response)?.as_bytes())?; - // The bundle state after re-execution should match the original one. - if bundle_state != output.state { - warn!(target: "engine::invalid_block_hooks::witness", "Bundle state mismatch after re-execution"); - } - - // Calculate the state root and trie updates after re-execution. They should match - // the original ones. - let (state_root, trie_output) = state_provider.state_root_with_updates(hashed_state)?; - if let Some(trie_updates) = trie_updates { - if state_root != trie_updates.1 { - warn!(target: "engine::invalid_block_hooks::witness", "State root mismatch after re-execution"); - } - if &trie_output != trie_updates.0 { - warn!(target: "engine::invalid_block_hooks::witness", "Trie updates mismatch after re-execution"); + if cfg!(debug_assertions) { + // The bundle state after re-execution should match the original one. + pretty_assertions::assert_eq!( + bundle_state, + output.state, + "Bundle state mismatch after re-execution" + ); + + // Calculate the state root and trie updates after re-execution. They should match + // the original ones. + let (state_root, trie_output) = state_provider.state_root_with_updates(hashed_state)?; + if let Some(trie_updates) = trie_updates { + pretty_assertions::assert_eq!( + state_root, + trie_updates.1, + "State root mismatch after re-execution" + ); + pretty_assertions::assert_eq!( + &trie_output, + trie_updates.0, + "Trie updates mismatch after re-execution" + ); } } From 3f27a1be4b1f774c628f3050ed1a8831ffa15178 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Thu, 5 Sep 2024 13:03:05 +0100 Subject: [PATCH 11/20] Revert "Merge branch 'alexey/engine-experimental-etherscan' into alexey/invalid-block-hook-witness" This reverts commit 9b424af5747675868e053e25133a67f2122c8a09, reversing changes made to 6c85323013de3ab818a677d5ac709ddb1e3270e4. --- crates/node/builder/src/launch/engine.rs | 32 ------------------------ 1 file changed, 32 deletions(-) diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index 1c5aaa7b6690..ae0cbee64c8e 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -7,7 +7,6 @@ use reth_beacon_consensus::{ }; use reth_blockchain_tree::BlockchainTreeConfig; use reth_chainspec::ChainSpec; -use reth_consensus_debug_client::{DebugConsensusClient, EtherscanBlockProvider}; use reth_engine_service::service::{ChainEvent, EngineService}; use reth_engine_tree::{ engine::{EngineApiRequest, EngineRequestHandler}, @@ -32,7 +31,6 @@ use reth_rpc_types::{engine::ClientVersionV1, WithOtherFields}; use reth_tasks::TaskExecutor; use reth_tokio_util::EventSender; use reth_tracing::tracing::{debug, error, info}; -use std::sync::Arc; use tokio::sync::{mpsc::unbounded_channel, oneshot}; use tokio_stream::wrappers::UnboundedReceiverStream; @@ -280,36 +278,6 @@ where ) .await?; - // TODO: migrate to devmode with https://github.com/paradigmxyz/reth/issues/10104 - if let Some(maybe_custom_etherscan_url) = ctx.node_config().debug.etherscan.clone() { - info!(target: "reth::cli", "Using etherscan as consensus client"); - - let chain = ctx.node_config().chain.chain; - let etherscan_url = maybe_custom_etherscan_url.map(Ok).unwrap_or_else(|| { - // If URL isn't provided, use default Etherscan URL for the chain if it is known - chain - .etherscan_urls() - .map(|urls| urls.0.to_string()) - .ok_or_else(|| eyre::eyre!("failed to get etherscan url for chain: {chain}")) - })?; - - let block_provider = EtherscanBlockProvider::new( - etherscan_url, - chain.etherscan_api_key().ok_or_else(|| { - eyre::eyre!( - "etherscan api key not found for rpc consensus client for chain: {chain}" - ) - })?, - ); - let rpc_consensus_client = DebugConsensusClient::new( - rpc_server_handles.auth.clone(), - Arc::new(block_provider), - ); - ctx.task_executor().spawn_critical("etherscan consensus client", async move { - rpc_consensus_client.run::().await - }); - } - // Run consensus engine to completion let initial_target = ctx.initial_backfill_target()?; let network_handle = ctx.components().network().clone(); From 22a7fff82227e9c8d7fe9499780f6d612c854d61 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Thu, 5 Sep 2024 13:03:39 +0100 Subject: [PATCH 12/20] newline at the end of Cargo.toml --- crates/engine/invalid-block-hooks/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/engine/invalid-block-hooks/Cargo.toml b/crates/engine/invalid-block-hooks/Cargo.toml index d747a215426b..17ac3cec5523 100644 --- a/crates/engine/invalid-block-hooks/Cargo.toml +++ b/crates/engine/invalid-block-hooks/Cargo.toml @@ -29,4 +29,3 @@ alloy-rpc-types-debug.workspace = true eyre.workspace = true pretty_assertions = "1.4" serde_json.workspace = true - From 1b91e7371e39ed6fe3cd280d714ef29b422bc307 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Thu, 5 Sep 2024 15:34:41 +0100 Subject: [PATCH 13/20] clarify bounds --- crates/node/builder/src/launch/common.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 748b4f0eea71..54f6a2b40d53 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -36,8 +36,9 @@ use reth_node_metrics::{ use reth_primitives::{BlockNumber, Head, B256}; use reth_provider::{ providers::{BlockchainProvider, BlockchainProvider2, StaticFileProvider}, - BlockHashReader, CanonStateNotificationSender, ProviderFactory, ProviderResult, - StageCheckpointReader, StaticFileProviderFactory, TreeViewer, + BlockHashReader, CanonStateNotificationSender, ChainSpecProvider, ProviderFactory, + ProviderResult, StageCheckpointReader, StateProviderFactory, StaticFileProviderFactory, + TreeViewer, }; use reth_prune::{PruneModes, PrunerBuilder}; use reth_rpc_builder::config::RethRpcServerConfig; @@ -840,7 +841,16 @@ where pub const fn components(&self) -> &CB::Components { &self.node_adapter().components } +} +impl LaunchContextWith>> +where + DB: Database + DatabaseMetrics + Send + Sync + Clone + 'static, + T: FullNodeTypes< + Provider: WithTree + StateProviderFactory + ChainSpecProvider, + >, + CB: NodeComponentsBuilder, +{ /// Returns the [`InvalidBlockHook`] to use for the node. pub fn invalid_block_hook(&self) -> eyre::Result> { Ok(if let Some(ref hook) = self.node_config().debug.invalid_block_hook { From 33063328560229c86e6fca0292cdd9f580fe48fc Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Thu, 5 Sep 2024 17:02:38 +0100 Subject: [PATCH 14/20] ok another types fix --- crates/node/builder/src/launch/common.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index f7e952c7ba38..1977dd1ed2b0 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -841,11 +841,11 @@ where } } -impl LaunchContextWith>> +impl LaunchContextWith>> where - DB: Database + DatabaseMetrics + Send + Sync + Clone + 'static, T: FullNodeTypes< Provider: WithTree + StateProviderFactory + ChainSpecProvider, + Types: NodeTypes, >, CB: NodeComponentsBuilder, { From e1981d8c83c82c8c82980772b2d3c0d981d70fc0 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Fri, 6 Sep 2024 10:15:24 +0100 Subject: [PATCH 15/20] always save mismatched bundle state / state root / trie updates --- .../engine/invalid-block-hooks/src/witness.rs | 79 +++++++++++++------ 1 file changed, 57 insertions(+), 22 deletions(-) diff --git a/crates/engine/invalid-block-hooks/src/witness.rs b/crates/engine/invalid-block-hooks/src/witness.rs index ff9faa979659..5d01f56ac1dd 100644 --- a/crates/engine/invalid-block-hooks/src/witness.rs +++ b/crates/engine/invalid-block-hooks/src/witness.rs @@ -1,7 +1,8 @@ -use std::{collections::HashMap, fs::File, io::Write, path::PathBuf}; +use std::{collections::HashMap, fmt::Debug, fs::File, io::Write, path::PathBuf}; use alloy_rpc_types_debug::ExecutionWitness; use eyre::OptionExt; +use pretty_assertions::Comparison; use reth_chainspec::ChainSpec; use reth_engine_primitives::InvalidBlockHook; use reth_evm::{ @@ -16,6 +17,7 @@ use reth_revm::{ primitives::{BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg}, DatabaseCommit, StateBuilder, }; +use reth_tracing::tracing::warn; use reth_trie::{updates::TrieUpdates, HashedPostState, HashedStorage}; /// Generates a witness for the given block and saves it to a file. @@ -33,6 +35,34 @@ impl Witness { } } +impl Witness { + /// Compares two values and saves the diff if they are different. + /// + /// Returns the path of the saved diff file if the values are different. + fn compare_and_save_diff( + &self, + filename: String, + original: &T, + new: &T, + ) -> Option { + if original == new { + return None + } + + let path = self.output_directory.join(filename); + let diff = Comparison::new(original, new); + File::options() + .write(true) + .create_new(true) + .open(&path) + .unwrap() + .write_all(diff.to_string().as_bytes()) + .unwrap(); + + Some(path) + } +} + impl InvalidBlockHook for Witness where P: StateProviderFactory + ChainSpecProvider + Send + Sync + 'static, @@ -155,28 +185,33 @@ where let response = ExecutionWitness { witness, state_preimages: Some(state_preimages) }; file.write_all(serde_json::to_string(&response)?.as_bytes())?; - if cfg!(debug_assertions) { - // The bundle state after re-execution should match the original one. - pretty_assertions::assert_eq!( - bundle_state, - output.state, - "Bundle state mismatch after re-execution" - ); + // The bundle state after re-execution should match the original one. + if let Some(path) = self.compare_and_save_diff( + format!("{}_{}.bundle_state.diff", block.number, block.hash()), + &bundle_state, + &output.state, + ) { + warn!(target: "engine::invalid_block_hooks::witness", path = %path.display(), "Bundle state mismatch after re-execution"); + } + + // Calculate the state root and trie updates after re-execution. They should match + // the original ones. + let (state_root, trie_output) = state_provider.state_root_with_updates(hashed_state)?; + if let Some(trie_updates) = trie_updates { + if let Some(path) = self.compare_and_save_diff( + format!("{}_{}.state_root.diff", block.number, block.hash()), + &state_root, + &trie_updates.1, + ) { + warn!(target: "engine::invalid_block_hooks::witness", path = %path.display(), "State root mismatch after re-execution"); + } - // Calculate the state root and trie updates after re-execution. They should match - // the original ones. - let (state_root, trie_output) = state_provider.state_root_with_updates(hashed_state)?; - if let Some(trie_updates) = trie_updates { - pretty_assertions::assert_eq!( - state_root, - trie_updates.1, - "State root mismatch after re-execution" - ); - pretty_assertions::assert_eq!( - &trie_output, - trie_updates.0, - "Trie updates mismatch after re-execution" - ); + if let Some(path) = self.compare_and_save_diff( + format!("{}_{}.trie_updates.diff", block.number, block.hash()), + &trie_output, + trie_updates.0, + ) { + warn!(target: "engine::invalid_block_hooks::witness", path = %path.display(), "Trie updates mismatch after re-execution"); } } From 9fdfed084d48ffadc6aa38617ce68064d42e63e3 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Fri, 6 Sep 2024 10:20:26 +0100 Subject: [PATCH 16/20] field comments --- crates/engine/invalid-block-hooks/src/witness.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/engine/invalid-block-hooks/src/witness.rs b/crates/engine/invalid-block-hooks/src/witness.rs index 5d01f56ac1dd..77becd44fb5a 100644 --- a/crates/engine/invalid-block-hooks/src/witness.rs +++ b/crates/engine/invalid-block-hooks/src/witness.rs @@ -23,8 +23,12 @@ use reth_trie::{updates::TrieUpdates, HashedPostState, HashedStorage}; /// Generates a witness for the given block and saves it to a file. #[derive(Debug)] pub struct Witness { + /// The directory to write the witness to. Additionally, diff files will be written to this + /// directory in case of failed sanity checks. output_directory: PathBuf, + /// The provider to read the historical state and do the EVM execution. provider: P, + /// The EVM configuration to use for the execution. evm_config: EvmConfig, } From 3c22f3e2087f9b052b1e41a4a473c97925f5b6b4 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Fri, 6 Sep 2024 10:55:57 +0100 Subject: [PATCH 17/20] fixes after Roman's review --- crates/engine/invalid-block-hooks/src/lib.rs | 2 +- .../engine/invalid-block-hooks/src/witness.rs | 73 ++++++++++--------- crates/node/builder/src/launch/common.rs | 3 +- 3 files changed, 43 insertions(+), 35 deletions(-) diff --git a/crates/engine/invalid-block-hooks/src/lib.rs b/crates/engine/invalid-block-hooks/src/lib.rs index 271b43eb057d..26e208da6436 100644 --- a/crates/engine/invalid-block-hooks/src/lib.rs +++ b/crates/engine/invalid-block-hooks/src/lib.rs @@ -2,4 +2,4 @@ mod witness; -pub use witness::Witness; +pub use witness::InvalidBlockWitnessHook; diff --git a/crates/engine/invalid-block-hooks/src/witness.rs b/crates/engine/invalid-block-hooks/src/witness.rs index 77becd44fb5a..a02487e460af 100644 --- a/crates/engine/invalid-block-hooks/src/witness.rs +++ b/crates/engine/invalid-block-hooks/src/witness.rs @@ -22,7 +22,7 @@ use reth_trie::{updates::TrieUpdates, HashedPostState, HashedStorage}; /// Generates a witness for the given block and saves it to a file. #[derive(Debug)] -pub struct Witness { +pub struct InvalidBlockWitnessHook { /// The directory to write the witness to. Additionally, diff files will be written to this /// directory in case of failed sanity checks. output_directory: PathBuf, @@ -32,42 +32,34 @@ pub struct Witness { evm_config: EvmConfig, } -impl Witness { +impl InvalidBlockWitnessHook { /// Creates a new witness hook. pub const fn new(output_directory: PathBuf, provider: P, evm_config: EvmConfig) -> Self { Self { output_directory, provider, evm_config } } } -impl Witness { - /// Compares two values and saves the diff if they are different. - /// - /// Returns the path of the saved diff file if the values are different. - fn compare_and_save_diff( +impl InvalidBlockWitnessHook { + /// Saves the diff of two values. + fn save_diff( &self, filename: String, original: &T, new: &T, - ) -> Option { - if original == new { - return None - } - + ) -> eyre::Result { let path = self.output_directory.join(filename); let diff = Comparison::new(original, new); File::options() .write(true) .create_new(true) - .open(&path) - .unwrap() - .write_all(diff.to_string().as_bytes()) - .unwrap(); + .open(&path)? + .write_all(diff.to_string().as_bytes())?; - Some(path) + Ok(path) } } -impl InvalidBlockHook for Witness +impl InvalidBlockHook for InvalidBlockWitnessHook where P: StateProviderFactory + ChainSpecProvider + Send + Sync + 'static, EvmConfig: ConfigureEvm, @@ -190,11 +182,16 @@ where file.write_all(serde_json::to_string(&response)?.as_bytes())?; // The bundle state after re-execution should match the original one. - if let Some(path) = self.compare_and_save_diff( - format!("{}_{}.bundle_state.diff", block.number, block.hash()), - &bundle_state, - &output.state, - ) { + if let Some(path) = (bundle_state != output.state) + .then(|| { + self.save_diff( + format!("{}_{}.bundle_state.diff", block.number, block.hash()), + &bundle_state, + &output.state, + ) + }) + .transpose()? + { warn!(target: "engine::invalid_block_hooks::witness", path = %path.display(), "Bundle state mismatch after re-execution"); } @@ -202,19 +199,29 @@ where // the original ones. let (state_root, trie_output) = state_provider.state_root_with_updates(hashed_state)?; if let Some(trie_updates) = trie_updates { - if let Some(path) = self.compare_and_save_diff( - format!("{}_{}.state_root.diff", block.number, block.hash()), - &state_root, - &trie_updates.1, - ) { + if let Some(path) = (state_root != trie_updates.1) + .then(|| { + self.save_diff( + format!("{}_{}.state_root.diff", block.number, block.hash()), + &state_root, + &trie_updates.1, + ) + }) + .transpose()? + { warn!(target: "engine::invalid_block_hooks::witness", path = %path.display(), "State root mismatch after re-execution"); } - if let Some(path) = self.compare_and_save_diff( - format!("{}_{}.trie_updates.diff", block.number, block.hash()), - &trie_output, - trie_updates.0, - ) { + if let Some(path) = (&trie_output != trie_updates.0) + .then(|| { + self.save_diff( + format!("{}_{}.trie_updates.diff", block.number, block.hash()), + &trie_output, + trie_updates.0, + ) + }) + .transpose()? + { warn!(target: "engine::invalid_block_hooks::witness", path = %path.display(), "Trie updates mismatch after re-execution"); } } diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 1977dd1ed2b0..5fe19cd199d7 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -18,6 +18,7 @@ use reth_downloaders::{bodies::noop::NoopBodiesDownloader, headers::noop::NoopHe use reth_engine_tree::tree::{InvalidBlockHook, InvalidBlockHooks, NoopInvalidBlockHook}; use reth_evm::noop::NoopBlockExecutorProvider; use reth_fs_util as fs; +use reth_invalid_block_hooks::InvalidBlockWitnessHook; use reth_network_p2p::headers::client::HeadersClient; use reth_node_api::{FullNodeTypes, NodeTypes, NodeTypesWithDB}; use reth_node_core::{ @@ -862,7 +863,7 @@ where Ok(match hook { reth_node_core::args::InvalidBlockHook::Witness => { - Box::new(reth_invalid_block_hooks::Witness::new( + Box::new(InvalidBlockWitnessHook::new( output_directory, self.blockchain_db().clone(), self.components().evm_config().clone(), From 0ad18f6c3b562f49c2ef97078925d91e8fc743f9 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Fri, 6 Sep 2024 11:00:59 +0100 Subject: [PATCH 18/20] handle hook error inside the impl --- Cargo.lock | 2 - .../engine/invalid-block-hooks/src/witness.rs | 58 ++++++++++++------- crates/engine/primitives/Cargo.toml | 1 - .../primitives/src/invalid_block_hook.rs | 7 +-- crates/engine/tree/Cargo.toml | 1 - .../tree/src/tree/invalid_block_hook.rs | 9 +-- crates/engine/tree/src/tree/mod.rs | 12 ++-- 7 files changed, 47 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e931fed33fcf..2a7bf4d9ca55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6825,7 +6825,6 @@ dependencies = [ name = "reth-engine-primitives" version = "1.0.6" dependencies = [ - "eyre", "reth-chainspec", "reth-execution-types", "reth-payload-primitives", @@ -6870,7 +6869,6 @@ version = "1.0.6" dependencies = [ "alloy-rlp", "assert_matches", - "eyre", "futures", "metrics", "rand 0.8.5", diff --git a/crates/engine/invalid-block-hooks/src/witness.rs b/crates/engine/invalid-block-hooks/src/witness.rs index a02487e460af..14398810dd4f 100644 --- a/crates/engine/invalid-block-hooks/src/witness.rs +++ b/crates/engine/invalid-block-hooks/src/witness.rs @@ -39,27 +39,7 @@ impl InvalidBlockWitnessHook { } } -impl InvalidBlockWitnessHook { - /// Saves the diff of two values. - fn save_diff( - &self, - filename: String, - original: &T, - new: &T, - ) -> eyre::Result { - let path = self.output_directory.join(filename); - let diff = Comparison::new(original, new); - File::options() - .write(true) - .create_new(true) - .open(&path)? - .write_all(diff.to_string().as_bytes())?; - - Ok(path) - } -} - -impl InvalidBlockHook for InvalidBlockWitnessHook +impl InvalidBlockWitnessHook where P: StateProviderFactory + ChainSpecProvider + Send + Sync + 'static, EvmConfig: ConfigureEvm, @@ -228,4 +208,40 @@ where Ok(()) } + + /// Saves the diff of two values. + fn save_diff( + &self, + filename: String, + original: &T, + new: &T, + ) -> eyre::Result { + let path = self.output_directory.join(filename); + let diff = Comparison::new(original, new); + File::options() + .write(true) + .create_new(true) + .open(&path)? + .write_all(diff.to_string().as_bytes())?; + + Ok(path) + } +} + +impl InvalidBlockHook for InvalidBlockWitnessHook +where + P: StateProviderFactory + ChainSpecProvider + Send + Sync + 'static, + EvmConfig: ConfigureEvm, +{ + fn on_invalid_block( + &self, + parent_header: &SealedHeader, + block: &SealedBlockWithSenders, + output: &BlockExecutionOutput, + trie_updates: Option<(&TrieUpdates, B256)>, + ) { + if let Err(err) = self.on_invalid_block(parent_header, block, output, trie_updates) { + warn!(target: "engine::invalid_block_hooks::witness", %err, "Failed to invoke hook"); + } + } } diff --git a/crates/engine/primitives/Cargo.toml b/crates/engine/primitives/Cargo.toml index 76765be919a2..437aac6a8793 100644 --- a/crates/engine/primitives/Cargo.toml +++ b/crates/engine/primitives/Cargo.toml @@ -19,5 +19,4 @@ reth-primitives.workspace = true reth-trie.workspace = true # misc -eyre.workspace = true serde.workspace = true diff --git a/crates/engine/primitives/src/invalid_block_hook.rs b/crates/engine/primitives/src/invalid_block_hook.rs index 51a30bd7eb42..9e1737dda047 100644 --- a/crates/engine/primitives/src/invalid_block_hook.rs +++ b/crates/engine/primitives/src/invalid_block_hook.rs @@ -11,7 +11,7 @@ pub trait InvalidBlockHook: Send + Sync { block: &SealedBlockWithSenders, output: &BlockExecutionOutput, trie_updates: Option<(&TrieUpdates, B256)>, - ) -> eyre::Result<()>; + ); } impl InvalidBlockHook for F @@ -21,8 +21,7 @@ where &SealedBlockWithSenders, &BlockExecutionOutput, Option<(&TrieUpdates, B256)>, - ) -> eyre::Result<()> - + Send + ) + Send + Sync, { fn on_invalid_block( @@ -31,7 +30,7 @@ where block: &SealedBlockWithSenders, output: &BlockExecutionOutput, trie_updates: Option<(&TrieUpdates, B256)>, - ) -> eyre::Result<()> { + ) { self(parent_header, block, output, trie_updates) } } diff --git a/crates/engine/tree/Cargo.toml b/crates/engine/tree/Cargo.toml index afd5ffeb6133..2d8aef52c23d 100644 --- a/crates/engine/tree/Cargo.toml +++ b/crates/engine/tree/Cargo.toml @@ -44,7 +44,6 @@ metrics.workspace = true reth-metrics = { workspace = true, features = ["common"] } # misc -eyre.workspace = true tracing.workspace = true # optional deps for test-utils diff --git a/crates/engine/tree/src/tree/invalid_block_hook.rs b/crates/engine/tree/src/tree/invalid_block_hook.rs index d4366019a5a2..7e401b53c575 100644 --- a/crates/engine/tree/src/tree/invalid_block_hook.rs +++ b/crates/engine/tree/src/tree/invalid_block_hook.rs @@ -15,8 +15,7 @@ impl InvalidBlockHook for NoopInvalidBlockHook { _block: &SealedBlockWithSenders, _output: &BlockExecutionOutput, _trie_updates: Option<(&TrieUpdates, B256)>, - ) -> eyre::Result<()> { - Ok(()) + ) { } } @@ -36,11 +35,9 @@ impl InvalidBlockHook for InvalidBlockHooks { block: &SealedBlockWithSenders, output: &BlockExecutionOutput, trie_updates: Option<(&TrieUpdates, B256)>, - ) -> eyre::Result<()> { + ) { for hook in &self.0 { - hook.on_invalid_block(parent_header, block, output, trie_updates)?; + hook.on_invalid_block(parent_header, block, output, trie_updates); } - - Ok(()) } } diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index dc6d0eb2f08c..d9279cbce29e 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -2061,14 +2061,12 @@ where PostExecutionInput::new(&output.receipts, &output.requests), ) { // call post-block hook - if let Err(hook_err) = self.invalid_block_hook.on_invalid_block( + self.invalid_block_hook.on_invalid_block( &parent_block, &block.seal_slow(), &output, None, - ) { - error!(target: "engine::tree", %err, %hook_err, "Failed to invoke invalid block hook after execution"); - } + ); return Err(err.into()) } @@ -2079,14 +2077,12 @@ where state_provider.state_root_with_updates(hashed_state.clone())?; if state_root != block.state_root { // call post-block hook - if let Err(hook_err) = self.invalid_block_hook.on_invalid_block( + self.invalid_block_hook.on_invalid_block( &parent_block, &block.clone().seal_slow(), &output, Some((&trie_output, state_root)), - ) { - error!(target: "engine::tree", %hook_err, "Failed to invoke invalid block hook on state root mismatch"); - } + ); return Err(ConsensusError::BodyStateRootDiff( GotExpected { got: state_root, expected: block.state_root }.into(), ) From 335fbf4d52ade34ae8edd7577e0cdd6aff15bd36 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Fri, 6 Sep 2024 11:02:18 +0100 Subject: [PATCH 19/20] clarify save_diff doc --- crates/engine/invalid-block-hooks/src/witness.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/engine/invalid-block-hooks/src/witness.rs b/crates/engine/invalid-block-hooks/src/witness.rs index 14398810dd4f..bcfe0c852589 100644 --- a/crates/engine/invalid-block-hooks/src/witness.rs +++ b/crates/engine/invalid-block-hooks/src/witness.rs @@ -209,7 +209,7 @@ where Ok(()) } - /// Saves the diff of two values. + /// Saves the diff of two values into a file with the given name in the output directory. fn save_diff( &self, filename: String, From 149a257208c335f6bf1a2796964d512779dd923c Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Fri, 6 Sep 2024 12:44:02 +0100 Subject: [PATCH 20/20] ok no cool expressions --- .../engine/invalid-block-hooks/src/witness.rs | 39 +++++-------------- 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/crates/engine/invalid-block-hooks/src/witness.rs b/crates/engine/invalid-block-hooks/src/witness.rs index bcfe0c852589..d22dc2b21de4 100644 --- a/crates/engine/invalid-block-hooks/src/witness.rs +++ b/crates/engine/invalid-block-hooks/src/witness.rs @@ -162,16 +162,9 @@ where file.write_all(serde_json::to_string(&response)?.as_bytes())?; // The bundle state after re-execution should match the original one. - if let Some(path) = (bundle_state != output.state) - .then(|| { - self.save_diff( - format!("{}_{}.bundle_state.diff", block.number, block.hash()), - &bundle_state, - &output.state, - ) - }) - .transpose()? - { + if bundle_state != output.state { + let filename = format!("{}_{}.bundle_state.diff", block.number, block.hash()); + let path = self.save_diff(filename, &bundle_state, &output.state)?; warn!(target: "engine::invalid_block_hooks::witness", path = %path.display(), "Bundle state mismatch after re-execution"); } @@ -179,29 +172,15 @@ where // the original ones. let (state_root, trie_output) = state_provider.state_root_with_updates(hashed_state)?; if let Some(trie_updates) = trie_updates { - if let Some(path) = (state_root != trie_updates.1) - .then(|| { - self.save_diff( - format!("{}_{}.state_root.diff", block.number, block.hash()), - &state_root, - &trie_updates.1, - ) - }) - .transpose()? - { + if state_root != trie_updates.1 { + let filename = format!("{}_{}.state_root.diff", block.number, block.hash()); + let path = self.save_diff(filename, &state_root, &trie_updates.1)?; warn!(target: "engine::invalid_block_hooks::witness", path = %path.display(), "State root mismatch after re-execution"); } - if let Some(path) = (&trie_output != trie_updates.0) - .then(|| { - self.save_diff( - format!("{}_{}.trie_updates.diff", block.number, block.hash()), - &trie_output, - trie_updates.0, - ) - }) - .transpose()? - { + if &trie_output != trie_updates.0 { + let filename = format!("{}_{}.trie_updates.diff", block.number, block.hash()); + let path = self.save_diff(filename, &trie_output, trie_updates.0)?; warn!(target: "engine::invalid_block_hooks::witness", path = %path.display(), "Trie updates mismatch after re-execution"); } }