mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-04-30 03:01:58 -04:00
Compare commits
1 Commits
yk/pr3-fre
...
performanc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c04d1abe1 |
6
.github/workflows/hive.yml
vendored
6
.github/workflows/hive.yml
vendored
@@ -24,8 +24,7 @@ jobs:
|
||||
prepare-hive:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
timeout-minutes: 45
|
||||
runs-on:
|
||||
group: Reth
|
||||
runs-on: depot-ubuntu-latest-16
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Checkout hive tests
|
||||
@@ -179,8 +178,7 @@ jobs:
|
||||
- prepare-reth
|
||||
- prepare-hive
|
||||
name: run ${{ matrix.scenario.sim }}${{ matrix.scenario.limit && format(' - {0}', matrix.scenario.limit) }}
|
||||
runs-on:
|
||||
group: Reth
|
||||
runs-on: depot-ubuntu-latest-16
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -277,7 +277,7 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: taiki-e/cache-cargo-install-action@v3
|
||||
- uses: taiki-e/cache-cargo-install-action@v2
|
||||
with:
|
||||
tool: zepter
|
||||
- name: Eagerly pull dependencies
|
||||
|
||||
20
.github/workflows/unit.yml
vendored
20
.github/workflows/unit.yml
vendored
@@ -99,24 +99,6 @@ jobs:
|
||||
cache-on-failure: true
|
||||
- run: cargo nextest run --release -p ef-tests --features "asm-keccak ef-tests"
|
||||
|
||||
rocksdb:
|
||||
name: rocksdb tests
|
||||
runs-on: depot-ubuntu-latest-4
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- name: Run RocksDB tests
|
||||
run: cargo nextest run -p reth-provider -p reth-stages --features "rocksdb" --locked
|
||||
|
||||
doc:
|
||||
name: doc tests
|
||||
runs-on: depot-ubuntu-latest
|
||||
@@ -138,7 +120,7 @@ jobs:
|
||||
name: unit success
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
needs: [test, state, rocksdb, doc]
|
||||
needs: [test, state, doc]
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Decide whether the needed jobs succeeded or failed
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,9 +12,6 @@ target/
|
||||
# Generated by Intellij-based IDEs.
|
||||
.idea
|
||||
|
||||
# ck-search metadata
|
||||
.ck
|
||||
|
||||
# Generated by MacOS
|
||||
.DS_Store
|
||||
|
||||
|
||||
913
Cargo.lock
generated
913
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
60
Cargo.toml
60
Cargo.toml
@@ -497,33 +497,33 @@ alloy-trie = { version = "0.9.1", default-features = false }
|
||||
|
||||
alloy-hardforks = "0.4.5"
|
||||
|
||||
alloy-consensus = { version = "1.2.1", default-features = false }
|
||||
alloy-contract = { version = "1.2.1", default-features = false }
|
||||
alloy-eips = { version = "1.2.1", default-features = false }
|
||||
alloy-genesis = { version = "1.2.1", default-features = false }
|
||||
alloy-json-rpc = { version = "1.2.1", default-features = false }
|
||||
alloy-network = { version = "1.2.1", default-features = false }
|
||||
alloy-network-primitives = { version = "1.2.1", default-features = false }
|
||||
alloy-provider = { version = "1.2.1", features = ["reqwest", "debug-api"], default-features = false }
|
||||
alloy-pubsub = { version = "1.2.1", default-features = false }
|
||||
alloy-rpc-client = { version = "1.2.1", default-features = false }
|
||||
alloy-rpc-types = { version = "1.2.1", features = ["eth"], default-features = false }
|
||||
alloy-rpc-types-admin = { version = "1.2.1", default-features = false }
|
||||
alloy-rpc-types-anvil = { version = "1.2.1", default-features = false }
|
||||
alloy-rpc-types-beacon = { version = "1.2.1", default-features = false }
|
||||
alloy-rpc-types-debug = { version = "1.2.1", default-features = false }
|
||||
alloy-rpc-types-engine = { version = "1.2.1", default-features = false }
|
||||
alloy-rpc-types-eth = { version = "1.2.1", default-features = false }
|
||||
alloy-rpc-types-mev = { version = "1.2.1", default-features = false }
|
||||
alloy-rpc-types-trace = { version = "1.2.1", default-features = false }
|
||||
alloy-rpc-types-txpool = { version = "1.2.1", default-features = false }
|
||||
alloy-serde = { version = "1.2.1", default-features = false }
|
||||
alloy-signer = { version = "1.2.1", default-features = false }
|
||||
alloy-signer-local = { version = "1.2.1", default-features = false }
|
||||
alloy-transport = { version = "1.2.1" }
|
||||
alloy-transport-http = { version = "1.2.1", features = ["reqwest-rustls-tls"], default-features = false }
|
||||
alloy-transport-ipc = { version = "1.2.1", default-features = false }
|
||||
alloy-transport-ws = { version = "1.2.1", default-features = false }
|
||||
alloy-consensus = { version = "1.1.3", default-features = false }
|
||||
alloy-contract = { version = "1.1.3", default-features = false }
|
||||
alloy-eips = { version = "1.1.3", default-features = false }
|
||||
alloy-genesis = { version = "1.1.3", default-features = false }
|
||||
alloy-json-rpc = { version = "1.1.3", default-features = false }
|
||||
alloy-network = { version = "1.1.3", default-features = false }
|
||||
alloy-network-primitives = { version = "1.1.3", default-features = false }
|
||||
alloy-provider = { version = "1.1.3", features = ["reqwest", "debug-api"], default-features = false }
|
||||
alloy-pubsub = { version = "1.1.3", default-features = false }
|
||||
alloy-rpc-client = { version = "1.1.3", default-features = false }
|
||||
alloy-rpc-types = { version = "1.1.3", features = ["eth"], default-features = false }
|
||||
alloy-rpc-types-admin = { version = "1.1.3", default-features = false }
|
||||
alloy-rpc-types-anvil = { version = "1.1.3", default-features = false }
|
||||
alloy-rpc-types-beacon = { version = "1.1.3", default-features = false }
|
||||
alloy-rpc-types-debug = { version = "1.1.3", default-features = false }
|
||||
alloy-rpc-types-engine = { version = "1.1.3", default-features = false }
|
||||
alloy-rpc-types-eth = { version = "1.1.3", default-features = false }
|
||||
alloy-rpc-types-mev = { version = "1.1.3", default-features = false }
|
||||
alloy-rpc-types-trace = { version = "1.1.3", default-features = false }
|
||||
alloy-rpc-types-txpool = { version = "1.1.3", default-features = false }
|
||||
alloy-serde = { version = "1.1.3", default-features = false }
|
||||
alloy-signer = { version = "1.1.3", default-features = false }
|
||||
alloy-signer-local = { version = "1.1.3", default-features = false }
|
||||
alloy-transport = { version = "1.1.3" }
|
||||
alloy-transport-http = { version = "1.1.3", features = ["reqwest-rustls-tls"], default-features = false }
|
||||
alloy-transport-ipc = { version = "1.1.3", default-features = false }
|
||||
alloy-transport-ws = { version = "1.1.3", default-features = false }
|
||||
|
||||
# op
|
||||
alloy-op-evm = { version = "0.25.0", default-features = false }
|
||||
@@ -548,7 +548,6 @@ bytes = { version = "1.5", default-features = false }
|
||||
brotli = "8"
|
||||
cfg-if = "1.0"
|
||||
clap = "4"
|
||||
color-eyre = "0.6"
|
||||
dashmap = "6.0"
|
||||
derive_more = { version = "2", default-features = false, features = ["full"] }
|
||||
dirs-next = "2.0.0"
|
||||
@@ -588,7 +587,6 @@ url = { version = "2.3", default-features = false }
|
||||
zstd = "0.13"
|
||||
byteorder = "1"
|
||||
mini-moka = "0.10"
|
||||
moka = "0.12"
|
||||
tar-no-std = { version = "0.3.2", default-features = false }
|
||||
miniz_oxide = { version = "0.8.4", default-features = false }
|
||||
chrono = "0.4.41"
|
||||
@@ -684,7 +682,6 @@ ethereum_ssz = "0.9.0"
|
||||
ethereum_ssz_derive = "0.9.0"
|
||||
|
||||
# allocators
|
||||
jemalloc_pprof = { version = "0.8", default-features = false }
|
||||
tikv-jemalloc-ctl = "0.6"
|
||||
tikv-jemallocator = "0.6"
|
||||
tracy-client = "0.18.0"
|
||||
@@ -695,7 +692,7 @@ ahash = "0.8"
|
||||
anyhow = "1.0"
|
||||
bindgen = { version = "0.71", default-features = false }
|
||||
block-padding = "0.3.2"
|
||||
cc = "1.2.15"
|
||||
cc = "=1.2.15"
|
||||
cipher = "0.4.3"
|
||||
comfy-table = "7.0"
|
||||
concat-kdf = "0.1.0"
|
||||
@@ -732,7 +729,6 @@ socket2 = { version = "0.5", default-features = false }
|
||||
sysinfo = { version = "0.33", default-features = false }
|
||||
tracing-journald = "0.3"
|
||||
tracing-logfmt = "0.3.3"
|
||||
tracing-samply = "0.1"
|
||||
tracing-subscriber = { version = "0.3", default-features = false }
|
||||
triehash = "0.8"
|
||||
typenum = "1.15.0"
|
||||
|
||||
@@ -18,7 +18,7 @@ FROM chef AS builder
|
||||
COPY --from=planner /app/recipe.json recipe.json
|
||||
|
||||
# Build profile, release by default
|
||||
ARG BUILD_PROFILE=maxperf
|
||||
ARG BUILD_PROFILE=release
|
||||
ENV BUILD_PROFILE=$BUILD_PROFILE
|
||||
|
||||
# Extra Cargo flags
|
||||
|
||||
@@ -14,7 +14,7 @@ RUN cargo chef prepare --recipe-path recipe.json
|
||||
FROM chef AS builder
|
||||
COPY --from=planner /app/recipe.json recipe.json
|
||||
|
||||
ARG BUILD_PROFILE=maxperf
|
||||
ARG BUILD_PROFILE=release
|
||||
ENV BUILD_PROFILE=$BUILD_PROFILE
|
||||
|
||||
ARG RUSTFLAGS=""
|
||||
|
||||
@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2022-2026 Reth Contributors
|
||||
Copyright 2022-2025 Reth Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2022-2026 Reth Contributors
|
||||
Copyright (c) 2022-2025 Reth Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -96,9 +96,13 @@ impl BenchmarkRunner {
|
||||
&from_block.to_string(),
|
||||
"--to",
|
||||
&to_block.to_string(),
|
||||
"--wait-time=0ms", // We just want to warm up caches.
|
||||
]);
|
||||
|
||||
// Add wait-time argument if provided
|
||||
if let Some(ref wait_time) = self.wait_time {
|
||||
cmd.args(["--wait-time", wait_time]);
|
||||
}
|
||||
|
||||
cmd.env("RUST_LOG_STYLE", "never")
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
|
||||
@@ -164,42 +164,12 @@ pub(crate) struct Args {
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
pub reth_args: Vec<String>,
|
||||
|
||||
/// Comma-separated list of features to enable during reth compilation (applied to both builds)
|
||||
/// Comma-separated list of features to enable during reth compilation
|
||||
///
|
||||
/// Example: `jemalloc,asm-keccak`
|
||||
#[arg(long, value_name = "FEATURES", default_value = "jemalloc,asm-keccak")]
|
||||
pub features: String,
|
||||
|
||||
/// Comma-separated list of features to enable only for baseline build (overrides --features)
|
||||
///
|
||||
/// Example: `--baseline-features jemalloc`
|
||||
#[arg(long, value_name = "FEATURES")]
|
||||
pub baseline_features: Option<String>,
|
||||
|
||||
/// Comma-separated list of features to enable only for feature build (overrides --features)
|
||||
///
|
||||
/// Example: `--feature-features jemalloc,asm-keccak`
|
||||
#[arg(long, value_name = "FEATURES")]
|
||||
pub feature_features: Option<String>,
|
||||
|
||||
/// RUSTFLAGS to use for both baseline and feature builds
|
||||
///
|
||||
/// Example: `--rustflags "-C target-cpu=native"`
|
||||
#[arg(long, value_name = "FLAGS", default_value = "-C target-cpu=native")]
|
||||
pub rustflags: String,
|
||||
|
||||
/// RUSTFLAGS to use only for baseline build (overrides --rustflags)
|
||||
///
|
||||
/// Example: `--baseline-rustflags "-C target-cpu=native -C lto"`
|
||||
#[arg(long, value_name = "FLAGS")]
|
||||
pub baseline_rustflags: Option<String>,
|
||||
|
||||
/// RUSTFLAGS to use only for feature build (overrides --rustflags)
|
||||
///
|
||||
/// Example: `--feature-rustflags "-C target-cpu=native -C lto"`
|
||||
#[arg(long, value_name = "FLAGS")]
|
||||
pub feature_rustflags: Option<String>,
|
||||
|
||||
/// Disable automatic --debug.startup-sync-state-idle flag for specific runs.
|
||||
/// Can be "baseline", "feature", or "all".
|
||||
/// By default, the flag is passed to warmup, baseline, and feature runs.
|
||||
@@ -358,6 +328,7 @@ pub(crate) async fn run_comparison(args: Args, _ctx: CliContext) -> Result<()> {
|
||||
git_manager.repo_root().to_string(),
|
||||
output_dir.clone(),
|
||||
git_manager.clone(),
|
||||
args.features.clone(),
|
||||
)?;
|
||||
// Initialize node manager
|
||||
let mut node_manager = NodeManager::new(&args);
|
||||
@@ -477,18 +448,6 @@ async fn run_compilation_phase(
|
||||
let ref_type = ref_types[i];
|
||||
let commit = &ref_commits[git_ref];
|
||||
|
||||
// Get per-build features and rustflags
|
||||
let features = match ref_type {
|
||||
"baseline" => args.baseline_features.as_ref().unwrap_or(&args.features),
|
||||
"feature" => args.feature_features.as_ref().unwrap_or(&args.features),
|
||||
_ => &args.features,
|
||||
};
|
||||
let rustflags = match ref_type {
|
||||
"baseline" => args.baseline_rustflags.as_ref().unwrap_or(&args.rustflags),
|
||||
"feature" => args.feature_rustflags.as_ref().unwrap_or(&args.rustflags),
|
||||
_ => &args.rustflags,
|
||||
};
|
||||
|
||||
info!(
|
||||
"Compiling {} binary for reference: {} (commit: {})",
|
||||
ref_type,
|
||||
@@ -500,7 +459,7 @@ async fn run_compilation_phase(
|
||||
git_manager.switch_ref(git_ref)?;
|
||||
|
||||
// Compile reth (with caching)
|
||||
compilation_manager.compile_reth(commit, is_optimism, features, rustflags)?;
|
||||
compilation_manager.compile_reth(commit, is_optimism)?;
|
||||
|
||||
info!("Completed compilation for {} reference", ref_type);
|
||||
}
|
||||
@@ -512,7 +471,6 @@ async fn run_compilation_phase(
|
||||
Ok((baseline_commit, feature_commit))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
/// Run warmup phase to warm up caches before benchmarking
|
||||
async fn run_warmup_phase(
|
||||
git_manager: &GitManager,
|
||||
@@ -522,15 +480,9 @@ async fn run_warmup_phase(
|
||||
args: &Args,
|
||||
is_optimism: bool,
|
||||
baseline_commit: &str,
|
||||
starting_tip: u64,
|
||||
) -> Result<()> {
|
||||
info!("=== Running warmup phase ===");
|
||||
|
||||
// Unwind to starting block minus warmup blocks, so we end up back at starting_tip
|
||||
let warmup_blocks = args.get_warmup_blocks();
|
||||
let unwind_target = starting_tip.saturating_sub(warmup_blocks);
|
||||
node_manager.unwind_to_block(unwind_target).await?;
|
||||
|
||||
// Use baseline for warmup
|
||||
let warmup_ref = &args.baseline_ref;
|
||||
|
||||
@@ -562,6 +514,9 @@ async fn run_warmup_phase(
|
||||
let current_tip = node_manager.wait_for_node_ready_and_get_tip().await?;
|
||||
info!("Warmup node is ready at tip: {}", current_tip);
|
||||
|
||||
// Store the tip we'll unwind back to
|
||||
let original_tip = current_tip;
|
||||
|
||||
// Clear filesystem caches before warmup run only (unless disabled)
|
||||
if args.no_clear_cache {
|
||||
info!("Skipping filesystem cache clearing (--no-clear-cache flag set)");
|
||||
@@ -572,9 +527,12 @@ async fn run_warmup_phase(
|
||||
// Run warmup to warm up caches
|
||||
benchmark_runner.run_warmup(current_tip).await?;
|
||||
|
||||
// Stop node after warmup
|
||||
// Stop node before unwinding (node must be stopped to release database lock)
|
||||
node_manager.stop_node(&mut node_process).await?;
|
||||
|
||||
// Unwind back to starting block after warmup
|
||||
node_manager.unwind_to_block(original_tip).await?;
|
||||
|
||||
info!("Warmup phase completed");
|
||||
Ok(())
|
||||
}
|
||||
@@ -596,27 +554,6 @@ async fn run_benchmark_workflow(
|
||||
let (baseline_commit, feature_commit) =
|
||||
run_compilation_phase(git_manager, compilation_manager, args, is_optimism).await?;
|
||||
|
||||
// Switch to baseline reference and get the starting tip
|
||||
git_manager.switch_ref(&args.baseline_ref)?;
|
||||
let binary_path =
|
||||
compilation_manager.get_cached_binary_path_for_commit(&baseline_commit, is_optimism);
|
||||
if !binary_path.exists() {
|
||||
return Err(eyre!(
|
||||
"Cached baseline binary not found at {:?}. Compilation phase should have created it.",
|
||||
binary_path
|
||||
));
|
||||
}
|
||||
|
||||
// Start node briefly to get the current tip, then stop it
|
||||
info!("=== Determining initial block height ===");
|
||||
let additional_args = args.build_additional_args("baseline", args.baseline_args.as_ref());
|
||||
let (mut node_process, _) = node_manager
|
||||
.start_node(&binary_path, &args.baseline_ref, "baseline", &additional_args)
|
||||
.await?;
|
||||
let starting_tip = node_manager.wait_for_node_ready_and_get_tip().await?;
|
||||
info!("Node starting tip: {}", starting_tip);
|
||||
node_manager.stop_node(&mut node_process).await?;
|
||||
|
||||
// Run warmup phase before benchmarking (skip if warmup_blocks is 0)
|
||||
if args.get_warmup_blocks() > 0 {
|
||||
run_warmup_phase(
|
||||
@@ -627,7 +564,6 @@ async fn run_benchmark_workflow(
|
||||
args,
|
||||
is_optimism,
|
||||
&baseline_commit,
|
||||
starting_tip,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
@@ -643,10 +579,6 @@ async fn run_benchmark_workflow(
|
||||
let commit = commits[i];
|
||||
info!("=== Processing {} reference: {} ===", ref_type, git_ref);
|
||||
|
||||
// Unwind to starting block minus benchmark blocks, so we end up back at starting_tip
|
||||
let unwind_target = starting_tip.saturating_sub(args.blocks);
|
||||
node_manager.unwind_to_block(unwind_target).await?;
|
||||
|
||||
// Switch to target reference
|
||||
git_manager.switch_ref(git_ref)?;
|
||||
|
||||
@@ -683,11 +615,14 @@ async fn run_benchmark_workflow(
|
||||
let current_tip = node_manager.wait_for_node_ready_and_get_tip().await?;
|
||||
info!("Node is ready at tip: {}", current_tip);
|
||||
|
||||
// Store the tip we'll unwind back to
|
||||
let original_tip = current_tip;
|
||||
|
||||
// Calculate benchmark range
|
||||
// Note: reth-bench has an off-by-one error where it consumes the first block
|
||||
// of the range, so we add 1 to compensate and get exactly args.blocks blocks
|
||||
let from_block = current_tip;
|
||||
let to_block = current_tip + args.blocks;
|
||||
let from_block = original_tip;
|
||||
let to_block = original_tip + args.blocks;
|
||||
|
||||
// Run benchmark
|
||||
let output_dir = comparison_generator.get_ref_output_dir(ref_type);
|
||||
@@ -704,6 +639,9 @@ async fn run_benchmark_workflow(
|
||||
// Stop node
|
||||
node_manager.stop_node(&mut node_process).await?;
|
||||
|
||||
// Unwind back to original tip
|
||||
node_manager.unwind_to_block(original_tip).await?;
|
||||
|
||||
// Store results for comparison
|
||||
comparison_generator.add_ref_results(ref_type, &output_dir)?;
|
||||
|
||||
|
||||
@@ -39,8 +39,7 @@ pub(crate) struct BenchmarkResults {
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub(crate) struct CombinedLatencyRow {
|
||||
pub block_number: u64,
|
||||
#[serde(default)]
|
||||
pub transaction_count: Option<u64>,
|
||||
pub transaction_count: u64,
|
||||
pub gas_used: u64,
|
||||
pub new_payload_latency: u128,
|
||||
}
|
||||
@@ -49,8 +48,7 @@ pub(crate) struct CombinedLatencyRow {
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub(crate) struct TotalGasRow {
|
||||
pub block_number: u64,
|
||||
#[serde(default)]
|
||||
pub transaction_count: Option<u64>,
|
||||
pub transaction_count: u64,
|
||||
pub gas_used: u64,
|
||||
pub time: u128,
|
||||
}
|
||||
@@ -127,8 +125,7 @@ pub(crate) struct ComparisonSummary {
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct BlockComparison {
|
||||
pub block_number: u64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub transaction_count: Option<u64>,
|
||||
pub transaction_count: u64,
|
||||
pub gas_used: u64,
|
||||
pub baseline_new_payload_latency: u128,
|
||||
pub feature_new_payload_latency: u128,
|
||||
|
||||
@@ -13,6 +13,7 @@ pub(crate) struct CompilationManager {
|
||||
repo_root: String,
|
||||
output_dir: PathBuf,
|
||||
git_manager: GitManager,
|
||||
features: String,
|
||||
}
|
||||
|
||||
impl CompilationManager {
|
||||
@@ -21,8 +22,9 @@ impl CompilationManager {
|
||||
repo_root: String,
|
||||
output_dir: PathBuf,
|
||||
git_manager: GitManager,
|
||||
features: String,
|
||||
) -> Result<Self> {
|
||||
Ok(Self { repo_root, output_dir, git_manager })
|
||||
Ok(Self { repo_root, output_dir, git_manager, features })
|
||||
}
|
||||
|
||||
/// Detect if the RPC endpoint is an Optimism chain
|
||||
@@ -66,13 +68,7 @@ impl CompilationManager {
|
||||
}
|
||||
|
||||
/// Compile reth using cargo build and cache the binary
|
||||
pub(crate) fn compile_reth(
|
||||
&self,
|
||||
commit: &str,
|
||||
is_optimism: bool,
|
||||
features: &str,
|
||||
rustflags: &str,
|
||||
) -> Result<()> {
|
||||
pub(crate) fn compile_reth(&self, commit: &str, is_optimism: bool) -> Result<()> {
|
||||
// Validate that current git commit matches the expected commit
|
||||
let current_commit = self.git_manager.get_current_commit()?;
|
||||
if current_commit != commit {
|
||||
@@ -104,8 +100,9 @@ impl CompilationManager {
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.arg("build").arg("--profile").arg("profiling");
|
||||
|
||||
cmd.arg("--features").arg(features);
|
||||
info!("Using features: {features}");
|
||||
// Add features
|
||||
cmd.arg("--features").arg(&self.features);
|
||||
info!("Using features: {}", self.features);
|
||||
|
||||
// Add bin-specific arguments for optimism
|
||||
if is_optimism {
|
||||
@@ -117,11 +114,11 @@ impl CompilationManager {
|
||||
|
||||
cmd.current_dir(&self.repo_root);
|
||||
|
||||
// Set RUSTFLAGS
|
||||
cmd.env("RUSTFLAGS", rustflags);
|
||||
info!("Using RUSTFLAGS: {rustflags}");
|
||||
// Set RUSTFLAGS for native CPU optimization
|
||||
cmd.env("RUSTFLAGS", "-C target-cpu=native");
|
||||
|
||||
info!("Compiling {binary_name} with {cmd:?}");
|
||||
// Debug log the command
|
||||
debug!("Executing cargo command: {:?}", cmd);
|
||||
|
||||
let output = cmd.output().wrap_err("Failed to execute cargo build command")?;
|
||||
|
||||
@@ -230,7 +227,8 @@ impl CompilationManager {
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.args(["install", "--locked", "samply"]);
|
||||
|
||||
info!("Installing samply with {cmd:?}");
|
||||
// Debug log the command
|
||||
debug!("Executing cargo command: {:?}", cmd);
|
||||
|
||||
let output = cmd.output().wrap_err("Failed to execute cargo install samply command")?;
|
||||
|
||||
@@ -305,7 +303,8 @@ impl CompilationManager {
|
||||
let mut cmd = Command::new("make");
|
||||
cmd.arg("install-reth-bench").current_dir(&self.repo_root);
|
||||
|
||||
info!("Compiling reth-bench with {cmd:?}");
|
||||
// Debug log the command
|
||||
debug!("Executing make command: {:?}", cmd);
|
||||
|
||||
let output = cmd.output().wrap_err("Failed to execute make install-reth-bench command")?;
|
||||
|
||||
|
||||
@@ -211,11 +211,6 @@ impl NodeManager {
|
||||
cmd.arg("--");
|
||||
cmd.args(reth_args);
|
||||
|
||||
// Enable tracing-samply
|
||||
if supports_samply_flags(&reth_args[0]) {
|
||||
cmd.arg("--log.samply");
|
||||
}
|
||||
|
||||
// Set environment variable to disable log styling
|
||||
cmd.env("RUST_LOG_STYLE", "never");
|
||||
|
||||
@@ -408,7 +403,7 @@ impl NodeManager {
|
||||
|
||||
/// Stop the reth node gracefully
|
||||
pub(crate) async fn stop_node(&self, child: &mut tokio::process::Child) -> Result<()> {
|
||||
let pid = child.id().ok_or_eyre("Child process ID should be available")?;
|
||||
let pid = child.id().expect("Child process ID should be available");
|
||||
|
||||
// Check if the process has already exited
|
||||
match child.try_wait() {
|
||||
@@ -557,16 +552,3 @@ impl NodeManager {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn supports_samply_flags(bin: &str) -> bool {
|
||||
let mut cmd = std::process::Command::new(bin);
|
||||
// NOTE: The flag to check must come before --help.
|
||||
// We pass --help as a shortcut to not execute any command.
|
||||
cmd.args(["--log.samply", "--help"]);
|
||||
debug!(?cmd, "Checking samply flags support");
|
||||
let Ok(output) = cmd.output() else {
|
||||
return false;
|
||||
};
|
||||
debug!(?output, "Samply flags support check");
|
||||
output.status.success()
|
||||
}
|
||||
|
||||
@@ -58,7 +58,6 @@ tokio = { workspace = true, features = ["sync", "macros", "time", "rt-multi-thre
|
||||
# misc
|
||||
clap = { workspace = true, features = ["derive", "env"] }
|
||||
eyre.workspace = true
|
||||
color-eyre.workspace = true
|
||||
thiserror.workspace = true
|
||||
humantime.workspace = true
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ impl AuthenticatedTransport {
|
||||
|
||||
// shift the iat forward by one second so there is some buffer time
|
||||
let mut shifted_claims = inner_and_claims.1;
|
||||
shifted_claims.iat -= 30;
|
||||
shifted_claims.iat -= 1;
|
||||
|
||||
// if the claims are out of date, reset the inner transport
|
||||
if !shifted_claims.is_within_time_window() {
|
||||
|
||||
@@ -103,20 +103,14 @@ impl BenchContext {
|
||||
(bench_args.from, bench_args.to)
|
||||
};
|
||||
|
||||
// If `--to` are not provided, we will run the benchmark continuously,
|
||||
// If neither `--from` nor `--to` are provided, we will run the benchmark continuously,
|
||||
// starting at the latest block.
|
||||
let latest_block = block_provider
|
||||
.get_block_by_number(BlockNumberOrTag::Latest)
|
||||
.full()
|
||||
.await?
|
||||
.ok_or_else(|| eyre::eyre!("Failed to fetch latest block from RPC"))?;
|
||||
let mut benchmark_mode = BenchMode::new(from, to, latest_block.into_inner().number())?;
|
||||
let mut benchmark_mode = BenchMode::new(from, to)?;
|
||||
|
||||
let first_block = match benchmark_mode {
|
||||
BenchMode::Continuous(start) => {
|
||||
block_provider.get_block_by_number(start.into()).full().await?.ok_or_else(|| {
|
||||
eyre::eyre!("Failed to fetch block {} from RPC for continuous mode", start)
|
||||
})?
|
||||
BenchMode::Continuous => {
|
||||
// fetch Latest block
|
||||
block_provider.get_block_by_number(BlockNumberOrTag::Latest).full().await?.unwrap()
|
||||
}
|
||||
BenchMode::Range(ref mut range) => {
|
||||
match range.next() {
|
||||
@@ -126,9 +120,7 @@ impl BenchContext {
|
||||
.get_block_by_number(block_number.into())
|
||||
.full()
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
eyre::eyre!("Failed to fetch block {} from RPC", block_number)
|
||||
})?
|
||||
.unwrap()
|
||||
}
|
||||
None => {
|
||||
return Err(eyre::eyre!(
|
||||
|
||||
@@ -5,8 +5,9 @@ use std::ops::RangeInclusive;
|
||||
/// Whether or not the benchmark should run as a continuous stream of payloads.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum BenchMode {
|
||||
// TODO: just include the start block in `Continuous`
|
||||
/// Run the benchmark as a continuous stream of payloads, until the benchmark is interrupted.
|
||||
Continuous(u64),
|
||||
Continuous,
|
||||
/// Run the benchmark for a specific range of blocks.
|
||||
Range(RangeInclusive<u64>),
|
||||
}
|
||||
@@ -15,19 +16,18 @@ impl BenchMode {
|
||||
/// Check if the block number is in the range
|
||||
pub fn contains(&self, block_number: u64) -> bool {
|
||||
match self {
|
||||
Self::Continuous(start) => block_number >= *start,
|
||||
Self::Continuous => true,
|
||||
Self::Range(range) => range.contains(&block_number),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [`BenchMode`] from optional `from` and `to` fields.
|
||||
pub fn new(from: Option<u64>, to: Option<u64>, latest_block: u64) -> Result<Self, eyre::Error> {
|
||||
pub fn new(from: Option<u64>, to: Option<u64>) -> Result<Self, eyre::Error> {
|
||||
// If neither `--from` nor `--to` are provided, we will run the benchmark continuously,
|
||||
// starting at the latest block.
|
||||
match (from, to) {
|
||||
(Some(from), Some(to)) => Ok(Self::Range(from..=to)),
|
||||
(None, None) => Ok(Self::Continuous(latest_block)),
|
||||
(Some(start), None) => Ok(Self::Continuous(start)),
|
||||
(None, None) => Ok(Self::Continuous),
|
||||
_ => {
|
||||
// both or neither are allowed, everything else is ambiguous
|
||||
Err(eyre::eyre!("`from` and `to` must be provided together, or not at all."))
|
||||
|
||||
@@ -23,7 +23,7 @@ use bench::BenchmarkCommand;
|
||||
use clap::Parser;
|
||||
use reth_cli_runner::CliRunner;
|
||||
|
||||
fn main() -> eyre::Result<()> {
|
||||
fn main() {
|
||||
// Enable backtraces unless a RUST_BACKTRACE value has already been explicitly provided.
|
||||
if std::env::var_os("RUST_BACKTRACE").is_none() {
|
||||
unsafe {
|
||||
@@ -31,11 +31,12 @@ fn main() -> eyre::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
color_eyre::install()?;
|
||||
|
||||
// Run until either exit or sigint or sigterm
|
||||
let runner = CliRunner::try_default_runtime()?;
|
||||
runner.run_command_until_exit(|ctx| BenchmarkCommand::parse().execute(ctx))?;
|
||||
|
||||
Ok(())
|
||||
let runner = CliRunner::try_default_runtime().unwrap();
|
||||
runner
|
||||
.run_command_until_exit(|ctx| {
|
||||
let command = BenchmarkCommand::parse();
|
||||
command.execute(ctx)
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ backon.workspace = true
|
||||
tempfile.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["jemalloc", "otlp", "reth-revm/portable", "js-tracer", "keccak-cache-global", "asm-keccak"]
|
||||
default = ["jemalloc", "otlp", "reth-revm/portable", "js-tracer", "keccak-cache-global"]
|
||||
|
||||
otlp = [
|
||||
"reth-ethereum-cli/otlp",
|
||||
@@ -103,7 +103,6 @@ asm-keccak = [
|
||||
"reth-node-ethereum/asm-keccak",
|
||||
]
|
||||
keccak-cache-global = [
|
||||
"reth-node-core/keccak-cache-global",
|
||||
"reth-node-ethereum/keccak-cache-global",
|
||||
]
|
||||
jemalloc = [
|
||||
@@ -116,11 +115,6 @@ jemalloc-prof = [
|
||||
"reth-cli-util/jemalloc",
|
||||
"reth-cli-util/jemalloc-prof",
|
||||
"reth-ethereum-cli/jemalloc-prof",
|
||||
"reth-node-metrics/jemalloc-prof",
|
||||
]
|
||||
jemalloc-symbols = [
|
||||
"jemalloc-prof",
|
||||
"reth-ethereum-cli/jemalloc-symbols",
|
||||
]
|
||||
jemalloc-unprefixed = [
|
||||
"reth-cli-util/jemalloc-unprefixed",
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
#[global_allocator]
|
||||
static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator();
|
||||
|
||||
#[cfg(all(feature = "jemalloc-prof", unix))]
|
||||
#[unsafe(export_name = "_rjem_malloc_conf")]
|
||||
static MALLOC_CONF: &[u8] = b"prof:true,prof_active:true,lg_prof_sample:19\0";
|
||||
|
||||
use clap::Parser;
|
||||
use reth::{args::RessArgs, cli::Cli, ress::install_ress_subprotocol};
|
||||
use reth_ethereum_cli::chainspec::EthereumChainSpecParser;
|
||||
|
||||
@@ -62,8 +62,7 @@ static DEFERRED_TRIE_METRICS: LazyLock<DeferredTrieMetrics> =
|
||||
/// Internal state for deferred trie data.
|
||||
enum DeferredState {
|
||||
/// Data is not yet available; raw inputs stored for fallback computation.
|
||||
/// Wrapped in `Option` to allow taking ownership during computation.
|
||||
Pending(Option<PendingInputs>),
|
||||
Pending(PendingInputs),
|
||||
/// Data has been computed and is ready.
|
||||
Ready(ComputedTrieData),
|
||||
}
|
||||
@@ -113,12 +112,12 @@ impl DeferredTrieData {
|
||||
ancestors: Vec<Self>,
|
||||
) -> Self {
|
||||
Self {
|
||||
state: Arc::new(Mutex::new(DeferredState::Pending(Some(PendingInputs {
|
||||
state: Arc::new(Mutex::new(DeferredState::Pending(PendingInputs {
|
||||
hashed_state,
|
||||
trie_updates,
|
||||
anchor_hash,
|
||||
ancestors,
|
||||
})))),
|
||||
}))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,38 +149,42 @@ impl DeferredTrieData {
|
||||
/// * `anchor_hash` - The persisted ancestor hash this trie input is anchored to
|
||||
/// * `ancestors` - Deferred trie data from ancestor blocks for merging
|
||||
pub fn sort_and_build_trie_input(
|
||||
hashed_state: Arc<HashedPostState>,
|
||||
trie_updates: Arc<TrieUpdates>,
|
||||
hashed_state: &HashedPostState,
|
||||
trie_updates: &TrieUpdates,
|
||||
anchor_hash: B256,
|
||||
ancestors: &[Self],
|
||||
) -> ComputedTrieData {
|
||||
let sorted_hashed_state = match Arc::try_unwrap(hashed_state) {
|
||||
Ok(state) => state.into_sorted(),
|
||||
Err(arc) => arc.clone_into_sorted(),
|
||||
};
|
||||
let sorted_trie_updates = match Arc::try_unwrap(trie_updates) {
|
||||
Ok(updates) => updates.into_sorted(),
|
||||
Err(arc) => arc.clone_into_sorted(),
|
||||
};
|
||||
// Sort the current block's hashed state and trie updates
|
||||
let sorted_hashed_state = Arc::new(hashed_state.clone_into_sorted());
|
||||
let sorted_trie_updates = Arc::new(trie_updates.clone().into_sorted());
|
||||
|
||||
// Build overlay by merging ancestors oldest-to-newest, then this block's data last.
|
||||
// Later entries take precedence, so this block's state overwrites any ancestor conflicts.
|
||||
// Merge trie data from ancestors (oldest -> newest so later state takes precedence)
|
||||
let mut overlay = TrieInputSorted::default();
|
||||
let state_mut = Arc::make_mut(&mut overlay.state);
|
||||
let nodes_mut = Arc::make_mut(&mut overlay.nodes);
|
||||
|
||||
for ancestor in ancestors {
|
||||
let ancestor_data = ancestor.wait_cloned();
|
||||
state_mut.extend_ref(ancestor_data.hashed_state.as_ref());
|
||||
nodes_mut.extend_ref(ancestor_data.trie_updates.as_ref());
|
||||
{
|
||||
let state_mut = Arc::make_mut(&mut overlay.state);
|
||||
state_mut.extend_ref(ancestor_data.hashed_state.as_ref());
|
||||
}
|
||||
{
|
||||
let nodes_mut = Arc::make_mut(&mut overlay.nodes);
|
||||
nodes_mut.extend_ref(ancestor_data.trie_updates.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
state_mut.extend_ref(&sorted_hashed_state);
|
||||
nodes_mut.extend_ref(&sorted_trie_updates);
|
||||
// Extend overlay with current block's sorted data
|
||||
{
|
||||
let state_mut = Arc::make_mut(&mut overlay.state);
|
||||
state_mut.extend_ref(sorted_hashed_state.as_ref());
|
||||
}
|
||||
{
|
||||
let nodes_mut = Arc::make_mut(&mut overlay.nodes);
|
||||
nodes_mut.extend_ref(sorted_trie_updates.as_ref());
|
||||
}
|
||||
|
||||
ComputedTrieData::with_trie_input(
|
||||
Arc::new(sorted_hashed_state),
|
||||
Arc::new(sorted_trie_updates),
|
||||
sorted_hashed_state,
|
||||
sorted_trie_updates,
|
||||
anchor_hash,
|
||||
Arc::new(overlay),
|
||||
)
|
||||
@@ -201,7 +204,7 @@ impl DeferredTrieData {
|
||||
#[instrument(level = "debug", target = "engine::tree::deferred_trie", skip_all)]
|
||||
pub fn wait_cloned(&self) -> ComputedTrieData {
|
||||
let mut state = self.state.lock();
|
||||
match &mut *state {
|
||||
match &*state {
|
||||
// If the deferred trie data is ready, return the cached result.
|
||||
DeferredState::Ready(bundle) => {
|
||||
DEFERRED_TRIE_METRICS.deferred_trie_async_ready.increment(1);
|
||||
@@ -209,14 +212,11 @@ impl DeferredTrieData {
|
||||
}
|
||||
// If the deferred trie data is pending, compute the trie data synchronously and return
|
||||
// the result. This is the fallback path if the async task hasn't completed.
|
||||
DeferredState::Pending(maybe_inputs) => {
|
||||
DeferredState::Pending(inputs) => {
|
||||
DEFERRED_TRIE_METRICS.deferred_trie_sync_fallback.increment(1);
|
||||
|
||||
let inputs = maybe_inputs.take().expect("inputs must be present in Pending state");
|
||||
|
||||
let computed = Self::sort_and_build_trie_input(
|
||||
inputs.hashed_state,
|
||||
inputs.trie_updates,
|
||||
&inputs.hashed_state,
|
||||
&inputs.trie_updates,
|
||||
inputs.anchor_hash,
|
||||
&inputs.ancestors,
|
||||
);
|
||||
|
||||
@@ -86,20 +86,14 @@ impl<N: NodePrimitives> InMemoryState<N> {
|
||||
///
|
||||
/// This tries to acquire a read lock. Drop any write locks before calling this.
|
||||
pub(crate) fn update_metrics(&self) {
|
||||
let (count, earliest, latest) = {
|
||||
let numbers = self.numbers.read();
|
||||
let count = numbers.len();
|
||||
let earliest = numbers.first_key_value().map(|(number, _)| *number);
|
||||
let latest = numbers.last_key_value().map(|(number, _)| *number);
|
||||
(count, earliest, latest)
|
||||
};
|
||||
if let Some(earliest_block_number) = earliest {
|
||||
self.metrics.earliest_block.set(earliest_block_number as f64);
|
||||
let numbers = self.numbers.read();
|
||||
if let Some((earliest_block_number, _)) = numbers.first_key_value() {
|
||||
self.metrics.earliest_block.set(*earliest_block_number as f64);
|
||||
}
|
||||
if let Some(latest_block_number) = latest {
|
||||
self.metrics.latest_block.set(latest_block_number as f64);
|
||||
if let Some((latest_block_number, _)) = numbers.last_key_value() {
|
||||
self.metrics.latest_block.set(*latest_block_number as f64);
|
||||
}
|
||||
self.metrics.num_blocks.set(count as f64);
|
||||
self.metrics.num_blocks.set(numbers.len() as f64);
|
||||
}
|
||||
|
||||
/// Returns the state for a given block hash.
|
||||
@@ -670,14 +664,22 @@ impl<N: NodePrimitives> BlockState<N> {
|
||||
receipts.first().map(|receipts| receipts.deref()).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns an iterator over __parent__ `BlockStates`.
|
||||
/// Returns a vector of __parent__ `BlockStates`.
|
||||
///
|
||||
/// The block state order is newest to oldest (highest to lowest):
|
||||
/// The block state order in the output vector is newest to oldest (highest to lowest):
|
||||
/// `[5,4,3,2,1]`
|
||||
///
|
||||
/// Note: This does not include self.
|
||||
pub fn parent_state_chain(&self) -> impl Iterator<Item = &Self> + '_ {
|
||||
std::iter::successors(self.parent.as_deref(), |state| state.parent.as_deref())
|
||||
pub fn parent_state_chain(&self) -> Vec<&Self> {
|
||||
let mut parents = Vec::new();
|
||||
let mut current = self.parent.as_deref();
|
||||
|
||||
while let Some(parent) = current {
|
||||
parents.push(parent);
|
||||
current = parent.parent.as_deref();
|
||||
}
|
||||
|
||||
parents
|
||||
}
|
||||
|
||||
/// Returns a vector of `BlockStates` representing the entire in memory chain.
|
||||
@@ -688,11 +690,6 @@ impl<N: NodePrimitives> BlockState<N> {
|
||||
}
|
||||
|
||||
/// Appends the parent chain of this [`BlockState`] to the given vector.
|
||||
///
|
||||
/// Parents are appended in order from newest to oldest (highest to lowest).
|
||||
/// This does not include self, only the parent states.
|
||||
///
|
||||
/// This is a convenience method equivalent to `chain.extend(self.parent_state_chain())`.
|
||||
pub fn append_parent_chain<'a>(&'a self, chain: &mut Vec<&'a Self>) {
|
||||
chain.extend(self.parent_state_chain());
|
||||
}
|
||||
@@ -1456,18 +1453,19 @@ mod tests {
|
||||
let mut test_block_builder: TestBlockBuilder = TestBlockBuilder::default();
|
||||
let chain = create_mock_state_chain(&mut test_block_builder, 4);
|
||||
|
||||
let parents: Vec<_> = chain[3].parent_state_chain().collect();
|
||||
let parents = chain[3].parent_state_chain();
|
||||
assert_eq!(parents.len(), 3);
|
||||
assert_eq!(parents[0].block().recovered_block().number, 3);
|
||||
assert_eq!(parents[1].block().recovered_block().number, 2);
|
||||
assert_eq!(parents[2].block().recovered_block().number, 1);
|
||||
|
||||
let parents: Vec<_> = chain[2].parent_state_chain().collect();
|
||||
let parents = chain[2].parent_state_chain();
|
||||
assert_eq!(parents.len(), 2);
|
||||
assert_eq!(parents[0].block().recovered_block().number, 2);
|
||||
assert_eq!(parents[1].block().recovered_block().number, 1);
|
||||
|
||||
assert_eq!(chain[0].parent_state_chain().count(), 0);
|
||||
let parents = chain[0].parent_state_chain();
|
||||
assert_eq!(parents.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1478,7 +1476,8 @@ mod tests {
|
||||
create_mock_state(&mut test_block_builder, single_block_number, B256::random());
|
||||
let single_block_hash = single_block.block().recovered_block().hash();
|
||||
|
||||
assert_eq!(single_block.parent_state_chain().count(), 0);
|
||||
let parents = single_block.parent_state_chain();
|
||||
assert_eq!(parents.len(), 0);
|
||||
|
||||
let block_state_chain = single_block.chain().collect::<Vec<_>>();
|
||||
assert_eq!(block_state_chain.len(), 1);
|
||||
|
||||
@@ -5,14 +5,14 @@ use reth_errors::ProviderResult;
|
||||
use reth_primitives_traits::{Account, Bytecode, NodePrimitives};
|
||||
use reth_storage_api::{
|
||||
AccountReader, BlockHashReader, BytecodeReader, HashedPostStateProvider, StateProofProvider,
|
||||
StateProvider, StateProviderBox, StateRootProvider, StorageRootProvider,
|
||||
StateProvider, StateRootProvider, StorageRootProvider,
|
||||
};
|
||||
use reth_trie::{
|
||||
updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof,
|
||||
MultiProofTargets, StorageMultiProof, TrieInput,
|
||||
};
|
||||
use revm_database::BundleState;
|
||||
use std::{borrow::Cow, sync::OnceLock};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
/// A state provider that stores references to in-memory blocks along with their state as well as a
|
||||
/// reference of the historical state provider for fallback lookups.
|
||||
@@ -24,11 +24,15 @@ pub struct MemoryOverlayStateProviderRef<
|
||||
/// Historical state provider for state lookups that are not found in memory blocks.
|
||||
pub(crate) historical: Box<dyn StateProvider + 'a>,
|
||||
/// The collection of executed parent blocks. Expected order is newest to oldest.
|
||||
pub(crate) in_memory: Cow<'a, [ExecutedBlock<N>]>,
|
||||
pub(crate) in_memory: Vec<ExecutedBlock<N>>,
|
||||
/// Lazy-loaded in-memory trie data.
|
||||
pub(crate) trie_input: OnceLock<TrieInput>,
|
||||
}
|
||||
|
||||
/// A state provider that stores references to in-memory blocks along with their state as well as
|
||||
/// the historical state provider for fallback lookups.
|
||||
pub type MemoryOverlayStateProvider<N> = MemoryOverlayStateProviderRef<'static, N>;
|
||||
|
||||
impl<'a, N: NodePrimitives> MemoryOverlayStateProviderRef<'a, N> {
|
||||
/// Create new memory overlay state provider.
|
||||
///
|
||||
@@ -38,7 +42,7 @@ impl<'a, N: NodePrimitives> MemoryOverlayStateProviderRef<'a, N> {
|
||||
/// - `historical` - a historical state provider for the latest ancestor block stored in the
|
||||
/// database.
|
||||
pub fn new(historical: Box<dyn StateProvider + 'a>, in_memory: Vec<ExecutedBlock<N>>) -> Self {
|
||||
Self { historical, in_memory: Cow::Owned(in_memory), trie_input: OnceLock::new() }
|
||||
Self { historical, in_memory, trie_input: OnceLock::new() }
|
||||
}
|
||||
|
||||
/// Turn this state provider into a state provider
|
||||
@@ -49,14 +53,11 @@ impl<'a, N: NodePrimitives> MemoryOverlayStateProviderRef<'a, N> {
|
||||
/// Return lazy-loaded trie state aggregated from in-memory blocks.
|
||||
fn trie_input(&self) -> &TrieInput {
|
||||
self.trie_input.get_or_init(|| {
|
||||
let mut input = TrieInput::default();
|
||||
// Iterate from oldest to newest
|
||||
for block in self.in_memory.iter().rev() {
|
||||
let data = block.trie_data();
|
||||
input.nodes.extend_from_sorted(&data.trie_updates);
|
||||
input.state.extend_from_sorted(&data.hashed_state);
|
||||
}
|
||||
input
|
||||
let bundles: Vec<_> =
|
||||
self.in_memory.iter().rev().map(|block| block.trie_data()).collect();
|
||||
TrieInput::from_blocks_sorted(
|
||||
bundles.iter().map(|data| (data.hashed_state.as_ref(), data.trie_updates.as_ref())),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -70,7 +71,7 @@ impl<'a, N: NodePrimitives> MemoryOverlayStateProviderRef<'a, N> {
|
||||
|
||||
impl<N: NodePrimitives> BlockHashReader for MemoryOverlayStateProviderRef<'_, N> {
|
||||
fn block_hash(&self, number: BlockNumber) -> ProviderResult<Option<B256>> {
|
||||
for block in self.in_memory.iter() {
|
||||
for block in &self.in_memory {
|
||||
if block.recovered_block().number() == number {
|
||||
return Ok(Some(block.recovered_block().hash()));
|
||||
}
|
||||
@@ -89,7 +90,7 @@ impl<N: NodePrimitives> BlockHashReader for MemoryOverlayStateProviderRef<'_, N>
|
||||
let mut in_memory_hashes = Vec::with_capacity(range.size_hint().0);
|
||||
|
||||
// iterate in ascending order (oldest to newest = low to high)
|
||||
for block in self.in_memory.iter() {
|
||||
for block in &self.in_memory {
|
||||
let block_num = block.recovered_block().number();
|
||||
if range.contains(&block_num) {
|
||||
in_memory_hashes.push(block.recovered_block().hash());
|
||||
@@ -111,7 +112,7 @@ impl<N: NodePrimitives> BlockHashReader for MemoryOverlayStateProviderRef<'_, N>
|
||||
|
||||
impl<N: NodePrimitives> AccountReader for MemoryOverlayStateProviderRef<'_, N> {
|
||||
fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>> {
|
||||
for block in self.in_memory.iter() {
|
||||
for block in &self.in_memory {
|
||||
if let Some(account) = block.execution_output.account(address) {
|
||||
return Ok(account);
|
||||
}
|
||||
@@ -215,7 +216,7 @@ impl<N: NodePrimitives> StateProvider for MemoryOverlayStateProviderRef<'_, N> {
|
||||
address: Address,
|
||||
storage_key: StorageKey,
|
||||
) -> ProviderResult<Option<StorageValue>> {
|
||||
for block in self.in_memory.iter() {
|
||||
for block in &self.in_memory {
|
||||
if let Some(value) = block.execution_output.storage(&address, storage_key.into()) {
|
||||
return Ok(Some(value));
|
||||
}
|
||||
@@ -227,7 +228,7 @@ impl<N: NodePrimitives> StateProvider for MemoryOverlayStateProviderRef<'_, N> {
|
||||
|
||||
impl<N: NodePrimitives> BytecodeReader for MemoryOverlayStateProviderRef<'_, N> {
|
||||
fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>> {
|
||||
for block in self.in_memory.iter() {
|
||||
for block in &self.in_memory {
|
||||
if let Some(contract) = block.execution_output.bytecode(code_hash) {
|
||||
return Ok(Some(contract));
|
||||
}
|
||||
@@ -236,46 +237,3 @@ impl<N: NodePrimitives> BytecodeReader for MemoryOverlayStateProviderRef<'_, N>
|
||||
self.historical.bytecode_by_hash(code_hash)
|
||||
}
|
||||
}
|
||||
|
||||
/// An owned state provider that stores references to in-memory blocks along with their state as
|
||||
/// well as a reference of the historical state provider for fallback lookups.
|
||||
#[expect(missing_debug_implementations)]
|
||||
pub struct MemoryOverlayStateProvider<N: NodePrimitives = reth_ethereum_primitives::EthPrimitives> {
|
||||
/// Historical state provider for state lookups that are not found in memory blocks.
|
||||
pub(crate) historical: StateProviderBox,
|
||||
/// The collection of executed parent blocks. Expected order is newest to oldest.
|
||||
pub(crate) in_memory: Vec<ExecutedBlock<N>>,
|
||||
/// Lazy-loaded in-memory trie data.
|
||||
pub(crate) trie_input: OnceLock<TrieInput>,
|
||||
}
|
||||
|
||||
impl<N: NodePrimitives> MemoryOverlayStateProvider<N> {
|
||||
/// Create new memory overlay state provider.
|
||||
///
|
||||
/// ## Arguments
|
||||
///
|
||||
/// - `in_memory` - the collection of executed ancestor blocks in reverse.
|
||||
/// - `historical` - a historical state provider for the latest ancestor block stored in the
|
||||
/// database.
|
||||
pub fn new(historical: StateProviderBox, in_memory: Vec<ExecutedBlock<N>>) -> Self {
|
||||
Self { historical, in_memory, trie_input: OnceLock::new() }
|
||||
}
|
||||
|
||||
/// Returns a new provider that takes the `TX` as reference
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> MemoryOverlayStateProviderRef<'_, N> {
|
||||
MemoryOverlayStateProviderRef {
|
||||
historical: Box::new(self.historical.as_ref()),
|
||||
in_memory: Cow::Borrowed(&self.in_memory),
|
||||
trie_input: self.trie_input.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps the [`Self`] in a `Box`.
|
||||
pub fn boxed(self) -> StateProviderBox {
|
||||
Box::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
// Delegates all provider impls to [`MemoryOverlayStateProviderRef`]
|
||||
reth_storage_api::macros::delegate_provider_impls!(MemoryOverlayStateProvider<N> where [N: NodePrimitives]);
|
||||
|
||||
@@ -117,7 +117,7 @@ impl<N: NodePrimitives> TestBlockBuilder<N> {
|
||||
.map(|_| {
|
||||
let tx = mock_tx(self.signer_build_account_info.nonce);
|
||||
self.signer_build_account_info.nonce += 1;
|
||||
self.signer_build_account_info.balance -= Self::single_tx_cost();
|
||||
self.signer_build_account_info.balance -= signer_balance_decrease;
|
||||
tx
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -80,8 +80,6 @@ pub fn make_genesis_header(genesis: &Genesis, hardforks: &ChainHardforks) -> Hea
|
||||
.then_some(EMPTY_REQUESTS_HASH);
|
||||
|
||||
Header {
|
||||
number: genesis.number.unwrap_or_default(),
|
||||
parent_hash: genesis.parent_hash.unwrap_or_default(),
|
||||
gas_limit: genesis.gas_limit,
|
||||
difficulty: genesis.difficulty,
|
||||
nonce: genesis.nonce.into(),
|
||||
@@ -970,7 +968,7 @@ impl<H: BlockHeader> EthereumHardforks for ChainSpec<H> {
|
||||
|
||||
/// A trait for reading the current chainspec.
|
||||
#[auto_impl::auto_impl(&, Arc)]
|
||||
pub trait ChainSpecProvider: Debug + Send {
|
||||
pub trait ChainSpecProvider: Debug + Send + Sync {
|
||||
/// The chain spec type.
|
||||
type ChainSpec: EthChainSpec + 'static;
|
||||
|
||||
|
||||
@@ -23,10 +23,7 @@ use reth_node_core::{
|
||||
dirs::{ChainPath, DataDirPath},
|
||||
};
|
||||
use reth_provider::{
|
||||
providers::{
|
||||
BlockchainProvider, NodeTypesForProvider, RocksDBProvider, StaticFileProvider,
|
||||
StaticFileProviderBuilder,
|
||||
},
|
||||
providers::{BlockchainProvider, NodeTypesForProvider, RocksDBProvider, StaticFileProvider},
|
||||
ProviderFactory, StaticFileProviderFactory,
|
||||
};
|
||||
use reth_stages::{sets::DefaultStages, Pipeline, PipelineTarget};
|
||||
@@ -103,23 +100,15 @@ impl<C: ChainSpecParser> EnvironmentArgs<C> {
|
||||
}
|
||||
|
||||
info!(target: "reth::cli", ?db_path, ?sf_path, "Opening storage");
|
||||
let genesis_block_number = self.chain.genesis().number.unwrap_or_default();
|
||||
let (db, sfp) = match access {
|
||||
AccessRights::RW => (
|
||||
Arc::new(init_db(db_path, self.db.database_args())?),
|
||||
StaticFileProviderBuilder::read_write(sf_path)
|
||||
.with_genesis_block_number(genesis_block_number)
|
||||
.build()?,
|
||||
StaticFileProvider::read_write(sf_path)?,
|
||||
),
|
||||
AccessRights::RO | AccessRights::RoInconsistent => (
|
||||
Arc::new(open_db_read_only(&db_path, self.db.database_args())?),
|
||||
StaticFileProvider::read_only(sf_path, false)?,
|
||||
),
|
||||
AccessRights::RO | AccessRights::RoInconsistent => {
|
||||
(Arc::new(open_db_read_only(&db_path, self.db.database_args())?), {
|
||||
let provider = StaticFileProviderBuilder::read_only(sf_path)
|
||||
.with_genesis_block_number(genesis_block_number)
|
||||
.build()?;
|
||||
provider.watch_directory();
|
||||
provider
|
||||
})
|
||||
}
|
||||
};
|
||||
// TransactionDB only support read-write mode
|
||||
let rocksdb_provider = RocksDBProvider::builder(data_dir.rocksdb())
|
||||
|
||||
@@ -2,8 +2,8 @@ use alloy_primitives::{hex, BlockHash};
|
||||
use clap::Parser;
|
||||
use reth_db::{
|
||||
static_file::{
|
||||
AccountChangesetMask, ColumnSelectorOne, ColumnSelectorTwo, HeaderWithHashMask,
|
||||
ReceiptMask, TransactionMask, TransactionSenderMask,
|
||||
ColumnSelectorOne, ColumnSelectorTwo, HeaderWithHashMask, ReceiptMask, TransactionMask,
|
||||
TransactionSenderMask,
|
||||
},
|
||||
RawDupSort,
|
||||
};
|
||||
@@ -19,7 +19,7 @@ use reth_db_common::DbTool;
|
||||
use reth_node_api::{HeaderTy, ReceiptTy, TxTy};
|
||||
use reth_node_builder::NodeTypesWithDB;
|
||||
use reth_primitives_traits::ValueWithSubKey;
|
||||
use reth_provider::{providers::ProviderNodeTypes, ChangeSetReader, StaticFileProviderFactory};
|
||||
use reth_provider::{providers::ProviderNodeTypes, StaticFileProviderFactory};
|
||||
use reth_static_file_types::StaticFileSegment;
|
||||
use tracing::error;
|
||||
|
||||
@@ -64,10 +64,6 @@ enum Subcommand {
|
||||
#[arg(value_parser = maybe_json_value_parser)]
|
||||
key: String,
|
||||
|
||||
/// The subkey to get content for, for example address in changeset
|
||||
#[arg(value_parser = maybe_json_value_parser)]
|
||||
subkey: Option<String>,
|
||||
|
||||
/// Output bytes instead of human-readable decoded value
|
||||
#[arg(long)]
|
||||
raw: bool,
|
||||
@@ -81,77 +77,33 @@ impl Command {
|
||||
Subcommand::Mdbx { table, key, subkey, end_key, end_subkey, raw } => {
|
||||
table.view(&GetValueViewer { tool, key, subkey, end_key, end_subkey, raw })?
|
||||
}
|
||||
Subcommand::StaticFile { segment, key, subkey, raw } => {
|
||||
let (key, subkey, mask): (u64, _, _) = match segment {
|
||||
Subcommand::StaticFile { segment, key, raw } => {
|
||||
let (key, mask): (u64, _) = match segment {
|
||||
StaticFileSegment::Headers => (
|
||||
table_key::<tables::Headers>(&key)?,
|
||||
None,
|
||||
<HeaderWithHashMask<HeaderTy<N>>>::MASK,
|
||||
),
|
||||
StaticFileSegment::Transactions => (
|
||||
table_key::<tables::Transactions>(&key)?,
|
||||
None,
|
||||
<TransactionMask<TxTy<N>>>::MASK,
|
||||
),
|
||||
StaticFileSegment::Receipts => (
|
||||
table_key::<tables::Receipts>(&key)?,
|
||||
None,
|
||||
<ReceiptMask<ReceiptTy<N>>>::MASK,
|
||||
),
|
||||
StaticFileSegment::Transactions => {
|
||||
(table_key::<tables::Transactions>(&key)?, <TransactionMask<TxTy<N>>>::MASK)
|
||||
}
|
||||
StaticFileSegment::Receipts => {
|
||||
(table_key::<tables::Receipts>(&key)?, <ReceiptMask<ReceiptTy<N>>>::MASK)
|
||||
}
|
||||
StaticFileSegment::TransactionSenders => (
|
||||
table_key::<tables::TransactionSenders>(&key)?,
|
||||
None,
|
||||
TransactionSenderMask::MASK,
|
||||
<TransactionSenderMask>::MASK,
|
||||
),
|
||||
StaticFileSegment::AccountChangeSets => {
|
||||
let subkey =
|
||||
table_subkey::<tables::AccountChangeSets>(subkey.as_deref()).ok();
|
||||
(
|
||||
table_key::<tables::AccountChangeSets>(&key)?,
|
||||
subkey,
|
||||
AccountChangesetMask::MASK,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// handle account changesets differently if a subkey is provided.
|
||||
if let StaticFileSegment::AccountChangeSets = segment {
|
||||
let Some(subkey) = subkey else {
|
||||
// get all changesets for the block
|
||||
let changesets = tool
|
||||
.provider_factory
|
||||
.static_file_provider()
|
||||
.account_block_changeset(key)?;
|
||||
|
||||
println!("{}", serde_json::to_string_pretty(&changesets)?);
|
||||
return Ok(())
|
||||
};
|
||||
|
||||
let account = tool
|
||||
.provider_factory
|
||||
.static_file_provider()
|
||||
.get_account_before_block(key, subkey)?;
|
||||
|
||||
if let Some(account) = account {
|
||||
println!("{}", serde_json::to_string_pretty(&account)?);
|
||||
} else {
|
||||
error!(target: "reth::cli", "No content for the given table key.");
|
||||
}
|
||||
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let content = tool.provider_factory.static_file_provider().find_static_file(
|
||||
segment,
|
||||
|provider| {
|
||||
let mut cursor = provider.cursor()?;
|
||||
cursor.get(key.into(), mask).map(|result| {
|
||||
result.map(|vec| {
|
||||
vec.iter().map(|slice| slice.to_vec()).collect::<Vec<_>>()
|
||||
})
|
||||
})
|
||||
},
|
||||
)?;
|
||||
let content = tool
|
||||
.provider_factory
|
||||
.static_file_provider()
|
||||
.get_segment_provider(segment, key)?
|
||||
.cursor()?
|
||||
.get(key.into(), mask)
|
||||
.map(|result| {
|
||||
result.map(|vec| vec.iter().map(|slice| slice.to_vec()).collect::<Vec<_>>())
|
||||
})?;
|
||||
|
||||
match content {
|
||||
Some(content) => {
|
||||
@@ -187,9 +139,6 @@ impl Command {
|
||||
)?;
|
||||
println!("{}", serde_json::to_string_pretty(&sender)?);
|
||||
}
|
||||
StaticFileSegment::AccountChangeSets => {
|
||||
unreachable!("account changeset static files are special cased before this match")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C>
|
||||
let access_rights =
|
||||
if command.dry_run { AccessRights::RO } else { AccessRights::RW };
|
||||
db_exec!(self.env, tool, N, access_rights, {
|
||||
command.execute(&tool, ctx.task_executor.clone(), &data_dir)?;
|
||||
command.execute(&tool, ctx.task_executor.clone())?;
|
||||
});
|
||||
}
|
||||
Subcommands::StaticFileHeader(command) => {
|
||||
|
||||
@@ -9,10 +9,7 @@ use reth_db_api::{
|
||||
transaction::{DbTx, DbTxMut},
|
||||
};
|
||||
use reth_db_common::DbTool;
|
||||
use reth_node_core::{
|
||||
dirs::{ChainPath, DataDirPath},
|
||||
version::version_metadata,
|
||||
};
|
||||
use reth_node_core::version::version_metadata;
|
||||
use reth_node_metrics::{
|
||||
chain::ChainSpecInfo,
|
||||
hooks::Hooks,
|
||||
@@ -56,13 +53,11 @@ impl Command {
|
||||
self,
|
||||
tool: &DbTool<N>,
|
||||
task_executor: TaskExecutor,
|
||||
data_dir: &ChainPath<DataDirPath>,
|
||||
) -> eyre::Result<()> {
|
||||
// Set up metrics server if requested
|
||||
let _metrics_handle = if let Some(listen_addr) = self.metrics {
|
||||
let chain_name = tool.provider_factory.chain_spec().chain().to_string();
|
||||
let executor = task_executor.clone();
|
||||
let pprof_dump_dir = data_dir.pprof_dumps();
|
||||
|
||||
let handle = task_executor.spawn_critical("metrics server", async move {
|
||||
let config = MetricServerConfig::new(
|
||||
@@ -78,7 +73,6 @@ impl Command {
|
||||
ChainSpecInfo { name: chain_name },
|
||||
executor,
|
||||
Hooks::builder().build(),
|
||||
pprof_dump_dir,
|
||||
);
|
||||
|
||||
// Spawn the metrics server
|
||||
@@ -307,8 +301,8 @@ fn verify_and_repair<N: ProviderNodeTypes>(tool: &DbTool<N>) -> eyre::Result<()>
|
||||
if inconsistent_nodes == 0 {
|
||||
info!("No inconsistencies found");
|
||||
} else {
|
||||
info!("Repaired {} inconsistencies, committing changes", inconsistent_nodes);
|
||||
provider_rw.commit()?;
|
||||
info!("Repaired {} inconsistencies and committed changes", inconsistent_nodes);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -40,17 +40,12 @@ enum Subcommands {
|
||||
#[clap(rename_all = "snake_case")]
|
||||
pub enum SetCommand {
|
||||
/// Store receipts in static files instead of the database
|
||||
Receipts {
|
||||
ReceiptsInStaticFiles {
|
||||
#[clap(action(ArgAction::Set))]
|
||||
value: bool,
|
||||
},
|
||||
/// Store transaction senders in static files instead of the database
|
||||
TransactionSenders {
|
||||
#[clap(action(ArgAction::Set))]
|
||||
value: bool,
|
||||
},
|
||||
/// Store account changesets in static files instead of the database
|
||||
AccountChangesets {
|
||||
TransactionSendersInStaticFiles {
|
||||
#[clap(action(ArgAction::Set))]
|
||||
value: bool,
|
||||
},
|
||||
@@ -99,12 +94,11 @@ impl Command {
|
||||
storages_history_in_rocksdb: _,
|
||||
transaction_hash_numbers_in_rocksdb: _,
|
||||
account_history_in_rocksdb: _,
|
||||
account_changesets_in_static_files: _,
|
||||
} = settings.unwrap_or_else(StorageSettings::legacy);
|
||||
|
||||
// Update the setting based on the key
|
||||
match cmd {
|
||||
SetCommand::Receipts { value } => {
|
||||
SetCommand::ReceiptsInStaticFiles { value } => {
|
||||
if settings.receipts_in_static_files == value {
|
||||
println!("receipts_in_static_files is already set to {}", value);
|
||||
return Ok(());
|
||||
@@ -112,7 +106,7 @@ impl Command {
|
||||
settings.receipts_in_static_files = value;
|
||||
println!("Set receipts_in_static_files = {}", value);
|
||||
}
|
||||
SetCommand::TransactionSenders { value } => {
|
||||
SetCommand::TransactionSendersInStaticFiles { value } => {
|
||||
if settings.transaction_senders_in_static_files == value {
|
||||
println!("transaction_senders_in_static_files is already set to {}", value);
|
||||
return Ok(());
|
||||
@@ -120,14 +114,6 @@ impl Command {
|
||||
settings.transaction_senders_in_static_files = value;
|
||||
println!("Set transaction_senders_in_static_files = {}", value);
|
||||
}
|
||||
SetCommand::AccountChangesets { value } => {
|
||||
if settings.account_changesets_in_static_files == value {
|
||||
println!("account_changesets_in_static_files is already set to {}", value);
|
||||
return Ok(());
|
||||
}
|
||||
settings.account_changesets_in_static_files = value;
|
||||
println!("Set account_changesets_in_static_files = {}", value);
|
||||
}
|
||||
}
|
||||
|
||||
// Write updated settings
|
||||
|
||||
@@ -69,7 +69,9 @@ pub async fn import_blocks_from_file<N>(
|
||||
provider_factory: ProviderFactory<N>,
|
||||
config: &Config,
|
||||
executor: impl ConfigureEvm<Primitives = N::Primitives> + 'static,
|
||||
consensus: Arc<impl FullConsensus<N::Primitives> + 'static>,
|
||||
consensus: Arc<
|
||||
impl FullConsensus<N::Primitives, Error = reth_consensus::ConsensusError> + 'static,
|
||||
>,
|
||||
) -> eyre::Result<ImportResult>
|
||||
where
|
||||
N: ProviderNodeTypes,
|
||||
@@ -196,7 +198,7 @@ pub fn build_import_pipeline_impl<N, C, E>(
|
||||
) -> eyre::Result<(Pipeline<N>, impl futures::Stream<Item = NodeEvent<N::Primitives>> + use<N, C, E>)>
|
||||
where
|
||||
N: ProviderNodeTypes,
|
||||
C: FullConsensus<N::Primitives> + 'static,
|
||||
C: FullConsensus<N::Primitives, Error = reth_consensus::ConsensusError> + 'static,
|
||||
E: ConfigureEvm<Primitives = N::Primitives> + 'static,
|
||||
{
|
||||
if !file_client.has_canonical_blocks() {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
//! Command that initializes the node from a genesis file.
|
||||
|
||||
use crate::common::{AccessRights, CliNodeTypes, Environment, EnvironmentArgs};
|
||||
use alloy_consensus::BlockHeader;
|
||||
use clap::Parser;
|
||||
use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks};
|
||||
use reth_chainspec::{EthChainSpec, EthereumHardforks};
|
||||
use reth_cli::chainspec::ChainSpecParser;
|
||||
use reth_provider::BlockHashReader;
|
||||
use std::sync::Arc;
|
||||
@@ -23,9 +22,8 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> InitComman
|
||||
|
||||
let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
|
||||
|
||||
let genesis_block_number = provider_factory.chain_spec().genesis_header().number();
|
||||
let hash = provider_factory
|
||||
.block_hash(genesis_block_number)?
|
||||
.block_hash(0)?
|
||||
.ok_or_else(|| eyre::eyre!("Genesis hash not found."))?;
|
||||
|
||||
info!(target: "reth::cli", hash = ?hash, "Genesis block written");
|
||||
|
||||
@@ -79,7 +79,7 @@ where
|
||||
+ StaticFileProviderFactory<Primitives: NodePrimitives<BlockHeader: Compact>>,
|
||||
{
|
||||
provider_rw.insert_block(
|
||||
&SealedBlock::<<Provider::Primitives as NodePrimitives>::Block>::from_sealed_parts(
|
||||
SealedBlock::<<Provider::Primitives as NodePrimitives>::Block>::from_sealed_parts(
|
||||
header.clone(),
|
||||
Default::default(),
|
||||
)
|
||||
|
||||
@@ -72,7 +72,7 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>
|
||||
.split();
|
||||
if result.len() != 1 {
|
||||
eyre::bail!(
|
||||
"Invalid number of bodies received. Expected: 1. Received: {}",
|
||||
"Invalid number of headers received. Expected: 1. Received: {}",
|
||||
result.len()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -87,9 +87,6 @@ impl<C: ChainSpecParser> Command<C> {
|
||||
.unwrap_or_default();
|
||||
writer.prune_transaction_senders(to_delete, 0)?;
|
||||
}
|
||||
StaticFileSegment::AccountChangeSets => {
|
||||
writer.prune_account_changesets(highest_block)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::setup;
|
||||
use reth_consensus::{noop::NoopConsensus, FullConsensus};
|
||||
use reth_consensus::{noop::NoopConsensus, ConsensusError, FullConsensus};
|
||||
use reth_db::DatabaseEnv;
|
||||
use reth_db_api::{
|
||||
cursor::DbCursorRO, database::Database, table::TableImporter, tables, transaction::DbTx,
|
||||
@@ -28,7 +28,7 @@ pub(crate) async fn dump_execution_stage<N, E, C>(
|
||||
where
|
||||
N: ProviderNodeTypes<DB = Arc<DatabaseEnv>>,
|
||||
E: ConfigureEvm<Primitives = N::Primitives> + 'static,
|
||||
C: FullConsensus<E::Primitives> + 'static,
|
||||
C: FullConsensus<E::Primitives, Error = ConsensusError> + 'static,
|
||||
{
|
||||
let (output_db, tip_block_number) = setup(from, to, &output_datadir.db(), db_tool)?;
|
||||
|
||||
@@ -169,7 +169,7 @@ fn dry_run<N, E, C>(
|
||||
where
|
||||
N: ProviderNodeTypes,
|
||||
E: ConfigureEvm<Primitives = N::Primitives> + 'static,
|
||||
C: FullConsensus<E::Primitives> + 'static,
|
||||
C: FullConsensus<E::Primitives, Error = ConsensusError> + 'static,
|
||||
{
|
||||
info!(target: "reth::cli", "Executing stage. [dry-run]");
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use super::setup;
|
||||
use alloy_primitives::{Address, BlockNumber};
|
||||
use eyre::Result;
|
||||
use reth_config::config::EtlConfig;
|
||||
use reth_consensus::FullConsensus;
|
||||
use reth_consensus::{ConsensusError, FullConsensus};
|
||||
use reth_db::DatabaseEnv;
|
||||
use reth_db_api::{database::Database, models::BlockNumberAddress, table::TableImporter, tables};
|
||||
use reth_db_common::DbTool;
|
||||
@@ -31,7 +31,7 @@ pub(crate) async fn dump_merkle_stage<N>(
|
||||
output_datadir: ChainPath<DataDirPath>,
|
||||
should_run: bool,
|
||||
evm_config: impl ConfigureEvm<Primitives = N::Primitives>,
|
||||
consensus: impl FullConsensus<N::Primitives> + 'static,
|
||||
consensus: impl FullConsensus<N::Primitives, Error = ConsensusError> + 'static,
|
||||
) -> Result<()>
|
||||
where
|
||||
N: ProviderNodeTypes<DB = Arc<DatabaseEnv>>,
|
||||
@@ -79,7 +79,7 @@ fn unwind_and_copy<N: ProviderNodeTypes>(
|
||||
tip_block_number: u64,
|
||||
output_db: &DatabaseEnv,
|
||||
evm_config: impl ConfigureEvm<Primitives = N::Primitives>,
|
||||
consensus: impl FullConsensus<N::Primitives> + 'static,
|
||||
consensus: impl FullConsensus<N::Primitives, Error = ConsensusError> + 'static,
|
||||
) -> eyre::Result<()> {
|
||||
let (from, to) = range;
|
||||
let provider = db_tool.provider_factory.database_provider_rw()?;
|
||||
|
||||
@@ -153,7 +153,6 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>
|
||||
}
|
||||
})
|
||||
.build(),
|
||||
data_dir.pprof_dumps(),
|
||||
);
|
||||
|
||||
MetricServer::new(config).serve().await?;
|
||||
|
||||
@@ -22,6 +22,7 @@ pub const DEFAULT_BLOCK_INTERVAL: usize = 5;
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct Config {
|
||||
/// Configuration for each stage in the pipeline.
|
||||
// TODO(onbjerg): Can we make this easier to maintain when we add/remove stages?
|
||||
pub stages: StageConfig,
|
||||
/// Configuration for pruning.
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
@@ -437,8 +438,6 @@ pub struct BlocksPerFileConfig {
|
||||
pub receipts: Option<u64>,
|
||||
/// Number of blocks per file for the transaction senders segment.
|
||||
pub transaction_senders: Option<u64>,
|
||||
/// Number of blocks per file for the account changesets segment.
|
||||
pub account_change_sets: Option<u64>,
|
||||
}
|
||||
|
||||
impl StaticFilesConfig {
|
||||
@@ -446,13 +445,8 @@ impl StaticFilesConfig {
|
||||
///
|
||||
/// Returns an error if any blocks per file value is zero.
|
||||
pub fn validate(&self) -> eyre::Result<()> {
|
||||
let BlocksPerFileConfig {
|
||||
headers,
|
||||
transactions,
|
||||
receipts,
|
||||
transaction_senders,
|
||||
account_change_sets,
|
||||
} = self.blocks_per_file;
|
||||
let BlocksPerFileConfig { headers, transactions, receipts, transaction_senders } =
|
||||
self.blocks_per_file;
|
||||
eyre::ensure!(headers != Some(0), "Headers segment blocks per file must be greater than 0");
|
||||
eyre::ensure!(
|
||||
transactions != Some(0),
|
||||
@@ -466,22 +460,13 @@ impl StaticFilesConfig {
|
||||
transaction_senders != Some(0),
|
||||
"Transaction senders segment blocks per file must be greater than 0"
|
||||
);
|
||||
eyre::ensure!(
|
||||
account_change_sets != Some(0),
|
||||
"Account changesets segment blocks per file must be greater than 0"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Converts the blocks per file configuration into a [`HashMap`] per segment.
|
||||
pub fn as_blocks_per_file_map(&self) -> HashMap<StaticFileSegment, u64> {
|
||||
let BlocksPerFileConfig {
|
||||
headers,
|
||||
transactions,
|
||||
receipts,
|
||||
transaction_senders,
|
||||
account_change_sets,
|
||||
} = self.blocks_per_file;
|
||||
let BlocksPerFileConfig { headers, transactions, receipts, transaction_senders } =
|
||||
self.blocks_per_file;
|
||||
|
||||
let mut map = HashMap::new();
|
||||
// Iterating over all possible segments allows us to do an exhaustive match here,
|
||||
@@ -492,7 +477,6 @@ impl StaticFilesConfig {
|
||||
StaticFileSegment::Transactions => transactions,
|
||||
StaticFileSegment::Receipts => receipts,
|
||||
StaticFileSegment::TransactionSenders => transaction_senders,
|
||||
StaticFileSegment::AccountChangeSets => account_change_sets,
|
||||
};
|
||||
|
||||
if let Some(blocks_per_file) = blocks_per_file {
|
||||
|
||||
@@ -49,12 +49,15 @@ pub trait FullConsensus<N: NodePrimitives>: Consensus<N::Block> {
|
||||
/// Consensus is a protocol that chooses canonical chain.
|
||||
#[auto_impl::auto_impl(&, Arc)]
|
||||
pub trait Consensus<B: Block>: HeaderValidator<B::Header> {
|
||||
/// The error type related to consensus.
|
||||
type Error;
|
||||
|
||||
/// Ensures that body field values match the header.
|
||||
fn validate_body_against_header(
|
||||
&self,
|
||||
body: &B::Body,
|
||||
header: &SealedHeader<B::Header>,
|
||||
) -> Result<(), ConsensusError>;
|
||||
) -> Result<(), Self::Error>;
|
||||
|
||||
/// Validate a block disregarding world state, i.e. things that can be checked before sender
|
||||
/// recovery and execution.
|
||||
@@ -66,7 +69,7 @@ pub trait Consensus<B: Block>: HeaderValidator<B::Header> {
|
||||
/// **This should not be called for the genesis block**.
|
||||
///
|
||||
/// Note: validating blocks does not include other validations of the Consensus
|
||||
fn validate_block_pre_execution(&self, block: &SealedBlock<B>) -> Result<(), ConsensusError>;
|
||||
fn validate_block_pre_execution(&self, block: &SealedBlock<B>) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
/// `HeaderValidator` is a protocol that validates headers and their relationships.
|
||||
@@ -132,7 +135,7 @@ pub enum ConsensusError {
|
||||
/// The gas limit in the block header.
|
||||
gas_limit: u64,
|
||||
},
|
||||
/// Error when the gas limit is more than the maximum allowed.
|
||||
/// Error when the gas the gas limit is more than the maximum allowed.
|
||||
#[error(
|
||||
"header gas limit ({gas_limit}) exceed the maximum allowed gas limit ({MAXIMUM_GAS_LIMIT_BLOCK})"
|
||||
)]
|
||||
|
||||
@@ -55,17 +55,19 @@ impl<H> HeaderValidator<H> for NoopConsensus {
|
||||
}
|
||||
|
||||
impl<B: Block> Consensus<B> for NoopConsensus {
|
||||
type Error = ConsensusError;
|
||||
|
||||
/// Validates body against header (no-op implementation).
|
||||
fn validate_body_against_header(
|
||||
&self,
|
||||
_body: &B::Body,
|
||||
_header: &SealedHeader<B::Header>,
|
||||
) -> Result<(), ConsensusError> {
|
||||
) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates block before execution (no-op implementation).
|
||||
fn validate_block_pre_execution(&self, _block: &SealedBlock<B>) -> Result<(), ConsensusError> {
|
||||
fn validate_block_pre_execution(&self, _block: &SealedBlock<B>) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,11 +61,13 @@ impl<N: NodePrimitives> FullConsensus<N> for TestConsensus {
|
||||
}
|
||||
|
||||
impl<B: Block> Consensus<B> for TestConsensus {
|
||||
type Error = ConsensusError;
|
||||
|
||||
fn validate_body_against_header(
|
||||
&self,
|
||||
_body: &B::Body,
|
||||
_header: &SealedHeader<B::Header>,
|
||||
) -> Result<(), ConsensusError> {
|
||||
) -> Result<(), Self::Error> {
|
||||
if self.fail_body_against_header() {
|
||||
Err(ConsensusError::BaseFeeMissing)
|
||||
} else {
|
||||
@@ -73,7 +75,7 @@ impl<B: Block> Consensus<B> for TestConsensus {
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_block_pre_execution(&self, _block: &SealedBlock<B>) -> Result<(), ConsensusError> {
|
||||
fn validate_block_pre_execution(&self, _block: &SealedBlock<B>) -> Result<(), Self::Error> {
|
||||
if self.fail_validation() {
|
||||
Err(ConsensusError::BaseFeeMissing)
|
||||
} else {
|
||||
|
||||
@@ -89,8 +89,8 @@ where
|
||||
match res {
|
||||
Ok(block) => {
|
||||
if tx.send((self.convert)(block)).await.is_err() {
|
||||
// Channel closed - receiver dropped, exit completely.
|
||||
return;
|
||||
// Channel closed.
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -107,7 +107,7 @@ where
|
||||
debug!(
|
||||
target: "consensus::debug-client",
|
||||
url=%self.url,
|
||||
"Re-establishing block subscription",
|
||||
"Re-estbalishing block subscription",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ use reth_node_builder::{
|
||||
PayloadTypes,
|
||||
};
|
||||
use reth_node_core::args::{DiscoveryArgs, NetworkArgs, RpcServerArgs};
|
||||
use reth_primitives_traits::AlloyBlockHeader;
|
||||
use reth_provider::providers::BlockchainProvider;
|
||||
use reth_rpc_server_types::RpcModuleSelection;
|
||||
use reth_tasks::TaskManager;
|
||||
@@ -158,8 +157,8 @@ where
|
||||
.await?;
|
||||
|
||||
let node = NodeTestContext::new(node, self.attributes_generator).await?;
|
||||
let genesis_number = self.chain_spec.genesis_header().number();
|
||||
let genesis = node.block_hash(genesis_number);
|
||||
|
||||
let genesis = node.block_hash(0);
|
||||
node.update_forkchoice(genesis, genesis).await?;
|
||||
|
||||
eyre::Ok(node)
|
||||
|
||||
@@ -113,6 +113,7 @@ pub async fn setup_engine_with_chain_import(
|
||||
let rocksdb_dir_path = datadir.join("rocksdb");
|
||||
|
||||
// Initialize the database using init_db (same as CLI import command)
|
||||
// Use the same database arguments as the node will use
|
||||
let db_args = reth_node_core::args::DatabaseArgs::default().database_args();
|
||||
let db_env = reth_db::init_db(&db_path, db_args)?;
|
||||
let db = Arc::new(db_env);
|
||||
@@ -316,8 +317,7 @@ mod tests {
|
||||
|
||||
// Import the chain
|
||||
{
|
||||
let db_args = reth_node_core::args::DatabaseArgs::default().database_args();
|
||||
let db_env = reth_db::init_db(&db_path, db_args).unwrap();
|
||||
let db_env = reth_db::init_db(&db_path, DatabaseArguments::default()).unwrap();
|
||||
let db = Arc::new(db_env);
|
||||
|
||||
let provider_factory: ProviderFactory<
|
||||
@@ -475,8 +475,7 @@ mod tests {
|
||||
let datadir = temp_dir.path().join("datadir");
|
||||
std::fs::create_dir_all(&datadir).unwrap();
|
||||
let db_path = datadir.join("db");
|
||||
let db_args = reth_node_core::args::DatabaseArgs::default().database_args();
|
||||
let db_env = reth_db::init_db(&db_path, db_args).unwrap();
|
||||
let db_env = reth_db::init_db(&db_path, DatabaseArguments::default()).unwrap();
|
||||
let db = Arc::new(reth_db::test_utils::TempDatabase::new(db_env, db_path));
|
||||
|
||||
// Create static files path
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
use crate::testsuite::{Action, Environment};
|
||||
use alloy_primitives::B256;
|
||||
use alloy_rpc_types_engine::{ExecutionPayloadV3, PayloadStatusEnum};
|
||||
use alloy_rpc_types_engine::{
|
||||
ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, PayloadStatusEnum,
|
||||
};
|
||||
use alloy_rpc_types_eth::{Block, Header, Receipt, Transaction, TransactionRequest};
|
||||
use eyre::Result;
|
||||
use futures_util::future::BoxFuture;
|
||||
@@ -129,10 +131,7 @@ where
|
||||
})?;
|
||||
|
||||
// Convert block to ExecutionPayloadV3
|
||||
let payload = ExecutionPayloadV3::from_block_unchecked(
|
||||
block.hash(),
|
||||
&block.map_transactions(|tx| tx.inner).into_consensus(),
|
||||
);
|
||||
let payload = block_to_payload_v3(block.clone());
|
||||
|
||||
// Send the payload to the target node
|
||||
let target_engine = env.node_clients[self.node_idx].engine.http_client();
|
||||
@@ -328,3 +327,32 @@ where
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to convert a block to `ExecutionPayloadV3`
|
||||
fn block_to_payload_v3(block: Block) -> ExecutionPayloadV3 {
|
||||
use alloy_primitives::U256;
|
||||
|
||||
ExecutionPayloadV3 {
|
||||
payload_inner: ExecutionPayloadV2 {
|
||||
payload_inner: ExecutionPayloadV1 {
|
||||
parent_hash: block.header.inner.parent_hash,
|
||||
fee_recipient: block.header.inner.beneficiary,
|
||||
state_root: block.header.inner.state_root,
|
||||
receipts_root: block.header.inner.receipts_root,
|
||||
logs_bloom: block.header.inner.logs_bloom,
|
||||
prev_randao: block.header.inner.mix_hash,
|
||||
block_number: block.header.inner.number,
|
||||
gas_limit: block.header.inner.gas_limit,
|
||||
gas_used: block.header.inner.gas_used,
|
||||
timestamp: block.header.inner.timestamp,
|
||||
extra_data: block.header.inner.extra_data.clone(),
|
||||
base_fee_per_gas: U256::from(block.header.inner.base_fee_per_gas.unwrap_or(0)),
|
||||
block_hash: block.header.hash,
|
||||
transactions: vec![], // No transactions needed for buffering tests
|
||||
},
|
||||
withdrawals: block.withdrawals.unwrap_or_default().to_vec(),
|
||||
},
|
||||
blob_gas_used: block.header.inner.blob_gas_used.unwrap_or(0),
|
||||
excess_blob_gas: block.header.inner.excess_blob_gas.unwrap_or(0),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use pretty_assertions::Comparison;
|
||||
use reth_engine_primitives::InvalidBlockHook;
|
||||
use reth_evm::{execute::Executor, ConfigureEvm};
|
||||
use reth_primitives_traits::{NodePrimitives, RecoveredBlock, SealedHeader};
|
||||
use reth_provider::{BlockExecutionOutput, StateProvider, StateProviderBox, StateProviderFactory};
|
||||
use reth_provider::{BlockExecutionOutput, StateProvider, StateProviderFactory};
|
||||
use reth_revm::{
|
||||
database::StateProviderDatabase,
|
||||
db::{BundleState, State},
|
||||
@@ -80,13 +80,13 @@ fn sort_bundle_state_for_comparison(bundle_state: &BundleState) -> BundleStateSo
|
||||
BundleAccountSorted {
|
||||
info: acc.info.clone(),
|
||||
original_info: acc.original_info.clone(),
|
||||
storage: acc.storage.iter().map(|(k, v)| (*k, *v)).collect(),
|
||||
storage: BTreeMap::from_iter(acc.storage.clone()),
|
||||
status: acc.status,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
contracts: bundle_state.contracts.iter().map(|(k, v)| (*k, v.clone())).collect(),
|
||||
contracts: BTreeMap::from_iter(bundle_state.contracts.clone()),
|
||||
reverts: bundle_state
|
||||
.reverts
|
||||
.iter()
|
||||
@@ -98,7 +98,7 @@ fn sort_bundle_state_for_comparison(bundle_state: &BundleState) -> BundleStateSo
|
||||
*addr,
|
||||
AccountRevertSorted {
|
||||
account: rev.account.clone(),
|
||||
storage: rev.storage.iter().map(|(k, v)| (*k, *v)).collect(),
|
||||
storage: BTreeMap::from_iter(rev.storage.clone()),
|
||||
previous_status: rev.previous_status,
|
||||
wipe_storage: rev.wipe_storage,
|
||||
},
|
||||
@@ -114,7 +114,7 @@ fn sort_bundle_state_for_comparison(bundle_state: &BundleState) -> BundleStateSo
|
||||
|
||||
/// Extracts execution data including codes, preimages, and hashed state from database
|
||||
fn collect_execution_data(
|
||||
mut db: State<StateProviderDatabase<StateProviderBox>>,
|
||||
mut db: State<StateProviderDatabase<Box<dyn StateProvider>>>,
|
||||
) -> eyre::Result<CollectionResult> {
|
||||
let bundle_state = db.take_bundle();
|
||||
let mut codes = BTreeMap::new();
|
||||
@@ -530,7 +530,9 @@ mod tests {
|
||||
// Create a State with StateProviderTest
|
||||
let state_provider = StateProviderTest::default();
|
||||
let mut state = State::builder()
|
||||
.with_database(StateProviderDatabase::new(Box::new(state_provider) as StateProviderBox))
|
||||
.with_database(StateProviderDatabase::new(
|
||||
Box::new(state_provider) as Box<dyn StateProvider>
|
||||
))
|
||||
.with_bundle_update()
|
||||
.build();
|
||||
|
||||
|
||||
@@ -135,8 +135,6 @@ pub struct TreeConfig {
|
||||
storage_worker_count: usize,
|
||||
/// Number of account proof worker threads.
|
||||
account_worker_count: usize,
|
||||
/// Whether to enable V2 storage proofs.
|
||||
enable_proof_v2: bool,
|
||||
}
|
||||
|
||||
impl Default for TreeConfig {
|
||||
@@ -165,7 +163,6 @@ impl Default for TreeConfig {
|
||||
allow_unwind_canonical_header: false,
|
||||
storage_worker_count: default_storage_worker_count(),
|
||||
account_worker_count: default_account_worker_count(),
|
||||
enable_proof_v2: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,7 +194,6 @@ impl TreeConfig {
|
||||
allow_unwind_canonical_header: bool,
|
||||
storage_worker_count: usize,
|
||||
account_worker_count: usize,
|
||||
enable_proof_v2: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
persistence_threshold,
|
||||
@@ -223,7 +219,6 @@ impl TreeConfig {
|
||||
allow_unwind_canonical_header,
|
||||
storage_worker_count,
|
||||
account_worker_count,
|
||||
enable_proof_v2,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,15 +500,4 @@ impl TreeConfig {
|
||||
self.account_worker_count = account_worker_count.max(MIN_WORKER_COUNT);
|
||||
self
|
||||
}
|
||||
|
||||
/// Return whether V2 storage proofs are enabled.
|
||||
pub const fn enable_proof_v2(&self) -> bool {
|
||||
self.enable_proof_v2
|
||||
}
|
||||
|
||||
/// Setter for whether to enable V2 storage proofs.
|
||||
pub const fn with_enable_proof_v2(mut self, enable_proof_v2: bool) -> Self {
|
||||
self.enable_proof_v2 = enable_proof_v2;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use futures::{Stream, StreamExt};
|
||||
use pin_project::pin_project;
|
||||
use reth_chainspec::EthChainSpec;
|
||||
use reth_consensus::FullConsensus;
|
||||
use reth_consensus::{ConsensusError, FullConsensus};
|
||||
use reth_engine_primitives::{BeaconEngineMessage, ConsensusEngineEvent};
|
||||
use reth_engine_tree::{
|
||||
backfill::PipelineSync,
|
||||
@@ -70,7 +70,7 @@ where
|
||||
/// Constructor for `EngineService`.
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub fn new<V, C>(
|
||||
consensus: Arc<dyn FullConsensus<N::Primitives>>,
|
||||
consensus: Arc<dyn FullConsensus<N::Primitives, Error = ConsensusError>>,
|
||||
chain_spec: Arc<N::ChainSpec>,
|
||||
client: Client,
|
||||
incoming_requests: EngineMessageStream<N::Payload>,
|
||||
|
||||
@@ -52,7 +52,6 @@ futures.workspace = true
|
||||
thiserror.workspace = true
|
||||
tokio = { workspace = true, features = ["rt", "rt-multi-thread", "sync", "macros"] }
|
||||
mini-moka = { workspace = true, features = ["sync"] }
|
||||
moka = { workspace = true, features = ["sync"] }
|
||||
smallvec.workspace = true
|
||||
|
||||
# metrics
|
||||
|
||||
@@ -128,12 +128,12 @@ we send them along with the state updates to the [Sparse Trie Task](#sparse-trie
|
||||
|
||||
### Finishing the calculation
|
||||
|
||||
Once all transactions are executed, the [Engine](#engine) sends a `StateRootMessage::FinishedStateUpdates` message
|
||||
Once all transactions are executed, the [Engine](#engine) sends a `StateRootMessage::FinishStateUpdates` message
|
||||
to the State Root Task, marking the end of receiving state updates.
|
||||
|
||||
Every time we receive a new proof from the [MultiProof Manager](#multiproof-manager), we also check
|
||||
the following conditions:
|
||||
1. Are all updates received? (`StateRootMessage::FinishedStateUpdates` was sent)
|
||||
1. Are all updates received? (`StateRootMessage::FinishStateUpdates` was sent)
|
||||
2. Is `ProofSequencer` empty? (no proofs are pending for sequencing)
|
||||
3. Are all proofs that were sent to the [`MultiProofManager::spawn_or_queue`](#multiproof-manager) finished
|
||||
calculating and were sent to the [Sparse Trie Task](#sparse-trie-task)?
|
||||
|
||||
@@ -47,7 +47,7 @@ impl BackfillSyncState {
|
||||
}
|
||||
|
||||
/// Backfill sync mode functionality.
|
||||
pub trait BackfillSync: Send {
|
||||
pub trait BackfillSync: Send + Sync {
|
||||
/// Performs a backfill action.
|
||||
fn on_action(&mut self, action: BackfillAction);
|
||||
|
||||
|
||||
@@ -219,19 +219,10 @@ pub enum HandlerEvent<T> {
|
||||
}
|
||||
|
||||
/// Internal events issued by the [`ChainOrchestrator`].
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum FromOrchestrator {
|
||||
/// Invoked when backfill sync finished
|
||||
BackfillSyncFinished(ControlFlow),
|
||||
/// Invoked when backfill sync started
|
||||
BackfillSyncStarted,
|
||||
/// Gracefully terminate the engine service.
|
||||
///
|
||||
/// When this variant is received, the engine will persist all remaining in-memory blocks
|
||||
/// to disk before shutting down. Once persistence is complete, a signal is sent through
|
||||
/// the oneshot channel to notify the caller.
|
||||
Terminate {
|
||||
/// Channel to signal termination completion.
|
||||
tx: tokio::sync::oneshot::Sender<()>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{engine::DownloadRequest, metrics::BlockDownloaderMetrics};
|
||||
use alloy_consensus::BlockHeader;
|
||||
use alloy_primitives::B256;
|
||||
use futures::FutureExt;
|
||||
use reth_consensus::Consensus;
|
||||
use reth_consensus::{Consensus, ConsensusError};
|
||||
use reth_network_p2p::{
|
||||
full_block::{FetchFullBlockFuture, FetchFullBlockRangeFuture, FullBlockClient},
|
||||
BlockClient,
|
||||
@@ -81,7 +81,7 @@ where
|
||||
B: Block,
|
||||
{
|
||||
/// Create a new instance
|
||||
pub fn new(client: Client, consensus: Arc<dyn Consensus<B>>) -> Self {
|
||||
pub fn new(client: Client, consensus: Arc<dyn Consensus<B, Error = ConsensusError>>) -> Self {
|
||||
Self {
|
||||
full_block_client: FullBlockClient::new(client, consensus),
|
||||
inflight_full_block_requests: Vec::new(),
|
||||
|
||||
@@ -6,7 +6,6 @@ use crate::{
|
||||
download::{BlockDownloader, DownloadAction, DownloadOutcome},
|
||||
};
|
||||
use alloy_primitives::B256;
|
||||
use crossbeam_channel::Sender;
|
||||
use futures::{Stream, StreamExt};
|
||||
use reth_chain_state::ExecutedBlock;
|
||||
use reth_engine_primitives::{BeaconEngineMessage, ConsensusEngineEvent};
|
||||
@@ -16,6 +15,7 @@ use reth_primitives_traits::{Block, NodePrimitives, SealedBlock};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fmt::Display,
|
||||
sync::mpsc::Sender,
|
||||
task::{ready, Context, Poll},
|
||||
};
|
||||
use tokio::sync::mpsc::UnboundedReceiver;
|
||||
|
||||
@@ -19,8 +19,6 @@ pub(crate) struct PersistenceMetrics {
|
||||
pub(crate) remove_blocks_above_duration_seconds: Histogram,
|
||||
/// How long it took for blocks to be saved
|
||||
pub(crate) save_blocks_duration_seconds: Histogram,
|
||||
/// How many blocks we persist at once.
|
||||
pub(crate) save_blocks_block_count: Histogram,
|
||||
/// How long it took for blocks to be pruned
|
||||
pub(crate) prune_before_duration_seconds: Histogram,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::metrics::PersistenceMetrics;
|
||||
use alloy_consensus::BlockHeader;
|
||||
use alloy_eips::BlockNumHash;
|
||||
use crossbeam_channel::Sender as CrossbeamSender;
|
||||
use reth_chain_state::ExecutedBlock;
|
||||
use reth_errors::ProviderError;
|
||||
use reth_ethereum_primitives::EthPrimitives;
|
||||
@@ -16,6 +16,7 @@ use std::{
|
||||
time::Instant,
|
||||
};
|
||||
use thiserror::Error;
|
||||
use tokio::sync::oneshot;
|
||||
use tracing::{debug, error};
|
||||
|
||||
/// Writes parts of reth's in memory tree state to the database and static files.
|
||||
@@ -141,25 +142,27 @@ where
|
||||
&self,
|
||||
blocks: Vec<ExecutedBlock<N::Primitives>>,
|
||||
) -> Result<Option<BlockNumHash>, PersistenceError> {
|
||||
let first_block = blocks.first().map(|b| b.recovered_block.num_hash());
|
||||
let last_block = blocks.last().map(|b| b.recovered_block.num_hash());
|
||||
let block_count = blocks.len();
|
||||
debug!(target: "engine::persistence", ?block_count, first=?first_block, last=?last_block, "Saving range of blocks");
|
||||
let first_block_hash = blocks.first().map(|b| b.recovered_block.num_hash());
|
||||
let last_block_hash = blocks.last().map(|b| b.recovered_block.num_hash());
|
||||
debug!(target: "engine::persistence", first=?first_block_hash, last=?last_block_hash, "Saving range of blocks");
|
||||
|
||||
let start_time = Instant::now();
|
||||
let last_block_hash_num = blocks.last().map(|block| BlockNumHash {
|
||||
hash: block.recovered_block().hash(),
|
||||
number: block.recovered_block().header().number(),
|
||||
});
|
||||
|
||||
if last_block.is_some() {
|
||||
if last_block_hash_num.is_some() {
|
||||
let provider_rw = self.provider.database_provider_rw()?;
|
||||
|
||||
provider_rw.save_blocks(blocks)?;
|
||||
provider_rw.commit()?;
|
||||
}
|
||||
|
||||
debug!(target: "engine::persistence", first=?first_block, last=?last_block, "Saved range of blocks");
|
||||
debug!(target: "engine::persistence", first=?first_block_hash, last=?last_block_hash, "Saved range of blocks");
|
||||
|
||||
self.metrics.save_blocks_block_count.record(block_count as f64);
|
||||
self.metrics.save_blocks_duration_seconds.record(start_time.elapsed());
|
||||
Ok(last_block)
|
||||
Ok(last_block_hash_num)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,13 +186,13 @@ pub enum PersistenceAction<N: NodePrimitives = EthPrimitives> {
|
||||
///
|
||||
/// First, header, transaction, and receipt-related data should be written to static files.
|
||||
/// Then the execution history-related data will be written to the database.
|
||||
SaveBlocks(Vec<ExecutedBlock<N>>, CrossbeamSender<Option<BlockNumHash>>),
|
||||
SaveBlocks(Vec<ExecutedBlock<N>>, oneshot::Sender<Option<BlockNumHash>>),
|
||||
|
||||
/// Removes block data above the given block number from the database.
|
||||
///
|
||||
/// This will first update checkpoints from the database, then remove actual block data from
|
||||
/// static files.
|
||||
RemoveBlocksAbove(u64, CrossbeamSender<Option<BlockNumHash>>),
|
||||
RemoveBlocksAbove(u64, oneshot::Sender<Option<BlockNumHash>>),
|
||||
|
||||
/// Update the persisted finalized block on disk
|
||||
SaveFinalizedBlock(u64),
|
||||
@@ -261,7 +264,7 @@ impl<T: NodePrimitives> PersistenceHandle<T> {
|
||||
pub fn save_blocks(
|
||||
&self,
|
||||
blocks: Vec<ExecutedBlock<T>>,
|
||||
tx: CrossbeamSender<Option<BlockNumHash>>,
|
||||
tx: oneshot::Sender<Option<BlockNumHash>>,
|
||||
) -> Result<(), SendError<PersistenceAction<T>>> {
|
||||
self.send_action(PersistenceAction::SaveBlocks(blocks, tx))
|
||||
}
|
||||
@@ -290,7 +293,7 @@ impl<T: NodePrimitives> PersistenceHandle<T> {
|
||||
pub fn remove_blocks_above(
|
||||
&self,
|
||||
block_num: u64,
|
||||
tx: CrossbeamSender<Option<BlockNumHash>>,
|
||||
tx: oneshot::Sender<Option<BlockNumHash>>,
|
||||
) -> Result<(), SendError<PersistenceAction<T>>> {
|
||||
self.send_action(PersistenceAction::RemoveBlocksAbove(block_num, tx))
|
||||
}
|
||||
@@ -319,22 +322,22 @@ mod tests {
|
||||
PersistenceHandle::<EthPrimitives>::spawn_service(provider, pruner, sync_metrics_tx)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_save_blocks_empty() {
|
||||
#[tokio::test]
|
||||
async fn test_save_blocks_empty() {
|
||||
reth_tracing::init_test_tracing();
|
||||
let persistence_handle = default_persistence_handle();
|
||||
|
||||
let blocks = vec![];
|
||||
let (tx, rx) = crossbeam_channel::bounded(1);
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
persistence_handle.save_blocks(blocks, tx).unwrap();
|
||||
|
||||
let hash = rx.recv().unwrap();
|
||||
let hash = rx.await.unwrap();
|
||||
assert_eq!(hash, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_save_blocks_single_block() {
|
||||
#[tokio::test]
|
||||
async fn test_save_blocks_single_block() {
|
||||
reth_tracing::init_test_tracing();
|
||||
let persistence_handle = default_persistence_handle();
|
||||
let block_number = 0;
|
||||
@@ -344,35 +347,37 @@ mod tests {
|
||||
let block_hash = executed.recovered_block().hash();
|
||||
|
||||
let blocks = vec![executed];
|
||||
let (tx, rx) = crossbeam_channel::bounded(1);
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
persistence_handle.save_blocks(blocks, tx).unwrap();
|
||||
|
||||
let BlockNumHash { hash: actual_hash, number: _ } = rx
|
||||
.recv_timeout(std::time::Duration::from_secs(10))
|
||||
.expect("test timed out")
|
||||
.expect("no hash returned");
|
||||
let BlockNumHash { hash: actual_hash, number: _ } =
|
||||
tokio::time::timeout(std::time::Duration::from_secs(10), rx)
|
||||
.await
|
||||
.expect("test timed out")
|
||||
.expect("channel closed unexpectedly")
|
||||
.expect("no hash returned");
|
||||
|
||||
assert_eq!(block_hash, actual_hash);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_save_blocks_multiple_blocks() {
|
||||
#[tokio::test]
|
||||
async fn test_save_blocks_multiple_blocks() {
|
||||
reth_tracing::init_test_tracing();
|
||||
let persistence_handle = default_persistence_handle();
|
||||
|
||||
let mut test_block_builder = TestBlockBuilder::eth();
|
||||
let blocks = test_block_builder.get_executed_blocks(0..5).collect::<Vec<_>>();
|
||||
let last_hash = blocks.last().unwrap().recovered_block().hash();
|
||||
let (tx, rx) = crossbeam_channel::bounded(1);
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
persistence_handle.save_blocks(blocks, tx).unwrap();
|
||||
let BlockNumHash { hash: actual_hash, number: _ } = rx.recv().unwrap().unwrap();
|
||||
let BlockNumHash { hash: actual_hash, number: _ } = rx.await.unwrap().unwrap();
|
||||
assert_eq!(last_hash, actual_hash);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_save_blocks_multiple_calls() {
|
||||
#[tokio::test]
|
||||
async fn test_save_blocks_multiple_calls() {
|
||||
reth_tracing::init_test_tracing();
|
||||
let persistence_handle = default_persistence_handle();
|
||||
|
||||
@@ -381,11 +386,11 @@ mod tests {
|
||||
for range in ranges {
|
||||
let blocks = test_block_builder.get_executed_blocks(range).collect::<Vec<_>>();
|
||||
let last_hash = blocks.last().unwrap().recovered_block().hash();
|
||||
let (tx, rx) = crossbeam_channel::bounded(1);
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
persistence_handle.save_blocks(blocks, tx).unwrap();
|
||||
|
||||
let BlockNumHash { hash: actual_hash, number: _ } = rx.recv().unwrap().unwrap();
|
||||
let BlockNumHash { hash: actual_hash, number: _ } = rx.await.unwrap().unwrap();
|
||||
assert_eq!(last_hash, actual_hash);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
|
||||
/// * [`BlockBuffer::remove_old_blocks`] to remove old blocks that precede the finalized number.
|
||||
///
|
||||
/// Note: Buffer is limited by number of blocks that it can contain and eviction of the block
|
||||
/// is done in FIFO order (oldest inserted block is evicted first).
|
||||
/// is done by last recently used block.
|
||||
#[derive(Debug)]
|
||||
pub struct BlockBuffer<B: Block> {
|
||||
/// All blocks in the buffer stored by their block hash.
|
||||
@@ -66,14 +66,9 @@ impl<B: Block> BlockBuffer<B> {
|
||||
pub fn insert_block(&mut self, block: SealedBlock<B>) {
|
||||
let hash = block.hash();
|
||||
|
||||
match self.blocks.entry(hash) {
|
||||
std::collections::hash_map::Entry::Occupied(_) => return,
|
||||
std::collections::hash_map::Entry::Vacant(entry) => {
|
||||
self.parent_to_child.entry(block.parent_hash()).or_default().insert(hash);
|
||||
self.earliest_blocks.entry(block.number()).or_default().insert(hash);
|
||||
entry.insert(block);
|
||||
}
|
||||
};
|
||||
self.parent_to_child.entry(block.parent_hash()).or_default().insert(hash);
|
||||
self.earliest_blocks.entry(block.number()).or_default().insert(hash);
|
||||
self.blocks.insert(hash, block);
|
||||
|
||||
// Add block to FIFO queue and handle eviction if needed
|
||||
if self.block_queue.len() >= self.max_blocks {
|
||||
|
||||
@@ -31,9 +31,6 @@ pub(crate) struct CachedStateProvider<S> {
|
||||
|
||||
/// Metrics for the cached state provider
|
||||
metrics: CachedStateMetrics,
|
||||
|
||||
/// If prewarm enabled we populate every cache miss
|
||||
prewarm: bool,
|
||||
}
|
||||
|
||||
impl<S> CachedStateProvider<S>
|
||||
@@ -42,32 +39,12 @@ where
|
||||
{
|
||||
/// Creates a new [`CachedStateProvider`] from an [`ExecutionCache`], state provider, and
|
||||
/// [`CachedStateMetrics`].
|
||||
pub(crate) const fn new(
|
||||
pub(crate) const fn new_with_caches(
|
||||
state_provider: S,
|
||||
caches: ExecutionCache,
|
||||
metrics: CachedStateMetrics,
|
||||
) -> Self {
|
||||
Self { state_provider, caches, metrics, prewarm: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> CachedStateProvider<S> {
|
||||
/// Enables pre-warm mode so that every cache miss is populated.
|
||||
///
|
||||
/// This is only relevant for pre-warm transaction execution with the intention to pre-populate
|
||||
/// the cache with data for regular block execution. During regular block execution the
|
||||
/// cache doesn't need to be populated because the actual EVM database
|
||||
/// [`State`](revm::database::State) also caches internally during block execution and the cache
|
||||
/// is then updated after the block with the entire [`BundleState`] output of that block which
|
||||
/// contains all accessed accounts,code,storage. See also [`ExecutionCache::insert_state`].
|
||||
pub(crate) const fn prewarm(mut self) -> Self {
|
||||
self.prewarm = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns whether this provider should pre-warm cache misses.
|
||||
const fn is_prewarm(&self) -> bool {
|
||||
self.prewarm
|
||||
Self { state_provider, caches, metrics }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,10 +123,7 @@ impl<S: AccountReader> AccountReader for CachedStateProvider<S> {
|
||||
self.metrics.account_cache_misses.increment(1);
|
||||
|
||||
let res = self.state_provider.basic_account(address)?;
|
||||
|
||||
if self.is_prewarm() {
|
||||
self.caches.account_cache.insert(*address, res);
|
||||
}
|
||||
self.caches.account_cache.insert(*address, res);
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
@@ -174,19 +148,15 @@ impl<S: StateProvider> StateProvider for CachedStateProvider<S> {
|
||||
match self.caches.get_storage(&account, &storage_key) {
|
||||
(SlotStatus::NotCached, maybe_cache) => {
|
||||
let final_res = self.state_provider.storage(account, storage_key)?;
|
||||
|
||||
if self.is_prewarm() {
|
||||
let account_cache = maybe_cache.unwrap_or_default();
|
||||
account_cache.insert_storage(storage_key, final_res);
|
||||
// we always need to insert the value to update the weights.
|
||||
// Note: there exists a race when the storage cache did not exist yet and two
|
||||
// consumers looking up the a storage value for this account for the first time,
|
||||
// however we can assume that this will only happen for the very first
|
||||
// (mostlikely the same) value, and don't expect that this
|
||||
// will accidentally replace an account storage cache with
|
||||
// additional values.
|
||||
self.caches.insert_storage_cache(account, account_cache);
|
||||
}
|
||||
let account_cache = maybe_cache.unwrap_or_default();
|
||||
account_cache.insert_storage(storage_key, final_res);
|
||||
// we always need to insert the value to update the weights.
|
||||
// Note: there exists a race when the storage cache did not exist yet and two
|
||||
// consumers looking up the a storage value for this account for the first time,
|
||||
// however we can assume that this will only happen for the very first (mostlikely
|
||||
// the same) value, and don't expect that this will accidentally
|
||||
// replace an account storage cache with additional values.
|
||||
self.caches.insert_storage_cache(account, account_cache);
|
||||
|
||||
self.metrics.storage_cache_misses.increment(1);
|
||||
Ok(final_res)
|
||||
@@ -213,11 +183,7 @@ impl<S: BytecodeReader> BytecodeReader for CachedStateProvider<S> {
|
||||
self.metrics.code_cache_misses.increment(1);
|
||||
|
||||
let final_res = self.state_provider.bytecode_by_hash(code_hash)?;
|
||||
|
||||
if self.is_prewarm() {
|
||||
self.caches.code_cache.insert(*code_hash, final_res.clone());
|
||||
}
|
||||
|
||||
self.caches.code_cache.insert(*code_hash, final_res.clone());
|
||||
Ok(final_res)
|
||||
}
|
||||
}
|
||||
@@ -629,11 +595,6 @@ impl SavedCache {
|
||||
Arc::strong_count(&self.usage_guard) == 1
|
||||
}
|
||||
|
||||
/// Returns the current strong count of the usage guard.
|
||||
pub(crate) fn usage_count(&self) -> usize {
|
||||
Arc::strong_count(&self.usage_guard)
|
||||
}
|
||||
|
||||
/// Returns the [`ExecutionCache`] belonging to the tracked hash.
|
||||
pub(crate) const fn cache(&self) -> &ExecutionCache {
|
||||
&self.caches
|
||||
@@ -824,7 +785,7 @@ mod tests {
|
||||
|
||||
let caches = ExecutionCacheBuilder::default().build_caches(1000);
|
||||
let state_provider =
|
||||
CachedStateProvider::new(provider, caches, CachedStateMetrics::zeroed());
|
||||
CachedStateProvider::new_with_caches(provider, caches, CachedStateMetrics::zeroed());
|
||||
|
||||
// check that the storage is empty
|
||||
let res = state_provider.storage(address, storage_key);
|
||||
@@ -847,7 +808,7 @@ mod tests {
|
||||
|
||||
let caches = ExecutionCacheBuilder::default().build_caches(1000);
|
||||
let state_provider =
|
||||
CachedStateProvider::new(provider, caches, CachedStateMetrics::zeroed());
|
||||
CachedStateProvider::new_with_caches(provider, caches, CachedStateMetrics::zeroed());
|
||||
|
||||
// check that the storage returns the expected value
|
||||
let res = state_provider.storage(address, storage_key);
|
||||
|
||||
@@ -6,13 +6,15 @@ use reth_errors::{BlockExecutionError, BlockValidationError, ProviderError};
|
||||
use reth_evm::execute::InternalBlockExecutionError;
|
||||
use reth_payload_primitives::NewPayloadError;
|
||||
use reth_primitives_traits::{Block, BlockBody, SealedBlock};
|
||||
use tokio::sync::oneshot::error::TryRecvError;
|
||||
|
||||
/// This is an error that can come from advancing persistence.
|
||||
/// This is an error that can come from advancing persistence. Either this can be a
|
||||
/// [`TryRecvError`], or this can be a [`ProviderError`]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AdvancePersistenceError {
|
||||
/// The persistence channel was closed unexpectedly
|
||||
#[error("persistence channel closed")]
|
||||
ChannelClosed,
|
||||
/// An error that can be from failing to receive a value from persistence
|
||||
#[error(transparent)]
|
||||
RecvError(#[from] TryRecvError),
|
||||
/// A provider error
|
||||
#[error(transparent)]
|
||||
Provider(#[from] ProviderError),
|
||||
|
||||
@@ -83,7 +83,7 @@ where
|
||||
{
|
||||
/// Creates a new [`InstrumentedStateProvider`] from a state provider with the provided label
|
||||
/// for metrics.
|
||||
pub fn new(state_provider: S, source: &'static str) -> Self {
|
||||
pub fn from_state_provider(state_provider: S, source: &'static str) -> Self {
|
||||
Self {
|
||||
state_provider,
|
||||
metrics: StateProviderMetrics::new_with_labels(&[("source", source)]),
|
||||
|
||||
@@ -48,7 +48,6 @@ impl InvalidHeaderCache {
|
||||
// if we get here, the entry has been hit too many times, so we evict it
|
||||
self.headers.remove(hash);
|
||||
self.metrics.hit_evictions.increment(1);
|
||||
self.metrics.count.set(self.headers.len() as f64);
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,6 @@ impl EngineApiMetrics {
|
||||
&self,
|
||||
executor: E,
|
||||
mut transactions: impl Iterator<Item = Result<impl ExecutableTx<E>, BlockExecutionError>>,
|
||||
transaction_count: usize,
|
||||
state_hook: Box<dyn OnStateHook>,
|
||||
) -> Result<(BlockExecutionOutput<E::Receipt>, Vec<Address>), BlockExecutionError>
|
||||
where
|
||||
@@ -76,7 +75,7 @@ impl EngineApiMetrics {
|
||||
// be accessible.
|
||||
let wrapper = MeteredStateHook { metrics: self.executor.clone(), inner_hook: state_hook };
|
||||
|
||||
let mut senders = Vec::with_capacity(transaction_count);
|
||||
let mut senders = Vec::new();
|
||||
let mut executor = executor.with_state_hook(Some(Box::new(wrapper)));
|
||||
|
||||
let f = || {
|
||||
@@ -530,7 +529,6 @@ mod tests {
|
||||
let _result = metrics.execute_metered::<_, EmptyDB>(
|
||||
executor,
|
||||
input.clone_transactions_recovered().map(Ok::<_, BlockExecutionError>),
|
||||
input.transaction_count(),
|
||||
state_hook,
|
||||
);
|
||||
|
||||
@@ -587,7 +585,6 @@ mod tests {
|
||||
let _result = metrics.execute_metered::<_, EmptyDB>(
|
||||
executor,
|
||||
input.clone_transactions_recovered().map(Ok::<_, BlockExecutionError>),
|
||||
input.transaction_count(),
|
||||
state_hook,
|
||||
);
|
||||
|
||||
|
||||
@@ -37,12 +37,17 @@ use reth_revm::database::StateProviderDatabase;
|
||||
use reth_stages_api::ControlFlow;
|
||||
use revm::state::EvmState;
|
||||
use state::TreeState;
|
||||
use std::{fmt::Debug, ops, sync::Arc, time::Instant};
|
||||
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
sync::{
|
||||
mpsc::{Receiver, RecvError, RecvTimeoutError, Sender},
|
||||
Arc,
|
||||
},
|
||||
time::Instant,
|
||||
};
|
||||
use tokio::sync::{
|
||||
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
||||
oneshot,
|
||||
oneshot::{self, error::TryRecvError},
|
||||
};
|
||||
use tracing::*;
|
||||
|
||||
@@ -58,6 +63,7 @@ mod persistence_state;
|
||||
pub mod precompile_cache;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
// TODO(alexey): compare trie updates in `insert_block_inner`
|
||||
#[expect(unused)]
|
||||
mod trie_updates;
|
||||
|
||||
@@ -234,7 +240,7 @@ where
|
||||
C: ConfigureEvm<Primitives = N> + 'static,
|
||||
{
|
||||
provider: P,
|
||||
consensus: Arc<dyn FullConsensus<N>>,
|
||||
consensus: Arc<dyn FullConsensus<N, Error = ConsensusError>>,
|
||||
payload_validator: V,
|
||||
/// Keeps track of internals such as executed and buffered blocks.
|
||||
state: EngineApiTreeState<N>,
|
||||
@@ -320,7 +326,7 @@ where
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
provider: P,
|
||||
consensus: Arc<dyn FullConsensus<N>>,
|
||||
consensus: Arc<dyn FullConsensus<N, Error = ConsensusError>>,
|
||||
payload_validator: V,
|
||||
outgoing: UnboundedSender<EngineApiEvent<N>>,
|
||||
state: EngineApiTreeState<N>,
|
||||
@@ -332,7 +338,7 @@ where
|
||||
engine_kind: EngineApiKind,
|
||||
evm_config: C,
|
||||
) -> Self {
|
||||
let (incoming_tx, incoming) = crossbeam_channel::unbounded();
|
||||
let (incoming_tx, incoming) = std::sync::mpsc::channel();
|
||||
|
||||
Self {
|
||||
provider,
|
||||
@@ -362,7 +368,7 @@ where
|
||||
#[expect(clippy::complexity)]
|
||||
pub fn spawn_new(
|
||||
provider: P,
|
||||
consensus: Arc<dyn FullConsensus<N>>,
|
||||
consensus: Arc<dyn FullConsensus<N, Error = ConsensusError>>,
|
||||
payload_validator: V,
|
||||
persistence: PersistenceHandle<N>,
|
||||
payload_builder: PayloadBuilderHandle<T>,
|
||||
@@ -417,34 +423,23 @@ where
|
||||
/// This will block the current thread and process incoming messages.
|
||||
pub fn run(mut self) {
|
||||
loop {
|
||||
match self.wait_for_event() {
|
||||
LoopEvent::EngineMessage(msg) => {
|
||||
match self.try_recv_engine_message() {
|
||||
Ok(Some(msg)) => {
|
||||
debug!(target: "engine::tree", %msg, "received new engine message");
|
||||
match self.on_engine_message(msg) {
|
||||
Ok(ops::ControlFlow::Break(())) => return,
|
||||
Ok(ops::ControlFlow::Continue(())) => {}
|
||||
Err(fatal) => {
|
||||
error!(target: "engine::tree", %fatal, "insert block fatal error");
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
LoopEvent::PersistenceComplete { result, start_time } => {
|
||||
if let Err(err) = self.on_persistence_complete(result, start_time) {
|
||||
error!(target: "engine::tree", %err, "Persistence complete handling failed");
|
||||
if let Err(fatal) = self.on_engine_message(msg) {
|
||||
error!(target: "engine::tree", %fatal, "insert block fatal error");
|
||||
return
|
||||
}
|
||||
}
|
||||
LoopEvent::Disconnected => {
|
||||
error!(target: "engine::tree", "Channel disconnected");
|
||||
Ok(None) => {
|
||||
debug!(target: "engine::tree", "received no engine message for some time, while waiting for persistence task to complete");
|
||||
}
|
||||
Err(_err) => {
|
||||
error!(target: "engine::tree", "Engine channel disconnected");
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Always check if we need to trigger new persistence after any event:
|
||||
// - After engine messages: new blocks may have been inserted that exceed the
|
||||
// persistence threshold
|
||||
// - After persistence completion: we can now persist more blocks if needed
|
||||
if let Err(err) = self.advance_persistence() {
|
||||
error!(target: "engine::tree", %err, "Advancing persistence failed");
|
||||
return
|
||||
@@ -452,47 +447,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Blocks until the next event is ready: either an incoming engine message or a persistence
|
||||
/// completion (if one is in progress).
|
||||
///
|
||||
/// Uses biased selection to prioritize persistence completion to update in-memory state and
|
||||
/// unblock further writes.
|
||||
fn wait_for_event(&mut self) -> LoopEvent<T, N> {
|
||||
// Take ownership of persistence rx if present
|
||||
let maybe_persistence = self.persistence_state.rx.take();
|
||||
|
||||
if let Some((persistence_rx, start_time, action)) = maybe_persistence {
|
||||
// Biased select prioritizes persistence completion to update in memory state and
|
||||
// unblock further writes
|
||||
crossbeam_channel::select_biased! {
|
||||
recv(persistence_rx) -> result => {
|
||||
// Don't put it back - consumed (oneshot-like behavior)
|
||||
match result {
|
||||
Ok(value) => LoopEvent::PersistenceComplete {
|
||||
result: value,
|
||||
start_time,
|
||||
},
|
||||
Err(_) => LoopEvent::Disconnected,
|
||||
}
|
||||
},
|
||||
recv(self.incoming) -> msg => {
|
||||
// Put the persistence rx back - we didn't consume it
|
||||
self.persistence_state.rx = Some((persistence_rx, start_time, action));
|
||||
match msg {
|
||||
Ok(m) => LoopEvent::EngineMessage(m),
|
||||
Err(_) => LoopEvent::Disconnected,
|
||||
}
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// No persistence in progress - just wait on incoming
|
||||
match self.incoming.recv() {
|
||||
Ok(m) => LoopEvent::EngineMessage(m),
|
||||
Err(_) => LoopEvent::Disconnected,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoked when previously requested blocks were downloaded.
|
||||
///
|
||||
/// If the block count exceeds the configured batch size we're allowed to execute at once, this
|
||||
@@ -867,8 +821,7 @@ where
|
||||
new_head_number: u64,
|
||||
current_head_number: u64,
|
||||
) -> Vec<ExecutedBlock<N>> {
|
||||
let mut old_blocks =
|
||||
Vec::with_capacity((current_head_number.saturating_sub(new_head_number)) as usize);
|
||||
let mut old_blocks = Vec::new();
|
||||
|
||||
for block_num in (new_head_number + 1)..=current_head_number {
|
||||
if let Some(block_state) = self.canonical_in_memory_state.state_by_number(block_num) {
|
||||
@@ -973,6 +926,48 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Determines if the given block is part of a fork by checking that these
|
||||
/// conditions are true:
|
||||
/// * walking back from the target hash to verify that the target hash is not part of an
|
||||
/// extension of the canonical chain.
|
||||
/// * walking back from the current head to verify that the target hash is not already part of
|
||||
/// the canonical chain.
|
||||
///
|
||||
/// The header is required as an arg, because we might be checking that the header is a fork
|
||||
/// block before it's in the tree state and before it's in the database.
|
||||
fn is_fork(&self, target: BlockWithParent) -> ProviderResult<bool> {
|
||||
let target_hash = target.block.hash;
|
||||
// verify that the given hash is not part of an extension of the canon chain.
|
||||
let canonical_head = self.state.tree_state.canonical_head();
|
||||
let mut current_hash;
|
||||
let mut current_block = target;
|
||||
loop {
|
||||
if current_block.block.hash == canonical_head.hash {
|
||||
return Ok(false)
|
||||
}
|
||||
// We already passed the canonical head
|
||||
if current_block.block.number <= canonical_head.number {
|
||||
break
|
||||
}
|
||||
current_hash = current_block.parent;
|
||||
|
||||
let Some(next_block) = self.sealed_header_by_hash(current_hash)? else { break };
|
||||
current_block = next_block.block_with_parent();
|
||||
}
|
||||
|
||||
// verify that the given hash is not already part of canonical chain stored in memory
|
||||
if self.canonical_in_memory_state.header_by_hash(target_hash).is_some() {
|
||||
return Ok(false)
|
||||
}
|
||||
|
||||
// verify that the given hash is not already part of persisted canonical chain
|
||||
if self.provider.block_number(target_hash)?.is_some() {
|
||||
return Ok(false)
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Invoked when we receive a new forkchoice update message. Calls into the blockchain tree
|
||||
/// to resolve chain forks and ensure that the Execution Layer is working with the latest valid
|
||||
/// chain.
|
||||
@@ -1233,13 +1228,39 @@ where
|
||||
.with_event(TreeEvent::Download(DownloadRequest::single_block(target))))
|
||||
}
|
||||
|
||||
/// Attempts to receive the next engine request.
|
||||
///
|
||||
/// If there's currently no persistence action in progress, this will block until a new request
|
||||
/// is received. If there's a persistence action in progress, this will try to receive the
|
||||
/// next request with a timeout to not block indefinitely and return `Ok(None)` if no request is
|
||||
/// received in time.
|
||||
///
|
||||
/// Returns an error if the engine channel is disconnected.
|
||||
#[expect(clippy::type_complexity)]
|
||||
fn try_recv_engine_message(
|
||||
&self,
|
||||
) -> Result<Option<FromEngine<EngineApiRequest<T, N>, N::Block>>, RecvError> {
|
||||
if self.persistence_state.in_progress() {
|
||||
// try to receive the next request with a timeout to not block indefinitely
|
||||
match self.incoming.recv_timeout(std::time::Duration::from_millis(500)) {
|
||||
Ok(msg) => Ok(Some(msg)),
|
||||
Err(err) => match err {
|
||||
RecvTimeoutError::Timeout => Ok(None),
|
||||
RecvTimeoutError::Disconnected => Err(RecvError),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
self.incoming.recv().map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper method to remove blocks and set the persistence state. This ensures we keep track of
|
||||
/// the current persistence action while we're removing blocks.
|
||||
fn remove_blocks(&mut self, new_tip_num: u64) {
|
||||
debug!(target: "engine::tree", ?new_tip_num, last_persisted_block_number=?self.persistence_state.last_persisted_block.number, "Removing blocks using persistence task");
|
||||
if new_tip_num < self.persistence_state.last_persisted_block.number {
|
||||
debug!(target: "engine::tree", ?new_tip_num, "Starting remove blocks job");
|
||||
let (tx, rx) = crossbeam_channel::bounded(1);
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.persistence.remove_blocks_above(new_tip_num, tx);
|
||||
self.persistence_state.start_remove(new_tip_num, rx);
|
||||
}
|
||||
@@ -1261,23 +1282,55 @@ where
|
||||
.expect("Checked non-empty persisting blocks");
|
||||
|
||||
debug!(target: "engine::tree", count=blocks_to_persist.len(), blocks = ?blocks_to_persist.iter().map(|block| block.recovered_block().num_hash()).collect::<Vec<_>>(), "Persisting blocks");
|
||||
let (tx, rx) = crossbeam_channel::bounded(1);
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.persistence.save_blocks(blocks_to_persist, tx);
|
||||
|
||||
self.persistence_state.start_save(highest_num_hash, rx);
|
||||
}
|
||||
|
||||
/// Triggers new persistence actions if no persistence task is currently in progress.
|
||||
/// Attempts to advance the persistence state.
|
||||
///
|
||||
/// This checks if we need to remove blocks (disk reorg) or save new blocks to disk.
|
||||
/// Persistence completion is handled separately via the `wait_for_event` method.
|
||||
/// If we're currently awaiting a response this will try to receive the response (non-blocking)
|
||||
/// or send a new persistence action if necessary.
|
||||
fn advance_persistence(&mut self) -> Result<(), AdvancePersistenceError> {
|
||||
if self.persistence_state.in_progress() {
|
||||
let (mut rx, start_time, current_action) = self
|
||||
.persistence_state
|
||||
.rx
|
||||
.take()
|
||||
.expect("if a persistence task is in progress Receiver must be Some");
|
||||
// Check if persistence has complete
|
||||
match rx.try_recv() {
|
||||
Ok(last_persisted_hash_num) => {
|
||||
self.metrics.engine.persistence_duration.record(start_time.elapsed());
|
||||
let Some(BlockNumHash {
|
||||
hash: last_persisted_block_hash,
|
||||
number: last_persisted_block_number,
|
||||
}) = last_persisted_hash_num
|
||||
else {
|
||||
// if this happened, then we persisted no blocks because we sent an
|
||||
// empty vec of blocks
|
||||
warn!(target: "engine::tree", "Persistence task completed but did not persist any blocks");
|
||||
return Ok(())
|
||||
};
|
||||
|
||||
debug!(target: "engine::tree", ?last_persisted_block_hash, ?last_persisted_block_number, elapsed=?start_time.elapsed(), "Finished persisting, calling finish");
|
||||
self.persistence_state
|
||||
.finish(last_persisted_block_hash, last_persisted_block_number);
|
||||
self.on_new_persisted_block()?;
|
||||
}
|
||||
Err(TryRecvError::Closed) => return Err(TryRecvError::Closed.into()),
|
||||
Err(TryRecvError::Empty) => {
|
||||
self.persistence_state.rx = Some((rx, start_time, current_action))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !self.persistence_state.in_progress() {
|
||||
if let Some(new_tip_num) = self.find_disk_reorg()? {
|
||||
self.remove_blocks(new_tip_num)
|
||||
} else if self.should_persist() {
|
||||
let blocks_to_persist =
|
||||
self.get_canonical_blocks_to_persist(PersistTarget::Threshold)?;
|
||||
let blocks_to_persist = self.get_canonical_blocks_to_persist()?;
|
||||
self.persist_blocks(blocks_to_persist);
|
||||
}
|
||||
}
|
||||
@@ -1285,97 +1338,11 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Finishes termination by persisting all remaining blocks and signaling completion.
|
||||
///
|
||||
/// This blocks until all persistence is complete. Always signals completion,
|
||||
/// even if an error occurs.
|
||||
fn finish_termination(
|
||||
&mut self,
|
||||
pending_termination: oneshot::Sender<()>,
|
||||
) -> Result<(), AdvancePersistenceError> {
|
||||
trace!(target: "engine::tree", "finishing termination, persisting remaining blocks");
|
||||
let result = self.persist_until_complete();
|
||||
let _ = pending_termination.send(());
|
||||
result
|
||||
}
|
||||
|
||||
/// Persists all remaining blocks until none are left.
|
||||
fn persist_until_complete(&mut self) -> Result<(), AdvancePersistenceError> {
|
||||
loop {
|
||||
// Wait for any in-progress persistence to complete (blocking)
|
||||
if let Some((rx, start_time, _action)) = self.persistence_state.rx.take() {
|
||||
let result = rx.recv().map_err(|_| AdvancePersistenceError::ChannelClosed)?;
|
||||
self.on_persistence_complete(result, start_time)?;
|
||||
}
|
||||
|
||||
let blocks_to_persist = self.get_canonical_blocks_to_persist(PersistTarget::Head)?;
|
||||
|
||||
if blocks_to_persist.is_empty() {
|
||||
debug!(target: "engine::tree", "persistence complete, signaling termination");
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
debug!(target: "engine::tree", count = blocks_to_persist.len(), "persisting remaining blocks before shutdown");
|
||||
self.persist_blocks(blocks_to_persist);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to poll for a completed persistence task (non-blocking).
|
||||
///
|
||||
/// Returns `true` if a persistence task was completed, `false` otherwise.
|
||||
#[cfg(test)]
|
||||
pub fn try_poll_persistence(&mut self) -> Result<bool, AdvancePersistenceError> {
|
||||
let Some((rx, start_time, action)) = self.persistence_state.rx.take() else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
match rx.try_recv() {
|
||||
Ok(result) => {
|
||||
self.on_persistence_complete(result, start_time)?;
|
||||
Ok(true)
|
||||
}
|
||||
Err(crossbeam_channel::TryRecvError::Empty) => {
|
||||
// Not ready yet, put it back
|
||||
self.persistence_state.rx = Some((rx, start_time, action));
|
||||
Ok(false)
|
||||
}
|
||||
Err(crossbeam_channel::TryRecvError::Disconnected) => {
|
||||
Err(AdvancePersistenceError::ChannelClosed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles a completed persistence task.
|
||||
fn on_persistence_complete(
|
||||
&mut self,
|
||||
last_persisted_hash_num: Option<BlockNumHash>,
|
||||
start_time: Instant,
|
||||
) -> Result<(), AdvancePersistenceError> {
|
||||
self.metrics.engine.persistence_duration.record(start_time.elapsed());
|
||||
|
||||
let Some(BlockNumHash {
|
||||
hash: last_persisted_block_hash,
|
||||
number: last_persisted_block_number,
|
||||
}) = last_persisted_hash_num
|
||||
else {
|
||||
// if this happened, then we persisted no blocks because we sent an empty vec of blocks
|
||||
warn!(target: "engine::tree", "Persistence task completed but did not persist any blocks");
|
||||
return Ok(())
|
||||
};
|
||||
|
||||
debug!(target: "engine::tree", ?last_persisted_block_hash, ?last_persisted_block_number, elapsed=?start_time.elapsed(), "Finished persisting, calling finish");
|
||||
self.persistence_state.finish(last_persisted_block_hash, last_persisted_block_number);
|
||||
self.on_new_persisted_block()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handles a message from the engine.
|
||||
///
|
||||
/// Returns `ControlFlow::Break(())` if the engine should terminate.
|
||||
fn on_engine_message(
|
||||
&mut self,
|
||||
msg: FromEngine<EngineApiRequest<T, N>, N::Block>,
|
||||
) -> Result<ops::ControlFlow<()>, InsertBlockFatalError> {
|
||||
) -> Result<(), InsertBlockFatalError> {
|
||||
match msg {
|
||||
FromEngine::Event(event) => match event {
|
||||
FromOrchestrator::BackfillSyncStarted => {
|
||||
@@ -1385,13 +1352,6 @@ where
|
||||
FromOrchestrator::BackfillSyncFinished(ctrl) => {
|
||||
self.on_backfill_sync_finished(ctrl)?;
|
||||
}
|
||||
FromOrchestrator::Terminate { tx } => {
|
||||
debug!(target: "engine::tree", "received terminate request");
|
||||
if let Err(err) = self.finish_termination(tx) {
|
||||
error!(target: "engine::tree", %err, "Termination failed");
|
||||
}
|
||||
return Ok(ops::ControlFlow::Break(()))
|
||||
}
|
||||
},
|
||||
FromEngine::Request(request) => {
|
||||
match request {
|
||||
@@ -1399,7 +1359,7 @@ where
|
||||
let block_num_hash = block.recovered_block().num_hash();
|
||||
if block_num_hash.number <= self.state.tree_state.canonical_block_number() {
|
||||
// outdated block that can be skipped
|
||||
return Ok(ops::ControlFlow::Continue(()))
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
debug!(target: "engine::tree", block=?block_num_hash, "inserting already executed block");
|
||||
@@ -1507,7 +1467,7 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ops::ControlFlow::Continue(()))
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Invoked if the backfill sync has finished to target.
|
||||
@@ -1741,10 +1701,10 @@ where
|
||||
}
|
||||
|
||||
/// Returns a batch of consecutive canonical blocks to persist in the range
|
||||
/// `(last_persisted_number .. target]`. The expected order is oldest -> newest.
|
||||
/// `(last_persisted_number .. canonical_head - threshold]`. The expected
|
||||
/// order is oldest -> newest.
|
||||
fn get_canonical_blocks_to_persist(
|
||||
&self,
|
||||
target: PersistTarget,
|
||||
) -> Result<Vec<ExecutedBlock<N>>, AdvancePersistenceError> {
|
||||
// We will calculate the state root using the database, so we need to be sure there are no
|
||||
// changes
|
||||
@@ -1755,12 +1715,9 @@ where
|
||||
let last_persisted_number = self.persistence_state.last_persisted_block.number;
|
||||
let canonical_head_number = self.state.tree_state.canonical_block_number();
|
||||
|
||||
let target_number = match target {
|
||||
PersistTarget::Head => canonical_head_number,
|
||||
PersistTarget::Threshold => {
|
||||
canonical_head_number.saturating_sub(self.config.memory_block_buffer_target())
|
||||
}
|
||||
};
|
||||
// Persist only up to block buffer target
|
||||
let target_number =
|
||||
canonical_head_number.saturating_sub(self.config.memory_block_buffer_target());
|
||||
|
||||
debug!(
|
||||
target: "engine::tree",
|
||||
@@ -2550,11 +2507,14 @@ where
|
||||
Ok(Some(_)) => {}
|
||||
}
|
||||
|
||||
// determine whether we are on a fork chain by comparing the block number with the
|
||||
// canonical head. This is a simple check that is sufficient for the event emission below.
|
||||
// A block is considered a fork if its number is less than or equal to the canonical head,
|
||||
// as this indicates there's already a canonical block at that height.
|
||||
let is_fork = block_id.block.number <= self.state.tree_state.current_canonical_head.number;
|
||||
// determine whether we are on a fork chain
|
||||
let is_fork = match self.is_fork(block_id) {
|
||||
Err(err) => {
|
||||
let block = convert_to_block(self, input)?;
|
||||
return Err(InsertBlockError::new(block, err.into()).into());
|
||||
}
|
||||
Ok(is_fork) => is_fork,
|
||||
};
|
||||
|
||||
let ctx = TreeCtx::new(&mut self.state, &self.canonical_in_memory_state);
|
||||
|
||||
@@ -2871,26 +2831,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Events received in the main engine loop.
|
||||
#[derive(Debug)]
|
||||
enum LoopEvent<T, N>
|
||||
where
|
||||
N: NodePrimitives,
|
||||
T: PayloadTypes,
|
||||
{
|
||||
/// An engine API message was received.
|
||||
EngineMessage(FromEngine<EngineApiRequest<T, N>, N::Block>),
|
||||
/// A persistence task completed.
|
||||
PersistenceComplete {
|
||||
/// The result of the persistence operation.
|
||||
result: Option<BlockNumHash>,
|
||||
/// When the persistence operation started.
|
||||
start_time: Instant,
|
||||
},
|
||||
/// A channel was disconnected.
|
||||
Disconnected,
|
||||
}
|
||||
|
||||
/// Block inclusion can be valid, accepted, or invalid. Invalid blocks are returned as an error
|
||||
/// variant.
|
||||
///
|
||||
@@ -2920,12 +2860,3 @@ pub enum InsertPayloadOk {
|
||||
/// The payload was valid and inserted into the tree.
|
||||
Inserted(BlockStatus),
|
||||
}
|
||||
|
||||
/// Target for block persistence.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum PersistTarget {
|
||||
/// Persist up to `canonical_head - memory_block_buffer_target`.
|
||||
Threshold,
|
||||
/// Persist all blocks up to and including the canonical head.
|
||||
Head,
|
||||
}
|
||||
|
||||
@@ -2,122 +2,16 @@
|
||||
|
||||
use alloy_consensus::constants::KECCAK_EMPTY;
|
||||
use alloy_eip7928::BlockAccessList;
|
||||
use alloy_primitives::{keccak256, Address, StorageKey, U256};
|
||||
use alloy_primitives::{keccak256, U256};
|
||||
use reth_primitives_traits::Account;
|
||||
use reth_provider::{AccountReader, ProviderError};
|
||||
use reth_trie::{HashedPostState, HashedStorage};
|
||||
use std::ops::Range;
|
||||
|
||||
/// Returns the total number of storage slots (both changed and read-only) across all accounts in
|
||||
/// the BAL.
|
||||
pub fn total_slots(bal: &BlockAccessList) -> usize {
|
||||
bal.iter().map(|account| account.storage_changes.len() + account.storage_reads.len()).sum()
|
||||
}
|
||||
|
||||
/// Iterator over storage slots in a [`BlockAccessList`], with range-based filtering.
|
||||
///
|
||||
/// Iterates over all `(Address, StorageKey)` pairs representing both changed and read-only
|
||||
/// storage slots across all accounts in the BAL. For each account, changed slots are iterated
|
||||
/// first, followed by read-only slots. The iterator intelligently skips accounts and slots
|
||||
/// outside the specified range for efficient traversal.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct BALSlotIter<'a> {
|
||||
bal: &'a BlockAccessList,
|
||||
range: Range<usize>,
|
||||
current_index: usize,
|
||||
account_idx: usize,
|
||||
/// Index within the current account's combined slots (changed + read-only).
|
||||
/// If `slot_idx < storage_changes.len()`, we're in changed slots.
|
||||
/// Otherwise, we're in read-only slots at index `slot_idx - storage_changes.len()`.
|
||||
slot_idx: usize,
|
||||
}
|
||||
|
||||
impl<'a> BALSlotIter<'a> {
|
||||
/// Creates a new iterator over storage slots within the specified range.
|
||||
pub(crate) fn new(bal: &'a BlockAccessList, range: Range<usize>) -> Self {
|
||||
let mut iter = Self { bal, range, current_index: 0, account_idx: 0, slot_idx: 0 };
|
||||
iter.skip_to_range_start();
|
||||
iter
|
||||
}
|
||||
|
||||
/// Skips to the first item within the range.
|
||||
fn skip_to_range_start(&mut self) {
|
||||
while self.account_idx < self.bal.len() {
|
||||
let account = &self.bal[self.account_idx];
|
||||
let slots_in_account = account.storage_changes.len() + account.storage_reads.len();
|
||||
|
||||
// Check if this account contains items in our range
|
||||
let account_end = self.current_index + slots_in_account;
|
||||
|
||||
if account_end <= self.range.start {
|
||||
// Entire account is before range, skip it
|
||||
self.current_index = account_end;
|
||||
self.account_idx += 1;
|
||||
self.slot_idx = 0;
|
||||
} else if self.current_index < self.range.start {
|
||||
// Range starts somewhere in this account
|
||||
let skip_slots = self.range.start - self.current_index;
|
||||
self.slot_idx = skip_slots;
|
||||
self.current_index = self.range.start;
|
||||
break;
|
||||
} else {
|
||||
// We're at or past range start
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for BALSlotIter<'a> {
|
||||
type Item = (Address, StorageKey);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// Check if we've exceeded the range
|
||||
if self.current_index >= self.range.end {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Find the next valid slot
|
||||
while self.account_idx < self.bal.len() {
|
||||
let account = &self.bal[self.account_idx];
|
||||
let changed_len = account.storage_changes.len();
|
||||
let total_len = changed_len + account.storage_reads.len();
|
||||
|
||||
if self.slot_idx < total_len {
|
||||
let address = account.address;
|
||||
let slot = if self.slot_idx < changed_len {
|
||||
// We're in changed slots
|
||||
account.storage_changes[self.slot_idx].slot
|
||||
} else {
|
||||
// We're in read-only slots
|
||||
account.storage_reads[self.slot_idx - changed_len]
|
||||
};
|
||||
|
||||
self.slot_idx += 1;
|
||||
self.current_index += 1;
|
||||
|
||||
// Check if we've reached the end of range
|
||||
if self.current_index > self.range.end {
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some((address, slot));
|
||||
}
|
||||
|
||||
// Move to next account
|
||||
self.account_idx += 1;
|
||||
self.slot_idx = 0;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a Block Access List into a [`HashedPostState`] by extracting the final state
|
||||
/// of modified accounts and storage slots.
|
||||
pub(crate) fn bal_to_hashed_post_state<P>(
|
||||
pub fn bal_to_hashed_post_state<P>(
|
||||
bal: &BlockAccessList,
|
||||
provider: P,
|
||||
provider: &P,
|
||||
) -> Result<HashedPostState, ProviderError>
|
||||
where
|
||||
P: AccountReader,
|
||||
@@ -126,10 +20,7 @@ where
|
||||
|
||||
for account_changes in bal {
|
||||
let address = account_changes.address;
|
||||
|
||||
// Always fetch the account; even if we don't need the db account to construct the final
|
||||
// `Account`, doing this fills the cache.
|
||||
let existing_account = provider.basic_account(&address)?;
|
||||
let hashed_address = keccak256(address);
|
||||
|
||||
// Get the latest balance (last balance change if any)
|
||||
let balance = account_changes.balance_changes.last().map(|change| change.post_balance);
|
||||
@@ -148,14 +39,12 @@ where
|
||||
None
|
||||
};
|
||||
|
||||
// If the account was only read then don't add it to the HashedPostState
|
||||
if balance.is_none() &&
|
||||
nonce.is_none() &&
|
||||
code_hash.is_none() &&
|
||||
account_changes.storage_changes.is_empty()
|
||||
{
|
||||
continue
|
||||
}
|
||||
// Only fetch account from provider if we're missing any field
|
||||
let existing_account = if balance.is_none() || nonce.is_none() || code_hash.is_none() {
|
||||
provider.basic_account(&address)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Build the final account state
|
||||
let account = Account {
|
||||
@@ -169,7 +58,6 @@ where
|
||||
}),
|
||||
};
|
||||
|
||||
let hashed_address = keccak256(address);
|
||||
hashed_state.accounts.insert(hashed_address, Some(account));
|
||||
|
||||
// Process storage changes
|
||||
@@ -187,7 +75,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
hashed_state.storages.insert(hashed_address, storage_map);
|
||||
if !storage_map.storage.is_empty() {
|
||||
hashed_state.storages.insert(hashed_address, storage_map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,117 +315,4 @@ mod tests {
|
||||
// Should have the last value
|
||||
assert_eq!(*stored_value, U256::from(300));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bal_slot_iter() {
|
||||
// Create test data with multiple accounts and slots (both changed and read-only)
|
||||
let addr1 = Address::repeat_byte(0x01);
|
||||
let addr2 = Address::repeat_byte(0x02);
|
||||
let addr3 = Address::repeat_byte(0x03);
|
||||
|
||||
// Account 1: 2 changed slots + 1 read-only = 3 total slots (indices 0, 1, 2)
|
||||
let account1 = AccountChanges {
|
||||
address: addr1,
|
||||
storage_changes: vec![
|
||||
SlotChanges {
|
||||
slot: StorageKey::from(U256::from(100)),
|
||||
changes: vec![StorageChange::new(0, B256::ZERO)],
|
||||
},
|
||||
SlotChanges {
|
||||
slot: StorageKey::from(U256::from(101)),
|
||||
changes: vec![StorageChange::new(0, B256::ZERO)],
|
||||
},
|
||||
],
|
||||
storage_reads: vec![StorageKey::from(U256::from(102))],
|
||||
balance_changes: vec![],
|
||||
nonce_changes: vec![],
|
||||
code_changes: vec![],
|
||||
};
|
||||
|
||||
// Account 2: 1 changed slot + 1 read-only = 2 total slots (indices 3, 4)
|
||||
let account2 = AccountChanges {
|
||||
address: addr2,
|
||||
storage_changes: vec![SlotChanges {
|
||||
slot: StorageKey::from(U256::from(200)),
|
||||
changes: vec![StorageChange::new(0, B256::ZERO)],
|
||||
}],
|
||||
storage_reads: vec![StorageKey::from(U256::from(201))],
|
||||
balance_changes: vec![],
|
||||
nonce_changes: vec![],
|
||||
code_changes: vec![],
|
||||
};
|
||||
|
||||
// Account 3: 2 changed slots + 1 read-only = 3 total slots (indices 5, 6, 7)
|
||||
let account3 = AccountChanges {
|
||||
address: addr3,
|
||||
storage_changes: vec![
|
||||
SlotChanges {
|
||||
slot: StorageKey::from(U256::from(300)),
|
||||
changes: vec![StorageChange::new(0, B256::ZERO)],
|
||||
},
|
||||
SlotChanges {
|
||||
slot: StorageKey::from(U256::from(301)),
|
||||
changes: vec![StorageChange::new(0, B256::ZERO)],
|
||||
},
|
||||
],
|
||||
storage_reads: vec![StorageKey::from(U256::from(302))],
|
||||
balance_changes: vec![],
|
||||
nonce_changes: vec![],
|
||||
code_changes: vec![],
|
||||
};
|
||||
|
||||
let bal = vec![account1, account2, account3];
|
||||
|
||||
// Test 1: Iterate over all slots (range 0..8)
|
||||
let items: Vec<_> = BALSlotIter::new(&bal, 0..8).collect();
|
||||
assert_eq!(items.len(), 8);
|
||||
// Account 1: changed slots first (100, 101), then read-only (102)
|
||||
assert_eq!(items[0], (addr1, StorageKey::from(U256::from(100))));
|
||||
assert_eq!(items[1], (addr1, StorageKey::from(U256::from(101))));
|
||||
assert_eq!(items[2], (addr1, StorageKey::from(U256::from(102))));
|
||||
// Account 2: changed slot (200), then read-only (201)
|
||||
assert_eq!(items[3], (addr2, StorageKey::from(U256::from(200))));
|
||||
assert_eq!(items[4], (addr2, StorageKey::from(U256::from(201))));
|
||||
// Account 3: changed slots (300, 301), then read-only (302)
|
||||
assert_eq!(items[5], (addr3, StorageKey::from(U256::from(300))));
|
||||
assert_eq!(items[6], (addr3, StorageKey::from(U256::from(301))));
|
||||
assert_eq!(items[7], (addr3, StorageKey::from(U256::from(302))));
|
||||
|
||||
// Test 2: Range that skips first account (range 3..6)
|
||||
let items: Vec<_> = BALSlotIter::new(&bal, 3..6).collect();
|
||||
assert_eq!(items.len(), 3);
|
||||
assert_eq!(items[0], (addr2, StorageKey::from(U256::from(200))));
|
||||
assert_eq!(items[1], (addr2, StorageKey::from(U256::from(201))));
|
||||
assert_eq!(items[2], (addr3, StorageKey::from(U256::from(300))));
|
||||
|
||||
// Test 3: Range within first account (range 1..2)
|
||||
let items: Vec<_> = BALSlotIter::new(&bal, 1..2).collect();
|
||||
assert_eq!(items.len(), 1);
|
||||
assert_eq!(items[0], (addr1, StorageKey::from(U256::from(101))));
|
||||
|
||||
// Test 4: Range spanning multiple accounts (range 2..5)
|
||||
let items: Vec<_> = BALSlotIter::new(&bal, 2..5).collect();
|
||||
assert_eq!(items.len(), 3);
|
||||
// Last slot from account 1 (read-only)
|
||||
assert_eq!(items[0], (addr1, StorageKey::from(U256::from(102))));
|
||||
// Account 2 (changed + read-only)
|
||||
assert_eq!(items[1], (addr2, StorageKey::from(U256::from(200))));
|
||||
assert_eq!(items[2], (addr2, StorageKey::from(U256::from(201))));
|
||||
|
||||
// Test 5: Empty range
|
||||
let items: Vec<_> = BALSlotIter::new(&bal, 5..5).collect();
|
||||
assert_eq!(items.len(), 0);
|
||||
|
||||
// Test 6: Range beyond end (starts at index 6)
|
||||
let items: Vec<_> = BALSlotIter::new(&bal, 6..100).collect();
|
||||
assert_eq!(items.len(), 2);
|
||||
assert_eq!(items[0], (addr3, StorageKey::from(U256::from(301))));
|
||||
assert_eq!(items[1], (addr3, StorageKey::from(U256::from(302))));
|
||||
|
||||
// Test 7: Range that starts in read-only slots (index 2 is the read-only slot of account 1)
|
||||
let items: Vec<_> = BALSlotIter::new(&bal, 2..4).collect();
|
||||
assert_eq!(items.len(), 2);
|
||||
assert_eq!(items[0], (addr1, StorageKey::from(U256::from(102))));
|
||||
assert_eq!(items[1], (addr2, StorageKey::from(U256::from(200))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Configured sparse trie enum for switching between serial and parallel implementations.
|
||||
|
||||
use alloy_primitives::B256;
|
||||
use reth_trie::{BranchNodeMasks, Nibbles, ProofTrieNode, TrieNode};
|
||||
use reth_trie::{Nibbles, ProofTrieNode, TrieMasks, TrieNode};
|
||||
use reth_trie_sparse::{
|
||||
errors::SparseTrieResult, provider::TrieNodeProvider, LeafLookup, LeafLookupError,
|
||||
SerialSparseTrie, SparseTrieInterface, SparseTrieUpdates,
|
||||
@@ -44,7 +44,7 @@ impl SparseTrieInterface for ConfiguredSparseTrie {
|
||||
fn with_root(
|
||||
self,
|
||||
root: TrieNode,
|
||||
masks: Option<BranchNodeMasks>,
|
||||
masks: TrieMasks,
|
||||
retain_updates: bool,
|
||||
) -> SparseTrieResult<Self> {
|
||||
match self {
|
||||
@@ -75,7 +75,7 @@ impl SparseTrieInterface for ConfiguredSparseTrie {
|
||||
&mut self,
|
||||
path: Nibbles,
|
||||
node: TrieNode,
|
||||
masks: Option<BranchNodeMasks>,
|
||||
masks: TrieMasks,
|
||||
) -> SparseTrieResult<()> {
|
||||
match self {
|
||||
Self::Serial(trie) => trie.reveal_node(path, node, masks),
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
use super::precompile_cache::PrecompileCacheMap;
|
||||
use crate::tree::{
|
||||
cached_state::{
|
||||
CachedStateMetrics, CachedStateProvider, ExecutionCache as StateExecutionCache,
|
||||
ExecutionCacheBuilder, SavedCache,
|
||||
CachedStateMetrics, ExecutionCache as StateExecutionCache, ExecutionCacheBuilder,
|
||||
SavedCache,
|
||||
},
|
||||
payload_processor::{
|
||||
prewarm::{PrewarmCacheTask, PrewarmContext, PrewarmMode, PrewarmTaskEvent},
|
||||
prewarm::{PrewarmCacheTask, PrewarmContext, PrewarmTaskEvent},
|
||||
sparse_trie::StateRootComputeOutcome,
|
||||
},
|
||||
sparse_trie::SparseTrieTask,
|
||||
@@ -23,16 +23,13 @@ use multiproof::{SparseTrieUpdate, *};
|
||||
use parking_lot::RwLock;
|
||||
use prewarm::PrewarmMetrics;
|
||||
use rayon::prelude::*;
|
||||
use reth_engine_primitives::ExecutableTxIterator;
|
||||
use reth_evm::{
|
||||
execute::{ExecutableTxFor, WithTxEnv},
|
||||
ConfigureEvm, EvmEnvFor, ExecutableTxIterator, ExecutableTxTuple, OnStateHook, SpecFor,
|
||||
TxEnvFor,
|
||||
ConfigureEvm, EvmEnvFor, OnStateHook, SpecFor, TxEnvFor,
|
||||
};
|
||||
use reth_execution_types::ExecutionOutcome;
|
||||
use reth_primitives_traits::NodePrimitives;
|
||||
use reth_provider::{
|
||||
BlockReader, DatabaseProviderROFactory, StateProvider, StateProviderFactory, StateReader,
|
||||
};
|
||||
use reth_provider::{BlockReader, DatabaseProviderROFactory, StateProviderFactory, StateReader};
|
||||
use reth_revm::{db::BundleState, state::EvmState};
|
||||
use reth_trie::{hashed_cursor::HashedCursorFactory, trie_cursor::TrieCursorFactory};
|
||||
use reth_trie_parallel::{
|
||||
@@ -46,7 +43,6 @@ use reth_trie_sparse::{
|
||||
use reth_trie_sparse_parallel::{ParallelSparseTrie, ParallelismThresholds};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
ops::Not,
|
||||
sync::{
|
||||
atomic::AtomicBool,
|
||||
mpsc::{self, channel},
|
||||
@@ -54,7 +50,7 @@ use std::{
|
||||
},
|
||||
time::Instant,
|
||||
};
|
||||
use tracing::{debug, debug_span, instrument, warn, Span};
|
||||
use tracing::{debug, debug_span, error, instrument, warn, Span};
|
||||
|
||||
pub mod bal;
|
||||
mod configured_sparse_trie;
|
||||
@@ -96,13 +92,6 @@ pub const SPARSE_TRIE_MAX_NODES_SHRINK_CAPACITY: usize = 1_000_000;
|
||||
/// 144MB.
|
||||
pub const SPARSE_TRIE_MAX_VALUES_SHRINK_CAPACITY: usize = 1_000_000;
|
||||
|
||||
/// Type alias for [`PayloadHandle`] returned by payload processor spawn methods.
|
||||
type IteratorPayloadHandle<Evm, I, N> = PayloadHandle<
|
||||
WithTxEnv<TxEnvFor<Evm>, <I as ExecutableTxTuple>::Tx>,
|
||||
<I as ExecutableTxTuple>::Error,
|
||||
<N as NodePrimitives>::Receipt,
|
||||
>;
|
||||
|
||||
/// Entrypoint for executing the payload.
|
||||
#[derive(Debug)]
|
||||
pub struct PayloadProcessor<Evm>
|
||||
@@ -211,6 +200,7 @@ where
|
||||
///
|
||||
/// This returns a handle to await the final state root and to interact with the tasks (e.g.
|
||||
/// canceling)
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[instrument(
|
||||
level = "debug",
|
||||
target = "engine::tree::payload_processor",
|
||||
@@ -225,7 +215,7 @@ where
|
||||
multiproof_provider_factory: F,
|
||||
config: &TreeConfig,
|
||||
bal: Option<Arc<BlockAccessList>>,
|
||||
) -> IteratorPayloadHandle<Evm, I, N>
|
||||
) -> PayloadHandle<WithTxEnv<TxEnvFor<Evm>, I::Tx>, I::Error>
|
||||
where
|
||||
P: BlockReader + StateProviderFactory + StateReader + Clone + 'static,
|
||||
F: DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>
|
||||
@@ -239,72 +229,70 @@ where
|
||||
|
||||
let span = Span::current();
|
||||
let (to_sparse_trie, sparse_trie_rx) = channel();
|
||||
let (to_multi_proof, from_multi_proof) = crossbeam_channel::unbounded();
|
||||
|
||||
// Handle BAL-based optimization if available
|
||||
let prewarm_handle = if let Some(bal) = bal {
|
||||
// When BAL is present, use BAL prewarming and send BAL to multiproof
|
||||
debug!(target: "engine::tree::payload_processor", "BAL present, using BAL prewarming");
|
||||
|
||||
// Send BAL message immediately to MultiProofTask
|
||||
let _ = to_multi_proof.send(MultiProofMessage::BlockAccessList(Arc::clone(&bal)));
|
||||
|
||||
// Spawn with BAL prewarming
|
||||
self.spawn_caching_with(
|
||||
env,
|
||||
prewarm_rx,
|
||||
transaction_count_hint,
|
||||
provider_builder.clone(),
|
||||
None, // Don't send proof targets when BAL is present
|
||||
Some(bal),
|
||||
)
|
||||
} else {
|
||||
// Normal path: spawn with transaction prewarming
|
||||
self.spawn_caching_with(
|
||||
env,
|
||||
prewarm_rx,
|
||||
transaction_count_hint,
|
||||
provider_builder.clone(),
|
||||
Some(to_multi_proof.clone()),
|
||||
None,
|
||||
)
|
||||
};
|
||||
// We rely on the cursor factory to provide whatever DB overlay is necessary to see a
|
||||
// consistent view of the database, including the trie tables. Because of this there is no
|
||||
// need for an overarching prefix set to invalidate any section of the trie tables, and so
|
||||
// we use an empty prefix set.
|
||||
|
||||
// Create and spawn the storage proof task
|
||||
let task_ctx = ProofTaskCtx::new(multiproof_provider_factory);
|
||||
let storage_worker_count = config.storage_worker_count();
|
||||
let account_worker_count = config.account_worker_count();
|
||||
let v2_proofs_enabled = config.enable_proof_v2();
|
||||
let proof_handle = ProofWorkerHandle::new(
|
||||
self.executor.handle().clone(),
|
||||
task_ctx,
|
||||
storage_worker_count,
|
||||
account_worker_count,
|
||||
v2_proofs_enabled,
|
||||
);
|
||||
|
||||
let multi_proof_task = MultiProofTask::new(
|
||||
proof_handle.clone(),
|
||||
to_sparse_trie,
|
||||
config.multiproof_chunking_enabled().then_some(config.multiproof_chunk_size()),
|
||||
to_multi_proof.clone(),
|
||||
from_multi_proof,
|
||||
);
|
||||
|
||||
// wire the multiproof task to the prewarm task
|
||||
let to_multi_proof = Some(multi_proof_task.state_root_message_sender());
|
||||
|
||||
// Handle BAL-based optimization if available
|
||||
let prewarm_handle = if let Some(bal) = bal {
|
||||
// When BAL is present, skip spawning prewarm tasks entirely and send BAL to multiproof
|
||||
debug!(target: "engine::tree::payload_processor", "BAL present, skipping prewarm tasks");
|
||||
|
||||
// Send BAL message immediately to MultiProofTask
|
||||
if let Some(ref sender) = to_multi_proof &&
|
||||
let Err(err) = sender.send(MultiProofMessage::BlockAccessList(bal))
|
||||
{
|
||||
// In this case state root validation will simply fail
|
||||
error!(target: "engine::tree::payload_processor", ?err, "Failed to send BAL to MultiProofTask");
|
||||
}
|
||||
|
||||
// Spawn minimal cache-only task without prewarming
|
||||
self.spawn_caching_with(
|
||||
env,
|
||||
prewarm_rx,
|
||||
transaction_count_hint,
|
||||
provider_builder.clone(),
|
||||
None, // Don't send proof targets when BAL is present
|
||||
)
|
||||
} else {
|
||||
// Normal path: spawn with full prewarming
|
||||
self.spawn_caching_with(
|
||||
env,
|
||||
prewarm_rx,
|
||||
transaction_count_hint,
|
||||
provider_builder.clone(),
|
||||
to_multi_proof.clone(),
|
||||
)
|
||||
};
|
||||
|
||||
// spawn multi-proof task
|
||||
let parent_span = span.clone();
|
||||
let saved_cache = prewarm_handle.saved_cache.clone();
|
||||
self.executor.spawn_blocking(move || {
|
||||
let _enter = parent_span.entered();
|
||||
// Build a state provider for the multiproof task
|
||||
let provider = provider_builder.build().expect("failed to build provider");
|
||||
let provider = if let Some(saved_cache) = saved_cache {
|
||||
let (cache, metrics) = saved_cache.split();
|
||||
Box::new(CachedStateProvider::new(provider, cache, metrics))
|
||||
as Box<dyn StateProvider>
|
||||
} else {
|
||||
Box::new(provider)
|
||||
};
|
||||
multi_proof_task.run(provider);
|
||||
});
|
||||
|
||||
@@ -315,7 +303,7 @@ where
|
||||
self.spawn_sparse_trie_task(sparse_trie_rx, proof_handle, state_root_tx);
|
||||
|
||||
PayloadHandle {
|
||||
to_multi_proof: Some(to_multi_proof),
|
||||
to_multi_proof,
|
||||
prewarm_handle,
|
||||
state_root: Some(state_root_rx),
|
||||
transactions: execution_rx,
|
||||
@@ -332,14 +320,13 @@ where
|
||||
env: ExecutionEnv<Evm>,
|
||||
transactions: I,
|
||||
provider_builder: StateProviderBuilder<N, P>,
|
||||
bal: Option<Arc<BlockAccessList>>,
|
||||
) -> IteratorPayloadHandle<Evm, I, N>
|
||||
) -> PayloadHandle<WithTxEnv<TxEnvFor<Evm>, I::Tx>, I::Error>
|
||||
where
|
||||
P: BlockReader + StateProviderFactory + StateReader + Clone + 'static,
|
||||
{
|
||||
let (prewarm_rx, execution_rx, size_hint) = self.spawn_tx_iterator(transactions);
|
||||
let prewarm_handle =
|
||||
self.spawn_caching_with(env, prewarm_rx, size_hint, provider_builder, None, bal);
|
||||
self.spawn_caching_with(env, prewarm_rx, size_hint, provider_builder, None);
|
||||
PayloadHandle {
|
||||
to_multi_proof: None,
|
||||
prewarm_handle,
|
||||
@@ -413,8 +400,7 @@ where
|
||||
transaction_count_hint: usize,
|
||||
provider_builder: StateProviderBuilder<N, P>,
|
||||
to_multi_proof: Option<CrossbeamSender<MultiProofMessage>>,
|
||||
bal: Option<Arc<BlockAccessList>>,
|
||||
) -> CacheTaskHandle<N::Receipt>
|
||||
) -> CacheTaskHandle
|
||||
where
|
||||
P: BlockReader + StateProviderFactory + StateReader + Clone + 'static,
|
||||
{
|
||||
@@ -424,13 +410,20 @@ where
|
||||
transactions = mpsc::channel().1;
|
||||
}
|
||||
|
||||
let saved_cache = self.disable_state_cache.not().then(|| self.cache_for(env.parent_hash));
|
||||
let (saved_cache, cache, cache_metrics) = if self.disable_state_cache {
|
||||
(None, None, None)
|
||||
} else {
|
||||
let saved_cache = self.cache_for(env.parent_hash);
|
||||
let cache = saved_cache.cache().clone();
|
||||
let cache_metrics = saved_cache.metrics().clone();
|
||||
(Some(saved_cache), Some(cache), Some(cache_metrics))
|
||||
};
|
||||
|
||||
// configure prewarming
|
||||
let prewarm_ctx = PrewarmContext {
|
||||
env,
|
||||
evm_config: self.evm_config.clone(),
|
||||
saved_cache: saved_cache.clone(),
|
||||
saved_cache,
|
||||
provider: provider_builder,
|
||||
metrics: PrewarmMetrics::default(),
|
||||
terminate_execution: Arc::new(AtomicBool::new(false)),
|
||||
@@ -451,16 +444,11 @@ where
|
||||
{
|
||||
let to_prewarm_task = to_prewarm_task.clone();
|
||||
self.executor.spawn_blocking(move || {
|
||||
let mode = if let Some(bal) = bal {
|
||||
PrewarmMode::BlockAccessList(bal)
|
||||
} else {
|
||||
PrewarmMode::Transactions(transactions)
|
||||
};
|
||||
prewarm_task.run(mode, to_prewarm_task);
|
||||
prewarm_task.run(transactions, to_prewarm_task);
|
||||
});
|
||||
}
|
||||
|
||||
CacheTaskHandle { saved_cache, to_prewarm_task: Some(to_prewarm_task) }
|
||||
CacheTaskHandle { cache, to_prewarm_task: Some(to_prewarm_task), cache_metrics }
|
||||
}
|
||||
|
||||
/// Returns the cache for the given parent hash.
|
||||
@@ -593,15 +581,12 @@ where
|
||||
}
|
||||
|
||||
/// Handle to all the spawned tasks.
|
||||
///
|
||||
/// Generic over `R` (receipt type) to allow sharing `Arc<ExecutionOutcome<R>>` with the
|
||||
/// caching task without cloning the expensive `BundleState`.
|
||||
#[derive(Debug)]
|
||||
pub struct PayloadHandle<Tx, Err, R> {
|
||||
pub struct PayloadHandle<Tx, Err> {
|
||||
/// Channel for evm state updates
|
||||
to_multi_proof: Option<CrossbeamSender<MultiProofMessage>>,
|
||||
// must include the receiver of the state root wired to the sparse trie
|
||||
prewarm_handle: CacheTaskHandle<R>,
|
||||
prewarm_handle: CacheTaskHandle,
|
||||
/// Stream of block transactions
|
||||
transactions: mpsc::Receiver<Result<Tx, Err>>,
|
||||
/// Receiver for the state root
|
||||
@@ -610,7 +595,7 @@ pub struct PayloadHandle<Tx, Err, R> {
|
||||
_span: Span,
|
||||
}
|
||||
|
||||
impl<Tx, Err, R: Send + Sync + 'static> PayloadHandle<Tx, Err, R> {
|
||||
impl<Tx, Err> PayloadHandle<Tx, Err> {
|
||||
/// Awaits the state root
|
||||
///
|
||||
/// # Panics
|
||||
@@ -646,12 +631,12 @@ impl<Tx, Err, R: Send + Sync + 'static> PayloadHandle<Tx, Err, R> {
|
||||
|
||||
/// Returns a clone of the caches used by prewarming
|
||||
pub(super) fn caches(&self) -> Option<StateExecutionCache> {
|
||||
self.prewarm_handle.saved_cache.as_ref().map(|cache| cache.cache().clone())
|
||||
self.prewarm_handle.cache.clone()
|
||||
}
|
||||
|
||||
/// Returns a clone of the cache metrics used by prewarming
|
||||
pub(super) fn cache_metrics(&self) -> Option<CachedStateMetrics> {
|
||||
self.prewarm_handle.saved_cache.as_ref().map(|cache| cache.metrics().clone())
|
||||
self.prewarm_handle.cache_metrics.clone()
|
||||
}
|
||||
|
||||
/// Terminates the pre-warming transaction processing.
|
||||
@@ -663,14 +648,9 @@ impl<Tx, Err, R: Send + Sync + 'static> PayloadHandle<Tx, Err, R> {
|
||||
|
||||
/// Terminates the entire caching task.
|
||||
///
|
||||
/// If the [`ExecutionOutcome`] is provided it will update the shared cache using its
|
||||
/// bundle state. Using `Arc<ExecutionOutcome>` allows sharing with the main execution
|
||||
/// path without cloning the expensive `BundleState`.
|
||||
pub(super) fn terminate_caching(
|
||||
&mut self,
|
||||
execution_outcome: Option<Arc<ExecutionOutcome<R>>>,
|
||||
) {
|
||||
self.prewarm_handle.terminate_caching(execution_outcome)
|
||||
/// If the [`BundleState`] is provided it will update the shared cache.
|
||||
pub(super) fn terminate_caching(&mut self, block_output: Option<&BundleState>) {
|
||||
self.prewarm_handle.terminate_caching(block_output)
|
||||
}
|
||||
|
||||
/// Returns iterator yielding transactions from the stream.
|
||||
@@ -682,18 +662,17 @@ impl<Tx, Err, R: Send + Sync + 'static> PayloadHandle<Tx, Err, R> {
|
||||
}
|
||||
|
||||
/// Access to the spawned [`PrewarmCacheTask`].
|
||||
///
|
||||
/// Generic over `R` (receipt type) to allow sharing `Arc<ExecutionOutcome<R>>` with the
|
||||
/// prewarm task without cloning the expensive `BundleState`.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CacheTaskHandle<R> {
|
||||
pub(crate) struct CacheTaskHandle {
|
||||
/// The shared cache the task operates with.
|
||||
saved_cache: Option<SavedCache>,
|
||||
cache: Option<StateExecutionCache>,
|
||||
/// Metrics for the caches
|
||||
cache_metrics: Option<CachedStateMetrics>,
|
||||
/// Channel to the spawned prewarm task if any
|
||||
to_prewarm_task: Option<std::sync::mpsc::Sender<PrewarmTaskEvent<R>>>,
|
||||
to_prewarm_task: Option<std::sync::mpsc::Sender<PrewarmTaskEvent>>,
|
||||
}
|
||||
|
||||
impl<R: Send + Sync + 'static> CacheTaskHandle<R> {
|
||||
impl CacheTaskHandle {
|
||||
/// Terminates the pre-warming transaction processing.
|
||||
///
|
||||
/// Note: This does not terminate the task yet.
|
||||
@@ -705,25 +684,20 @@ impl<R: Send + Sync + 'static> CacheTaskHandle<R> {
|
||||
|
||||
/// Terminates the entire pre-warming task.
|
||||
///
|
||||
/// If the [`ExecutionOutcome`] is provided it will update the shared cache using its
|
||||
/// bundle state. Using `Arc<ExecutionOutcome>` avoids cloning the expensive `BundleState`.
|
||||
pub(super) fn terminate_caching(
|
||||
&mut self,
|
||||
execution_outcome: Option<Arc<ExecutionOutcome<R>>>,
|
||||
) {
|
||||
/// If the [`BundleState`] is provided it will update the shared cache.
|
||||
pub(super) fn terminate_caching(&mut self, block_output: Option<&BundleState>) {
|
||||
if let Some(tx) = self.to_prewarm_task.take() {
|
||||
let event = PrewarmTaskEvent::Terminate { execution_outcome };
|
||||
// Only clone when we have an active task and a state to send
|
||||
let event = PrewarmTaskEvent::Terminate { block_output: block_output.cloned() };
|
||||
let _ = tx.send(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Drop for CacheTaskHandle<R> {
|
||||
impl Drop for CacheTaskHandle {
|
||||
fn drop(&mut self) {
|
||||
// Ensure we always terminate on drop - send None without needing Send + Sync bounds
|
||||
if let Some(tx) = self.to_prewarm_task.take() {
|
||||
let _ = tx.send(PrewarmTaskEvent::Terminate { execution_outcome: None });
|
||||
}
|
||||
// Ensure we always terminate on drop
|
||||
self.terminate_caching(None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -774,34 +748,10 @@ impl ExecutionCache {
|
||||
warn!(blocked_for=?elapsed, "Blocked waiting for execution cache mutex");
|
||||
}
|
||||
|
||||
if let Some(c) = cache.as_ref() {
|
||||
let cached_hash = c.executed_block_hash();
|
||||
// Check that the cache hash matches the parent hash of the current block. It won't
|
||||
// match in case it's a fork block.
|
||||
let hash_matches = cached_hash == parent_hash;
|
||||
// Check `is_available()` to ensure no other tasks (e.g., prewarming) currently hold
|
||||
// a reference to this cache. We can only reuse it when we have exclusive access.
|
||||
let available = c.is_available();
|
||||
let usage_count = c.usage_count();
|
||||
|
||||
debug!(
|
||||
target: "engine::caching",
|
||||
%cached_hash,
|
||||
%parent_hash,
|
||||
hash_matches,
|
||||
available,
|
||||
usage_count,
|
||||
"Existing cache found"
|
||||
);
|
||||
|
||||
if hash_matches && available {
|
||||
return Some(c.clone());
|
||||
}
|
||||
} else {
|
||||
debug!(target: "engine::caching", %parent_hash, "No cache found");
|
||||
}
|
||||
|
||||
None
|
||||
cache
|
||||
.as_ref()
|
||||
.filter(|c| c.executed_block_hash() == parent_hash && c.is_available())
|
||||
.cloned()
|
||||
}
|
||||
|
||||
/// Clears the tracked cache
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,30 +14,25 @@
|
||||
use crate::tree::{
|
||||
cached_state::{CachedStateProvider, SavedCache},
|
||||
payload_processor::{
|
||||
bal::{total_slots, BALSlotIter},
|
||||
executor::WorkloadExecutor,
|
||||
multiproof::MultiProofMessage,
|
||||
executor::WorkloadExecutor, multiproof::MultiProofMessage,
|
||||
ExecutionCache as PayloadExecutionCache,
|
||||
},
|
||||
precompile_cache::{CachedPrecompile, PrecompileCacheMap},
|
||||
ExecutionEnv, StateProviderBuilder,
|
||||
};
|
||||
use alloy_consensus::transaction::TxHashRef;
|
||||
use alloy_eip7928::BlockAccessList;
|
||||
use alloy_eips::Typed2718;
|
||||
use alloy_evm::Database;
|
||||
use alloy_primitives::{keccak256, map::B256Set, B256};
|
||||
use crossbeam_channel::Sender as CrossbeamSender;
|
||||
use metrics::{Counter, Gauge, Histogram};
|
||||
use reth_evm::{execute::ExecutableTxFor, ConfigureEvm, Evm, EvmFor, SpecFor};
|
||||
use reth_execution_types::ExecutionOutcome;
|
||||
use reth_metrics::Metrics;
|
||||
use reth_primitives_traits::NodePrimitives;
|
||||
use reth_provider::{AccountReader, BlockReader, StateProvider, StateProviderFactory, StateReader};
|
||||
use reth_revm::{database::StateProviderDatabase, state::EvmState};
|
||||
use reth_provider::{BlockReader, StateProviderBox, StateProviderFactory, StateReader};
|
||||
use reth_revm::{database::StateProviderDatabase, db::BundleState, state::EvmState};
|
||||
use reth_trie::MultiProofTargets;
|
||||
use std::{
|
||||
ops::Range,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc::{self, channel, Receiver, Sender},
|
||||
@@ -47,14 +42,6 @@ use std::{
|
||||
};
|
||||
use tracing::{debug, debug_span, instrument, trace, warn, Span};
|
||||
|
||||
/// Determines the prewarming mode: transaction-based or BAL-based.
|
||||
pub(super) enum PrewarmMode<Tx> {
|
||||
/// Prewarm by executing transactions from a stream.
|
||||
Transactions(Receiver<Tx>),
|
||||
/// Prewarm by prefetching slots from a Block Access List.
|
||||
BlockAccessList(Arc<BlockAccessList>),
|
||||
}
|
||||
|
||||
/// A wrapper for transactions that includes their index in the block.
|
||||
#[derive(Clone)]
|
||||
struct IndexedTransaction<Tx> {
|
||||
@@ -99,7 +86,7 @@ where
|
||||
/// Sender to emit evm state outcome messages, if any.
|
||||
to_multi_proof: Option<CrossbeamSender<MultiProofMessage>>,
|
||||
/// Receiver for events produced by tx execution
|
||||
actions_rx: Receiver<PrewarmTaskEvent<N::Receipt>>,
|
||||
actions_rx: Receiver<PrewarmTaskEvent>,
|
||||
/// Parent span for tracing
|
||||
parent_span: Span,
|
||||
}
|
||||
@@ -118,7 +105,7 @@ where
|
||||
to_multi_proof: Option<CrossbeamSender<MultiProofMessage>>,
|
||||
transaction_count_hint: usize,
|
||||
max_concurrency: usize,
|
||||
) -> (Self, Sender<PrewarmTaskEvent<N::Receipt>>) {
|
||||
) -> (Self, Sender<PrewarmTaskEvent>) {
|
||||
let (actions_tx, actions_rx) = channel();
|
||||
|
||||
trace!(
|
||||
@@ -148,11 +135,8 @@ where
|
||||
/// For Optimism chains, special handling is applied to the first transaction if it's a
|
||||
/// deposit transaction (type 0x7E/126) which sets critical metadata that affects all
|
||||
/// subsequent transactions in the block.
|
||||
fn spawn_all<Tx>(
|
||||
&self,
|
||||
pending: mpsc::Receiver<Tx>,
|
||||
actions_tx: Sender<PrewarmTaskEvent<N::Receipt>>,
|
||||
) where
|
||||
fn spawn_all<Tx>(&self, pending: mpsc::Receiver<Tx>, actions_tx: Sender<PrewarmTaskEvent>)
|
||||
where
|
||||
Tx: ExecutableTxFor<Evm> + Clone + Send + 'static,
|
||||
{
|
||||
let executor = self.executor.clone();
|
||||
@@ -176,7 +160,12 @@ where
|
||||
};
|
||||
|
||||
// Initialize worker handles container
|
||||
let handles = ctx.clone().spawn_workers(workers_needed, &executor, actions_tx.clone(), done_tx.clone());
|
||||
let mut handles = Vec::with_capacity(workers_needed);
|
||||
|
||||
// Only spawn initial workers as needed
|
||||
for i in 0..workers_needed {
|
||||
handles.push(ctx.spawn_worker(i, &executor, actions_tx.clone(), done_tx.clone()));
|
||||
}
|
||||
|
||||
// Distribute transactions to workers
|
||||
let mut tx_index = 0usize;
|
||||
@@ -259,7 +248,7 @@ where
|
||||
///
|
||||
/// This method is called from `run()` only after all execution tasks are complete.
|
||||
#[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)]
|
||||
fn save_cache(self, execution_outcome: Arc<ExecutionOutcome<N::Receipt>>) {
|
||||
fn save_cache(self, state: BundleState) {
|
||||
let start = Instant::now();
|
||||
|
||||
let Self { execution_cache, ctx: PrewarmContext { env, metrics, saved_cache, .. }, .. } =
|
||||
@@ -276,8 +265,7 @@ where
|
||||
let new_cache = SavedCache::new(hash, caches, cache_metrics);
|
||||
|
||||
// Insert state into cache while holding the lock
|
||||
// Access the BundleState through the shared ExecutionOutcome
|
||||
if new_cache.cache().insert_state(execution_outcome.state()).is_err() {
|
||||
if new_cache.cache().insert_state(&state).is_err() {
|
||||
// Clear the cache on error to prevent having a polluted cache
|
||||
*cached = None;
|
||||
debug!(target: "engine::caching", "cleared execution cache on update error");
|
||||
@@ -298,86 +286,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs BAL-based prewarming by spawning workers to prefetch storage slots.
|
||||
///
|
||||
/// Divides the total slots across `max_concurrency` workers, each responsible for
|
||||
/// prefetching a range of slots from the BAL.
|
||||
#[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)]
|
||||
fn run_bal_prewarm(
|
||||
&self,
|
||||
bal: Arc<BlockAccessList>,
|
||||
actions_tx: Sender<PrewarmTaskEvent<N::Receipt>>,
|
||||
) {
|
||||
// Only prefetch if we have a cache to populate
|
||||
if self.ctx.saved_cache.is_none() {
|
||||
trace!(
|
||||
target: "engine::tree::payload_processor::prewarm",
|
||||
"Skipping BAL prewarm - no cache available"
|
||||
);
|
||||
let _ =
|
||||
actions_tx.send(PrewarmTaskEvent::FinishedTxExecution { executed_transactions: 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
let total_slots = total_slots(&bal);
|
||||
|
||||
trace!(
|
||||
target: "engine::tree::payload_processor::prewarm",
|
||||
total_slots,
|
||||
max_concurrency = self.max_concurrency,
|
||||
"Starting BAL prewarm"
|
||||
);
|
||||
|
||||
if total_slots == 0 {
|
||||
// No slots to prefetch, signal completion immediately
|
||||
let _ =
|
||||
actions_tx.send(PrewarmTaskEvent::FinishedTxExecution { executed_transactions: 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
let (done_tx, done_rx) = mpsc::channel();
|
||||
|
||||
// Calculate number of workers needed (at most max_concurrency)
|
||||
let workers_needed = total_slots.min(self.max_concurrency);
|
||||
|
||||
// Calculate slots per worker
|
||||
let slots_per_worker = total_slots / workers_needed;
|
||||
let remainder = total_slots % workers_needed;
|
||||
|
||||
// Spawn workers with their assigned ranges
|
||||
for i in 0..workers_needed {
|
||||
let start = i * slots_per_worker + i.min(remainder);
|
||||
let extra = if i < remainder { 1 } else { 0 };
|
||||
let end = start + slots_per_worker + extra;
|
||||
|
||||
self.ctx.spawn_bal_worker(
|
||||
i,
|
||||
&self.executor,
|
||||
Arc::clone(&bal),
|
||||
start..end,
|
||||
done_tx.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
// Drop our handle to done_tx so we can detect completion
|
||||
drop(done_tx);
|
||||
|
||||
// Wait for all workers to complete
|
||||
let mut completed_workers = 0;
|
||||
while done_rx.recv().is_ok() {
|
||||
completed_workers += 1;
|
||||
}
|
||||
|
||||
trace!(
|
||||
target: "engine::tree::payload_processor::prewarm",
|
||||
completed_workers,
|
||||
"All BAL prewarm workers completed"
|
||||
);
|
||||
|
||||
// Signal that execution has finished
|
||||
let _ = actions_tx.send(PrewarmTaskEvent::FinishedTxExecution { executed_transactions: 0 });
|
||||
}
|
||||
|
||||
/// Executes the task.
|
||||
///
|
||||
/// This will execute the transactions until all transactions have been processed or the task
|
||||
@@ -389,24 +297,15 @@ where
|
||||
name = "prewarm and caching",
|
||||
skip_all
|
||||
)]
|
||||
pub(super) fn run<Tx>(
|
||||
pub(super) fn run(
|
||||
self,
|
||||
mode: PrewarmMode<Tx>,
|
||||
actions_tx: Sender<PrewarmTaskEvent<N::Receipt>>,
|
||||
) where
|
||||
Tx: ExecutableTxFor<Evm> + Clone + Send + 'static,
|
||||
{
|
||||
// Spawn execution tasks based on mode
|
||||
match mode {
|
||||
PrewarmMode::Transactions(pending) => {
|
||||
self.spawn_all(pending, actions_tx);
|
||||
}
|
||||
PrewarmMode::BlockAccessList(bal) => {
|
||||
self.run_bal_prewarm(bal, actions_tx);
|
||||
}
|
||||
}
|
||||
pending: mpsc::Receiver<impl ExecutableTxFor<Evm> + Clone + Send + 'static>,
|
||||
actions_tx: Sender<PrewarmTaskEvent>,
|
||||
) {
|
||||
// spawn execution tasks.
|
||||
self.spawn_all(pending, actions_tx);
|
||||
|
||||
let mut final_execution_outcome = None;
|
||||
let mut final_block_output = None;
|
||||
let mut finished_execution = false;
|
||||
while let Ok(event) = self.actions_rx.recv() {
|
||||
match event {
|
||||
@@ -419,9 +318,9 @@ where
|
||||
// completed executing a set of transactions
|
||||
self.send_multi_proof_targets(proof_targets);
|
||||
}
|
||||
PrewarmTaskEvent::Terminate { execution_outcome } => {
|
||||
PrewarmTaskEvent::Terminate { block_output } => {
|
||||
trace!(target: "engine::tree::payload_processor::prewarm", "Received termination signal");
|
||||
final_execution_outcome = Some(execution_outcome);
|
||||
final_block_output = Some(block_output);
|
||||
|
||||
if finished_execution {
|
||||
// all tasks are done, we can exit, which will save caches and exit
|
||||
@@ -435,7 +334,7 @@ where
|
||||
|
||||
finished_execution = true;
|
||||
|
||||
if final_execution_outcome.is_some() {
|
||||
if final_block_output.is_some() {
|
||||
// all tasks are done, we can exit, which will save caches and exit
|
||||
break
|
||||
}
|
||||
@@ -445,9 +344,9 @@ where
|
||||
|
||||
debug!(target: "engine::tree::payload_processor::prewarm", "Completed prewarm execution");
|
||||
|
||||
// save caches and finish using the shared ExecutionOutcome
|
||||
if let Some(Some(execution_outcome)) = final_execution_outcome {
|
||||
self.save_cache(execution_outcome);
|
||||
// save caches and finish
|
||||
if let Some(Some(state)) = final_block_output {
|
||||
self.save_cache(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -489,10 +388,10 @@ where
|
||||
metrics,
|
||||
terminate_execution,
|
||||
precompile_cache_disabled,
|
||||
precompile_cache_map,
|
||||
mut precompile_cache_map,
|
||||
} = self;
|
||||
|
||||
let mut state_provider = match provider.build() {
|
||||
let state_provider = match provider.build() {
|
||||
Ok(provider) => provider,
|
||||
Err(err) => {
|
||||
trace!(
|
||||
@@ -505,15 +404,13 @@ where
|
||||
};
|
||||
|
||||
// Use the caches to create a new provider with caching
|
||||
if let Some(saved_cache) = saved_cache {
|
||||
let state_provider: StateProviderBox = if let Some(saved_cache) = saved_cache {
|
||||
let caches = saved_cache.cache().clone();
|
||||
let cache_metrics = saved_cache.metrics().clone();
|
||||
state_provider = Box::new(
|
||||
CachedStateProvider::new(state_provider, caches, cache_metrics)
|
||||
// ensure we pre-warm the cache
|
||||
.prewarm(),
|
||||
);
|
||||
}
|
||||
Box::new(CachedStateProvider::new_with_caches(state_provider, caches, cache_metrics))
|
||||
} else {
|
||||
state_provider
|
||||
};
|
||||
|
||||
let state_provider = StateProviderDatabase::new(state_provider);
|
||||
|
||||
@@ -555,7 +452,7 @@ where
|
||||
fn transact_batch<Tx>(
|
||||
self,
|
||||
txs: mpsc::Receiver<IndexedTransaction<Tx>>,
|
||||
sender: Sender<PrewarmTaskEvent<N::Receipt>>,
|
||||
sender: Sender<PrewarmTaskEvent>,
|
||||
done_tx: Sender<()>,
|
||||
) where
|
||||
Tx: ExecutableTxFor<Evm>,
|
||||
@@ -632,134 +529,27 @@ where
|
||||
}
|
||||
|
||||
/// Spawns a worker task for transaction execution and returns its sender channel.
|
||||
fn spawn_workers<Tx>(
|
||||
self,
|
||||
workers_needed: usize,
|
||||
task_executor: &WorkloadExecutor,
|
||||
actions_tx: Sender<PrewarmTaskEvent<N::Receipt>>,
|
||||
done_tx: Sender<()>,
|
||||
) -> Vec<mpsc::Sender<IndexedTransaction<Tx>>>
|
||||
where
|
||||
Tx: ExecutableTxFor<Evm> + Send + 'static,
|
||||
{
|
||||
let mut handles = Vec::with_capacity(workers_needed);
|
||||
let mut receivers = Vec::with_capacity(workers_needed);
|
||||
|
||||
for _ in 0..workers_needed {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
handles.push(tx);
|
||||
receivers.push(rx);
|
||||
}
|
||||
|
||||
// Spawn a separate task spawning workers in parallel.
|
||||
let executor = task_executor.clone();
|
||||
let span = Span::current();
|
||||
task_executor.spawn_blocking(move || {
|
||||
let _enter = span.entered();
|
||||
for (idx, rx) in receivers.into_iter().enumerate() {
|
||||
let ctx = self.clone();
|
||||
let actions_tx = actions_tx.clone();
|
||||
let done_tx = done_tx.clone();
|
||||
let span = debug_span!(target: "engine::tree::payload_processor::prewarm", "prewarm worker", idx);
|
||||
executor.spawn_blocking(move || {
|
||||
let _enter = span.entered();
|
||||
ctx.transact_batch(rx, actions_tx, done_tx);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
handles
|
||||
}
|
||||
|
||||
/// Spawns a worker task for BAL slot prefetching.
|
||||
///
|
||||
/// The worker iterates over the specified range of slots in the BAL and ensures
|
||||
/// each slot is loaded into the cache by accessing it through the state provider.
|
||||
fn spawn_bal_worker(
|
||||
fn spawn_worker<Tx>(
|
||||
&self,
|
||||
idx: usize,
|
||||
executor: &WorkloadExecutor,
|
||||
bal: Arc<BlockAccessList>,
|
||||
range: Range<usize>,
|
||||
actions_tx: Sender<PrewarmTaskEvent>,
|
||||
done_tx: Sender<()>,
|
||||
) {
|
||||
) -> mpsc::Sender<IndexedTransaction<Tx>>
|
||||
where
|
||||
Tx: ExecutableTxFor<Evm> + Send + 'static,
|
||||
{
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let ctx = self.clone();
|
||||
let span = debug_span!(
|
||||
target: "engine::tree::payload_processor::prewarm",
|
||||
"bal prewarm worker",
|
||||
idx,
|
||||
range_start = range.start,
|
||||
range_end = range.end
|
||||
);
|
||||
let span =
|
||||
debug_span!(target: "engine::tree::payload_processor::prewarm", "prewarm worker", idx);
|
||||
|
||||
executor.spawn_blocking(move || {
|
||||
let _enter = span.entered();
|
||||
ctx.prefetch_bal_slots(bal, range, done_tx);
|
||||
ctx.transact_batch(rx, actions_tx, done_tx);
|
||||
});
|
||||
}
|
||||
|
||||
/// Prefetches storage slots from a BAL range into the cache.
|
||||
///
|
||||
/// This iterates through the specified range of slots and accesses them via the state
|
||||
/// provider to populate the cache.
|
||||
#[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)]
|
||||
fn prefetch_bal_slots(
|
||||
self,
|
||||
bal: Arc<BlockAccessList>,
|
||||
range: Range<usize>,
|
||||
done_tx: Sender<()>,
|
||||
) {
|
||||
let Self { saved_cache, provider, metrics, .. } = self;
|
||||
|
||||
// Build state provider
|
||||
let state_provider = match provider.build() {
|
||||
Ok(provider) => provider,
|
||||
Err(err) => {
|
||||
trace!(
|
||||
target: "engine::tree::payload_processor::prewarm",
|
||||
%err,
|
||||
"Failed to build state provider in BAL prewarm thread"
|
||||
);
|
||||
let _ = done_tx.send(());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Wrap with cache (guaranteed to be Some since run_bal_prewarm checks)
|
||||
let saved_cache = saved_cache.expect("BAL prewarm should only run with cache");
|
||||
let caches = saved_cache.cache().clone();
|
||||
let cache_metrics = saved_cache.metrics().clone();
|
||||
let state_provider = CachedStateProvider::new(state_provider, caches, cache_metrics);
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
// Track last seen address to avoid fetching the same account multiple times.
|
||||
let mut last_address = None;
|
||||
|
||||
// Iterate through the assigned range of slots
|
||||
for (address, slot) in BALSlotIter::new(&bal, range.clone()) {
|
||||
// Fetch the account if this is a different address than the last one
|
||||
if last_address != Some(address) {
|
||||
let _ = state_provider.basic_account(&address);
|
||||
last_address = Some(address);
|
||||
}
|
||||
|
||||
// Access the slot to populate the cache
|
||||
let _ = state_provider.storage(address, slot);
|
||||
}
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
trace!(
|
||||
target: "engine::tree::payload_processor::prewarm",
|
||||
?range,
|
||||
elapsed_ms = elapsed.as_millis(),
|
||||
"BAL prewarm worker completed"
|
||||
);
|
||||
|
||||
// Signal completion
|
||||
let _ = done_tx.send(());
|
||||
metrics.bal_slot_iteration_duration.record(elapsed.as_secs_f64());
|
||||
tx
|
||||
}
|
||||
}
|
||||
|
||||
@@ -799,18 +589,14 @@ fn multiproof_targets_from_state(state: EvmState) -> (MultiProofTargets, usize)
|
||||
}
|
||||
|
||||
/// The events the pre-warm task can handle.
|
||||
///
|
||||
/// Generic over `R` (receipt type) to allow sharing `Arc<ExecutionOutcome<R>>` with the main
|
||||
/// execution path without cloning the expensive `BundleState`.
|
||||
pub(super) enum PrewarmTaskEvent<R> {
|
||||
pub(super) enum PrewarmTaskEvent {
|
||||
/// Forcefully terminate all remaining transaction execution.
|
||||
TerminateTransactionExecution,
|
||||
/// Forcefully terminate the task on demand and update the shared cache with the given output
|
||||
/// before exiting.
|
||||
Terminate {
|
||||
/// The final execution outcome. Using `Arc` allows sharing with the main execution
|
||||
/// path without cloning the expensive `BundleState`.
|
||||
execution_outcome: Option<Arc<ExecutionOutcome<R>>>,
|
||||
/// The final block state output.
|
||||
block_output: Option<BundleState>,
|
||||
},
|
||||
/// The outcome of a pre-warm task
|
||||
Outcome {
|
||||
@@ -842,6 +628,4 @@ pub(crate) struct PrewarmMetrics {
|
||||
pub(crate) cache_saving_duration: Gauge,
|
||||
/// Counter for transaction execution errors during prewarming
|
||||
pub(crate) transaction_errors: Counter,
|
||||
/// A histogram of BAL slot iteration duration during prefetching
|
||||
pub(crate) bal_slot_iteration_duration: Histogram,
|
||||
}
|
||||
|
||||
@@ -166,14 +166,15 @@ where
|
||||
|
||||
// Update storage slots with new values and calculate storage roots.
|
||||
let span = tracing::Span::current();
|
||||
let results: Vec<_> = state
|
||||
let (tx, rx) = mpsc::channel();
|
||||
state
|
||||
.storages
|
||||
.into_iter()
|
||||
.map(|(address, storage)| (address, storage, trie.take_storage_trie(&address)))
|
||||
.par_bridge()
|
||||
.map(|(address, storage, storage_trie)| {
|
||||
let _enter =
|
||||
debug_span!(target: "engine::tree::payload_processor::sparse_trie", parent: &span, "storage trie", ?address)
|
||||
debug_span!(target: "engine::tree::payload_processor::sparse_trie", parent: span.clone(), "storage trie", ?address)
|
||||
.entered();
|
||||
|
||||
trace!(target: "engine::tree::payload_processor::sparse_trie", "Updating storage");
|
||||
@@ -216,7 +217,13 @@ where
|
||||
|
||||
SparseStateTrieResult::Ok((address, storage_trie))
|
||||
})
|
||||
.collect();
|
||||
.for_each_init(
|
||||
|| tx.clone(),
|
||||
|tx, result| {
|
||||
let _ = tx.send(result);
|
||||
},
|
||||
);
|
||||
drop(tx);
|
||||
|
||||
// Defer leaf removals until after updates/additions, so that we don't delete an intermediate
|
||||
// branch node during a removal and then re-add that branch back during a later leaf addition.
|
||||
@@ -228,7 +235,7 @@ where
|
||||
let _enter =
|
||||
tracing::debug_span!(target: "engine::tree::payload_processor::sparse_trie", "account trie")
|
||||
.entered();
|
||||
for result in results {
|
||||
for result in rx {
|
||||
let (address, storage_trie) = result?;
|
||||
trie.insert_storage_trie(address, storage_trie);
|
||||
|
||||
|
||||
@@ -34,13 +34,13 @@ use reth_primitives_traits::{
|
||||
SealedHeader, SignerRecoverable,
|
||||
};
|
||||
use reth_provider::{
|
||||
providers::OverlayStateProviderFactory, BlockExecutionOutput, BlockNumReader, BlockReader,
|
||||
ChangeSetReader, DatabaseProviderFactory, DatabaseProviderROFactory, ExecutionOutcome,
|
||||
HashedPostStateProvider, ProviderError, PruneCheckpointReader, StageCheckpointReader,
|
||||
StateProvider, StateProviderFactory, StateReader, TrieReader,
|
||||
providers::OverlayStateProviderFactory, BlockExecutionOutput, BlockReader,
|
||||
DatabaseProviderFactory, ExecutionOutcome, HashedPostStateProvider, ProviderError,
|
||||
PruneCheckpointReader, StageCheckpointReader, StateProvider, StateProviderFactory, StateReader,
|
||||
StateRootProvider, TrieReader,
|
||||
};
|
||||
use reth_revm::db::State;
|
||||
use reth_trie::{updates::TrieUpdates, HashedPostState, StateRoot, TrieInputSorted};
|
||||
use reth_trie::{updates::TrieUpdates, HashedPostState, TrieInputSorted};
|
||||
use reth_trie_parallel::root::{ParallelStateRoot, ParallelStateRootError};
|
||||
use revm_primitives::Address;
|
||||
use std::{
|
||||
@@ -111,7 +111,7 @@ where
|
||||
/// Provider for database access.
|
||||
provider: P,
|
||||
/// Consensus implementation for validation.
|
||||
consensus: Arc<dyn FullConsensus<Evm::Primitives>>,
|
||||
consensus: Arc<dyn FullConsensus<Evm::Primitives, Error = ConsensusError>>,
|
||||
/// EVM configuration.
|
||||
evm_config: Evm,
|
||||
/// Configuration for the tree.
|
||||
@@ -135,15 +135,8 @@ impl<N, P, Evm, V> BasicEngineValidator<P, Evm, V>
|
||||
where
|
||||
N: NodePrimitives,
|
||||
P: DatabaseProviderFactory<
|
||||
Provider: BlockReader
|
||||
+ TrieReader
|
||||
+ StageCheckpointReader
|
||||
+ PruneCheckpointReader
|
||||
+ ChangeSetReader
|
||||
+ BlockNumReader,
|
||||
Provider: BlockReader + TrieReader + StageCheckpointReader + PruneCheckpointReader,
|
||||
> + BlockReader<Header = N::BlockHeader>
|
||||
+ ChangeSetReader
|
||||
+ BlockNumReader
|
||||
+ StateProviderFactory
|
||||
+ StateReader
|
||||
+ HashedPostStateProvider
|
||||
@@ -155,7 +148,7 @@ where
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
provider: P,
|
||||
consensus: Arc<dyn FullConsensus<N>>,
|
||||
consensus: Arc<dyn FullConsensus<N, Error = ConsensusError>>,
|
||||
evm_config: Evm,
|
||||
validator: V,
|
||||
config: TreeConfig,
|
||||
@@ -381,8 +374,7 @@ where
|
||||
let mut state_provider = ensure_ok!(provider_builder.build());
|
||||
drop(_enter);
|
||||
|
||||
// Fetch parent block. This goes to memory most of the time unless the parent block is
|
||||
// beyond the in-memory buffer.
|
||||
// fetch parent block
|
||||
let Some(parent_block) = ensure_ok!(self.sealed_header_by_hash(parent_hash, ctx.state()))
|
||||
else {
|
||||
return Err(InsertBlockError::new(
|
||||
@@ -407,7 +399,7 @@ where
|
||||
"Decided which state root algorithm to run"
|
||||
);
|
||||
|
||||
// Get an iterator over the transactions in the payload
|
||||
// use prewarming background task
|
||||
let txs = self.tx_iterator_for(&input)?;
|
||||
|
||||
// Extract the BAL, if valid and available
|
||||
@@ -432,16 +424,21 @@ where
|
||||
// Use cached state provider before executing, used in execution after prewarming threads
|
||||
// complete
|
||||
if let Some((caches, cache_metrics)) = handle.caches().zip(handle.cache_metrics()) {
|
||||
state_provider =
|
||||
Box::new(CachedStateProvider::new(state_provider, caches, cache_metrics));
|
||||
state_provider = Box::new(CachedStateProvider::new_with_caches(
|
||||
state_provider,
|
||||
caches,
|
||||
cache_metrics,
|
||||
));
|
||||
};
|
||||
|
||||
if self.config.state_provider_metrics() {
|
||||
state_provider = Box::new(InstrumentedStateProvider::new(state_provider, "engine"));
|
||||
}
|
||||
|
||||
// Execute the block and handle any execution errors
|
||||
let (output, senders) = match self.execute_block(state_provider, env, &input, &mut handle) {
|
||||
let (output, senders) = match if self.config.state_provider_metrics() {
|
||||
let state_provider =
|
||||
InstrumentedStateProvider::from_state_provider(&state_provider, "engine");
|
||||
self.execute_block(&state_provider, env, &input, &mut handle)
|
||||
} else {
|
||||
self.execute_block(&state_provider, env, &input, &mut handle)
|
||||
} {
|
||||
Ok(output) => output,
|
||||
Err(err) => return self.handle_execution_error(input, err, &parent_block),
|
||||
};
|
||||
@@ -525,7 +522,7 @@ where
|
||||
}
|
||||
|
||||
let (root, updates) = ensure_ok_post_block!(
|
||||
self.compute_state_root_serial(block.parent_hash(), &hashed_state, ctx.state()),
|
||||
state_provider.state_root_with_updates(hashed_state.clone()),
|
||||
block
|
||||
);
|
||||
(root, updates, root_time.elapsed())
|
||||
@@ -555,14 +552,17 @@ where
|
||||
.into())
|
||||
}
|
||||
|
||||
// Create ExecutionOutcome and wrap in Arc for sharing with both the caching task
|
||||
// and the deferred trie task. This avoids cloning the expensive BundleState.
|
||||
let execution_outcome = Arc::new(ExecutionOutcome::from((output, block_num_hash.number)));
|
||||
// terminate prewarming task with good state output
|
||||
handle.terminate_caching(Some(&output.state));
|
||||
|
||||
// Terminate prewarming task with the shared execution outcome
|
||||
handle.terminate_caching(Some(Arc::clone(&execution_outcome)));
|
||||
|
||||
Ok(self.spawn_deferred_trie_task(block, execution_outcome, &ctx, hashed_state, trie_output))
|
||||
Ok(self.spawn_deferred_trie_task(
|
||||
block,
|
||||
output,
|
||||
block_num_hash.number,
|
||||
&ctx,
|
||||
hashed_state,
|
||||
trie_output,
|
||||
))
|
||||
}
|
||||
|
||||
/// Return sealed block header from database or in-memory state by hash.
|
||||
@@ -605,10 +605,10 @@ where
|
||||
state_provider: S,
|
||||
env: ExecutionEnv<Evm>,
|
||||
input: &BlockOrPayload<T>,
|
||||
handle: &mut PayloadHandle<impl ExecutableTxFor<Evm>, Err, N::Receipt>,
|
||||
handle: &mut PayloadHandle<impl ExecutableTxFor<Evm>, Err>,
|
||||
) -> Result<(BlockExecutionOutput<N::Receipt>, Vec<Address>), InsertBlockErrorKind>
|
||||
where
|
||||
S: StateProvider + Send,
|
||||
S: StateProvider,
|
||||
Err: core::error::Error + Send + Sync + 'static,
|
||||
V: PayloadValidator<T, Block = N::Block>,
|
||||
T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
|
||||
@@ -617,7 +617,7 @@ where
|
||||
debug!(target: "engine::tree::payload_validator", "Executing block");
|
||||
|
||||
let mut db = State::builder()
|
||||
.with_database(StateProviderDatabase::new(state_provider))
|
||||
.with_database(StateProviderDatabase::new(&state_provider))
|
||||
.with_bundle_update()
|
||||
.without_state_clear()
|
||||
.build();
|
||||
@@ -649,7 +649,6 @@ where
|
||||
let (output, senders) = self.metrics.execute_metered(
|
||||
executor,
|
||||
handle.iter_transactions().map(|res| res.map_err(BlockExecutionError::other)),
|
||||
input.transaction_count(),
|
||||
state_hook,
|
||||
)?;
|
||||
let execution_finish = Instant::now();
|
||||
@@ -664,6 +663,8 @@ where
|
||||
///
|
||||
/// Returns `Ok(_)` if computed successfully.
|
||||
/// Returns `Err(_)` if error was encountered during computation.
|
||||
/// `Err(ProviderError::ConsistentView(_))` can be safely ignored and fallback computation
|
||||
/// should be used instead.
|
||||
#[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
|
||||
fn compute_state_root_parallel(
|
||||
&self,
|
||||
@@ -693,35 +694,6 @@ where
|
||||
ParallelStateRoot::new(factory, prefix_sets).incremental_root_with_updates()
|
||||
}
|
||||
|
||||
/// Compute state root for the given hashed post state in serial.
|
||||
fn compute_state_root_serial(
|
||||
&self,
|
||||
parent_hash: B256,
|
||||
hashed_state: &HashedPostState,
|
||||
state: &EngineApiTreeState<N>,
|
||||
) -> ProviderResult<(B256, TrieUpdates)> {
|
||||
let (mut input, block_hash) = self.compute_trie_input(parent_hash, state)?;
|
||||
|
||||
// Extend state overlay with current block's sorted state.
|
||||
input.prefix_sets.extend(hashed_state.construct_prefix_sets());
|
||||
let sorted_hashed_state = hashed_state.clone_into_sorted();
|
||||
Arc::make_mut(&mut input.state).extend_ref(&sorted_hashed_state);
|
||||
|
||||
let TrieInputSorted { nodes, state, .. } = input;
|
||||
let prefix_sets = hashed_state.construct_prefix_sets();
|
||||
|
||||
let factory = OverlayStateProviderFactory::new(self.provider.clone())
|
||||
.with_block_hash(Some(block_hash))
|
||||
.with_trie_overlay(Some(nodes))
|
||||
.with_hashed_state_overlay(Some(state));
|
||||
|
||||
let provider = factory.database_provider_ro()?;
|
||||
|
||||
Ok(StateRoot::new(&provider, &provider)
|
||||
.with_prefix_sets(prefix_sets.freeze())
|
||||
.root_with_updates()?)
|
||||
}
|
||||
|
||||
/// Validates the block after execution.
|
||||
///
|
||||
/// This performs:
|
||||
@@ -821,7 +793,6 @@ where
|
||||
PayloadHandle<
|
||||
impl ExecutableTxFor<Evm> + use<N, P, Evm, V, T>,
|
||||
impl core::error::Error + Send + Sync + 'static + use<N, P, Evm, V, T>,
|
||||
N::Receipt,
|
||||
>,
|
||||
InsertBlockErrorKind,
|
||||
> {
|
||||
@@ -867,12 +838,8 @@ where
|
||||
}
|
||||
StateRootStrategy::Parallel | StateRootStrategy::Synchronous => {
|
||||
let start = Instant::now();
|
||||
let handle = self.payload_processor.spawn_cache_exclusive(
|
||||
env,
|
||||
txs,
|
||||
provider_builder,
|
||||
block_access_list,
|
||||
);
|
||||
let handle =
|
||||
self.payload_processor.spawn_cache_exclusive(env, txs, provider_builder);
|
||||
|
||||
// Record prewarming initialization duration
|
||||
self.metrics
|
||||
@@ -1060,7 +1027,8 @@ where
|
||||
fn spawn_deferred_trie_task(
|
||||
&self,
|
||||
block: RecoveredBlock<N::Block>,
|
||||
execution_outcome: Arc<ExecutionOutcome<N::Receipt>>,
|
||||
output: BlockExecutionOutput<N::Receipt>,
|
||||
block_number: u64,
|
||||
ctx: &TreeCtx<'_, N>,
|
||||
hashed_state: HashedPostState,
|
||||
trie_output: TrieUpdates,
|
||||
@@ -1110,7 +1078,7 @@ where
|
||||
|
||||
ExecutedBlock::with_deferred_trie_data(
|
||||
Arc::new(block),
|
||||
execution_outcome,
|
||||
Arc::new(ExecutionOutcome::from((output, block_number))),
|
||||
deferred_trie_data,
|
||||
)
|
||||
}
|
||||
@@ -1190,17 +1158,10 @@ pub trait EngineValidator<
|
||||
impl<N, Types, P, Evm, V> EngineValidator<Types> for BasicEngineValidator<P, Evm, V>
|
||||
where
|
||||
P: DatabaseProviderFactory<
|
||||
Provider: BlockReader
|
||||
+ TrieReader
|
||||
+ StageCheckpointReader
|
||||
+ PruneCheckpointReader
|
||||
+ ChangeSetReader
|
||||
+ BlockNumReader,
|
||||
Provider: BlockReader + TrieReader + StageCheckpointReader + PruneCheckpointReader,
|
||||
> + BlockReader<Header = N::BlockHeader>
|
||||
+ StateProviderFactory
|
||||
+ StateReader
|
||||
+ ChangeSetReader
|
||||
+ BlockNumReader
|
||||
+ HashedPostStateProvider
|
||||
+ Clone
|
||||
+ 'static,
|
||||
@@ -1304,15 +1265,4 @@ impl<T: PayloadTypes> BlockOrPayload<T> {
|
||||
// TODO decode and return `BlockAccessList`
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns the number of transactions in the payload or block.
|
||||
pub fn transaction_count(&self) -> usize
|
||||
where
|
||||
T::ExecutionData: ExecutionPayload,
|
||||
{
|
||||
match self {
|
||||
Self::Payload(payload) => payload.transaction_count(),
|
||||
Self::Block(block) => block.transaction_count(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,12 @@
|
||||
|
||||
use alloy_eips::BlockNumHash;
|
||||
use alloy_primitives::B256;
|
||||
use crossbeam_channel::Receiver as CrossbeamReceiver;
|
||||
use std::time::Instant;
|
||||
use tokio::sync::oneshot;
|
||||
use tracing::trace;
|
||||
|
||||
/// The state of the persistence task.
|
||||
#[derive(Debug)]
|
||||
#[derive(Default, Debug)]
|
||||
pub struct PersistenceState {
|
||||
/// Hash and number of the last block persisted.
|
||||
///
|
||||
@@ -36,7 +36,7 @@ pub struct PersistenceState {
|
||||
/// Receiver end of channel where the result of the persistence task will be
|
||||
/// sent when done. A None value means there's no persistence task in progress.
|
||||
pub(crate) rx:
|
||||
Option<(CrossbeamReceiver<Option<BlockNumHash>>, Instant, CurrentPersistenceAction)>,
|
||||
Option<(oneshot::Receiver<Option<BlockNumHash>>, Instant, CurrentPersistenceAction)>,
|
||||
}
|
||||
|
||||
impl PersistenceState {
|
||||
@@ -50,7 +50,7 @@ impl PersistenceState {
|
||||
pub(crate) fn start_remove(
|
||||
&mut self,
|
||||
new_tip_num: u64,
|
||||
rx: CrossbeamReceiver<Option<BlockNumHash>>,
|
||||
rx: oneshot::Receiver<Option<BlockNumHash>>,
|
||||
) {
|
||||
self.rx =
|
||||
Some((rx, Instant::now(), CurrentPersistenceAction::RemovingBlocks { new_tip_num }));
|
||||
@@ -60,7 +60,7 @@ impl PersistenceState {
|
||||
pub(crate) fn start_save(
|
||||
&mut self,
|
||||
highest: BlockNumHash,
|
||||
rx: CrossbeamReceiver<Option<BlockNumHash>>,
|
||||
rx: oneshot::Receiver<Option<BlockNumHash>>,
|
||||
) {
|
||||
self.rx = Some((rx, Instant::now(), CurrentPersistenceAction::SavingBlocks { highest }));
|
||||
}
|
||||
|
||||
@@ -1,58 +1,50 @@
|
||||
//! Contains a precompile cache backed by `schnellru::LruMap` (LRU by length).
|
||||
|
||||
use alloy_primitives::Bytes;
|
||||
use dashmap::DashMap;
|
||||
use moka::policy::EvictionPolicy;
|
||||
use parking_lot::Mutex;
|
||||
use reth_evm::precompiles::{DynPrecompile, Precompile, PrecompileInput};
|
||||
use revm::precompile::{PrecompileId, PrecompileOutput, PrecompileResult};
|
||||
use revm_primitives::Address;
|
||||
use std::{hash::Hash, sync::Arc};
|
||||
use schnellru::LruMap;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
hash::{Hash, Hasher},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// Default max cache size for [`PrecompileCache`]
|
||||
const MAX_CACHE_SIZE: u32 = 10_000;
|
||||
|
||||
/// Stores caches for each precompile.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct PrecompileCacheMap<S>(Arc<DashMap<Address, PrecompileCache<S>>>)
|
||||
pub struct PrecompileCacheMap<S>(HashMap<Address, PrecompileCache<S>>)
|
||||
where
|
||||
S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone + 'static;
|
||||
S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone;
|
||||
|
||||
impl<S> PrecompileCacheMap<S>
|
||||
where
|
||||
S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone + 'static,
|
||||
{
|
||||
pub(crate) fn cache_for_address(&self, address: Address) -> PrecompileCache<S> {
|
||||
// Try just using `.get` first to avoid acquiring a write lock.
|
||||
if let Some(cache) = self.0.get(&address) {
|
||||
return cache.clone();
|
||||
}
|
||||
// Otherwise, fallback to `.entry` and initialize the cache.
|
||||
//
|
||||
// This should be very rare as caches for all precompiles will be initialized as soon as
|
||||
// first EVM is created.
|
||||
pub(crate) fn cache_for_address(&mut self, address: Address) -> PrecompileCache<S> {
|
||||
self.0.entry(address).or_default().clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Cache for precompiles, for each input stores the result.
|
||||
///
|
||||
/// [`LruMap`] requires a mutable reference on `get` since it updates the LRU order,
|
||||
/// so we use a [`Mutex`] instead of an `RwLock`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PrecompileCache<S>(
|
||||
moka::sync::Cache<Bytes, CacheEntry<S>, alloy_primitives::map::DefaultHashBuilder>,
|
||||
)
|
||||
pub struct PrecompileCache<S>(Arc<Mutex<LruMap<CacheKey<S>, CacheEntry>>>)
|
||||
where
|
||||
S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone + 'static;
|
||||
S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone;
|
||||
|
||||
impl<S> Default for PrecompileCache<S>
|
||||
where
|
||||
S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone + 'static,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self(
|
||||
moka::sync::CacheBuilder::new(MAX_CACHE_SIZE as u64)
|
||||
.initial_capacity(MAX_CACHE_SIZE as usize)
|
||||
.eviction_policy(EvictionPolicy::lru())
|
||||
.build_with_hasher(Default::default()),
|
||||
)
|
||||
Self(Arc::new(Mutex::new(LruMap::new(schnellru::ByLength::new(MAX_CACHE_SIZE)))))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,31 +52,63 @@ impl<S> PrecompileCache<S>
|
||||
where
|
||||
S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone + 'static,
|
||||
{
|
||||
fn get(&self, input: &[u8], spec: S) -> Option<CacheEntry<S>> {
|
||||
self.0.get(input).filter(|e| e.spec == spec)
|
||||
fn get(&self, key: &CacheKeyRef<'_, S>) -> Option<CacheEntry> {
|
||||
self.0.lock().get(key).cloned()
|
||||
}
|
||||
|
||||
/// Inserts the given key and value into the cache, returning the new cache size.
|
||||
fn insert(&self, input: Bytes, value: CacheEntry<S>) -> usize {
|
||||
self.0.insert(input, value);
|
||||
self.0.entry_count() as usize
|
||||
fn insert(&self, key: CacheKey<S>, value: CacheEntry) -> usize {
|
||||
let mut cache = self.0.lock();
|
||||
cache.insert(key, value);
|
||||
cache.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Cache key, spec id and precompile call input. spec id is included in the key to account for
|
||||
/// precompile repricing across fork activations.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct CacheKey<S>((S, Bytes));
|
||||
|
||||
impl<S> CacheKey<S> {
|
||||
const fn new(spec_id: S, input: Bytes) -> Self {
|
||||
Self((spec_id, input))
|
||||
}
|
||||
}
|
||||
|
||||
/// Cache key reference, used to avoid cloning the input bytes when looking up using a [`CacheKey`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CacheKeyRef<'a, S>((S, &'a [u8]));
|
||||
|
||||
impl<'a, S> CacheKeyRef<'a, S> {
|
||||
const fn new(spec_id: S, input: &'a [u8]) -> Self {
|
||||
Self((spec_id, input))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: PartialEq> PartialEq<CacheKey<S>> for CacheKeyRef<'_, S> {
|
||||
fn eq(&self, other: &CacheKey<S>) -> bool {
|
||||
self.0 .0 == other.0 .0 && self.0 .1 == other.0 .1.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: Hash> Hash for CacheKeyRef<'a, S> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0 .0.hash(state);
|
||||
self.0 .1.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// Cache entry, precompile successful output.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CacheEntry<S> {
|
||||
output: PrecompileOutput,
|
||||
spec: S,
|
||||
}
|
||||
pub struct CacheEntry(PrecompileOutput);
|
||||
|
||||
impl<S> CacheEntry<S> {
|
||||
impl CacheEntry {
|
||||
const fn gas_used(&self) -> u64 {
|
||||
self.output.gas_used
|
||||
self.0.gas_used
|
||||
}
|
||||
|
||||
fn to_precompile_result(&self) -> PrecompileResult {
|
||||
Ok(self.output.clone())
|
||||
Ok(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +190,9 @@ where
|
||||
}
|
||||
|
||||
fn call(&self, input: PrecompileInput<'_>) -> PrecompileResult {
|
||||
if let Some(entry) = &self.cache.get(input.data, self.spec_id.clone()) {
|
||||
let key = CacheKeyRef::new(self.spec_id.clone(), input.data);
|
||||
|
||||
if let Some(entry) = &self.cache.get(&key) {
|
||||
self.increment_by_one_precompile_cache_hits();
|
||||
if input.gas >= entry.gas_used() {
|
||||
return entry.to_precompile_result()
|
||||
@@ -178,10 +204,8 @@ where
|
||||
|
||||
match &result {
|
||||
Ok(output) => {
|
||||
let size = self.cache.insert(
|
||||
Bytes::copy_from_slice(calldata),
|
||||
CacheEntry { output: output.clone(), spec: self.spec_id.clone() },
|
||||
);
|
||||
let key = CacheKey::new(self.spec_id.clone(), Bytes::copy_from_slice(calldata));
|
||||
let size = self.cache.insert(key, CacheEntry(output.clone()));
|
||||
self.set_precompile_cache_size_metric(size as f64);
|
||||
self.increment_by_one_precompile_cache_misses();
|
||||
}
|
||||
@@ -222,12 +246,31 @@ impl CachedPrecompileMetrics {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::hash::DefaultHasher;
|
||||
|
||||
use super::*;
|
||||
use reth_evm::{EthEvmFactory, Evm, EvmEnv, EvmFactory};
|
||||
use reth_revm::db::EmptyDB;
|
||||
use revm::{context::TxEnv, precompile::PrecompileOutput};
|
||||
use revm_primitives::hardfork::SpecId;
|
||||
|
||||
#[test]
|
||||
fn test_cache_key_ref_hash() {
|
||||
let key1 = CacheKey::new(SpecId::PRAGUE, b"test_input".into());
|
||||
let key2 = CacheKeyRef::new(SpecId::PRAGUE, b"test_input");
|
||||
assert!(PartialEq::eq(&key2, &key1));
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
key1.hash(&mut hasher);
|
||||
let hash1 = hasher.finish();
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
key2.hash(&mut hasher);
|
||||
let hash2 = hasher.finish();
|
||||
|
||||
assert_eq!(hash1, hash2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_precompile_cache_basic() {
|
||||
let dyn_precompile: DynPrecompile = (|_input: PrecompileInput<'_>| -> PrecompileResult {
|
||||
@@ -250,11 +293,12 @@ mod tests {
|
||||
reverted: false,
|
||||
};
|
||||
|
||||
let input = b"test_input";
|
||||
let expected = CacheEntry { output, spec: SpecId::PRAGUE };
|
||||
cache.cache.insert(input.into(), expected.clone());
|
||||
let key = CacheKey::new(SpecId::PRAGUE, b"test_input".into());
|
||||
let expected = CacheEntry(output);
|
||||
cache.cache.insert(key, expected.clone());
|
||||
|
||||
let actual = cache.cache.get(input, SpecId::PRAGUE).unwrap();
|
||||
let key = CacheKeyRef::new(SpecId::PRAGUE, b"test_input");
|
||||
let actual = cache.cache.get(&key).unwrap();
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
@@ -268,7 +312,7 @@ mod tests {
|
||||
let address1 = Address::repeat_byte(1);
|
||||
let address2 = Address::repeat_byte(2);
|
||||
|
||||
let cache_map = PrecompileCacheMap::default();
|
||||
let mut cache_map = PrecompileCacheMap::default();
|
||||
|
||||
// create the first precompile with a specific output
|
||||
let precompile1: DynPrecompile = (PrecompileId::custom("custom"), {
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
tree::{
|
||||
payload_validator::{BasicEngineValidator, TreeCtx, ValidationOutcome},
|
||||
persistence_state::CurrentPersistenceAction,
|
||||
PersistTarget, TreeConfig,
|
||||
TreeConfig,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@ use std::{
|
||||
collections::BTreeMap,
|
||||
str::FromStr,
|
||||
sync::{
|
||||
mpsc::{Receiver, Sender},
|
||||
mpsc::{channel, Receiver, Sender},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
@@ -97,7 +97,6 @@ struct TestChannel<T> {
|
||||
impl<T: Send + 'static> TestChannel<T> {
|
||||
/// Creates a new test channel
|
||||
fn spawn_channel() -> (Sender<T>, Receiver<T>, TestChannelHandle) {
|
||||
use std::sync::mpsc::channel;
|
||||
let (original_tx, original_rx) = channel();
|
||||
let (wrapped_tx, wrapped_rx) = channel();
|
||||
let (release_tx, release_rx) = channel();
|
||||
@@ -144,9 +143,7 @@ struct TestHarness {
|
||||
BasicEngineValidator<MockEthProvider, MockEvmConfig, MockEngineValidator>,
|
||||
MockEvmConfig,
|
||||
>,
|
||||
to_tree_tx: crossbeam_channel::Sender<
|
||||
FromEngine<EngineApiRequest<EthEngineTypes, EthPrimitives>, Block>,
|
||||
>,
|
||||
to_tree_tx: Sender<FromEngine<EngineApiRequest<EthEngineTypes, EthPrimitives>, Block>>,
|
||||
from_tree_rx: UnboundedReceiver<EngineApiEvent>,
|
||||
blocks: Vec<ExecutedBlock>,
|
||||
action_rx: Receiver<PersistenceAction>,
|
||||
@@ -156,7 +153,6 @@ struct TestHarness {
|
||||
|
||||
impl TestHarness {
|
||||
fn new(chain_spec: Arc<ChainSpec>) -> Self {
|
||||
use std::sync::mpsc::channel;
|
||||
let (action_tx, action_rx) = channel();
|
||||
Self::with_persistence_channel(chain_spec, action_tx, action_rx)
|
||||
}
|
||||
@@ -209,7 +205,7 @@ impl TestHarness {
|
||||
engine_api_tree_state,
|
||||
canonical_in_memory_state,
|
||||
persistence_handle,
|
||||
PersistenceState { last_persisted_block: BlockNumHash::default(), rx: None },
|
||||
PersistenceState::default(),
|
||||
payload_builder,
|
||||
// always assume enough parallelism for tests
|
||||
TreeConfig::default().with_legacy_state_root(false).with_has_enough_parallelism(true),
|
||||
@@ -289,8 +285,7 @@ impl TestHarness {
|
||||
let fcu_state = self.fcu_state(block_hash);
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self
|
||||
.tree
|
||||
self.tree
|
||||
.on_engine_message(FromEngine::Request(
|
||||
BeaconEngineMessage::ForkchoiceUpdated {
|
||||
state: fcu_state,
|
||||
@@ -403,8 +398,10 @@ impl ValidatorTestHarness {
|
||||
|
||||
/// Configure `PersistenceState` for specific persistence scenarios
|
||||
fn start_persistence_operation(&mut self, action: CurrentPersistenceAction) {
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
// Create a dummy receiver for testing - it will never receive a value
|
||||
let (_tx, rx) = crossbeam_channel::bounded(1);
|
||||
let (_tx, rx) = oneshot::channel();
|
||||
|
||||
match action {
|
||||
CurrentPersistenceAction::SavingBlocks { highest } => {
|
||||
@@ -500,17 +497,11 @@ fn test_tree_persist_block_batch() {
|
||||
test_harness.to_tree_tx.send(FromEngine::DownloadedBlocks(blocks)).unwrap();
|
||||
|
||||
// process the message
|
||||
let msg = match test_harness.tree.wait_for_event() {
|
||||
super::LoopEvent::EngineMessage(msg) => msg,
|
||||
other => panic!("unexpected event: {other:?}"),
|
||||
};
|
||||
let _ = test_harness.tree.on_engine_message(msg).unwrap();
|
||||
let msg = test_harness.tree.try_recv_engine_message().unwrap().unwrap();
|
||||
test_harness.tree.on_engine_message(msg).unwrap();
|
||||
|
||||
// we now should receive the other batch
|
||||
let msg = match test_harness.tree.wait_for_event() {
|
||||
super::LoopEvent::EngineMessage(msg) => msg,
|
||||
other => panic!("unexpected event: {other:?}"),
|
||||
};
|
||||
let msg = test_harness.tree.try_recv_engine_message().unwrap().unwrap();
|
||||
match msg {
|
||||
FromEngine::DownloadedBlocks(blocks) => {
|
||||
assert_eq!(blocks.len(), tree_config.max_execute_block_batch_size());
|
||||
@@ -586,7 +577,7 @@ async fn test_engine_request_during_backfill() {
|
||||
.with_backfill_state(BackfillSyncState::Active);
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = test_harness
|
||||
test_harness
|
||||
.tree
|
||||
.on_engine_message(FromEngine::Request(
|
||||
BeaconEngineMessage::ForkchoiceUpdated {
|
||||
@@ -667,7 +658,7 @@ async fn test_holesky_payload() {
|
||||
TestHarness::new(HOLESKY.clone()).with_backfill_state(BackfillSyncState::Active);
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = test_harness
|
||||
test_harness
|
||||
.tree
|
||||
.on_engine_message(FromEngine::Request(
|
||||
BeaconEngineMessage::NewPayload {
|
||||
@@ -761,8 +752,8 @@ async fn test_tree_state_on_new_head_reorg() {
|
||||
})
|
||||
);
|
||||
|
||||
// after polling persistence completion, we should be at `None` for the next action
|
||||
test_harness.tree.try_poll_persistence().unwrap();
|
||||
// after advancing persistence, we should be at `None` for the next action
|
||||
test_harness.tree.advance_persistence().unwrap();
|
||||
let current_action = test_harness.tree.persistence_state.current_action().cloned();
|
||||
assert_eq!(current_action, None);
|
||||
|
||||
@@ -892,8 +883,7 @@ async fn test_get_canonical_blocks_to_persist() {
|
||||
.with_persistence_threshold(persistence_threshold)
|
||||
.with_memory_block_buffer_target(memory_block_buffer_target);
|
||||
|
||||
let blocks_to_persist =
|
||||
test_harness.tree.get_canonical_blocks_to_persist(PersistTarget::Threshold).unwrap();
|
||||
let blocks_to_persist = test_harness.tree.get_canonical_blocks_to_persist().unwrap();
|
||||
|
||||
let expected_blocks_to_persist_length: usize =
|
||||
(canonical_head_number - memory_block_buffer_target - last_persisted_block_number)
|
||||
@@ -912,8 +902,7 @@ async fn test_get_canonical_blocks_to_persist() {
|
||||
|
||||
assert!(test_harness.tree.state.tree_state.sealed_header_by_hash(&fork_block_hash).is_some());
|
||||
|
||||
let blocks_to_persist =
|
||||
test_harness.tree.get_canonical_blocks_to_persist(PersistTarget::Threshold).unwrap();
|
||||
let blocks_to_persist = test_harness.tree.get_canonical_blocks_to_persist().unwrap();
|
||||
assert_eq!(blocks_to_persist.len(), expected_blocks_to_persist_length);
|
||||
|
||||
// check that the fork block is not included in the blocks to persist
|
||||
@@ -992,7 +981,7 @@ async fn test_engine_tree_live_sync_transition_required_blocks_requested() {
|
||||
let backfill_tip_block = main_chain[(backfill_finished_block_number - 1) as usize].clone();
|
||||
// add block to mock provider to enable persistence clean up.
|
||||
test_harness.provider.add_block(backfill_tip_block.hash(), backfill_tip_block.into_block());
|
||||
let _ = test_harness.tree.on_engine_message(FromEngine::Event(backfill_finished)).unwrap();
|
||||
test_harness.tree.on_engine_message(FromEngine::Event(backfill_finished)).unwrap();
|
||||
|
||||
let event = test_harness.from_tree_rx.recv().await.unwrap();
|
||||
match event {
|
||||
@@ -1002,7 +991,7 @@ async fn test_engine_tree_live_sync_transition_required_blocks_requested() {
|
||||
_ => panic!("Unexpected event: {event:#?}"),
|
||||
}
|
||||
|
||||
let _ = test_harness
|
||||
test_harness
|
||||
.tree
|
||||
.on_engine_message(FromEngine::DownloadedBlocks(vec![main_chain
|
||||
.last()
|
||||
@@ -1058,7 +1047,7 @@ async fn test_fcu_with_canonical_ancestor_updates_latest_block() {
|
||||
|
||||
// Send FCU to the canonical ancestor
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = test_harness
|
||||
test_harness
|
||||
.tree
|
||||
.on_engine_message(FromEngine::Request(
|
||||
BeaconEngineMessage::ForkchoiceUpdated {
|
||||
@@ -1954,53 +1943,4 @@ mod forkchoice_updated_tests {
|
||||
.unwrap();
|
||||
assert!(result.is_some(), "OpStack should handle canonical head");
|
||||
}
|
||||
|
||||
/// Test that engine termination persists all blocks and signals completion.
|
||||
#[test]
|
||||
fn test_engine_termination_with_everything_persisted() {
|
||||
let chain_spec = MAINNET.clone();
|
||||
let mut test_block_builder = TestBlockBuilder::eth().with_chain_spec((*chain_spec).clone());
|
||||
|
||||
// Create 10 blocks to persist
|
||||
let blocks: Vec<_> = test_block_builder.get_executed_blocks(1..11).collect();
|
||||
let canonical_tip = blocks.last().unwrap().recovered_block().number;
|
||||
let test_harness = TestHarness::new(chain_spec).with_blocks(blocks);
|
||||
|
||||
// Create termination channel
|
||||
let (terminate_tx, mut terminate_rx) = oneshot::channel();
|
||||
|
||||
let to_tree_tx = test_harness.to_tree_tx.clone();
|
||||
let action_rx = test_harness.action_rx;
|
||||
|
||||
// Spawn tree in background thread
|
||||
std::thread::Builder::new()
|
||||
.name("Engine Task".to_string())
|
||||
.spawn(|| test_harness.tree.run())
|
||||
.unwrap();
|
||||
|
||||
// Send terminate request
|
||||
to_tree_tx
|
||||
.send(FromEngine::Event(FromOrchestrator::Terminate { tx: terminate_tx }))
|
||||
.unwrap();
|
||||
|
||||
// Handle persistence actions until termination completes
|
||||
let mut last_persisted_number = 0;
|
||||
loop {
|
||||
if terminate_rx.try_recv().is_ok() {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Ok(PersistenceAction::SaveBlocks(saved_blocks, sender)) =
|
||||
action_rx.recv_timeout(std::time::Duration::from_millis(100))
|
||||
{
|
||||
if let Some(last) = saved_blocks.last() {
|
||||
last_persisted_number = last.recovered_block().number;
|
||||
}
|
||||
sender.send(saved_blocks.last().map(|b| b.recovered_block().num_hash())).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we persisted right to the tip
|
||||
assert_eq!(last_persisted_number, canonical_tip);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ where
|
||||
/// these stages that this work has already been done. Otherwise, there might be some conflict with
|
||||
/// database integrity.
|
||||
pub fn save_stage_checkpoints<P>(
|
||||
provider: P,
|
||||
provider: &P,
|
||||
from: BlockNumber,
|
||||
to: BlockNumber,
|
||||
processed: u64,
|
||||
@@ -170,14 +170,18 @@ where
|
||||
<P as NodePrimitivesProvider>::Primitives: NodePrimitives<BlockHeader = BH, BlockBody = BB>,
|
||||
{
|
||||
let reader = open(meta)?;
|
||||
let iter = reader.iter().map(decode as fn(_) -> _);
|
||||
let iter =
|
||||
reader
|
||||
.iter()
|
||||
.map(Box::new(decode)
|
||||
as Box<dyn Fn(Result<BlockTuple, E2sError>) -> eyre::Result<(BH, BB)>>);
|
||||
let iter = ProcessIter { iter, era: meta };
|
||||
|
||||
process_iter(iter, writer, provider, hash_collector, block_numbers)
|
||||
}
|
||||
|
||||
type ProcessInnerIter<R, BH, BB> =
|
||||
Map<BlockTupleIterator<R>, fn(Result<BlockTuple, E2sError>) -> eyre::Result<(BH, BB)>>;
|
||||
Map<BlockTupleIterator<R>, Box<dyn Fn(Result<BlockTuple, E2sError>) -> eyre::Result<(BH, BB)>>>;
|
||||
|
||||
/// An iterator that wraps era file extraction. After the final item [`EraMeta::mark_as_processed`]
|
||||
/// is called to ensure proper cleanup.
|
||||
@@ -305,7 +309,7 @@ where
|
||||
writer.append_header(&header, &hash)?;
|
||||
|
||||
// Write bodies to database.
|
||||
provider.append_block_bodies(vec![(header.number(), Some(&body))])?;
|
||||
provider.append_block_bodies(vec![(header.number(), Some(body))])?;
|
||||
|
||||
hash_collector.insert(hash, number)?;
|
||||
}
|
||||
|
||||
@@ -59,36 +59,11 @@
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use crate::e2s::{error::E2sError, types::Entry};
|
||||
use snap::{read::FrameDecoder, write::FrameEncoder};
|
||||
use std::io::{Read, Write};
|
||||
|
||||
/// Maximum allowed decompressed size for a signed beacon block SSZ payload.
|
||||
const MAX_DECOMPRESSED_SIGNED_BEACON_BLOCK_BYTES: usize = 256 * 1024 * 1024; // 256 MiB
|
||||
|
||||
/// Maximum allowed decompressed size for a beacon state SSZ payload.
|
||||
const MAX_DECOMPRESSED_BEACON_STATE_BYTES: usize = 2 * 1024 * 1024 * 1024; // 2 GiB
|
||||
|
||||
fn decompress_snappy_bounded(
|
||||
compressed: &[u8],
|
||||
max_decompressed_bytes: usize,
|
||||
what: &str,
|
||||
) -> Result<Vec<u8>, E2sError> {
|
||||
let mut decoder = FrameDecoder::new(compressed).take(max_decompressed_bytes as u64);
|
||||
let mut decompressed = Vec::new();
|
||||
|
||||
Read::read_to_end(&mut decoder, &mut decompressed)
|
||||
.map_err(|e| E2sError::SnappyDecompression(format!("Failed to decompress {what}: {e}")))?;
|
||||
|
||||
if decompressed.len() >= max_decompressed_bytes {
|
||||
return Err(E2sError::SnappyDecompression(format!(
|
||||
"Failed to decompress {what}: decompressed data exceeded limit of {max_decompressed_bytes} bytes"
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(decompressed)
|
||||
}
|
||||
|
||||
/// `CompressedSignedBeaconBlock` record type: [0x01, 0x00]
|
||||
pub const COMPRESSED_SIGNED_BEACON_BLOCK: [u8; 2] = [0x01, 0x00];
|
||||
|
||||
@@ -129,11 +104,13 @@ impl CompressedSignedBeaconBlock {
|
||||
|
||||
/// Decompress to get the original ssz-encoded signed beacon block
|
||||
pub fn decompress(&self) -> Result<Vec<u8>, E2sError> {
|
||||
decompress_snappy_bounded(
|
||||
self.data.as_slice(),
|
||||
MAX_DECOMPRESSED_SIGNED_BEACON_BLOCK_BYTES,
|
||||
"signed beacon block",
|
||||
)
|
||||
let mut decoder = FrameDecoder::new(self.data.as_slice());
|
||||
let mut decompressed = Vec::new();
|
||||
Read::read_to_end(&mut decoder, &mut decompressed).map_err(|e| {
|
||||
E2sError::SnappyDecompression(format!("Failed to decompress signed beacon block: {e}"))
|
||||
})?;
|
||||
|
||||
Ok(decompressed)
|
||||
}
|
||||
|
||||
/// Convert to an [`Entry`]
|
||||
@@ -191,11 +168,13 @@ impl CompressedBeaconState {
|
||||
|
||||
/// Decompress to get the original ssz-encoded beacon state
|
||||
pub fn decompress(&self) -> Result<Vec<u8>, E2sError> {
|
||||
decompress_snappy_bounded(
|
||||
self.data.as_slice(),
|
||||
MAX_DECOMPRESSED_BEACON_STATE_BYTES,
|
||||
"beacon state",
|
||||
)
|
||||
let mut decoder = FrameDecoder::new(self.data.as_slice());
|
||||
let mut decompressed = Vec::new();
|
||||
Read::read_to_end(&mut decoder, &mut decompressed).map_err(|e| {
|
||||
E2sError::SnappyDecompression(format!("Failed to decompress beacon state: {e}"))
|
||||
})?;
|
||||
|
||||
Ok(decompressed)
|
||||
}
|
||||
|
||||
/// Convert to an [`Entry`]
|
||||
@@ -281,15 +260,4 @@ mod tests {
|
||||
let result = CompressedBeaconState::from_entry(&invalid_entry);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bounded_decompression_rejects_oversized_output() {
|
||||
let ssz_data = vec![42u8; 1024];
|
||||
let compressed = CompressedBeaconState::from_ssz(&ssz_data).unwrap();
|
||||
|
||||
let err =
|
||||
decompress_snappy_bounded(compressed.data.as_slice(), 100, "beacon state").unwrap_err();
|
||||
|
||||
assert!(format!("{err:?}").contains("exceeded limit"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ impl CompressedBody {
|
||||
let mut encoder = FrameEncoder::new(&mut compressed);
|
||||
|
||||
Write::write_all(&mut encoder, rlp_data).map_err(|e| {
|
||||
E2sError::SnappyCompression(format!("Failed to compress body: {e}"))
|
||||
E2sError::SnappyCompression(format!("Failed to compress header: {e}"))
|
||||
})?;
|
||||
|
||||
encoder.flush().map_err(|e| {
|
||||
@@ -339,7 +339,7 @@ impl CompressedReceipts {
|
||||
let mut encoder = FrameEncoder::new(&mut compressed);
|
||||
|
||||
Write::write_all(&mut encoder, rlp_data).map_err(|e| {
|
||||
E2sError::SnappyCompression(format!("Failed to compress receipts: {e}"))
|
||||
E2sError::SnappyCompression(format!("Failed to compress header: {e}"))
|
||||
})?;
|
||||
|
||||
encoder.flush().map_err(|e| {
|
||||
|
||||
@@ -51,12 +51,7 @@ jemalloc = [
|
||||
"reth-node-metrics/jemalloc",
|
||||
]
|
||||
jemalloc-prof = [
|
||||
"jemalloc",
|
||||
"reth-node-metrics/jemalloc-prof",
|
||||
]
|
||||
jemalloc-symbols = [
|
||||
"jemalloc-prof",
|
||||
"reth-node-metrics/jemalloc-symbols",
|
||||
"reth-node-core/jemalloc",
|
||||
]
|
||||
tracy-allocator = []
|
||||
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
use crate::{
|
||||
interface::{Commands, NoSubCmd},
|
||||
Cli,
|
||||
};
|
||||
use clap::Subcommand;
|
||||
use crate::{interface::Commands, Cli};
|
||||
use eyre::{eyre, Result};
|
||||
use reth_chainspec::{ChainSpec, EthChainSpec, Hardforks};
|
||||
use reth_cli::chainspec::ChainSpecParser;
|
||||
@@ -22,26 +18,20 @@ use std::{fmt, sync::Arc};
|
||||
|
||||
/// A wrapper around a parsed CLI that handles command execution.
|
||||
#[derive(Debug)]
|
||||
pub struct CliApp<
|
||||
Spec: ChainSpecParser,
|
||||
Ext: clap::Args + fmt::Debug,
|
||||
Rpc: RpcModuleValidator,
|
||||
SubCmd: Subcommand + fmt::Debug = NoSubCmd,
|
||||
> {
|
||||
cli: Cli<Spec, Ext, Rpc, SubCmd>,
|
||||
pub struct CliApp<Spec: ChainSpecParser, Ext: clap::Args + fmt::Debug, Rpc: RpcModuleValidator> {
|
||||
cli: Cli<Spec, Ext, Rpc>,
|
||||
runner: Option<CliRunner>,
|
||||
layers: Option<Layers>,
|
||||
guard: Option<FileWorkerGuard>,
|
||||
}
|
||||
|
||||
impl<C, Ext, Rpc, SubCmd> CliApp<C, Ext, Rpc, SubCmd>
|
||||
impl<C, Ext, Rpc> CliApp<C, Ext, Rpc>
|
||||
where
|
||||
C: ChainSpecParser,
|
||||
Ext: clap::Args + fmt::Debug,
|
||||
Rpc: RpcModuleValidator,
|
||||
SubCmd: ExtendedCommand + Subcommand + fmt::Debug,
|
||||
{
|
||||
pub(crate) fn new(cli: Cli<C, Ext, Rpc, SubCmd>) -> Self {
|
||||
pub(crate) fn new(cli: Cli<C, Ext, Rpc>) -> Self {
|
||||
Self { cli, runner: None, layers: Some(Layers::new()), guard: None }
|
||||
}
|
||||
|
||||
@@ -108,9 +98,9 @@ where
|
||||
self.init_tracing(&runner)?;
|
||||
|
||||
// Install the prometheus recorder to be sure to record all metrics
|
||||
install_prometheus_recorder();
|
||||
let _ = install_prometheus_recorder();
|
||||
|
||||
run_commands_with::<C, Ext, Rpc, N, SubCmd>(self.cli, runner, components, launcher)
|
||||
run_commands_with::<C, Ext, Rpc, N>(self.cli, runner, components, launcher)
|
||||
}
|
||||
|
||||
/// Initializes tracing with the configured options.
|
||||
@@ -127,8 +117,8 @@ where
|
||||
|
||||
/// Run CLI commands with the provided runner, components and launcher.
|
||||
/// This is the shared implementation used by both `CliApp` and Cli methods.
|
||||
pub(crate) fn run_commands_with<C, Ext, Rpc, N, SubCmd>(
|
||||
cli: Cli<C, Ext, Rpc, SubCmd>,
|
||||
pub(crate) fn run_commands_with<C, Ext, Rpc, N>(
|
||||
cli: Cli<C, Ext, Rpc>,
|
||||
runner: CliRunner,
|
||||
components: impl CliComponentsBuilder<N>,
|
||||
launcher: impl AsyncFnOnce(
|
||||
@@ -141,7 +131,6 @@ where
|
||||
Ext: clap::Args + fmt::Debug,
|
||||
Rpc: RpcModuleValidator,
|
||||
N: CliNodeTypes<Primitives: NodePrimitives<BlockHeader: HeaderMut>, ChainSpec: Hardforks>,
|
||||
SubCmd: ExtendedCommand + Subcommand + fmt::Debug,
|
||||
{
|
||||
match cli.command {
|
||||
Commands::Node(command) => {
|
||||
@@ -178,19 +167,9 @@ where
|
||||
#[cfg(feature = "dev")]
|
||||
Commands::TestVectors(command) => runner.run_until_ctrl_c(command.execute()),
|
||||
Commands::ReExecute(command) => runner.run_until_ctrl_c(command.execute::<N>(components)),
|
||||
Commands::Ext(command) => command.execute(runner),
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for extension subcommands that can be added to the CLI.
|
||||
///
|
||||
/// Consumers implement this trait for their custom subcommands to define
|
||||
/// how they should be executed.
|
||||
pub trait ExtendedCommand {
|
||||
/// Execute the extension command with the provided CLI runner.
|
||||
fn execute(self, runner: CliRunner) -> Result<()>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -37,11 +37,10 @@ pub struct Cli<
|
||||
C: ChainSpecParser = EthereumChainSpecParser,
|
||||
Ext: clap::Args + fmt::Debug = NoArgs,
|
||||
Rpc: RpcModuleValidator = DefaultRpcModuleValidator,
|
||||
SubCmd: Subcommand + fmt::Debug = NoSubCmd,
|
||||
> {
|
||||
/// The command to run
|
||||
#[command(subcommand)]
|
||||
pub command: Commands<C, Ext, SubCmd>,
|
||||
pub command: Commands<C, Ext>,
|
||||
|
||||
/// The logging configuration for the CLI.
|
||||
#[command(flatten)]
|
||||
@@ -72,18 +71,15 @@ impl Cli {
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
C: ChainSpecParser,
|
||||
Ext: clap::Args + fmt::Debug,
|
||||
Rpc: RpcModuleValidator,
|
||||
SubCmd: crate::app::ExtendedCommand + Subcommand + fmt::Debug,
|
||||
> Cli<C, Ext, Rpc, SubCmd>
|
||||
{
|
||||
impl<C: ChainSpecParser, Ext: clap::Args + fmt::Debug, Rpc: RpcModuleValidator> Cli<C, Ext, Rpc> {
|
||||
/// Configures the CLI and returns a [`CliApp`] instance.
|
||||
///
|
||||
/// This method is used to prepare the CLI for execution by wrapping it in a
|
||||
/// [`CliApp`] that can be further configured before running.
|
||||
pub fn configure(self) -> CliApp<C, Ext, Rpc, SubCmd> {
|
||||
pub fn configure(self) -> CliApp<C, Ext, Rpc>
|
||||
where
|
||||
C: ChainSpecParser<ChainSpec = ChainSpec>,
|
||||
{
|
||||
CliApp::new(self)
|
||||
}
|
||||
|
||||
@@ -212,10 +208,10 @@ impl<
|
||||
let _guard = self.init_tracing(&runner, Layers::new())?;
|
||||
|
||||
// Install the prometheus recorder to be sure to record all metrics
|
||||
install_prometheus_recorder();
|
||||
let _ = install_prometheus_recorder();
|
||||
|
||||
// Use the shared standalone function to avoid duplication
|
||||
run_commands_with::<C, Ext, Rpc, N, SubCmd>(self, runner, components, launcher)
|
||||
run_commands_with::<C, Ext, Rpc, N>(self, runner, components, launcher)
|
||||
}
|
||||
|
||||
/// Initializes tracing with the configured options.
|
||||
@@ -249,11 +245,7 @@ impl<
|
||||
|
||||
/// Commands to be executed
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum Commands<
|
||||
C: ChainSpecParser,
|
||||
Ext: clap::Args + fmt::Debug,
|
||||
SubCmd: Subcommand + fmt::Debug = NoSubCmd,
|
||||
> {
|
||||
pub enum Commands<C: ChainSpecParser, Ext: clap::Args + fmt::Debug> {
|
||||
/// Start the node
|
||||
#[command(name = "node")]
|
||||
Node(Box<node::NodeCommand<C, Ext>>),
|
||||
@@ -299,27 +291,9 @@ pub enum Commands<
|
||||
/// Re-execute blocks in parallel to verify historical sync correctness.
|
||||
#[command(name = "re-execute")]
|
||||
ReExecute(re_execute::Command<C>),
|
||||
/// Extension subcommands provided by consumers.
|
||||
#[command(flatten)]
|
||||
Ext(SubCmd),
|
||||
}
|
||||
|
||||
/// A no-op subcommand type for when no extension subcommands are needed.
|
||||
///
|
||||
/// This is the default type parameter for `Commands` when consumers don't need
|
||||
/// to add custom subcommands.
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum NoSubCmd {}
|
||||
|
||||
impl crate::app::ExtendedCommand for NoSubCmd {
|
||||
fn execute(self, _runner: CliRunner) -> eyre::Result<()> {
|
||||
match self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ChainSpecParser, Ext: clap::Args + fmt::Debug, SubCmd: Subcommand + fmt::Debug>
|
||||
Commands<C, Ext, SubCmd>
|
||||
{
|
||||
impl<C: ChainSpecParser, Ext: clap::Args + fmt::Debug> Commands<C, Ext> {
|
||||
/// Returns the underlying chain being used for commands
|
||||
pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
|
||||
match self {
|
||||
@@ -339,7 +313,6 @@ impl<C: ChainSpecParser, Ext: clap::Args + fmt::Debug, SubCmd: Subcommand + fmt:
|
||||
Self::Config(_) => None,
|
||||
Self::Prune(cmd) => cmd.chain_spec(),
|
||||
Self::ReExecute(cmd) => cmd.chain_spec(),
|
||||
Self::Ext(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -563,100 +536,4 @@ mod tests {
|
||||
_ => panic!("Expected Stage command"),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extensible_subcommands() {
|
||||
use crate::app::ExtendedCommand;
|
||||
use reth_cli_runner::CliRunner;
|
||||
use reth_rpc_server_types::DefaultRpcModuleValidator;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum CustomCommands {
|
||||
/// A custom hello command
|
||||
#[command(name = "hello")]
|
||||
Hello {
|
||||
/// Name to greet
|
||||
#[arg(long)]
|
||||
name: String,
|
||||
},
|
||||
/// Another custom command
|
||||
#[command(name = "goodbye")]
|
||||
Goodbye,
|
||||
}
|
||||
|
||||
static EXECUTED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
impl ExtendedCommand for CustomCommands {
|
||||
fn execute(self, _runner: CliRunner) -> eyre::Result<()> {
|
||||
match self {
|
||||
Self::Hello { name } => {
|
||||
assert_eq!(name, "world");
|
||||
EXECUTED.store(true, Ordering::SeqCst);
|
||||
Ok(())
|
||||
}
|
||||
Self::Goodbye => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test parsing the custom "hello" command
|
||||
let cli = Cli::<
|
||||
EthereumChainSpecParser,
|
||||
NoArgs,
|
||||
DefaultRpcModuleValidator,
|
||||
CustomCommands,
|
||||
>::try_parse_from(["reth", "hello", "--name", "world"])
|
||||
.unwrap();
|
||||
|
||||
match &cli.command {
|
||||
Commands::Ext(CustomCommands::Hello { name }) => {
|
||||
assert_eq!(name, "world");
|
||||
}
|
||||
_ => panic!("Expected Ext(Hello) command"),
|
||||
}
|
||||
|
||||
// Test parsing the custom "goodbye" command
|
||||
let cli = Cli::<
|
||||
EthereumChainSpecParser,
|
||||
NoArgs,
|
||||
DefaultRpcModuleValidator,
|
||||
CustomCommands,
|
||||
>::try_parse_from(["reth", "goodbye"])
|
||||
.unwrap();
|
||||
|
||||
match &cli.command {
|
||||
Commands::Ext(CustomCommands::Goodbye) => {}
|
||||
_ => panic!("Expected Ext(Goodbye) command"),
|
||||
}
|
||||
|
||||
// Test that built-in commands still work alongside custom ones
|
||||
let cli = Cli::<
|
||||
EthereumChainSpecParser,
|
||||
NoArgs,
|
||||
DefaultRpcModuleValidator,
|
||||
CustomCommands,
|
||||
>::try_parse_from(["reth", "node"])
|
||||
.unwrap();
|
||||
|
||||
match &cli.command {
|
||||
Commands::Node(_) => {}
|
||||
_ => panic!("Expected Node command"),
|
||||
}
|
||||
|
||||
// Test executing the custom command
|
||||
let cli = Cli::<
|
||||
EthereumChainSpecParser,
|
||||
NoArgs,
|
||||
DefaultRpcModuleValidator,
|
||||
CustomCommands,
|
||||
>::try_parse_from(["reth", "hello", "--name", "world"])
|
||||
.unwrap();
|
||||
|
||||
if let Commands::Ext(cmd) = cli.command {
|
||||
let runner = CliRunner::try_default_runtime().unwrap();
|
||||
cmd.execute(runner).unwrap();
|
||||
assert!(EXECUTED.load(Ordering::SeqCst), "Custom command should have been executed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ pub mod app;
|
||||
pub mod chainspec;
|
||||
pub mod interface;
|
||||
|
||||
pub use app::{CliApp, ExtendedCommand};
|
||||
pub use interface::{Cli, Commands, NoSubCmd};
|
||||
pub use app::CliApp;
|
||||
pub use interface::{Cli, Commands};
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
@@ -84,15 +84,17 @@ where
|
||||
B: Block,
|
||||
ChainSpec: EthChainSpec<Header = B::Header> + EthereumHardforks + Debug + Send + Sync,
|
||||
{
|
||||
type Error = ConsensusError;
|
||||
|
||||
fn validate_body_against_header(
|
||||
&self,
|
||||
body: &B::Body,
|
||||
header: &SealedHeader<B::Header>,
|
||||
) -> Result<(), ConsensusError> {
|
||||
) -> Result<(), Self::Error> {
|
||||
validate_body_against_header(body, header.header())
|
||||
}
|
||||
|
||||
fn validate_block_pre_execution(&self, block: &SealedBlock<B>) -> Result<(), ConsensusError> {
|
||||
fn validate_block_pre_execution(&self, block: &SealedBlock<B>) -> Result<(), Self::Error> {
|
||||
validate_block_pre_execution(block, &self.chain_spec)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,9 @@ fn verify_receipts<R: Receipt>(
|
||||
logs_bloom,
|
||||
expected_receipts_root,
|
||||
expected_logs_bloom,
|
||||
)
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compare the calculated receipts root with the expected receipts root, also compare
|
||||
|
||||
@@ -170,7 +170,7 @@ impl DisplayHardforks {
|
||||
let mut post_merge = Vec::new();
|
||||
|
||||
for (fork, condition, metadata) in hardforks {
|
||||
let display_fork = DisplayFork {
|
||||
let mut display_fork = DisplayFork {
|
||||
name: fork.name().to_string(),
|
||||
activated_at: condition,
|
||||
eip: None,
|
||||
@@ -181,7 +181,12 @@ impl DisplayHardforks {
|
||||
ForkCondition::Block(_) => {
|
||||
pre_merge.push(display_fork);
|
||||
}
|
||||
ForkCondition::TTD { .. } => {
|
||||
ForkCondition::TTD { activation_block_number, total_difficulty, fork_block } => {
|
||||
display_fork.activated_at = ForkCondition::TTD {
|
||||
activation_block_number,
|
||||
fork_block,
|
||||
total_difficulty,
|
||||
};
|
||||
with_merge.push(display_fork);
|
||||
}
|
||||
ForkCondition::Timestamp(_) => {
|
||||
|
||||
@@ -61,7 +61,6 @@ reth-node-core.workspace = true
|
||||
reth-e2e-test-utils.workspace = true
|
||||
reth-tasks.workspace = true
|
||||
reth-testing-utils.workspace = true
|
||||
reth-stages-types.workspace = true
|
||||
tempfile.workspace = true
|
||||
jsonrpsee-core.workspace = true
|
||||
|
||||
@@ -91,7 +90,6 @@ asm-keccak = [
|
||||
]
|
||||
keccak-cache-global = [
|
||||
"alloy-primitives/keccak-cache-global",
|
||||
"reth-node-core/keccak-cache-global",
|
||||
]
|
||||
js-tracer = [
|
||||
"reth-node-builder/js-tracer",
|
||||
@@ -111,5 +109,4 @@ test-utils = [
|
||||
"reth-evm/test-utils",
|
||||
"reth-primitives-traits/test-utils",
|
||||
"reth-evm-ethereum/test-utils",
|
||||
"reth-stages-types/test-utils",
|
||||
]
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
use crate::utils::eth_payload_attributes;
|
||||
use alloy_genesis::Genesis;
|
||||
use alloy_primitives::B256;
|
||||
use reth_chainspec::{ChainSpecBuilder, MAINNET};
|
||||
use reth_e2e_test_utils::{setup, transaction::TransactionTestContext};
|
||||
use reth_node_ethereum::EthereumNode;
|
||||
use reth_provider::{HeaderProvider, StageCheckpointReader};
|
||||
use reth_stages_types::StageId;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Tests that a node can initialize and advance with a custom genesis block number.
|
||||
#[tokio::test]
|
||||
async fn can_run_eth_node_with_custom_genesis_number() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
// Create genesis with custom block number (e.g., 1000)
|
||||
let mut genesis: Genesis =
|
||||
serde_json::from_str(include_str!("../assets/genesis.json")).unwrap();
|
||||
genesis.number = Some(1000);
|
||||
genesis.parent_hash = Some(B256::random());
|
||||
|
||||
let chain_spec = Arc::new(
|
||||
ChainSpecBuilder::default()
|
||||
.chain(MAINNET.chain)
|
||||
.genesis(genesis)
|
||||
.cancun_activated()
|
||||
.build(),
|
||||
);
|
||||
|
||||
let (mut nodes, _tasks, wallet) =
|
||||
setup::<EthereumNode>(1, chain_spec, false, eth_payload_attributes).await?;
|
||||
|
||||
let mut node = nodes.pop().unwrap();
|
||||
|
||||
// Verify stage checkpoints are initialized to genesis block number (1000)
|
||||
for stage in StageId::ALL {
|
||||
let checkpoint = node.inner.provider.get_stage_checkpoint(stage)?;
|
||||
assert!(checkpoint.is_some(), "Stage {:?} checkpoint should exist", stage);
|
||||
assert_eq!(
|
||||
checkpoint.unwrap().block_number,
|
||||
1000,
|
||||
"Stage {:?} checkpoint should be at genesis block 1000",
|
||||
stage
|
||||
);
|
||||
}
|
||||
|
||||
// Advance the chain (block 1001)
|
||||
let raw_tx = TransactionTestContext::transfer_tx_bytes(1, wallet.inner).await;
|
||||
let tx_hash = node.rpc.inject_tx(raw_tx).await?;
|
||||
let payload = node.advance_block().await?;
|
||||
|
||||
let block_hash = payload.block().hash();
|
||||
let block_number = payload.block().number;
|
||||
|
||||
// Verify we're at block 1001 (genesis + 1)
|
||||
assert_eq!(block_number, 1001, "Block number should be 1001 after advancing from genesis 1000");
|
||||
|
||||
// Assert the block has been committed
|
||||
node.assert_new_block(tx_hash, block_hash, block_number).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that block queries respect custom genesis boundaries.
|
||||
#[tokio::test]
|
||||
async fn custom_genesis_block_query_boundaries() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let genesis_number = 5000u64;
|
||||
|
||||
let mut genesis: Genesis =
|
||||
serde_json::from_str(include_str!("../assets/genesis.json")).unwrap();
|
||||
genesis.number = Some(genesis_number);
|
||||
genesis.parent_hash = Some(B256::random());
|
||||
|
||||
let chain_spec = Arc::new(
|
||||
ChainSpecBuilder::default()
|
||||
.chain(MAINNET.chain)
|
||||
.genesis(genesis)
|
||||
.cancun_activated()
|
||||
.build(),
|
||||
);
|
||||
|
||||
let (mut nodes, _tasks, _wallet) =
|
||||
setup::<EthereumNode>(1, chain_spec, false, eth_payload_attributes).await?;
|
||||
|
||||
let node = nodes.pop().unwrap();
|
||||
|
||||
// Query genesis block should succeed
|
||||
let genesis_header = node.inner.provider.header_by_number(genesis_number)?;
|
||||
assert!(genesis_header.is_some(), "Genesis block at {} should exist", genesis_number);
|
||||
|
||||
// Query blocks before genesis should return None
|
||||
for block_num in [0, 1, genesis_number - 1] {
|
||||
let header = node.inner.provider.header_by_number(block_num)?;
|
||||
assert!(header.is_none(), "Block {} before genesis should not exist", block_num);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -7,7 +7,6 @@ use reth_e2e_test_utils::{
|
||||
use reth_node_builder::{NodeBuilder, NodeHandle};
|
||||
use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig};
|
||||
use reth_node_ethereum::EthereumNode;
|
||||
use reth_provider::BlockNumReader;
|
||||
use reth_tasks::TaskManager;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -128,55 +127,3 @@ async fn test_failed_run_eth_node_with_no_auth_engine_api_over_ipc_opts() -> eyr
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_engine_graceful_shutdown() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let (mut nodes, _tasks, wallet) = setup::<EthereumNode>(
|
||||
1,
|
||||
Arc::new(
|
||||
ChainSpecBuilder::default()
|
||||
.chain(MAINNET.chain)
|
||||
.genesis(serde_json::from_str(include_str!("../assets/genesis.json")).unwrap())
|
||||
.cancun_activated()
|
||||
.build(),
|
||||
),
|
||||
false,
|
||||
eth_payload_attributes,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut node = nodes.pop().unwrap();
|
||||
|
||||
let raw_tx = TransactionTestContext::transfer_tx_bytes(1, wallet.inner).await;
|
||||
let tx_hash = node.rpc.inject_tx(raw_tx).await?;
|
||||
let payload = node.advance_block().await?;
|
||||
node.assert_new_block(tx_hash, payload.block().hash(), payload.block().number).await?;
|
||||
|
||||
// Get block number before shutdown
|
||||
let block_before = node.inner.provider.best_block_number()?;
|
||||
assert_eq!(block_before, 1, "Expected 1 block before shutdown");
|
||||
|
||||
// Verify block is NOT yet persisted to database
|
||||
let db_block_before = node.inner.provider.last_block_number()?;
|
||||
assert_eq!(db_block_before, 0, "Block should not be persisted yet");
|
||||
|
||||
// Trigger graceful shutdown
|
||||
let done_rx = node
|
||||
.inner
|
||||
.add_ons_handle
|
||||
.engine_shutdown
|
||||
.shutdown()
|
||||
.expect("shutdown should return receiver");
|
||||
|
||||
tokio::time::timeout(std::time::Duration::from_secs(2), done_rx)
|
||||
.await
|
||||
.expect("shutdown timed out")
|
||||
.expect("shutdown completion channel should not be closed");
|
||||
|
||||
let db_block = node.inner.provider.last_block_number()?;
|
||||
assert_eq!(db_block, 1, "Database should have persisted block 1");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
mod blobs;
|
||||
mod custom_genesis;
|
||||
mod dev;
|
||||
mod eth;
|
||||
mod p2p;
|
||||
|
||||
@@ -29,7 +29,6 @@ async fn testing_rpc_build_block_works() -> eyre::Result<()> {
|
||||
.expect("valid datadir"),
|
||||
static_files_path: Some(tempdir.path().join("static")),
|
||||
rocksdb_path: Some(tempdir.path().join("rocksdb")),
|
||||
pprof_dumps_path: Some(tempdir.path().join("pprof")),
|
||||
};
|
||||
let config = NodeConfig::test().with_datadir_args(datadir_args).with_rpc(rpc_args);
|
||||
let db = create_test_rw_db();
|
||||
|
||||
@@ -153,9 +153,9 @@ where
|
||||
let PayloadConfig { parent_header, attributes } = config;
|
||||
|
||||
let state_provider = client.state_by_block_hash(parent_header.hash())?;
|
||||
let state = StateProviderDatabase::new(state_provider.as_ref());
|
||||
let state = StateProviderDatabase::new(&state_provider);
|
||||
let mut db =
|
||||
State::builder().with_database_ref(cached_reads.as_db(state)).with_bundle_update().build();
|
||||
State::builder().with_database(cached_reads.as_db_mut(state)).with_bundle_update().build();
|
||||
|
||||
let mut builder = evm_config
|
||||
.builder_for_next_block(
|
||||
@@ -211,8 +211,6 @@ where
|
||||
|
||||
let is_osaka = chain_spec.is_osaka_active_at_timestamp(attributes.timestamp);
|
||||
|
||||
let withdrawals_rlp_length = attributes.withdrawals().length();
|
||||
|
||||
while let Some(pool_tx) = best_txs.next() {
|
||||
// ensure we still have capacity for this transaction
|
||||
if cumulative_gas_used + pool_tx.gas_limit() > block_gas_limit {
|
||||
@@ -234,10 +232,10 @@ where
|
||||
// convert tx to a signed transaction
|
||||
let tx = pool_tx.to_consensus();
|
||||
|
||||
let tx_rlp_len = tx.inner().length();
|
||||
|
||||
let estimated_block_size_with_tx =
|
||||
block_transactions_rlp_length + tx_rlp_len + withdrawals_rlp_length + 1024; // 1Kb of overhead for the block header
|
||||
let estimated_block_size_with_tx = block_transactions_rlp_length +
|
||||
tx.inner().length() +
|
||||
attributes.withdrawals().length() +
|
||||
1024; // 1Kb of overhead for the block header
|
||||
|
||||
if is_osaka && estimated_block_size_with_tx > MAX_RLP_BLOCK_SIZE {
|
||||
best_txs.mark_invalid(
|
||||
@@ -338,7 +336,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
block_transactions_rlp_length += tx_rlp_len;
|
||||
block_transactions_rlp_length += tx.inner().length();
|
||||
|
||||
// update and add to total fees
|
||||
let miner_fee =
|
||||
@@ -360,8 +358,7 @@ where
|
||||
return Ok(BuildOutcome::Aborted { fees: total_fees, cached_reads })
|
||||
}
|
||||
|
||||
let BlockBuilderOutcome { execution_result, block, .. } =
|
||||
builder.finish(state_provider.as_ref())?;
|
||||
let BlockBuilderOutcome { execution_result, block, .. } = builder.finish(&state_provider)?;
|
||||
|
||||
let requests = chain_spec
|
||||
.is_prague_active_at_timestamp(attributes.timestamp)
|
||||
|
||||
@@ -258,7 +258,10 @@ impl<T: TxTy> IsTyped2718 for Receipt<T> {
|
||||
|
||||
impl<T: TxTy> InMemorySize for Receipt<T> {
|
||||
fn size(&self) -> usize {
|
||||
size_of::<Self>() + self.logs.iter().map(|log| log.size()).sum::<usize>()
|
||||
self.tx_type.size() +
|
||||
core::mem::size_of::<bool>() +
|
||||
core::mem::size_of::<u64>() +
|
||||
self.logs.iter().map(|log| log.size()).sum::<usize>()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,6 @@ arbitrary = [
|
||||
]
|
||||
keccak-cache-global = [
|
||||
"reth-node-ethereum?/keccak-cache-global",
|
||||
"reth-node-core?/keccak-cache-global",
|
||||
]
|
||||
test-utils = [
|
||||
"reth-chainspec/test-utils",
|
||||
@@ -152,15 +151,6 @@ jemalloc = [
|
||||
"reth-ethereum-cli?/jemalloc",
|
||||
"reth-node-core?/jemalloc",
|
||||
]
|
||||
jemalloc-prof = [
|
||||
"jemalloc",
|
||||
"reth-cli-util?/jemalloc-prof",
|
||||
"reth-ethereum-cli?/jemalloc-prof",
|
||||
]
|
||||
jemalloc-symbols = [
|
||||
"jemalloc-prof",
|
||||
"reth-ethereum-cli?/jemalloc-symbols",
|
||||
]
|
||||
js-tracer = [
|
||||
"rpc",
|
||||
"reth-rpc/js-tracer",
|
||||
|
||||
@@ -61,6 +61,8 @@ pub use alloy_evm::{
|
||||
*,
|
||||
};
|
||||
|
||||
pub use alloy_evm::block::state_changes as state_change;
|
||||
|
||||
/// A complete configuration of EVM for Reth.
|
||||
///
|
||||
/// This trait encapsulates complete configuration required for transaction execution and block
|
||||
@@ -256,7 +258,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
|
||||
attributes: Self::NextBlockEnvCtx,
|
||||
) -> Result<ExecutionCtxFor<'_, Self>, Self::Error>;
|
||||
|
||||
/// Returns a [`TxEnv`] from a transaction.
|
||||
/// Returns a [`TxEnv`] from a transaction and [`Address`].
|
||||
fn tx_env(&self, transaction: impl IntoTxEnv<TxEnvFor<Self>>) -> TxEnvFor<Self> {
|
||||
transaction.into_tx_env()
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use reth_primitives_traits::{Block, RecoveredBlock};
|
||||
use std::time::Instant;
|
||||
|
||||
/// Executor metrics.
|
||||
// TODO(onbjerg): add sload/sstore
|
||||
#[derive(Metrics, Clone)]
|
||||
#[metrics(scope = "sync.execution")]
|
||||
pub struct ExecutorMetrics {
|
||||
|
||||
@@ -7,7 +7,7 @@ use reth_storage_errors::{db::DatabaseError, provider::ProviderError};
|
||||
use thiserror::Error;
|
||||
|
||||
/// State root errors.
|
||||
#[derive(Error, Clone, Debug)]
|
||||
#[derive(Error, PartialEq, Eq, Clone, Debug)]
|
||||
pub enum StateRootError {
|
||||
/// Internal database error.
|
||||
#[error(transparent)]
|
||||
@@ -15,25 +15,19 @@ pub enum StateRootError {
|
||||
/// Storage root error.
|
||||
#[error(transparent)]
|
||||
StorageRootError(#[from] StorageRootError),
|
||||
/// Provider error when loading prefix sets
|
||||
#[error(transparent)]
|
||||
PrefixSetLoadError(#[from] ProviderError),
|
||||
}
|
||||
|
||||
impl From<StateRootError> for ProviderError {
|
||||
fn from(value: StateRootError) -> Self {
|
||||
match value {
|
||||
impl From<StateRootError> for DatabaseError {
|
||||
fn from(err: StateRootError) -> Self {
|
||||
match err {
|
||||
StateRootError::Database(err) |
|
||||
StateRootError::StorageRootError(StorageRootError::Database(err)) => {
|
||||
Self::Database(err)
|
||||
}
|
||||
StateRootError::PrefixSetLoadError(err) => err,
|
||||
StateRootError::StorageRootError(StorageRootError::Database(err)) => err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Storage root error.
|
||||
#[derive(Error, Clone, Debug)]
|
||||
#[derive(Error, PartialEq, Eq, Clone, Debug)]
|
||||
pub enum StorageRootError {
|
||||
/// Internal database error.
|
||||
#[error(transparent)]
|
||||
@@ -49,7 +43,7 @@ impl From<StorageRootError> for DatabaseError {
|
||||
}
|
||||
|
||||
/// State proof errors.
|
||||
#[derive(Error, Clone, Debug)]
|
||||
#[derive(Error, PartialEq, Eq, Clone, Debug)]
|
||||
pub enum StateProofError {
|
||||
/// Internal database error.
|
||||
#[error(transparent)]
|
||||
|
||||
@@ -7,8 +7,8 @@ use alloy_eips::{eip1898::ForkBlock, eip2718::Encodable2718, BlockNumHash};
|
||||
use alloy_primitives::{Address, BlockHash, BlockNumber, TxHash};
|
||||
use core::{fmt, ops::RangeInclusive};
|
||||
use reth_primitives_traits::{
|
||||
transaction::signed::SignedTransaction, Block, BlockBody, IndexedTx, NodePrimitives,
|
||||
RecoveredBlock, SealedHeader,
|
||||
transaction::signed::SignedTransaction, Block, BlockBody, NodePrimitives, RecoveredBlock,
|
||||
SealedHeader,
|
||||
};
|
||||
use reth_trie_common::updates::TrieUpdates;
|
||||
use revm::database::BundleState;
|
||||
@@ -41,13 +41,6 @@ pub struct Chain<N: NodePrimitives = reth_ethereum_primitives::EthPrimitives> {
|
||||
trie_updates: Option<TrieUpdates>,
|
||||
}
|
||||
|
||||
type ChainTxReceiptMeta<'a, N> = (
|
||||
&'a RecoveredBlock<<N as NodePrimitives>::Block>,
|
||||
IndexedTx<'a, <N as NodePrimitives>::Block>,
|
||||
&'a <N as NodePrimitives>::Receipt,
|
||||
&'a [<N as NodePrimitives>::Receipt],
|
||||
);
|
||||
|
||||
impl<N: NodePrimitives> Default for Chain<N> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@@ -192,24 +185,6 @@ impl<N: NodePrimitives> Chain<N> {
|
||||
self.blocks_iter().zip(self.block_receipts_iter())
|
||||
}
|
||||
|
||||
/// Finds a transaction by hash and returns it along with its corresponding receipt data.
|
||||
///
|
||||
/// Returns `None` if the transaction is not found in this chain.
|
||||
pub fn find_transaction_and_receipt_by_hash(
|
||||
&self,
|
||||
tx_hash: TxHash,
|
||||
) -> Option<ChainTxReceiptMeta<'_, N>> {
|
||||
for (block, receipts) in self.blocks_and_receipts() {
|
||||
let Some(indexed_tx) = block.find_indexed(tx_hash) else {
|
||||
continue;
|
||||
};
|
||||
let receipt = receipts.get(indexed_tx.index())?;
|
||||
return Some((block, indexed_tx, receipt, receipts.as_slice()));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the block at which this chain forked.
|
||||
pub fn fork_block(&self) -> ForkBlock {
|
||||
let first = self.first();
|
||||
|
||||
@@ -144,12 +144,7 @@ impl<T> ExecutionOutcome<T> {
|
||||
bundle: BundleState,
|
||||
results: Vec<BlockExecutionResult<T>>,
|
||||
) -> Self {
|
||||
let mut value = Self {
|
||||
bundle,
|
||||
first_block,
|
||||
receipts: Vec::with_capacity(results.len()),
|
||||
requests: Vec::with_capacity(results.len()),
|
||||
};
|
||||
let mut value = Self { bundle, first_block, receipts: Vec::new(), requests: Vec::new() };
|
||||
for result in results {
|
||||
value.receipts.push(result.receipts);
|
||||
value.requests.push(result.requests);
|
||||
@@ -342,7 +337,7 @@ impl<T> ExecutionOutcome<T> {
|
||||
/// Prepends present the state with the given `BundleState`.
|
||||
/// It adds changes from the given state but does not override any existing changes.
|
||||
///
|
||||
/// Reverts and receipts are not updated.
|
||||
/// Reverts and receipts are not updated.
|
||||
pub fn prepend_state(&mut self, mut other: BundleState) {
|
||||
let other_len = other.reverts.len();
|
||||
// take this bundle
|
||||
@@ -396,7 +391,7 @@ impl ExecutionOutcome {
|
||||
/// Returns the ethereum receipt root for all recorded receipts.
|
||||
///
|
||||
/// Note: this function calculated Bloom filters for every receipt and created merkle trees
|
||||
/// of receipt. This is an expensive operation.
|
||||
/// of receipt. This is a expensive operation.
|
||||
pub fn ethereum_receipts_root(&self, block_number: BlockNumber) -> Option<B256> {
|
||||
self.generic_receipts_root_slow(
|
||||
block_number,
|
||||
|
||||
@@ -118,6 +118,8 @@ where
|
||||
results.push(executor.execute_one(&block)?);
|
||||
execution_duration += execute_start.elapsed();
|
||||
|
||||
// TODO(alexey): report gas metrics using `block.header.gas_used`
|
||||
|
||||
// Seal the block back and save it
|
||||
blocks.push(block);
|
||||
// Check if we should commit now
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user