feat(engine, tree): witness invalid block hook (#10685)

This commit is contained in:
Alexey Shekhirin
2024-09-06 12:56:03 +01:00
committed by GitHub
parent db7834dd51
commit 3ec5d373c1
15 changed files with 393 additions and 100 deletions

36
Cargo.lock generated
View File

@ -2370,6 +2370,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"
@ -5568,6 +5574,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"
@ -6870,7 +6886,10 @@ name = "reth-engine-primitives"
version = "1.0.6"
dependencies = [
"reth-chainspec",
"reth-execution-types",
"reth-payload-primitives",
"reth-primitives",
"reth-trie",
"serde",
]
@ -7306,9 +7325,19 @@ dependencies = [
name = "reth-invalid-block-hooks"
version = "1.0.6"
dependencies = [
"alloy-rlp",
"alloy-rpc-types-debug",
"eyre",
"pretty_assertions",
"reth-chainspec",
"reth-engine-primitives",
"reth-evm",
"reth-primitives",
"reth-provider",
"reth-revm",
"reth-tracing",
"reth-trie",
"serde_json",
]
[[package]]
@ -7591,6 +7620,7 @@ dependencies = [
"reth-engine-util",
"reth-evm",
"reth-exex",
"reth-fs-util",
"reth-invalid-block-hooks",
"reth-network",
"reth-network-api",
@ -11254,6 +11284,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"

View File

@ -12,6 +12,20 @@ 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-trie.workspace = true
reth-revm.workspace = true
reth-tracing.workspace = true
reth-trie = { workspace = true, features = ["serde"] }
# alloy
alloy-rlp.workspace = true
alloy-rpc-types-debug.workspace = true
# misc
eyre.workspace = true
pretty_assertions = "1.4"
serde_json.workspace = true

View File

@ -2,4 +2,4 @@
mod witness;
pub use witness::witness;
pub use witness::InvalidBlockWitnessHook;

View File

@ -1,13 +1,226 @@
use reth_primitives::{Receipt, SealedBlockWithSenders, SealedHeader, B256};
use reth_provider::BlockExecutionOutput;
use reth_trie::updates::TrieUpdates;
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::{
system_calls::{apply_beacon_root_contract_call, apply_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, 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.
pub fn witness(
_block: &SealedBlockWithSenders,
_header: &SealedHeader,
_output: &BlockExecutionOutput<Receipt>,
_trie_updates: Option<(&TrieUpdates, B256)>,
) {
unimplemented!("witness generation is not supported")
#[derive(Debug)]
pub struct InvalidBlockWitnessHook<P, EvmConfig> {
/// 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,
}
impl<P, EvmConfig> InvalidBlockWitnessHook<P, EvmConfig> {
/// 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<P, EvmConfig> InvalidBlockWitnessHook<P, EvmConfig>
where
P: StateProviderFactory + ChainSpecProvider<ChainSpec = ChainSpec> + Send + Sync + 'static,
EvmConfig: ConfigureEvm,
{
fn on_invalid_block(
&self,
parent_header: &SealedHeader,
block: &SealedBlockWithSenders,
output: &BlockExecutionOutput<Receipt>,
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(
self.provider.state_by_block_hash(parent_header.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();
self.evm_config.fill_cfg_and_block_env(
&mut cfg,
&mut block_env,
&self.provider.chain_spec(),
block.header(),
U256::MAX,
);
// Setup EVM
let mut evm = self.evm_config.evm_with_env(
&mut db,
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(),
block.timestamp,
block.number,
block.parent_beacon_block_root,
&mut evm,
)?;
apply_blockhashes_contract_call(
&self.evm_config,
&self.provider.chain_spec(),
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.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);
// Take the bundle state
let bundle_state = db.take_bundle();
// Initialize a map of preimages.
let mut state_preimages = HashMap::new();
// 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);
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, 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());
}
}
}
// 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.clone())?;
// Write the witness to the output directory.
let mut file = File::options()
.write(true)
.create_new(true)
.open(self.output_directory.join(format!("{}_{}.json", block.number, block.hash())))?;
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 {
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");
}
// 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 {
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 &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");
}
}
Ok(())
}
/// Saves the diff of two values into a file with the given name in the output directory.
fn save_diff<T: PartialEq + Debug>(
&self,
filename: String,
original: &T,
new: &T,
) -> eyre::Result<PathBuf> {
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<P, EvmConfig> InvalidBlockHook for InvalidBlockWitnessHook<P, EvmConfig>
where
P: StateProviderFactory + ChainSpecProvider<ChainSpec = ChainSpec> + Send + Sync + 'static,
EvmConfig: ConfigureEvm,
{
fn on_invalid_block(
&self,
parent_header: &SealedHeader,
block: &SealedBlockWithSenders,
output: &BlockExecutionOutput<Receipt>,
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");
}
}
}

View File

@ -13,7 +13,10 @@ 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
serde.workspace = true

View File

@ -0,0 +1,36 @@
use reth_execution_types::BlockExecutionOutput;
use reth_primitives::{Receipt, SealedBlockWithSenders, SealedHeader, B256};
use reth_trie::updates::TrieUpdates;
/// An invalid block hook.
pub trait InvalidBlockHook: Send + Sync {
/// Invoked when an invalid block is encountered.
fn on_invalid_block(
&self,
parent_header: &SealedHeader,
block: &SealedBlockWithSenders,
output: &BlockExecutionOutput<Receipt>,
trie_updates: Option<(&TrieUpdates, B256)>,
);
}
impl<F> InvalidBlockHook for F
where
F: Fn(
&SealedHeader,
&SealedBlockWithSenders,
&BlockExecutionOutput<Receipt>,
Option<(&TrieUpdates, B256)>,
) + Send
+ Sync,
{
fn on_invalid_block(
&self,
parent_header: &SealedHeader,
block: &SealedBlockWithSenders,
output: &BlockExecutionOutput<Receipt>,
trie_updates: Option<(&TrieUpdates, B256)>,
) {
self(parent_header, block, output, trie_updates)
}
}

View File

@ -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,

View File

@ -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<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]
@ -43,8 +11,8 @@ pub struct NoopInvalidBlockHook;
impl InvalidBlockHook for NoopInvalidBlockHook {
fn on_invalid_block(
&self,
_parent_header: &SealedHeader,
_block: &SealedBlockWithSenders,
_header: &SealedHeader,
_output: &BlockExecutionOutput<Receipt>,
_trie_updates: Option<(&TrieUpdates, B256)>,
) {
@ -63,13 +31,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<Receipt>,
trie_updates: Option<(&TrieUpdates, B256)>,
) {
for hook in &self.0 {
hook.on_invalid_block(block, header, output, trie_updates);
hook.on_invalid_block(parent_header, block, output, trie_updates);
}
}
}

View File

@ -63,7 +63,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.
///
@ -490,7 +491,7 @@ pub struct EngineApiTreeHandler<P, E, T: EngineTypes> {
config: TreeConfig,
/// Metrics for the engine api.
metrics: EngineApiMetrics,
/// A bad block hook.
/// An invalid block hook.
invalid_block_hook: Box<dyn InvalidBlockHook>,
}
@ -2156,8 +2157,8 @@ where
) {
// call post-block hook
self.invalid_block_hook.on_invalid_block(
&block.seal_slow(),
&parent_block,
&block.seal_slow(),
&output,
None,
);
@ -2172,8 +2173,8 @@ where
if state_root != block.state_root {
// call post-block hook
self.invalid_block_hook.on_invalid_block(
&block.clone().seal_slow(),
&parent_block,
&block.clone().seal_slow(),
&output,
Some((&trie_output, state_root)),
);

View File

@ -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-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-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-eth-types.workspace = true
reth-network-api.workspace = true
reth-node-metrics.workspace = true
reth-payload-builder.workspace = true
reth-payload-validator.workspace = true
reth-engine-service.workspace = true
reth-primitives.workspace = true
reth-provider.workspace = true
reth-prune.workspace = true
reth-rpc-builder.workspace = true
reth-rpc-engine-api.workspace = true
reth-rpc-eth-types.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

View File

@ -17,6 +17,8 @@ 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_invalid_block_hooks::InvalidBlockWitnessHook;
use reth_network_p2p::headers::client::HeadersClient;
use reth_node_api::{FullNodeTypes, NodeTypes, NodeTypesWithDB};
use reth_node_core::{
@ -35,8 +37,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;
@ -837,17 +840,34 @@ where
pub const fn components(&self) -> &CB::Components {
&self.node_adapter().components
}
}
impl<T, CB> LaunchContextWith<Attached<WithConfigs, WithComponents<T, CB>>>
where
T: FullNodeTypes<
Provider: WithTree + StateProviderFactory + ChainSpecProvider<ChainSpec = ChainSpec>,
Types: NodeTypes<ChainSpec = ChainSpec>,
>,
CB: NodeComponentsBuilder<T>,
{
/// Returns the [`InvalidBlockHook`] to use for the node.
pub fn invalid_block_hook(&self) -> eyre::Result<Box<dyn InvalidBlockHook>> {
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());
fs::create_dir_all(&output_directory)?;
Ok(match hook {
reth_node_core::args::InvalidBlockHook::Witness => {
Box::new(reth_invalid_block_hooks::witness) as Box<dyn InvalidBlockHook>
Box::new(InvalidBlockWitnessHook::new(
output_directory,
self.blockchain_db().clone(),
self.components().evm_config().clone(),
)) as Box<dyn InvalidBlockHook>
}
reth_node_core::args::InvalidBlockHook::PreState |
reth_node_core::args::InvalidBlockHook::Opcode => {

View File

@ -33,7 +33,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(),

View File

@ -36,7 +36,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(

View File

@ -343,6 +343,13 @@ impl<D> ChainPath<D> {
pub fn jwt(&self) -> PathBuf {
self.data_dir().join("jwt.hex")
}
/// Returns the path to the invalid block hooks directory for this chain.
///
/// `<DIR>/<CHAIN_ID>/invalid_block_hooks`
pub fn invalid_block_hooks(&self) -> PathBuf {
self.data_dir().join("invalid_block_hooks")
}
}
impl<D> AsRef<Path> for ChainPath<D> {

View File

@ -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<StateProviderBox>;
/// 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<StateProviderBox>;