mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat: add invalid block hook field to tree (#10432)
Co-authored-by: Alexey Shekhirin <a.shekhirin@gmail.com>
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -7611,6 +7611,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"shellexpand",
|
"shellexpand",
|
||||||
|
"strum",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
|
|||||||
7
book/cli/reth/node.md
vendored
7
book/cli/reth/node.md
vendored
@ -533,6 +533,13 @@ Debug:
|
|||||||
--debug.engine-api-store <PATH>
|
--debug.engine-api-store <PATH>
|
||||||
The path to store engine API messages at. If specified, all of the intercepted engine API messages will be written to specified location
|
The path to store engine API messages at. If specified, all of the intercepted engine API messages will be written to specified location
|
||||||
|
|
||||||
|
--debug.invalid-block-hook <INVALID_BLOCK_HOOK>
|
||||||
|
Determines which type of bad block hook to install
|
||||||
|
|
||||||
|
Example: `witness,prestate`
|
||||||
|
|
||||||
|
[possible values: witness, pre-state, opcode]
|
||||||
|
|
||||||
Database:
|
Database:
|
||||||
--db.log-level <LOG_LEVEL>
|
--db.log-level <LOG_LEVEL>
|
||||||
Database logging level. Levels higher than "notice" require a debug build
|
Database logging level. Levels higher than "notice" require a debug build
|
||||||
|
|||||||
@ -10,7 +10,7 @@ use reth_engine_tree::{
|
|||||||
download::BasicBlockDownloader,
|
download::BasicBlockDownloader,
|
||||||
engine::{EngineApiRequest, EngineApiRequestHandler, EngineHandler},
|
engine::{EngineApiRequest, EngineApiRequestHandler, EngineHandler},
|
||||||
persistence::PersistenceHandle,
|
persistence::PersistenceHandle,
|
||||||
tree::{EngineApiTreeHandler, TreeConfig},
|
tree::{EngineApiTreeHandler, InvalidBlockHook, TreeConfig},
|
||||||
};
|
};
|
||||||
pub use reth_engine_tree::{
|
pub use reth_engine_tree::{
|
||||||
chain::{ChainEvent, ChainOrchestrator},
|
chain::{ChainEvent, ChainOrchestrator},
|
||||||
@ -80,6 +80,7 @@ where
|
|||||||
pruner: Pruner<DB, ProviderFactory<DB>>,
|
pruner: Pruner<DB, ProviderFactory<DB>>,
|
||||||
payload_builder: PayloadBuilderHandle<T>,
|
payload_builder: PayloadBuilderHandle<T>,
|
||||||
tree_config: TreeConfig,
|
tree_config: TreeConfig,
|
||||||
|
invalid_block_hook: Box<dyn InvalidBlockHook>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let downloader = BasicBlockDownloader::new(client, consensus.clone());
|
let downloader = BasicBlockDownloader::new(client, consensus.clone());
|
||||||
|
|
||||||
@ -97,6 +98,7 @@ where
|
|||||||
payload_builder,
|
payload_builder,
|
||||||
canonical_in_memory_state,
|
canonical_in_memory_state,
|
||||||
tree_config,
|
tree_config,
|
||||||
|
invalid_block_hook,
|
||||||
);
|
);
|
||||||
|
|
||||||
let engine_handler = EngineApiRequestHandler::new(to_tree_tx, from_tree);
|
let engine_handler = EngineApiRequestHandler::new(to_tree_tx, from_tree);
|
||||||
@ -141,7 +143,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use reth_beacon_consensus::EthBeaconConsensus;
|
use reth_beacon_consensus::EthBeaconConsensus;
|
||||||
use reth_chainspec::{ChainSpecBuilder, MAINNET};
|
use reth_chainspec::{ChainSpecBuilder, MAINNET};
|
||||||
use reth_engine_tree::test_utils::TestPipelineBuilder;
|
use reth_engine_tree::{test_utils::TestPipelineBuilder, tree::NoopInvalidBlockHook};
|
||||||
use reth_ethereum_engine_primitives::EthEngineTypes;
|
use reth_ethereum_engine_primitives::EthEngineTypes;
|
||||||
use reth_evm_ethereum::execute::EthExecutorProvider;
|
use reth_evm_ethereum::execute::EthExecutorProvider;
|
||||||
use reth_exex_types::FinishedExExHeight;
|
use reth_exex_types::FinishedExExHeight;
|
||||||
@ -196,6 +198,7 @@ mod tests {
|
|||||||
pruner,
|
pruner,
|
||||||
PayloadBuilderHandle::new(tx),
|
PayloadBuilderHandle::new(tx),
|
||||||
TreeConfig::default(),
|
TreeConfig::default(),
|
||||||
|
Box::new(NoopInvalidBlockHook::default()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
52
crates/engine/tree/src/tree/invalid_block_hook.rs
Normal file
52
crates/engine/tree/src/tree/invalid_block_hook.rs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
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<Receipt>,
|
||||||
|
trie_updates: Option<(TrieUpdates, B256)>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> InvalidBlockHook for F
|
||||||
|
where
|
||||||
|
F: Fn(
|
||||||
|
SealedBlockWithSenders,
|
||||||
|
SealedHeader,
|
||||||
|
BlockExecutionOutput<Receipt>,
|
||||||
|
Option<(TrieUpdates, B256)>,
|
||||||
|
) + Send
|
||||||
|
+ Sync,
|
||||||
|
{
|
||||||
|
fn on_invalid_block(
|
||||||
|
&self,
|
||||||
|
block: SealedBlockWithSenders,
|
||||||
|
header: SealedHeader,
|
||||||
|
output: BlockExecutionOutput<Receipt>,
|
||||||
|
trie_updates: Option<(TrieUpdates, B256)>,
|
||||||
|
) {
|
||||||
|
self(block, header, output, trie_updates)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A no-op [`InvalidBlockHook`] that does nothing.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct NoopInvalidBlockHook;
|
||||||
|
|
||||||
|
impl InvalidBlockHook for NoopInvalidBlockHook {
|
||||||
|
fn on_invalid_block(
|
||||||
|
&self,
|
||||||
|
_block: SealedBlockWithSenders,
|
||||||
|
_header: SealedHeader,
|
||||||
|
_output: BlockExecutionOutput<Receipt>,
|
||||||
|
_trie_updates: Option<(TrieUpdates, B256)>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -42,6 +42,7 @@ use reth_stages_api::ControlFlow;
|
|||||||
use reth_trie::{updates::TrieUpdates, HashedPostState};
|
use reth_trie::{updates::TrieUpdates, HashedPostState};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{btree_map, hash_map, BTreeMap, HashMap, HashSet, VecDeque},
|
collections::{btree_map, hash_map, BTreeMap, HashMap, HashSet, VecDeque},
|
||||||
|
fmt::Debug,
|
||||||
ops::Bound,
|
ops::Bound,
|
||||||
sync::{
|
sync::{
|
||||||
mpsc::{Receiver, RecvError, RecvTimeoutError, Sender},
|
mpsc::{Receiver, RecvError, RecvTimeoutError, Sender},
|
||||||
@ -57,9 +58,11 @@ use tokio::sync::{
|
|||||||
use tracing::*;
|
use tracing::*;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
mod invalid_block_hook;
|
||||||
mod metrics;
|
mod metrics;
|
||||||
use crate::{engine::EngineApiRequest, tree::metrics::EngineApiMetrics};
|
use crate::{engine::EngineApiRequest, tree::metrics::EngineApiMetrics};
|
||||||
pub use config::TreeConfig;
|
pub use config::TreeConfig;
|
||||||
|
pub use invalid_block_hook::{InvalidBlockHook, NoopInvalidBlockHook};
|
||||||
|
|
||||||
/// Keeps track of the state of the tree.
|
/// Keeps track of the state of the tree.
|
||||||
///
|
///
|
||||||
@ -481,7 +484,6 @@ pub enum TreeAction {
|
|||||||
///
|
///
|
||||||
/// This type is responsible for processing engine API requests, maintaining the canonical state and
|
/// This type is responsible for processing engine API requests, maintaining the canonical state and
|
||||||
/// emitting events.
|
/// emitting events.
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct EngineApiTreeHandler<P, E, T: EngineTypes> {
|
pub struct EngineApiTreeHandler<P, E, T: EngineTypes> {
|
||||||
provider: P,
|
provider: P,
|
||||||
executor_provider: E,
|
executor_provider: E,
|
||||||
@ -518,6 +520,29 @@ pub struct EngineApiTreeHandler<P, E, T: EngineTypes> {
|
|||||||
config: TreeConfig,
|
config: TreeConfig,
|
||||||
/// Metrics for the engine api.
|
/// Metrics for the engine api.
|
||||||
metrics: EngineApiMetrics,
|
metrics: EngineApiMetrics,
|
||||||
|
/// A bad block hook.
|
||||||
|
invalid_block_hook: Box<dyn InvalidBlockHook>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Debug, E: Debug, T: EngineTypes + Debug> std::fmt::Debug for EngineApiTreeHandler<P, E, T> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("EngineApiTreeHandler")
|
||||||
|
.field("provider", &self.provider)
|
||||||
|
.field("executor_provider", &self.executor_provider)
|
||||||
|
.field("consensus", &self.consensus)
|
||||||
|
.field("payload_validator", &self.payload_validator)
|
||||||
|
.field("state", &self.state)
|
||||||
|
.field("incoming_tx", &self.incoming_tx)
|
||||||
|
.field("persistence", &self.persistence)
|
||||||
|
.field("persistence_state", &self.persistence_state)
|
||||||
|
.field("backfill_sync_state", &self.backfill_sync_state)
|
||||||
|
.field("canonical_in_memory_state", &self.canonical_in_memory_state)
|
||||||
|
.field("payload_builder", &self.payload_builder)
|
||||||
|
.field("config", &self.config)
|
||||||
|
.field("metrics", &self.metrics)
|
||||||
|
.field("invalid_block_hook", &format!("{:p}", self.invalid_block_hook))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P, E, T> EngineApiTreeHandler<P, E, T>
|
impl<P, E, T> EngineApiTreeHandler<P, E, T>
|
||||||
@ -558,9 +583,15 @@ where
|
|||||||
config,
|
config,
|
||||||
metrics: Default::default(),
|
metrics: Default::default(),
|
||||||
incoming_tx,
|
incoming_tx,
|
||||||
|
invalid_block_hook: Box::new(NoopInvalidBlockHook),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the bad block hook.
|
||||||
|
fn set_invalid_block_hook(&mut self, invalid_block_hook: Box<dyn InvalidBlockHook>) {
|
||||||
|
self.invalid_block_hook = invalid_block_hook;
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new [`EngineApiTreeHandler`] instance and spawns it in its
|
/// Creates a new [`EngineApiTreeHandler`] instance and spawns it in its
|
||||||
/// own thread.
|
/// own thread.
|
||||||
///
|
///
|
||||||
@ -576,6 +607,7 @@ where
|
|||||||
payload_builder: PayloadBuilderHandle<T>,
|
payload_builder: PayloadBuilderHandle<T>,
|
||||||
canonical_in_memory_state: CanonicalInMemoryState,
|
canonical_in_memory_state: CanonicalInMemoryState,
|
||||||
config: TreeConfig,
|
config: TreeConfig,
|
||||||
|
invalid_block_hook: Box<dyn InvalidBlockHook>,
|
||||||
) -> (Sender<FromEngine<EngineApiRequest<T>>>, UnboundedReceiver<EngineApiEvent>) {
|
) -> (Sender<FromEngine<EngineApiRequest<T>>>, UnboundedReceiver<EngineApiEvent>) {
|
||||||
let best_block_number = provider.best_block_number().unwrap_or(0);
|
let best_block_number = provider.best_block_number().unwrap_or(0);
|
||||||
let header = provider.sealed_header(best_block_number).ok().flatten().unwrap_or_default();
|
let header = provider.sealed_header(best_block_number).ok().flatten().unwrap_or_default();
|
||||||
@ -593,7 +625,7 @@ where
|
|||||||
header.num_hash(),
|
header.num_hash(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let task = Self::new(
|
let mut task = Self::new(
|
||||||
provider,
|
provider,
|
||||||
executor_provider,
|
executor_provider,
|
||||||
consensus,
|
consensus,
|
||||||
@ -606,6 +638,7 @@ where
|
|||||||
payload_builder,
|
payload_builder,
|
||||||
config,
|
config,
|
||||||
);
|
);
|
||||||
|
task.set_invalid_block_hook(invalid_block_hook);
|
||||||
let incoming = task.incoming_tx.clone();
|
let incoming = task.incoming_tx.clone();
|
||||||
std::thread::Builder::new().name("Tree Task".to_string()).spawn(|| task.run()).unwrap();
|
std::thread::Builder::new().name("Tree Task".to_string()).spawn(|| task.run()).unwrap();
|
||||||
(incoming, outgoing)
|
(incoming, outgoing)
|
||||||
@ -1871,10 +1904,14 @@ where
|
|||||||
let output = executor.execute((&block, U256::MAX).into())?;
|
let output = executor.execute((&block, U256::MAX).into())?;
|
||||||
debug!(target: "engine", elapsed=?exec_time.elapsed(), ?block_number, "Executed block");
|
debug!(target: "engine", elapsed=?exec_time.elapsed(), ?block_number, "Executed block");
|
||||||
|
|
||||||
self.consensus.validate_block_post_execution(
|
if let Err(err) = self.consensus.validate_block_post_execution(
|
||||||
&block,
|
&block,
|
||||||
PostExecutionInput::new(&output.receipts, &output.requests),
|
PostExecutionInput::new(&output.receipts, &output.requests),
|
||||||
)?;
|
) {
|
||||||
|
// call post-block hook
|
||||||
|
self.invalid_block_hook.on_invalid_block(block.seal_slow(), parent_block, output, None);
|
||||||
|
return Err(err.into())
|
||||||
|
}
|
||||||
|
|
||||||
let hashed_state = HashedPostState::from_bundle_state(&output.state.state);
|
let hashed_state = HashedPostState::from_bundle_state(&output.state.state);
|
||||||
|
|
||||||
@ -1882,6 +1919,13 @@ where
|
|||||||
let (state_root, trie_output) =
|
let (state_root, trie_output) =
|
||||||
state_provider.state_root_with_updates(hashed_state.clone())?;
|
state_provider.state_root_with_updates(hashed_state.clone())?;
|
||||||
if state_root != block.state_root {
|
if state_root != block.state_root {
|
||||||
|
// call post-block hook
|
||||||
|
self.invalid_block_hook.on_invalid_block(
|
||||||
|
block.clone().seal_slow(),
|
||||||
|
parent_block,
|
||||||
|
output,
|
||||||
|
Some((trie_output, state_root)),
|
||||||
|
);
|
||||||
return Err(ConsensusError::BodyStateRootDiff(
|
return Err(ConsensusError::BodyStateRootDiff(
|
||||||
GotExpected { got: state_root, expected: block.state_root }.into(),
|
GotExpected { got: state_root, expected: block.state_root }.into(),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -10,7 +10,7 @@ use reth_chainspec::ChainSpec;
|
|||||||
use reth_engine_service::service::{ChainEvent, EngineService};
|
use reth_engine_service::service::{ChainEvent, EngineService};
|
||||||
use reth_engine_tree::{
|
use reth_engine_tree::{
|
||||||
engine::{EngineApiRequest, EngineRequestHandler},
|
engine::{EngineApiRequest, EngineRequestHandler},
|
||||||
tree::TreeConfig,
|
tree::{NoopInvalidBlockHook, TreeConfig},
|
||||||
};
|
};
|
||||||
use reth_engine_util::EngineMessageStreamExt;
|
use reth_engine_util::EngineMessageStreamExt;
|
||||||
use reth_exex::ExExManagerHandle;
|
use reth_exex::ExExManagerHandle;
|
||||||
@ -30,7 +30,7 @@ use reth_rpc_engine_api::{capabilities::EngineCapabilities, EngineApi};
|
|||||||
use reth_rpc_types::{engine::ClientVersionV1, WithOtherFields};
|
use reth_rpc_types::{engine::ClientVersionV1, WithOtherFields};
|
||||||
use reth_tasks::TaskExecutor;
|
use reth_tasks::TaskExecutor;
|
||||||
use reth_tokio_util::EventSender;
|
use reth_tokio_util::EventSender;
|
||||||
use reth_tracing::tracing::{debug, error, info};
|
use reth_tracing::tracing::{debug, error, info, warn};
|
||||||
use tokio::sync::{mpsc::unbounded_channel, oneshot};
|
use tokio::sync::{mpsc::unbounded_channel, oneshot};
|
||||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||||
|
|
||||||
@ -202,6 +202,13 @@ where
|
|||||||
let pruner_events = pruner.events();
|
let pruner_events = pruner.events();
|
||||||
info!(target: "reth::cli", prune_config=?ctx.prune_config().unwrap_or_default(), "Pruner initialized");
|
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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let invalid_block_hook = Box::new(NoopInvalidBlockHook::default());
|
||||||
|
|
||||||
// Configure the consensus engine
|
// Configure the consensus engine
|
||||||
let mut eth_service = EngineService::new(
|
let mut eth_service = EngineService::new(
|
||||||
ctx.consensus(),
|
ctx.consensus(),
|
||||||
@ -216,6 +223,7 @@ where
|
|||||||
pruner,
|
pruner,
|
||||||
ctx.components().payload_builder().clone(),
|
ctx.components().payload_builder().clone(),
|
||||||
TreeConfig::default(),
|
TreeConfig::default(),
|
||||||
|
invalid_block_hook,
|
||||||
);
|
);
|
||||||
|
|
||||||
let event_sender = EventSender::default();
|
let event_sender = EventSender::default();
|
||||||
|
|||||||
@ -34,7 +34,7 @@ use reth_provider::providers::BlockchainProvider;
|
|||||||
use reth_rpc_engine_api::{capabilities::EngineCapabilities, EngineApi};
|
use reth_rpc_engine_api::{capabilities::EngineCapabilities, EngineApi};
|
||||||
use reth_rpc_types::{engine::ClientVersionV1, WithOtherFields};
|
use reth_rpc_types::{engine::ClientVersionV1, WithOtherFields};
|
||||||
use reth_tasks::TaskExecutor;
|
use reth_tasks::TaskExecutor;
|
||||||
use reth_tracing::tracing::{debug, info};
|
use reth_tracing::tracing::{debug, info, warn};
|
||||||
use reth_transaction_pool::TransactionPool;
|
use reth_transaction_pool::TransactionPool;
|
||||||
use tokio::sync::{mpsc::unbounded_channel, oneshot};
|
use tokio::sync::{mpsc::unbounded_channel, oneshot};
|
||||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||||
@ -210,6 +210,10 @@ where
|
|||||||
let max_block = ctx.max_block(network_client.clone()).await?;
|
let max_block = ctx.max_block(network_client.clone()).await?;
|
||||||
let mut hooks = EngineHooks::new();
|
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 = ctx.static_file_producer();
|
||||||
let static_file_producer_events = static_file_producer.lock().events();
|
let static_file_producer_events = static_file_producer.lock().events();
|
||||||
hooks.add(StaticFileHook::new(
|
hooks.add(StaticFileHook::new(
|
||||||
|
|||||||
@ -55,6 +55,7 @@ rand.workspace = true
|
|||||||
derive_more.workspace = true
|
derive_more.workspace = true
|
||||||
toml.workspace = true
|
toml.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
strum = { workspace = true, features = ["derive"] }
|
||||||
|
|
||||||
# io
|
# io
|
||||||
dirs-next = "2.0.0"
|
dirs-next = "2.0.0"
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
//! clap [Args](clap::Args) for debugging purposes
|
//! clap [Args](clap::Args) for debugging purposes
|
||||||
|
|
||||||
use clap::Args;
|
use clap::{
|
||||||
|
builder::{PossibleValue, TypedValueParser},
|
||||||
|
Arg, Args, Command,
|
||||||
|
};
|
||||||
use reth_primitives::B256;
|
use reth_primitives::B256;
|
||||||
use std::path::PathBuf;
|
use std::{collections::HashSet, ffi::OsStr, fmt, path::PathBuf, str::FromStr};
|
||||||
|
use strum::{AsRefStr, EnumIter, IntoStaticStr, ParseError, VariantArray, VariantNames};
|
||||||
|
|
||||||
/// Parameters for debugging purposes
|
/// Parameters for debugging purposes
|
||||||
#[derive(Debug, Clone, Args, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, Args, PartialEq, Eq, Default)]
|
||||||
@ -63,6 +67,208 @@ pub struct DebugArgs {
|
|||||||
/// will be written to specified location.
|
/// will be written to specified location.
|
||||||
#[arg(long = "debug.engine-api-store", help_heading = "Debug", value_name = "PATH")]
|
#[arg(long = "debug.engine-api-store", help_heading = "Debug", value_name = "PATH")]
|
||||||
pub engine_api_store: Option<PathBuf>,
|
pub engine_api_store: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Determines which type of bad block hook to install
|
||||||
|
///
|
||||||
|
/// Example: `witness,prestate`
|
||||||
|
#[arg(long = "debug.invalid-block-hook", help_heading = "Debug", value_parser = InvalidBlockSelectionValueParser::default())]
|
||||||
|
pub invalid_block_hook: Option<InvalidBlockSelection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes the invalid block hooks that should be installed.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Create a [`InvalidBlockSelection`] from a selection.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use reth_node_core::args::{InvalidBlockHook, InvalidBlockSelection};
|
||||||
|
/// let config: InvalidBlockSelection = vec![InvalidBlockHook::Witness].into();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct InvalidBlockSelection(HashSet<InvalidBlockHook>);
|
||||||
|
|
||||||
|
impl InvalidBlockSelection {
|
||||||
|
/// Creates a new _unique_ [`InvalidBlockSelection`] from the given items.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// This will dedupe the selection and remove duplicates while preserving the order.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Create a selection from the [`InvalidBlockHook`] string identifiers
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use reth_node_core::args::{InvalidBlockHook, InvalidBlockSelection};
|
||||||
|
/// let selection = vec!["witness", "prestate", "opcode"];
|
||||||
|
/// let config = InvalidBlockSelection::try_from_selection(selection).unwrap();
|
||||||
|
/// assert_eq!(
|
||||||
|
/// config,
|
||||||
|
/// InvalidBlockSelection::from([
|
||||||
|
/// InvalidBlockHook::Witness,
|
||||||
|
/// InvalidBlockHook::PreState,
|
||||||
|
/// InvalidBlockHook::Opcode
|
||||||
|
/// ])
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Create a unique selection from the [`InvalidBlockHook`] string identifiers
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use reth_node_core::args::{InvalidBlockHook, InvalidBlockSelection};
|
||||||
|
/// let selection = vec!["witness", "prestate", "opcode", "witness", "prestate"];
|
||||||
|
/// let config = InvalidBlockSelection::try_from_selection(selection).unwrap();
|
||||||
|
/// assert_eq!(
|
||||||
|
/// config,
|
||||||
|
/// InvalidBlockSelection::from([
|
||||||
|
/// InvalidBlockHook::Witness,
|
||||||
|
/// InvalidBlockHook::PreState,
|
||||||
|
/// InvalidBlockHook::Opcode
|
||||||
|
/// ])
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
pub fn try_from_selection<I, T>(selection: I) -> Result<Self, T::Error>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = T>,
|
||||||
|
T: TryInto<InvalidBlockHook>,
|
||||||
|
{
|
||||||
|
selection.into_iter().map(TryInto::try_into).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&[InvalidBlockHook]> for InvalidBlockSelection {
|
||||||
|
fn from(s: &[InvalidBlockHook]) -> Self {
|
||||||
|
Self(s.iter().copied().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<InvalidBlockHook>> for InvalidBlockSelection {
|
||||||
|
fn from(s: Vec<InvalidBlockHook>) -> Self {
|
||||||
|
Self(s.into_iter().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> From<[InvalidBlockHook; N]> for InvalidBlockSelection {
|
||||||
|
fn from(s: [InvalidBlockHook; N]) -> Self {
|
||||||
|
Self(s.iter().copied().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromIterator<InvalidBlockHook> for InvalidBlockSelection {
|
||||||
|
fn from_iter<I>(iter: I) -> Self
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = InvalidBlockHook>,
|
||||||
|
{
|
||||||
|
Self(iter.into_iter().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for InvalidBlockSelection {
|
||||||
|
type Err = ParseError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
if s.is_empty() {
|
||||||
|
return Ok(Self(Default::default()))
|
||||||
|
}
|
||||||
|
let hooks = s.split(',').map(str::trim).peekable();
|
||||||
|
Self::try_from_selection(hooks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for InvalidBlockSelection {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "[{}]", self.0.iter().map(|s| s.to_string()).collect::<Vec<_>>().join(", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// clap value parser for [`InvalidBlockSelection`].
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
struct InvalidBlockSelectionValueParser;
|
||||||
|
|
||||||
|
impl TypedValueParser for InvalidBlockSelectionValueParser {
|
||||||
|
type Value = InvalidBlockSelection;
|
||||||
|
|
||||||
|
fn parse_ref(
|
||||||
|
&self,
|
||||||
|
_cmd: &Command,
|
||||||
|
arg: Option<&Arg>,
|
||||||
|
value: &OsStr,
|
||||||
|
) -> Result<Self::Value, clap::Error> {
|
||||||
|
let val =
|
||||||
|
value.to_str().ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
|
||||||
|
val.parse::<InvalidBlockSelection>().map_err(|err| {
|
||||||
|
let arg = arg.map(|a| a.to_string()).unwrap_or_else(|| "...".to_owned());
|
||||||
|
let possible_values = InvalidBlockHook::all_variant_names().to_vec().join(",");
|
||||||
|
let msg = format!(
|
||||||
|
"Invalid value '{val}' for {arg}: {err}.\n [possible values: {possible_values}]"
|
||||||
|
);
|
||||||
|
clap::Error::raw(clap::error::ErrorKind::InvalidValue, msg)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
|
||||||
|
let values = InvalidBlockHook::all_variant_names().iter().map(PossibleValue::new);
|
||||||
|
Some(Box::new(values))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The type of bad block hook to install
|
||||||
|
#[derive(
|
||||||
|
Debug,
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Hash,
|
||||||
|
AsRefStr,
|
||||||
|
IntoStaticStr,
|
||||||
|
VariantNames,
|
||||||
|
VariantArray,
|
||||||
|
EnumIter,
|
||||||
|
)]
|
||||||
|
#[strum(serialize_all = "kebab-case")]
|
||||||
|
pub enum InvalidBlockHook {
|
||||||
|
/// A witness value enum
|
||||||
|
Witness,
|
||||||
|
/// A prestate trace value enum
|
||||||
|
PreState,
|
||||||
|
/// An opcode trace value enum
|
||||||
|
Opcode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for InvalidBlockHook {
|
||||||
|
type Err = ParseError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match s {
|
||||||
|
"witness" => Self::Witness,
|
||||||
|
"prestate" => Self::PreState,
|
||||||
|
"opcode" => Self::Opcode,
|
||||||
|
_ => return Err(ParseError::VariantNotFound),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for InvalidBlockHook {
|
||||||
|
type Error = ParseError;
|
||||||
|
fn try_from(s: &str) -> Result<Self, <Self as TryFrom<&str>>::Error> {
|
||||||
|
FromStr::from_str(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for InvalidBlockHook {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.pad(self.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InvalidBlockHook {
|
||||||
|
/// Returns all variant names of the enum
|
||||||
|
pub const fn all_variant_names() -> &'static [&'static str] {
|
||||||
|
<Self as VariantNames>::VARIANTS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -78,9 +284,63 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_database_args() {
|
fn test_parse_default_debug_args() {
|
||||||
let default_args = DebugArgs::default();
|
let default_args = DebugArgs::default();
|
||||||
let args = CommandParser::<DebugArgs>::parse_from(["reth"]).args;
|
let args = CommandParser::<DebugArgs>::parse_from(["reth"]).args;
|
||||||
assert_eq!(args, default_args);
|
assert_eq!(args, default_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_invalid_block_args() {
|
||||||
|
let expected_args = DebugArgs {
|
||||||
|
invalid_block_hook: Some(InvalidBlockSelection::from([InvalidBlockHook::Witness])),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let args = CommandParser::<DebugArgs>::parse_from([
|
||||||
|
"reth",
|
||||||
|
"--debug.invalid-block-hook",
|
||||||
|
"witness",
|
||||||
|
])
|
||||||
|
.args;
|
||||||
|
assert_eq!(args, expected_args);
|
||||||
|
|
||||||
|
let expected_args = DebugArgs {
|
||||||
|
invalid_block_hook: Some(InvalidBlockSelection::from([
|
||||||
|
InvalidBlockHook::Witness,
|
||||||
|
InvalidBlockHook::PreState,
|
||||||
|
])),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let args = CommandParser::<DebugArgs>::parse_from([
|
||||||
|
"reth",
|
||||||
|
"--debug.invalid-block-hook",
|
||||||
|
"witness,prestate",
|
||||||
|
])
|
||||||
|
.args;
|
||||||
|
assert_eq!(args, expected_args);
|
||||||
|
|
||||||
|
let args = CommandParser::<DebugArgs>::parse_from([
|
||||||
|
"reth",
|
||||||
|
"--debug.invalid-block-hook",
|
||||||
|
"witness,prestate,prestate",
|
||||||
|
])
|
||||||
|
.args;
|
||||||
|
assert_eq!(args, expected_args);
|
||||||
|
|
||||||
|
let args = CommandParser::<DebugArgs>::parse_from([
|
||||||
|
"reth",
|
||||||
|
"--debug.invalid-block-hook",
|
||||||
|
"witness,witness,prestate",
|
||||||
|
])
|
||||||
|
.args;
|
||||||
|
assert_eq!(args, expected_args);
|
||||||
|
|
||||||
|
let args = CommandParser::<DebugArgs>::parse_from([
|
||||||
|
"reth",
|
||||||
|
"--debug.invalid-block-hook",
|
||||||
|
"prestate,witness,prestate",
|
||||||
|
])
|
||||||
|
.args;
|
||||||
|
assert_eq!(args, expected_args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ pub use rpc_state_cache::RpcStateCacheArgs;
|
|||||||
|
|
||||||
/// DebugArgs struct for debugging purposes
|
/// DebugArgs struct for debugging purposes
|
||||||
mod debug;
|
mod debug;
|
||||||
pub use debug::DebugArgs;
|
pub use debug::{DebugArgs, InvalidBlockHook, InvalidBlockSelection};
|
||||||
|
|
||||||
/// DatabaseArgs struct for configuring the database
|
/// DatabaseArgs struct for configuring the database
|
||||||
mod database;
|
mod database;
|
||||||
|
|||||||
Reference in New Issue
Block a user