Compare commits

..

21 Commits

Author SHA1 Message Date
Dan Cline
a58a623579 wip: benching dashmap and execution cache with big blocks 2026-01-14 12:32:23 +00:00
Arsenii Kulikov
edf67c1375 clone 2026-01-14 11:39:21 +00:00
Arsenii Kulikov
8e595694a8 wip 2026-01-14 10:25:07 +00:00
Dan Cline
26e6b19ea1 feat(reth-bench): add generate-big-block command
Adds a new reth-bench subcommand that generates large blocks by:
1. Fetching transactions from existing blocks via RPC
2. Packing them until target gas is reached
3. Calling engine_getPayloadV3Hacked or V4 to build the payload
4. Optionally executing via newPayload + forkchoiceUpdated

Usage:
  reth-bench generate-big-block \
    --rpc-url http://localhost:8545 \
    --engine-rpc-url http://localhost:8551 \
    --jwt-secret ~/.local/share/reth/mainnet/jwt.hex \
    --target-gas 30000000 \
    --execute
2026-01-14 10:17:10 +00:00
Dan Cline
3be8671028 feat(reth-bench): create gas ramp-up bench command 2026-01-13 14:10:08 +00:00
Alexey Shekhirin
9e3720d105 perf(engine): clear cache on hash mismatch instead of creating new 2026-01-13 00:12:01 +00:00
Alexey Shekhirin
8c3c753208 fix split usage 2026-01-13 00:12:01 +00:00
Alexey Shekhirin
6bf64ba793 Merge remote-tracking branch 'origin/main' into alexey/execution-cache-fixed-cache 2026-01-13 00:12:01 +00:00
Alexey Shekhirin
701301724f use sizes in bytes for cache size 2026-01-13 00:12:01 +00:00
Alexey Shekhirin
d7edca95c8 nits 2026-01-13 00:12:01 +00:00
Alexey Shekhirin
b36d588779 fix imports 2026-01-13 00:12:01 +00:00
Alexey Shekhirin
a1341ec875 Merge remote-tracking branch 'origin/main' into alexey/execution-cache-fixed-cache 2026-01-13 00:12:01 +00:00
Alexey Shekhirin
7e93256b01 fixed cache metrics 2026-01-13 00:12:01 +00:00
Alexey Shekhirin
34f0e138f5 remove selfdestruct handling 2026-01-13 00:12:01 +00:00
Alexey Shekhirin
90b51e960e use 64k sizes for caches 2026-01-13 00:12:01 +00:00
Alexey Shekhirin
33236d7bdf check is_prewarm like before 2026-01-13 00:12:01 +00:00
Alexey Shekhirin
64f9ca4d7b handle wipe with dashmap (bad) 2026-01-13 00:12:01 +00:00
Alexey Shekhirin
fa149fcd8c more get_or_try_insert methods 2026-01-13 00:12:01 +00:00
Alexey Shekhirin
50a777e6eb Merge remote-tracking branch 'origin/main' into alexey/execution-cache-fixed-cache 2026-01-13 00:12:01 +00:00
Alexey Shekhirin
bb034ee406 timestamped storage slots for invalidation 2026-01-13 00:12:01 +00:00
Alexey Shekhirin
bf4a697bbb use fixed-cache for accounts and storages 2026-01-13 00:12:01 +00:00
330 changed files with 2535 additions and 9178 deletions

View File

@@ -12,7 +12,7 @@ workflows:
# Check that `A` activates the features of `B`.
"propagate-feature",
# These are the features to check:
"--features=std,op,dev,asm-keccak,jemalloc,jemalloc-prof,tracy-allocator,tracy,serde-bincode-compat,serde,test-utils,arbitrary,bench,alloy-compat,min-error-logs,min-warn-logs,min-info-logs,min-debug-logs,min-trace-logs,otlp,otlp-logs,js-tracer,portable,keccak-cache-global",
"--features=std,op,dev,asm-keccak,jemalloc,jemalloc-prof,tracy-allocator,tracy,serde-bincode-compat,serde,test-utils,arbitrary,bench,alloy-compat,min-error-logs,min-warn-logs,min-info-logs,min-debug-logs,min-trace-logs,otlp,js-tracer,portable,keccak-cache-global",
# Do not try to add a new section to `[features]` of `A` only because `B` exposes that feature. There are edge-cases where this is still needed, but we can add them manually.
"--left-side-feature-missing=ignore",
# Ignore the case that `A` it outside of the workspace. Otherwise it will report errors in external dependencies that we have no influence on.

View File

@@ -11,17 +11,14 @@ go build .
# Run each hive command in the background for each simulator and wait
echo "Building images"
./hive -client reth --sim "ethereum/eels" \
--sim.buildarg fixtures=https://github.com/ethereum/execution-spec-tests/releases/download/bal@v2.0.0/fixtures_bal.tar.gz \
--sim.buildarg branch=eips/amsterdam/eip-7928 \
--sim.timelimit 1s || true &
# TODO: test code has been moved from https://github.com/ethereum/execution-spec-tests to https://github.com/ethereum/execution-specs we need to pin eels branch with `--sim.buildarg branch=<release-branch-name>` once we have the fusaka release tagged on the new repo
./hive -client reth --sim "ethereum/eels" --sim.buildarg fixtures=https://github.com/ethereum/execution-spec-tests/releases/download/v5.3.0/fixtures_develop.tar.gz -sim.timelimit 1s || true &
./hive -client reth --sim "ethereum/engine" -sim.timelimit 1s || true &
./hive -client reth --sim "devp2p" -sim.timelimit 1s || true &
./hive -client reth --sim "ethereum/rpc-compat" -sim.timelimit 1s || true &
./hive -client reth --sim "smoke/genesis" -sim.timelimit 1s || true &
./hive -client reth --sim "smoke/network" -sim.timelimit 1s || true &
./hive -client reth --sim "ethereum/sync" -sim.timelimit 1s || true &
wait
# Run docker save in parallel, wait and exit on error
@@ -40,7 +37,7 @@ for pid in "${saving_pids[@]}"; do
wait "$pid" || exit
done
# Make sure we don't rebuild images on the CI jobs
# Make sure we don't rebuild images on the CI jobs
git apply ../.github/assets/hive/no_sim_build.diff
go build .
mv ./hive ../hive_assets/

View File

@@ -24,4 +24,4 @@ done
wait
docker image ls -a
docker image ls -a

View File

@@ -3,9 +3,9 @@
on:
pull_request:
# TODO: Disabled temporarily for https://github.com/CodSpeedHQ/runner/issues/55
# merge_group :
# merge_group:
push:
branches: ["**"]
branches: [main]
env:
CARGO_TERM_COLOR: always

View File

@@ -74,4 +74,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v4

View File

@@ -9,7 +9,7 @@ on:
pull_request:
merge_group:
push:
branches: ["**"]
branches: [main]
env:
CARGO_TERM_COLOR: always

View File

@@ -6,7 +6,7 @@ on:
pull_request:
merge_group:
push:
branches: ["**"]
branches: [main]
env:
CARGO_TERM_COLOR: always

View File

@@ -6,9 +6,7 @@ on:
workflow_dispatch:
schedule:
- cron: "0 */6 * * *"
pull_request:
branches:
- main
env:
CARGO_TERM_COLOR: always
@@ -44,7 +42,6 @@ jobs:
uses: actions/checkout@v6
with:
repository: ethereum/hive
ref: master
path: hivetests
- name: Get hive commit hash
@@ -130,29 +127,27 @@ jobs:
# eth_ rpc methods
- sim: ethereum/rpc-compat
include:
# - eth_blockNumber
- eth_blockNumber
- eth_call
# - eth_chainId
# - eth_createAccessList
# - eth_estimateGas
# - eth_feeHistory
# - eth_getBalance
# - eth_getBlockBy
# - eth_getBlockTransactionCountBy
# - eth_getCode
# - eth_getProof
# - eth_getStorage
# - eth_getTransactionBy
# - eth_getTransactionCount
# - eth_getTransactionReceipt
# - eth_sendRawTransaction
# - eth_syncing
# # debug_ rpc methods
# - debug_
- eth_chainId
- eth_createAccessList
- eth_estimateGas
- eth_feeHistory
- eth_getBalance
- eth_getBlockBy
- eth_getBlockTransactionCountBy
- eth_getCode
- eth_getProof
- eth_getStorage
- eth_getTransactionBy
- eth_getTransactionCount
- eth_getTransactionReceipt
- eth_sendRawTransaction
- eth_syncing
# debug_ rpc methods
- debug_
# consume-engine
- sim: ethereum/eels/consume-engine
limit: .*tests/amsterdam.*
- sim: ethereum/eels/consume-engine
limit: .*tests/osaka.*
- sim: ethereum/eels/consume-engine
@@ -169,10 +164,10 @@ jobs:
limit: .*tests/homestead.*
- sim: ethereum/eels/consume-engine
limit: .*tests/frontier.*
- sim: ethereum/eels/consume-engine
limit: .*tests/paris.*
# consume-rlp
- sim: ethereum/eels/consume-rlp
limit: .*tests/amsterdam.*
- sim: ethereum/eels/consume-rlp
limit: .*tests/osaka.*
- sim: ethereum/eels/consume-rlp
@@ -189,6 +184,8 @@ jobs:
limit: .*tests/homestead.*
- sim: ethereum/eels/consume-rlp
limit: .*tests/frontier.*
- sim: ethereum/eels/consume-rlp
limit: .*tests/paris.*
needs:
- prepare-reth-stable
- prepare-reth-edge

View File

@@ -6,7 +6,7 @@ on:
pull_request:
merge_group:
push:
branches: ["**"]
branches: [main]
schedule:
# Run once a day at 3:00 UTC
- cron: "0 3 * * *"

View File

@@ -4,7 +4,7 @@ on:
pull_request:
merge_group:
push:
branches: ["**"]
branches: [main]
env:
CARGO_TERM_COLOR: always

View File

@@ -6,7 +6,7 @@ on:
pull_request:
merge_group:
push:
branches: ["**"]
branches: [main]
env:
CARGO_TERM_COLOR: always

View File

@@ -4,10 +4,9 @@ name: unit
on:
pull_request:
branches: ["**"]
merge_group:
push:
branches: ["**"]
branches: [main]
env:
CARGO_TERM_COLOR: always

View File

@@ -4,9 +4,9 @@ name: windows
on:
push:
branches: ["**"]
branches: [main]
pull_request:
branches: ["**"]
branches: [main]
merge_group:
env:

671
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
[workspace.package]
version = "1.10.0"
version = "1.9.3"
edition = "2024"
rust-version = "1.88"
license = "MIT OR Apache-2.0"
@@ -376,11 +376,11 @@ reth-era-utils = { path = "crates/era-utils" }
reth-errors = { path = "crates/errors" }
reth-eth-wire = { path = "crates/net/eth-wire" }
reth-eth-wire-types = { path = "crates/net/eth-wire-types" }
reth-ethereum-payload-builder = { path = "crates/ethereum/payload" }
reth-ethereum-cli = { path = "crates/ethereum/cli", default-features = false }
reth-ethereum-consensus = { path = "crates/ethereum/consensus", default-features = false }
reth-ethereum-engine-primitives = { path = "crates/ethereum/engine-primitives", default-features = false }
reth-ethereum-forks = { path = "crates/ethereum/hardforks", default-features = false }
reth-ethereum-payload-builder = { path = "crates/ethereum/payload" }
reth-ethereum-primitives = { path = "crates/ethereum/primitives", default-features = false }
reth-ethereum = { path = "crates/ethereum/reth" }
reth-etl = { path = "crates/etl" }
@@ -472,29 +472,24 @@ reth-zstd-compressors = { path = "crates/storage/zstd-compressors", default-feat
reth-ress-protocol = { path = "crates/ress/protocol" }
reth-ress-provider = { path = "crates/ress/provider" }
# revm v103 (revm v34.0.0 with BAL EIP-7928 support)
revm = { version = "34.0.0", default-features = false }
revm-bytecode = { version = "8.0.0", default-features = false }
revm-database = { version = "10.0.0", default-features = false }
revm-state = { version = "9.0.0", default-features = false }
revm-primitives = { version = "22.0.0", default-features = false }
revm-interpreter = { version = "32.0.0", default-features = false }
revm-database-interface = { version = "9.0.0", default-features = false }
revm-context = { version = "13.0.0", default-features = false }
revm-context-interface = { version = "14.0.0", default-features = false }
revm-inspector = { version = "15.0.0", default-features = false }
revm-handler = { version = "15.0.0", default-features = false }
revm-precompile = { version = "32.0.0", default-features = false }
op-revm = { version = "15.0.0", default-features = false }
# revm
revm = { version = "33.1.0", default-features = false }
revm-bytecode = { version = "7.1.1", default-features = false }
revm-database = { version = "9.0.5", default-features = false }
revm-state = { version = "8.1.1", default-features = false }
revm-primitives = { version = "21.0.2", default-features = false }
revm-interpreter = { version = "31.1.0", default-features = false }
revm-database-interface = { version = "8.0.5", default-features = false }
op-revm = { version = "14.1.0", default-features = false }
revm-inspectors = "0.33.2"
# eth
alloy-primitives = { version = "1.5.0", default-features = false, features = ["map-foldhash"] }
alloy-chains = { version = "0.2.5", default-features = false }
alloy-evm = { version = "0.25.2", default-features = false }
alloy-dyn-abi = "1.4.1"
alloy-eip7928 = { version = "0.3.0", default-features = false }
alloy-eip2124 = { version = "0.2.0", default-features = false }
alloy-eip7928 = { version = "0.1.0", default-features = false }
alloy-evm = { version = "0.25.1", default-features = false }
alloy-primitives = { version = "1.5.0", default-features = false, features = ["map-foldhash"] }
alloy-rlp = { version = "0.3.10", default-features = false, features = ["core-net"] }
alloy-sol-macro = "1.5.0"
alloy-sol-types = { version = "1.5.0", default-features = false }
@@ -502,36 +497,36 @@ alloy-trie = { version = "0.9.1", default-features = false }
alloy-hardforks = "0.4.5"
alloy-consensus = { version = "1.4.3", default-features = false }
alloy-contract = { version = "1.4.3", default-features = false }
alloy-eips = { version = "1.4.3", default-features = false }
alloy-genesis = { version = "1.4.3", default-features = false }
alloy-json-rpc = { version = "1.4.3", default-features = false }
alloy-network = { version = "1.4.3", default-features = false }
alloy-network-primitives = { version = "1.4.3", default-features = false }
alloy-provider = { version = "1.4.3", features = ["reqwest", "debug-api"], default-features = false }
alloy-pubsub = { version = "1.4.3", default-features = false }
alloy-rpc-client = { version = "1.4.3", default-features = false }
alloy-rpc-types = { version = "1.4.3", features = ["eth"], default-features = false }
alloy-rpc-types-admin = { version = "1.4.3", default-features = false }
alloy-rpc-types-anvil = { version = "1.4.3", default-features = false }
alloy-rpc-types-beacon = { version = "1.4.3", default-features = false }
alloy-rpc-types-debug = { version = "1.4.3", default-features = false }
alloy-rpc-types-engine = { version = "1.4.3", default-features = false }
alloy-rpc-types-eth = { version = "1.4.3", default-features = false }
alloy-rpc-types-mev = { version = "1.4.3", default-features = false }
alloy-rpc-types-trace = { version = "1.4.3", default-features = false }
alloy-rpc-types-txpool = { version = "1.4.3", default-features = false }
alloy-serde = { version = "1.4.3", default-features = false }
alloy-signer = { version = "1.4.3", default-features = false }
alloy-signer-local = { version = "1.4.3", default-features = false }
alloy-transport = { version = "1.4.3" }
alloy-transport-http = { version = "1.4.3", features = ["reqwest-rustls-tls"], default-features = false }
alloy-transport-ipc = { version = "1.4.3", default-features = false }
alloy-transport-ws = { version = "1.4.3", default-features = false }
alloy-consensus = { version = "1.4.0", default-features = false }
alloy-contract = { version = "1.4.0", default-features = false }
alloy-eips = { version = "1.4.0", default-features = false }
alloy-genesis = { version = "1.4.0", default-features = false }
alloy-json-rpc = { version = "1.4.0", default-features = false }
alloy-network = { version = "1.4.0", default-features = false }
alloy-network-primitives = { version = "1.4.0", default-features = false }
alloy-provider = { version = "1.4.0", features = ["reqwest", "debug-api"], default-features = false }
alloy-pubsub = { version = "1.4.0", default-features = false }
alloy-rpc-client = { version = "1.4.0", default-features = false }
alloy-rpc-types = { version = "1.4.0", features = ["eth"], default-features = false }
alloy-rpc-types-admin = { version = "1.4.0", default-features = false }
alloy-rpc-types-anvil = { version = "1.4.0", default-features = false }
alloy-rpc-types-beacon = { version = "1.4.0", default-features = false }
alloy-rpc-types-debug = { version = "1.4.0", default-features = false }
alloy-rpc-types-engine = { version = "1.4.0", default-features = false }
alloy-rpc-types-eth = { version = "1.4.0", default-features = false }
alloy-rpc-types-mev = { version = "1.4.0", default-features = false }
alloy-rpc-types-trace = { version = "1.4.0", default-features = false }
alloy-rpc-types-txpool = { version = "1.4.0", default-features = false }
alloy-serde = { version = "1.4.0", default-features = false }
alloy-signer = { version = "1.4.0", default-features = false }
alloy-signer-local = { version = "1.4.0", default-features = false }
alloy-transport = { version = "1.4.0" }
alloy-transport-http = { version = "1.4.0", features = ["reqwest-rustls-tls"], default-features = false }
alloy-transport-ipc = { version = "1.4.0", default-features = false }
alloy-transport-ws = { version = "1.4.0", default-features = false }
# op
alloy-op-evm = { version = "0.25.2", default-features = false }
alloy-op-evm = { version = "0.25.0", default-features = false }
alloy-op-hardforks = "0.4.4"
op-alloy-rpc-types = { version = "0.23.1", default-features = false }
op-alloy-rpc-types-engine = { version = "0.23.1", default-features = false }
@@ -593,7 +588,7 @@ tracing-appender = "0.2"
url = { version = "2.3", default-features = false }
zstd = "0.13"
byteorder = "1"
mini-moka = "0.10"
fixed-cache = { version = "0.1.4", features = ["stats"] }
moka = "0.12"
tar-no-std = { version = "0.3.2", default-features = false }
miniz_oxide = { version = "0.8.4", default-features = false }
@@ -670,7 +665,6 @@ opentelemetry_sdk = "0.31"
opentelemetry = "0.31"
opentelemetry-otlp = "0.31"
opentelemetry-semantic-conventions = "0.31"
opentelemetry-appender-tracing = "0.31"
tracing-opentelemetry = "0.32"
# misc-testing
@@ -748,50 +742,49 @@ vergen = "9.0.4"
visibility = "0.1.1"
walkdir = "2.3.3"
vergen-git2 = "1.0.5"
# networking
ipnet = "2.11"
[patch.crates-io]
alloy-consensus = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-contract = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-eips = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-genesis = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-json-rpc = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-network = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-network-primitives = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-provider = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-pubsub = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-rpc-client = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-rpc-types = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-rpc-types-admin = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-rpc-types-anvil = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-rpc-types-beacon = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-rpc-types-debug = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-rpc-types-engine = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-rpc-types-eth = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-rpc-types-mev = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-rpc-types-trace = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-rpc-types-txpool = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-serde = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-signer = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-signer-local = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-transport = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-transport-http = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-transport-ipc = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
alloy-transport-ws = { git = "https://github.com/Soubhik-10/alloy", branch = "bal-devnet-1" }
# alloy-hardforks = { git = "https://github.com/Rimeeeeee/hardforks", branch = "amsterdam" }
alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-consensus-any = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-rpc-types-any = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-rpc-types-debug = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-rpc-types-mev = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
alloy-tx-macros = { git = "https://github.com/alloy-rs/alloy", rev = "82055ca44d660508a3614a1fb9acfd9ece2c24d7" }
# alloy-op-hardforks = { git = "https://github.com/Rimeeeeee/hardforks", branch = "amsterdam" }
# op-alloy-consensus = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" }
# op-alloy-network = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" }
# op-alloy-rpc-types = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" }
# op-alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" }
# op-alloy-rpc-jsonrpsee = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" }
#
# alloy-evm with BAL support for revm v34/v103
alloy-evm = { git = "https://github.com/alloy-rs/evm", branch = "staging-revm" }
alloy-op-evm = { git = "https://github.com/alloy-rs/evm", branch = "staging-revm" }
revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", branch = "staging-revm" }
# revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "1207e33" }
#
# jsonrpsee = { git = "https://github.com/paradigmxyz/jsonrpsee", branch = "matt/make-rpc-service-pub" }
# jsonrpsee-core = { git = "https://github.com/paradigmxyz/jsonrpsee", branch = "matt/make-rpc-service-pub" }

27
analyze_pinned_thread.py Normal file
View File

@@ -0,0 +1,27 @@
from playwright.sync_api import sync_playwright
import time
# The pinned thread URL - switch to call tree view
BASE_URL = "https://profiler.firefox.com/public/q0h10v5sjtqstm2sj90azpzxpmhjd8r3agnx058"
CALL_TREE_URL = BASE_URL + "/calltree/?globalTrackOrder=201&hiddenGlobalTracks=01&hiddenLocalTracksByPid=1153071.2-0wh~1153071.1-xgwxiz7z9wA5A7wAp&localTrackOrderByPid=1153071.1-xhxiz7z9wA5A7wApz8xjwz1Di0wxfz2wz6A6AqAswDfArDgDhxg&range=19841m4475&symbolServer=http%3A%2F%2F127.0.0.1%3A3001%2F24w2lg79pyph7f2pbcpqnhsfk1rrhp6yl4pr074&thread=E5&v=12"
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
print(f"Loading: {CALL_TREE_URL}")
page.goto(CALL_TREE_URL, timeout=60000)
page.wait_for_load_state('networkidle')
time.sleep(8) # Extra time for profile to load
page.screenshot(path='/tmp/pinned_thread_calltree.png', full_page=True)
print("Screenshot saved to /tmp/pinned_thread_calltree.png")
# Get the full page text
body_text = page.locator('body').inner_text()
print("\n" + "="*80)
print("FULL PAGE TEXT:")
print("="*80)
print(body_text[:8000])
browser.close()

59
analyze_profiler.py Normal file
View File

@@ -0,0 +1,59 @@
from playwright.sync_api import sync_playwright
import time
URLS = {
"slow_block_main": "https://share.firefox.dev/45a9S1G",
"normal_blocks": "https://share.firefox.dev/4j7tadO",
"pinned_thread": "https://share.firefox.dev/3LylcxY",
"proof_fetch_tasks": "https://share.firefox.dev/4pAZyac",
}
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
for name, url in URLS.items():
print(f"\n{'='*60}")
print(f"PROFILE: {name}")
print(f"URL: {url}")
print('='*60)
page = browser.new_page()
page.goto(url, timeout=60000)
# Wait for the profiler to fully load
page.wait_for_load_state('networkidle')
time.sleep(5) # Extra time for JS rendering
# Get the final URL (profiler redirects)
final_url = page.url
print(f"Final URL: {final_url}")
# Take a screenshot
page.screenshot(path=f'/tmp/{name}.png', full_page=True)
print(f"Screenshot saved to /tmp/{name}.png")
# Try to extract call tree content
try:
# Look for the call tree table
call_tree = page.locator('.treeView, .call-tree, [class*="CallTree"], [class*="callTree"]').first
if call_tree:
text = call_tree.inner_text()
print(f"\nCall Tree Content (first 3000 chars):\n{text[:3000]}")
except Exception as e:
print(f"Could not extract call tree: {e}")
# Get visible text content
try:
body_text = page.locator('body').inner_text()
# Filter for relevant content
lines = body_text.split('\n')
relevant = [l for l in lines if any(kw in l.lower() for kw in
['%', 'reth', 'proof', 'trie', 'hash', 'fetch', 'state', 'root',
'rayon', 'tokio', 'engine', 'execute', 'sload', 'journal'])]
print(f"\nRelevant lines:\n" + '\n'.join(relevant[:100]))
except Exception as e:
print(f"Could not extract body: {e}")
page.close()
browser.close()

View File

@@ -147,11 +147,6 @@ pub(crate) struct Args {
#[arg(long)]
pub no_clear_cache: bool,
/// Skip waiting for the node to sync before starting benchmarks.
/// When enabled, assumes the node is already synced and skips the initial tip check.
#[arg(long)]
pub skip_wait_syncing: bool,
#[command(flatten)]
pub logs: LogArgs,
@@ -583,11 +578,7 @@ async fn run_warmup_phase(
node_manager.start_node(&binary_path, warmup_ref, "warmup", &additional_args).await?;
// Wait for node to be ready and get its current tip
let current_tip = if args.skip_wait_syncing {
node_manager.wait_for_rpc_and_get_tip(&mut node_process).await?
} else {
node_manager.wait_for_node_ready_and_get_tip(&mut node_process).await?
};
let current_tip = node_manager.wait_for_node_ready_and_get_tip(&mut node_process).await?;
info!("Warmup node is ready at tip: {}", current_tip);
// Clear filesystem caches before warmup run only (unless disabled)
@@ -641,11 +632,7 @@ async fn run_benchmark_workflow(
let (mut node_process, _) = node_manager
.start_node(&binary_path, &args.baseline_ref, "baseline", &additional_args)
.await?;
let starting_tip = if args.skip_wait_syncing {
node_manager.wait_for_rpc_and_get_tip(&mut node_process).await?
} else {
node_manager.wait_for_node_ready_and_get_tip(&mut node_process).await?
};
let starting_tip = node_manager.wait_for_node_ready_and_get_tip(&mut node_process).await?;
info!("Node starting tip: {}", starting_tip);
node_manager.stop_node(&mut node_process).await?;
@@ -712,11 +699,7 @@ async fn run_benchmark_workflow(
node_manager.start_node(&binary_path, git_ref, ref_type, &additional_args).await?;
// Wait for node to be ready and get its current tip (wherever it is)
let current_tip = if args.skip_wait_syncing {
node_manager.wait_for_rpc_and_get_tip(&mut node_process).await?
} else {
node_manager.wait_for_node_ready_and_get_tip(&mut node_process).await?
};
let current_tip = node_manager.wait_for_node_ready_and_get_tip(&mut node_process).await?;
info!("Node is ready at tip: {}", current_tip);
// Calculate benchmark range

View File

@@ -458,76 +458,6 @@ impl NodeManager {
.wrap_err("Timed out waiting for node to be ready and synced")?
}
/// Wait for the node RPC to be ready and return its current tip, without waiting for sync.
///
/// This is faster than `wait_for_node_ready_and_get_tip` but may return a tip while
/// the node is still syncing.
pub(crate) async fn wait_for_rpc_and_get_tip(
&self,
child: &mut tokio::process::Child,
) -> Result<u64> {
info!("Waiting for node RPC to be ready (skipping sync wait)...");
let max_wait = Duration::from_secs(60);
let check_interval = Duration::from_secs(2);
let rpc_url = "http://localhost:8545";
let url = rpc_url.parse().map_err(|e| eyre!("Invalid RPC URL '{}': {}", rpc_url, e))?;
let provider = ProviderBuilder::new().connect_http(url);
let start_time = tokio::time::Instant::now();
let mut iteration = 0;
timeout(max_wait, async {
loop {
iteration += 1;
debug!(
"RPC readiness check iteration {} (elapsed: {:?})",
iteration,
start_time.elapsed()
);
if let Some(status) = child.try_wait()? {
return Err(eyre!("Node process exited unexpectedly with {status}"));
}
match provider.get_block_number().await {
Ok(tip) => {
debug!("HTTP RPC ready at block: {}, checking WebSocket...", tip);
let ws_url = format!("ws://localhost:{}", DEFAULT_WS_RPC_PORT);
let ws_connect = WsConnect::new(&ws_url);
match RpcClient::connect_pubsub(ws_connect).await {
Ok(_) => {
info!(
"Node RPC is ready at block: {} (took {:?}, {} iterations)",
tip,
start_time.elapsed(),
iteration
);
return Ok(tip);
}
Err(e) => {
debug!(
"HTTP RPC ready but WebSocket not ready yet (iteration {}): {:?}",
iteration, e
);
}
}
}
Err(e) => {
debug!("RPC not ready yet (iteration {}): {:?}", iteration, e);
}
}
sleep(check_interval).await;
}
})
.await
.wrap_err("Timed out waiting for node RPC to be ready")?
}
/// 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")?;

View File

@@ -4,7 +4,9 @@ use crate::{
authenticated_transport::AuthenticatedTransportConnect,
bench::{
helpers::{build_payload, prepare_payload_request, rpc_block_to_header},
output::GasRampPayloadFile,
output::{
write_benchmark_results, CombinedResult, NewPayloadResult, TotalGasOutput, TotalGasRow,
},
},
valid_payload::{call_forkchoice_updated, call_new_payload, payload_to_new_payload},
};
@@ -15,12 +17,12 @@ use alloy_rpc_types_engine::{ExecutionPayload, ForkchoiceState, JwtSecret};
use clap::Parser;
use reqwest::Url;
use reth_chainspec::ChainSpec;
use reth_chainspec::{ChainSpec, NamedChain, DEV, HOLESKY, HOODI, MAINNET, SEPOLIA};
use reth_cli_runner::CliContext;
use reth_ethereum_primitives::TransactionSigned;
use reth_primitives_traits::constants::{GAS_LIMIT_BOUND_DIVISOR, MAXIMUM_GAS_LIMIT_BLOCK};
use std::{path::PathBuf, time::Instant};
use tracing::info;
use std::{path::PathBuf, sync::Arc, time::Instant};
use tracing::{debug, info};
/// `reth benchmark gas-limit-ramp` command.
#[derive(Debug, Parser)]
@@ -37,9 +39,21 @@ pub struct Command {
#[arg(long = "jwt-secret", value_name = "JWT_SECRET")]
jwt_secret: PathBuf,
/// Output directory for benchmark results and generated payloads.
/// Optional output directory for benchmark results.
#[arg(long, value_name = "OUTPUT")]
output: PathBuf,
output: Option<PathBuf>,
}
/// Map a chain ID to a known chain spec.
fn chain_spec_from_id(chain_id: u64) -> Option<Arc<ChainSpec>> {
match NamedChain::try_from(chain_id).ok()? {
NamedChain::Mainnet => Some(MAINNET.clone()),
NamedChain::Sepolia => Some(SEPOLIA.clone()),
NamedChain::Holesky => Some(HOLESKY.clone()),
NamedChain::Hoodi => Some(HOODI.clone()),
NamedChain::Dev => Some(DEV.clone()),
_ => None,
}
}
impl Command {
@@ -50,12 +64,14 @@ impl Command {
}
// Ensure output directory exists
if self.output.is_file() {
return Err(eyre::eyre!("Output path must be a directory"));
}
if !self.output.exists() {
std::fs::create_dir_all(&self.output)?;
info!("Created output directory: {:?}", self.output);
if let Some(ref output) = self.output {
if output.is_file() {
return Err(eyre::eyre!("Output path must be a directory"));
}
if !output.exists() {
std::fs::create_dir_all(output)?;
info!("Created output directory: {:?}", output);
}
}
// Set up authenticated provider (used for both Engine API and eth_ methods)
@@ -70,7 +86,7 @@ impl Command {
// Get chain spec - required for fork detection
let chain_id = provider.get_chain_id().await?;
let chain_spec = ChainSpec::from_chain_id(chain_id)
let chain_spec = chain_spec_from_id(chain_id)
.ok_or_else(|| eyre::eyre!("Unsupported chain id: {chain_id}"))?;
// Fetch the current head block as parent
@@ -80,15 +96,19 @@ impl Command {
.await?
.ok_or_else(|| eyre::eyre!("Failed to fetch latest block"))?;
debug!(?parent_block, "Raw RPC parent block");
let (mut parent_header, mut parent_hash) = rpc_block_to_header(parent_block);
let canonical_parent = parent_header.number;
let start_block = canonical_parent + 1;
let start_block = parent_header.number + 1;
let end_block = start_block + self.blocks - 1;
info!(canonical_parent, start_block, end_block, "Starting gas limit ramp benchmark");
debug!(?parent_header, "Converted parent header");
info!(start_block, end_block, "Starting gas limit ramp benchmark");
let mut next_block_number = start_block;
let mut results = Vec::new();
let total_benchmark_duration = Instant::now();
while next_block_number <= end_block {
@@ -103,8 +123,7 @@ impl Command {
payload.clone().try_into_block_with_sidecar::<TransactionSigned>(&sidecar)?;
let max_increase = max_gas_limit_increase(parent_header.gas_limit);
let gas_limit =
parent_header.gas_limit.saturating_add(max_increase).min(MAXIMUM_GAS_LIMIT_BLOCK);
let gas_limit = next_gas_limit(parent_header.gas_limit, max_increase);
block.header.gas_limit = gas_limit;
@@ -120,17 +139,20 @@ impl Command {
Some(new_payload_version),
)?;
// Save payload to file with version info for replay
let payload_path =
self.output.join(format!("payload_block_{}.json", block.header.number));
let file =
GasRampPayloadFile { version: version as u8, block_hash, params: params.clone() };
let payload_json = serde_json::to_string_pretty(&file)?;
std::fs::write(&payload_path, &payload_json)?;
info!(block_number = block.header.number, path = %payload_path.display(), "Saved payload");
debug!(
target: "reth-bench",
block_number = block.header.number,
gas_limit,
max_increase,
"Sending empty payload"
);
let start = Instant::now();
call_new_payload(&provider, version, params).await?;
let new_payload_result =
NewPayloadResult { gas_used: block.header.gas_used, latency: start.elapsed() };
let forkchoice_state = ForkchoiceState {
head_block_hash: block_hash,
safe_block_hash: block_hash,
@@ -138,17 +160,51 @@ impl Command {
};
call_forkchoice_updated(&provider, version, forkchoice_state, None).await?;
let total_latency = start.elapsed();
let fcu_latency = total_latency - new_payload_result.latency;
let transaction_count = block.body.transactions.len() as u64;
let combined_result = CombinedResult {
block_number: block.header.number,
gas_limit,
transaction_count,
new_payload_result,
fcu_latency,
total_latency,
};
info!(%combined_result);
let gas_row = TotalGasRow {
block_number: block.header.number,
transaction_count,
gas_used: block.header.gas_used,
time: total_benchmark_duration.elapsed(),
};
results.push((gas_row, combined_result));
parent_header = block.header;
parent_hash = block_hash;
debug!(?parent_header, "RPC parent header");
next_block_number += 1;
}
let (gas_output_results, combined_results): (Vec<TotalGasRow>, Vec<CombinedResult>) =
results.into_iter().unzip();
if let Some(ref path) = self.output {
write_benchmark_results(path, &gas_output_results, combined_results)?;
}
let final_gas_limit = parent_header.gas_limit;
let gas_output = TotalGasOutput::new(gas_output_results)?;
info!(
total_duration=?total_benchmark_duration.elapsed(),
blocks_processed = self.blocks,
total_duration=?gas_output.total_duration,
total_gas_used=?gas_output.total_gas_used,
blocks_processed=?gas_output.blocks_processed,
final_gas_limit,
"Benchmark complete"
"Total Ggas/s: {:.4}",
gas_output.total_gigagas_per_second()
);
Ok(())
@@ -158,3 +214,7 @@ impl Command {
const fn max_gas_limit_increase(parent_gas_limit: u64) -> u64 {
(parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR).saturating_sub(1)
}
fn next_gas_limit(parent_gas_limit: u64, max_increase: u64) -> u64 {
parent_gas_limit.saturating_add(max_increase).min(MAXIMUM_GAS_LIMIT_BLOCK)
}

View File

@@ -1,9 +1,10 @@
//! Command for generating large blocks by packing transactions from real blocks.
//!
//! This command fetches transactions from existing blocks and packs them into a single
//! large block using the `testing_buildBlockV1` RPC endpoint.
//! large block using the `testing_packBlock` RPC endpoint.
use crate::authenticated_transport::AuthenticatedTransportConnect;
use alloy_consensus::Transaction;
use alloy_eips::{BlockNumberOrTag, Typed2718};
use alloy_primitives::{Bytes, B256};
use alloy_provider::{ext::EngineApi, network::AnyNetwork, Provider, RootProvider};
@@ -20,13 +21,13 @@ use reth_cli_runner::CliContext;
use reth_rpc_api::TestingBuildBlockRequestV1;
use std::future::Future;
use tokio::sync::mpsc;
use tracing::{info, warn};
use tracing::{debug, info, warn};
/// A single transaction with its gas used and raw encoded bytes.
/// A single transaction with its gas limit and raw encoded bytes.
#[derive(Debug, Clone)]
pub struct RawTransaction {
/// The actual gas used by the transaction (from receipt).
pub gas_used: u64,
/// The gas limit of the transaction.
pub gas_limit: u64,
/// The transaction type (e.g., 3 for EIP-4844 blob txs).
pub tx_type: u8,
/// The raw RLP-encoded transaction bytes.
@@ -55,7 +56,7 @@ pub struct RpcTransactionSource {
impl RpcTransactionSource {
/// Create a new RPC transaction source.
pub const fn new(provider: RootProvider<AnyNetwork>) -> Self {
pub fn new(provider: RootProvider<AnyNetwork>) -> Self {
Self { provider }
}
@@ -74,47 +75,31 @@ impl TransactionSource for RpcTransactionSource {
&self,
block_number: u64,
) -> eyre::Result<Option<(Vec<RawTransaction>, u64)>> {
// Fetch block and receipts in parallel
let (block, receipts) = tokio::try_join!(
self.provider.get_block_by_number(block_number.into()).full(),
self.provider.get_block_receipts(block_number.into())
)?;
let block = self.provider.get_block_by_number(block_number.into()).full().await?;
let Some(block) = block else {
return Ok(None);
};
let Some(receipts) = receipts else {
return Err(eyre::eyre!("Receipts not found for block {}", block_number));
};
let block_gas_used = block.header.gas_used;
// Convert cumulative gas from receipts to per-tx gas_used
let mut prev_cumulative = 0u64;
let transactions: Vec<RawTransaction> = block
let gas_used = block.header.gas_used;
let transactions = block
.transactions
.txns()
.zip(receipts.iter())
.map(|(tx, receipt)| {
let cumulative = receipt.inner.inner.inner.receipt.cumulative_gas_used;
let gas_used = cumulative - prev_cumulative;
prev_cumulative = cumulative;
.map(|tx| {
let with_encoded = tx.inner.inner.clone().into_encoded();
RawTransaction {
gas_used,
gas_limit: tx.gas_limit(),
tx_type: tx.inner.ty(),
raw: with_encoded.encoded_bytes().clone(),
}
})
.collect();
Ok(Some((transactions, block_gas_used)))
Ok(Some((transactions, gas_used)))
}
}
/// Collects transactions from a source up to a target gas usage.
/// Collects transactions from a source up to a target gas limit.
#[derive(Debug)]
pub struct TransactionCollector<S> {
source: S,
@@ -123,35 +108,53 @@ pub struct TransactionCollector<S> {
impl<S: TransactionSource> TransactionCollector<S> {
/// Create a new transaction collector.
pub const fn new(source: S, target_gas: u64) -> Self {
pub fn new(source: S, target_gas: u64) -> Self {
Self { source, target_gas }
}
/// Collect transactions starting from the given block number.
///
/// Skips blob transactions (type 3) and collects until target gas is reached.
/// Returns the collected raw transaction bytes, total gas used, and the next block number.
/// Returns the collected raw transaction bytes, total gas collected, and the next block number.
pub async fn collect(&self, start_block: u64) -> eyre::Result<(Vec<Bytes>, u64, u64)> {
let mut transactions: Vec<Bytes> = Vec::new();
let mut total_gas: u64 = 0;
let mut current_block = start_block;
info!(start_block = start_block, "Fetching transactions from blocks");
while total_gas < self.target_gas {
let Some((block_txs, _)) = self.source.fetch_block_transactions(current_block).await?
info!(
block = current_block,
total_gas = total_gas,
target_gas = self.target_gas,
progress_pct = (total_gas as f64 / self.target_gas as f64 * 100.0) as u32,
"Fetching block"
);
let Some((block_txs, block_gas_used)) =
self.source.fetch_block_transactions(current_block).await?
else {
warn!(block = current_block, "Block not found, stopping");
break;
};
let block_tx_count = block_txs.len();
let mut block_gas_added: u64 = 0;
let mut block_txs_added: usize = 0;
for tx in block_txs {
// Skip blob transactions (EIP-4844, type 3)
if tx.tx_type == 3 {
debug!("Skipping blob transaction");
continue;
}
if total_gas + tx.gas_used <= self.target_gas {
if total_gas + tx.gas_limit <= self.target_gas {
transactions.push(tx.raw);
total_gas += tx.gas_used;
total_gas += tx.gas_limit;
block_gas_added += tx.gas_limit;
block_txs_added += 1;
}
if total_gas >= self.target_gas {
@@ -159,18 +162,30 @@ impl<S: TransactionSource> TransactionCollector<S> {
}
}
info!(
block = current_block,
block_txs = block_tx_count,
block_gas_used = block_gas_used,
added_txs = block_txs_added,
added_gas = block_gas_added,
total_txs = transactions.len(),
total_gas = total_gas,
"Processed block"
);
current_block += 1;
// Stop early if remaining gas is under 1M (close enough to target)
let remaining_gas = self.target_gas.saturating_sub(total_gas);
if remaining_gas < 1_000_000 {
info!(remaining_gas = remaining_gas, "Stopping: within 1M of target gas");
break;
}
}
info!(
total_txs = transactions.len(),
total_gas,
total_gas = total_gas,
next_block = current_block,
"Finished collecting transactions"
);
@@ -182,7 +197,7 @@ impl<S: TransactionSource> TransactionCollector<S> {
/// `reth bench generate-big-block` command
///
/// Generates a large block by fetching transactions from existing blocks and packing them
/// into a single block using the `testing_buildBlockV1` RPC endpoint.
/// into a single block using the `testing_packBlock` RPC endpoint.
#[derive(Debug, Parser)]
pub struct Command {
/// The RPC URL to use for fetching blocks (can be an external archive node).
@@ -193,7 +208,7 @@ pub struct Command {
#[arg(long, value_name = "ENGINE_RPC_URL", default_value = "http://localhost:8551")]
engine_rpc_url: String,
/// The RPC URL for `testing_buildBlockV1` calls (same node as engine, regular RPC port).
/// The RPC URL for testing_packBlock calls (same node as engine, regular RPC port).
#[arg(long, value_name = "TESTING_RPC_URL", default_value = "http://localhost:8545")]
testing_rpc_url: String,
@@ -216,8 +231,6 @@ pub struct Command {
execute: bool,
/// Number of payloads to generate. Each payload uses the previous as parent.
/// When count == 1, the payload is only generated and saved, not executed.
/// When count > 1, each payload is executed before building the next.
#[arg(long, default_value = "1")]
count: u64,
@@ -226,14 +239,14 @@ pub struct Command {
#[arg(long, default_value = "4")]
prefetch_buffer: usize,
/// Output directory for generated payloads. Each payload is saved as `payload_block_N.json`.
/// Output directory for generated payloads. Each payload is saved as payload_NNN.json.
#[arg(long, value_name = "OUTPUT_DIR")]
output_dir: std::path::PathBuf,
}
/// A built payload ready for execution.
struct BuiltPayload {
block_number: u64,
index: u64,
envelope: ExecutionPayloadEnvelopeV4,
block_hash: B256,
timestamp: u64,
@@ -255,7 +268,7 @@ impl Command {
let auth_client = ClientBuilder::default().connect_with(auth_transport).await?;
let auth_provider = RootProvider::<AnyNetwork>::new(auth_client);
// Set up testing RPC provider (for testing_buildBlockV1)
// Set up testing RPC provider (for testing_packBlock)
info!("Connecting to Testing RPC at {}", self.testing_rpc_url);
let testing_client = ClientBuilder::default()
.layer(RetryBackoffLayer::new(10, 800, u64::MAX))
@@ -335,7 +348,7 @@ impl Command {
total = self.count,
parent_hash = %parent_hash,
parent_timestamp = parent_timestamp,
"Building payload via testing_buildBlockV1"
"Building payload via testing_packBlock"
);
let built = self
@@ -348,6 +361,9 @@ impl Command {
info!(payload = i + 1, block_hash = %built.block_hash, "Executing payload (newPayload + FCU)");
self.execute_payload_v4(auth_provider, built.envelope, parent_hash).await?;
info!(payload = i + 1, "Payload executed successfully");
// Small delay to allow in-memory state to propagate before building next payload
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
}
parent_hash = built.block_hash;
@@ -386,14 +402,20 @@ impl Command {
let mut current_block = start_block;
for payload_idx in 0..count {
info!(
payload = payload_idx + 1,
start_block = current_block,
"Fetching transactions for payload"
);
match collector.collect(current_block).await {
Ok((transactions, total_gas, next_block)) => {
info!(
payload = payload_idx + 1,
tx_count = transactions.len(),
total_gas,
blocks = format!("{}..{}", current_block, next_block),
"Fetched transactions"
total_gas = total_gas,
next_block = next_block,
"Fetched transactions for payload"
);
current_block = next_block;
@@ -436,7 +458,7 @@ impl Command {
parent_hash = %parent_hash,
parent_timestamp = parent_timestamp,
tx_count = transactions.len(),
"Building payload via testing_buildBlockV1"
"Building payload via testing_packBlock"
);
self.build_payload(
testing_provider,
@@ -458,6 +480,9 @@ impl Command {
self.execute_payload_v4(auth_provider, current_payload.envelope, parent_hash).await?;
info!(payload = i + 1, "Payload executed successfully");
// Small delay to allow in-memory state to propagate before building next payload
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
// Start building next payload in background (if not last) - AFTER execution
if !is_last {
// Get transactions for next payload (should already be fetched or fetching)
@@ -481,7 +506,7 @@ impl Command {
parent_hash = %current_block_hash,
parent_timestamp = current_timestamp,
tx_count = next_transactions.len(),
"Building payload via testing_buildBlockV1"
"Building payload via testing_packBlock"
);
Self::build_payload_static(
@@ -506,7 +531,7 @@ impl Command {
Ok(())
}
/// Build a single payload via `testing_buildBlockV1`.
/// Build a single payload via testing_packBlock.
async fn build_payload(
&self,
testing_provider: &RootProvider<AnyNetwork>,
@@ -541,41 +566,40 @@ impl Command {
suggested_fee_recipient: alloy_primitives::Address::ZERO,
withdrawals: Some(vec![]),
parent_beacon_block_root: Some(B256::ZERO),
slot_number: None,
},
transactions: transactions.to_vec(),
extra_data: None,
};
let total_tx_bytes: usize = transactions.iter().map(|tx| tx.len()).sum();
info!(
endpoint = "testing",
method = "testing_packBlock",
payload = index + 1,
tx_count = transactions.len(),
total_tx_bytes = total_tx_bytes,
parent_hash = %parent_hash,
"Sending to testing_buildBlockV1"
"RPC call"
);
let envelope: ExecutionPayloadEnvelopeV5 =
testing_provider.client().request("testing_buildBlockV1", [request]).await?;
testing_provider.client().request("testing_packBlock", [request]).await?;
info!(payload = index + 1, "testing_packBlock response received, converting to V4 envelope");
let v4_envelope = envelope.try_into_v4()?;
let inner = &v4_envelope.envelope_inner.execution_payload.payload_inner.payload_inner;
let block_hash = inner.block_hash;
let block_number = inner.block_number;
let timestamp = inner.timestamp;
let block_hash =
v4_envelope.envelope_inner.execution_payload.payload_inner.payload_inner.block_hash;
let timestamp =
v4_envelope.envelope_inner.execution_payload.payload_inner.payload_inner.timestamp;
Ok(BuiltPayload { block_number, envelope: v4_envelope, block_hash, timestamp })
Ok(BuiltPayload { index, envelope: v4_envelope, block_hash, timestamp })
}
/// Save a payload to disk.
fn save_payload(&self, payload: &BuiltPayload) -> eyre::Result<()> {
let filename = format!("payload_block_{}.json", payload.block_number);
let filename = format!("payload_{:03}.json", payload.index + 1);
let filepath = self.output_dir.join(&filename);
let json = serde_json::to_string_pretty(&payload.envelope)?;
std::fs::write(&filepath, &json)
.wrap_err_with(|| format!("Failed to write payload to {:?}", filepath))?;
info!(block_number = payload.block_number, block_hash = %payload.block_hash, path = %filepath.display(), "Payload saved");
info!(payload = payload.index + 1, block_hash = %payload.block_hash, path = %filepath.display(), "Payload saved");
Ok(())
}
@@ -588,6 +612,13 @@ impl Command {
let block_hash =
envelope.envelope_inner.execution_payload.payload_inner.payload_inner.block_hash;
info!(
endpoint = "engine",
method = "engine_newPayloadV4",
block_hash = %block_hash,
"RPC call"
);
let status = provider
.new_payload_v4(
envelope.envelope_inner.execution_payload,
@@ -597,6 +628,8 @@ impl Command {
)
.await?;
info!(endpoint = "engine", method = "engine_newPayloadV4", ?status, "RPC response");
if !status.is_valid() {
return Err(eyre::eyre!("Payload rejected: {:?}", status));
}
@@ -607,11 +640,18 @@ impl Command {
finalized_block_hash: parent_hash,
};
info!(
endpoint = "engine",
method = "engine_forkchoiceUpdatedV3",
head = %block_hash,
safe = %parent_hash,
finalized = %parent_hash,
"RPC call"
);
let fcu_result = provider.fork_choice_updated_v3(fcu_state, None).await?;
if !fcu_result.is_valid() {
return Err(eyre::eyre!("FCU rejected: {:?}", fcu_result));
}
info!(endpoint = "engine", method = "engine_forkchoiceUpdatedV3", ?fcu_result, "RPC response");
Ok(())
}

View File

@@ -69,7 +69,6 @@ pub(crate) fn prepare_payload_request(
suggested_fee_recipient: Address::ZERO,
withdrawals: shanghai_active.then(Vec::new),
parent_beacon_block_root: cancun_active.then_some(B256::ZERO),
slot_number: None,
},
forkchoice_state: ForkchoiceState {
head_block_hash: parent_hash,
@@ -131,7 +130,21 @@ pub(crate) async fn get_payload_with_sidecar(
payload_id: PayloadId,
parent_beacon_block_root: Option<B256>,
) -> eyre::Result<(ExecutionPayload, ExecutionPayloadSidecar)> {
debug!(get_payload_version = ?version, ?payload_id, "Sending getPayload");
let method = match version {
1 => "engine_getPayloadV1",
2 => "engine_getPayloadV2",
3 => "engine_getPayloadV3",
4 => "engine_getPayloadV4",
5 => "engine_getPayloadV5",
_ => return Err(eyre::eyre!("Unsupported getPayload version: {}", version)),
};
debug!(
target: "reth-bench",
method,
?payload_id,
"Sending getPayload"
);
match version {
1 => {
@@ -178,7 +191,11 @@ pub(crate) async fn get_payload_with_sidecar(
}
5 => {
// V5 (Osaka) - use raw request since alloy doesn't have get_payload_v5 yet
let envelope = provider.get_payload_v5(payload_id).await?;
use alloy_provider::Provider;
use alloy_rpc_types_engine::ExecutionPayloadEnvelopeV5;
let envelope: ExecutionPayloadEnvelopeV5 =
provider.client().request(method, (payload_id,)).await?;
let versioned_hashes =
versioned_hashes_from_commitments(&envelope.blobs_bundle.commitments);
let cancun_fields = CancunPayloadFields {
@@ -192,6 +209,6 @@ pub(crate) async fn get_payload_with_sidecar(
ExecutionPayloadSidecar::v4(cancun_fields, prague_fields),
))
}
_ => panic!("This tool does not support getPayload versions past v5"),
_ => unreachable!(),
}
}

View File

@@ -55,7 +55,7 @@ pub enum Subcommands {
/// Generate a large block by packing transactions from existing blocks.
///
/// This command fetches transactions from real blocks and packs them into a single
/// block using the `testing_buildBlockV1` RPC endpoint.
/// block using the `testing_packBlock` RPC endpoint.
///
/// Example:
///

View File

@@ -97,7 +97,11 @@ impl Command {
let transaction_count = block.transactions.len() as u64;
let gas_used = block.header.gas_used;
debug!(number=?block.header.number, "Sending payload to engine");
debug!(
target: "reth-bench",
number=?block.header.number,
"Sending payload to engine",
);
let (version, params) = block_to_new_payload(block, is_optimism)?;

View File

@@ -1,11 +1,10 @@
//! Contains various benchmark output formats, either for logging or for
//! serialization to / from files.
use alloy_primitives::B256;
use csv::Writer;
use eyre::OptionExt;
use reth_primitives_traits::constants::GIGAGAS;
use serde::{ser::SerializeStruct, Deserialize, Serialize};
use serde::{ser::SerializeStruct, Serialize};
use std::{path::Path, time::Duration};
use tracing::info;
@@ -18,17 +17,6 @@ pub(crate) const COMBINED_OUTPUT_SUFFIX: &str = "combined_latency.csv";
/// This is the suffix for new payload output csv files.
pub(crate) const NEW_PAYLOAD_OUTPUT_SUFFIX: &str = "new_payload_latency.csv";
/// Serialized format for gas ramp payloads on disk.
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct GasRampPayloadFile {
/// Engine API version (1-5).
pub(crate) version: u8,
/// The block hash for FCU.
pub(crate) block_hash: B256,
/// The params to pass to newPayload.
pub(crate) params: serde_json::Value,
}
/// This represents the results of a single `newPayload` call in the benchmark, containing the gas
/// used and the `newPayload` latency.
#[derive(Debug)]
@@ -104,8 +92,9 @@ impl std::fmt::Display for CombinedResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Block {} processed at {:.4} Ggas/s, used {} total gas. Combined: {:.4} Ggas/s. fcu: {:?}, newPayload: {:?}",
"Block {} (gas_limit: {}) at {:.4} Ggas/s, used {} gas. Combined: {:.4} Ggas/s. fcu: {:?}, newPayload: {:?}",
self.block_number,
self.gas_limit,
self.new_payload_result.gas_per_second() / GIGAGAS as f64,
self.new_payload_result.gas_used,
self.combined_gas_per_second() / GIGAGAS as f64,

View File

@@ -3,11 +3,7 @@
//! This command reads `ExecutionPayloadEnvelopeV4` files from a directory and replays them
//! in sequence using `newPayload` followed by `forkchoiceUpdated`.
use crate::{
authenticated_transport::AuthenticatedTransportConnect,
bench::output::GasRampPayloadFile,
valid_payload::{call_forkchoice_updated, call_new_payload},
};
use crate::authenticated_transport::AuthenticatedTransportConnect;
use alloy_primitives::B256;
use alloy_provider::{ext::EngineApi, network::AnyNetwork, Provider, RootProvider};
use alloy_rpc_client::ClientBuilder;
@@ -16,7 +12,6 @@ use clap::Parser;
use eyre::Context;
use reqwest::Url;
use reth_cli_runner::CliContext;
use reth_node_api::EngineApiMessageVersion;
use std::path::PathBuf;
use tracing::{debug, info};
@@ -34,7 +29,7 @@ pub struct Command {
#[arg(long, value_name = "JWT_SECRET")]
jwt_secret: PathBuf,
/// Directory containing payload files (`payload_block_N.json`).
/// Directory containing payload files (payload_001.json, payload_002.json, etc.).
#[arg(long, value_name = "PAYLOAD_DIR")]
payload_dir: PathBuf,
@@ -46,11 +41,6 @@ pub struct Command {
/// Skip the first N payloads.
#[arg(long, value_name = "SKIP", default_value = "0")]
skip: usize,
/// Optional directory containing gas ramp payloads to replay first.
/// These are replayed before the main payloads to warm up the gas limit.
#[arg(long, value_name = "GAS_RAMP_DIR")]
gas_ramp_dir: Option<PathBuf>,
}
/// A loaded payload ready for execution.
@@ -63,16 +53,6 @@ struct LoadedPayload {
block_hash: B256,
}
/// A gas ramp payload loaded from disk.
struct GasRampPayload {
/// Block number from filename.
block_number: u64,
/// Engine API version for newPayload.
version: EngineApiMessageVersion,
/// The file contents.
file: GasRampPayloadFile,
}
impl Command {
/// Execute the `replay-payloads` command.
pub async fn execute(self, _ctx: CliContext) -> eyre::Result<()> {
@@ -104,53 +84,18 @@ impl Command {
"Using initial parent block"
);
// Load all payloads upfront to avoid I/O delays between phases
let gas_ramp_payloads = if let Some(ref gas_ramp_dir) = self.gas_ramp_dir {
let payloads = self.load_gas_ramp_payloads(gas_ramp_dir)?;
if payloads.is_empty() {
return Err(eyre::eyre!("No gas ramp payload files found in {:?}", gas_ramp_dir));
}
info!(count = payloads.len(), "Loaded gas ramp payloads from disk");
payloads
} else {
Vec::new()
};
// Discover and load payloads
let payloads = self.load_payloads()?;
if payloads.is_empty() {
return Err(eyre::eyre!("No payload files found in {:?}", self.payload_dir));
}
info!(count = payloads.len(), "Loaded main payloads from disk");
info!(count = payloads.len(), "Loaded payloads from disk");
// Execute payloads in sequence
let mut parent_hash = initial_parent_hash;
// Replay gas ramp payloads first
for (i, payload) in gas_ramp_payloads.iter().enumerate() {
info!(
gas_ramp_payload = i + 1,
total = gas_ramp_payloads.len(),
block_number = payload.block_number,
block_hash = %payload.file.block_hash,
"Executing gas ramp payload (newPayload + FCU)"
);
call_new_payload(&auth_provider, payload.version, payload.file.params.clone()).await?;
let fcu_state = ForkchoiceState {
head_block_hash: payload.file.block_hash,
safe_block_hash: parent_hash,
finalized_block_hash: parent_hash,
};
call_forkchoice_updated(&auth_provider, payload.version, fcu_state, None).await?;
info!(gas_ramp_payload = i + 1, "Gas ramp payload executed successfully");
parent_hash = payload.file.block_hash;
}
if !gas_ramp_payloads.is_empty() {
info!(count = gas_ramp_payloads.len(), "All gas ramp payloads replayed");
}
for (i, payload) in payloads.iter().enumerate() {
info!(
payload = i + 1,
@@ -229,62 +174,6 @@ impl Command {
Ok(payloads)
}
/// Load and parse gas ramp payload files from a directory.
fn load_gas_ramp_payloads(&self, dir: &PathBuf) -> eyre::Result<Vec<GasRampPayload>> {
let mut payloads = Vec::new();
let entries: Vec<_> = std::fs::read_dir(dir)
.wrap_err_with(|| format!("Failed to read directory {:?}", dir))?
.filter_map(|e| e.ok())
.filter(|e| {
e.path().extension().and_then(|s| s.to_str()) == Some("json") &&
e.file_name().to_string_lossy().starts_with("payload_block_")
})
.collect();
// Parse filenames to get block numbers and sort
let mut indexed_paths: Vec<(u64, PathBuf)> = entries
.into_iter()
.filter_map(|e| {
let name = e.file_name();
let name_str = name.to_string_lossy();
// Extract block number from "payload_block_NNN.json"
let block_str = name_str.strip_prefix("payload_block_")?.strip_suffix(".json")?;
let block_number: u64 = block_str.parse().ok()?;
Some((block_number, e.path()))
})
.collect();
indexed_paths.sort_by_key(|(num, _)| *num);
for (block_number, path) in indexed_paths {
let content = std::fs::read_to_string(&path)
.wrap_err_with(|| format!("Failed to read {:?}", path))?;
let file: GasRampPayloadFile = serde_json::from_str(&content)
.wrap_err_with(|| format!("Failed to parse {:?}", path))?;
let version = match file.version {
1 => EngineApiMessageVersion::V1,
2 => EngineApiMessageVersion::V2,
3 => EngineApiMessageVersion::V3,
4 => EngineApiMessageVersion::V4,
5 => EngineApiMessageVersion::V5,
v => return Err(eyre::eyre!("Invalid version {} in {:?}", v, path)),
};
info!(
block_number,
block_hash = %file.block_hash,
path = %path.display(),
"Loaded gas ramp payload"
);
payloads.push(GasRampPayload { block_number, version, file });
}
Ok(payloads)
}
async fn execute_payload_v4(
&self,
provider: &RootProvider<AnyNetwork>,

View File

@@ -54,6 +54,7 @@ where
payload_attributes: Option<PayloadAttributes>,
) -> TransportResult<ForkchoiceUpdated> {
debug!(
target: "reth-bench",
method = "engine_forkchoiceUpdatedV1",
?fork_choice_state,
?payload_attributes,
@@ -91,6 +92,7 @@ where
payload_attributes: Option<PayloadAttributes>,
) -> TransportResult<ForkchoiceUpdated> {
debug!(
target: "reth-bench",
method = "engine_forkchoiceUpdatedV2",
?fork_choice_state,
?payload_attributes,
@@ -128,6 +130,7 @@ where
payload_attributes: Option<PayloadAttributes>,
) -> TransportResult<ForkchoiceUpdated> {
debug!(
target: "reth-bench",
method = "engine_forkchoiceUpdatedV3",
?fork_choice_state,
?payload_attributes,
@@ -181,45 +184,6 @@ pub(crate) fn payload_to_new_payload(
target_version: Option<EngineApiMessageVersion>,
) -> eyre::Result<(EngineApiMessageVersion, serde_json::Value)> {
let (version, params) = match payload {
ExecutionPayload::V4(payload) => {
let cancun = sidecar.cancun().unwrap();
if let Some(prague) = sidecar.prague() {
if is_optimism {
(
EngineApiMessageVersion::V4,
serde_json::to_value((
OpExecutionPayloadV4 {
payload_inner: payload.payload_inner,
withdrawals_root: withdrawals_root.unwrap(),
},
cancun.versioned_hashes.clone(),
cancun.parent_beacon_block_root,
Requests::default(),
))?,
)
} else {
(
EngineApiMessageVersion::V4,
serde_json::to_value((
payload,
cancun.versioned_hashes.clone(),
cancun.parent_beacon_block_root,
prague.requests.requests_hash(),
))?,
)
}
} else {
(
EngineApiMessageVersion::V3,
serde_json::to_value((
payload,
cancun.versioned_hashes.clone(),
cancun.parent_beacon_block_root,
))?,
)
}
}
ExecutionPayload::V3(payload) => {
let cancun = sidecar.cancun().unwrap();
@@ -296,7 +260,12 @@ pub(crate) async fn call_new_payload<N: Network, P: Provider<N>>(
) -> TransportResult<()> {
let method = version.method_name();
debug!(method, "Sending newPayload");
debug!(
target: "reth-bench",
method,
params = %serde_json::to_string_pretty(&params).unwrap_or_default(),
"Sending newPayload"
);
let mut status: PayloadStatus = provider.client().request(method, &params).await?;
@@ -328,10 +297,7 @@ pub(crate) async fn call_forkchoice_updated<N, P: EngineApiValidWaitExt<N>>(
) -> TransportResult<ForkchoiceUpdated> {
// FCU V3 is used for both Cancun and Prague (there is no FCU V4)
match message_version {
EngineApiMessageVersion::V3 |
EngineApiMessageVersion::V4 |
EngineApiMessageVersion::V5 |
EngineApiMessageVersion::V6 => {
EngineApiMessageVersion::V3 | EngineApiMessageVersion::V4 | EngineApiMessageVersion::V5 => {
provider.fork_choice_updated_v3_wait(forkchoice_state, payload_attributes).await
}
EngineApiMessageVersion::V2 => {

View File

@@ -81,16 +81,12 @@ backon.workspace = true
tempfile.workspace = true
[features]
default = ["jemalloc", "otlp", "otlp-logs", "reth-revm/portable", "js-tracer", "keccak-cache-global", "asm-keccak"]
default = ["jemalloc", "otlp", "reth-revm/portable", "js-tracer", "keccak-cache-global", "asm-keccak"]
otlp = [
"reth-ethereum-cli/otlp",
"reth-node-core/otlp",
]
otlp-logs = [
"reth-ethereum-cli/otlp-logs",
"reth-node-core/otlp-logs",
]
js-tracer = [
"reth-node-builder/js-tracer",
"reth-node-ethereum/js-tracer",

View File

@@ -173,7 +173,6 @@ impl<N: NodePrimitives> TestBlockBuilder<N> {
transactions: transactions.into_iter().map(|tx| tx.into_inner()).collect(),
ommers: Vec::new(),
withdrawals: Some(vec![].into()),
block_access_list: None,
},
)
}

View File

@@ -45,10 +45,6 @@ use reth_network_peers::{
};
use reth_primitives_traits::{sync::LazyLock, BlockHeader, SealedHeader};
/// The hash of an empty block access list.
const EMPTY_BLOCK_ACCESS_LIST_HASH: B256 =
b256!("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347");
/// Helper method building a [`Header`] given [`Genesis`] and [`ChainHardforks`].
pub fn make_genesis_header(genesis: &Genesis, hardforks: &ChainHardforks) -> Header {
// If London is activated at genesis, we set the initial base fee as per EIP-1559.
@@ -83,12 +79,6 @@ pub fn make_genesis_header(genesis: &Genesis, hardforks: &ChainHardforks) -> Hea
.active_at_timestamp(genesis.timestamp)
.then_some(EMPTY_REQUESTS_HASH);
// If Amsterdam is activated at genesis we set block access list hash empty hash.
let block_access_list_hash = hardforks
.fork(EthereumHardfork::Amsterdam)
.active_at_timestamp(genesis.timestamp)
.then_some(EMPTY_BLOCK_ACCESS_LIST_HASH);
Header {
number: genesis.number.unwrap_or_default(),
parent_hash: genesis.parent_hash.unwrap_or_default(),
@@ -106,7 +96,6 @@ pub fn make_genesis_header(genesis: &Genesis, hardforks: &ChainHardforks) -> Hea
blob_gas_used,
excess_blob_gas,
requests_hash,
block_access_list_hash,
..Default::default()
}
}
@@ -311,7 +300,6 @@ pub fn create_chain_config(
cancun_time: timestamp(EthereumHardfork::Cancun),
prague_time: timestamp(EthereumHardfork::Prague),
osaka_time: timestamp(EthereumHardfork::Osaka),
amsterdam_time: timestamp(EthereumHardfork::Amsterdam),
bpo1_time: timestamp(EthereumHardfork::Bpo1),
bpo2_time: timestamp(EthereumHardfork::Bpo2),
bpo3_time: timestamp(EthereumHardfork::Bpo3),
@@ -913,7 +901,6 @@ impl From<Genesis> for ChainSpec {
(EthereumHardfork::Bpo3.boxed(), genesis.config.bpo3_time),
(EthereumHardfork::Bpo4.boxed(), genesis.config.bpo4_time),
(EthereumHardfork::Bpo5.boxed(), genesis.config.bpo5_time),
(EthereumHardfork::Amsterdam.boxed(), genesis.config.amsterdam_time),
];
let mut time_hardforks = time_hardfork_opts
@@ -1220,19 +1207,6 @@ impl ChainSpecBuilder {
self
}
/// Enable Amsterdam at genesis.
pub fn amsterdam_activated(mut self) -> Self {
self = self.osaka_activated();
self.hardforks.insert(EthereumHardfork::Amsterdam, ForkCondition::Timestamp(0));
self
}
/// Enable Amsterdam at the given timestamp.
pub fn with_amsterdam_at(mut self, timestamp: u64) -> Self {
self.hardforks.insert(EthereumHardfork::Amsterdam, ForkCondition::Timestamp(timestamp));
self
}
/// Build the resulting [`ChainSpec`].
///
/// # Panics

View File

@@ -83,7 +83,6 @@ backon.workspace = true
secp256k1 = { workspace = true, features = ["global-context", "std", "recovery"] }
tokio-stream.workspace = true
reqwest.workspace = true
url.workspace = true
metrics.workspace = true
# io

View File

@@ -100,7 +100,7 @@ impl<N: NodeTypes> TableViewer<()> for ListTableViewer<'_, N> {
tx.disable_long_read_transaction_safety();
let table_db = tx.inner.open_db(Some(self.args.table.name())).wrap_err("Could not open db.")?;
let stats = tx.inner.db_stat(table_db.dbi()).wrap_err(format!("Could not find table: {}", self.args.table.name()))?;
let stats = tx.inner.db_stat(&table_db).wrap_err(format!("Could not find table: {}", self.args.table.name()))?;
let total_entries = stats.entries();
let final_entry_idx = total_entries.saturating_sub(1);
if self.args.skip > final_entry_idx {

View File

@@ -54,21 +54,6 @@ pub enum SetCommand {
#[clap(action(ArgAction::Set))]
value: bool,
},
/// Store storage history in rocksdb instead of MDBX
StoragesHistory {
#[clap(action(ArgAction::Set))]
value: bool,
},
/// Store transaction hash to number mapping in rocksdb instead of MDBX
TransactionHashNumbers {
#[clap(action(ArgAction::Set))]
value: bool,
},
/// Store account history in rocksdb instead of MDBX
AccountHistory {
#[clap(action(ArgAction::Set))]
value: bool,
},
}
impl Command {
@@ -143,30 +128,6 @@ impl Command {
settings.account_changesets_in_static_files = value;
println!("Set account_changesets_in_static_files = {}", value);
}
SetCommand::StoragesHistory { value } => {
if settings.storages_history_in_rocksdb == value {
println!("storages_history_in_rocksdb is already set to {}", value);
return Ok(());
}
settings.storages_history_in_rocksdb = value;
println!("Set storages_history_in_rocksdb = {}", value);
}
SetCommand::TransactionHashNumbers { value } => {
if settings.transaction_hash_numbers_in_rocksdb == value {
println!("transaction_hash_numbers_in_rocksdb is already set to {}", value);
return Ok(());
}
settings.transaction_hash_numbers_in_rocksdb = value;
println!("Set transaction_hash_numbers_in_rocksdb = {}", value);
}
SetCommand::AccountHistory { value } => {
if settings.account_history_in_rocksdb == value {
println!("account_history_in_rocksdb is already set to {}", value);
return Ok(());
}
settings.account_history_in_rocksdb = value;
println!("Set account_history_in_rocksdb = {}", value);
}
}
// Write updated settings

View File

@@ -88,7 +88,7 @@ impl Command {
let stats = tx
.inner
.db_stat(table_db.dbi())
.db_stat(&table_db)
.wrap_err(format!("Could not find table: {db_table}"))?;
// Defaults to 16KB right now but we should
@@ -129,8 +129,7 @@ impl Command {
table.add_row(row);
let freelist = tx.inner.env().freelist()?;
let pagesize =
tx.inner.db_stat(mdbx::Database::freelist_db().dbi())?.page_size() as usize;
let pagesize = tx.inner.db_stat(&mdbx::Database::freelist_db())?.page_size() as usize;
let freelist_size = freelist * pagesize;
let mut row = Row::new();

View File

@@ -16,7 +16,6 @@ use std::{
use tar::Archive;
use tokio::task;
use tracing::info;
use url::Url;
use zstd::stream::read::Decoder as ZstdDecoder;
const BYTE_UNITS: [&str; 4] = ["B", "KB", "MB", "GB"];
@@ -86,9 +85,6 @@ impl DownloadDefaults {
"\nIf no URL is provided, the latest mainnet archive snapshot\nwill be proposed for download from ",
);
help.push_str(self.default_base_url.as_ref());
help.push_str(
".\n\nLocal file:// URLs are also supported for extracting snapshots from disk.",
);
help
}
@@ -174,14 +170,12 @@ struct DownloadProgress {
downloaded: u64,
total_size: u64,
last_displayed: Instant,
started_at: Instant,
}
impl DownloadProgress {
/// Creates new progress tracker with given total size
fn new(total_size: u64) -> Self {
let now = Instant::now();
Self { downloaded: 0, total_size, last_displayed: now, started_at: now }
Self { downloaded: 0, total_size, last_displayed: Instant::now() }
}
/// Converts bytes to human readable format (B, KB, MB, GB)
@@ -197,18 +191,6 @@ impl DownloadProgress {
format!("{:.2} {}", size, BYTE_UNITS[unit_index])
}
/// Format duration as human readable string
fn format_duration(duration: Duration) -> String {
let secs = duration.as_secs();
if secs < 60 {
format!("{secs}s")
} else if secs < 3600 {
format!("{}m {}s", secs / 60, secs % 60)
} else {
format!("{}h {}m", secs / 3600, (secs % 3600) / 60)
}
}
/// Updates progress bar
fn update(&mut self, chunk_size: u64) -> Result<()> {
self.downloaded += chunk_size;
@@ -219,24 +201,8 @@ impl DownloadProgress {
let formatted_total = Self::format_size(self.total_size);
let progress = (self.downloaded as f64 / self.total_size as f64) * 100.0;
// Calculate ETA based on current speed
let elapsed = self.started_at.elapsed();
let eta = if self.downloaded > 0 {
let remaining = self.total_size.saturating_sub(self.downloaded);
let speed = self.downloaded as f64 / elapsed.as_secs_f64();
if speed > 0.0 {
Duration::from_secs_f64(remaining as f64 / speed)
} else {
Duration::ZERO
}
} else {
Duration::ZERO
};
let eta_str = Self::format_duration(eta);
// Pad with spaces to clear any previous longer line
print!(
"\rDownloading and extracting... {progress:.2}% ({formatted_downloaded} / {formatted_total}) ETA: {eta_str} ",
"\rDownloading and extracting... {progress:.2}% ({formatted_downloaded} / {formatted_total})",
);
io::stdout().flush()?;
self.last_displayed = Instant::now();
@@ -280,30 +246,29 @@ enum CompressionFormat {
impl CompressionFormat {
/// Detect compression format from file extension
fn from_url(url: &str) -> Result<Self> {
let path =
Url::parse(url).map(|u| u.path().to_string()).unwrap_or_else(|_| url.to_string());
if path.ends_with(EXTENSION_TAR_LZ4) {
if url.ends_with(EXTENSION_TAR_LZ4) {
Ok(Self::Lz4)
} else if path.ends_with(EXTENSION_TAR_ZSTD) {
} else if url.ends_with(EXTENSION_TAR_ZSTD) {
Ok(Self::Zstd)
} else {
Err(eyre::eyre!(
"Unsupported file format. Expected .tar.lz4 or .tar.zst, got: {}",
path
))
Err(eyre::eyre!("Unsupported file format. Expected .tar.lz4 or .tar.zst, got: {}", url))
}
}
}
/// Extracts a compressed tar archive to the target directory with progress tracking.
fn extract_archive<R: Read>(
reader: R,
total_size: u64,
format: CompressionFormat,
target_dir: &Path,
) -> Result<()> {
let progress_reader = ProgressReader::new(reader, total_size);
/// Downloads and extracts a snapshot, blocking until finished.
fn blocking_download_and_extract(url: &str, target_dir: &Path) -> Result<()> {
let client = reqwest::blocking::Client::builder().build()?;
let response = client.get(url).send()?.error_for_status()?;
let total_size = response.content_length().ok_or_else(|| {
eyre::eyre!(
"Server did not provide Content-Length header. This is required for snapshot downloads"
)
})?;
let progress_reader = ProgressReader::new(response, total_size);
let format = CompressionFormat::from_url(url)?;
match format {
CompressionFormat::Lz4 => {
@@ -320,45 +285,6 @@ fn extract_archive<R: Read>(
Ok(())
}
/// Extracts a snapshot from a local file.
fn extract_from_file(path: &Path, format: CompressionFormat, target_dir: &Path) -> Result<()> {
let file = std::fs::File::open(path)?;
let total_size = file.metadata()?.len();
extract_archive(file, total_size, format, target_dir)
}
/// Fetches the snapshot from a remote URL, uncompressing it in a streaming fashion.
fn download_and_extract(url: &str, format: CompressionFormat, target_dir: &Path) -> Result<()> {
let client = reqwest::blocking::Client::builder().build()?;
let response = client.get(url).send()?.error_for_status()?;
let total_size = response.content_length().ok_or_else(|| {
eyre::eyre!(
"Server did not provide Content-Length header. This is required for snapshot downloads"
)
})?;
extract_archive(response, total_size, format, target_dir)
}
/// Downloads and extracts a snapshot, blocking until finished.
///
/// Supports both `file://` URLs for local files and HTTP(S) URLs for remote downloads.
fn blocking_download_and_extract(url: &str, target_dir: &Path) -> Result<()> {
let format = CompressionFormat::from_url(url)?;
if let Ok(parsed_url) = Url::parse(url) &&
parsed_url.scheme() == "file"
{
let file_path = parsed_url
.to_file_path()
.map_err(|_| eyre::eyre!("Invalid file:// URL path: {}", url))?;
extract_from_file(&file_path, format, target_dir)
} else {
download_and_extract(url, format, target_dir)
}
}
async fn stream_and_extract(url: &str, target_dir: &Path) -> Result<()> {
let target_dir = target_dir.to_path_buf();
let url = url.to_string();
@@ -417,7 +343,6 @@ mod tests {
assert!(help.contains("Available snapshot sources:"));
assert!(help.contains("merkle.io"));
assert!(help.contains("publicnode.com"));
assert!(help.contains("file://"));
}
#[test]
@@ -442,25 +367,4 @@ mod tests {
assert_eq!(defaults.available_snapshots.len(), 4); // 2 defaults + 2 added
assert_eq!(defaults.long_help, Some("Custom help for snapshots".to_string()));
}
#[test]
fn test_compression_format_detection() {
assert!(matches!(
CompressionFormat::from_url("https://example.com/snapshot.tar.lz4"),
Ok(CompressionFormat::Lz4)
));
assert!(matches!(
CompressionFormat::from_url("https://example.com/snapshot.tar.zst"),
Ok(CompressionFormat::Zstd)
));
assert!(matches!(
CompressionFormat::from_url("file:///path/to/snapshot.tar.lz4"),
Ok(CompressionFormat::Lz4)
));
assert!(matches!(
CompressionFormat::from_url("file:///path/to/snapshot.tar.zst"),
Ok(CompressionFormat::Zstd)
));
assert!(CompressionFormat::from_url("https://example.com/snapshot.tar.gz").is_err());
}
}

View File

@@ -113,7 +113,6 @@ impl<C: ChainSpecParser> Command<C> {
tx.clear::<tables::TransactionBlocks>()?;
tx.clear::<tables::BlockOmmers<HeaderTy<N>>>()?;
tx.clear::<tables::BlockWithdrawals>()?;
tx.clear::<tables::BlockAccessLists>()?;
reset_stage_checkpoint(tx, StageId::Bodies)?;
insert_genesis_header(&provider_rw, &self.env.chain)?;

View File

@@ -14,14 +14,11 @@ workspace = true
# reth
reth-chainspec.workspace = true
reth-consensus.workspace = true
tracing.workspace = true
# ethereum
reth-primitives-traits.workspace = true
alloy-consensus.workspace = true
alloy-primitives.workspace = true
alloy-eips.workspace = true
alloy-rlp.workspace = true
[dev-dependencies]
alloy-primitives = { workspace = true, features = ["rand"] }
@@ -38,6 +35,4 @@ std = [
"reth-primitives-traits/std",
"reth-ethereum-primitives/std",
"alloy-primitives/std",
"alloy-rlp/std",
"tracing/std",
]

View File

@@ -69,29 +69,6 @@ pub fn validate_shanghai_withdrawals<B: Block>(
Ok(())
}
/// Validate that block access lists are present in Amsterdam
///
/// [EIP-7928]: https://eips.ethereum.org/EIPS/eip-7928
#[inline]
pub fn validate_amsterdam_block_access_lists<B: Block>(
block: &SealedBlock<B>,
) -> Result<(), ConsensusError> {
let bal = block.body().block_access_list().ok_or(ConsensusError::BlockAccessListMissing)?;
let bal_hash = alloy_primitives::keccak256(alloy_rlp::encode(bal));
let header_bal_hash =
block.block_access_list_hash().ok_or(ConsensusError::BlockAccessListHashMissing)?;
if bal_hash != header_bal_hash {
tracing::error!(
target: "consensus",
?header_bal_hash,
?bal,
"Block access list hash mismatch in validation.rs in L81"
);
return Err(ConsensusError::InvalidBalHash);
}
Ok(())
}
/// Validate that blob gas is present in the block if Cancun is active.
///
/// See [EIP-4844]: Shard Blob Transactions
@@ -154,21 +131,6 @@ where
}
_ => return Err(ConsensusError::WithdrawalsRootUnexpected),
}
if let (Some(expected_hash), Some(body_bal)) =
(header.block_access_list_hash(), body.block_access_list())
{
let got_hash = alloy_primitives::keccak256(alloy_rlp::encode(body_bal));
if got_hash != expected_hash {
tracing::error!(
target: "consensus",
?expected_hash,
?body_bal,
"Block access list hash mismatch in validation.rs in L164"
);
return Err(ConsensusError::InvalidBalHash);
}
}
Ok(())
}
@@ -255,10 +217,6 @@ where
})
}
if chain_spec.is_amsterdam_active_at_timestamp(block.header().timestamp()) {
validate_amsterdam_block_access_lists(block)?;
}
Ok(())
}
@@ -534,7 +492,6 @@ mod tests {
transactions: vec![transaction],
ommers: vec![],
withdrawals: Some(Withdrawals::default()),
block_access_list: None,
};
let block = SealedBlock::seal_slow(alloy_consensus::Block { header, body });

View File

@@ -402,41 +402,9 @@ pub enum ConsensusError {
/// The maximum allowed RLP length.
max_rlp_length: usize,
},
/// Error when the hash of block access list is different from the expected hash.
#[error("Block header's BAL hash does not match the computed BAL hash.")]
InvalidBalHash,
/// Error when the block access list hash is missing.
#[error("block access list hash missing")]
BlockAccessListHashMissing,
/// Error when the block access list is different from the expected access list.
#[error("Block's access list is invalid.")]
InvalidBlockAccessList,
/// Error when the block access list is missing.
#[error("block access list missing")]
BlockAccessListMissing,
/// Error when the block access list hash is unexpected.
#[error("block access list hash unexpected")]
BlockAccessListHashUnexpected,
/// Error when the block access list contains an account change that is not present in the
/// computed access list.
#[error("Block BAL contains an account change that is not present in the computed BAL.")]
InvalidBalExtraAccount,
/// Error when the block access list is missing an account change that is present in the
/// computed access list.
#[error("Block BAL is missing an account change that is present in the computed BAL.")]
InvalidBalMissingAccount,
/// EIP-7825: Transaction gas limit exceeds maximum allowed
#[error(transparent)]
TransactionGasLimitTooHigh(Box<TxGasLimitTooHighErr>),
/// Other, likely an injected L2 error.
#[error("{0}")]
Other(String),

View File

@@ -56,7 +56,6 @@ pub fn generate_test_blocks(chain_spec: &ChainSpec, count: u64) -> Vec<SealedBlo
excess_blob_gas: None,
parent_beacon_block_root: None,
requests_hash: None,
block_access_list_hash: None,
};
// Set required fields based on chain spec
@@ -107,7 +106,6 @@ pub fn generate_test_blocks(chain_spec: &ChainSpec, count: u64) -> Vec<SealedBlo
transactions: vec![],
ommers: vec![],
withdrawals: header.withdrawals_root.is_some().then(Withdrawals::default),
block_access_list: None,
};
// Create the block

View File

@@ -227,7 +227,6 @@ where
suggested_fee_recipient: alloy_primitives::Address::random(),
withdrawals: Some(vec![]),
parent_beacon_block_root: Some(B256::ZERO),
slot_number: None,
};
env.active_node_state_mut()?
@@ -300,7 +299,6 @@ where
suggested_fee_recipient: alloy_primitives::Address::random(),
withdrawals: Some(vec![]),
parent_beacon_block_root: Some(B256::ZERO),
slot_number: None,
};
let fresh_fcu_result = EngineApiClient::<Engine>::fork_choice_updated_v3(

View File

@@ -254,7 +254,6 @@ where
suggested_fee_recipient: alloy_primitives::Address::ZERO,
withdrawals: Some(vec![]),
parent_beacon_block_root: Some(B256::ZERO),
slot_number: None,
};
EthPayloadBuilderAttributes::new(B256::ZERO, attributes)
};
@@ -285,7 +284,6 @@ where
suggested_fee_recipient: alloy_primitives::Address::ZERO,
withdrawals: Some(vec![]),
parent_beacon_block_root: Some(B256::ZERO),
slot_number: None,
};
<<N as NodeTypes>::Payload as PayloadTypes>::PayloadBuilderAttributes::from(
EthPayloadBuilderAttributes::new(B256::ZERO, attributes),

View File

@@ -161,7 +161,6 @@ async fn test_testsuite_assert_mine_block() -> Result<()> {
suggested_fee_recipient: Address::random(),
withdrawals: None,
parent_beacon_block_root: None,
slot_number: None,
},
));

View File

@@ -448,14 +448,12 @@ mod tests {
nonce: account.nonce,
code_hash: account.bytecode_hash.unwrap_or_default(),
code: None,
account_id: None,
}),
original_info: (i == 0).then(|| AccountInfo {
balance: account.balance.checked_div(U256::from(2)).unwrap_or(U256::ZERO),
nonce: 0,
code_hash: account.bytecode_hash.unwrap_or_default(),
code: None,
account_id: None,
}),
storage,
status: AccountStatus::default(),
@@ -839,7 +837,6 @@ mod tests {
receipts: vec![],
requests: Requests::default(),
gas_used: 0,
block_access_list: Default::default(),
blob_gas_used: 0,
},
};

View File

@@ -204,7 +204,7 @@ where
EngineApiMessageVersion::default(),
)
.await?;
tracing::debug!(target: "engine::local", "FCU result: {res:?}");
if !res.is_valid() {
eyre::bail!("Invalid payload status")
}

View File

@@ -57,7 +57,6 @@ where
.chain_spec
.is_cancun_active_at_timestamp(timestamp)
.then(B256::random),
slot_number: None,
}
}
}

View File

@@ -45,7 +45,7 @@ pub const DEFAULT_PREWARM_MAX_CONCURRENCY: usize = 16;
const DEFAULT_BLOCK_BUFFER_LIMIT: u32 = EPOCH_SLOTS as u32 * 2;
const DEFAULT_MAX_INVALID_HEADER_CACHE_LENGTH: u32 = 256;
const DEFAULT_MAX_EXECUTE_BLOCK_BATCH_SIZE: usize = 4;
const DEFAULT_CROSS_BLOCK_CACHE_SIZE: u64 = 4 * 1024 * 1024 * 1024;
const DEFAULT_CROSS_BLOCK_CACHE_SIZE: usize = 4 * 1024 * 1024 * 1024;
/// Determines if the host has enough parallelism to run the payload processor.
///
@@ -100,7 +100,7 @@ pub struct TreeConfig {
/// Whether to enable state provider metrics.
state_provider_metrics: bool,
/// Cross-block cache size in bytes.
cross_block_cache_size: u64,
cross_block_cache_size: usize,
/// Whether the host has enough parallelism to run state root task.
has_enough_parallelism: bool,
/// Whether multiproof task should chunk proof targets.
@@ -185,7 +185,7 @@ impl TreeConfig {
disable_prewarming: bool,
disable_parallel_sparse_trie: bool,
state_provider_metrics: bool,
cross_block_cache_size: u64,
cross_block_cache_size: usize,
has_enough_parallelism: bool,
multiproof_chunking_enabled: bool,
multiproof_chunk_size: usize,
@@ -300,7 +300,7 @@ impl TreeConfig {
}
/// Returns the cross-block cache size.
pub const fn cross_block_cache_size(&self) -> u64 {
pub const fn cross_block_cache_size(&self) -> usize {
self.cross_block_cache_size
}
@@ -403,7 +403,7 @@ impl TreeConfig {
}
/// Setter for cross block cache size.
pub const fn with_cross_block_cache_size(mut self, cross_block_cache_size: u64) -> Self {
pub const fn with_cross_block_cache_size(mut self, cross_block_cache_size: usize) -> Self {
self.cross_block_cache_size = cross_block_cache_size;
self
}

View File

@@ -62,8 +62,7 @@ pub trait EngineTypes:
+ TryInto<Self::ExecutionPayloadEnvelopeV2>
+ TryInto<Self::ExecutionPayloadEnvelopeV3>
+ TryInto<Self::ExecutionPayloadEnvelopeV4>
+ TryInto<Self::ExecutionPayloadEnvelopeV5>
+ TryInto<Self::ExecutionPayloadEnvelopeV6>,
+ TryInto<Self::ExecutionPayloadEnvelopeV5>,
> + DeserializeOwned
+ Serialize
{
@@ -107,14 +106,6 @@ pub trait EngineTypes:
+ Send
+ Sync
+ 'static;
/// Execution Payload V6 envelope type.
type ExecutionPayloadEnvelopeV6: DeserializeOwned
+ Serialize
+ Clone
+ Unpin
+ Send
+ Sync
+ 'static;
}
/// Type that validates the payloads processed by the engine API.

View File

@@ -44,6 +44,8 @@ alloy-primitives.workspace = true
alloy-rlp.workspace = true
alloy-rpc-types-engine.workspace = true
parking_lot.workspace = true
revm.workspace = true
revm-primitives.workspace = true
@@ -51,7 +53,7 @@ revm-primitives.workspace = true
futures.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["rt", "rt-multi-thread", "sync", "macros"] }
mini-moka = { workspace = true, features = ["sync"] }
fixed-cache.workspace = true
moka = { workspace = true, features = ["sync"] }
smallvec.workspace = true
@@ -65,7 +67,6 @@ schnellru.workspace = true
rayon.workspace = true
tracing.workspace = true
derive_more.workspace = true
parking_lot.workspace = true
crossbeam-channel.workspace = true
# optional deps for test-utils

View File

@@ -26,12 +26,10 @@ fn create_bench_state(num_accounts: usize) -> EvmState {
nonce: 10,
code_hash: B256::from_slice(&rng.random::<[u8; 32]>()),
code: Default::default(),
account_id: None,
},
storage,
status: AccountStatus::empty(),
transaction_id: 0,
..Default::default()
};
let address = Address::with_last_byte(i as u8);

View File

@@ -62,7 +62,6 @@ fn create_bench_state_updates(params: &BenchParams) -> Vec<EvmState> {
storage: HashMap::default(),
status: AccountStatus::SelfDestructed,
transaction_id: 0,
..Default::default()
}
} else {
RevmAccount {
@@ -71,7 +70,6 @@ fn create_bench_state_updates(params: &BenchParams) -> Vec<EvmState> {
nonce: rng.random::<u64>(),
code_hash: KECCAK_EMPTY,
code: Some(Default::default()),
account_id: None,
},
storage: (0..rng.random_range(0..=params.storage_slots_per_account))
.map(|_| {
@@ -87,7 +85,6 @@ fn create_bench_state_updates(params: &BenchParams) -> Vec<EvmState> {
.collect(),
status: AccountStatus::Touched,
transaction_id: 0,
..Default::default()
}
};

View File

@@ -7,7 +7,7 @@ use reth_ethereum_primitives::EthPrimitives;
use reth_primitives_traits::NodePrimitives;
use reth_provider::{
providers::ProviderNodeTypes, BlockExecutionWriter, BlockHashReader, ChainStateBlockWriter,
DBProvider, DatabaseProviderFactory, ProviderFactory, SaveBlocksMode,
DBProvider, DatabaseProviderFactory, ProviderFactory,
};
use reth_prune::{PrunerError, PrunerOutput, PrunerWithFactory};
use reth_stages_api::{MetricEvent, MetricEventsSender};
@@ -151,7 +151,7 @@ where
if last_block.is_some() {
let provider_rw = self.provider.database_provider_rw()?;
provider_rw.save_blocks(blocks, SaveBlocksMode::Full)?;
provider_rw.save_blocks(blocks)?;
provider_rw.commit()?;
}

File diff suppressed because it is too large Load Diff

View File

@@ -486,7 +486,6 @@ mod tests {
receipts: vec![],
requests: Requests::default(),
gas_used: 1000,
block_access_list: None,
blob_gas_used: 0,
},
))
@@ -581,12 +580,10 @@ mod tests {
nonce: 10,
code_hash: B256::random(),
code: Default::default(),
account_id: None,
},
storage,
status: AccountStatus::default(),
transaction_id: 0,
..Default::default()
},
);
state

View File

@@ -582,7 +582,7 @@ where
// null}` if the expected and the actual arrays don't match.
//
// This validation **MUST** be instantly run in all cases even during active sync process.
tracing::debug!("Payload received {:?}", payload);
let num_hash = payload.num_hash();
let engine_event = ConsensusEngineEvent::BlockReceived(num_hash);
self.emit_event(EngineApiEvent::BeaconConsensus(engine_event));
@@ -591,7 +591,6 @@ where
// Check for invalid ancestors
if let Some(invalid) = self.find_invalid_ancestor(&payload) {
tracing::debug!(target: "engine::tree", ?invalid, "found invalid ancestor for payload");
let status = self.handle_invalid_ancestor_payload(payload, invalid)?;
return Ok(TreeOutcome::new(status));
}
@@ -600,7 +599,6 @@ where
self.metrics.block_validation.record_payload_validation(start.elapsed().as_secs_f64());
let status = if self.backfill_sync_state.is_idle() {
tracing::debug!(target: "engine::tree", "inserting payload directly");
self.try_insert_payload(payload)?
} else {
self.try_buffer_payload(payload)?
@@ -639,7 +637,7 @@ where
let parent_hash = payload.parent_hash();
let mut latest_valid_hash = None;
match self.insert_payload(payload.clone()) {
match self.insert_payload(payload) {
Ok(status) => {
let status = match status {
InsertPayloadOk::Inserted(BlockStatus::Valid) => {
@@ -661,10 +659,7 @@ where
Ok(PayloadStatus::new(status, latest_valid_hash))
}
Err(error) => match error {
InsertPayloadError::Block(error) => {
tracing::debug!("payload in new payload l 617 {:?}", payload);
Ok(self.on_insert_block_error(error)?)
}
InsertPayloadError::Block(error) => Ok(self.on_insert_block_error(error)?),
InsertPayloadError::Payload(error) => {
Ok(self.on_new_payload_error(error, num_hash, parent_hash)?)
}

View File

@@ -6,7 +6,6 @@ use alloy_primitives::{keccak256, Address, StorageKey, U256};
use reth_primitives_traits::Account;
use reth_provider::{AccountReader, ProviderError};
use reth_trie::{HashedPostState, HashedStorage};
use revm_primitives::B256;
use std::ops::Range;
/// Returns the total number of storage slots (both changed and read-only) across all accounts in
@@ -102,7 +101,7 @@ impl<'a> Iterator for BALSlotIter<'a> {
return None;
}
return Some((address, slot.into()));
return Some((address, slot));
}
// Move to next account
@@ -178,11 +177,13 @@ where
let mut storage_map = HashedStorage::new(false);
for slot_changes in &account_changes.storage_changes {
let hashed_slot = keccak256(B256::from(slot_changes.slot));
let hashed_slot = keccak256(slot_changes.slot);
// Get the last change for this slot
if let Some(last_change) = slot_changes.changes.last() {
storage_map.storage.insert(hashed_slot, last_change.new_value);
storage_map
.storage
.insert(hashed_slot, U256::from_be_bytes(last_change.new_value.0));
}
}
@@ -199,7 +200,7 @@ mod tests {
use alloy_eip7928::{
AccountChanges, BalanceChange, CodeChange, NonceChange, SlotChanges, StorageChange,
};
use alloy_primitives::{Address, Bytes, B256};
use alloy_primitives::{Address, Bytes, StorageKey, B256};
use reth_revm::test_utils::StateProviderTest;
#[test]
@@ -236,8 +237,8 @@ mod tests {
let provider = StateProviderTest::default();
let address = Address::random();
let slot = U256::random();
let value = U256::random();
let slot = StorageKey::random();
let value = B256::random();
let slot_changes = SlotChanges { slot, changes: vec![StorageChange::new(0, value)] };
@@ -257,10 +258,10 @@ mod tests {
assert!(result.storages.contains_key(&hashed_address));
let storage = result.storages.get(&hashed_address).unwrap();
let hashed_slot = keccak256(B256::from(slot));
let hashed_slot = keccak256(slot);
let stored_value = storage.storage.get(&hashed_slot).unwrap();
assert_eq!(*stored_value, value);
assert_eq!(*stored_value, U256::from_be_bytes(value.0));
}
#[test]
@@ -391,15 +392,15 @@ mod tests {
let provider = StateProviderTest::default();
let address = Address::random();
let slot = U256::random();
let slot = StorageKey::random();
// Multiple changes to the same slot - should take the last one
let slot_changes = SlotChanges {
slot,
changes: vec![
StorageChange::new(0, U256::from(100)),
StorageChange::new(1, U256::from(200)),
StorageChange::new(2, U256::from(300)),
StorageChange::new(0, B256::from(U256::from(100).to_be_bytes::<32>())),
StorageChange::new(1, B256::from(U256::from(200).to_be_bytes::<32>())),
StorageChange::new(2, B256::from(U256::from(300).to_be_bytes::<32>())),
],
};
@@ -417,7 +418,7 @@ mod tests {
let hashed_address = keccak256(address);
let storage = result.storages.get(&hashed_address).unwrap();
let hashed_slot = keccak256(B256::from(slot));
let hashed_slot = keccak256(slot);
let stored_value = storage.storage.get(&hashed_slot).unwrap();
@@ -437,15 +438,15 @@ mod tests {
address: addr1,
storage_changes: vec![
SlotChanges {
slot: U256::from(100),
changes: vec![StorageChange::new(0, U256::ZERO)],
slot: StorageKey::from(U256::from(100)),
changes: vec![StorageChange::new(0, B256::ZERO)],
},
SlotChanges {
slot: U256::from(101),
changes: vec![StorageChange::new(0, U256::ZERO)],
slot: StorageKey::from(U256::from(101)),
changes: vec![StorageChange::new(0, B256::ZERO)],
},
],
storage_reads: vec![U256::from(102)],
storage_reads: vec![StorageKey::from(U256::from(102))],
balance_changes: vec![],
nonce_changes: vec![],
code_changes: vec![],
@@ -455,10 +456,10 @@ mod tests {
let account2 = AccountChanges {
address: addr2,
storage_changes: vec![SlotChanges {
slot: U256::from(200),
changes: vec![StorageChange::new(0, U256::ZERO)],
slot: StorageKey::from(U256::from(200)),
changes: vec![StorageChange::new(0, B256::ZERO)],
}],
storage_reads: vec![U256::from(201)],
storage_reads: vec![StorageKey::from(U256::from(201))],
balance_changes: vec![],
nonce_changes: vec![],
code_changes: vec![],
@@ -469,15 +470,15 @@ mod tests {
address: addr3,
storage_changes: vec![
SlotChanges {
slot: U256::from(300),
changes: vec![StorageChange::new(0, U256::ZERO)],
slot: StorageKey::from(U256::from(300)),
changes: vec![StorageChange::new(0, B256::ZERO)],
},
SlotChanges {
slot: U256::from(301),
changes: vec![StorageChange::new(0, U256::ZERO)],
slot: StorageKey::from(U256::from(301)),
changes: vec![StorageChange::new(0, B256::ZERO)],
},
],
storage_reads: vec![U256::from(302)],
storage_reads: vec![StorageKey::from(U256::from(302))],
balance_changes: vec![],
nonce_changes: vec![],
code_changes: vec![],

View File

@@ -4,7 +4,7 @@ use super::precompile_cache::PrecompileCacheMap;
use crate::tree::{
cached_state::{
CachedStateMetrics, CachedStateProvider, ExecutionCache as StateExecutionCache,
ExecutionCacheBuilder, SavedCache,
FixedCacheMetrics, SavedCache,
},
payload_processor::{
prewarm::{PrewarmCacheTask, PrewarmContext, PrewarmMode, PrewarmTaskEvent},
@@ -116,7 +116,7 @@ where
/// Metrics for trie operations
trie_metrics: MultiProofTaskMetrics,
/// Cross-block cache size in bytes.
cross_block_cache_size: u64,
cross_block_cache_size: usize,
/// Whether transactions should not be executed on prewarming task.
disable_transaction_prewarming: bool,
/// Whether state cache should be disable
@@ -299,7 +299,7 @@ where
// 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();
let (cache, metrics, _) = saved_cache.split();
Box::new(CachedStateProvider::new(provider, cache, metrics))
as Box<dyn StateProvider>
} else {
@@ -474,8 +474,13 @@ where
cache
} else {
debug!("creating new execution cache on cache miss");
let cache = ExecutionCacheBuilder::default().build_caches(self.cross_block_cache_size);
SavedCache::new(parent_hash, cache, CachedStateMetrics::zeroed())
let cache = crate::tree::cached_state::ExecutionCache::new(self.cross_block_cache_size);
SavedCache::new(
parent_hash,
cache,
CachedStateMetrics::zeroed(),
FixedCacheMetrics::zeroed(),
)
}
}
@@ -568,18 +573,22 @@ where
}
// Take existing cache (if any) or create fresh caches
let (caches, cache_metrics) = match cached.take() {
Some(existing) => {
existing.split()
}
let (caches, cache_metrics, fixed_cache_metrics) = match cached.take() {
Some(existing) => existing.split(),
None => (
ExecutionCacheBuilder::default().build_caches(self.cross_block_cache_size),
crate::tree::cached_state::ExecutionCache::new(self.cross_block_cache_size),
CachedStateMetrics::zeroed(),
FixedCacheMetrics::zeroed(),
),
};
// Insert the block's bundle state into cache
let new_cache = SavedCache::new(block_with_parent.block.hash, caches, cache_metrics);
let new_cache = SavedCache::new(
block_with_parent.block.hash,
caches,
cache_metrics,
fixed_cache_metrics,
);
if new_cache.cache().insert_state(bundle_state).is_err() {
*cached = None;
debug!(target: "engine::caching", "cleared execution cache on update error");
@@ -796,7 +805,10 @@ impl ExecutionCache {
"Existing cache found"
);
if hash_matches && available {
if available {
if !hash_matches {
c.clear();
}
return Some(c.clone());
}
} else {
@@ -862,7 +874,7 @@ where
mod tests {
use super::ExecutionCache;
use crate::tree::{
cached_state::{CachedStateMetrics, ExecutionCacheBuilder, SavedCache},
cached_state::{CachedStateMetrics, FixedCacheMetrics, SavedCache},
payload_processor::{
evm_state_to_hashed_post_state, executor::WorkloadExecutor, PayloadProcessor,
},
@@ -891,8 +903,13 @@ mod tests {
use std::sync::Arc;
fn make_saved_cache(hash: B256) -> SavedCache {
let execution_cache = ExecutionCacheBuilder::default().build_caches(1_000);
SavedCache::new(hash, execution_cache, CachedStateMetrics::zeroed())
let execution_cache = crate::tree::cached_state::ExecutionCache::new(1_000);
SavedCache::new(
hash,
execution_cache,
CachedStateMetrics::zeroed(),
FixedCacheMetrics::zeroed(),
)
}
#[test]
@@ -1058,12 +1075,10 @@ mod tests {
nonce: rng.random::<u64>(),
code_hash: KECCAK_EMPTY,
code: Some(Default::default()),
account_id: None,
},
storage,
status: AccountStatus::Touched,
transaction_id: 0,
..Default::default()
};
state_update.insert(address, account);

View File

@@ -20,7 +20,6 @@ use reth_trie_parallel::{
AccountMultiproofInput, ProofResultContext, ProofResultMessage, ProofWorkerHandle,
},
};
use revm_primitives::map::{hash_map, B256Map};
use std::{collections::BTreeMap, sync::Arc, time::Instant};
use tracing::{debug, error, instrument, trace};
@@ -226,7 +225,7 @@ struct MultiproofInput {
proof_targets: MultiProofTargets,
proof_sequence_number: u64,
state_root_message_sender: CrossbeamSender<MultiProofMessage>,
multi_added_removed_keys: Option<Arc<MultiAddedRemovedKeys>>,
multi_added_removed_keys: Option<MultiAddedRemovedKeys>,
}
impl MultiproofInput {
@@ -609,21 +608,6 @@ impl MultiProofTask {
// we still want to optimistically fetch extension children for the leaf addition case.
self.multi_added_removed_keys.touch_accounts(targets.keys().copied());
// Clone+Arc MultiAddedRemovedKeys for sharing with the dispatched multiproof tasks
let multi_added_removed_keys = Arc::new(MultiAddedRemovedKeys {
account: self.multi_added_removed_keys.account.clone(),
storages: targets
.keys()
.filter_map(|account| {
self.multi_added_removed_keys
.storages
.get(account)
.cloned()
.map(|keys| (*account, keys))
})
.collect(),
});
self.metrics.prefetch_proof_targets_accounts_histogram.record(targets.len() as f64);
self.metrics
.prefetch_proof_targets_storages_histogram
@@ -649,7 +633,7 @@ impl MultiProofTask {
proof_targets,
proof_sequence_number: self.proof_sequencer.next_sequence(),
state_root_message_sender: self.tx.clone(),
multi_added_removed_keys: Some(multi_added_removed_keys.clone()),
multi_added_removed_keys: Some(self.multi_added_removed_keys.clone()),
});
},
);
@@ -717,35 +701,6 @@ impl MultiProofTask {
state_updates += 1;
}
// Clone+Arc MultiAddedRemovedKeys for sharing with the dispatched multiproof tasks
let multi_added_removed_keys = Arc::new(MultiAddedRemovedKeys {
account: self.multi_added_removed_keys.account.clone(),
storages: {
let mut storages = B256Map::with_capacity_and_hasher(
not_fetched_state_update.storages.len(),
Default::default(),
);
for account in not_fetched_state_update
.storages
.keys()
.chain(not_fetched_state_update.accounts.keys())
{
if let hash_map::Entry::Vacant(entry) = storages.entry(*account) {
entry.insert(
self.multi_added_removed_keys
.storages
.get(account)
.cloned()
.unwrap_or_default(),
);
}
}
storages
},
});
let chunking_len = not_fetched_state_update.chunking_length();
let mut spawned_proof_targets = MultiProofTargets::default();
let available_account_workers =
@@ -764,7 +719,7 @@ impl MultiProofTask {
let proof_targets = get_proof_targets(
&hashed_state_update,
&self.fetched_proof_targets,
&multi_added_removed_keys,
&self.multi_added_removed_keys,
);
spawned_proof_targets.extend_ref(&proof_targets);
@@ -774,7 +729,7 @@ impl MultiProofTask {
proof_targets,
proof_sequence_number: self.proof_sequencer.next_sequence(),
state_root_message_sender: self.tx.clone(),
multi_added_removed_keys: Some(multi_added_removed_keys.clone()),
multi_added_removed_keys: Some(self.multi_added_removed_keys.clone()),
});
},
);
@@ -1256,7 +1211,7 @@ fn get_proof_targets(
.keys()
.filter(|slot| {
!fetched.is_some_and(|f| f.contains(*slot)) ||
storage_added_removed_keys.is_some_and(|k| k.is_removed(slot))
storage_added_removed_keys.as_ref().is_some_and(|k| k.is_removed(slot))
})
.peekable();
@@ -1311,8 +1266,9 @@ where
#[cfg(test)]
mod tests {
use crate::tree::cached_state::CachedStateProvider;
use super::*;
use crate::tree::cached_state::{CachedStateProvider, ExecutionCacheBuilder};
use alloy_eip7928::{AccountChanges, BalanceChange};
use alloy_primitives::Address;
use reth_provider::{
@@ -1323,74 +1279,9 @@ mod tests {
use reth_trie::MultiProof;
use reth_trie_parallel::proof_task::{ProofTaskCtx, ProofWorkerHandle};
use revm_primitives::{B256, U256};
use std::{
mem,
sync::{Arc, OnceLock},
};
use std::sync::{Arc, OnceLock};
use tokio::runtime::{Handle, Runtime};
/// Maximum number of targets to batch together for state update batching.
const STATE_UPDATE_MAX_BATCH_TARGETS: usize = 64;
/// Checks whether two `Source` values refer to the same origin.
fn same_source(lhs: Source, rhs: Source) -> bool {
match (lhs, rhs) {
(Source::Evm(a), Source::Evm(b)) => same_state_change_source(a, b),
(Source::BlockAccessList, Source::BlockAccessList) => true,
_ => false,
}
}
/// Checks whether two state change sources refer to the same origin.
fn same_state_change_source(lhs: StateChangeSource, rhs: StateChangeSource) -> bool {
match (lhs, rhs) {
(StateChangeSource::Transaction(a), StateChangeSource::Transaction(b)) => a == b,
(StateChangeSource::PreBlock(a), StateChangeSource::PreBlock(b)) => {
mem::discriminant(&a) == mem::discriminant(&b)
}
(StateChangeSource::PostBlock(a), StateChangeSource::PostBlock(b)) => {
mem::discriminant(&a) == mem::discriminant(&b)
}
_ => false,
}
}
/// Determines if a state update can be batched with the current batch.
fn can_batch_state_update(
batch_source: Source,
batch_update: &EvmState,
next_source: Source,
next_update: &EvmState,
) -> bool {
if !same_source(batch_source, next_source) {
return false;
}
match (batch_source, next_source) {
(
Source::Evm(StateChangeSource::PreBlock(_)),
Source::Evm(StateChangeSource::PreBlock(_)),
) |
(
Source::Evm(StateChangeSource::PostBlock(_)),
Source::Evm(StateChangeSource::PostBlock(_)),
) => batch_update == next_update,
_ => true,
}
}
/// Estimates target count from `EvmState` for batching decisions.
fn estimate_evm_state_targets(state: &EvmState) -> usize {
state
.values()
.filter(|account| account.is_touched())
.map(|account| {
let changed_slots = account.storage.iter().filter(|(_, v)| v.is_changed()).count();
1 + changed_slots
})
.sum()
}
/// Get a handle to the test runtime, creating it if necessary
fn get_test_runtime_handle() -> Handle {
static TEST_RT: OnceLock<Runtime> = OnceLock::new();
@@ -1435,7 +1326,7 @@ mod tests {
{
let db_provider = factory.database_provider_ro().unwrap();
let state_provider: StateProviderBox = Box::new(LatestStateProvider::new(db_provider));
let cache = ExecutionCacheBuilder::default().build_caches(1000);
let cache = crate::tree::cached_state::ExecutionCache::new(1000);
CachedStateProvider::new(state_provider, cache, Default::default())
}
@@ -1840,328 +1731,6 @@ mod tests {
assert_eq!(proofs_requested, 1);
}
/// Verifies that consecutive state update messages from the same source are batched together.
#[test]
fn test_state_update_batching() {
use alloy_evm::block::StateChangeSource;
use revm_state::Account;
let test_provider_factory = create_test_provider_factory();
let mut task = create_test_state_root_task(test_provider_factory);
// create multiple state updates
let addr1 = alloy_primitives::Address::random();
let addr2 = alloy_primitives::Address::random();
let mut update1 = EvmState::default();
update1.insert(
addr1,
Account {
info: revm_state::AccountInfo {
balance: U256::from(100),
nonce: 1,
code_hash: Default::default(),
code: Default::default(),
account_id: Some(0),
},
original_info: Default::default(),
transaction_id: Default::default(),
storage: Default::default(),
status: revm_state::AccountStatus::Touched,
},
);
let mut update2 = EvmState::default();
update2.insert(
addr2,
Account {
info: revm_state::AccountInfo {
balance: U256::from(200),
nonce: 2,
code_hash: Default::default(),
code: Default::default(),
account_id: Some(0),
},
original_info: Default::default(),
transaction_id: Default::default(),
storage: Default::default(),
status: revm_state::AccountStatus::Touched,
},
);
let source = StateChangeSource::Transaction(0);
let tx = task.tx.clone();
tx.send(MultiProofMessage::StateUpdate(source.into(), update1.clone())).unwrap();
tx.send(MultiProofMessage::StateUpdate(source.into(), update2.clone())).unwrap();
let proofs_requested =
if let Ok(MultiProofMessage::StateUpdate(_src, update)) = task.rx.recv() {
let mut merged_update = update;
let mut num_batched = 1;
while let Ok(MultiProofMessage::StateUpdate(_next_source, next_update)) =
task.rx.try_recv()
{
merged_update.extend(next_update);
num_batched += 1;
}
assert_eq!(num_batched, 2);
assert_eq!(merged_update.len(), 2);
assert!(merged_update.contains_key(&addr1));
assert!(merged_update.contains_key(&addr2));
task.on_state_update(source.into(), merged_update)
} else {
panic!("Expected StateUpdate message");
};
assert_eq!(proofs_requested, 1);
}
/// Verifies that state updates from different sources are not batched together.
#[test]
fn test_state_update_batching_separates_sources() {
use alloy_evm::block::StateChangeSource;
use revm_state::Account;
let test_provider_factory = create_test_provider_factory();
let task = create_test_state_root_task(test_provider_factory);
let addr_a1 = alloy_primitives::Address::random();
let addr_b1 = alloy_primitives::Address::random();
let addr_a2 = alloy_primitives::Address::random();
let create_state_update = |addr: alloy_primitives::Address, balance: u64| {
let mut state = EvmState::default();
state.insert(
addr,
Account {
info: revm_state::AccountInfo {
balance: U256::from(balance),
nonce: 1,
code_hash: Default::default(),
code: Default::default(),
account_id: Some(0),
},
original_info: Default::default(),
transaction_id: Default::default(),
storage: Default::default(),
status: revm_state::AccountStatus::Touched,
},
);
state
};
let source_a = StateChangeSource::Transaction(1);
let source_b = StateChangeSource::Transaction(2);
// Queue: A1 (immediate dispatch), B1 (batched), A2 (should become pending)
let tx = task.tx.clone();
tx.send(MultiProofMessage::StateUpdate(source_a.into(), create_state_update(addr_a1, 100)))
.unwrap();
tx.send(MultiProofMessage::StateUpdate(source_b.into(), create_state_update(addr_b1, 200)))
.unwrap();
tx.send(MultiProofMessage::StateUpdate(source_a.into(), create_state_update(addr_a2, 300)))
.unwrap();
let mut pending_msg: Option<MultiProofMessage> = None;
if let Ok(MultiProofMessage::StateUpdate(first_source, _)) = task.rx.recv() {
assert!(same_source(first_source, source_a.into()));
// Simulate batching loop for remaining messages
let mut accumulated_updates: Vec<(Source, EvmState)> = Vec::new();
let mut accumulated_targets = 0usize;
loop {
if accumulated_targets >= STATE_UPDATE_MAX_BATCH_TARGETS {
break;
}
match task.rx.try_recv() {
Ok(MultiProofMessage::StateUpdate(next_source, next_update)) => {
if let Some((batch_source, batch_update)) = accumulated_updates.first() &&
!can_batch_state_update(
*batch_source,
batch_update,
next_source,
&next_update,
)
{
pending_msg =
Some(MultiProofMessage::StateUpdate(next_source, next_update));
break;
}
let next_estimate = estimate_evm_state_targets(&next_update);
if next_estimate > STATE_UPDATE_MAX_BATCH_TARGETS {
pending_msg =
Some(MultiProofMessage::StateUpdate(next_source, next_update));
break;
}
if accumulated_targets + next_estimate > STATE_UPDATE_MAX_BATCH_TARGETS &&
!accumulated_updates.is_empty()
{
pending_msg =
Some(MultiProofMessage::StateUpdate(next_source, next_update));
break;
}
accumulated_targets += next_estimate;
accumulated_updates.push((next_source, next_update));
}
Ok(other_msg) => {
pending_msg = Some(other_msg);
break;
}
Err(_) => break,
}
}
assert_eq!(accumulated_updates.len(), 1, "Should only batch matching sources");
let batch_source = accumulated_updates[0].0;
assert!(same_source(batch_source, source_b.into()));
let batch_source = accumulated_updates[0].0;
let mut merged_update = accumulated_updates.remove(0).1;
for (_, next_update) in accumulated_updates {
merged_update.extend(next_update);
}
assert!(same_source(batch_source, source_b.into()), "Batch should use matching source");
assert!(merged_update.contains_key(&addr_b1));
assert!(!merged_update.contains_key(&addr_a1));
assert!(!merged_update.contains_key(&addr_a2));
} else {
panic!("Expected first StateUpdate");
}
match pending_msg {
Some(MultiProofMessage::StateUpdate(pending_source, pending_update)) => {
assert!(same_source(pending_source, source_a.into()));
assert!(pending_update.contains_key(&addr_a2));
}
other => panic!("Expected pending StateUpdate with source_a, got {:?}", other),
}
}
/// Verifies that pre-block updates only batch when their payloads are identical.
#[test]
fn test_pre_block_updates_require_payload_match_to_batch() {
use alloy_evm::block::{StateChangePreBlockSource, StateChangeSource};
use revm_state::Account;
let test_provider_factory = create_test_provider_factory();
let task = create_test_state_root_task(test_provider_factory);
let addr1 = alloy_primitives::Address::random();
let addr2 = alloy_primitives::Address::random();
let addr3 = alloy_primitives::Address::random();
let create_state_update = |addr: alloy_primitives::Address, balance: u64| {
let mut state = EvmState::default();
state.insert(
addr,
Account {
info: revm_state::AccountInfo {
balance: U256::from(balance),
nonce: 1,
code_hash: Default::default(),
code: Default::default(),
account_id: Some(0),
},
original_info: Default::default(),
transaction_id: Default::default(),
storage: Default::default(),
status: revm_state::AccountStatus::Touched,
},
);
state
};
let source = StateChangeSource::PreBlock(StateChangePreBlockSource::BeaconRootContract);
// Queue: first update dispatched immediately, next two should not merge
let tx = task.tx.clone();
tx.send(MultiProofMessage::StateUpdate(source.into(), create_state_update(addr1, 100)))
.unwrap();
tx.send(MultiProofMessage::StateUpdate(source.into(), create_state_update(addr2, 200)))
.unwrap();
tx.send(MultiProofMessage::StateUpdate(source.into(), create_state_update(addr3, 300)))
.unwrap();
let mut pending_msg: Option<MultiProofMessage> = None;
if let Ok(MultiProofMessage::StateUpdate(first_source, first_update)) = task.rx.recv() {
assert!(same_source(first_source, source.into()));
assert!(first_update.contains_key(&addr1));
let mut accumulated_updates: Vec<(Source, EvmState)> = Vec::new();
let mut accumulated_targets = 0usize;
loop {
if accumulated_targets >= STATE_UPDATE_MAX_BATCH_TARGETS {
break;
}
match task.rx.try_recv() {
Ok(MultiProofMessage::StateUpdate(next_source, next_update)) => {
if let Some((batch_source, batch_update)) = accumulated_updates.first() &&
!can_batch_state_update(
*batch_source,
batch_update,
next_source,
&next_update,
)
{
pending_msg =
Some(MultiProofMessage::StateUpdate(next_source, next_update));
break;
}
let next_estimate = estimate_evm_state_targets(&next_update);
if next_estimate > STATE_UPDATE_MAX_BATCH_TARGETS {
pending_msg =
Some(MultiProofMessage::StateUpdate(next_source, next_update));
break;
}
if accumulated_targets + next_estimate > STATE_UPDATE_MAX_BATCH_TARGETS &&
!accumulated_updates.is_empty()
{
pending_msg =
Some(MultiProofMessage::StateUpdate(next_source, next_update));
break;
}
accumulated_targets += next_estimate;
accumulated_updates.push((next_source, next_update));
}
Ok(other_msg) => {
pending_msg = Some(other_msg);
break;
}
Err(_) => break,
}
}
assert_eq!(
accumulated_updates.len(),
1,
"Second pre-block update should not merge with a different payload"
);
let (batched_source, batched_update) = accumulated_updates.remove(0);
assert!(same_source(batched_source, source.into()));
assert!(batched_update.contains_key(&addr2));
assert!(!batched_update.contains_key(&addr3));
match pending_msg {
Some(MultiProofMessage::StateUpdate(_, pending_update)) => {
assert!(pending_update.contains_key(&addr3));
}
other => panic!("Expected pending third pre-block update, got {:?}", other),
}
} else {
panic!("Expected first StateUpdate");
}
}
/// Verifies that different message types arriving mid-batch are not lost and preserve order.
#[test]
fn test_batching_preserves_ordering_with_different_message_type() {
@@ -2197,9 +1766,7 @@ mod tests {
nonce: 1,
code_hash: Default::default(),
code: Default::default(),
account_id: Some(0),
},
original_info: Default::default(),
transaction_id: Default::default(),
storage: Default::default(),
status: revm_state::AccountStatus::Touched,
@@ -2216,9 +1783,7 @@ mod tests {
nonce: 2,
code_hash: Default::default(),
code: Default::default(),
account_id: Some(0),
},
original_info: Default::default(),
transaction_id: Default::default(),
storage: Default::default(),
status: revm_state::AccountStatus::Touched,
@@ -2320,9 +1885,7 @@ mod tests {
nonce: 1,
code_hash: Default::default(),
code: Default::default(),
account_id: Some(0),
},
original_info: Default::default(),
transaction_id: Default::default(),
storage: Default::default(),
status: revm_state::AccountStatus::Touched,
@@ -2369,157 +1932,7 @@ mod tests {
assert_eq!(targets.len(), 1);
assert!(targets.contains_key(&prefetch_addr2));
}
other => panic!("Expected remaining PrefetchProofs2 in pending_msg, got {:?}", other),
}
}
/// Verifies that pending messages from a previous batch drain get full batching treatment.
#[test]
fn test_pending_messages_get_full_batching_treatment() {
// Queue: [Prefetch1, State1, State2, State3, Prefetch2]
//
// Expected behavior:
// 1. recv() → Prefetch1
// 2. try_recv() → State1 is different type → pending = State1, break
// 3. Process Prefetch1
// 4. Next iteration: pending = State1 → process with batching
// 5. try_recv() → State2 same type → merge
// 6. try_recv() → State3 same type → merge
// 7. try_recv() → Prefetch2 different type → pending = Prefetch2, break
// 8. Process merged State (1+2+3)
//
// Without the state-machine fix, State1 would be processed alone (no batching).
use alloy_evm::block::StateChangeSource;
use revm_state::Account;
let test_provider_factory = create_test_provider_factory();
let task = create_test_state_root_task(test_provider_factory);
let prefetch_addr1 = B256::random();
let prefetch_addr2 = B256::random();
let state_addr1 = alloy_primitives::Address::random();
let state_addr2 = alloy_primitives::Address::random();
let state_addr3 = alloy_primitives::Address::random();
// Create Prefetch targets
let mut prefetch1 = MultiProofTargets::default();
prefetch1.insert(prefetch_addr1, HashSet::default());
let mut prefetch2 = MultiProofTargets::default();
prefetch2.insert(prefetch_addr2, HashSet::default());
// Create StateUpdates
let create_state_update = |addr: alloy_primitives::Address, balance: u64| {
let mut state = EvmState::default();
state.insert(
addr,
Account {
info: revm_state::AccountInfo {
balance: U256::from(balance),
nonce: 1,
code_hash: Default::default(),
code: Default::default(),
account_id: Some(0),
},
original_info: Default::default(),
transaction_id: Default::default(),
storage: Default::default(),
status: revm_state::AccountStatus::Touched,
},
);
state
};
let source = StateChangeSource::Transaction(42);
// Queue: [Prefetch1, State1, State2, State3, Prefetch2]
let tx = task.tx.clone();
tx.send(MultiProofMessage::PrefetchProofs(prefetch1.clone())).unwrap();
tx.send(MultiProofMessage::StateUpdate(
source.into(),
create_state_update(state_addr1, 100),
))
.unwrap();
tx.send(MultiProofMessage::StateUpdate(
source.into(),
create_state_update(state_addr2, 200),
))
.unwrap();
tx.send(MultiProofMessage::StateUpdate(
source.into(),
create_state_update(state_addr3, 300),
))
.unwrap();
tx.send(MultiProofMessage::PrefetchProofs(prefetch2.clone())).unwrap();
// Simulate the state-machine loop behavior
let mut pending_msg: Option<MultiProofMessage> = None;
// First iteration: recv() gets Prefetch1, drains until State1
if let Ok(MultiProofMessage::PrefetchProofs(targets)) = task.rx.recv() {
let mut merged_targets = targets;
loop {
match task.rx.try_recv() {
Ok(MultiProofMessage::PrefetchProofs(next_targets)) => {
merged_targets.extend(next_targets);
}
Ok(other_msg) => {
pending_msg = Some(other_msg);
break;
}
Err(_) => break,
}
}
// Should have only Prefetch1 (State1 is different type)
assert_eq!(merged_targets.len(), 1);
assert!(merged_targets.contains_key(&prefetch_addr1));
} else {
panic!("Expected PrefetchProofs");
}
// Pending should be State1
assert!(matches!(pending_msg, Some(MultiProofMessage::StateUpdate(_, _))));
// Second iteration: process pending State1 WITH BATCHING
// This is the key test - the pending message should drain State2 and State3
if let Some(MultiProofMessage::StateUpdate(_src, first_update)) = pending_msg.take() {
let mut merged_update = first_update;
let mut num_batched = 1;
loop {
match task.rx.try_recv() {
Ok(MultiProofMessage::StateUpdate(_src, next_update)) => {
merged_update.extend(next_update);
num_batched += 1;
}
Ok(other_msg) => {
pending_msg = Some(other_msg);
break;
}
Err(_) => break,
}
}
// THE KEY ASSERTION: pending State1 should have batched with State2 and State3
assert_eq!(
num_batched, 3,
"Pending message should get full batching treatment and merge all 3 StateUpdates"
);
assert_eq!(merged_update.len(), 3, "Should have all 3 addresses in merged update");
assert!(merged_update.contains_key(&state_addr1));
assert!(merged_update.contains_key(&state_addr2));
assert!(merged_update.contains_key(&state_addr3));
} else {
panic!("Expected pending StateUpdate");
}
// Pending should now be Prefetch2
match pending_msg {
Some(MultiProofMessage::PrefetchProofs(targets)) => {
assert_eq!(targets.len(), 1);
assert!(targets.contains_key(&prefetch_addr2));
}
_ => panic!("Prefetch2 was lost!"),
other => panic!("Expected PrefetchProofs2 in channel, got {:?}", other),
}
}

View File

@@ -272,8 +272,8 @@ where
execution_cache.update_with_guard(|cached| {
// consumes the `SavedCache` held by the prewarming task, which releases its usage
// guard
let (caches, cache_metrics) = saved_cache.split();
let new_cache = SavedCache::new(hash, caches, cache_metrics);
let (caches, cache_metrics, fixed_cache_metrics) = saved_cache.split();
let new_cache = SavedCache::new(hash, caches, cache_metrics, fixed_cache_metrics);
// Insert state into cache while holding the lock
// Access the BundleState through the shared ExecutionOutcome

View File

@@ -1,11 +1,5 @@
//! Types and traits for validating blocks and payloads.
/// Threshold for switching from `extend_ref` loop to `merge_batch` in `merge_overlay_trie_input`.
///
/// Benchmarked crossover: `extend_ref` wins up to ~64 blocks, `merge_batch` wins beyond.
/// Using 64 as threshold since they're roughly equal there.
const MERGE_BATCH_THRESHOLD: usize = 64;
use crate::tree::{
cached_state::CachedStateProvider,
error::{InsertBlockError, InsertBlockErrorKind, InsertPayloadError},
@@ -46,10 +40,7 @@ use reth_provider::{
StateProvider, StateProviderFactory, StateReader, TrieReader,
};
use reth_revm::db::State;
use reth_trie::{
updates::{TrieUpdates, TrieUpdatesSorted},
HashedPostState, HashedPostStateSorted, StateRoot, TrieInputSorted,
};
use reth_trie::{updates::TrieUpdates, HashedPostState, StateRoot, TrieInputSorted};
use reth_trie_parallel::root::{ParallelStateRoot, ParallelStateRootError};
use revm_primitives::Address;
use std::{
@@ -624,14 +615,12 @@ where
Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
{
debug!(target: "engine::tree::payload_validator", "Executing block");
let mut db = State::builder()
.with_database(StateProviderDatabase::new(state_provider))
.with_bundle_update()
.with_bal_builder() //TODO
.without_state_clear()
.build();
db.bal_state.bal_index = 0;
db.bal_state.bal_builder = Some(revm::state::bal::Bal::new());
let spec_id = *env.evm_env.spec_id();
let evm = self.evm_config.evm_with_env(&mut db, env.evm_env);
@@ -1023,63 +1012,34 @@ where
Ok((input, block_hash))
}
/// Aggregates in-memory blocks into a single [`TrieInputSorted`] by combining their
/// Aggregates multiple in-memory blocks into a single [`TrieInputSorted`] by combining their
/// state changes.
///
/// The input `blocks` vector is ordered newest -> oldest (see `TreeState::blocks_by_hash`).
///
/// Uses `extend_ref` loop for small k, k-way `merge_batch` for large k.
/// See [`MERGE_BATCH_THRESHOLD`] for crossover point.
/// We iterate it in reverse so we start with the oldest block's trie data and extend forward
/// toward the newest, ensuring newer state takes precedence.
fn merge_overlay_trie_input(blocks: &[ExecutedBlock<N>]) -> TrieInputSorted {
if blocks.is_empty() {
return TrieInputSorted::default();
}
let mut input = TrieInputSorted::default();
let mut blocks_iter = blocks.iter().rev().peekable();
// Single block: return Arc directly without cloning
if blocks.len() == 1 {
let data = blocks[0].trie_data();
return TrieInputSorted {
state: Arc::clone(&data.hashed_state),
nodes: Arc::clone(&data.trie_updates),
prefix_sets: Default::default(),
};
}
if blocks.len() < MERGE_BATCH_THRESHOLD {
// Small k: extend_ref loop is faster
// Iterate oldest->newest so newer values override older ones
let mut blocks_iter = blocks.iter().rev();
let first = blocks_iter.next().expect("blocks is non-empty");
if let Some(first) = blocks_iter.next() {
let data = first.trie_data();
input.state = data.hashed_state;
input.nodes = data.trie_updates;
let mut state = Arc::clone(&data.hashed_state);
let mut nodes = Arc::clone(&data.trie_updates);
let state_mut = Arc::make_mut(&mut state);
let nodes_mut = Arc::make_mut(&mut nodes);
for block in blocks_iter {
let data = block.trie_data();
state_mut.extend_ref(data.hashed_state.as_ref());
nodes_mut.extend_ref(data.trie_updates.as_ref());
}
TrieInputSorted { state, nodes, prefix_sets: Default::default() }
} else {
// Large k: merge_batch is faster (O(n log k) via k-way merge)
let trie_data: Vec<_> = blocks.iter().map(|b| b.trie_data()).collect();
let merged_state = HashedPostStateSorted::merge_batch(
trie_data.iter().map(|d| d.hashed_state.as_ref()),
);
let merged_nodes =
TrieUpdatesSorted::merge_batch(trie_data.iter().map(|d| d.trie_updates.as_ref()));
TrieInputSorted {
state: Arc::new(merged_state),
nodes: Arc::new(merged_nodes),
prefix_sets: Default::default(),
// Only clone and mutate if there are more in-memory blocks.
if blocks_iter.peek().is_some() {
let state_mut = Arc::make_mut(&mut input.state);
let nodes_mut = Arc::make_mut(&mut input.nodes);
for block in blocks_iter {
let data = block.trie_data();
state_mut.extend_ref(data.hashed_state.as_ref());
nodes_mut.extend_ref(data.trie_updates.as_ref());
}
}
}
input
}
/// Spawns a background task to compute and sort trie data for the executed block.

View File

@@ -233,9 +233,9 @@ mod tests {
let dyn_precompile: DynPrecompile = (|_input: PrecompileInput<'_>| -> PrecompileResult {
Ok(PrecompileOutput {
gas_used: 0,
gas_refunded: 0,
bytes: Bytes::default(),
reverted: false,
gas_refunded: 0,
})
})
.into();
@@ -245,9 +245,9 @@ mod tests {
let output = PrecompileOutput {
gas_used: 50,
gas_refunded: 0,
bytes: alloy_primitives::Bytes::copy_from_slice(b"cached_result"),
reverted: false,
gas_refunded: 0,
};
let input = b"test_input";
@@ -277,9 +277,9 @@ mod tests {
Ok(PrecompileOutput {
gas_used: 5000,
gas_refunded: 0,
bytes: alloy_primitives::Bytes::copy_from_slice(b"output_from_precompile_1"),
reverted: false,
gas_refunded: 0,
})
}
})
@@ -292,9 +292,9 @@ mod tests {
Ok(PrecompileOutput {
gas_used: 7000,
gas_refunded: 0,
bytes: alloy_primitives::Bytes::copy_from_slice(b"output_from_precompile_2"),
reverted: false,
gas_refunded: 0,
})
}
})

View File

@@ -27,9 +27,6 @@ reth-payload-primitives.workspace = true
alloy-rpc-types-engine.workspace = true
alloy-consensus.workspace = true
# revm
revm.workspace = true
# async
tokio = { workspace = true, default-features = false }
tokio-util.workspace = true

View File

@@ -283,12 +283,8 @@ where
let mut state = State::builder()
.with_database_ref(StateProviderDatabase::new(&state_provider))
.with_bundle_update()
.with_bal_builder()
.build();
state.bal_state.bal_index = 0;
state.bal_state.bal_builder = Some(revm::state::bal::Bal::new());
let ctx = evm_config.context_for_block(&reorg_target).map_err(RethError::other)?;
let evm = evm_config.evm_for_block(&mut state, &reorg_target).map_err(RethError::other)?;
let mut builder = evm_config.create_block_builder(evm, &reorg_target_parent, ctx);

View File

@@ -39,7 +39,6 @@
//! transactions: vec![Bytes::from(vec![1, 2, 3])],
//! ommers: vec![],
//! withdrawals: None,
//! block_access_list: None,
//! };
//! // Compress the body: rlp encoding and snappy compression
//! let compressed_body = CompressedBody::from_body(&body)?;
@@ -582,12 +581,8 @@ mod tests {
#[test]
fn test_block_body_conversion() {
let block_body: BlockBody<Bytes> = BlockBody {
transactions: vec![],
ommers: vec![],
withdrawals: None,
block_access_list: None,
};
let block_body: BlockBody<Bytes> =
BlockBody { transactions: vec![], ommers: vec![], withdrawals: None };
let compressed_body = CompressedBody::from_body(&block_body).unwrap();
@@ -642,8 +637,7 @@ mod tests {
let withdrawals = Some(Withdrawals(vec![]));
let block_body =
BlockBody { transactions, ommers: vec![], withdrawals, block_access_list: None };
let block_body = BlockBody { transactions, ommers: vec![], withdrawals };
let block = Block::new(header, block_body);

View File

@@ -34,7 +34,6 @@ pub(crate) fn create_header() -> Header {
excess_blob_gas: None,
parent_beacon_block_root: None,
requests_hash: None,
block_access_list_hash: None,
}
}
@@ -139,7 +138,6 @@ pub(crate) fn create_test_block_with_compressed_data(number: BlockNumber) -> Blo
excess_blob_gas: None,
parent_beacon_block_root: None,
requests_hash: None,
block_access_list_hash: None,
};
// Create test body
@@ -147,7 +145,6 @@ pub(crate) fn create_test_block_with_compressed_data(number: BlockNumber) -> Blo
transactions: vec![Bytes::from(vec![(number % 256) as u8; 10])],
ommers: vec![],
withdrawals: Some(Withdrawals(vec![])),
block_access_list: None,
};
// Create test receipt list with bloom

View File

@@ -38,7 +38,6 @@ tempfile.workspace = true
default = []
otlp = ["reth-tracing/otlp", "reth-node-core/otlp"]
otlp-logs = ["reth-tracing/otlp-logs", "reth-node-core/otlp-logs"]
dev = ["reth-cli-commands/arbitrary"]

View File

@@ -19,7 +19,7 @@ use reth_db::DatabaseEnv;
use reth_node_api::NodePrimitives;
use reth_node_builder::{NodeBuilder, WithLaunchContext};
use reth_node_core::{
args::{LogArgs, OtlpInitStatus, OtlpLogsStatus, TraceArgs},
args::{LogArgs, OtlpInitStatus, TraceArgs},
version::version_metadata,
};
use reth_node_metrics::recorder::install_prometheus_recorder;
@@ -223,19 +223,16 @@ impl<
/// If file logging is enabled, this function returns a guard that must be kept alive to ensure
/// that all logs are flushed to disk.
///
/// If an OTLP endpoint is specified, it will export traces and logs to the configured
/// collector.
/// If an OTLP endpoint is specified, it will export metrics to the configured collector.
pub fn init_tracing(
&mut self,
runner: &CliRunner,
mut layers: Layers,
) -> eyre::Result<Option<FileWorkerGuard>> {
let otlp_status = runner.block_on(self.traces.init_otlp_tracing(&mut layers))?;
let otlp_logs_status = runner.block_on(self.traces.init_otlp_logs(&mut layers))?;
let guard = self.logs.init_tracing_with_layers(layers)?;
info!(target: "reth::cli", "Initialized tracing, debug log directory: {}", self.logs.log_file_directory);
match otlp_status {
OtlpInitStatus::Started(endpoint) => {
info!(target: "reth::cli", "Started OTLP {:?} tracing export to {endpoint}", self.traces.protocol);
@@ -246,16 +243,6 @@ impl<
OtlpInitStatus::Disabled => {}
}
match otlp_logs_status {
OtlpLogsStatus::Started(endpoint) => {
info!(target: "reth::cli", "Started OTLP {:?} logs export to {endpoint}", self.traces.protocol);
}
OtlpLogsStatus::NoFeature => {
warn!(target: "reth::cli", "Provided OTLP logs arguments do not have effect, compile with the `otlp-logs` feature")
}
OtlpLogsStatus::Disabled => {}
}
Ok(guard)
}
}

View File

@@ -22,7 +22,6 @@ reth-consensus.workspace = true
alloy-eips.workspace = true
alloy-primitives.workspace = true
alloy-consensus.workspace = true
alloy-rlp.workspace = true
tracing.workspace = true
@@ -39,7 +38,6 @@ std = [
"reth-execution-types/std",
"reth-primitives-traits/std",
"tracing/std",
"alloy-rlp/std",
]
[dev-dependencies]

View File

@@ -75,13 +75,7 @@ where
block: &RecoveredBlock<N::Block>,
result: &BlockExecutionResult<N::Receipt>,
) -> Result<(), ConsensusError> {
validate_block_post_execution(
block,
&self.chain_spec,
&result.receipts,
&result.requests,
&result.block_access_list,
)
validate_block_post_execution(block, &self.chain_spec, &result.receipts, &result.requests)
}
}
@@ -180,15 +174,6 @@ where
} else if header.requests_hash().is_some() {
return Err(ConsensusError::RequestsHashUnexpected)
}
// if self.chain_spec.is_amsterdam_active_at_timestamp(header.timestamp()) &&
// header.block_access_list_hash().is_none()
// {
// return Err(ConsensusError::BlockAccessListHashMissing)
// } else if !self.chain_spec.is_amsterdam_active_at_timestamp(header.timestamp()) &&
// header.block_access_list_hash().is_some()
// {
// return Err(ConsensusError::BlockAccessListHashUnexpected)
// }
Ok(())
}

View File

@@ -1,11 +1,11 @@
use alloc::vec::Vec;
use alloy_consensus::{proofs::calculate_receipt_root, BlockHeader, TxReceipt};
use alloy_eips::{eip7685::Requests, eip7928::BlockAccessList, Encodable2718};
use alloy_eips::{eip7685::Requests, Encodable2718};
use alloy_primitives::{Bloom, Bytes, B256};
use reth_chainspec::EthereumHardforks;
use reth_consensus::ConsensusError;
use reth_primitives_traits::{
receipt::gas_spent_by_transactions, Block, BlockBody, GotExpected, Receipt, RecoveredBlock,
receipt::gas_spent_by_transactions, Block, GotExpected, Receipt, RecoveredBlock,
};
/// Validate a block with regard to execution results:
@@ -17,7 +17,6 @@ pub fn validate_block_post_execution<B, R, ChainSpec>(
chain_spec: &ChainSpec,
receipts: &[R],
requests: &Requests,
block_access_list: &Option<BlockAccessList>,
) -> Result<(), ConsensusError>
where
B: Block,
@@ -66,33 +65,6 @@ where
}
}
// Validate bal hash matches the calculated hash
if chain_spec.is_amsterdam_active_at_timestamp(block.header().timestamp()) {
let Some(header_block_access_list_hash) = block.header().block_access_list_hash() else {
return Err(ConsensusError::BlockAccessListHashMissing)
};
if let Some(bal) = block_access_list {
let bal_hash = alloy_primitives::keccak256(alloy_rlp::encode(bal));
let block_bal = block.body().block_access_list();
tracing::debug!("Block Bal :{:?}", block_bal);
if let Some(body_bal) = block_bal {
if body_bal.is_empty() {
tracing::debug!("Hit Empty BAL : Block is {:?}", block);
}
verify_bal(body_bal, bal)?;
}
if bal_hash != header_block_access_list_hash {
tracing::debug!(
?bal_hash,
?header_block_access_list_hash,
"block access list hash mismatch"
);
return Err(ConsensusError::InvalidBalHash);
}
}
}
Ok(())
}
@@ -141,47 +113,6 @@ fn compare_receipts_root_and_logs_bloom(
Ok(())
}
/// Validates that the block access list in the body matches the expected block access list.
fn verify_bal(
body_bal: &BlockAccessList,
expected_bal: &BlockAccessList,
) -> Result<(), ConsensusError> {
if body_bal == expected_bal {
return Ok(());
}
// Extract addresses
let body_addrs: Vec<_> = body_bal.iter().map(|a| a.address).collect();
let expected_addrs: Vec<_> = expected_bal.iter().map(|a| a.address).collect();
// Missing accounts (expected but not found in body)
for addr in &expected_addrs {
if !body_addrs.contains(addr) {
tracing::debug!("Missing acc : computed bal {:?},body bal{:?}", expected_bal, body_bal);
tracing::debug!("Missing Address: {:?}", addr);
return Err(ConsensusError::InvalidBalMissingAccount);
}
}
// Extra accounts (body has accounts not in expected)
for addr in &body_addrs {
if !expected_addrs.contains(addr) {
tracing::debug!("Extra acc : computed bal {:?},body bal{:?}", expected_bal, body_bal);
tracing::debug!("Extra Address: {:?}", addr);
return Err(ConsensusError::InvalidBalExtraAccount);
}
}
tracing::debug!(
?expected_bal,
?body_bal,
"block access list in body does not match the provided block access list"
);
// Fallback: mismatched access lists
Err(ConsensusError::InvalidBlockAccessList)
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -17,9 +17,7 @@ pub use payload::{payload_id, BlobSidecars, EthBuiltPayload, EthPayloadBuilderAt
mod error;
pub use error::*;
use alloy_rpc_types_engine::{
ExecutionData, ExecutionPayload, ExecutionPayloadEnvelopeV5, ExecutionPayloadEnvelopeV6,
};
use alloy_rpc_types_engine::{ExecutionData, ExecutionPayload, ExecutionPayloadEnvelopeV5};
pub use alloy_rpc_types_engine::{
ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4,
ExecutionPayloadV1, PayloadAttributes as EthPayloadAttributes,
@@ -68,15 +66,13 @@ where
+ TryInto<ExecutionPayloadEnvelopeV2>
+ TryInto<ExecutionPayloadEnvelopeV3>
+ TryInto<ExecutionPayloadEnvelopeV4>
+ TryInto<ExecutionPayloadEnvelopeV5>
+ TryInto<ExecutionPayloadEnvelopeV6>,
+ TryInto<ExecutionPayloadEnvelopeV5>,
{
type ExecutionPayloadEnvelopeV1 = ExecutionPayloadV1;
type ExecutionPayloadEnvelopeV2 = ExecutionPayloadEnvelopeV2;
type ExecutionPayloadEnvelopeV3 = ExecutionPayloadEnvelopeV3;
type ExecutionPayloadEnvelopeV4 = ExecutionPayloadEnvelopeV4;
type ExecutionPayloadEnvelopeV5 = ExecutionPayloadEnvelopeV5;
type ExecutionPayloadEnvelopeV6 = ExecutionPayloadEnvelopeV6;
}
/// A default payload type for [`EthEngineTypes`]

View File

@@ -11,9 +11,8 @@ use alloy_primitives::{Address, B256, U256};
use alloy_rlp::Encodable;
use alloy_rpc_types_engine::{
BlobsBundleV1, BlobsBundleV2, ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3,
ExecutionPayloadEnvelopeV4, ExecutionPayloadEnvelopeV5, ExecutionPayloadEnvelopeV6,
ExecutionPayloadFieldV2, ExecutionPayloadV1, ExecutionPayloadV3, ExecutionPayloadV4,
PayloadAttributes, PayloadId,
ExecutionPayloadEnvelopeV4, ExecutionPayloadEnvelopeV5, ExecutionPayloadFieldV2,
ExecutionPayloadV1, ExecutionPayloadV3, PayloadAttributes, PayloadId,
};
use core::convert::Infallible;
use reth_ethereum_primitives::EthPrimitives;
@@ -161,38 +160,6 @@ impl EthBuiltPayload {
execution_requests: requests.unwrap_or_default(),
})
}
/// Try converting built payload into [`ExecutionPayloadEnvelopeV6`].
pub fn try_into_v6(self) -> Result<ExecutionPayloadEnvelopeV6, BuiltPayloadConversionError> {
let Self { block, fees, sidecars, requests, .. } = self;
let blobs_bundle = match sidecars {
BlobSidecars::Empty => BlobsBundleV2::empty(),
BlobSidecars::Eip7594(sidecars) => BlobsBundleV2::from(sidecars),
BlobSidecars::Eip4844(_) => {
return Err(BuiltPayloadConversionError::UnexpectedEip4844Sidecars)
}
};
Ok(ExecutionPayloadEnvelopeV6 {
execution_payload: ExecutionPayloadV4::from_block_unchecked(
block.hash(),
&Arc::unwrap_or_clone(block).into_block(),
),
block_value: fees,
// From the engine API spec:
//
// > Client software **MAY** use any heuristics to decide whether to set
// `shouldOverrideBuilder` flag or not. If client software does not implement any
// heuristic this flag **SHOULD** be set to `false`.
//
// Spec:
// <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#specification-2>
should_override_builder: false,
blobs_bundle,
execution_requests: requests.unwrap_or_default(),
})
}
}
impl<N: NodePrimitives> BuiltPayload for EthBuiltPayload<N> {
@@ -260,14 +227,6 @@ impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV5 {
}
}
impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV6 {
type Error = BuiltPayloadConversionError;
fn try_from(value: EthBuiltPayload) -> Result<Self, Self::Error> {
value.try_into_v6()
}
}
/// An enum representing blob transaction sidecars belonging to [`EthBuiltPayload`].
#[derive(Clone, Default, Debug)]
pub enum BlobSidecars {
@@ -503,7 +462,6 @@ mod tests {
.unwrap(),
withdrawals: None,
parent_beacon_block_root: None,
slot_number: None,
};
// Verify that the generated payload ID matches the expected value
@@ -541,7 +499,6 @@ mod tests {
},
]),
parent_beacon_block_root: None,
slot_number: None,
};
// Verify that the generated payload ID matches the expected value
@@ -574,7 +531,6 @@ mod tests {
)
.unwrap(),
),
slot_number: None,
};
// Verify that the generated payload ID matches the expected value

View File

@@ -27,7 +27,6 @@ alloy-eips.workspace = true
alloy-evm.workspace = true
alloy-consensus.workspace = true
alloy-rpc-types-engine.workspace = true
alloy-rlp.workspace = true
# Misc
parking_lot = { workspace = true, optional = true }
@@ -58,7 +57,6 @@ std = [
"derive_more?/std",
"alloy-rpc-types-engine/std",
"reth-storage-errors/std",
"alloy-rlp/std",
]
test-utils = [
"dep:parking_lot",

View File

@@ -45,8 +45,7 @@ where
execution_ctx: ctx,
parent,
transactions,
output:
BlockExecutionResult { receipts, requests, gas_used, blob_gas_used, block_access_list },
output: BlockExecutionResult { receipts, requests, gas_used, blob_gas_used },
state_root,
..
} = input;
@@ -91,18 +90,6 @@ where
};
}
let (built_block_access_list, block_access_list_hash) =
if self.chain_spec.is_amsterdam_active_at_timestamp(timestamp) {
if let Some(bal) = block_access_list {
let hash = alloy_primitives::keccak256(alloy_rlp::encode(bal));
(Some(bal), Some(hash))
} else {
(None, None)
}
} else {
(None, None)
};
let header = Header {
parent_hash: ctx.parent_hash,
ommers_hash: EMPTY_OMMER_ROOT_HASH,
@@ -125,17 +112,11 @@ where
blob_gas_used: block_blob_gas_used,
excess_blob_gas,
requests_hash,
block_access_list_hash,
};
Ok(Block {
header,
body: BlockBody {
transactions,
ommers: Default::default(),
withdrawals,
block_access_list: built_block_access_list.cloned(),
},
body: BlockBody { transactions, ommers: Default::default(), withdrawals },
})
}
}

View File

@@ -238,9 +238,8 @@ where
revm_spec_by_timestamp_and_block_number(self.chain_spec(), timestamp, block_number);
// configure evm env based on parent block
let mut cfg_env = CfgEnv::new()
.with_chain_id(self.chain_spec().chain().id())
.with_spec_and_mainnet_gas_params(spec);
let mut cfg_env =
CfgEnv::new().with_chain_id(self.chain_spec().chain().id()).with_spec(spec);
if let Some(blob_params) = &blob_params {
cfg_env.set_max_blobs_per_tx(blob_params.max_blobs_per_tx);
@@ -408,7 +407,7 @@ mod tests {
let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
let evm_env = EvmEnv {
cfg_env: CfgEnv::new().with_spec_and_mainnet_gas_params(SpecId::CONSTANTINOPLE),
cfg_env: CfgEnv::new().with_spec(SpecId::CONSTANTINOPLE),
..Default::default()
};
@@ -475,7 +474,7 @@ mod tests {
let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
let evm_env = EvmEnv {
cfg_env: CfgEnv::new().with_spec_and_mainnet_gas_params(SpecId::CONSTANTINOPLE),
cfg_env: CfgEnv::new().with_spec(SpecId::CONSTANTINOPLE),
..Default::default()
};

View File

@@ -2,7 +2,7 @@ use crate::EthEvmConfig;
use alloc::{boxed::Box, sync::Arc, vec, vec::Vec};
use alloy_consensus::Header;
use alloy_eips::eip7685::Requests;
use alloy_evm::{block::StateDB, precompiles::PrecompilesMap};
use alloy_evm::precompiles::PrecompilesMap;
use alloy_primitives::Bytes;
use alloy_rpc_types_engine::ExecutionData;
use parking_lot::Mutex;
@@ -19,6 +19,7 @@ use reth_execution_types::{BlockExecutionResult, ExecutionOutcome};
use reth_primitives_traits::{BlockTy, SealedBlock, SealedHeader};
use revm::{
context::result::{ExecutionResult, Output, ResultAndState, SuccessReason},
database::State,
Inspector,
};
@@ -57,12 +58,12 @@ impl BlockExecutorFactory for MockEvmConfig {
fn create_executor<'a, DB, I>(
&'a self,
evm: EthEvm<DB, I, PrecompilesMap>,
evm: EthEvm<&'a mut State<DB>, I, PrecompilesMap>,
_ctx: Self::ExecutionCtx<'a>,
) -> impl BlockExecutorFor<'a, Self, DB, I>
where
DB: StateDB + Database + 'a,
I: Inspector<<Self::EvmFactory as EvmFactory>::Context<DB>> + 'a,
DB: Database + 'a,
I: Inspector<<Self::EvmFactory as EvmFactory>::Context<&'a mut State<DB>>> + 'a,
{
MockExecutor { result: self.exec_results.lock().pop().unwrap(), evm, hook: None }
}
@@ -70,17 +71,17 @@ impl BlockExecutorFactory for MockEvmConfig {
/// Mock executor that returns a fixed execution result.
#[derive(derive_more::Debug)]
pub struct MockExecutor<DB: Database, I> {
pub struct MockExecutor<'a, DB: Database, I> {
result: ExecutionOutcome,
evm: EthEvm<DB, I, PrecompilesMap>,
evm: EthEvm<&'a mut State<DB>, I, PrecompilesMap>,
#[debug(skip)]
hook: Option<Box<dyn reth_evm::OnStateHook>>,
}
impl<DB: StateDB + Database, I: Inspector<EthEvmContext<DB>>> BlockExecutor
for MockExecutor<DB, I>
impl<'a, DB: Database, I: Inspector<EthEvmContext<&'a mut State<DB>>>> BlockExecutor
for MockExecutor<'a, DB, I>
{
type Evm = EthEvm<DB, I, PrecompilesMap>;
type Evm = EthEvm<&'a mut State<DB>, I, PrecompilesMap>;
type Transaction = TransactionSigned;
type Receipt = Receipt;
@@ -124,11 +125,10 @@ impl<DB: StateDB + Database, I: Inspector<EthEvmContext<DB>>> BlockExecutor
reqs
}),
gas_used: 0,
block_access_list: None,
blob_gas_used: 0,
};
*evm.db_mut().bundle_state_mut() = bundle;
evm.db_mut().bundle_state = bundle;
Ok((evm, result))
}

View File

@@ -38,7 +38,6 @@ fn create_database_with_beacon_root_contract() -> CacheDB<EmptyDB> {
code_hash: keccak256(BEACON_ROOTS_CODE.clone()),
nonce: 1,
code: Some(Bytecode::new_raw(BEACON_ROOTS_CODE.clone())),
account_id: None,
};
db.insert_account_info(BEACON_ROOTS_ADDRESS, beacon_root_contract_account);
@@ -54,7 +53,6 @@ fn create_database_with_withdrawal_requests_contract() -> CacheDB<EmptyDB> {
balance: U256::ZERO,
code_hash: keccak256(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone()),
code: Some(Bytecode::new_raw(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone())),
account_id: None,
};
db.insert_account_info(
@@ -88,12 +86,7 @@ fn eip_4788_non_genesis_call() {
.execute_one(&RecoveredBlock::new_unhashed(
Block {
header: header.clone(),
body: BlockBody {
transactions: vec![],
ommers: vec![],
withdrawals: None,
block_access_list: None,
},
body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None },
},
vec![],
))
@@ -112,12 +105,7 @@ fn eip_4788_non_genesis_call() {
.execute_one(&RecoveredBlock::new_unhashed(
Block {
header: header.clone(),
body: BlockBody {
transactions: vec![],
ommers: vec![],
withdrawals: None,
block_access_list: None,
},
body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None },
},
vec![],
))
@@ -177,12 +165,7 @@ fn eip_4788_no_code_cancun() {
.execute_one(&RecoveredBlock::new_unhashed(
Block {
header,
body: BlockBody {
transactions: vec![],
ommers: vec![],
withdrawals: None,
block_access_list: None,
},
body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None },
},
vec![],
))
@@ -224,12 +207,7 @@ fn eip_4788_empty_account_call() {
.execute_one(&RecoveredBlock::new_unhashed(
Block {
header,
body: BlockBody {
transactions: vec![],
ommers: vec![],
withdrawals: None,
block_access_list: None,
},
body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None },
},
vec![],
))
@@ -361,7 +339,6 @@ fn create_database_with_block_hashes(latest_block: u64) -> CacheDB<EmptyDB> {
code_hash: keccak256(HISTORY_STORAGE_CODE.clone()),
code: Some(Bytecode::new_raw(HISTORY_STORAGE_CODE.clone())),
nonce: 1,
account_id: None,
};
db.insert_account_info(HISTORY_STORAGE_ADDRESS, blockhashes_contract_account);
@@ -819,7 +796,6 @@ fn test_balance_increment_not_duplicated() {
transactions: vec![],
ommers: vec![],
withdrawals: Some(vec![withdrawal].into()),
block_access_list: None,
},
},
vec![],

View File

@@ -303,8 +303,6 @@ where
let eth_config =
EthConfigHandler::new(ctx.node.provider().clone(), ctx.node.evm_config().clone());
let testing_skip_invalid_transactions = ctx.config.rpc.testing_skip_invalid_transactions;
self.inner
.launch_add_ons_with(ctx, move |container| {
container.modules.merge_if_module_configured(
@@ -318,16 +316,14 @@ where
// testing_buildBlockV1: only wire when the hidden testing module is explicitly
// requested on any transport. Default stays disabled to honor security guidance.
let mut testing_api = TestingApi::new(
let testing_api = TestingApi::new(
container.registry.eth_api().clone(),
container.registry.evm_config().clone(),
);
if testing_skip_invalid_transactions {
testing_api = testing_api.with_skip_invalid_transactions();
}
)
.into_rpc();
container
.modules
.merge_if_module_configured(RethRpcModule::Testing, testing_api.into_rpc())?;
.merge_if_module_configured(RethRpcModule::Testing, testing_api)?;
Ok(())
})

View File

@@ -223,7 +223,6 @@ async fn test_testing_build_block_v1_osaka() -> eyre::Result<()> {
suggested_fee_recipient: Address::ZERO,
withdrawals: Some(vec![]),
parent_beacon_block_root: Some(B256::ZERO),
slot_number: None,
};
let request = TestingBuildBlockRequestV1 {

View File

@@ -25,7 +25,6 @@ pub(crate) fn eth_payload_attributes(timestamp: u64) -> EthPayloadBuilderAttribu
suggested_fee_recipient: Address::ZERO,
withdrawals: Some(vec![]),
parent_beacon_block_root: Some(B256::ZERO),
slot_number: None,
};
EthPayloadBuilderAttributes::new(B256::ZERO, attributes)
}

View File

@@ -56,7 +56,6 @@ async fn testing_rpc_build_block_works() -> eyre::Result<()> {
suggested_fee_recipient: Address::ZERO,
withdrawals: None,
parent_beacon_block_root: None,
slot_number: None,
};
let request = TestingBuildBlockRequestV1 {

View File

@@ -153,14 +153,10 @@ where
let PayloadConfig { parent_header, attributes } = config;
let state_provider = client.state_by_block_hash(parent_header.hash())?;
let state = StateProviderDatabase::new(&state_provider);
let mut db = State::builder()
.with_database(cached_reads.as_db_mut(state))
.with_bundle_update()
.with_bal_builder()
.build();
db.bal_state.bal_index = 0;
db.bal_state.bal_builder = Some(revm::state::bal::Bal::new());
let state = StateProviderDatabase::new(state_provider.as_ref());
let mut db =
State::builder().with_database_ref(cached_reads.as_db(state)).with_bundle_update().build();
let mut builder = evm_config
.builder_for_next_block(
&mut db,

View File

@@ -3,9 +3,10 @@
use alloy_consensus::Block;
use alloy_rpc_types_engine::{ExecutionData, PayloadError};
use reth_chainspec::EthereumHardforks;
use reth_payload_validator::{amsterdam, cancun, prague, shanghai};
use reth_payload_validator::{cancun, prague, shanghai};
use reth_primitives_traits::{Block as _, SealedBlock, SignedTransaction};
use std::sync::Arc;
use tracing::info;
/// Execution payload validator.
#[derive(Clone, Debug)]
@@ -78,6 +79,15 @@ where
// First parse the block
let sealed_block = payload.try_into_block_with_sidecar(&sidecar)?.seal_slow();
info!(
target: "engine::payload_validator",
header = ?sealed_block.header(),
computed_hash = ?sealed_block.hash(),
expected_hash = ?expected_hash,
?sidecar,
"Validating payload block hash"
);
// Ensure the hash included in the payload matches the block hash
if expected_hash != sealed_block.hash() {
return Err(PayloadError::BlockHash {
@@ -103,10 +113,5 @@ where
chain_spec.is_prague_active_at_timestamp(sealed_block.timestamp),
)?;
amsterdam::ensure_well_formed_fields(
sealed_block.body(),
chain_spec.is_amsterdam_active_at_timestamp(sealed_block.timestamp),
)?;
Ok(sealed_block)
}

View File

@@ -1,12 +1,12 @@
//! Traits for execution.
use crate::{ConfigureEvm, Database, OnStateHook, TxEnvFor};
use alloc::{borrow::Cow, boxed::Box, sync::Arc, vec::Vec};
use alloc::{boxed::Box, sync::Arc, vec::Vec};
use alloy_consensus::{BlockHeader, Header};
use alloy_eips::eip2718::WithEncoded;
pub use alloy_evm::block::{BlockExecutor, BlockExecutorFactory};
use alloy_evm::{
block::{CommitChanges, ExecutableTx, StateDB},
block::{CommitChanges, ExecutableTx},
Evm, EvmEnv, EvmFactory, RecoveredTx, ToTxEnv,
};
use alloy_primitives::{Address, B256};
@@ -214,7 +214,7 @@ pub struct BlockAssemblerInput<'a, 'b, F: BlockExecutorFactory, H = Header> {
/// Output of block execution.
pub output: &'b BlockExecutionResult<F::Receipt>,
/// [`BundleState`] after the block execution.
pub bundle_state: Cow<'a, BundleState>,
pub bundle_state: &'a BundleState,
/// Provider with access to state.
#[debug(skip)]
pub state_provider: &'b dyn StateProvider,
@@ -234,7 +234,7 @@ impl<'a, 'b, F: BlockExecutorFactory, H> BlockAssemblerInput<'a, 'b, F, H> {
parent: &'a SealedHeader<H>,
transactions: Vec<F::Transaction>,
output: &'b BlockExecutionResult<F::Receipt>,
bundle_state: impl Into<Cow<'a, BundleState>>,
bundle_state: &'a BundleState,
state_provider: &'b dyn StateProvider,
state_root: B256,
) -> Self {
@@ -244,7 +244,7 @@ impl<'a, 'b, F: BlockExecutorFactory, H> BlockAssemblerInput<'a, 'b, F, H> {
parent,
transactions,
output,
bundle_state: bundle_state.into(),
bundle_state,
state_provider,
state_root,
}
@@ -461,7 +461,8 @@ where
}
}
impl<'a, F, Executor, Builder, N> BlockBuilder for BasicBlockBuilder<'a, F, Executor, Builder, N>
impl<'a, F, DB, Executor, Builder, N> BlockBuilder
for BasicBlockBuilder<'a, F, Executor, Builder, N>
where
F: BlockExecutorFactory<Transaction = N::SignedTx, Receipt = N::Receipt>,
Executor: BlockExecutor<
@@ -469,11 +470,12 @@ where
Spec = <F::EvmFactory as EvmFactory>::Spec,
HaltReason = <F::EvmFactory as EvmFactory>::HaltReason,
BlockEnv = <F::EvmFactory as EvmFactory>::BlockEnv,
DB: StateDB + 'a,
DB = &'a mut State<DB>,
>,
Transaction = N::SignedTx,
Receipt = N::Receipt,
>,
DB: Database + 'a,
Builder: BlockAssembler<F, Block = N::Block>,
N: NodePrimitives,
{
@@ -506,13 +508,13 @@ where
state: impl StateProvider,
) -> Result<BlockBuilderOutcome<N>, BlockExecutionError> {
let (evm, result) = self.executor.finish()?;
let (mut db, evm_env) = evm.finish();
let (db, evm_env) = evm.finish();
// merge all transitions into bundle state
db.merge_transitions(BundleRetention::Reverts);
// calculate the state root
let hashed_state = state.hashed_post_state(db.bundle_state());
let hashed_state = state.hashed_post_state(&db.bundle_state);
let (state_root, trie_updates) = state
.state_root_with_updates(hashed_state.clone())
.map_err(BlockExecutionError::other)?;
@@ -526,7 +528,7 @@ where
parent: self.parent,
transactions,
output: &result,
bundle_state: Cow::Owned(db.take_bundle()),
bundle_state: &db.bundle_state,
state_provider: &state,
state_root,
})?;
@@ -562,14 +564,8 @@ pub struct BasicBlockExecutor<F, DB> {
impl<F, DB: Database> BasicBlockExecutor<F, DB> {
/// Creates a new `BasicBlockExecutor` with the given strategy.
pub fn new(strategy_factory: F, db: DB) -> Self {
let mut db = State::builder()
.with_database(db)
.with_bundle_update()
.with_bal_builder()
.without_state_clear()
.build();
db.bal_state.bal_index = 0;
db.bal_state.bal_builder = Some(revm::state::bal::Bal::new());
let db =
State::builder().with_database(db).with_bundle_update().without_state_clear().build();
Self { strategy_factory, db }
}
}
@@ -745,7 +741,6 @@ mod tests {
nonce,
code_hash: KECCAK_EMPTY,
code: None,
account_id: None,
};
state.insert_account(addr, account_info);
state
@@ -782,13 +777,8 @@ mod tests {
let mut state = setup_state_with_account(addr1, 100, 1);
let account2 = AccountInfo {
balance: U256::from(200),
nonce: 1,
code_hash: KECCAK_EMPTY,
code: None,
account_id: None,
};
let account2 =
AccountInfo { balance: U256::from(200), nonce: 1, code_hash: KECCAK_EMPTY, code: None };
state.insert_account(addr2, account2);
let mut increments = HashMap::default();
@@ -809,13 +799,8 @@ mod tests {
let mut state = setup_state_with_account(addr1, 100, 1);
let account2 = AccountInfo {
balance: U256::from(200),
nonce: 1,
code_hash: KECCAK_EMPTY,
code: None,
account_id: None,
};
let account2 =
AccountInfo { balance: U256::from(200), nonce: 1, code_hash: KECCAK_EMPTY, code: None };
state.insert_account(addr2, account2);
let mut increments = HashMap::default();

View File

@@ -18,7 +18,6 @@
extern crate alloc;
use crate::execute::{BasicBlockBuilder, Executor};
use ::revm::context::TxEnv;
use alloc::vec::Vec;
use alloy_eips::{
eip2718::{EIP2930_TX_TYPE_ID, LEGACY_TX_TYPE_ID},
@@ -26,7 +25,7 @@ use alloy_eips::{
eip4895::Withdrawals,
};
use alloy_evm::{
block::{BlockExecutorFactory, BlockExecutorFor, StateDB},
block::{BlockExecutorFactory, BlockExecutorFor},
precompiles::PrecompilesMap,
};
use alloy_primitives::{Address, Bytes, B256};
@@ -36,7 +35,7 @@ use reth_execution_errors::BlockExecutionError;
use reth_primitives_traits::{
BlockTy, HeaderTy, NodePrimitives, ReceiptTy, SealedBlock, SealedHeader, TxTy,
};
use revm::DatabaseCommit;
use revm::{context::TxEnv, database::State};
pub mod either;
/// EVM environment configuration.
@@ -313,20 +312,20 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
/// Creates a strategy with given EVM and execution context.
fn create_executor<'a, DB, I>(
&'a self,
evm: EvmFor<Self, DB, I>,
evm: EvmFor<Self, &'a mut State<DB>, I>,
ctx: <Self::BlockExecutorFactory as BlockExecutorFactory>::ExecutionCtx<'a>,
) -> impl BlockExecutorFor<'a, Self::BlockExecutorFactory, DB, I>
where
DB: StateDB + DatabaseCommit + Database + 'a,
I: InspectorFor<Self, DB> + 'a,
DB: Database,
I: InspectorFor<Self, &'a mut State<DB>> + 'a,
{
self.block_executor_factory().create_executor(evm, ctx)
}
/// Creates a strategy for execution of a given block.
fn executor_for_block<'a, DB: StateDB + DatabaseCommit + Database + 'a>(
fn executor_for_block<'a, DB: Database>(
&'a self,
db: DB,
db: &'a mut State<DB>,
block: &'a SealedBlock<<Self::Primitives as NodePrimitives>::Block>,
) -> Result<impl BlockExecutorFor<'a, Self::BlockExecutorFactory, DB>, Self::Error> {
let evm = self.evm_for_block(db, block.header())?;
@@ -351,7 +350,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
/// ```
fn create_block_builder<'a, DB, I>(
&'a self,
evm: EvmFor<Self, DB, I>,
evm: EvmFor<Self, &'a mut State<DB>, I>,
parent: &'a SealedHeader<HeaderTy<Self::Primitives>>,
ctx: <Self::BlockExecutorFactory as BlockExecutorFactory>::ExecutionCtx<'a>,
) -> impl BlockBuilder<
@@ -359,8 +358,8 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
Executor: BlockExecutorFor<'a, Self::BlockExecutorFactory, DB, I>,
>
where
DB: StateDB + DatabaseCommit + Database + 'a,
I: InspectorFor<Self, DB> + 'a,
DB: Database,
I: InspectorFor<Self, &'a mut State<DB>> + 'a,
{
BasicBlockBuilder {
executor: self.create_executor(evm, ctx.clone()),
@@ -400,9 +399,9 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
/// // Complete block building
/// let outcome = builder.finish(state_provider)?;
/// ```
fn builder_for_next_block<'a, DB: StateDB + DatabaseCommit + Database + 'a>(
fn builder_for_next_block<'a, DB: Database>(
&'a self,
db: DB,
db: &'a mut State<DB>,
parent: &'a SealedHeader<<Self::Primitives as NodePrimitives>::BlockHeader>,
attributes: Self::NextBlockEnvCtx,
) -> Result<

View File

@@ -934,20 +934,10 @@ mod tests {
let address3 = Address::random();
// Set up account info with some changes
let account_info1 = AccountInfo {
nonce: 1,
balance: U256::from(100),
code_hash: B256::ZERO,
code: None,
account_id: None,
};
let account_info2 = AccountInfo {
nonce: 2,
balance: U256::from(200),
code_hash: B256::ZERO,
code: None,
account_id: None,
};
let account_info1 =
AccountInfo { nonce: 1, balance: U256::from(100), code_hash: B256::ZERO, code: None };
let account_info2 =
AccountInfo { nonce: 2, balance: U256::from(200), code_hash: B256::ZERO, code: None };
// Set up the bundle state with these accounts
let mut bundle_state = BundleState::default();

View File

@@ -195,22 +195,22 @@ mod tests {
// wal with 1 block and tx (old 3-field format)
// <https://github.com/paradigmxyz/reth/issues/15012>
// #[test]
// fn decode_notification_wal() {
// let wal = include_bytes!("../../test-data/28.wal");
// let notification: reth_exex_types::serde_bincode_compat::ExExNotification<
// '_,
// reth_ethereum_primitives::EthPrimitives,
// > = rmp_serde::decode::from_slice(wal.as_slice()).unwrap();
// let notification: ExExNotification = notification.into();
// match notification {
// ExExNotification::ChainCommitted { new } => {
// assert_eq!(new.blocks().len(), 1);
// assert_eq!(new.tip().transaction_count(), 1);
// }
// _ => panic!("unexpected notification"),
// }
// }
#[test]
fn decode_notification_wal() {
let wal = include_bytes!("../../test-data/28.wal");
let notification: reth_exex_types::serde_bincode_compat::ExExNotification<
'_,
reth_ethereum_primitives::EthPrimitives,
> = rmp_serde::decode::from_slice(wal.as_slice()).unwrap();
let notification: ExExNotification = notification.into();
match notification {
ExExNotification::ChainCommitted { new } => {
assert_eq!(new.blocks().len(), 1);
assert_eq!(new.tip().transaction_count(), 1);
}
_ => panic!("unexpected notification"),
}
}
// wal with 1 block and tx (new 4-field format with trie updates and hashed state)
#[test]

View File

@@ -256,10 +256,6 @@ impl<B: FullBlock<Header: reth_primitives_traits::BlockHeader>> FromReader
Err(err) => return Err(err),
};
tracing::debug!(target: "downloaders::file",
block=?block,
"decoded block from file chunk"
);
let block = SealedBlock::seal_slow(block);
// Validate standalone header
@@ -276,11 +272,6 @@ impl<B: FullBlock<Header: reth_primitives_traits::BlockHeader>> FromReader
let block_hash = block.hash();
let block_number = block.number();
let (header, body) = block.split_sealed_header_body();
tracing::debug!(target: "downloaders::file",
header=?header,
body=?body,
"adding block to file client buffers"
);
headers.insert(block_number, header.unseal());
hash_to_number.insert(block_hash, block_number);
bodies.insert(block_hash, body);

View File

@@ -265,7 +265,6 @@ mod tests {
excess_blob_gas: None,
parent_beacon_block_root: None,
requests_hash: None,
block_access_list_hash:None
},
]),
}.encode(&mut data);
@@ -303,7 +302,6 @@ mod tests {
excess_blob_gas: None,
parent_beacon_block_root: None,
requests_hash: None,
block_access_list_hash: None
},
]),
};
@@ -410,11 +408,9 @@ mod tests {
excess_blob_gas: None,
parent_beacon_block_root: None,
requests_hash: None,
block_access_list_hash:None
},
],
withdrawals: None,
block_access_list:None
}
]),
};
@@ -489,11 +485,9 @@ mod tests {
excess_blob_gas: None,
parent_beacon_block_root: None,
requests_hash: None,
block_access_list_hash:None
},
],
withdrawals: None,
block_access_list:None
}
]),
};

View File

@@ -152,7 +152,6 @@ mod tests {
excess_blob_gas: None,
parent_beacon_block_root: None,
requests_hash: None,
block_access_list_hash:None
};
assert_eq!(header.hash_slow(), expected_hash);
}
@@ -269,7 +268,6 @@ mod tests {
excess_blob_gas: Some(0),
parent_beacon_block_root: None,
requests_hash: None,
block_access_list_hash: None,
};
let header = Header::decode(&mut data.as_slice()).unwrap();
@@ -312,7 +310,6 @@ mod tests {
blob_gas_used: Some(0),
excess_blob_gas: Some(0x1600000),
requests_hash: None,
block_access_list_hash: None,
};
let header = Header::decode(&mut data.as_slice()).unwrap();

View File

@@ -824,7 +824,6 @@ mod tests {
transactions: vec![],
ommers: vec![],
withdrawals: Some(Default::default()),
block_access_list: None,
}]
.into(),
}));

View File

@@ -290,7 +290,6 @@ impl EngineNodeLauncher {
let startup_sync_state_idle = ctx.node_config().debug.startup_sync_state_idle;
info!(target: "reth::cli", "Starting consensus engine");
info!(target: "reth::cli", "built payloads ready: {:#?}", built_payloads);
let consensus_engine = async move {
if let Some(initial_target) = initial_target {
debug!(target: "reth::cli", %initial_target, "start backfill sync");

View File

@@ -81,8 +81,7 @@ tokio.workspace = true
jemalloc = ["reth-cli-util/jemalloc"]
asm-keccak = ["alloy-primitives/asm-keccak"]
keccak-cache-global = ["alloy-primitives/keccak-cache-global"]
otlp = ["reth-tracing/otlp", "reth-tracing-otlp/otlp"]
otlp-logs = ["reth-tracing/otlp-logs", "reth-tracing-otlp/otlp-logs"]
otlp = ["reth-tracing/otlp"]
tracy = ["reth-tracing/tracy"]
min-error-logs = ["tracing/release_max_level_error"]

View File

@@ -24,7 +24,7 @@ pub struct DefaultEngineValues {
prewarming_disabled: bool,
parallel_sparse_trie_disabled: bool,
state_provider_metrics: bool,
cross_block_cache_size: u64,
cross_block_cache_size: usize,
state_root_task_compare_updates: bool,
accept_execution_requests_hash: bool,
multiproof_chunking_enabled: bool,
@@ -93,7 +93,7 @@ impl DefaultEngineValues {
}
/// Set the default cross-block cache size in MB
pub const fn with_cross_block_cache_size(mut self, v: u64) -> Self {
pub const fn with_cross_block_cache_size(mut self, v: usize) -> Self {
self.cross_block_cache_size = v;
self
}
@@ -254,7 +254,7 @@ pub struct EngineArgs {
/// Configure the size of cross-block cache in megabytes
#[arg(long = "engine.cross-block-cache-size", default_value_t = DefaultEngineValues::get_global().cross_block_cache_size)]
pub cross_block_cache_size: u64,
pub cross_block_cache_size: usize,
/// Enable comparing trie updates from the state root task to the trie updates from the regular
/// state root calculation.

View File

@@ -26,7 +26,7 @@ pub use log::{ColorMode, LogArgs, Verbosity};
/// `TraceArgs` for tracing and spans support
mod trace;
pub use trace::{OtlpInitStatus, OtlpLogsStatus, TraceArgs};
pub use trace::{OtlpInitStatus, TraceArgs};
/// `MetricArgs` to configure metrics.
mod metric;

View File

@@ -640,13 +640,6 @@ pub struct RpcServerArgs {
value_parser = parse_duration_from_secs_or_ms,
)]
pub rpc_send_raw_transaction_sync_timeout: Duration,
/// Skip invalid transactions in `testing_buildBlockV1` instead of failing.
///
/// When enabled, transactions that fail execution will be skipped, and all subsequent
/// transactions from the same sender will also be skipped.
#[arg(long = "testing.skip-invalid-transactions", default_value_t = false)]
pub testing_skip_invalid_transactions: bool,
}
impl RpcServerArgs {
@@ -859,7 +852,6 @@ impl Default for RpcServerArgs {
rpc_state_cache,
gas_price_oracle,
rpc_send_raw_transaction_sync_timeout,
testing_skip_invalid_transactions: false,
}
}
}
@@ -1034,7 +1026,6 @@ mod tests {
default_suggested_fee: None,
},
rpc_send_raw_transaction_sync_timeout: std::time::Duration::from_secs(30),
testing_skip_invalid_transactions: true,
};
let parsed_args = CommandParser::<RpcServerArgs>::parse_from([
@@ -1123,7 +1114,6 @@ mod tests {
"60",
"--rpc.send-raw-transaction-sync-timeout",
"30s",
"--testing.skip-invalid-transactions",
])
.args;

View File

@@ -1,4 +1,4 @@
//! Opentelemetry tracing and logging configuration through CLI args.
//! Opentelemetry tracing configuration through CLI args.
use clap::Parser;
use eyre::WrapErr;
@@ -6,7 +6,7 @@ use reth_tracing::{tracing_subscriber::EnvFilter, Layers};
use reth_tracing_otlp::OtlpProtocol;
use url::Url;
/// CLI arguments for configuring `Opentelemetry` trace and logs export.
/// CLI arguments for configuring `Opentelemetry` trace and span export.
#[derive(Debug, Clone, Parser)]
pub struct TraceArgs {
/// Enable `Opentelemetry` tracing export to an OTLP endpoint.
@@ -30,29 +30,9 @@ pub struct TraceArgs {
)]
pub otlp: Option<Url>,
/// Enable `Opentelemetry` logs export to an OTLP endpoint.
/// OTLP transport protocol to use for exporting traces.
///
/// If no value provided, defaults based on protocol:
/// - HTTP: `http://localhost:4318/v1/logs`
/// - gRPC: `http://localhost:4317`
///
/// Example: --logs-otlp=http://collector:4318/v1/logs
#[arg(
long = "logs-otlp",
env = "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT",
global = true,
value_name = "URL",
num_args = 0..=1,
default_missing_value = "http://localhost:4318/v1/logs",
require_equals = true,
value_parser = parse_otlp_endpoint,
help_heading = "Logging"
)]
pub logs_otlp: Option<Url>,
/// OTLP transport protocol to use for exporting traces and logs.
///
/// - `http`: expects endpoint path to end with `/v1/traces` or `/v1/logs`
/// - `http`: expects endpoint path to end with `/v1/traces`
/// - `grpc`: expects endpoint without a path
///
/// Defaults to HTTP if not specified.
@@ -82,22 +62,6 @@ pub struct TraceArgs {
)]
pub otlp_filter: EnvFilter,
/// Set a filter directive for the OTLP logs exporter. This controls the verbosity
/// of logs sent to the OTLP endpoint. It follows the same syntax as the
/// `RUST_LOG` environment variable.
///
/// Example: --logs-otlp.filter=info,reth=debug
///
/// Defaults to INFO if not specified.
#[arg(
long = "logs-otlp.filter",
global = true,
value_name = "FILTER",
default_value = "info",
help_heading = "Logging"
)]
pub logs_otlp_filter: EnvFilter,
/// Service name to use for OTLP tracing export.
///
/// This name will be used to identify the service in distributed tracing systems
@@ -137,10 +101,8 @@ impl Default for TraceArgs {
fn default() -> Self {
Self {
otlp: None,
logs_otlp: None,
protocol: OtlpProtocol::Http,
otlp_filter: EnvFilter::from_default_env(),
logs_otlp_filter: EnvFilter::try_new("info").expect("valid filter"),
sample_ratio: None,
service_name: "reth".to_string(),
}
@@ -188,37 +150,6 @@ impl TraceArgs {
Ok(OtlpInitStatus::Disabled)
}
}
/// Initialize OTLP logs export with the given layers.
///
/// This method handles OTLP logs initialization based on the configured options,
/// including validation and protocol selection.
///
/// Returns the initialization status to allow callers to log appropriate messages.
pub async fn init_otlp_logs(&mut self, _layers: &mut Layers) -> eyre::Result<OtlpLogsStatus> {
if let Some(endpoint) = self.logs_otlp.as_mut() {
self.protocol.validate_logs_endpoint(endpoint)?;
#[cfg(feature = "otlp-logs")]
{
let config = reth_tracing_otlp::OtlpLogsConfig::new(
self.service_name.clone(),
endpoint.clone(),
self.protocol,
)?;
_layers.with_log_layer(config.clone(), self.logs_otlp_filter.clone())?;
Ok(OtlpLogsStatus::Started(config.endpoint().clone()))
}
#[cfg(not(feature = "otlp-logs"))]
{
Ok(OtlpLogsStatus::NoFeature)
}
} else {
Ok(OtlpLogsStatus::Disabled)
}
}
}
/// Status of OTLP tracing initialization.
@@ -232,17 +163,6 @@ pub enum OtlpInitStatus {
NoFeature,
}
/// Status of OTLP logs initialization.
#[derive(Debug)]
pub enum OtlpLogsStatus {
/// OTLP logs export was successfully started with the given endpoint.
Started(Url),
/// OTLP logs export is disabled (no endpoint configured).
Disabled,
/// OTLP logs arguments provided but feature is not compiled.
NoFeature,
}
// Parses an OTLP endpoint url.
fn parse_otlp_endpoint(arg: &str) -> eyre::Result<Url> {
Url::parse(arg).wrap_err("Invalid URL for OTLP trace output")

View File

@@ -38,7 +38,7 @@ pub use reth_engine_primitives::{
};
/// Default size of cross-block cache in megabytes.
pub const DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB: u64 = 4 * 1024;
pub const DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB: usize = 4 * 1024;
/// This includes all necessary configuration to launch the node.
/// The individual configuration options can be overwritten before launching the node.

View File

@@ -43,7 +43,6 @@ tracy = ["reth-optimism-cli/tracy"]
asm-keccak = ["reth-optimism-cli/asm-keccak", "reth-optimism-node/asm-keccak"]
keccak-cache-global = [
"reth-optimism-cli/keccak-cache-global",
"reth-optimism-node/keccak-cache-global",
]
dev = [

Some files were not shown because too many files have changed in this diff Show More