mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-04-30 03:01:58 -04:00
Compare commits
7 Commits
yk/rocksdb
...
v1.8.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c30bf7af5 | ||
|
|
8950b4eb1e | ||
|
|
1de013b21f | ||
|
|
95897e21b8 | ||
|
|
12794769c1 | ||
|
|
611c307213 | ||
|
|
994d73edf6 |
@@ -15,12 +15,3 @@ slow-timeout = { period = "2m", terminate-after = 10 }
|
||||
[[profile.default.overrides]]
|
||||
filter = "binary(e2e_testsuite)"
|
||||
slow-timeout = { period = "2m", terminate-after = 3 }
|
||||
|
||||
[[profile.default.overrides]]
|
||||
filter = "package(reth-era) and binary(it)"
|
||||
slow-timeout = { period = "2m", terminate-after = 10 }
|
||||
|
||||
# Allow slower ethereum node e2e tests (p2p + blobs) to run up to 5 minutes.
|
||||
[[profile.default.overrides]]
|
||||
filter = "package(reth-node-ethereum) and binary(e2e)"
|
||||
slow-timeout = { period = "1m", terminate-after = 5 }
|
||||
|
||||
@@ -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,js-tracer,portable,keccak-cache-global",
|
||||
"--features=std,op,dev,asm-keccak,jemalloc,jemalloc-prof,tracy-allocator,serde-bincode-compat,serde,test-utils,arbitrary,bench,alloy-compat",
|
||||
# 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.
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
# include source files
|
||||
!/bin
|
||||
!/crates
|
||||
!/pkg
|
||||
!/testing
|
||||
!book.toml
|
||||
!Cargo.lock
|
||||
@@ -12,7 +11,6 @@
|
||||
!Cross.toml
|
||||
!deny.toml
|
||||
!Makefile
|
||||
!README.md
|
||||
|
||||
# include for vergen constants
|
||||
!/.git
|
||||
|
||||
21
.github/CODEOWNERS
vendored
21
.github/CODEOWNERS
vendored
@@ -1,12 +1,12 @@
|
||||
* @gakonst
|
||||
crates/blockchain-tree-api/ @rakita @mattsse @Rjected
|
||||
crates/blockchain-tree/ @rakita @mattsse @Rjected
|
||||
crates/chain-state/ @fgimenez @mattsse
|
||||
crates/blockchain-tree-api/ @rakita @rkrasiuk @mattsse @Rjected
|
||||
crates/blockchain-tree/ @rakita @rkrasiuk @mattsse @Rjected
|
||||
crates/chain-state/ @fgimenez @mattsse @rkrasiuk
|
||||
crates/chainspec/ @Rjected @joshieDo @mattsse
|
||||
crates/cli/ @mattsse
|
||||
crates/consensus/ @mattsse @Rjected
|
||||
crates/consensus/ @rkrasiuk @mattsse @Rjected
|
||||
crates/e2e-test-utils/ @mattsse @Rjected @klkvr @fgimenez
|
||||
crates/engine/ @mattsse @Rjected @fgimenez @mediocregopher @yongkangc
|
||||
crates/engine/ @rkrasiuk @mattsse @Rjected @fgimenez @mediocregopher @yongkangc
|
||||
crates/era/ @mattsse @RomanHodulak
|
||||
crates/errors/ @mattsse
|
||||
crates/ethereum-forks/ @mattsse @Rjected
|
||||
@@ -15,17 +15,17 @@ crates/etl/ @joshieDo @shekhirin
|
||||
crates/evm/ @rakita @mattsse @Rjected
|
||||
crates/exex/ @shekhirin
|
||||
crates/net/ @mattsse @Rjected
|
||||
crates/net/downloaders/ @Rjected
|
||||
crates/net/downloaders/ @rkrasiuk
|
||||
crates/node/ @mattsse @Rjected @klkvr
|
||||
crates/optimism/ @mattsse @Rjected @fgimenez
|
||||
crates/payload/ @mattsse @Rjected
|
||||
crates/primitives-traits/ @Rjected @RomanHodulak @mattsse @klkvr
|
||||
crates/primitives/ @Rjected @mattsse @klkvr
|
||||
crates/prune/ @shekhirin @joshieDo
|
||||
crates/ress @shekhirin @Rjected
|
||||
crates/ress @rkrasiuk
|
||||
crates/revm/ @mattsse @rakita
|
||||
crates/rpc/ @mattsse @Rjected @RomanHodulak
|
||||
crates/stages/ @shekhirin @mediocregopher
|
||||
crates/stages/ @rkrasiuk @shekhirin @mediocregopher
|
||||
crates/static-file/ @joshieDo @shekhirin
|
||||
crates/storage/codecs/ @joshieDo
|
||||
crates/storage/db-api/ @joshieDo @rakita
|
||||
@@ -35,11 +35,10 @@ crates/storage/errors/ @rakita
|
||||
crates/storage/libmdbx-rs/ @rakita @shekhirin
|
||||
crates/storage/nippy-jar/ @joshieDo @shekhirin
|
||||
crates/storage/provider/ @rakita @joshieDo @shekhirin
|
||||
crates/storage/storage-api/ @joshieDo
|
||||
crates/storage/storage-api/ @joshieDo @rkrasiuk
|
||||
crates/tasks/ @mattsse
|
||||
crates/tokio-util/ @fgimenez
|
||||
crates/transaction-pool/ @mattsse @yongkangc
|
||||
crates/trie/ @Rjected @shekhirin @mediocregopher
|
||||
bin/reth-bench-compare/ @mediocregopher @shekhirin @yongkangc
|
||||
crates/trie/ @rkrasiuk @Rjected @shekhirin @mediocregopher
|
||||
etc/ @Rjected @shekhirin
|
||||
.github/ @gakonst @DaniPopes
|
||||
|
||||
7
.github/actionlint.yaml
vendored
7
.github/actionlint.yaml
vendored
@@ -1,7 +0,0 @@
|
||||
self-hosted-runner:
|
||||
labels:
|
||||
- depot-ubuntu-latest
|
||||
- depot-ubuntu-latest-2
|
||||
- depot-ubuntu-latest-4
|
||||
- depot-ubuntu-latest-8
|
||||
- depot-ubuntu-latest-16
|
||||
2
.github/assets/check_wasm.sh
vendored
2
.github/assets/check_wasm.sh
vendored
@@ -11,7 +11,6 @@ exclude_crates=(
|
||||
# The following require investigation if they can be fixed
|
||||
reth-basic-payload-builder
|
||||
reth-bench
|
||||
reth-bench-compare
|
||||
reth-cli
|
||||
reth-cli-commands
|
||||
reth-cli-runner
|
||||
@@ -69,7 +68,6 @@ exclude_crates=(
|
||||
reth-payload-builder # reth-metrics
|
||||
reth-provider # tokio
|
||||
reth-prune # tokio
|
||||
reth-prune-static-files # reth-provider
|
||||
reth-stages-api # reth-provider, reth-prune
|
||||
reth-static-file # tokio
|
||||
reth-transaction-pool # c-kzg
|
||||
|
||||
7
.github/assets/hive/build_simulators.sh
vendored
7
.github/assets/hive/build_simulators.sh
vendored
@@ -11,8 +11,7 @@ go build .
|
||||
|
||||
# Run each hive command in the background for each simulator and wait
|
||||
echo "Building images"
|
||||
# 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/eest" --sim.buildarg fixtures=https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0/fixtures_develop.tar.gz --sim.buildarg branch=v5.1.0 -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 &
|
||||
@@ -28,8 +27,8 @@ docker save hive/hiveproxy:latest -o ../hive_assets/hiveproxy.tar & saving_pids+
|
||||
docker save hive/simulators/devp2p:latest -o ../hive_assets/devp2p.tar & saving_pids+=( $! )
|
||||
docker save hive/simulators/ethereum/engine:latest -o ../hive_assets/engine.tar & saving_pids+=( $! )
|
||||
docker save hive/simulators/ethereum/rpc-compat:latest -o ../hive_assets/rpc_compat.tar & saving_pids+=( $! )
|
||||
docker save hive/simulators/ethereum/eels/consume-engine:latest -o ../hive_assets/eels_engine.tar & saving_pids+=( $! )
|
||||
docker save hive/simulators/ethereum/eels/consume-rlp:latest -o ../hive_assets/eels_rlp.tar & saving_pids+=( $! )
|
||||
docker save hive/simulators/ethereum/eest/consume-engine:latest -o ../hive_assets/eest_engine.tar & saving_pids+=( $! )
|
||||
docker save hive/simulators/ethereum/eest/consume-rlp:latest -o ../hive_assets/eest_rlp.tar & saving_pids+=( $! )
|
||||
docker save hive/simulators/smoke/genesis:latest -o ../hive_assets/smoke_genesis.tar & saving_pids+=( $! )
|
||||
docker save hive/simulators/smoke/network:latest -o ../hive_assets/smoke_network.tar & saving_pids+=( $! )
|
||||
docker save hive/simulators/ethereum/sync:latest -o ../hive_assets/ethereum_sync.tar & saving_pids+=( $! )
|
||||
|
||||
155
.github/assets/hive/expected_failures.yaml
vendored
155
.github/assets/hive/expected_failures.yaml
vendored
@@ -28,46 +28,34 @@ engine-withdrawals:
|
||||
- Withdraw zero amount (Paris) (reth)
|
||||
- Empty Withdrawals (Paris) (reth)
|
||||
- Corrupted Block Hash Payload (INVALID) (Paris) (reth)
|
||||
- Withdrawals Fork on Block 1 - 8 Block Re-Org NewPayload (Paris) (reth)
|
||||
- Withdrawals Fork on Canonical Block 8 / Side Block 7 - 10 Block Re-Org (Paris) (reth)
|
||||
|
||||
engine-api: [ ]
|
||||
engine-api: []
|
||||
|
||||
# no fix due to https://github.com/paradigmxyz/reth/issues/8732
|
||||
engine-cancun:
|
||||
- Invalid PayloadAttributes, Missing BeaconRoot, Syncing=True (Cancun) (reth)
|
||||
# the test fails with older versions of the code for which it passed before, probably related to changes
|
||||
# the test fails with older verions of the code for which it passed before, probably related to changes
|
||||
# in hive or its dependencies
|
||||
- Blob Transaction Ordering, Multiple Clients (Cancun) (reth)
|
||||
|
||||
sync: [ ]
|
||||
sync: []
|
||||
|
||||
engine-auth: [ ]
|
||||
# https://github.com/ethereum/hive/issues/1277
|
||||
engine-auth:
|
||||
- "JWT Authentication: No time drift, correct secret (Paris) (reth)"
|
||||
- "JWT Authentication: Negative time drift, within limit, correct secret (Paris) (reth)"
|
||||
- "JWT Authentication: Positive time drift, within limit, correct secret (Paris) (reth)"
|
||||
|
||||
# EIP-7610 related tests (Revert creation in case of non-empty storage):
|
||||
#
|
||||
# tests/prague/eip7702_set_code_tx/test_set_code_txs.py::test_set_code_to_non_empty_storage
|
||||
# The test artificially creates an empty account with storage, then tests EIP-7610's behavior.
|
||||
# On mainnet, ~25 such accounts exist as contract addresses (derived from keccak(prefix, caller,
|
||||
# nonce/salt), not from public keys). No private key exists for contract addresses. To trigger
|
||||
# this with EIP-7702, you'd need to recover a private key from one of the already deployed contract addresses - mathematically impossible.
|
||||
#
|
||||
# tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_*
|
||||
# Requires hash collision on create2 address to target already deployed accounts with storage.
|
||||
# ~20-30 such accounts exist from before the state-clear EIP. Creating new accounts targeting
|
||||
# these requires hash collision - mathematically impossible to trigger on mainnet.
|
||||
# ref: https://github.com/ethereum/go-ethereum/pull/28666#issuecomment-1891997143
|
||||
#
|
||||
# System contract tests (already fixed and deployed):
|
||||
#
|
||||
# tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout and test_invalid_log_length
|
||||
# System contract is already fixed and deployed; tests cover scenarios where contract is
|
||||
# malformed which can't happen retroactively. No point in adding checks.
|
||||
#
|
||||
# tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py::test_system_contract_deployment
|
||||
# tests/prague/eip7251_consolidations/test_contract_deployment.py::test_system_contract_deployment
|
||||
# Post-fork system contract deployment tests. Should fix for spec compliance but not realistic
|
||||
# on mainnet as these contracts are already deployed at the correct addresses.
|
||||
eels/consume-engine:
|
||||
# 7702 test - no fix: it’s too expensive to check whether the storage is empty on each creation
|
||||
# 6110 related tests - may start passing when fixtures improve
|
||||
# 7002 related tests - post-fork test, should fix for spec compliance but not
|
||||
# realistic on mainnet
|
||||
# 7251 related tests - modified contract, not necessarily practical on mainnet,
|
||||
# 7594: https://github.com/paradigmxyz/reth/issues/18471
|
||||
# worth re-visiting when more of these related tests are passing
|
||||
eest/consume-engine:
|
||||
- tests/prague/eip7702_set_code_tx/test_set_code_txs.py::test_set_code_to_non_empty_storage[fork_Prague-blockchain_test_engine-zero_nonce]-reth
|
||||
- tests/prague/eip7251_consolidations/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test_engine-deploy_after_fork-nonzero_balance]-reth
|
||||
- tests/prague/eip7251_consolidations/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test_engine-deploy_after_fork-zero_balance]-reth
|
||||
@@ -97,64 +85,7 @@ eels/consume-engine:
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_withdrawal_credentials_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Osaka-blockchain_test_engine-slice_bytes_False]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Osaka-blockchain_test_engine-slice_bytes_True]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Cancun-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Cancun-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Cancun-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Osaka-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Cancun-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Osaka-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Osaka-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Paris-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Paris-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Paris-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Paris-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Osaka-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Prague-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Prague-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Prague-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Prague-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Shanghai-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Shanghai-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Shanghai-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Shanghai-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
|
||||
# Blob limit tests:
|
||||
#
|
||||
# tests/osaka/eip7594_peerdas/test_max_blob_per_tx.py::test_max_blobs_per_tx_fork_transition[fork_PragueToOsakaAtTime15k-blob_count_7-blockchain_test]
|
||||
# this test inserts a chain via chain.rlp where the last block is invalid, but expects import to stop there, this doesn't work properly with our pipeline import approach hence the import fails when the invalid block is detected.
|
||||
#. In other words, if this test fails, this means we're correctly rejecting the block.
|
||||
#. The same test exists in the consume-engine simulator where it is passing as expected
|
||||
eels/consume-rlp:
|
||||
eest/consume-rlp:
|
||||
- tests/prague/eip7702_set_code_tx/test_set_code_txs.py::test_set_code_to_non_empty_storage[fork_Prague-blockchain_test-zero_nonce]-reth
|
||||
- tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py::test_system_contract_errors[fork_Prague-blockchain_test_engine-system_contract_reaches_gas_limit-system_contract_0x0000bbddc7ce488642fb579f8b00f3a590007251]-reth
|
||||
- tests/prague/eip7251_consolidations/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test_engine-deploy_after_fork-nonzero_balance]-reth
|
||||
@@ -191,53 +122,3 @@ eels/consume-rlp:
|
||||
- tests/prague/eip7251_consolidations/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test-deploy_after_fork-zero_balance]-reth
|
||||
- tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test-deploy_after_fork-nonzero_balance]-reth
|
||||
- tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test-deploy_after_fork-zero_balance]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_1-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_0-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_0-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_2-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_1-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_1-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_1-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_2-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_1-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_2-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_0-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Cancun-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Cancun-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_2-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_1-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Cancun-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Osaka-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Cancun-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Osaka-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Osaka-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Paris-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Osaka-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Paris-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Paris-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Paris-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Prague-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Prague-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Prague-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Prague-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Shanghai-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Shanghai-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_0-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Shanghai-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Shanghai-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_1-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_2-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_1-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_2-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_1-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_2-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_0-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_0-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_2-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_0-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_0-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_0-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_2-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_1-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_2-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_0-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
|
||||
2
.github/assets/hive/ignored_tests.yaml
vendored
2
.github/assets/hive/ignored_tests.yaml
vendored
@@ -21,7 +21,6 @@ engine-cancun:
|
||||
- Transaction Re-Org, New Payload on Revert Back (Cancun) (reth)
|
||||
- Transaction Re-Org, Re-Org to Different Block (Cancun) (reth)
|
||||
- Transaction Re-Org, Re-Org Out (Cancun) (reth)
|
||||
- Invalid Missing Ancestor ReOrg, StateRoot, EmptyTxs=False, Invalid P9 (Cancun) (reth)
|
||||
- Multiple New Payloads Extending Canonical Chain, Wait for Canonical Payload (Cancun) (reth)
|
||||
engine-api:
|
||||
- Transaction Re-Org, Re-Org Out (Paris) (reth)
|
||||
@@ -31,6 +30,5 @@ engine-api:
|
||||
- Invalid Missing Ancestor Syncing ReOrg, Transaction Nonce, EmptyTxs=False, CanonicalReOrg=False, Invalid P9 (Paris) (reth)
|
||||
- Invalid Missing Ancestor Syncing ReOrg, Transaction Signature, EmptyTxs=False, CanonicalReOrg=True, Invalid P9 (Paris) (reth)
|
||||
- Invalid Missing Ancestor Syncing ReOrg, Transaction Signature, EmptyTxs=False, CanonicalReOrg=False, Invalid P9 (Paris) (reth)
|
||||
- Invalid Missing Ancestor ReOrg, StateRoot, EmptyTxs=True, Invalid P10 (Paris) (reth)
|
||||
- Multiple New Payloads Extending Canonical Chain, Wait for Canonical Payload (Paris) (reth)
|
||||
- Multiple New Payloads Extending Canonical Chain, Set Head to First Payload Received (Paris) (reth)
|
||||
|
||||
4
.github/assets/hive/load_images.sh
vendored
4
.github/assets/hive/load_images.sh
vendored
@@ -11,8 +11,8 @@ IMAGES=(
|
||||
"/tmp/smoke_genesis.tar"
|
||||
"/tmp/smoke_network.tar"
|
||||
"/tmp/ethereum_sync.tar"
|
||||
"/tmp/eels_engine.tar"
|
||||
"/tmp/eels_rlp.tar"
|
||||
"/tmp/eest_engine.tar"
|
||||
"/tmp/eest_rlp.tar"
|
||||
"/tmp/reth_image.tar"
|
||||
)
|
||||
|
||||
|
||||
2
.github/assets/hive/run_simulator.sh
vendored
2
.github/assets/hive/run_simulator.sh
vendored
@@ -7,7 +7,7 @@ sim="${1}"
|
||||
limit="${2}"
|
||||
|
||||
run_hive() {
|
||||
hive --sim "${sim}" --sim.limit "${limit}" --sim.parallelism 16 --client reth 2>&1 | tee /tmp/log || true
|
||||
hive --sim "${sim}" --sim.limit "${limit}" --sim.parallelism 8 --client reth 2>&1 | tee /tmp/log || true
|
||||
}
|
||||
|
||||
check_log() {
|
||||
|
||||
7
.github/workflows/bench.yml
vendored
7
.github/workflows/bench.yml
vendored
@@ -11,19 +11,18 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
BASELINE: base
|
||||
SEED: reth
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
name: bench
|
||||
jobs:
|
||||
codspeed:
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on:
|
||||
group: Reth
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: true
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
|
||||
13
.github/workflows/book.yml
vendored
13
.github/workflows/book.yml
vendored
@@ -10,21 +10,16 @@ on:
|
||||
types: [opened, reopened, synchronize, closed]
|
||||
merge_group:
|
||||
|
||||
env:
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: depot-ubuntu-latest-8
|
||||
timeout-minutes: 90
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: v1.2.23
|
||||
|
||||
- name: Install Playwright browsers
|
||||
# Required for rehype-mermaid to render Mermaid diagrams during build
|
||||
@@ -36,8 +31,6 @@ jobs:
|
||||
- name: Install Rust nightly
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
|
||||
- name: Build docs
|
||||
run: cd docs/vocs && bash scripts/build-cargo-docs.sh
|
||||
|
||||
|
||||
9
.github/workflows/compact.yml
vendored
9
.github/workflows/compact.yml
vendored
@@ -13,12 +13,12 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
name: compact-codec
|
||||
jobs:
|
||||
compact-codec:
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on:
|
||||
group: Reth
|
||||
strategy:
|
||||
matrix:
|
||||
bin:
|
||||
@@ -27,12 +27,11 @@ jobs:
|
||||
steps:
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Checkout base
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.base_ref || 'main' }}
|
||||
# On `main` branch, generates test vectors and serializes them to disk using `Compact`.
|
||||
@@ -40,7 +39,7 @@ jobs:
|
||||
run: |
|
||||
${{ matrix.bin }} -- test-vectors compact --write
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
clean: false
|
||||
# On incoming merge try to read and decode previously generated vectors with `Compact`
|
||||
|
||||
2
.github/workflows/docker-git.yml
vendored
2
.github/workflows/docker-git.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
- name: 'Build and push the git-sha-tagged op-reth image'
|
||||
command: 'make IMAGE_NAME=$OP_IMAGE_NAME DOCKER_IMAGE_NAME=$OP_DOCKER_IMAGE_NAME GIT_SHA=$GIT_SHA PROFILE=maxperf op-docker-build-push-git-sha'
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
6
.github/workflows/docker-nightly.yml
vendored
6
.github/workflows/docker-nightly.yml
vendored
@@ -28,18 +28,14 @@ jobs:
|
||||
build:
|
||||
- name: 'Build and push the nightly reth image'
|
||||
command: 'make PROFILE=maxperf docker-build-push-nightly'
|
||||
- name: 'Build and push the nightly edge profiling reth image'
|
||||
command: 'make PROFILE=profiling docker-build-push-nightly-edge-profiling'
|
||||
- name: 'Build and push the nightly profiling reth image'
|
||||
command: 'make PROFILE=profiling docker-build-push-nightly-profiling'
|
||||
- name: 'Build and push the nightly op-reth image'
|
||||
command: 'make IMAGE_NAME=$OP_IMAGE_NAME DOCKER_IMAGE_NAME=$OP_DOCKER_IMAGE_NAME PROFILE=maxperf op-docker-build-push-nightly'
|
||||
- name: 'Build and push the nightly edge profiling op-reth image'
|
||||
command: 'make IMAGE_NAME=$OP_IMAGE_NAME DOCKER_IMAGE_NAME=$OP_DOCKER_IMAGE_NAME PROFILE=profiling op-docker-build-push-nightly-edge-profiling'
|
||||
- name: 'Build and push the nightly profiling op-reth image'
|
||||
command: 'make IMAGE_NAME=$OP_IMAGE_NAME DOCKER_IMAGE_NAME=$OP_DOCKER_IMAGE_NAME PROFILE=profiling op-docker-build-push-nightly-profiling'
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- name: Remove bloatware
|
||||
uses: laverdet/remove-bloatware@v1.0.0
|
||||
with:
|
||||
|
||||
73
.github/workflows/docker-tag-latest.yml
vendored
73
.github/workflows/docker-tag-latest.yml
vendored
@@ -1,73 +0,0 @@
|
||||
# Tag a specific Docker release version as latest
|
||||
|
||||
name: docker-tag-latest
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Release version to tag as latest (e.g., v1.8.4)'
|
||||
required: true
|
||||
type: string
|
||||
tag_reth:
|
||||
description: 'Tag reth image as latest'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
tag_op_reth:
|
||||
description: 'Tag op-reth image as latest'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ github.actor }}
|
||||
|
||||
jobs:
|
||||
tag-reth-latest:
|
||||
name: Tag reth as latest
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ inputs.tag_reth }}
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Log in to Docker
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username ${DOCKER_USERNAME} --password-stdin
|
||||
|
||||
- name: Pull reth release image
|
||||
run: |
|
||||
docker pull ghcr.io/${{ github.repository_owner }}/reth:${{ inputs.version }}
|
||||
|
||||
- name: Tag reth as latest
|
||||
run: |
|
||||
docker tag ghcr.io/${{ github.repository_owner }}/reth:${{ inputs.version }} ghcr.io/${{ github.repository_owner }}/reth:latest
|
||||
|
||||
- name: Push reth latest tag
|
||||
run: |
|
||||
docker push ghcr.io/${{ github.repository_owner }}/reth:latest
|
||||
|
||||
tag-op-reth-latest:
|
||||
name: Tag op-reth as latest
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ inputs.tag_op_reth }}
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Log in to Docker
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username ${DOCKER_USERNAME} --password-stdin
|
||||
|
||||
- name: Pull op-reth release image
|
||||
run: |
|
||||
docker pull ghcr.io/${{ github.repository_owner }}/op-reth:${{ inputs.version }}
|
||||
|
||||
- name: Tag op-reth as latest
|
||||
run: |
|
||||
docker tag ghcr.io/${{ github.repository_owner }}/op-reth:${{ inputs.version }} ghcr.io/${{ github.repository_owner }}/op-reth:latest
|
||||
|
||||
- name: Push op-reth latest tag
|
||||
run: |
|
||||
docker push ghcr.io/${{ github.repository_owner }}/op-reth:latest
|
||||
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
- name: "Build and push op-reth image"
|
||||
command: "make IMAGE_NAME=$OP_IMAGE_NAME DOCKER_IMAGE_NAME=$OP_DOCKER_IMAGE_NAME PROFILE=maxperf op-docker-build-push"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
- name: "Build and push op-reth image"
|
||||
command: "make IMAGE_NAME=$OP_IMAGE_NAME DOCKER_IMAGE_NAME=$OP_DOCKER_IMAGE_NAME PROFILE=maxperf op-docker-build-push-latest"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
8
.github/workflows/e2e.yml
vendored
8
.github/workflows/e2e.yml
vendored
@@ -11,7 +11,6 @@ on:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
SEED: rustethereumethereumrust
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
@@ -20,14 +19,14 @@ concurrency:
|
||||
jobs:
|
||||
test:
|
||||
name: e2e-testsuite
|
||||
runs-on: depot-ubuntu-latest-4
|
||||
runs-on:
|
||||
group: Reth
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
@@ -44,3 +43,4 @@ jobs:
|
||||
--exclude 'op-reth' \
|
||||
--exclude 'reth' \
|
||||
-E 'binary(e2e_testsuite)'
|
||||
|
||||
|
||||
21
.github/workflows/grafana.yml
vendored
21
.github/workflows/grafana.yml
vendored
@@ -1,21 +0,0 @@
|
||||
name: grafana
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
check-dashboard:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Check for ${DS_PROMETHEUS} in overview.json
|
||||
run: |
|
||||
if grep -Fn '${DS_PROMETHEUS}' etc/grafana/dashboards/overview.json; then
|
||||
echo "Error: overview.json contains '\${DS_PROMETHEUS}' placeholder"
|
||||
echo "Please replace it with '\${datasource}'"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ overview.json does not contain '\${DS_PROMETHEUS}' placeholder"
|
||||
100
.github/workflows/hive.yml
vendored
100
.github/workflows/hive.yml
vendored
@@ -15,21 +15,11 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
prepare-reth-stable:
|
||||
prepare-reth:
|
||||
uses: ./.github/workflows/prepare-reth.yml
|
||||
with:
|
||||
image_tag: ghcr.io/paradigmxyz/reth:latest
|
||||
binary_name: reth
|
||||
cargo_features: "asm-keccak"
|
||||
artifact_name: "reth-stable"
|
||||
|
||||
prepare-reth-edge:
|
||||
uses: ./.github/workflows/prepare-reth.yml
|
||||
with:
|
||||
image_tag: ghcr.io/paradigmxyz/reth:latest
|
||||
binary_name: reth
|
||||
cargo_features: "asm-keccak edge"
|
||||
artifact_name: "reth-edge"
|
||||
|
||||
prepare-hive:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
@@ -37,48 +27,23 @@ jobs:
|
||||
runs-on:
|
||||
group: Reth
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- name: Checkout hive tests
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: ethereum/hive
|
||||
path: hivetests
|
||||
|
||||
- name: Get hive commit hash
|
||||
id: hive-commit
|
||||
run: echo "hash=$(cd hivetests && git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.13.1"
|
||||
- run: go version
|
||||
|
||||
- name: Restore hive assets cache
|
||||
id: cache-hive
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ./hive_assets
|
||||
key: hive-assets-${{ steps.hive-commit.outputs.hash }}-${{ hashFiles('.github/assets/hive/build_simulators.sh') }}
|
||||
|
||||
- name: Build hive assets
|
||||
if: steps.cache-hive.outputs.cache-hit != 'true'
|
||||
run: .github/assets/hive/build_simulators.sh
|
||||
|
||||
- name: Load cached Docker images
|
||||
if: steps.cache-hive.outputs.cache-hit == 'true'
|
||||
run: |
|
||||
cd hive_assets
|
||||
for tar_file in *.tar; do
|
||||
if [ -f "$tar_file" ]; then
|
||||
echo "Loading $tar_file..."
|
||||
docker load -i "$tar_file"
|
||||
fi
|
||||
done
|
||||
# Make hive binary executable
|
||||
chmod +x hive
|
||||
|
||||
- name: Upload hive assets
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: hive_assets
|
||||
path: ./hive_assets
|
||||
@@ -87,7 +52,6 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
storage: [stable, edge]
|
||||
# ethereum/rpc to be deprecated:
|
||||
# https://github.com/ethereum/hive/pull/1117
|
||||
scenario:
|
||||
@@ -97,7 +61,7 @@ jobs:
|
||||
- sim: devp2p
|
||||
limit: discv4
|
||||
# started failing after https://github.com/ethereum/go-ethereum/pull/31843, no
|
||||
# action on our side, remove from here when we get unexpected passes on these tests
|
||||
# action on our side, remove from here when we get unxpected passes on these tests
|
||||
# - sim: devp2p
|
||||
# limit: eth
|
||||
# include:
|
||||
@@ -148,68 +112,63 @@ jobs:
|
||||
- debug_
|
||||
|
||||
# consume-engine
|
||||
- sim: ethereum/eels/consume-engine
|
||||
- sim: ethereum/eest/consume-engine
|
||||
limit: .*tests/osaka.*
|
||||
- sim: ethereum/eels/consume-engine
|
||||
- sim: ethereum/eest/consume-engine
|
||||
limit: .*tests/prague.*
|
||||
- sim: ethereum/eels/consume-engine
|
||||
- sim: ethereum/eest/consume-engine
|
||||
limit: .*tests/cancun.*
|
||||
- sim: ethereum/eels/consume-engine
|
||||
- sim: ethereum/eest/consume-engine
|
||||
limit: .*tests/shanghai.*
|
||||
- sim: ethereum/eels/consume-engine
|
||||
- sim: ethereum/eest/consume-engine
|
||||
limit: .*tests/berlin.*
|
||||
- sim: ethereum/eels/consume-engine
|
||||
- sim: ethereum/eest/consume-engine
|
||||
limit: .*tests/istanbul.*
|
||||
- sim: ethereum/eels/consume-engine
|
||||
- sim: ethereum/eest/consume-engine
|
||||
limit: .*tests/homestead.*
|
||||
- sim: ethereum/eels/consume-engine
|
||||
- sim: ethereum/eest/consume-engine
|
||||
limit: .*tests/frontier.*
|
||||
- sim: ethereum/eels/consume-engine
|
||||
limit: .*tests/paris.*
|
||||
|
||||
# consume-rlp
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
- sim: ethereum/eest/consume-rlp
|
||||
limit: .*tests/osaka.*
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
- sim: ethereum/eest/consume-rlp
|
||||
limit: .*tests/prague.*
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
- sim: ethereum/eest/consume-rlp
|
||||
limit: .*tests/cancun.*
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
- sim: ethereum/eest/consume-rlp
|
||||
limit: .*tests/shanghai.*
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
- sim: ethereum/eest/consume-rlp
|
||||
limit: .*tests/berlin.*
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
- sim: ethereum/eest/consume-rlp
|
||||
limit: .*tests/istanbul.*
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
- sim: ethereum/eest/consume-rlp
|
||||
limit: .*tests/homestead.*
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
- sim: ethereum/eest/consume-rlp
|
||||
limit: .*tests/frontier.*
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
limit: .*tests/paris.*
|
||||
needs:
|
||||
- prepare-reth-stable
|
||||
- prepare-reth-edge
|
||||
- prepare-reth
|
||||
- prepare-hive
|
||||
name: ${{ matrix.storage }} / ${{ matrix.scenario.sim }}${{ matrix.scenario.limit && format(' - {0}', matrix.scenario.limit) }}
|
||||
name: run ${{ matrix.scenario.sim }}${{ matrix.scenario.limit && format(' - {0}', matrix.scenario.limit) }}
|
||||
runs-on:
|
||||
group: Reth
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download hive assets
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: hive_assets
|
||||
path: /tmp
|
||||
|
||||
- name: Download reth image
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: reth-${{ matrix.storage }}
|
||||
name: artifacts
|
||||
path: /tmp
|
||||
|
||||
- name: Load Docker images
|
||||
@@ -221,7 +180,7 @@ jobs:
|
||||
chmod +x /usr/local/bin/hive
|
||||
|
||||
- name: Checkout hive tests
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: ethereum/hive
|
||||
ref: master
|
||||
@@ -259,7 +218,8 @@ jobs:
|
||||
notify-on-error:
|
||||
needs: test
|
||||
if: failure()
|
||||
runs-on: ubuntu-latest
|
||||
runs-on:
|
||||
group: Reth
|
||||
steps:
|
||||
- name: Slack Webhook Action
|
||||
uses: rtCamp/action-slack-notify@v2
|
||||
|
||||
12
.github/workflows/integration.yml
vendored
12
.github/workflows/integration.yml
vendored
@@ -14,7 +14,6 @@ on:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
SEED: rustethereumethereumrust
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
@@ -24,7 +23,8 @@ jobs:
|
||||
test:
|
||||
name: test / ${{ matrix.network }}
|
||||
if: github.event_name != 'schedule'
|
||||
runs-on: depot-ubuntu-latest-4
|
||||
runs-on:
|
||||
group: Reth
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
strategy:
|
||||
@@ -32,13 +32,12 @@ jobs:
|
||||
network: ["ethereum", "optimism"]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Install Geth
|
||||
run: .github/assets/install_geth.sh
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -72,13 +71,12 @@ jobs:
|
||||
if: github.event_name == 'schedule'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: run era1 files integration tests
|
||||
run: cargo nextest run --release --package reth-era --test it -- --ignored
|
||||
run: cargo nextest run --package reth-era --test it -- --ignored
|
||||
|
||||
13
.github/workflows/kurtosis-op.yml
vendored
13
.github/workflows/kurtosis-op.yml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
- '*'
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -32,16 +32,17 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
name: run kurtosis
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on:
|
||||
group: Reth
|
||||
needs:
|
||||
- prepare-reth
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download reth image
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: artifacts
|
||||
path: /tmp
|
||||
@@ -82,10 +83,12 @@ jobs:
|
||||
kurtosis service logs -a op-devnet op-cl-2151908-2-op-node-op-reth-op-kurtosis
|
||||
exit 1
|
||||
|
||||
|
||||
notify-on-error:
|
||||
needs: test
|
||||
if: failure()
|
||||
runs-on: ubuntu-latest
|
||||
runs-on:
|
||||
group: Reth
|
||||
steps:
|
||||
- name: Slack Webhook Action
|
||||
uses: rtCamp/action-slack-notify@v2
|
||||
|
||||
14
.github/workflows/kurtosis.yml
vendored
14
.github/workflows/kurtosis.yml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
- '*'
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -30,16 +30,17 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
name: run kurtosis
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on:
|
||||
group: Reth
|
||||
needs:
|
||||
- prepare-reth
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download reth image
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: artifacts
|
||||
path: /tmp
|
||||
@@ -53,12 +54,13 @@ jobs:
|
||||
- name: Run kurtosis
|
||||
uses: ethpandaops/kurtosis-assertoor-github-action@v1
|
||||
with:
|
||||
ethereum_package_args: ".github/assets/kurtosis_network_params.yaml"
|
||||
ethereum_package_args: '.github/assets/kurtosis_network_params.yaml'
|
||||
|
||||
notify-on-error:
|
||||
needs: test
|
||||
if: failure()
|
||||
runs-on: ubuntu-latest
|
||||
runs-on:
|
||||
group: Reth
|
||||
steps:
|
||||
- name: Slack Webhook Action
|
||||
uses: rtCamp/action-slack-notify@v2
|
||||
|
||||
2
.github/workflows/label-pr.yml
vendored
2
.github/workflows/label-pr.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
2
.github/workflows/lint-actions.yml
vendored
2
.github/workflows/lint-actions.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
actionlint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- name: Download actionlint
|
||||
id: get_actionlint
|
||||
run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
|
||||
|
||||
110
.github/workflows/lint.yml
vendored
110
.github/workflows/lint.yml
vendored
@@ -8,12 +8,11 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
jobs:
|
||||
clippy-binaries:
|
||||
name: clippy binaries / ${{ matrix.type }}
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -22,12 +21,11 @@ jobs:
|
||||
args: --workspace --lib --examples --tests --benches --locked
|
||||
features: "ethereum asm-keccak jemalloc jemalloc-prof min-error-logs min-warn-logs min-info-logs min-debug-logs min-trace-logs"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@clippy
|
||||
with:
|
||||
components: clippy
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -42,15 +40,14 @@ jobs:
|
||||
|
||||
clippy:
|
||||
name: clippy
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: clippy
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -59,16 +56,15 @@ jobs:
|
||||
RUSTFLAGS: -D warnings
|
||||
|
||||
wasm:
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
target: wasm32-wasip1
|
||||
- uses: taiki-e/install-action@cargo-hack
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -79,16 +75,15 @@ jobs:
|
||||
.github/assets/check_wasm.sh
|
||||
|
||||
riscv:
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
target: riscv32imac-unknown-none-elf
|
||||
- uses: taiki-e/install-action@cargo-hack
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -97,27 +92,21 @@ jobs:
|
||||
run: .github/assets/check_rv32imac.sh
|
||||
|
||||
crate-checks:
|
||||
name: crate-checks (${{ matrix.partition }}/${{ matrix.total_partitions }})
|
||||
runs-on: depot-ubuntu-latest-4
|
||||
strategy:
|
||||
matrix:
|
||||
partition: [1, 2, 3]
|
||||
total_partitions: [3]
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: taiki-e/install-action@cargo-hack
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- run: cargo hack check --workspace --partition ${{ matrix.partition }}/${{ matrix.total_partitions }}
|
||||
- run: cargo hack check --workspace
|
||||
|
||||
msrv:
|
||||
name: MSRV
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -125,12 +114,11 @@ jobs:
|
||||
- binary: reth
|
||||
- binary: op-reth
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: "1.88" # MSRV
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -140,13 +128,12 @@ jobs:
|
||||
|
||||
docs:
|
||||
name: docs
|
||||
runs-on: depot-ubuntu-latest-4
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -158,27 +145,25 @@ jobs:
|
||||
|
||||
fmt:
|
||||
name: fmt
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rustfmt
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: Run fmt
|
||||
run: cargo fmt --all --check
|
||||
|
||||
udeps:
|
||||
name: udeps
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -187,21 +172,19 @@ jobs:
|
||||
|
||||
book:
|
||||
name: book
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- run: cargo build --bin reth --workspace
|
||||
- run: cargo build --bin op-reth --workspace
|
||||
- run: cargo build --bin reth --workspace --features ethereum
|
||||
env:
|
||||
RUSTFLAGS: -D warnings
|
||||
- run: ./docs/cli/update.sh target/debug/reth target/debug/op-reth
|
||||
- run: ./docs/cli/update.sh target/debug/reth
|
||||
- name: Check docs changes
|
||||
run: git diff --exit-code
|
||||
|
||||
@@ -209,7 +192,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: crate-ci/typos@v1
|
||||
|
||||
check-toml:
|
||||
@@ -217,7 +200,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v5
|
||||
- name: Run dprint
|
||||
uses: dprint/check@v2.3
|
||||
with:
|
||||
@@ -227,7 +210,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- name: Check dashboard JSON with jq
|
||||
uses: sergeysova/jq-action@v2
|
||||
with:
|
||||
@@ -237,52 +220,45 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Ensure no arbitrary or proptest dependency on default build
|
||||
run: cargo tree --package reth -e=features,no-dev | grep -Eq "arbitrary|proptest" && exit 1 || exit 0
|
||||
|
||||
# Checks that selected crates can compile with power set of features
|
||||
# Checks that selected rates can compile with power set of features
|
||||
features:
|
||||
name: features
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@clippy
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: cargo install cargo-hack
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
- run: |
|
||||
cargo hack check \
|
||||
--package reth-codecs \
|
||||
--package reth-primitives-traits \
|
||||
--package reth-primitives \
|
||||
--feature-powerset \
|
||||
--depth 2
|
||||
- run: make check-features
|
||||
env:
|
||||
RUSTFLAGS: -D warnings
|
||||
|
||||
# Check crates correctly propagate features
|
||||
feature-propagation:
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: taiki-e/cache-cargo-install-action@v3
|
||||
with:
|
||||
tool: zepter
|
||||
- name: Eagerly pull dependencies
|
||||
run: cargo metadata --format-version=1 --locked > /dev/null
|
||||
- run: zepter run check
|
||||
- uses: actions/checkout@v5
|
||||
- name: fetch deps
|
||||
run: |
|
||||
# Eagerly pull dependencies
|
||||
time cargo metadata --format-version=1 --locked > /dev/null
|
||||
- name: run zepter
|
||||
run: |
|
||||
cargo install zepter -f --locked
|
||||
zepter --version
|
||||
time zepter run check
|
||||
|
||||
deny:
|
||||
uses: ithacaxyz/ci/.github/workflows/deny.yml@main
|
||||
|
||||
14
.github/workflows/prepare-reth.yml
vendored
14
.github/workflows/prepare-reth.yml
vendored
@@ -21,19 +21,15 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
description: "Optional cargo package path"
|
||||
artifact_name:
|
||||
required: false
|
||||
type: string
|
||||
default: "artifacts"
|
||||
description: "Name for the uploaded artifact"
|
||||
|
||||
jobs:
|
||||
prepare-reth:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
timeout-minutes: 45
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on:
|
||||
group: Reth
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- run: mkdir artifacts
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
@@ -55,7 +51,7 @@ jobs:
|
||||
|
||||
- name: Upload reth image
|
||||
id: upload
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ inputs.artifact_name }}
|
||||
name: artifacts
|
||||
path: ./artifacts
|
||||
|
||||
2
.github/workflows/release-dist.yml
vendored
2
.github/workflows/release-dist.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Update Homebrew formula
|
||||
uses: dawidd6/action-homebrew-bump-formula@v7
|
||||
uses: dawidd6/action-homebrew-bump-formula@v5
|
||||
with:
|
||||
token: ${{ secrets.HOMEBREW }}
|
||||
no_fork: true
|
||||
|
||||
92
.github/workflows/release-reproducible.yml
vendored
92
.github/workflows/release-reproducible.yml
vendored
@@ -1,11 +1,11 @@
|
||||
# This workflow is for building and pushing reproducible artifacts for releases
|
||||
# This workflow is for building and pushing reproducible Docker images for releases.
|
||||
|
||||
name: release-reproducible
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [release]
|
||||
types: [completed]
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
env:
|
||||
DOCKER_REPRODUCIBLE_IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/reth-reproducible
|
||||
@@ -13,41 +13,23 @@ env:
|
||||
jobs:
|
||||
extract-version:
|
||||
name: extract version
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Extract version from triggering tag
|
||||
- name: Extract version
|
||||
run: echo "VERSION=$(echo ${GITHUB_REF#refs/tags/})" >> $GITHUB_OUTPUT
|
||||
id: extract_version
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
# Get the tag that points to the head SHA of the triggering workflow
|
||||
TAG=$(gh api /repos/${{ github.repository }}/git/refs/tags \
|
||||
--jq '.[] | select(.object.sha == "${{ github.event.workflow_run.head_sha }}") | .ref' \
|
||||
| head -1 \
|
||||
| sed 's|refs/tags/||')
|
||||
|
||||
if [ -z "$TAG" ]; then
|
||||
echo "No tag found for SHA ${{ github.event.workflow_run.head_sha }}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "VERSION=$TAG" >> $GITHUB_OUTPUT
|
||||
outputs:
|
||||
VERSION: ${{ steps.extract_version.outputs.VERSION }}
|
||||
|
||||
build-reproducible:
|
||||
name: build and push reproducible image and binaries
|
||||
name: build and push reproducible image
|
||||
runs-on: ubuntu-latest
|
||||
needs: [extract-version]
|
||||
needs: extract-version
|
||||
permissions:
|
||||
packages: write
|
||||
contents: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.extract-version.outputs.VERSION }}
|
||||
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
@@ -58,37 +40,12 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract Rust version
|
||||
id: rust_version
|
||||
run: |
|
||||
RUST_TOOLCHAIN=$(rustc --version | cut -d' ' -f2)
|
||||
echo "RUST_TOOLCHAIN=$RUST_TOOLCHAIN" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build reproducible artifacts
|
||||
uses: docker/build-push-action@v6
|
||||
id: docker_build
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.reproducible
|
||||
build-args: |
|
||||
RUST_TOOLCHAIN=${{ steps.rust_version.outputs.RUST_TOOLCHAIN }}
|
||||
VERSION=${{ needs.extract-version.outputs.VERSION }}
|
||||
target: artifacts
|
||||
outputs: type=local,dest=./docker-artifacts
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
env:
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
|
||||
- name: Build and push final image
|
||||
- name: Build and push reproducible image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.reproducible
|
||||
push: true
|
||||
build-args: |
|
||||
RUST_TOOLCHAIN=${{ steps.rust_version.outputs.RUST_TOOLCHAIN }}
|
||||
VERSION=${{ needs.extract-version.outputs.VERSION }}
|
||||
tags: |
|
||||
${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:${{ needs.extract-version.outputs.VERSION }}
|
||||
${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:latest
|
||||
@@ -97,30 +54,3 @@ jobs:
|
||||
provenance: false
|
||||
env:
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
|
||||
- name: Prepare artifacts from Docker build
|
||||
run: |
|
||||
mkdir reproducible-artifacts
|
||||
cp docker-artifacts/reth reproducible-artifacts/reth-reproducible-${{ needs.extract-version.outputs.VERSION }}-x86_64-unknown-linux-gnu
|
||||
cp docker-artifacts/*.deb reproducible-artifacts/reth-${{ needs.extract-version.outputs.VERSION }}-x86_64-unknown-linux-gnu-reproducible.deb
|
||||
|
||||
- name: Configure GPG and create artifacts
|
||||
env:
|
||||
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
|
||||
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||
run: |
|
||||
export GPG_TTY=$(tty)
|
||||
echo -n "$GPG_SIGNING_KEY" | base64 --decode | gpg --batch --import
|
||||
|
||||
cd reproducible-artifacts
|
||||
tar -czf reth-reproducible-${{ needs.extract-version.outputs.VERSION }}-x86_64-unknown-linux-gnu.tar.gz reth-reproducible-${{ needs.extract-version.outputs.VERSION }}-x86_64-unknown-linux-gnu --remove-files
|
||||
echo "$GPG_PASSPHRASE" | gpg --passphrase-fd 0 --pinentry-mode loopback --batch -ab reth-reproducible-${{ needs.extract-version.outputs.VERSION }}-x86_64-unknown-linux-gnu.tar.gz
|
||||
echo "$GPG_PASSPHRASE" | gpg --passphrase-fd 0 --pinentry-mode loopback --batch -ab reth-${{ needs.extract-version.outputs.VERSION }}-x86_64-unknown-linux-gnu-reproducible.deb
|
||||
|
||||
- name: Upload reproducible artifacts to release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release upload ${{ needs.extract-version.outputs.VERSION }} \
|
||||
reproducible-artifacts/*
|
||||
|
||||
|
||||
17
.github/workflows/release.yml
vendored
17
.github/workflows/release.yml
vendored
@@ -22,7 +22,6 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
DOCKER_IMAGE_NAME_URL: https://ghcr.io/${{ github.repository_owner }}/reth
|
||||
DOCKER_OP_IMAGE_NAME_URL: https://ghcr.io/${{ github.repository_owner }}/op-reth
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
jobs:
|
||||
dry-run:
|
||||
@@ -50,9 +49,8 @@ jobs:
|
||||
needs: extract-version
|
||||
if: ${{ github.event.inputs.dry_run != 'true' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: Verify crate version matches tag
|
||||
# Check that the Cargo version starts with the tag,
|
||||
# so that Cargo version 1.4.8 can be matched against both v1.4.8 and v1.4.8-rc.1
|
||||
@@ -80,7 +78,7 @@ jobs:
|
||||
profile: maxperf
|
||||
allow_fail: false
|
||||
- target: x86_64-apple-darwin
|
||||
os: macos-14
|
||||
os: macos-13
|
||||
profile: maxperf
|
||||
allow_fail: false
|
||||
- target: aarch64-apple-darwin
|
||||
@@ -101,12 +99,11 @@ jobs:
|
||||
- command: op-build
|
||||
binary: op-reth
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
target: ${{ matrix.configs.target }}
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: Install cross main
|
||||
id: cross_main
|
||||
run: |
|
||||
@@ -144,14 +141,14 @@ jobs:
|
||||
|
||||
- name: Upload artifact
|
||||
if: ${{ github.event.inputs.dry_run != 'true' }}
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz
|
||||
path: ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz
|
||||
|
||||
- name: Upload signature
|
||||
if: ${{ github.event.inputs.dry_run != 'true' }}
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz.asc
|
||||
path: ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz.asc
|
||||
@@ -169,11 +166,11 @@ jobs:
|
||||
steps:
|
||||
# This is necessary for generating the changelog.
|
||||
# It has to come before "Download Artifacts" or else it deletes the artifacts.
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v5
|
||||
- name: Generate full changelog
|
||||
id: changelog
|
||||
run: |
|
||||
|
||||
82
.github/workflows/reproducible-build.yml
vendored
82
.github/workflows/reproducible-build.yml
vendored
@@ -8,73 +8,31 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
name: build reproducible binaries
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- runner: ubuntu-latest
|
||||
machine: machine-1
|
||||
- runner: ubuntu-22.04
|
||||
machine: machine-2
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
target: x86_64-unknown-linux-gnu
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build reproducible binary with Docker
|
||||
- name: Install cross main
|
||||
run: |
|
||||
RUST_TOOLCHAIN=$(rustc --version | cut -d' ' -f2)
|
||||
docker build \
|
||||
--build-arg "RUST_TOOLCHAIN=${RUST_TOOLCHAIN}" \
|
||||
-f Dockerfile.reproducible -t reth:release \
|
||||
--target artifacts \
|
||||
--output type=local,dest=./target .
|
||||
|
||||
- name: Calculate SHA256
|
||||
id: sha256
|
||||
cargo install cross --git https://github.com/cross-rs/cross
|
||||
- name: Install cargo-cache
|
||||
run: |
|
||||
sha256sum target/reth > checksum.sha256
|
||||
echo "Binaries SHA256 on ${{ matrix.machine }}: $(cat checksum.sha256)"
|
||||
|
||||
- name: Upload the hash
|
||||
uses: actions/upload-artifact@v6
|
||||
cargo install cargo-cache
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
name: checksum-${{ matrix.machine }}
|
||||
path: |
|
||||
checksum.sha256
|
||||
retention-days: 1
|
||||
|
||||
compare:
|
||||
name: compare reproducible binaries
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifacts from machine-1
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: checksum-machine-1
|
||||
path: machine-1/
|
||||
- name: Download artifacts from machine-2
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: checksum-machine-2
|
||||
path: machine-2/
|
||||
- name: Compare SHA256 hashes
|
||||
cache-on-failure: true
|
||||
- name: Build Reth
|
||||
run: |
|
||||
echo "=== SHA256 Comparison ==="
|
||||
echo "Machine 1 hash:"
|
||||
cat machine-1/checksum.sha256
|
||||
echo "Machine 2 hash:"
|
||||
cat machine-2/checksum.sha256
|
||||
|
||||
if cmp -s machine-1/checksum.sha256 machine-2/checksum.sha256; then
|
||||
echo "✅ SUCCESS: Binaries are identical (reproducible build verified)"
|
||||
else
|
||||
echo "❌ FAILURE: Binaries differ (reproducible build failed)"
|
||||
exit 1
|
||||
fi
|
||||
make build-reproducible
|
||||
mv target/x86_64-unknown-linux-gnu/release/reth reth-build-1
|
||||
- name: Clean cache
|
||||
run: make clean && cargo cache -a
|
||||
- name: Build Reth again
|
||||
run: |
|
||||
make build-reproducible
|
||||
mv target/x86_64-unknown-linux-gnu/release/reth reth-build-2
|
||||
- name: Compare binaries
|
||||
run: cmp reth-build-1 reth-build-2
|
||||
|
||||
7
.github/workflows/stage.yml
vendored
7
.github/workflows/stage.yml
vendored
@@ -12,7 +12,6 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
FROM_BLOCK: 0
|
||||
TO_BLOCK: 50000
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
@@ -23,16 +22,16 @@ jobs:
|
||||
name: stage-run-test
|
||||
# Only run stage commands test in merge groups
|
||||
if: github.event_name == 'merge_group'
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on:
|
||||
group: Reth
|
||||
env:
|
||||
RUST_LOG: info,sync=error
|
||||
RUST_BACKTRACE: 1
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
|
||||
9
.github/workflows/sync-era.yml
vendored
9
.github/workflows/sync-era.yml
vendored
@@ -9,7 +9,6 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
@@ -18,7 +17,8 @@ concurrency:
|
||||
jobs:
|
||||
sync:
|
||||
name: sync (${{ matrix.chain.bin }})
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on:
|
||||
group: Reth
|
||||
env:
|
||||
RUST_LOG: info,sync=error
|
||||
RUST_BACKTRACE: 1
|
||||
@@ -39,10 +39,9 @@ jobs:
|
||||
block: 10000
|
||||
unwind-target: "0x118a6e922a8c6cab221fc5adfe5056d2b72d58c6580e9c5629de55299e2cf8de"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -65,4 +64,4 @@ jobs:
|
||||
${{ matrix.chain.bin }} stage unwind num-blocks 100 --chain ${{ matrix.chain.chain }}
|
||||
- name: Run stage unwind to block hash
|
||||
run: |
|
||||
${{ matrix.chain.bin }} stage unwind to-block ${{ matrix.chain.unwind-target }} --chain ${{ matrix.chain.chain }}
|
||||
${{ matrix.chain.bin }} stage unwind to-block ${{ matrix.chain.unwind-target }} --chain ${{ matrix.chain.chain }}
|
||||
|
||||
9
.github/workflows/sync.yml
vendored
9
.github/workflows/sync.yml
vendored
@@ -9,7 +9,6 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
@@ -18,7 +17,8 @@ concurrency:
|
||||
jobs:
|
||||
sync:
|
||||
name: sync (${{ matrix.chain.bin }})
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on:
|
||||
group: Reth
|
||||
env:
|
||||
RUST_LOG: info,sync=error
|
||||
RUST_BACKTRACE: 1
|
||||
@@ -39,10 +39,9 @@ jobs:
|
||||
block: 10000
|
||||
unwind-target: "0x118a6e922a8c6cab221fc5adfe5056d2b72d58c6580e9c5629de55299e2cf8de"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -64,4 +63,4 @@ jobs:
|
||||
${{ matrix.chain.bin }} stage unwind num-blocks 100 --chain ${{ matrix.chain.chain }}
|
||||
- name: Run stage unwind to block hash
|
||||
run: |
|
||||
${{ matrix.chain.bin }} stage unwind to-block ${{ matrix.chain.unwind-target }} --chain ${{ matrix.chain.chain }}
|
||||
${{ matrix.chain.bin }} stage unwind to-block ${{ matrix.chain.unwind-target }} --chain ${{ matrix.chain.chain }}
|
||||
|
||||
48
.github/workflows/unit.yml
vendored
48
.github/workflows/unit.yml
vendored
@@ -11,7 +11,6 @@ on:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
SEED: rustethereumethereumrust
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
@@ -19,28 +18,35 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: test / ${{ matrix.type }} / ${{ matrix.storage }}
|
||||
runs-on: depot-ubuntu-latest-4
|
||||
name: test / ${{ matrix.type }} (${{ matrix.partition }}/${{ matrix.total_partitions }})
|
||||
runs-on:
|
||||
group: Reth
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
EDGE_FEATURES: ${{ matrix.storage == 'edge' && 'edge' || '' }}
|
||||
strategy:
|
||||
matrix:
|
||||
type: [ethereum, optimism]
|
||||
storage: [stable, edge]
|
||||
include:
|
||||
- type: ethereum
|
||||
features: asm-keccak ethereum
|
||||
exclude_args: ""
|
||||
args: --features "asm-keccak ethereum" --locked
|
||||
partition: 1
|
||||
total_partitions: 2
|
||||
- type: ethereum
|
||||
args: --features "asm-keccak ethereum" --locked
|
||||
partition: 2
|
||||
total_partitions: 2
|
||||
- type: optimism
|
||||
features: asm-keccak
|
||||
exclude_args: --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum"
|
||||
args: --features "asm-keccak" --locked --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum"
|
||||
partition: 1
|
||||
total_partitions: 2
|
||||
- type: optimism
|
||||
args: --features "asm-keccak" --locked --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum"
|
||||
partition: 2
|
||||
total_partitions: 2
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -52,22 +58,23 @@ jobs:
|
||||
- name: Run tests
|
||||
run: |
|
||||
cargo nextest run \
|
||||
--features "${{ matrix.features }} $EDGE_FEATURES" --locked \
|
||||
${{ matrix.exclude_args }} --workspace \
|
||||
${{ matrix.args }} --workspace \
|
||||
--exclude ef-tests --no-tests=warn \
|
||||
--partition hash:${{ matrix.partition }}/2 \
|
||||
-E "!kind(test) and not binary(e2e_testsuite)"
|
||||
|
||||
state:
|
||||
name: Ethereum state tests
|
||||
runs-on: depot-ubuntu-latest-4
|
||||
runs-on:
|
||||
group: Reth
|
||||
env:
|
||||
RUST_LOG: info,sync=error
|
||||
RUST_BACKTRACE: 1
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- name: Checkout ethereum/tests
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: ethereum/tests
|
||||
ref: 81862e4848585a438d64f911a19b3825f0f4cd95
|
||||
@@ -86,7 +93,6 @@ jobs:
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -94,15 +100,15 @@ jobs:
|
||||
|
||||
doc:
|
||||
name: doc tests
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on:
|
||||
group: Reth
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
|
||||
4
.github/workflows/update-superchain.yml
vendored
4
.github/workflows/update-superchain.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install required tools
|
||||
run: |
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
./fetch_superchain_config.sh
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
commit-message: "chore: update superchain config"
|
||||
title: "chore: update superchain config"
|
||||
|
||||
13
.github/workflows/windows.yml
vendored
13
.github/workflows/windows.yml
vendored
@@ -9,22 +9,18 @@ on:
|
||||
branches: [main]
|
||||
merge_group:
|
||||
|
||||
env:
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
jobs:
|
||||
check-reth:
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
target: x86_64-pc-windows-gnu
|
||||
- uses: taiki-e/install-action@cross
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -34,17 +30,16 @@ jobs:
|
||||
run: cargo check --target x86_64-pc-windows-gnu
|
||||
|
||||
check-op-reth:
|
||||
runs-on: depot-ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v5
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
target: x86_64-pc-windows-gnu
|
||||
- uses: taiki-e/install-action@cross
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,9 +12,6 @@ target/
|
||||
# Generated by Intellij-based IDEs.
|
||||
.idea
|
||||
|
||||
# ck-search metadata
|
||||
.ck
|
||||
|
||||
# Generated by MacOS
|
||||
.DS_Store
|
||||
|
||||
|
||||
81
CLAUDE.md
81
CLAUDE.md
@@ -18,7 +18,7 @@ Reth is a high-performance Ethereum execution client written in Rust, focusing o
|
||||
6. **Pipeline (`crates/stages/`)**: Staged sync architecture for blockchain synchronization
|
||||
7. **Trie (`crates/trie/`)**: Merkle Patricia Trie implementation with parallel state root computation
|
||||
8. **Node Builder (`crates/node/`)**: High-level node orchestration and configuration
|
||||
9. **The Consensus Engine (`crates/engine/`)**: Handles processing blocks received from the consensus layer with the Engine API (newPayload, forkchoiceUpdated)
|
||||
9 **The Consensus Engine (`crates/engine/`)**: Handles processing blocks received from the consensus layer with the Engine API (newPayload, forkchoiceUpdated)
|
||||
|
||||
### Key Design Principles
|
||||
|
||||
@@ -236,85 +236,6 @@ Common refactoring pattern:
|
||||
- Add trait bounds for flexibility
|
||||
- Enable reuse across different chain types (Ethereum, Optimism)
|
||||
|
||||
#### When to Comment
|
||||
|
||||
Write comments that remain valuable after the PR is merged. Future readers won't have PR context - they only see the current code.
|
||||
|
||||
##### ✅ DO: Add Value
|
||||
|
||||
**Explain WHY and non-obvious behavior:**
|
||||
```rust
|
||||
// Process must handle allocations atomically to prevent race conditions
|
||||
// between dealloc on drop and concurrent limit checks
|
||||
unsafe impl GlobalAlloc for LimitedAllocator { ... }
|
||||
|
||||
// Binary search requires sorted input. Panics on unsorted slices.
|
||||
fn find_index(items: &[Item], target: &Item) -> Option
|
||||
|
||||
// Timeout set to 5s to match EVM block processing limits
|
||||
const TRACER_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
```
|
||||
|
||||
**Document constraints and assumptions:**
|
||||
```rust
|
||||
/// Returns heap size estimate.
|
||||
///
|
||||
/// Note: May undercount shared references (Rc/Arc). For precise
|
||||
/// accounting, combine with an allocator-based approach.
|
||||
fn deep_size_of(&self) -> usize
|
||||
```
|
||||
|
||||
**Explain complex logic:**
|
||||
```rust
|
||||
// We reset limits at task start because tokio reuses threads in
|
||||
// spawn_blocking pool. Without reset, second task inherits first
|
||||
// task's allocation count and immediately hits limit.
|
||||
THREAD_ALLOCATED.with(|allocated| allocated.set(0));
|
||||
```
|
||||
|
||||
##### ❌ DON'T: Describe Changes
|
||||
```rust
|
||||
// ❌ BAD - Describes the change, not the code
|
||||
// Changed from Vec to HashMap for O(1) lookups
|
||||
|
||||
// ✅ GOOD - Explains the decision
|
||||
// HashMap provides O(1) symbol lookups during trace replay
|
||||
```
|
||||
```rust
|
||||
// ❌ BAD - PR-specific context
|
||||
// Fix for issue #234 where memory wasn't freed
|
||||
|
||||
// ✅ GOOD - Documents the actual behavior
|
||||
// Explicitly drop allocations before limit check to ensure
|
||||
// accurate accounting
|
||||
```
|
||||
```rust
|
||||
// ❌ BAD - States the obvious
|
||||
// Increment counter
|
||||
counter += 1;
|
||||
|
||||
// ✅ GOOD - Explains non-obvious purpose
|
||||
// Track allocations across all threads for global limit enforcement
|
||||
GLOBAL_COUNTER.fetch_add(1, Ordering::SeqCst);
|
||||
```
|
||||
|
||||
✅ **Comment when:**
|
||||
- Non-obvious behavior or edge cases
|
||||
- Performance trade-offs
|
||||
- Safety requirements (unsafe blocks must always be documented)
|
||||
- Limitations or gotchas
|
||||
- Why simpler alternatives don't work
|
||||
|
||||
❌ **Don't comment when:**
|
||||
- Code is self-explanatory
|
||||
- Just restating the code in English
|
||||
- Describing what changed in this PR
|
||||
|
||||
##### The Test: "Will this make sense in 6 months?"
|
||||
|
||||
Before adding a comment, ask: Would someone reading just the current code (no PR, no history) find this helpful?
|
||||
|
||||
|
||||
### Example Contribution Workflow
|
||||
|
||||
Let's say you want to fix a bug where external IP resolution fails on startup:
|
||||
|
||||
@@ -51,7 +51,9 @@ elsewhere.
|
||||
<!-- - **Asking in the support Telegram:** The [Foundry Support Telegram][support-tg] is a fast and easy way to ask questions. -->
|
||||
<!-- - **Opening a discussion:** This repository comes with a discussions board where you can also ask for help. Click the "Discussions" tab at the top. -->
|
||||
|
||||
If you have reviewed existing documentation and still have questions, or you are having problems, you can get help by **opening a discussion**. This repository comes with a discussions board where you can also ask for help. Click the "Discussions" tab at the top.
|
||||
If you have reviewed existing documentation and still have questions, or you are having problems, you can get help by *
|
||||
*opening a discussion**. This repository comes with a discussions board where you can also ask for help. Click the "
|
||||
Discussions" tab at the top.
|
||||
|
||||
As Reth is still in heavy development, the documentation can be a bit scattered. The [Reth Docs][reth-docs] is our
|
||||
current best-effort attempt at keeping up-to-date information.
|
||||
|
||||
3623
Cargo.lock
generated
3623
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
167
Cargo.toml
167
Cargo.toml
@@ -1,5 +1,5 @@
|
||||
[workspace.package]
|
||||
version = "1.9.3"
|
||||
version = "1.8.2"
|
||||
edition = "2024"
|
||||
rust-version = "1.88"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@@ -10,7 +10,6 @@ exclude = [".github/"]
|
||||
[workspace]
|
||||
members = [
|
||||
"bin/reth-bench/",
|
||||
"bin/reth-bench-compare/",
|
||||
"bin/reth/",
|
||||
"crates/storage/rpc-provider/",
|
||||
"crates/chain-state/",
|
||||
@@ -94,7 +93,6 @@ members = [
|
||||
"crates/payload/util/",
|
||||
"crates/primitives-traits/",
|
||||
"crates/primitives/",
|
||||
"crates/prune/db",
|
||||
"crates/prune/prune",
|
||||
"crates/prune/types",
|
||||
"crates/ress/protocol",
|
||||
@@ -148,12 +146,10 @@ members = [
|
||||
"examples/custom-node/",
|
||||
"examples/custom-engine-types/",
|
||||
"examples/custom-evm/",
|
||||
"examples/custom-hardforks/",
|
||||
"examples/custom-inspector/",
|
||||
"examples/custom-node-components/",
|
||||
"examples/custom-payload-builder/",
|
||||
"examples/custom-rlpx-subprotocol",
|
||||
"examples/custom-rpc-middleware",
|
||||
"examples/custom-node",
|
||||
"examples/db-access",
|
||||
"examples/engine-api-access",
|
||||
@@ -329,12 +325,6 @@ inherits = "release"
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
|
||||
[profile.reproducible]
|
||||
inherits = "release"
|
||||
panic = "abort"
|
||||
codegen-units = 1
|
||||
incremental = false
|
||||
|
||||
[workspace.dependencies]
|
||||
# reth
|
||||
op-reth = { path = "crates/optimism/bin" }
|
||||
@@ -342,7 +332,6 @@ reth = { path = "bin/reth" }
|
||||
reth-storage-rpc-provider = { path = "crates/storage/rpc-provider" }
|
||||
reth-basic-payload-builder = { path = "crates/payload/basic" }
|
||||
reth-bench = { path = "bin/reth-bench" }
|
||||
reth-bench-compare = { path = "bin/reth-bench-compare" }
|
||||
reth-chain-state = { path = "crates/chain-state" }
|
||||
reth-chainspec = { path = "crates/chainspec", default-features = false }
|
||||
reth-cli = { path = "crates/cli/cli" }
|
||||
@@ -376,11 +365,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-cli = { path = "crates/ethereum/cli" }
|
||||
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" }
|
||||
@@ -417,7 +406,7 @@ reth-optimism-node = { path = "crates/optimism/node" }
|
||||
reth-node-types = { path = "crates/node/types" }
|
||||
reth-op = { path = "crates/optimism/reth", default-features = false }
|
||||
reth-optimism-chainspec = { path = "crates/optimism/chainspec", default-features = false }
|
||||
reth-optimism-cli = { path = "crates/optimism/cli", default-features = false }
|
||||
reth-optimism-cli = { path = "crates/optimism/cli" }
|
||||
reth-optimism-consensus = { path = "crates/optimism/consensus", default-features = false }
|
||||
reth-optimism-forks = { path = "crates/optimism/hardforks", default-features = false }
|
||||
reth-optimism-payload-builder = { path = "crates/optimism/payload" }
|
||||
@@ -451,7 +440,7 @@ reth-rpc-convert = { path = "crates/rpc/rpc-convert" }
|
||||
reth-stages = { path = "crates/stages/stages" }
|
||||
reth-stages-api = { path = "crates/stages/api" }
|
||||
reth-stages-types = { path = "crates/stages/types", default-features = false }
|
||||
reth-stateless = { path = "crates/stateless", default-features = false }
|
||||
reth-stateless = { path = "crates/stateless" }
|
||||
reth-static-file = { path = "crates/static-file/static-file" }
|
||||
reth-static-file-types = { path = "crates/static-file/types", default-features = false }
|
||||
reth-storage-api = { path = "crates/storage/storage-api", default-features = false }
|
||||
@@ -459,8 +448,7 @@ reth-storage-errors = { path = "crates/storage/errors", default-features = false
|
||||
reth-tasks = { path = "crates/tasks" }
|
||||
reth-testing-utils = { path = "testing/testing-utils" }
|
||||
reth-tokio-util = { path = "crates/tokio-util" }
|
||||
reth-tracing = { path = "crates/tracing", default-features = false }
|
||||
reth-tracing-otlp = { path = "crates/tracing-otlp" }
|
||||
reth-tracing = { path = "crates/tracing" }
|
||||
reth-transaction-pool = { path = "crates/transaction-pool" }
|
||||
reth-trie = { path = "crates/trie/trie" }
|
||||
reth-trie-common = { path = "crates/trie/common", default-features = false }
|
||||
@@ -473,71 +461,72 @@ reth-ress-protocol = { path = "crates/ress/protocol" }
|
||||
reth-ress-provider = { path = "crates/ress/provider" }
|
||||
|
||||
# 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"
|
||||
revm = { version = "29.0.1", default-features = false }
|
||||
revm-bytecode = { version = "6.2.2", default-features = false }
|
||||
revm-database = { version = "7.0.5", default-features = false }
|
||||
revm-state = { version = "7.0.5", default-features = false }
|
||||
revm-primitives = { version = "20.2.1", default-features = false }
|
||||
revm-interpreter = { version = "25.0.3", default-features = false }
|
||||
revm-inspector = { version = "10.0.1", default-features = false }
|
||||
revm-context = { version = "9.1.0", default-features = false }
|
||||
revm-context-interface = { version = "10.2.0", default-features = false }
|
||||
revm-database-interface = { version = "7.0.5", default-features = false }
|
||||
op-revm = { version = "10.1.0", default-features = false }
|
||||
revm-inspectors = "0.30.0"
|
||||
|
||||
# eth
|
||||
alloy-chains = { version = "0.2.5", default-features = false }
|
||||
alloy-dyn-abi = "1.4.1"
|
||||
alloy-dyn-abi = "1.3.1"
|
||||
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-evm = { version = "0.21.0", default-features = false }
|
||||
alloy-primitives = { version = "1.3.1", 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 }
|
||||
alloy-sol-macro = "1.3.1"
|
||||
alloy-sol-types = { version = "1.3.1", default-features = false }
|
||||
alloy-trie = { version = "0.9.1", default-features = false }
|
||||
|
||||
alloy-hardforks = "0.4.5"
|
||||
alloy-hardforks = "0.3.5"
|
||||
|
||||
alloy-consensus = { version = "1.4.1", default-features = false }
|
||||
alloy-contract = { version = "1.4.1", default-features = false }
|
||||
alloy-eips = { version = "1.4.1", default-features = false }
|
||||
alloy-genesis = { version = "1.4.1", default-features = false }
|
||||
alloy-json-rpc = { version = "1.4.1", default-features = false }
|
||||
alloy-network = { version = "1.4.1", default-features = false }
|
||||
alloy-network-primitives = { version = "1.4.1", default-features = false }
|
||||
alloy-provider = { version = "1.4.1", features = ["reqwest", "debug-api"], default-features = false }
|
||||
alloy-pubsub = { version = "1.4.1", default-features = false }
|
||||
alloy-rpc-client = { version = "1.4.1", default-features = false }
|
||||
alloy-rpc-types = { version = "1.4.1", features = ["eth"], default-features = false }
|
||||
alloy-rpc-types-admin = { version = "1.4.1", default-features = false }
|
||||
alloy-rpc-types-anvil = { version = "1.4.1", default-features = false }
|
||||
alloy-rpc-types-beacon = { version = "1.4.1", default-features = false }
|
||||
alloy-rpc-types-debug = { version = "1.4.1", default-features = false }
|
||||
alloy-rpc-types-engine = { version = "1.4.1", default-features = false }
|
||||
alloy-rpc-types-eth = { version = "1.4.1", default-features = false }
|
||||
alloy-rpc-types-mev = { version = "1.4.1", default-features = false }
|
||||
alloy-rpc-types-trace = { version = "1.4.1", default-features = false }
|
||||
alloy-rpc-types-txpool = { version = "1.4.1", default-features = false }
|
||||
alloy-serde = { version = "1.4.1", default-features = false }
|
||||
alloy-signer = { version = "1.4.1", default-features = false }
|
||||
alloy-signer-local = { version = "1.4.1", default-features = false }
|
||||
alloy-transport = { version = "1.4.1" }
|
||||
alloy-transport-http = { version = "1.4.1", features = ["reqwest-rustls-tls"], default-features = false }
|
||||
alloy-transport-ipc = { version = "1.4.1", default-features = false }
|
||||
alloy-transport-ws = { version = "1.4.1", default-features = false }
|
||||
alloy-consensus = { version = "1.0.37", default-features = false }
|
||||
alloy-contract = { version = "1.0.37", default-features = false }
|
||||
alloy-eips = { version = "1.0.37", default-features = false }
|
||||
alloy-genesis = { version = "1.0.37", default-features = false }
|
||||
alloy-json-rpc = { version = "1.0.37", default-features = false }
|
||||
alloy-network = { version = "1.0.37", default-features = false }
|
||||
alloy-network-primitives = { version = "1.0.37", default-features = false }
|
||||
alloy-provider = { version = "1.0.37", features = ["reqwest"], default-features = false }
|
||||
alloy-pubsub = { version = "1.0.37", default-features = false }
|
||||
alloy-rpc-client = { version = "1.0.37", default-features = false }
|
||||
alloy-rpc-types = { version = "1.0.37", features = ["eth"], default-features = false }
|
||||
alloy-rpc-types-admin = { version = "1.0.37", default-features = false }
|
||||
alloy-rpc-types-anvil = { version = "1.0.37", default-features = false }
|
||||
alloy-rpc-types-beacon = { version = "1.0.37", default-features = false }
|
||||
alloy-rpc-types-debug = { version = "1.0.37", default-features = false }
|
||||
alloy-rpc-types-engine = { version = "1.0.37", default-features = false }
|
||||
alloy-rpc-types-eth = { version = "1.0.37", default-features = false }
|
||||
alloy-rpc-types-mev = { version = "1.0.37", default-features = false }
|
||||
alloy-rpc-types-trace = { version = "1.0.37", default-features = false }
|
||||
alloy-rpc-types-txpool = { version = "1.0.37", default-features = false }
|
||||
alloy-serde = { version = "1.0.37", default-features = false }
|
||||
alloy-signer = { version = "1.0.37", default-features = false }
|
||||
alloy-signer-local = { version = "1.0.37", default-features = false }
|
||||
alloy-transport = { version = "1.0.37" }
|
||||
alloy-transport-http = { version = "1.0.37", features = ["reqwest-rustls-tls"], default-features = false }
|
||||
alloy-transport-ipc = { version = "1.0.37", default-features = false }
|
||||
alloy-transport-ws = { version = "1.0.37", default-features = false }
|
||||
|
||||
# op
|
||||
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 }
|
||||
op-alloy-network = { version = "0.23.1", default-features = false }
|
||||
op-alloy-consensus = { version = "0.23.1", default-features = false }
|
||||
op-alloy-rpc-jsonrpsee = { version = "0.23.1", default-features = false }
|
||||
alloy-op-evm = { version = "0.21.0", default-features = false }
|
||||
alloy-op-hardforks = "0.3.5"
|
||||
op-alloy-rpc-types = { version = "0.20.0", default-features = false }
|
||||
op-alloy-rpc-types-engine = { version = "0.20.0", default-features = false }
|
||||
op-alloy-network = { version = "0.20.0", default-features = false }
|
||||
op-alloy-consensus = { version = "0.20.0", default-features = false }
|
||||
op-alloy-rpc-jsonrpsee = { version = "0.20.0", default-features = false }
|
||||
op-alloy-flz = { version = "0.13.1", default-features = false }
|
||||
|
||||
# misc
|
||||
either = { version = "1.15.0", default-features = false }
|
||||
arrayvec = { version = "0.7.6", default-features = false }
|
||||
aquamarine = "0.6"
|
||||
auto_impl = "1"
|
||||
backon = { version = "1.2", default-features = false, features = ["std-blocking-sleep", "tokio-sleep"] }
|
||||
@@ -548,14 +537,13 @@ bytes = { version = "1.5", default-features = false }
|
||||
brotli = "8"
|
||||
cfg-if = "1.0"
|
||||
clap = "4"
|
||||
color-eyre = "0.6"
|
||||
dashmap = "6.0"
|
||||
derive_more = { version = "2", default-features = false, features = ["full"] }
|
||||
dirs-next = "2.0.0"
|
||||
dyn-clone = "1.0.17"
|
||||
eyre = "0.6"
|
||||
fdlimit = "0.3.0"
|
||||
fixed-map = { version = "0.9", default-features = false }
|
||||
generic-array = "0.14"
|
||||
humantime = "2.1"
|
||||
humantime-serde = "1.1"
|
||||
itertools = { version = "0.14", default-features = false }
|
||||
@@ -576,7 +564,6 @@ serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
|
||||
serde_with = { version = "3", default-features = false, features = ["macros"] }
|
||||
sha2 = { version = "0.10", default-features = false }
|
||||
shellexpand = "3.0.0"
|
||||
shlex = "1.3"
|
||||
smallvec = "1"
|
||||
strum = { version = "0.27", default-features = false }
|
||||
strum_macros = "0.27"
|
||||
@@ -589,7 +576,6 @@ url = { version = "2.3", default-features = false }
|
||||
zstd = "0.13"
|
||||
byteorder = "1"
|
||||
mini-moka = "0.10"
|
||||
moka = "0.12"
|
||||
tar-no-std = { version = "0.3.2", default-features = false }
|
||||
miniz_oxide = { version = "0.8.4", default-features = false }
|
||||
chrono = "0.4.41"
|
||||
@@ -597,9 +583,9 @@ chrono = "0.4.41"
|
||||
# metrics
|
||||
metrics = "0.24.0"
|
||||
metrics-derive = "0.1"
|
||||
metrics-exporter-prometheus = { version = "0.18.0", default-features = false }
|
||||
metrics-exporter-prometheus = { version = "0.16.0", default-features = false }
|
||||
metrics-process = "2.1.0"
|
||||
metrics-util = { default-features = false, version = "0.20.0" }
|
||||
metrics-util = { default-features = false, version = "0.19.0" }
|
||||
|
||||
# proc-macros
|
||||
proc-macro2 = "1.0"
|
||||
@@ -612,7 +598,6 @@ tokio-tungstenite = "0.26.2"
|
||||
tokio-util = { version = "0.7.4", features = ["codec"] }
|
||||
|
||||
# async
|
||||
async-compression = { version = "0.4", default-features = false }
|
||||
async-stream = "0.3"
|
||||
async-trait = "0.1.68"
|
||||
futures = "0.3"
|
||||
@@ -627,8 +612,8 @@ tower = "0.5"
|
||||
tower-http = "0.6"
|
||||
|
||||
# p2p
|
||||
discv5 = "0.10"
|
||||
if-addrs = "0.14"
|
||||
discv5 = "0.9"
|
||||
if-addrs = "0.13"
|
||||
|
||||
# rpc
|
||||
jsonrpsee = "0.26.0"
|
||||
@@ -652,26 +637,15 @@ secp256k1 = { version = "0.30", default-features = false, features = ["global-co
|
||||
rand_08 = { package = "rand", version = "0.8" }
|
||||
|
||||
# for eip-4844
|
||||
c-kzg = "2.1.5"
|
||||
c-kzg = "2.1.4"
|
||||
|
||||
# config
|
||||
toml = "0.8"
|
||||
|
||||
# rocksdb
|
||||
rocksdb = { version = "0.24" }
|
||||
|
||||
# otlp obs
|
||||
opentelemetry_sdk = "0.31"
|
||||
opentelemetry = "0.31"
|
||||
opentelemetry-otlp = "0.31"
|
||||
opentelemetry-semantic-conventions = "0.31"
|
||||
tracing-opentelemetry = "0.32"
|
||||
|
||||
# misc-testing
|
||||
arbitrary = "1.3"
|
||||
assert_matches = "1.5.0"
|
||||
criterion = { package = "codspeed-criterion-compat", version = "2.7" }
|
||||
insta = "1.41"
|
||||
proptest = "1.7"
|
||||
proptest-derive = "0.5"
|
||||
similar-asserts = { version = "1.5.0", features = ["serde"] }
|
||||
@@ -685,7 +659,6 @@ ethereum_ssz = "0.9.0"
|
||||
ethereum_ssz_derive = "0.9.0"
|
||||
|
||||
# allocators
|
||||
jemalloc_pprof = { version = "0.8", default-features = false }
|
||||
tikv-jemalloc-ctl = "0.6"
|
||||
tikv-jemallocator = "0.6"
|
||||
tracy-client = "0.18.0"
|
||||
@@ -696,14 +669,14 @@ ahash = "0.8"
|
||||
anyhow = "1.0"
|
||||
bindgen = { version = "0.71", default-features = false }
|
||||
block-padding = "0.3.2"
|
||||
cc = "1.2.15"
|
||||
cc = "=1.2.15"
|
||||
cipher = "0.4.3"
|
||||
comfy-table = "7.0"
|
||||
concat-kdf = "0.1.0"
|
||||
convert_case = "0.7.0"
|
||||
crossbeam-channel = "0.5.13"
|
||||
crossterm = "0.28.0"
|
||||
csv = "1.3.0"
|
||||
ctrlc = "3.4"
|
||||
ctr = "0.9.2"
|
||||
data-encoding = "2"
|
||||
delegate = "0.13"
|
||||
@@ -733,9 +706,7 @@ socket2 = { version = "0.5", default-features = false }
|
||||
sysinfo = { version = "0.33", default-features = false }
|
||||
tracing-journald = "0.3"
|
||||
tracing-logfmt = "0.3.3"
|
||||
tracing-samply = "0.1"
|
||||
tracing-subscriber = { version = "0.3", default-features = false }
|
||||
tracing-tracy = "0.11"
|
||||
triehash = "0.8"
|
||||
typenum = "1.15.0"
|
||||
vergen = "9.0.4"
|
||||
@@ -743,9 +714,6 @@ 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/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
|
||||
# alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
|
||||
@@ -788,6 +756,3 @@ ipnet = "2.11"
|
||||
# jsonrpsee-server = { git = "https://github.com/paradigmxyz/jsonrpsee", branch = "matt/make-rpc-service-pub" }
|
||||
# jsonrpsee-http-client = { git = "https://github.com/paradigmxyz/jsonrpsee", branch = "matt/make-rpc-service-pub" }
|
||||
# jsonrpsee-types = { git = "https://github.com/paradigmxyz/jsonrpsee", branch = "matt/make-rpc-service-pub" }
|
||||
|
||||
# alloy-evm = { git = "https://github.com/alloy-rs/evm", rev = "a69f0b45a6b0286e16072cb8399e02ce6ceca353" }
|
||||
# alloy-op-evm = { git = "https://github.com/alloy-rs/evm", rev = "a69f0b45a6b0286e16072cb8399e02ce6ceca353" }
|
||||
|
||||
@@ -7,7 +7,7 @@ LABEL org.opencontainers.image.source=https://github.com/paradigmxyz/reth
|
||||
LABEL org.opencontainers.image.licenses="MIT OR Apache-2.0"
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y libclang-dev pkg-config
|
||||
RUN apt-get update && apt-get -y upgrade && apt-get install -y libclang-dev pkg-config
|
||||
|
||||
# Builds a cargo-chef plan
|
||||
FROM chef AS planner
|
||||
@@ -18,7 +18,7 @@ FROM chef AS builder
|
||||
COPY --from=planner /app/recipe.json recipe.json
|
||||
|
||||
# Build profile, release by default
|
||||
ARG BUILD_PROFILE=maxperf
|
||||
ARG BUILD_PROFILE=release
|
||||
ENV BUILD_PROFILE=$BUILD_PROFILE
|
||||
|
||||
# Extra Cargo flags
|
||||
|
||||
@@ -1,25 +1,20 @@
|
||||
ARG RUST_TOOLCHAIN=1.89.0
|
||||
FROM docker.io/rust:$RUST_TOOLCHAIN-trixie AS builder
|
||||
# Use the Rust 1.88 image based on Debian Bookworm
|
||||
FROM rust:1.88-bookworm AS builder
|
||||
|
||||
ARG PROFILE
|
||||
ARG VERSION
|
||||
# Switch to snapshot repository to pin dependencies
|
||||
RUN sed -i '/^# http/{N;s|^# \(http[^ ]*\)\nURIs: .*|# \1\nURIs: \1|}' /etc/apt/sources.list.d/debian.sources
|
||||
RUN apt-get -o Acquire::Check-Valid-Until=false update && \
|
||||
apt-get install -y \
|
||||
libjemalloc-dev \
|
||||
libclang-dev \
|
||||
mold
|
||||
# Install specific version of libclang-dev
|
||||
RUN apt-get update && apt-get install -y libclang-dev=1:14.0-55.7~deb12u1
|
||||
|
||||
# Copy the project to the container
|
||||
COPY ./ /app
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN RUSTFLAGS_REPRODUCIBLE_EXTRA="-Clink-arg=-fuse-ld=mold" make build-reth-reproducible && \
|
||||
PROFILE=${PROFILE:-reproducible} VERSION=$VERSION make build-deb-x86_64-unknown-linux-gnu
|
||||
|
||||
FROM scratch AS artifacts
|
||||
COPY --from=builder /app/target/x86_64-unknown-linux-gnu/reproducible/reth /reth
|
||||
COPY --from=builder /app/target/x86_64-unknown-linux-gnu/reproducible/*.deb /
|
||||
# Build the project with the reproducible settings
|
||||
RUN make build-reproducible
|
||||
|
||||
FROM gcr.io/distroless/cc-debian13:nonroot-239cdd2c8a6b275b6a6f6ed1428c57de2fff3e50
|
||||
COPY --from=artifacts /reth /reth
|
||||
RUN mv /app/target/x86_64-unknown-linux-gnu/release/reth /reth
|
||||
|
||||
# Create a minimal final image with just the binary
|
||||
FROM gcr.io/distroless/cc-debian12:nonroot-6755e21ccd99ddead6edc8106ba03888cbeed41a
|
||||
COPY --from=builder /reth /reth
|
||||
EXPOSE 30303 30303/udp 9001 8545 8546
|
||||
ENTRYPOINT [ "/reth" ]
|
||||
|
||||
@@ -14,7 +14,7 @@ RUN cargo chef prepare --recipe-path recipe.json
|
||||
FROM chef AS builder
|
||||
COPY --from=planner /app/recipe.json recipe.json
|
||||
|
||||
ARG BUILD_PROFILE=maxperf
|
||||
ARG BUILD_PROFILE=release
|
||||
ENV BUILD_PROFILE=$BUILD_PROFILE
|
||||
|
||||
ARG RUSTFLAGS=""
|
||||
@@ -31,7 +31,7 @@ RUN cargo build --profile $BUILD_PROFILE --features "$FEATURES" --bin op-reth --
|
||||
RUN ls -la /app/target/$BUILD_PROFILE/op-reth
|
||||
RUN cp /app/target/$BUILD_PROFILE/op-reth /app/op-reth
|
||||
|
||||
FROM ubuntu AS runtime
|
||||
FROM ubuntu:22.04 AS runtime
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y ca-certificates libssl-dev pkg-config strace && \
|
||||
|
||||
@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2022-2026 Reth Contributors
|
||||
Copyright 2022-2025 Reth Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2022-2026 Reth Contributors
|
||||
Copyright (c) 2022-2025 Reth Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
85
Makefile
85
Makefile
@@ -65,31 +65,37 @@ build: ## Build the reth binary into `target` directory.
|
||||
cargo build --bin reth --features "$(FEATURES)" --profile "$(PROFILE)"
|
||||
|
||||
# Environment variables for reproducible builds
|
||||
# Initialize RUSTFLAGS
|
||||
RUST_BUILD_FLAGS =
|
||||
# Enable static linking to ensure reproducibility across builds
|
||||
RUST_BUILD_FLAGS += --C target-feature=+crt-static
|
||||
# Set the linker to use static libgcc to ensure reproducibility across builds
|
||||
RUST_BUILD_FLAGS += -C link-arg=-static-libgcc
|
||||
# Remove build ID from the binary to ensure reproducibility across builds
|
||||
RUST_BUILD_FLAGS += -C link-arg=-Wl,--build-id=none
|
||||
# Remove metadata hash from symbol names to ensure reproducible builds
|
||||
RUST_BUILD_FLAGS += -C metadata=''
|
||||
# Set timestamp from last git commit for reproducible builds
|
||||
SOURCE_DATE ?= $(shell git log -1 --pretty=%ct)
|
||||
# Disable incremental compilation to avoid non-deterministic artifacts
|
||||
CARGO_INCREMENTAL_VAL = 0
|
||||
# Set C locale for consistent string handling and sorting
|
||||
LOCALE_VAL = C
|
||||
# Set UTC timezone for consistent time handling across builds
|
||||
TZ_VAL = UTC
|
||||
|
||||
# Extra RUSTFLAGS for reproducible builds. Can be overridden via the environment.
|
||||
RUSTFLAGS_REPRODUCIBLE_EXTRA ?=
|
||||
|
||||
# `reproducible` only supports reth on x86_64-unknown-linux-gnu
|
||||
build-%-reproducible:
|
||||
@if [ "$*" != "reth" ]; then \
|
||||
echo "Error: Reproducible builds are only supported for reth, not $*"; \
|
||||
exit 1; \
|
||||
fi
|
||||
.PHONY: build-reproducible
|
||||
build-reproducible: ## Build the reth binary into `target` directory with reproducible builds. Only works for x86_64-unknown-linux-gnu currently
|
||||
SOURCE_DATE_EPOCH=$(SOURCE_DATE) \
|
||||
RUSTFLAGS="-C symbol-mangling-version=v0 -C strip=none -C link-arg=-Wl,--build-id=none -C metadata='' --remap-path-prefix $$(pwd)=. $(RUSTFLAGS_REPRODUCIBLE_EXTRA)" \
|
||||
LC_ALL=C \
|
||||
TZ=UTC \
|
||||
JEMALLOC_OVERRIDE=/usr/lib/x86_64-linux-gnu/libjemalloc.a \
|
||||
cargo build --bin reth --features "$(FEATURES) jemalloc-unprefixed" --profile "reproducible" --locked --target x86_64-unknown-linux-gnu
|
||||
RUSTFLAGS="${RUST_BUILD_FLAGS} --remap-path-prefix $$(pwd)=." \
|
||||
CARGO_INCREMENTAL=${CARGO_INCREMENTAL_VAL} \
|
||||
LC_ALL=${LOCALE_VAL} \
|
||||
TZ=${TZ_VAL} \
|
||||
cargo build --bin reth --features "$(FEATURES)" --profile "release" --locked --target x86_64-unknown-linux-gnu
|
||||
|
||||
.PHONY: build-debug
|
||||
build-debug: ## Build the reth binary into `target/debug` directory.
|
||||
cargo build --bin reth --features "$(FEATURES)"
|
||||
.PHONY: build-debug-op
|
||||
build-debug-op: ## Build the op-reth binary into `target/debug` directory.
|
||||
cargo build --bin op-reth --features "$(FEATURES)" --manifest-path crates/optimism/bin/Cargo.toml
|
||||
|
||||
.PHONY: build-op
|
||||
build-op: ## Build the op-reth binary into `target` directory.
|
||||
@@ -149,22 +155,6 @@ op-build-x86_64-apple-darwin:
|
||||
op-build-aarch64-apple-darwin:
|
||||
$(MAKE) op-build-native-aarch64-apple-darwin
|
||||
|
||||
build-deb-%:
|
||||
@case "$*" in \
|
||||
x86_64-unknown-linux-gnu|aarch64-unknown-linux-gnu|riscv64gc-unknown-linux-gnu) \
|
||||
echo "Building debian package for $*"; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Error: Debian packages are only supported for x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, and riscv64gc-unknown-linux-gnu, not $*"; \
|
||||
exit 1; \
|
||||
;; \
|
||||
esac
|
||||
cargo install cargo-deb@3.6.0 --locked
|
||||
cargo deb --profile $(PROFILE) --no-build --no-dbgsym --no-strip \
|
||||
--target $* \
|
||||
$(if $(VERSION),--deb-version "1~$(VERSION)") \
|
||||
$(if $(VERSION),--output "target/$*/$(PROFILE)/reth-$(VERSION)-$*-$(PROFILE).deb")
|
||||
|
||||
# Create a `.tar.gz` containing a binary for a specific target.
|
||||
define tarball_release_binary
|
||||
cp $(CARGO_TARGET_DIR)/$(1)/$(PROFILE)/$(2) $(BIN_DIR)/$(2)
|
||||
@@ -276,18 +266,13 @@ docker-build-push-latest: ## Build and push a cross-arch Docker image tagged wit
|
||||
docker-build-push-nightly: ## Build and push cross-arch Docker image tagged with the latest git tag with a `-nightly` suffix, and `latest-nightly`.
|
||||
$(call docker_build_push,nightly,nightly)
|
||||
|
||||
.PHONY: docker-build-push-nightly-edge-profiling
|
||||
docker-build-push-nightly-edge-profiling: FEATURES := $(FEATURES) edge
|
||||
docker-build-push-nightly-edge-profiling: ## Build and push cross-arch Docker image with edge features tagged with `nightly-edge-profiling`.
|
||||
$(call docker_build_push,nightly-edge-profiling,nightly-edge-profiling)
|
||||
|
||||
# Create a cross-arch Docker image with the given tags and push it
|
||||
define docker_build_push
|
||||
$(MAKE) FEATURES="$(FEATURES)" build-x86_64-unknown-linux-gnu
|
||||
$(MAKE) build-x86_64-unknown-linux-gnu
|
||||
mkdir -p $(BIN_DIR)/amd64
|
||||
cp $(CARGO_TARGET_DIR)/x86_64-unknown-linux-gnu/$(PROFILE)/reth $(BIN_DIR)/amd64/reth
|
||||
|
||||
$(MAKE) FEATURES="$(FEATURES)" build-aarch64-unknown-linux-gnu
|
||||
$(MAKE) build-aarch64-unknown-linux-gnu
|
||||
mkdir -p $(BIN_DIR)/arm64
|
||||
cp $(CARGO_TARGET_DIR)/aarch64-unknown-linux-gnu/$(PROFILE)/reth $(BIN_DIR)/arm64/reth
|
||||
|
||||
@@ -333,11 +318,6 @@ op-docker-build-push-latest: ## Build and push a cross-arch Docker image tagged
|
||||
op-docker-build-push-nightly: ## Build and push cross-arch Docker image tagged with the latest git tag with a `-nightly` suffix, and `latest-nightly`.
|
||||
$(call op_docker_build_push,nightly,nightly)
|
||||
|
||||
.PHONY: op-docker-build-push-nightly-edge-profiling
|
||||
op-docker-build-push-nightly-edge-profiling: FEATURES := $(FEATURES) edge
|
||||
op-docker-build-push-nightly-edge-profiling: ## Build and push cross-arch Docker image with edge features tagged with `nightly-edge-profiling`.
|
||||
$(call op_docker_build_push,nightly-edge-profiling,nightly-edge-profiling)
|
||||
|
||||
# Note: This requires a buildx builder with emulation support. For example:
|
||||
#
|
||||
# `docker run --privileged --rm tonistiigi/binfmt --install amd64,arm64`
|
||||
@@ -357,11 +337,11 @@ op-docker-build-push-nightly-profiling: ## Build and push cross-arch Docker imag
|
||||
|
||||
# Create a cross-arch Docker image with the given tags and push it
|
||||
define op_docker_build_push
|
||||
$(MAKE) FEATURES="$(FEATURES)" op-build-x86_64-unknown-linux-gnu
|
||||
$(MAKE) op-build-x86_64-unknown-linux-gnu
|
||||
mkdir -p $(BIN_DIR)/amd64
|
||||
cp $(CARGO_TARGET_DIR)/x86_64-unknown-linux-gnu/$(PROFILE)/op-reth $(BIN_DIR)/amd64/op-reth
|
||||
|
||||
$(MAKE) FEATURES="$(FEATURES)" op-build-aarch64-unknown-linux-gnu
|
||||
$(MAKE) op-build-aarch64-unknown-linux-gnu
|
||||
mkdir -p $(BIN_DIR)/arm64
|
||||
cp $(CARGO_TARGET_DIR)/aarch64-unknown-linux-gnu/$(PROFILE)/op-reth $(BIN_DIR)/arm64/op-reth
|
||||
|
||||
@@ -400,9 +380,9 @@ db-tools: ## Compile MDBX debugging tools.
|
||||
@echo "Run \"$(DB_TOOLS_DIR)/mdbx_chk\" for the MDBX db file integrity check."
|
||||
|
||||
.PHONY: update-book-cli
|
||||
update-book-cli: build-debug build-debug-op## Update book cli documentation.
|
||||
update-book-cli: build-debug ## Update book cli documentation.
|
||||
@echo "Updating book cli doc..."
|
||||
@./docs/cli/update.sh $(CARGO_TARGET_DIR)/debug/reth $(CARGO_TARGET_DIR)/debug/op-reth
|
||||
@./docs/cli/update.sh $(CARGO_TARGET_DIR)/debug/reth
|
||||
|
||||
.PHONY: profiling
|
||||
profiling: ## Builds `reth` with optimisations, but also symbols.
|
||||
@@ -531,3 +511,10 @@ pr:
|
||||
make update-book-cli && \
|
||||
cargo docs --document-private-items && \
|
||||
make test
|
||||
|
||||
check-features:
|
||||
cargo hack check \
|
||||
--package reth-codecs \
|
||||
--package reth-primitives-traits \
|
||||
--package reth-primitives \
|
||||
--feature-powerset
|
||||
|
||||
14
README.md
14
README.md
@@ -20,7 +20,7 @@
|
||||
|
||||
## What is Reth?
|
||||
|
||||
Reth (short for Rust Ethereum, [pronunciation](https://x.com/kelvinfichter/status/1597653609411268608)) is a new Ethereum full node implementation that is focused on being user-friendly, highly modular, as well as being fast and efficient. Reth is an Execution Layer (EL) and is compatible with all Ethereum Consensus Layer (CL) implementations that support the [Engine API](https://github.com/ethereum/execution-apis/tree/a0d03086564ab1838b462befbc083f873dcf0c0f/src/engine). It is originally built and driven forward by [Paradigm](https://paradigm.xyz/), and is licensed under the Apache and MIT licenses.
|
||||
Reth (short for Rust Ethereum, [pronunciation](https://twitter.com/kelvinfichter/status/1597653609411268608)) is a new Ethereum full node implementation that is focused on being user-friendly, highly modular, as well as being fast and efficient. Reth is an Execution Layer (EL) and is compatible with all Ethereum Consensus Layer (CL) implementations that support the [Engine API](https://github.com/ethereum/execution-apis/tree/a0d03086564ab1838b462befbc083f873dcf0c0f/src/engine). It is originally built and driven forward by [Paradigm](https://paradigm.xyz/), and is licensed under the Apache and MIT licenses.
|
||||
|
||||
## Goals
|
||||
|
||||
@@ -43,8 +43,8 @@ More historical context below:
|
||||
|
||||
- We released 1.0 "production-ready" stable Reth in June 2024.
|
||||
- Reth completed an audit with [Sigma Prime](https://sigmaprime.io/), the developers of [Lighthouse](https://github.com/sigp/lighthouse), the Rust Consensus Layer implementation. Find it [here](./audit/sigma_prime_audit_v2.pdf).
|
||||
- Revm (the EVM used in Reth) underwent an audit with [Guido Vranken](https://x.com/guidovranken) (#1 [Ethereum Bug Bounty](https://ethereum.org/en/bug-bounty)). We will publish the results soon.
|
||||
- We released multiple iterative beta versions, up to [beta.9](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.9) on Monday June 3, 2024, the last beta release.
|
||||
- Revm (the EVM used in Reth) underwent an audit with [Guido Vranken](https://twitter.com/guidovranken) (#1 [Ethereum Bug Bounty](https://ethereum.org/en/bug-bounty)). We will publish the results soon.
|
||||
- We released multiple iterative beta versions, up to [beta.9](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.9) on Monday June 3, 2024,the last beta release.
|
||||
- We released [beta](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.1) on Monday March 4, 2024, our first breaking change to the database model, providing faster query speed, smaller database footprint, and allowing "history" to be mounted on separate drives.
|
||||
- We shipped iterative improvements until the last alpha release on February 28, 2024, [0.1.0-alpha.21](https://github.com/paradigmxyz/reth/releases/tag/v0.1.0-alpha.21).
|
||||
- We [initially announced](https://www.paradigm.xyz/2023/06/reth-alpha) [0.1.0-alpha.1](https://github.com/paradigmxyz/reth/releases/tag/v0.1.0-alpha.1) on June 20, 2023.
|
||||
@@ -61,7 +61,7 @@ If you had a database produced by alpha versions of Reth, you need to drop it wi
|
||||
|
||||
## For Users
|
||||
|
||||
See the [Reth documentation](https://reth.rs/) for instructions on how to install and run Reth.
|
||||
See the [Reth documentation](https://paradigmxyz.github.io/reth) for instructions on how to install and run Reth.
|
||||
|
||||
## For Developers
|
||||
|
||||
@@ -69,7 +69,7 @@ See the [Reth documentation](https://reth.rs/) for instructions on how to instal
|
||||
|
||||
You can use individual crates of reth in your project.
|
||||
|
||||
The crate docs can be found [here](https://reth.rs/docs/).
|
||||
The crate docs can be found [here](https://paradigmxyz.github.io/reth/docs).
|
||||
|
||||
For a general overview of the crates, see [Project Layout](./docs/repo/layout.md).
|
||||
|
||||
@@ -90,7 +90,7 @@ When updating this, also update:
|
||||
|
||||
The Minimum Supported Rust Version (MSRV) of this project is [1.88.0](https://blog.rust-lang.org/2025/06/26/Rust-1.88.0/).
|
||||
|
||||
See the docs for detailed instructions on how to [build from source](https://reth.rs/installation/source/).
|
||||
See the docs for detailed instructions on how to [build from source](https://paradigmxyz.github.io/reth/installation/source).
|
||||
|
||||
To fully test Reth, you will need to have [Geth installed](https://geth.ethereum.org/docs/getting-started/installing-geth), but it is possible to run a subset of tests without Geth.
|
||||
|
||||
@@ -145,5 +145,5 @@ None of this would have been possible without them, so big shoutout to the teams
|
||||
|
||||
The `NippyJar` and `Compact` encoding formats and their implementations are designed for storing and retrieving data internally. They are not hardened to safely read potentially malicious data.
|
||||
|
||||
[book]: https://reth.rs/
|
||||
[book]: https://paradigmxyz.github.io/reth/
|
||||
[tg-url]: https://t.me/paradigm_reth
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
[package]
|
||||
name = "reth-bench-compare"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Automated reth benchmark comparison between git references"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "reth-bench-compare"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
# reth
|
||||
reth-cli-runner.workspace = true
|
||||
reth-cli-util.workspace = true
|
||||
reth-node-core.workspace = true
|
||||
reth-tracing.workspace = true
|
||||
reth-chainspec.workspace = true
|
||||
|
||||
# alloy
|
||||
alloy-provider = { workspace = true, features = ["reqwest-rustls-tls"], default-features = false }
|
||||
alloy-rpc-client = { workspace = true, features = ["pubsub"] }
|
||||
alloy-rpc-types-eth.workspace = true
|
||||
alloy-transport-ws.workspace = true
|
||||
alloy-primitives.workspace = true
|
||||
|
||||
# CLI and argument parsing
|
||||
clap = { workspace = true, features = ["derive", "env"] }
|
||||
eyre.workspace = true
|
||||
|
||||
# Async runtime
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
tracing.workspace = true
|
||||
|
||||
# Serialization
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
|
||||
# Time handling
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
|
||||
# Path manipulation
|
||||
shellexpand.workspace = true
|
||||
|
||||
# CSV handling
|
||||
csv.workspace = true
|
||||
|
||||
# Process management
|
||||
ctrlc.workspace = true
|
||||
shlex.workspace = true
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = { version = "0.29", features = ["signal", "process"] }
|
||||
|
||||
[features]
|
||||
default = ["jemalloc"]
|
||||
|
||||
asm-keccak = [
|
||||
"reth-node-core/asm-keccak",
|
||||
"alloy-primitives/asm-keccak",
|
||||
]
|
||||
|
||||
jemalloc = [
|
||||
"reth-cli-util/jemalloc",
|
||||
"reth-node-core/jemalloc",
|
||||
]
|
||||
jemalloc-prof = ["reth-cli-util/jemalloc-prof"]
|
||||
tracy-allocator = ["reth-cli-util/tracy-allocator", "tracy"]
|
||||
tracy = [
|
||||
"reth-node-core/tracy",
|
||||
"reth-tracing/tracy",
|
||||
]
|
||||
|
||||
min-error-logs = [
|
||||
"tracing/release_max_level_error",
|
||||
"reth-node-core/min-error-logs",
|
||||
]
|
||||
min-warn-logs = [
|
||||
"tracing/release_max_level_warn",
|
||||
"reth-node-core/min-warn-logs",
|
||||
]
|
||||
min-info-logs = [
|
||||
"tracing/release_max_level_info",
|
||||
"reth-node-core/min-info-logs",
|
||||
]
|
||||
min-debug-logs = [
|
||||
"tracing/release_max_level_debug",
|
||||
"reth-node-core/min-debug-logs",
|
||||
]
|
||||
min-trace-logs = [
|
||||
"tracing/release_max_level_trace",
|
||||
"reth-node-core/min-trace-logs",
|
||||
]
|
||||
|
||||
# no-op feature flag for switching between the `optimism` and default functionality in CI matrices
|
||||
ethereum = []
|
||||
@@ -1,50 +0,0 @@
|
||||
# reth-bench-compare
|
||||
|
||||
Compare reth performance between two git references.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
reth-bench-compare \
|
||||
--baseline-ref main \
|
||||
--feature-ref my-feature \
|
||||
--blocks 100 \
|
||||
--wait-for-persistence
|
||||
```
|
||||
|
||||
## Arguments
|
||||
|
||||
| Argument | Description | Default | Required |
|
||||
|----------|-------------|---------|----------|
|
||||
| `--baseline-ref <REF>` | Git reference for baseline | - | Yes |
|
||||
| `--feature-ref <REF>` | Git reference to compare | - | Yes |
|
||||
| `--blocks <N>` | Number of blocks to benchmark | `100` | No |
|
||||
| `--chain <CHAIN>` | Chain to benchmark | `mainnet` | No |
|
||||
| `--datadir <PATH>` | Data directory path | OS-specific | No |
|
||||
| `--rpc-url <URL>` | RPC endpoint for block data | Chain default | No |
|
||||
| `--output-dir <PATH>` | Output directory | `./reth-bench-compare` | No |
|
||||
| `--wait-for-persistence` | Wait for block persistence | `false` | No |
|
||||
| `--persistence-threshold <N>` | Wait after every N+1 blocks | `2` | No |
|
||||
| `--wait-time <DURATION>` | Fixed delay (legacy) | - | No |
|
||||
| `--warmup-blocks <N>` | Cache warmup blocks | Same as `--blocks` | No |
|
||||
| `--draw` | Generate charts (needs Python/uv) | `false` | No |
|
||||
| `--profile` | Enable CPU profiling (needs samply) | `false` | No |
|
||||
| `-vvvv` | Debug logging | Info | No |
|
||||
| `--features <FEATURES>` | Rust features for both builds | `jemalloc,asm-keccak` | No |
|
||||
| `--rustflags <FLAGS>` | RUSTFLAGS for both builds | `-C target-cpu=native` | No |
|
||||
| `--baseline-features <FEATURES>` | Features for baseline only | Inherits `--features` | No |
|
||||
| `--feature-features <FEATURES>` | Features for feature only | Inherits `--features` | No |
|
||||
| `--baseline-rustflags <FLAGS>` | RUSTFLAGS for baseline only | Inherits `--rustflags` | No |
|
||||
| `--feature-rustflags <FLAGS>` | RUSTFLAGS for feature only | Inherits `--rustflags` | No |
|
||||
| `--baseline-args <ARGS>` | Extra args for baseline node | - | No |
|
||||
| `--feature-args <ARGS>` | Extra args for feature node | - | No |
|
||||
| `--metrics-port <PORT>` | Metrics endpoint port | `5005` | No |
|
||||
| `--sudo` | Run with elevated privileges | `false` | No |
|
||||
|
||||
## Output
|
||||
|
||||
Results in `./reth-bench-compare/results/<timestamp>/`:
|
||||
- `comparison_report.json` - Metrics comparison
|
||||
- `per_block_comparison.csv` - Per-block statistics
|
||||
- `baseline/` and `feature/` - Individual run results
|
||||
- `latency_comparison.png` - Chart (if `--draw` used)
|
||||
@@ -1,305 +0,0 @@
|
||||
//! Benchmark execution using reth-bench.
|
||||
|
||||
use crate::cli::Args;
|
||||
use eyre::{eyre, Result, WrapErr};
|
||||
use std::{
|
||||
path::Path,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use tokio::{
|
||||
fs::File as AsyncFile,
|
||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
|
||||
process::Command,
|
||||
};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
/// Manages benchmark execution using reth-bench
|
||||
pub(crate) struct BenchmarkRunner {
|
||||
rpc_url: String,
|
||||
jwt_secret: String,
|
||||
wait_time: Option<String>,
|
||||
wait_for_persistence: bool,
|
||||
persistence_threshold: Option<u64>,
|
||||
warmup_blocks: u64,
|
||||
}
|
||||
|
||||
impl BenchmarkRunner {
|
||||
/// Create a new `BenchmarkRunner` from CLI arguments
|
||||
pub(crate) fn new(args: &Args) -> Self {
|
||||
Self {
|
||||
rpc_url: args.get_rpc_url(),
|
||||
jwt_secret: args.jwt_secret_path().to_string_lossy().to_string(),
|
||||
wait_time: args.wait_time.clone(),
|
||||
wait_for_persistence: args.wait_for_persistence,
|
||||
persistence_threshold: args.persistence_threshold,
|
||||
warmup_blocks: args.get_warmup_blocks(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear filesystem caches (page cache, dentries, and inodes)
|
||||
pub(crate) async fn clear_fs_caches() -> Result<()> {
|
||||
info!("Clearing filesystem caches...");
|
||||
|
||||
// First sync to ensure all pending writes are flushed
|
||||
let sync_output =
|
||||
Command::new("sync").output().await.wrap_err("Failed to execute sync command")?;
|
||||
|
||||
if !sync_output.status.success() {
|
||||
return Err(eyre!("sync command failed"));
|
||||
}
|
||||
|
||||
// Drop caches - requires sudo/root permissions
|
||||
// 3 = drop pagecache, dentries, and inodes
|
||||
let drop_caches_cmd = Command::new("sudo")
|
||||
.args(["-n", "sh", "-c", "echo 3 > /proc/sys/vm/drop_caches"])
|
||||
.output()
|
||||
.await;
|
||||
|
||||
match drop_caches_cmd {
|
||||
Ok(output) if output.status.success() => {
|
||||
info!("Successfully cleared filesystem caches");
|
||||
Ok(())
|
||||
}
|
||||
Ok(output) => {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
if stderr.contains("sudo: a password is required") {
|
||||
warn!("Unable to clear filesystem caches: sudo password required");
|
||||
warn!(
|
||||
"For optimal benchmarking, configure passwordless sudo for cache clearing:"
|
||||
);
|
||||
warn!(" echo '$USER ALL=(ALL) NOPASSWD: /bin/sh -c echo\\\\ [0-9]\\\\ \\\\>\\\\ /proc/sys/vm/drop_caches' | sudo tee /etc/sudoers.d/drop_caches");
|
||||
Ok(())
|
||||
} else {
|
||||
Err(eyre!("Failed to clear filesystem caches: {}", stderr))
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Unable to clear filesystem caches: {}", e);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a warmup benchmark for cache warming
|
||||
pub(crate) async fn run_warmup(&self, from_block: u64) -> Result<()> {
|
||||
let to_block = from_block + self.warmup_blocks;
|
||||
info!(
|
||||
"Running warmup benchmark from block {} to {} ({} blocks)",
|
||||
from_block, to_block, self.warmup_blocks
|
||||
);
|
||||
|
||||
// Build the reth-bench command for warmup (no output flag)
|
||||
let mut cmd = Command::new("reth-bench");
|
||||
cmd.args([
|
||||
"new-payload-fcu",
|
||||
"--rpc-url",
|
||||
&self.rpc_url,
|
||||
"--jwt-secret",
|
||||
&self.jwt_secret,
|
||||
"--from",
|
||||
&from_block.to_string(),
|
||||
"--to",
|
||||
&to_block.to_string(),
|
||||
"--wait-time=0ms", // Warmup should avoid persistence waits.
|
||||
]);
|
||||
|
||||
cmd.env("RUST_LOG_STYLE", "never")
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.kill_on_drop(true);
|
||||
|
||||
// Set process group for consistent signal handling
|
||||
#[cfg(unix)]
|
||||
{
|
||||
cmd.process_group(0);
|
||||
}
|
||||
|
||||
debug!("Executing warmup reth-bench command: {:?}", cmd);
|
||||
|
||||
// Execute the warmup benchmark
|
||||
let mut child = cmd.spawn().wrap_err("Failed to start warmup reth-bench process")?;
|
||||
|
||||
// Stream output at debug level
|
||||
if let Some(stdout) = child.stdout.take() {
|
||||
tokio::spawn(async move {
|
||||
let reader = BufReader::new(stdout);
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
debug!("[WARMUP] {}", line);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(stderr) = child.stderr.take() {
|
||||
tokio::spawn(async move {
|
||||
let reader = BufReader::new(stderr);
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
debug!("[WARMUP] {}", line);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let status = child.wait().await.wrap_err("Failed to wait for warmup reth-bench")?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(eyre!("Warmup reth-bench failed with exit code: {:?}", status.code()));
|
||||
}
|
||||
|
||||
info!("Warmup completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run a benchmark for the specified block range
|
||||
pub(crate) async fn run_benchmark(
|
||||
&self,
|
||||
from_block: u64,
|
||||
to_block: u64,
|
||||
output_dir: &Path,
|
||||
) -> Result<()> {
|
||||
info!(
|
||||
"Running benchmark from block {} to {} (output: {:?})",
|
||||
from_block, to_block, output_dir
|
||||
);
|
||||
|
||||
// Ensure output directory exists
|
||||
std::fs::create_dir_all(output_dir)
|
||||
.wrap_err_with(|| format!("Failed to create output directory: {output_dir:?}"))?;
|
||||
|
||||
// Create log file path for reth-bench output
|
||||
let log_file_path = output_dir.join("reth_bench.log");
|
||||
info!("reth-bench logs will be saved to: {:?}", log_file_path);
|
||||
|
||||
// Build the reth-bench command
|
||||
let mut cmd = Command::new("reth-bench");
|
||||
cmd.args([
|
||||
"new-payload-fcu",
|
||||
"--rpc-url",
|
||||
&self.rpc_url,
|
||||
"--jwt-secret",
|
||||
&self.jwt_secret,
|
||||
"--from",
|
||||
&from_block.to_string(),
|
||||
"--to",
|
||||
&to_block.to_string(),
|
||||
"--output",
|
||||
&output_dir.to_string_lossy(),
|
||||
]);
|
||||
|
||||
// Configure wait mode: wait-time takes precedence over persistence-based flow
|
||||
if let Some(ref wait_time) = self.wait_time {
|
||||
cmd.args(["--wait-time", wait_time]);
|
||||
} else if self.wait_for_persistence {
|
||||
cmd.arg("--wait-for-persistence");
|
||||
|
||||
// Add persistence threshold if specified
|
||||
if let Some(threshold) = self.persistence_threshold {
|
||||
cmd.args(["--persistence-threshold", &threshold.to_string()]);
|
||||
}
|
||||
}
|
||||
|
||||
cmd.env("RUST_LOG_STYLE", "never")
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.kill_on_drop(true);
|
||||
|
||||
// Set process group for consistent signal handling
|
||||
#[cfg(unix)]
|
||||
{
|
||||
cmd.process_group(0);
|
||||
}
|
||||
|
||||
// Debug log the command
|
||||
debug!("Executing reth-bench command: {:?}", cmd);
|
||||
|
||||
// Execute the benchmark
|
||||
let mut child = cmd.spawn().wrap_err("Failed to start reth-bench process")?;
|
||||
|
||||
// Capture stdout and stderr for error reporting
|
||||
let stdout_lines = Arc::new(Mutex::new(Vec::new()));
|
||||
let stderr_lines = Arc::new(Mutex::new(Vec::new()));
|
||||
|
||||
// Stream stdout with prefix at debug level, capture for error reporting, and write to log
|
||||
// file
|
||||
if let Some(stdout) = child.stdout.take() {
|
||||
let stdout_lines_clone = stdout_lines.clone();
|
||||
let log_file = AsyncFile::create(&log_file_path)
|
||||
.await
|
||||
.wrap_err(format!("Failed to create log file: {:?}", log_file_path))?;
|
||||
tokio::spawn(async move {
|
||||
let reader = BufReader::new(stdout);
|
||||
let mut lines = reader.lines();
|
||||
let mut log_file = log_file;
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
debug!("[RETH-BENCH] {}", line);
|
||||
if let Ok(mut captured) = stdout_lines_clone.lock() {
|
||||
captured.push(line.clone());
|
||||
}
|
||||
// Write to log file (reth-bench output already has timestamps if needed)
|
||||
let log_line = format!("{}\n", line);
|
||||
if let Err(e) = log_file.write_all(log_line.as_bytes()).await {
|
||||
debug!("Failed to write to log file: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Stream stderr with prefix at debug level, capture for error reporting, and write to log
|
||||
// file
|
||||
if let Some(stderr) = child.stderr.take() {
|
||||
let stderr_lines_clone = stderr_lines.clone();
|
||||
let log_file = AsyncFile::options()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&log_file_path)
|
||||
.await
|
||||
.wrap_err(format!("Failed to open log file for stderr: {:?}", log_file_path))?;
|
||||
tokio::spawn(async move {
|
||||
let reader = BufReader::new(stderr);
|
||||
let mut lines = reader.lines();
|
||||
let mut log_file = log_file;
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
debug!("[RETH-BENCH] {}", line);
|
||||
if let Ok(mut captured) = stderr_lines_clone.lock() {
|
||||
captured.push(line.clone());
|
||||
}
|
||||
// Write to log file (reth-bench output already has timestamps if needed)
|
||||
let log_line = format!("{}\n", line);
|
||||
if let Err(e) = log_file.write_all(log_line.as_bytes()).await {
|
||||
debug!("Failed to write to log file: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let status = child.wait().await.wrap_err("Failed to wait for reth-bench")?;
|
||||
|
||||
if !status.success() {
|
||||
// Print all captured output when command fails
|
||||
error!("reth-bench failed with exit code: {:?}", status.code());
|
||||
|
||||
if let Ok(stdout) = stdout_lines.lock() &&
|
||||
!stdout.is_empty()
|
||||
{
|
||||
error!("reth-bench stdout:");
|
||||
for line in stdout.iter() {
|
||||
error!(" {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(stderr) = stderr_lines.lock() &&
|
||||
!stderr.is_empty()
|
||||
{
|
||||
error!("reth-bench stderr:");
|
||||
for line in stderr.iter() {
|
||||
error!(" {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
return Err(eyre!("reth-bench failed with exit code: {:?}", status.code()));
|
||||
}
|
||||
|
||||
info!("Benchmark completed");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,723 +0,0 @@
|
||||
//! Results comparison and report generation.
|
||||
|
||||
use crate::cli::Args;
|
||||
use chrono::{DateTime, Utc};
|
||||
use csv::Reader;
|
||||
use eyre::{eyre, Result, WrapErr};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::HashMap,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use tracing::{info, warn};
|
||||
|
||||
/// Manages comparison between baseline and feature reference results
|
||||
pub(crate) struct ComparisonGenerator {
|
||||
output_dir: PathBuf,
|
||||
timestamp: String,
|
||||
baseline_ref_name: String,
|
||||
feature_ref_name: String,
|
||||
baseline_results: Option<BenchmarkResults>,
|
||||
feature_results: Option<BenchmarkResults>,
|
||||
baseline_command: Option<String>,
|
||||
feature_command: Option<String>,
|
||||
}
|
||||
|
||||
/// Represents the results from a single benchmark run
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct BenchmarkResults {
|
||||
pub ref_name: String,
|
||||
pub combined_latency_data: Vec<CombinedLatencyRow>,
|
||||
pub summary: BenchmarkSummary,
|
||||
pub start_timestamp: Option<DateTime<Utc>>,
|
||||
pub end_timestamp: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
/// Combined latency CSV row structure
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub(crate) struct CombinedLatencyRow {
|
||||
pub block_number: u64,
|
||||
#[serde(default)]
|
||||
pub transaction_count: Option<u64>,
|
||||
pub gas_used: u64,
|
||||
pub new_payload_latency: u128,
|
||||
}
|
||||
|
||||
/// Total gas CSV row structure
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub(crate) struct TotalGasRow {
|
||||
pub block_number: u64,
|
||||
#[serde(default)]
|
||||
pub transaction_count: Option<u64>,
|
||||
pub gas_used: u64,
|
||||
pub time: u128,
|
||||
}
|
||||
|
||||
/// Summary statistics for a benchmark run.
|
||||
///
|
||||
/// Latencies are derived from per-block `engine_newPayload` timings (converted from µs to ms):
|
||||
/// - `mean_new_payload_latency_ms`: arithmetic mean latency across blocks.
|
||||
/// - `median_new_payload_latency_ms`: p50 latency across blocks.
|
||||
/// - `p90_new_payload_latency_ms` / `p99_new_payload_latency_ms`: tail latencies across blocks.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub(crate) struct BenchmarkSummary {
|
||||
pub total_blocks: u64,
|
||||
pub total_gas_used: u64,
|
||||
pub total_duration_ms: u128,
|
||||
pub mean_new_payload_latency_ms: f64,
|
||||
pub median_new_payload_latency_ms: f64,
|
||||
pub p90_new_payload_latency_ms: f64,
|
||||
pub p99_new_payload_latency_ms: f64,
|
||||
pub gas_per_second: f64,
|
||||
pub blocks_per_second: f64,
|
||||
pub min_block_number: u64,
|
||||
pub max_block_number: u64,
|
||||
}
|
||||
|
||||
/// Comparison report between two benchmark runs
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct ComparisonReport {
|
||||
pub timestamp: String,
|
||||
pub baseline: RefInfo,
|
||||
pub feature: RefInfo,
|
||||
pub comparison_summary: ComparisonSummary,
|
||||
pub per_block_comparisons: Vec<BlockComparison>,
|
||||
}
|
||||
|
||||
/// Information about a reference in the comparison
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct RefInfo {
|
||||
pub ref_name: String,
|
||||
pub summary: BenchmarkSummary,
|
||||
pub start_timestamp: Option<DateTime<Utc>>,
|
||||
pub end_timestamp: Option<DateTime<Utc>>,
|
||||
pub reth_command: Option<String>,
|
||||
}
|
||||
|
||||
/// Summary of the comparison between references.
|
||||
///
|
||||
/// Percent deltas are `(feature - baseline) / baseline * 100`:
|
||||
/// - `new_payload_latency_mean_change_percent`: percent changes of the per-block means.
|
||||
/// - `new_payload_latency_p50_change_percent` / p90 / p99: percent changes of the respective
|
||||
/// per-block percentiles.
|
||||
/// - `per_block_latency_change_mean_percent` / `per_block_latency_change_median_percent` are the
|
||||
/// mean and median of per-block percent deltas (feature vs baseline), capturing block-level
|
||||
/// drift.
|
||||
/// - `per_block_latency_change_std_dev_percent`: standard deviation of per-block percent changes,
|
||||
/// measuring consistency of performance changes across blocks.
|
||||
/// - `new_payload_total_latency_change_percent` is the percent change of the total newPayload time
|
||||
/// across the run.
|
||||
///
|
||||
/// Positive means slower/higher; negative means faster/lower.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct ComparisonSummary {
|
||||
pub per_block_latency_change_mean_percent: f64,
|
||||
pub per_block_latency_change_median_percent: f64,
|
||||
pub per_block_latency_change_std_dev_percent: f64,
|
||||
pub new_payload_total_latency_change_percent: f64,
|
||||
pub new_payload_latency_mean_change_percent: f64,
|
||||
pub new_payload_latency_p50_change_percent: f64,
|
||||
pub new_payload_latency_p90_change_percent: f64,
|
||||
pub new_payload_latency_p99_change_percent: f64,
|
||||
pub gas_per_second_change_percent: f64,
|
||||
pub blocks_per_second_change_percent: f64,
|
||||
}
|
||||
|
||||
/// Per-block comparison data
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct BlockComparison {
|
||||
pub block_number: u64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub transaction_count: Option<u64>,
|
||||
pub gas_used: u64,
|
||||
pub baseline_new_payload_latency: u128,
|
||||
pub feature_new_payload_latency: u128,
|
||||
pub new_payload_latency_change_percent: f64,
|
||||
}
|
||||
|
||||
impl ComparisonGenerator {
|
||||
/// Create a new comparison generator
|
||||
pub(crate) fn new(args: &Args) -> Self {
|
||||
let now: DateTime<Utc> = Utc::now();
|
||||
let timestamp = now.format("%Y%m%d_%H%M%S").to_string();
|
||||
|
||||
Self {
|
||||
output_dir: args.output_dir_path(),
|
||||
timestamp,
|
||||
baseline_ref_name: args.baseline_ref.clone(),
|
||||
feature_ref_name: args.feature_ref.clone(),
|
||||
baseline_results: None,
|
||||
feature_results: None,
|
||||
baseline_command: None,
|
||||
feature_command: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the output directory for a specific reference
|
||||
pub(crate) fn get_ref_output_dir(&self, ref_type: &str) -> PathBuf {
|
||||
self.output_dir.join("results").join(&self.timestamp).join(ref_type)
|
||||
}
|
||||
|
||||
/// Get the main output directory for this comparison run
|
||||
pub(crate) fn get_output_dir(&self) -> PathBuf {
|
||||
self.output_dir.join("results").join(&self.timestamp)
|
||||
}
|
||||
|
||||
/// Add benchmark results for a reference
|
||||
pub(crate) fn add_ref_results(&mut self, ref_type: &str, output_path: &Path) -> Result<()> {
|
||||
let ref_name = match ref_type {
|
||||
"baseline" => &self.baseline_ref_name,
|
||||
"feature" => &self.feature_ref_name,
|
||||
_ => return Err(eyre!("Unknown reference type: {}", ref_type)),
|
||||
};
|
||||
|
||||
let results = self.load_benchmark_results(ref_name, output_path)?;
|
||||
|
||||
match ref_type {
|
||||
"baseline" => self.baseline_results = Some(results),
|
||||
"feature" => self.feature_results = Some(results),
|
||||
_ => return Err(eyre!("Unknown reference type: {}", ref_type)),
|
||||
}
|
||||
|
||||
info!("Loaded benchmark results for {} reference", ref_type);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the benchmark run timestamps for a reference
|
||||
pub(crate) fn set_ref_timestamps(
|
||||
&mut self,
|
||||
ref_type: &str,
|
||||
start: DateTime<Utc>,
|
||||
end: DateTime<Utc>,
|
||||
) -> Result<()> {
|
||||
match ref_type {
|
||||
"baseline" => {
|
||||
if let Some(ref mut results) = self.baseline_results {
|
||||
results.start_timestamp = Some(start);
|
||||
results.end_timestamp = Some(end);
|
||||
} else {
|
||||
return Err(eyre!("Baseline results not loaded yet"));
|
||||
}
|
||||
}
|
||||
"feature" => {
|
||||
if let Some(ref mut results) = self.feature_results {
|
||||
results.start_timestamp = Some(start);
|
||||
results.end_timestamp = Some(end);
|
||||
} else {
|
||||
return Err(eyre!("Feature results not loaded yet"));
|
||||
}
|
||||
}
|
||||
_ => return Err(eyre!("Unknown reference type: {}", ref_type)),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the reth command for a reference
|
||||
pub(crate) fn set_ref_command(&mut self, ref_type: &str, command: String) -> Result<()> {
|
||||
match ref_type {
|
||||
"baseline" => {
|
||||
self.baseline_command = Some(command);
|
||||
}
|
||||
"feature" => {
|
||||
self.feature_command = Some(command);
|
||||
}
|
||||
_ => return Err(eyre!("Unknown reference type: {}", ref_type)),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate the final comparison report
|
||||
pub(crate) async fn generate_comparison_report(&self) -> Result<()> {
|
||||
info!("Generating comparison report...");
|
||||
|
||||
let baseline =
|
||||
self.baseline_results.as_ref().ok_or_else(|| eyre!("Baseline results not loaded"))?;
|
||||
|
||||
let feature =
|
||||
self.feature_results.as_ref().ok_or_else(|| eyre!("Feature results not loaded"))?;
|
||||
|
||||
let per_block_comparisons = self.calculate_per_block_comparisons(baseline, feature)?;
|
||||
let comparison_summary = self.calculate_comparison_summary(
|
||||
&baseline.summary,
|
||||
&feature.summary,
|
||||
&per_block_comparisons,
|
||||
)?;
|
||||
|
||||
let report = ComparisonReport {
|
||||
timestamp: self.timestamp.clone(),
|
||||
baseline: RefInfo {
|
||||
ref_name: baseline.ref_name.clone(),
|
||||
summary: baseline.summary.clone(),
|
||||
start_timestamp: baseline.start_timestamp,
|
||||
end_timestamp: baseline.end_timestamp,
|
||||
reth_command: self.baseline_command.clone(),
|
||||
},
|
||||
feature: RefInfo {
|
||||
ref_name: feature.ref_name.clone(),
|
||||
summary: feature.summary.clone(),
|
||||
start_timestamp: feature.start_timestamp,
|
||||
end_timestamp: feature.end_timestamp,
|
||||
reth_command: self.feature_command.clone(),
|
||||
},
|
||||
comparison_summary,
|
||||
per_block_comparisons,
|
||||
};
|
||||
|
||||
// Write reports
|
||||
self.write_comparison_reports(&report).await?;
|
||||
|
||||
// Print summary to console
|
||||
self.print_comparison_summary(&report);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load benchmark results from CSV files
|
||||
fn load_benchmark_results(
|
||||
&self,
|
||||
ref_name: &str,
|
||||
output_path: &Path,
|
||||
) -> Result<BenchmarkResults> {
|
||||
let combined_latency_path = output_path.join("combined_latency.csv");
|
||||
let total_gas_path = output_path.join("total_gas.csv");
|
||||
|
||||
let combined_latency_data = self.load_combined_latency_csv(&combined_latency_path)?;
|
||||
let total_gas_data = self.load_total_gas_csv(&total_gas_path)?;
|
||||
|
||||
let summary = self.calculate_summary(&combined_latency_data, &total_gas_data)?;
|
||||
|
||||
Ok(BenchmarkResults {
|
||||
ref_name: ref_name.to_string(),
|
||||
combined_latency_data,
|
||||
summary,
|
||||
start_timestamp: None,
|
||||
end_timestamp: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Load combined latency CSV data
|
||||
fn load_combined_latency_csv(&self, path: &Path) -> Result<Vec<CombinedLatencyRow>> {
|
||||
let mut reader = Reader::from_path(path)
|
||||
.wrap_err_with(|| format!("Failed to open combined latency CSV: {path:?}"))?;
|
||||
|
||||
let mut rows = Vec::new();
|
||||
for result in reader.deserialize() {
|
||||
let row: CombinedLatencyRow = result
|
||||
.wrap_err_with(|| format!("Failed to parse combined latency row in {path:?}"))?;
|
||||
rows.push(row);
|
||||
}
|
||||
|
||||
if rows.is_empty() {
|
||||
return Err(eyre!("No data found in combined latency CSV: {:?}", path));
|
||||
}
|
||||
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
/// Load total gas CSV data
|
||||
fn load_total_gas_csv(&self, path: &Path) -> Result<Vec<TotalGasRow>> {
|
||||
let mut reader = Reader::from_path(path)
|
||||
.wrap_err_with(|| format!("Failed to open total gas CSV: {path:?}"))?;
|
||||
|
||||
let mut rows = Vec::new();
|
||||
for result in reader.deserialize() {
|
||||
let row: TotalGasRow =
|
||||
result.wrap_err_with(|| format!("Failed to parse total gas row in {path:?}"))?;
|
||||
rows.push(row);
|
||||
}
|
||||
|
||||
if rows.is_empty() {
|
||||
return Err(eyre!("No data found in total gas CSV: {:?}", path));
|
||||
}
|
||||
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
/// Calculate summary statistics for a benchmark run.
|
||||
///
|
||||
/// Computes latency statistics from per-block `new_payload_latency` values in `combined_data`
|
||||
/// (converting from µs to ms), and throughput metrics using the total run duration from
|
||||
/// `total_gas_data`. Percentiles (p50/p90/p99) use linear interpolation on sorted latencies.
|
||||
fn calculate_summary(
|
||||
&self,
|
||||
combined_data: &[CombinedLatencyRow],
|
||||
total_gas_data: &[TotalGasRow],
|
||||
) -> Result<BenchmarkSummary> {
|
||||
if combined_data.is_empty() || total_gas_data.is_empty() {
|
||||
return Err(eyre!("Cannot calculate summary for empty data"));
|
||||
}
|
||||
|
||||
let total_blocks = combined_data.len() as u64;
|
||||
let total_gas_used: u64 = combined_data.iter().map(|r| r.gas_used).sum();
|
||||
|
||||
let total_duration_ms = total_gas_data.last().unwrap().time / 1000; // Convert microseconds to milliseconds
|
||||
|
||||
let latencies_ms: Vec<f64> =
|
||||
combined_data.iter().map(|r| r.new_payload_latency as f64 / 1000.0).collect();
|
||||
let mean_new_payload_latency_ms: f64 =
|
||||
latencies_ms.iter().sum::<f64>() / total_blocks as f64;
|
||||
|
||||
let mut sorted_latencies_ms = latencies_ms;
|
||||
sorted_latencies_ms.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
|
||||
let median_new_payload_latency_ms = percentile(&sorted_latencies_ms, 0.5);
|
||||
let p90_new_payload_latency_ms = percentile(&sorted_latencies_ms, 0.9);
|
||||
let p99_new_payload_latency_ms = percentile(&sorted_latencies_ms, 0.99);
|
||||
|
||||
let total_duration_seconds = total_duration_ms as f64 / 1000.0;
|
||||
let gas_per_second = if total_duration_seconds > f64::EPSILON {
|
||||
total_gas_used as f64 / total_duration_seconds
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let blocks_per_second = if total_duration_seconds > f64::EPSILON {
|
||||
total_blocks as f64 / total_duration_seconds
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let min_block_number = combined_data.first().unwrap().block_number;
|
||||
let max_block_number = combined_data.last().unwrap().block_number;
|
||||
|
||||
Ok(BenchmarkSummary {
|
||||
total_blocks,
|
||||
total_gas_used,
|
||||
total_duration_ms,
|
||||
mean_new_payload_latency_ms,
|
||||
median_new_payload_latency_ms,
|
||||
p90_new_payload_latency_ms,
|
||||
p99_new_payload_latency_ms,
|
||||
gas_per_second,
|
||||
blocks_per_second,
|
||||
min_block_number,
|
||||
max_block_number,
|
||||
})
|
||||
}
|
||||
|
||||
/// Calculate comparison summary between baseline and feature
|
||||
fn calculate_comparison_summary(
|
||||
&self,
|
||||
baseline: &BenchmarkSummary,
|
||||
feature: &BenchmarkSummary,
|
||||
per_block_comparisons: &[BlockComparison],
|
||||
) -> Result<ComparisonSummary> {
|
||||
let calc_percent_change = |baseline: f64, feature: f64| -> f64 {
|
||||
if baseline.abs() > f64::EPSILON {
|
||||
((feature - baseline) / baseline) * 100.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate per-block statistics. "Per-block" means: for each block, compute the percent
|
||||
// change (feature - baseline) / baseline * 100, then calculate statistics across those
|
||||
// per-block percent changes. This captures how consistently the feature performs relative
|
||||
// to baseline across all blocks.
|
||||
let per_block_percent_changes: Vec<f64> =
|
||||
per_block_comparisons.iter().map(|c| c.new_payload_latency_change_percent).collect();
|
||||
let per_block_latency_change_mean_percent = if per_block_percent_changes.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
per_block_percent_changes.iter().sum::<f64>() / per_block_percent_changes.len() as f64
|
||||
};
|
||||
let per_block_latency_change_median_percent = if per_block_percent_changes.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
let mut sorted = per_block_percent_changes.clone();
|
||||
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
|
||||
percentile(&sorted, 0.5)
|
||||
};
|
||||
let per_block_latency_change_std_dev_percent =
|
||||
calculate_std_dev(&per_block_percent_changes, per_block_latency_change_mean_percent);
|
||||
|
||||
let baseline_total_latency_ms =
|
||||
baseline.mean_new_payload_latency_ms * baseline.total_blocks as f64;
|
||||
let feature_total_latency_ms =
|
||||
feature.mean_new_payload_latency_ms * feature.total_blocks as f64;
|
||||
let new_payload_total_latency_change_percent =
|
||||
calc_percent_change(baseline_total_latency_ms, feature_total_latency_ms);
|
||||
|
||||
Ok(ComparisonSummary {
|
||||
per_block_latency_change_mean_percent,
|
||||
per_block_latency_change_median_percent,
|
||||
per_block_latency_change_std_dev_percent,
|
||||
new_payload_total_latency_change_percent,
|
||||
new_payload_latency_mean_change_percent: calc_percent_change(
|
||||
baseline.mean_new_payload_latency_ms,
|
||||
feature.mean_new_payload_latency_ms,
|
||||
),
|
||||
new_payload_latency_p50_change_percent: calc_percent_change(
|
||||
baseline.median_new_payload_latency_ms,
|
||||
feature.median_new_payload_latency_ms,
|
||||
),
|
||||
new_payload_latency_p90_change_percent: calc_percent_change(
|
||||
baseline.p90_new_payload_latency_ms,
|
||||
feature.p90_new_payload_latency_ms,
|
||||
),
|
||||
new_payload_latency_p99_change_percent: calc_percent_change(
|
||||
baseline.p99_new_payload_latency_ms,
|
||||
feature.p99_new_payload_latency_ms,
|
||||
),
|
||||
gas_per_second_change_percent: calc_percent_change(
|
||||
baseline.gas_per_second,
|
||||
feature.gas_per_second,
|
||||
),
|
||||
blocks_per_second_change_percent: calc_percent_change(
|
||||
baseline.blocks_per_second,
|
||||
feature.blocks_per_second,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
/// Calculate per-block comparisons
|
||||
fn calculate_per_block_comparisons(
|
||||
&self,
|
||||
baseline: &BenchmarkResults,
|
||||
feature: &BenchmarkResults,
|
||||
) -> Result<Vec<BlockComparison>> {
|
||||
let mut baseline_map: HashMap<u64, &CombinedLatencyRow> = HashMap::new();
|
||||
for row in &baseline.combined_latency_data {
|
||||
baseline_map.insert(row.block_number, row);
|
||||
}
|
||||
|
||||
let mut comparisons = Vec::new();
|
||||
for feature_row in &feature.combined_latency_data {
|
||||
if let Some(baseline_row) = baseline_map.get(&feature_row.block_number) {
|
||||
let calc_percent_change = |baseline: u128, feature: u128| -> f64 {
|
||||
if baseline > 0 {
|
||||
((feature as f64 - baseline as f64) / baseline as f64) * 100.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
};
|
||||
|
||||
let comparison = BlockComparison {
|
||||
block_number: feature_row.block_number,
|
||||
transaction_count: feature_row.transaction_count,
|
||||
gas_used: feature_row.gas_used,
|
||||
baseline_new_payload_latency: baseline_row.new_payload_latency,
|
||||
feature_new_payload_latency: feature_row.new_payload_latency,
|
||||
new_payload_latency_change_percent: calc_percent_change(
|
||||
baseline_row.new_payload_latency,
|
||||
feature_row.new_payload_latency,
|
||||
),
|
||||
};
|
||||
comparisons.push(comparison);
|
||||
} else {
|
||||
warn!("Block {} not found in baseline data", feature_row.block_number);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(comparisons)
|
||||
}
|
||||
|
||||
/// Write comparison reports to files
|
||||
async fn write_comparison_reports(&self, report: &ComparisonReport) -> Result<()> {
|
||||
let report_dir = self.output_dir.join("results").join(&self.timestamp);
|
||||
fs::create_dir_all(&report_dir)
|
||||
.wrap_err_with(|| format!("Failed to create report directory: {report_dir:?}"))?;
|
||||
|
||||
// Write JSON report
|
||||
let json_path = report_dir.join("comparison_report.json");
|
||||
let json_content = serde_json::to_string_pretty(report)
|
||||
.wrap_err("Failed to serialize comparison report to JSON")?;
|
||||
fs::write(&json_path, json_content)
|
||||
.wrap_err_with(|| format!("Failed to write JSON report: {json_path:?}"))?;
|
||||
|
||||
// Write CSV report for per-block comparisons
|
||||
let csv_path = report_dir.join("per_block_comparison.csv");
|
||||
let mut writer = csv::Writer::from_path(&csv_path)
|
||||
.wrap_err_with(|| format!("Failed to create CSV writer: {csv_path:?}"))?;
|
||||
|
||||
for comparison in &report.per_block_comparisons {
|
||||
writer.serialize(comparison).wrap_err("Failed to write comparison row to CSV")?;
|
||||
}
|
||||
writer.flush().wrap_err("Failed to flush CSV writer")?;
|
||||
|
||||
info!("Comparison reports written to: {:?}", report_dir);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Print comparison summary to console
|
||||
fn print_comparison_summary(&self, report: &ComparisonReport) {
|
||||
// Parse and format timestamp nicely
|
||||
let formatted_timestamp = if let Ok(dt) = chrono::DateTime::parse_from_str(
|
||||
&format!("{} +0000", report.timestamp.replace('_', " ")),
|
||||
"%Y%m%d %H%M%S %z",
|
||||
) {
|
||||
dt.format("%Y-%m-%d %H:%M:%S UTC").to_string()
|
||||
} else {
|
||||
// Fallback to original if parsing fails
|
||||
report.timestamp.clone()
|
||||
};
|
||||
|
||||
println!("\n=== BENCHMARK COMPARISON SUMMARY ===");
|
||||
println!("Timestamp: {formatted_timestamp}");
|
||||
println!("Baseline: {}", report.baseline.ref_name);
|
||||
println!("Feature: {}", report.feature.ref_name);
|
||||
println!();
|
||||
|
||||
let summary = &report.comparison_summary;
|
||||
|
||||
println!("Performance Changes:");
|
||||
println!(
|
||||
" NewPayload Latency per-block mean change: {:+.2}%",
|
||||
summary.per_block_latency_change_mean_percent
|
||||
);
|
||||
println!(
|
||||
" NewPayload Latency per-block median change: {:+.2}%",
|
||||
summary.per_block_latency_change_median_percent
|
||||
);
|
||||
println!(
|
||||
" NewPayload Latency per-block std dev: {:.2}%",
|
||||
summary.per_block_latency_change_std_dev_percent
|
||||
);
|
||||
println!(
|
||||
" Total newPayload time change: {:+.2}%",
|
||||
summary.new_payload_total_latency_change_percent
|
||||
);
|
||||
println!(
|
||||
" NewPayload Latency mean: {:+.2}%",
|
||||
summary.new_payload_latency_mean_change_percent
|
||||
);
|
||||
println!(
|
||||
" NewPayload Latency p50: {:+.2}%",
|
||||
summary.new_payload_latency_p50_change_percent
|
||||
);
|
||||
println!(
|
||||
" NewPayload Latency p90: {:+.2}%",
|
||||
summary.new_payload_latency_p90_change_percent
|
||||
);
|
||||
println!(
|
||||
" NewPayload Latency p99: {:+.2}%",
|
||||
summary.new_payload_latency_p99_change_percent
|
||||
);
|
||||
println!(
|
||||
" Gas/Second: {:+.2}%",
|
||||
summary.gas_per_second_change_percent
|
||||
);
|
||||
println!(
|
||||
" Blocks/Second: {:+.2}%",
|
||||
summary.blocks_per_second_change_percent
|
||||
);
|
||||
println!();
|
||||
|
||||
println!("Baseline Summary:");
|
||||
let baseline = &report.baseline.summary;
|
||||
println!(
|
||||
" Blocks: {} (blocks {} to {}), Gas: {}, Duration: {:.2}s",
|
||||
baseline.total_blocks,
|
||||
baseline.min_block_number,
|
||||
baseline.max_block_number,
|
||||
baseline.total_gas_used,
|
||||
baseline.total_duration_ms as f64 / 1000.0
|
||||
);
|
||||
println!(" NewPayload latency (ms):");
|
||||
println!(
|
||||
" mean: {:.2}, p50: {:.2}, p90: {:.2}, p99: {:.2}",
|
||||
baseline.mean_new_payload_latency_ms,
|
||||
baseline.median_new_payload_latency_ms,
|
||||
baseline.p90_new_payload_latency_ms,
|
||||
baseline.p99_new_payload_latency_ms
|
||||
);
|
||||
if let (Some(start), Some(end)) =
|
||||
(&report.baseline.start_timestamp, &report.baseline.end_timestamp)
|
||||
{
|
||||
println!(
|
||||
" Started: {}, Ended: {}",
|
||||
start.format("%Y-%m-%d %H:%M:%S UTC"),
|
||||
end.format("%Y-%m-%d %H:%M:%S UTC")
|
||||
);
|
||||
}
|
||||
if let Some(ref cmd) = report.baseline.reth_command {
|
||||
println!(" Command: {}", cmd);
|
||||
}
|
||||
println!();
|
||||
|
||||
println!("Feature Summary:");
|
||||
let feature = &report.feature.summary;
|
||||
println!(
|
||||
" Blocks: {} (blocks {} to {}), Gas: {}, Duration: {:.2}s",
|
||||
feature.total_blocks,
|
||||
feature.min_block_number,
|
||||
feature.max_block_number,
|
||||
feature.total_gas_used,
|
||||
feature.total_duration_ms as f64 / 1000.0
|
||||
);
|
||||
println!(" NewPayload latency (ms):");
|
||||
println!(
|
||||
" mean: {:.2}, p50: {:.2}, p90: {:.2}, p99: {:.2}",
|
||||
feature.mean_new_payload_latency_ms,
|
||||
feature.median_new_payload_latency_ms,
|
||||
feature.p90_new_payload_latency_ms,
|
||||
feature.p99_new_payload_latency_ms
|
||||
);
|
||||
if let (Some(start), Some(end)) =
|
||||
(&report.feature.start_timestamp, &report.feature.end_timestamp)
|
||||
{
|
||||
println!(
|
||||
" Started: {}, Ended: {}",
|
||||
start.format("%Y-%m-%d %H:%M:%S UTC"),
|
||||
end.format("%Y-%m-%d %H:%M:%S UTC")
|
||||
);
|
||||
}
|
||||
if let Some(ref cmd) = report.feature.reth_command {
|
||||
println!(" Command: {}", cmd);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate standard deviation from a set of values and their mean.
|
||||
///
|
||||
/// Computes the population standard deviation using the formula:
|
||||
/// `sqrt(sum((x - mean)²) / n)`
|
||||
///
|
||||
/// Returns 0.0 for empty input.
|
||||
fn calculate_std_dev(values: &[f64], mean: f64) -> f64 {
|
||||
if values.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let variance = values
|
||||
.iter()
|
||||
.map(|x| {
|
||||
let diff = x - mean;
|
||||
diff * diff
|
||||
})
|
||||
.sum::<f64>() /
|
||||
values.len() as f64;
|
||||
|
||||
variance.sqrt()
|
||||
}
|
||||
|
||||
/// Calculate percentile using linear interpolation on a sorted slice.
|
||||
///
|
||||
/// Computes `rank = percentile × (n - 1)` where n is the array length. If the rank falls
|
||||
/// between two indices, linearly interpolates between those values. For example, with 100 values,
|
||||
/// p90 computes rank = 0.9 × 99 = 89.1, then returns `values[89] × 0.9 + values[90] × 0.1`.
|
||||
///
|
||||
/// Returns 0.0 for empty input.
|
||||
fn percentile(sorted_values: &[f64], percentile: f64) -> f64 {
|
||||
if sorted_values.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let clamped = percentile.clamp(0.0, 1.0);
|
||||
let max_index = sorted_values.len() - 1;
|
||||
let rank = clamped * max_index as f64;
|
||||
let lower = rank.floor() as usize;
|
||||
let upper = rank.ceil() as usize;
|
||||
|
||||
if lower == upper {
|
||||
sorted_values[lower]
|
||||
} else {
|
||||
let weight = rank - lower as f64;
|
||||
sorted_values[lower].mul_add(1.0 - weight, sorted_values[upper] * weight)
|
||||
}
|
||||
}
|
||||
@@ -1,355 +0,0 @@
|
||||
//! Compilation operations for reth and reth-bench.
|
||||
|
||||
use crate::git::GitManager;
|
||||
use alloy_primitives::address;
|
||||
use alloy_provider::{Provider, ProviderBuilder};
|
||||
use eyre::{eyre, Result, WrapErr};
|
||||
use std::{fs, path::PathBuf, process::Command};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
/// Manages compilation operations for reth components
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CompilationManager {
|
||||
repo_root: String,
|
||||
output_dir: PathBuf,
|
||||
git_manager: GitManager,
|
||||
}
|
||||
|
||||
impl CompilationManager {
|
||||
/// Create a new `CompilationManager`
|
||||
pub(crate) const fn new(
|
||||
repo_root: String,
|
||||
output_dir: PathBuf,
|
||||
git_manager: GitManager,
|
||||
) -> Result<Self> {
|
||||
Ok(Self { repo_root, output_dir, git_manager })
|
||||
}
|
||||
|
||||
/// Detect if the RPC endpoint is an Optimism chain
|
||||
pub(crate) async fn detect_optimism_chain(&self, rpc_url: &str) -> Result<bool> {
|
||||
info!("Detecting chain type from RPC endpoint...");
|
||||
|
||||
// Create Alloy provider
|
||||
let url = rpc_url.parse().map_err(|e| eyre!("Invalid RPC URL '{}': {}", rpc_url, e))?;
|
||||
let provider = ProviderBuilder::new().connect_http(url);
|
||||
|
||||
// Check for Optimism predeploy at address 0x420000000000000000000000000000000000000F
|
||||
let is_optimism = !provider
|
||||
.get_code_at(address!("0x420000000000000000000000000000000000000F"))
|
||||
.await?
|
||||
.is_empty();
|
||||
|
||||
if is_optimism {
|
||||
info!("Detected Optimism chain");
|
||||
} else {
|
||||
info!("Detected Ethereum chain");
|
||||
}
|
||||
|
||||
Ok(is_optimism)
|
||||
}
|
||||
|
||||
/// Get the path to the cached binary using explicit commit hash
|
||||
pub(crate) fn get_cached_binary_path_for_commit(
|
||||
&self,
|
||||
commit: &str,
|
||||
is_optimism: bool,
|
||||
) -> PathBuf {
|
||||
let identifier = &commit[..8]; // Use first 8 chars of commit
|
||||
|
||||
let binary_name = if is_optimism {
|
||||
format!("op-reth_{}", identifier)
|
||||
} else {
|
||||
format!("reth_{}", identifier)
|
||||
};
|
||||
|
||||
self.output_dir.join("bin").join(binary_name)
|
||||
}
|
||||
|
||||
/// Compile reth using cargo build and cache the binary
|
||||
pub(crate) fn compile_reth(
|
||||
&self,
|
||||
commit: &str,
|
||||
is_optimism: bool,
|
||||
features: &str,
|
||||
rustflags: &str,
|
||||
) -> Result<()> {
|
||||
// Validate that current git commit matches the expected commit
|
||||
let current_commit = self.git_manager.get_current_commit()?;
|
||||
if current_commit != commit {
|
||||
return Err(eyre!(
|
||||
"Git commit mismatch! Expected: {}, but currently at: {}",
|
||||
&commit[..8],
|
||||
¤t_commit[..8]
|
||||
));
|
||||
}
|
||||
|
||||
let cached_path = self.get_cached_binary_path_for_commit(commit, is_optimism);
|
||||
|
||||
// Check if cached binary already exists (since path contains commit hash, it's valid)
|
||||
if cached_path.exists() {
|
||||
info!("Using cached binary (commit: {})", &commit[..8]);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
info!("No cached binary found, compiling (commit: {})...", &commit[..8]);
|
||||
|
||||
let binary_name = if is_optimism { "op-reth" } else { "reth" };
|
||||
|
||||
info!(
|
||||
"Compiling {} with profiling configuration (commit: {})...",
|
||||
binary_name,
|
||||
&commit[..8]
|
||||
);
|
||||
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.arg("build").arg("--profile").arg("profiling");
|
||||
|
||||
cmd.arg("--features").arg(features);
|
||||
info!("Using features: {features}");
|
||||
|
||||
// Add bin-specific arguments for optimism
|
||||
if is_optimism {
|
||||
cmd.arg("--bin")
|
||||
.arg("op-reth")
|
||||
.arg("--manifest-path")
|
||||
.arg("crates/optimism/bin/Cargo.toml");
|
||||
}
|
||||
|
||||
cmd.current_dir(&self.repo_root);
|
||||
|
||||
// Set RUSTFLAGS
|
||||
cmd.env("RUSTFLAGS", rustflags);
|
||||
info!("Using RUSTFLAGS: {rustflags}");
|
||||
|
||||
info!("Compiling {binary_name} with {cmd:?}");
|
||||
|
||||
let output = cmd.output().wrap_err("Failed to execute cargo build command")?;
|
||||
|
||||
// Print stdout and stderr with prefixes at debug level
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
for line in stdout.lines() {
|
||||
if !line.trim().is_empty() {
|
||||
debug!("[CARGO] {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
for line in stderr.lines() {
|
||||
if !line.trim().is_empty() {
|
||||
debug!("[CARGO] {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
if !output.status.success() {
|
||||
// Print all output when compilation fails
|
||||
error!("Cargo build failed with exit code: {:?}", output.status.code());
|
||||
|
||||
if !stdout.trim().is_empty() {
|
||||
error!("Cargo stdout:");
|
||||
for line in stdout.lines() {
|
||||
error!(" {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
if !stderr.trim().is_empty() {
|
||||
error!("Cargo stderr:");
|
||||
for line in stderr.lines() {
|
||||
error!(" {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
return Err(eyre!("Compilation failed with exit code: {:?}", output.status.code()));
|
||||
}
|
||||
|
||||
info!("{} compilation completed", binary_name);
|
||||
|
||||
// Copy the compiled binary to cache
|
||||
let source_path =
|
||||
PathBuf::from(&self.repo_root).join(format!("target/profiling/{}", binary_name));
|
||||
if !source_path.exists() {
|
||||
return Err(eyre!("Compiled binary not found at {:?}", source_path));
|
||||
}
|
||||
|
||||
// Create bin directory if it doesn't exist
|
||||
let bin_dir = self.output_dir.join("bin");
|
||||
fs::create_dir_all(&bin_dir).wrap_err("Failed to create bin directory")?;
|
||||
|
||||
// Copy binary to cache
|
||||
fs::copy(&source_path, &cached_path).wrap_err("Failed to copy binary to cache")?;
|
||||
|
||||
// Make the cached binary executable
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = fs::metadata(&cached_path)?.permissions();
|
||||
perms.set_mode(0o755);
|
||||
fs::set_permissions(&cached_path, perms)?;
|
||||
}
|
||||
|
||||
info!("Cached compiled binary at: {:?}", cached_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if reth-bench is available in PATH
|
||||
pub(crate) fn is_reth_bench_available(&self) -> bool {
|
||||
match Command::new("which").arg("reth-bench").output() {
|
||||
Ok(output) => {
|
||||
if output.status.success() {
|
||||
let path = String::from_utf8_lossy(&output.stdout);
|
||||
info!("Found reth-bench: {}", path.trim());
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if samply is available in PATH
|
||||
pub(crate) fn is_samply_available(&self) -> bool {
|
||||
match Command::new("which").arg("samply").output() {
|
||||
Ok(output) => {
|
||||
if output.status.success() {
|
||||
let path = String::from_utf8_lossy(&output.stdout);
|
||||
info!("Found samply: {}", path.trim());
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Install samply using cargo
|
||||
pub(crate) fn install_samply(&self) -> Result<()> {
|
||||
info!("Installing samply via cargo...");
|
||||
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.args(["install", "--locked", "samply"]);
|
||||
|
||||
info!("Installing samply with {cmd:?}");
|
||||
|
||||
let output = cmd.output().wrap_err("Failed to execute cargo install samply command")?;
|
||||
|
||||
// Print stdout and stderr with prefixes at debug level
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
for line in stdout.lines() {
|
||||
if !line.trim().is_empty() {
|
||||
debug!("[CARGO-SAMPLY] {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
for line in stderr.lines() {
|
||||
if !line.trim().is_empty() {
|
||||
debug!("[CARGO-SAMPLY] {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
if !output.status.success() {
|
||||
// Print all output when installation fails
|
||||
error!("Cargo install samply failed with exit code: {:?}", output.status.code());
|
||||
|
||||
if !stdout.trim().is_empty() {
|
||||
error!("Cargo stdout:");
|
||||
for line in stdout.lines() {
|
||||
error!(" {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
if !stderr.trim().is_empty() {
|
||||
error!("Cargo stderr:");
|
||||
for line in stderr.lines() {
|
||||
error!(" {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
return Err(eyre!(
|
||||
"samply installation failed with exit code: {:?}",
|
||||
output.status.code()
|
||||
));
|
||||
}
|
||||
|
||||
info!("Samply installation completed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensure samply is available, installing if necessary
|
||||
pub(crate) fn ensure_samply_available(&self) -> Result<()> {
|
||||
if self.is_samply_available() {
|
||||
Ok(())
|
||||
} else {
|
||||
warn!("samply not found in PATH, installing...");
|
||||
self.install_samply()
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure reth-bench is available, compiling if necessary
|
||||
pub(crate) fn ensure_reth_bench_available(&self) -> Result<()> {
|
||||
if self.is_reth_bench_available() {
|
||||
Ok(())
|
||||
} else {
|
||||
warn!("reth-bench not found in PATH, compiling and installing...");
|
||||
self.compile_reth_bench()
|
||||
}
|
||||
}
|
||||
|
||||
/// Compile and install reth-bench using `make install-reth-bench`
|
||||
pub(crate) fn compile_reth_bench(&self) -> Result<()> {
|
||||
info!("Compiling and installing reth-bench...");
|
||||
|
||||
let mut cmd = Command::new("make");
|
||||
cmd.arg("install-reth-bench").current_dir(&self.repo_root);
|
||||
|
||||
info!("Compiling reth-bench with {cmd:?}");
|
||||
|
||||
let output = cmd.output().wrap_err("Failed to execute make install-reth-bench command")?;
|
||||
|
||||
// Print stdout and stderr with prefixes at debug level
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
for line in stdout.lines() {
|
||||
if !line.trim().is_empty() {
|
||||
debug!("[MAKE-BENCH] {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
for line in stderr.lines() {
|
||||
if !line.trim().is_empty() {
|
||||
debug!("[MAKE-BENCH] {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
if !output.status.success() {
|
||||
// Print all output when compilation fails
|
||||
error!("Make install-reth-bench failed with exit code: {:?}", output.status.code());
|
||||
|
||||
if !stdout.trim().is_empty() {
|
||||
error!("Make stdout:");
|
||||
for line in stdout.lines() {
|
||||
error!(" {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
if !stderr.trim().is_empty() {
|
||||
error!("Make stderr:");
|
||||
for line in stderr.lines() {
|
||||
error!(" {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
return Err(eyre!(
|
||||
"reth-bench compilation failed with exit code: {:?}",
|
||||
output.status.code()
|
||||
));
|
||||
}
|
||||
|
||||
info!("Reth-bench compilation completed");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,328 +0,0 @@
|
||||
//! Git operations for branch management.
|
||||
|
||||
use eyre::{eyre, Result, WrapErr};
|
||||
use std::process::Command;
|
||||
use tracing::{info, warn};
|
||||
|
||||
/// Manages git operations for branch switching
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct GitManager {
|
||||
repo_root: String,
|
||||
}
|
||||
|
||||
impl GitManager {
|
||||
/// Create a new `GitManager`, detecting the repository root
|
||||
pub(crate) fn new() -> Result<Self> {
|
||||
let output = Command::new("git")
|
||||
.args(["rev-parse", "--show-toplevel"])
|
||||
.output()
|
||||
.wrap_err("Failed to execute git command - is git installed?")?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(eyre!("Not in a git repository or git command failed"));
|
||||
}
|
||||
|
||||
let repo_root = String::from_utf8(output.stdout)
|
||||
.wrap_err("Git output is not valid UTF-8")?
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
let manager = Self { repo_root };
|
||||
info!(
|
||||
"Detected git repository at: {}, current reference: {}",
|
||||
manager.repo_root(),
|
||||
manager.get_current_ref()?
|
||||
);
|
||||
|
||||
Ok(manager)
|
||||
}
|
||||
|
||||
/// Get the current git branch name
|
||||
pub(crate) fn get_current_branch(&self) -> Result<String> {
|
||||
let output = Command::new("git")
|
||||
.args(["branch", "--show-current"])
|
||||
.current_dir(&self.repo_root)
|
||||
.output()
|
||||
.wrap_err("Failed to get current branch")?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(eyre!("Failed to determine current branch"));
|
||||
}
|
||||
|
||||
let branch = String::from_utf8(output.stdout)
|
||||
.wrap_err("Branch name is not valid UTF-8")?
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
if branch.is_empty() {
|
||||
return Err(eyre!("Not on a named branch (detached HEAD?)"));
|
||||
}
|
||||
|
||||
Ok(branch)
|
||||
}
|
||||
|
||||
/// Get the current git reference (branch name, tag, or commit hash)
|
||||
pub(crate) fn get_current_ref(&self) -> Result<String> {
|
||||
// First try to get branch name
|
||||
if let Ok(branch) = self.get_current_branch() {
|
||||
return Ok(branch);
|
||||
}
|
||||
|
||||
// If not on a branch, check if we're on a tag
|
||||
let tag_output = Command::new("git")
|
||||
.args(["describe", "--exact-match", "--tags", "HEAD"])
|
||||
.current_dir(&self.repo_root)
|
||||
.output()
|
||||
.wrap_err("Failed to check for tag")?;
|
||||
|
||||
if tag_output.status.success() {
|
||||
let tag = String::from_utf8(tag_output.stdout)
|
||||
.wrap_err("Tag name is not valid UTF-8")?
|
||||
.trim()
|
||||
.to_string();
|
||||
return Ok(tag);
|
||||
}
|
||||
|
||||
// If not on a branch or tag, return the commit hash
|
||||
let commit_output = Command::new("git")
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.current_dir(&self.repo_root)
|
||||
.output()
|
||||
.wrap_err("Failed to get current commit")?;
|
||||
|
||||
if !commit_output.status.success() {
|
||||
return Err(eyre!("Failed to get current commit hash"));
|
||||
}
|
||||
|
||||
let commit_hash = String::from_utf8(commit_output.stdout)
|
||||
.wrap_err("Commit hash is not valid UTF-8")?
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
Ok(commit_hash)
|
||||
}
|
||||
|
||||
/// Check if the git working directory has uncommitted changes to tracked files
|
||||
pub(crate) fn validate_clean_state(&self) -> Result<()> {
|
||||
let output = Command::new("git")
|
||||
.args(["status", "--porcelain"])
|
||||
.current_dir(&self.repo_root)
|
||||
.output()
|
||||
.wrap_err("Failed to check git status")?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(eyre!("Git status command failed"));
|
||||
}
|
||||
|
||||
let status_output =
|
||||
String::from_utf8(output.stdout).wrap_err("Git status output is not valid UTF-8")?;
|
||||
|
||||
// Check for uncommitted changes to tracked files
|
||||
// Status codes: M = modified, A = added, D = deleted, R = renamed, C = copied, U = updated
|
||||
// ?? = untracked files (we want to ignore these)
|
||||
let has_uncommitted_changes = status_output.lines().any(|line| {
|
||||
if line.len() >= 2 {
|
||||
let status = &line[0..2];
|
||||
// Ignore untracked files (??) and ignored files (!!)
|
||||
!matches!(status, "??" | "!!")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
if has_uncommitted_changes {
|
||||
warn!("Git working directory has uncommitted changes to tracked files:");
|
||||
for line in status_output.lines() {
|
||||
if line.len() >= 2 && !matches!(&line[0..2], "??" | "!!") {
|
||||
warn!(" {}", line);
|
||||
}
|
||||
}
|
||||
return Err(eyre!(
|
||||
"Git working directory has uncommitted changes to tracked files. Please commit or stash changes before running benchmark comparison."
|
||||
));
|
||||
}
|
||||
|
||||
// Check if there are untracked files and log them as info
|
||||
let untracked_files: Vec<&str> =
|
||||
status_output.lines().filter(|line| line.starts_with("??")).collect();
|
||||
|
||||
if !untracked_files.is_empty() {
|
||||
info!(
|
||||
"Git working directory has {} untracked files (this is OK)",
|
||||
untracked_files.len()
|
||||
);
|
||||
}
|
||||
|
||||
info!("Git working directory is clean (no uncommitted changes to tracked files)");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fetch all refs from remote to ensure we have latest branches and tags
|
||||
pub(crate) fn fetch_all(&self) -> Result<()> {
|
||||
let output = Command::new("git")
|
||||
.args(["fetch", "--all", "--tags", "--quiet", "--force"])
|
||||
.current_dir(&self.repo_root)
|
||||
.output()
|
||||
.wrap_err("Failed to fetch latest refs")?;
|
||||
|
||||
if output.status.success() {
|
||||
info!("Fetched latest refs");
|
||||
} else {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
// Only warn if there's actual error content, not just fetch progress
|
||||
if !stderr.trim().is_empty() && !stderr.contains("-> origin/") {
|
||||
warn!("Git fetch encountered issues (continuing anyway): {}", stderr);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate that the specified git references exist (branches, tags, or commits)
|
||||
pub(crate) fn validate_refs(&self, refs: &[&str]) -> Result<()> {
|
||||
for &git_ref in refs {
|
||||
// Try to resolve the ref similar to `git checkout` by peeling to a commit.
|
||||
// First try the ref as-is with ^{commit}, then fall back to origin/{ref}^{commit}.
|
||||
let as_is = format!("{git_ref}^{{commit}}");
|
||||
let ref_check = Command::new("git")
|
||||
.args(["rev-parse", "--verify", &as_is])
|
||||
.current_dir(&self.repo_root)
|
||||
.output();
|
||||
|
||||
let found = if let Ok(output) = ref_check &&
|
||||
output.status.success()
|
||||
{
|
||||
info!("Validated reference exists: {}", git_ref);
|
||||
true
|
||||
} else {
|
||||
// Try remote-only branches via origin/{ref}
|
||||
let origin_ref = format!("origin/{git_ref}^{{commit}}");
|
||||
let origin_check = Command::new("git")
|
||||
.args(["rev-parse", "--verify", &origin_ref])
|
||||
.current_dir(&self.repo_root)
|
||||
.output();
|
||||
|
||||
if let Ok(output) = origin_check &&
|
||||
output.status.success()
|
||||
{
|
||||
info!("Validated remote reference exists: origin/{}", git_ref);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if !found {
|
||||
return Err(eyre!(
|
||||
"Git reference '{}' does not exist as branch, tag, or commit (tried '{}' and 'origin/{}^{{commit}}')",
|
||||
git_ref,
|
||||
format!("{git_ref}^{{commit}}"),
|
||||
git_ref,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Switch to the specified git reference (branch, tag, or commit)
|
||||
pub(crate) fn switch_ref(&self, git_ref: &str) -> Result<()> {
|
||||
// First checkout the reference
|
||||
let output = Command::new("git")
|
||||
.args(["checkout", git_ref])
|
||||
.current_dir(&self.repo_root)
|
||||
.output()
|
||||
.wrap_err_with(|| format!("Failed to switch to reference '{git_ref}'"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(eyre!("Failed to switch to reference '{}': {}", git_ref, stderr));
|
||||
}
|
||||
|
||||
// Check if this is a branch that tracks a remote and pull latest changes
|
||||
let is_branch = Command::new("git")
|
||||
.args(["show-ref", "--verify", "--quiet", &format!("refs/heads/{git_ref}")])
|
||||
.current_dir(&self.repo_root)
|
||||
.status()
|
||||
.map(|s| s.success())
|
||||
.unwrap_or(false);
|
||||
|
||||
if is_branch {
|
||||
// Check if the branch tracks a remote
|
||||
let tracking_output = Command::new("git")
|
||||
.args([
|
||||
"rev-parse",
|
||||
"--abbrev-ref",
|
||||
"--symbolic-full-name",
|
||||
&format!("{git_ref}@{{upstream}}"),
|
||||
])
|
||||
.current_dir(&self.repo_root)
|
||||
.output();
|
||||
|
||||
if let Ok(output) = tracking_output &&
|
||||
output.status.success()
|
||||
{
|
||||
let upstream = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
if !upstream.is_empty() && upstream != format!("{git_ref}@{{upstream}}") {
|
||||
// Branch tracks a remote, pull latest changes
|
||||
info!("Pulling latest changes for branch: {}", git_ref);
|
||||
|
||||
let pull_output = Command::new("git")
|
||||
.args(["pull", "--ff-only"])
|
||||
.current_dir(&self.repo_root)
|
||||
.output()
|
||||
.wrap_err_with(|| {
|
||||
format!("Failed to pull latest changes for branch '{git_ref}'")
|
||||
})?;
|
||||
|
||||
if pull_output.status.success() {
|
||||
info!("Successfully pulled latest changes for branch: {}", git_ref);
|
||||
} else {
|
||||
let stderr = String::from_utf8_lossy(&pull_output.stderr);
|
||||
warn!("Failed to pull latest changes for branch '{}': {}", git_ref, stderr);
|
||||
// Continue anyway, we'll use whatever version we have
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the checkout succeeded by checking the current commit
|
||||
let current_commit_output = Command::new("git")
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.current_dir(&self.repo_root)
|
||||
.output()
|
||||
.wrap_err("Failed to get current commit")?;
|
||||
|
||||
if !current_commit_output.status.success() {
|
||||
return Err(eyre!("Failed to verify git checkout"));
|
||||
}
|
||||
|
||||
info!("Switched to reference: {}", git_ref);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the current commit hash
|
||||
pub(crate) fn get_current_commit(&self) -> Result<String> {
|
||||
let output = Command::new("git")
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.current_dir(&self.repo_root)
|
||||
.output()
|
||||
.wrap_err("Failed to get current commit")?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(eyre!("Failed to get current commit hash"));
|
||||
}
|
||||
|
||||
let commit_hash = String::from_utf8(output.stdout)
|
||||
.wrap_err("Commit hash is not valid UTF-8")?
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
Ok(commit_hash)
|
||||
}
|
||||
|
||||
/// Get the repository root path
|
||||
pub(crate) fn repo_root(&self) -> &str {
|
||||
&self.repo_root
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
//! # reth-bench-compare
|
||||
//!
|
||||
//! Automated tool for comparing reth performance between two git branches.
|
||||
//! This tool automates the complete workflow of compiling, running, and benchmarking
|
||||
//! reth on different branches to provide meaningful performance comparisons.
|
||||
|
||||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
|
||||
html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
|
||||
issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
|
||||
)]
|
||||
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator();
|
||||
|
||||
mod benchmark;
|
||||
mod cli;
|
||||
mod comparison;
|
||||
mod compilation;
|
||||
mod git;
|
||||
mod node;
|
||||
|
||||
use clap::Parser;
|
||||
use cli::{run_comparison, Args};
|
||||
use eyre::Result;
|
||||
use reth_cli_runner::CliRunner;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// Enable backtraces unless a RUST_BACKTRACE value has already been explicitly provided.
|
||||
if std::env::var_os("RUST_BACKTRACE").is_none() {
|
||||
unsafe {
|
||||
std::env::set_var("RUST_BACKTRACE", "1");
|
||||
}
|
||||
}
|
||||
|
||||
let args = Args::parse();
|
||||
|
||||
// Initialize tracing
|
||||
let _guard = args.init_tracing()?;
|
||||
|
||||
// Run until either exit or sigint or sigterm
|
||||
let runner = CliRunner::try_default_runtime()?;
|
||||
runner.run_command_until_exit(|ctx| run_comparison(args, ctx))
|
||||
}
|
||||
@@ -1,694 +0,0 @@
|
||||
//! Node management for starting, stopping, and controlling reth instances.
|
||||
|
||||
use crate::cli::Args;
|
||||
use alloy_provider::{Provider, ProviderBuilder};
|
||||
use alloy_rpc_client::RpcClient;
|
||||
use alloy_rpc_types_eth::SyncStatus;
|
||||
use alloy_transport_ws::WsConnect;
|
||||
use eyre::{eyre, OptionExt, Result, WrapErr};
|
||||
#[cfg(unix)]
|
||||
use nix::sys::signal::{killpg, Signal};
|
||||
#[cfg(unix)]
|
||||
use nix::unistd::Pid;
|
||||
use reth_chainspec::Chain;
|
||||
use std::{fs, path::PathBuf, time::Duration};
|
||||
use tokio::{
|
||||
fs::File as AsyncFile,
|
||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader as AsyncBufReader},
|
||||
process::Command,
|
||||
time::{sleep, timeout},
|
||||
};
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
/// Default websocket RPC port used by reth
|
||||
const DEFAULT_WS_RPC_PORT: u16 = 8546;
|
||||
|
||||
/// Manages reth node lifecycle and operations
|
||||
pub(crate) struct NodeManager {
|
||||
datadir: Option<String>,
|
||||
metrics_port: u16,
|
||||
chain: Chain,
|
||||
use_sudo: bool,
|
||||
binary_path: Option<std::path::PathBuf>,
|
||||
enable_profiling: bool,
|
||||
output_dir: PathBuf,
|
||||
additional_reth_args: Vec<String>,
|
||||
comparison_dir: Option<PathBuf>,
|
||||
tracing_endpoint: Option<String>,
|
||||
otlp_max_queue_size: usize,
|
||||
}
|
||||
|
||||
impl NodeManager {
|
||||
/// Create a new `NodeManager` with configuration from CLI args
|
||||
pub(crate) fn new(args: &Args) -> Self {
|
||||
Self {
|
||||
datadir: Some(args.datadir_path().to_string_lossy().to_string()),
|
||||
metrics_port: args.metrics_port,
|
||||
chain: args.chain,
|
||||
use_sudo: args.sudo,
|
||||
binary_path: None,
|
||||
enable_profiling: args.profile,
|
||||
output_dir: args.output_dir_path(),
|
||||
// Filter out empty strings to prevent invalid arguments being passed to reth node
|
||||
additional_reth_args: args
|
||||
.reth_args
|
||||
.iter()
|
||||
.filter(|s| !s.is_empty())
|
||||
.cloned()
|
||||
.collect(),
|
||||
comparison_dir: None,
|
||||
tracing_endpoint: args.traces.otlp.as_ref().map(|u| u.to_string()),
|
||||
otlp_max_queue_size: args.otlp_max_queue_size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the comparison directory path for logging
|
||||
pub(crate) fn set_comparison_dir(&mut self, dir: PathBuf) {
|
||||
self.comparison_dir = Some(dir);
|
||||
}
|
||||
|
||||
/// Get the log file path for a given reference type
|
||||
fn get_log_file_path(&self, ref_type: &str) -> Result<PathBuf> {
|
||||
let comparison_dir = self
|
||||
.comparison_dir
|
||||
.as_ref()
|
||||
.ok_or_eyre("Comparison directory not set. Call set_comparison_dir first.")?;
|
||||
|
||||
// The comparison directory already contains the full path to results/<timestamp>
|
||||
let log_dir = comparison_dir.join(ref_type);
|
||||
|
||||
// Create the directory if it doesn't exist
|
||||
fs::create_dir_all(&log_dir)
|
||||
.wrap_err(format!("Failed to create log directory: {:?}", log_dir))?;
|
||||
|
||||
let log_file = log_dir.join("reth_node.log");
|
||||
Ok(log_file)
|
||||
}
|
||||
|
||||
/// Get the perf event max sample rate from the system, capped at 10000
|
||||
fn get_perf_sample_rate(&self) -> Option<String> {
|
||||
let perf_rate_file = "/proc/sys/kernel/perf_event_max_sample_rate";
|
||||
if let Ok(content) = fs::read_to_string(perf_rate_file) {
|
||||
let rate_str = content.trim();
|
||||
if !rate_str.is_empty() {
|
||||
if let Ok(system_rate) = rate_str.parse::<u32>() {
|
||||
let capped_rate = std::cmp::min(system_rate, 10000);
|
||||
info!(
|
||||
"Detected perf_event_max_sample_rate: {}, using: {}",
|
||||
system_rate, capped_rate
|
||||
);
|
||||
return Some(capped_rate.to_string());
|
||||
}
|
||||
warn!("Failed to parse perf_event_max_sample_rate: {}", rate_str);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the absolute path to samply using 'which' command
|
||||
async fn get_samply_path(&self) -> Result<String> {
|
||||
let output = Command::new("which")
|
||||
.arg("samply")
|
||||
.output()
|
||||
.await
|
||||
.wrap_err("Failed to execute 'which samply' command")?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(eyre!("samply not found in PATH"));
|
||||
}
|
||||
|
||||
let samply_path = String::from_utf8(output.stdout)
|
||||
.wrap_err("samply path is not valid UTF-8")?
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
if samply_path.is_empty() {
|
||||
return Err(eyre!("which samply returned empty path"));
|
||||
}
|
||||
|
||||
Ok(samply_path)
|
||||
}
|
||||
|
||||
/// Build reth arguments as a vector of strings
|
||||
fn build_reth_args(
|
||||
&self,
|
||||
binary_path_str: &str,
|
||||
additional_args: &[String],
|
||||
ref_type: &str,
|
||||
) -> (Vec<String>, String) {
|
||||
let mut reth_args = vec![binary_path_str.to_string(), "node".to_string()];
|
||||
|
||||
// Add chain argument (skip for mainnet as it's the default)
|
||||
let chain_str = self.chain.to_string();
|
||||
if chain_str != "mainnet" {
|
||||
reth_args.extend_from_slice(&["--chain".to_string(), chain_str.clone()]);
|
||||
}
|
||||
|
||||
// Add datadir if specified
|
||||
if let Some(ref datadir) = self.datadir {
|
||||
reth_args.extend_from_slice(&["--datadir".to_string(), datadir.clone()]);
|
||||
}
|
||||
|
||||
// Add reth-specific arguments
|
||||
let metrics_arg = format!("0.0.0.0:{}", self.metrics_port);
|
||||
reth_args.extend_from_slice(&[
|
||||
"--engine.accept-execution-requests-hash".to_string(),
|
||||
"--metrics".to_string(),
|
||||
metrics_arg,
|
||||
"--http".to_string(),
|
||||
"--http.api".to_string(),
|
||||
"eth,reth".to_string(),
|
||||
"--ws".to_string(),
|
||||
"--ws.api".to_string(),
|
||||
"eth,reth".to_string(),
|
||||
"--disable-discovery".to_string(),
|
||||
"--trusted-only".to_string(),
|
||||
]);
|
||||
|
||||
// Add tracing arguments if OTLP endpoint is configured
|
||||
if let Some(ref endpoint) = self.tracing_endpoint {
|
||||
info!("Enabling OTLP tracing export to: {} (service: reth-{})", endpoint, ref_type);
|
||||
// Endpoint requires equals per clap settings in reth
|
||||
reth_args.push(format!("--tracing-otlp={}", endpoint));
|
||||
}
|
||||
|
||||
// Add any additional arguments passed via command line (common to both baseline and
|
||||
// feature)
|
||||
reth_args.extend_from_slice(&self.additional_reth_args);
|
||||
|
||||
// Add reference-specific additional arguments
|
||||
reth_args.extend_from_slice(additional_args);
|
||||
|
||||
(reth_args, chain_str)
|
||||
}
|
||||
|
||||
/// Create a command for profiling mode
|
||||
async fn create_profiling_command(
|
||||
&self,
|
||||
ref_type: &str,
|
||||
reth_args: &[String],
|
||||
) -> Result<Command> {
|
||||
// Create profiles directory if it doesn't exist
|
||||
let profile_dir = self.output_dir.join("profiles");
|
||||
fs::create_dir_all(&profile_dir).wrap_err("Failed to create profiles directory")?;
|
||||
|
||||
let profile_path = profile_dir.join(format!("{}.json.gz", ref_type));
|
||||
info!("Starting reth node with samply profiling...");
|
||||
info!("Profile output: {:?}", profile_path);
|
||||
|
||||
// Get absolute path to samply
|
||||
let samply_path = self.get_samply_path().await?;
|
||||
|
||||
let mut cmd = if self.use_sudo {
|
||||
let mut sudo_cmd = Command::new("sudo");
|
||||
sudo_cmd.arg(&samply_path);
|
||||
sudo_cmd
|
||||
} else {
|
||||
Command::new(&samply_path)
|
||||
};
|
||||
|
||||
// Add samply arguments
|
||||
cmd.args(["record", "--save-only", "-o", &profile_path.to_string_lossy()]);
|
||||
|
||||
// Add rate argument if available
|
||||
if let Some(rate) = self.get_perf_sample_rate() {
|
||||
cmd.args(["--rate", &rate]);
|
||||
}
|
||||
|
||||
// Add separator and complete reth command
|
||||
cmd.arg("--");
|
||||
cmd.args(reth_args);
|
||||
|
||||
// Enable tracing-samply
|
||||
if supports_samply_flags(&reth_args[0]) {
|
||||
cmd.arg("--log.samply");
|
||||
}
|
||||
|
||||
// Set environment variable to disable log styling
|
||||
cmd.env("RUST_LOG_STYLE", "never");
|
||||
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
/// Create a command for direct reth execution
|
||||
fn create_direct_command(&self, reth_args: &[String]) -> Command {
|
||||
let binary_path = &reth_args[0];
|
||||
|
||||
let mut cmd = if self.use_sudo {
|
||||
info!("Starting reth node with sudo...");
|
||||
let mut sudo_cmd = Command::new("sudo");
|
||||
sudo_cmd.args(reth_args);
|
||||
sudo_cmd
|
||||
} else {
|
||||
info!("Starting reth node...");
|
||||
let mut reth_cmd = Command::new(binary_path);
|
||||
reth_cmd.args(&reth_args[1..]); // Skip the binary path since it's the command
|
||||
reth_cmd
|
||||
};
|
||||
|
||||
// Set environment variable to disable log styling
|
||||
cmd.env("RUST_LOG_STYLE", "never");
|
||||
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Start a reth node using the specified binary path and return the process handle
|
||||
/// along with the formatted reth command string for reporting.
|
||||
pub(crate) async fn start_node(
|
||||
&mut self,
|
||||
binary_path: &std::path::Path,
|
||||
_git_ref: &str,
|
||||
ref_type: &str,
|
||||
additional_args: &[String],
|
||||
) -> Result<(tokio::process::Child, String)> {
|
||||
// Store the binary path for later use (e.g., in unwind_to_block)
|
||||
self.binary_path = Some(binary_path.to_path_buf());
|
||||
|
||||
let binary_path_str = binary_path.to_string_lossy();
|
||||
let (reth_args, _) = self.build_reth_args(&binary_path_str, additional_args, ref_type);
|
||||
|
||||
// Format the reth command string for reporting
|
||||
let reth_command = shlex::try_join(reth_args.iter().map(|s| s.as_str()))
|
||||
.wrap_err("Failed to format reth command string")?;
|
||||
|
||||
// Log additional arguments if any
|
||||
if !self.additional_reth_args.is_empty() {
|
||||
info!("Using common additional reth arguments: {:?}", self.additional_reth_args);
|
||||
}
|
||||
if !additional_args.is_empty() {
|
||||
info!("Using reference-specific additional reth arguments: {:?}", additional_args);
|
||||
}
|
||||
|
||||
let mut cmd = if self.enable_profiling {
|
||||
self.create_profiling_command(ref_type, &reth_args).await?
|
||||
} else {
|
||||
self.create_direct_command(&reth_args)
|
||||
};
|
||||
|
||||
// Set process group for better signal handling
|
||||
#[cfg(unix)]
|
||||
{
|
||||
cmd.process_group(0);
|
||||
}
|
||||
|
||||
// Set high queue size to prevent trace dropping during benchmarks
|
||||
if self.tracing_endpoint.is_some() {
|
||||
cmd.env("OTEL_BSP_MAX_QUEUE_SIZE", self.otlp_max_queue_size.to_string()); // Traces
|
||||
cmd.env("OTEL_BLRP_MAX_QUEUE_SIZE", "10000"); // Logs
|
||||
|
||||
// Set service name to differentiate baseline vs feature runs in Jaeger
|
||||
cmd.env("OTEL_SERVICE_NAME", format!("reth-{}", ref_type));
|
||||
}
|
||||
|
||||
debug!("Executing reth command: {cmd:?}");
|
||||
|
||||
let mut child = cmd
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.kill_on_drop(true) // Kill on drop so that on Ctrl-C for parent process we stop all child processes
|
||||
.spawn()
|
||||
.wrap_err("Failed to start reth node")?;
|
||||
|
||||
info!(
|
||||
"Reth node started with PID: {:?} (binary: {})",
|
||||
child.id().ok_or_eyre("Reth node is not running")?,
|
||||
binary_path_str
|
||||
);
|
||||
|
||||
// Prepare log file path
|
||||
let log_file_path = self.get_log_file_path(ref_type)?;
|
||||
info!("Reth node logs will be saved to: {:?}", log_file_path);
|
||||
|
||||
// Stream stdout and stderr with prefixes at debug level and to log file
|
||||
if let Some(stdout) = child.stdout.take() {
|
||||
let log_file = AsyncFile::create(&log_file_path)
|
||||
.await
|
||||
.wrap_err(format!("Failed to create log file: {:?}", log_file_path))?;
|
||||
tokio::spawn(async move {
|
||||
let reader = AsyncBufReader::new(stdout);
|
||||
let mut lines = reader.lines();
|
||||
let mut log_file = log_file;
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
debug!("[RETH] {}", line);
|
||||
// Write to log file (reth already includes timestamps)
|
||||
let log_line = format!("{}\n", line);
|
||||
if let Err(e) = log_file.write_all(log_line.as_bytes()).await {
|
||||
debug!("Failed to write to log file: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(stderr) = child.stderr.take() {
|
||||
let log_file = AsyncFile::options()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&log_file_path)
|
||||
.await
|
||||
.wrap_err(format!("Failed to open log file for stderr: {:?}", log_file_path))?;
|
||||
tokio::spawn(async move {
|
||||
let reader = AsyncBufReader::new(stderr);
|
||||
let mut lines = reader.lines();
|
||||
let mut log_file = log_file;
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
debug!("[RETH] {}", line);
|
||||
// Write to log file (reth already includes timestamps)
|
||||
let log_line = format!("{}\n", line);
|
||||
if let Err(e) = log_file.write_all(log_line.as_bytes()).await {
|
||||
debug!("Failed to write to log file: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Give the node a moment to start up
|
||||
sleep(Duration::from_secs(5)).await;
|
||||
|
||||
Ok((child, reth_command))
|
||||
}
|
||||
|
||||
/// Wait for the node to be ready and return its current tip.
|
||||
///
|
||||
/// Fails early if the node process exits before becoming ready.
|
||||
pub(crate) async fn wait_for_node_ready_and_get_tip(
|
||||
&self,
|
||||
child: &mut tokio::process::Child,
|
||||
) -> Result<u64> {
|
||||
info!("Waiting for node to be ready and synced...");
|
||||
|
||||
let max_wait = Duration::from_secs(120); // 2 minutes to allow for sync
|
||||
let check_interval = Duration::from_secs(2);
|
||||
let rpc_url = "http://localhost:8545";
|
||||
|
||||
// Create Alloy provider
|
||||
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!(
|
||||
"Readiness check iteration {} (elapsed: {:?})",
|
||||
iteration,
|
||||
start_time.elapsed()
|
||||
);
|
||||
|
||||
// Check if the node process has exited.
|
||||
if let Some(status) = child.try_wait()? {
|
||||
return Err(eyre!("Node process exited unexpectedly with {status}"));
|
||||
}
|
||||
|
||||
// First check if RPC is up and node is not syncing
|
||||
match provider.syncing().await {
|
||||
Ok(sync_result) => {
|
||||
match sync_result {
|
||||
SyncStatus::Info(sync_info) => {
|
||||
debug!("Node is still syncing {sync_info:?}, waiting...");
|
||||
}
|
||||
_ => {
|
||||
debug!("HTTP RPC is up and node is not syncing, checking block number...");
|
||||
// Node is not syncing, now get the tip
|
||||
match provider.get_block_number().await {
|
||||
Ok(tip) => {
|
||||
debug!("HTTP RPC ready at block: {}, checking WebSocket...", tip);
|
||||
|
||||
// Verify WebSocket RPC is ready (public endpoint, no JWT required)
|
||||
let ws_url = format!("ws://localhost:{}", DEFAULT_WS_RPC_PORT);
|
||||
debug!("Attempting WebSocket connection to {} (public endpoint)", ws_url);
|
||||
let ws_connect = WsConnect::new(&ws_url);
|
||||
|
||||
match RpcClient::connect_pubsub(ws_connect).await
|
||||
{
|
||||
Ok(_) => {
|
||||
info!(
|
||||
"Node is ready (HTTP and WebSocket) 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
|
||||
);
|
||||
debug!("WebSocket error details: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Failed to get block number (iteration {}): {:?}", iteration, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Node RPC not ready yet or failed to check sync status (iteration {}): {:?}", iteration, e);
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Sleeping for {:?} before next check", check_interval);
|
||||
sleep(check_interval).await;
|
||||
}
|
||||
})
|
||||
.await
|
||||
.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")?;
|
||||
|
||||
// Check if the process has already exited
|
||||
match child.try_wait() {
|
||||
Ok(Some(status)) => {
|
||||
info!("Reth node (PID: {}) has already exited with status: {:?}", pid, status);
|
||||
return Ok(());
|
||||
}
|
||||
Ok(None) => {
|
||||
// Process is still running, proceed to stop it
|
||||
info!("Stopping process gracefully with SIGINT (PID: {})...", pid);
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(eyre!("Failed to check process status: {}", e));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
// Send SIGINT to process group to mimic Ctrl-C behavior
|
||||
let nix_pgid = Pid::from_raw(pid as i32);
|
||||
|
||||
match killpg(nix_pgid, Signal::SIGINT) {
|
||||
Ok(()) => {}
|
||||
Err(nix::errno::Errno::ESRCH) => {
|
||||
info!("Process group {} has already exited", pid);
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(eyre!("Failed to send SIGINT to process group {}: {}", pid, e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
// On non-Unix systems, fall back to using external kill command
|
||||
let output = Command::new("taskkill")
|
||||
.args(["/PID", &pid.to_string(), "/F"])
|
||||
.output()
|
||||
.await
|
||||
.wrap_err("Failed to execute taskkill command")?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
// Check if the error is because the process doesn't exist
|
||||
if stderr.contains("not found") || stderr.contains("not exist") {
|
||||
info!("Process {} has already exited", pid);
|
||||
} else {
|
||||
return Err(eyre!("Failed to kill process {}: {}", pid, stderr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the process to exit
|
||||
match child.wait().await {
|
||||
Ok(status) => {
|
||||
info!("Reth node (PID: {}) exited with status: {:?}", pid, status);
|
||||
}
|
||||
Err(e) => {
|
||||
// If we get an error here, it might be because the process already exited
|
||||
debug!("Error waiting for process exit (may have already exited): {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unwind the node to a specific block
|
||||
pub(crate) async fn unwind_to_block(&self, block_number: u64) -> Result<()> {
|
||||
if self.use_sudo {
|
||||
info!("Unwinding node to block: {} (with sudo)", block_number);
|
||||
} else {
|
||||
info!("Unwinding node to block: {}", block_number);
|
||||
}
|
||||
|
||||
// Use the binary path from the last start_node call, or fallback to default
|
||||
let binary_path = self
|
||||
.binary_path
|
||||
.as_ref()
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| "./target/profiling/reth".to_string());
|
||||
|
||||
let mut cmd = if self.use_sudo {
|
||||
let mut sudo_cmd = Command::new("sudo");
|
||||
sudo_cmd.args([&binary_path, "stage", "unwind"]);
|
||||
sudo_cmd
|
||||
} else {
|
||||
let mut reth_cmd = Command::new(&binary_path);
|
||||
reth_cmd.args(["stage", "unwind"]);
|
||||
reth_cmd
|
||||
};
|
||||
|
||||
// Add chain argument (skip for mainnet as it's the default)
|
||||
let chain_str = self.chain.to_string();
|
||||
if chain_str != "mainnet" {
|
||||
cmd.args(["--chain", &chain_str]);
|
||||
}
|
||||
|
||||
// Add datadir if specified
|
||||
if let Some(ref datadir) = self.datadir {
|
||||
cmd.args(["--datadir", datadir]);
|
||||
}
|
||||
|
||||
cmd.args(["to-block", &block_number.to_string()]);
|
||||
|
||||
// Set environment variable to disable log styling
|
||||
cmd.env("RUST_LOG_STYLE", "never");
|
||||
|
||||
// Debug log the command
|
||||
debug!("Executing reth unwind command: {:?}", cmd);
|
||||
|
||||
let mut child = cmd
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.wrap_err("Failed to start unwind command")?;
|
||||
|
||||
// Stream stdout and stderr with prefixes in real-time
|
||||
if let Some(stdout) = child.stdout.take() {
|
||||
tokio::spawn(async move {
|
||||
let reader = AsyncBufReader::new(stdout);
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
debug!("[RETH-UNWIND] {}", line);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(stderr) = child.stderr.take() {
|
||||
tokio::spawn(async move {
|
||||
let reader = AsyncBufReader::new(stderr);
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
debug!("[RETH-UNWIND] {}", line);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Wait for the command to complete
|
||||
let status = child.wait().await.wrap_err("Failed to wait for unwind command")?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(eyre!("Unwind command failed with exit code: {:?}", status.code()));
|
||||
}
|
||||
|
||||
info!("Unwound to block: {}", block_number);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn supports_samply_flags(bin: &str) -> bool {
|
||||
let mut cmd = std::process::Command::new(bin);
|
||||
// NOTE: The flag to check must come before --help.
|
||||
// We pass --help as a shortcut to not execute any command.
|
||||
cmd.args(["--log.samply", "--help"]);
|
||||
debug!(?cmd, "Checking samply flags support");
|
||||
let Ok(output) = cmd.output() else {
|
||||
return false;
|
||||
};
|
||||
debug!(?output, "Samply flags support check");
|
||||
output.status.success()
|
||||
}
|
||||
@@ -16,7 +16,6 @@ workspace = true
|
||||
# reth
|
||||
reth-cli-runner.workspace = true
|
||||
reth-cli-util.workspace = true
|
||||
reth-engine-primitives.workspace = true
|
||||
reth-fs-util.workspace = true
|
||||
reth-node-api.workspace = true
|
||||
reth-node-core.workspace = true
|
||||
@@ -26,11 +25,10 @@ reth-tracing.workspace = true
|
||||
# alloy
|
||||
alloy-eips.workspace = true
|
||||
alloy-json-rpc.workspace = true
|
||||
alloy-network.workspace = true
|
||||
alloy-primitives.workspace = true
|
||||
alloy-provider = { workspace = true, features = ["engine-api", "pubsub", "reqwest-rustls-tls"], default-features = false }
|
||||
alloy-provider = { workspace = true, features = ["engine-api", "reqwest-rustls-tls"], default-features = false }
|
||||
alloy-pubsub.workspace = true
|
||||
alloy-rpc-client = { workspace = true, features = ["pubsub"] }
|
||||
alloy-rpc-client.workspace = true
|
||||
alloy-rpc-types-engine.workspace = true
|
||||
alloy-transport-http.workspace = true
|
||||
alloy-transport-ipc.workspace = true
|
||||
@@ -52,9 +50,6 @@ tracing.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
# url parsing
|
||||
url.workspace = true
|
||||
|
||||
# async
|
||||
async-trait.workspace = true
|
||||
futures.workspace = true
|
||||
@@ -63,7 +58,6 @@ tokio = { workspace = true, features = ["sync", "macros", "time", "rt-multi-thre
|
||||
# misc
|
||||
clap = { workspace = true, features = ["derive", "env"] }
|
||||
eyre.workspace = true
|
||||
color-eyre.workspace = true
|
||||
thiserror.workspace = true
|
||||
humantime.workspace = true
|
||||
|
||||
@@ -85,32 +79,13 @@ jemalloc = [
|
||||
"reth-node-core/jemalloc",
|
||||
]
|
||||
jemalloc-prof = ["reth-cli-util/jemalloc-prof"]
|
||||
tracy-allocator = ["reth-cli-util/tracy-allocator", "tracy"]
|
||||
tracy = [
|
||||
"reth-node-core/tracy",
|
||||
"reth-tracing/tracy",
|
||||
]
|
||||
tracy-allocator = ["reth-cli-util/tracy-allocator"]
|
||||
|
||||
min-error-logs = [
|
||||
"tracing/release_max_level_error",
|
||||
"reth-node-core/min-error-logs",
|
||||
]
|
||||
min-warn-logs = [
|
||||
"tracing/release_max_level_warn",
|
||||
"reth-node-core/min-warn-logs",
|
||||
]
|
||||
min-info-logs = [
|
||||
"tracing/release_max_level_info",
|
||||
"reth-node-core/min-info-logs",
|
||||
]
|
||||
min-debug-logs = [
|
||||
"tracing/release_max_level_debug",
|
||||
"reth-node-core/min-debug-logs",
|
||||
]
|
||||
min-trace-logs = [
|
||||
"tracing/release_max_level_trace",
|
||||
"reth-node-core/min-trace-logs",
|
||||
]
|
||||
min-error-logs = ["tracing/release_max_level_error"]
|
||||
min-warn-logs = ["tracing/release_max_level_warn"]
|
||||
min-info-logs = ["tracing/release_max_level_info"]
|
||||
min-debug-logs = ["tracing/release_max_level_debug"]
|
||||
min-trace-logs = ["tracing/release_max_level_trace"]
|
||||
|
||||
# no-op feature flag for switching between the `optimism` and default functionality in CI matrices
|
||||
ethereum = []
|
||||
|
||||
@@ -31,14 +31,6 @@ Otherwise, running `make maxperf` at the root of the repo should be sufficient f
|
||||
`reth-bench` contains different commands to benchmark different patterns of engine API calls.
|
||||
The `reth-bench new-payload-fcu` command is the most representative of ethereum mainnet live sync, alternating between sending `engine_newPayload` calls and `engine_forkchoiceUpdated` calls.
|
||||
|
||||
The `new-payload-fcu` command supports two optional waiting modes that can be used together or independently:
|
||||
- `--wait-time <duration>`: Fixed sleep interval between blocks (e.g., `--wait-time 100ms`)
|
||||
- `--wait-for-persistence`: Waits for blocks to be persisted using the `reth_subscribePersistedBlock` subscription
|
||||
|
||||
When using `--wait-for-persistence`, the benchmark waits after every `(threshold + 1)` blocks, where the threshold defaults to the engine's persistence threshold (2). This can be customized with `--persistence-threshold <N>`.
|
||||
|
||||
By default, the WebSocket URL for persistence subscriptions is derived from `--engine-rpc-url` (converting to ws:// on port 8546). Use `--ws-rpc-url` to override this.
|
||||
|
||||
Below is an overview of how to run a benchmark:
|
||||
|
||||
### Setup
|
||||
@@ -88,7 +80,7 @@ RUSTFLAGS="-C target-cpu=native" cargo build --profile profiling --no-default-fe
|
||||
### Run the Benchmark:
|
||||
First, start the reth node. Here is an example that runs `reth` compiled with the `profiling` profile, runs `samply`, and configures `reth` to run with metrics enabled:
|
||||
```bash
|
||||
samply record -p 3001 target/profiling/reth node --metrics localhost:9001 --authrpc.jwtsecret <jwt_file_path>
|
||||
samply record -p 3001 target/profiling/reth node --metrics localhost:9001 --authrpc.jwt-secret <jwt_file_path>
|
||||
```
|
||||
|
||||
```bash
|
||||
@@ -151,5 +143,5 @@ To reproduce the benchmark, first re-set the node to the block that the benchmar
|
||||
- **RPC Configuration**: The RPC endpoints should be accessible and configured correctly, specifically the RPC endpoint must support `eth_getBlockByNumber` and support fetching full transactions. The benchmark will make one RPC query per block as fast as possible, so ensure the RPC endpoint does not rate limit or block requests after a certain volume.
|
||||
- **Reproducibility**: Ensure that the node is at the same state before attempting to retry a benchmark. The `new-payload-fcu` command specifically will commit to the database, so the node must be rolled back using `reth stage unwind` to reproducibly retry benchmarks.
|
||||
- **Profiling tools**: If you are collecting CPU profiles, tools like [`samply`](https://github.com/mstange/samply) and [`perf`](https://perf.wiki.kernel.org/index.php/Main_Page) can be useful for analyzing node performance.
|
||||
- **Benchmark Data**: `reth-bench` additionally contains a `--output` flag, which will output gas used benchmarks across the benchmark range in CSV format. This may be useful for further data analysis.
|
||||
- **Benchmark Data**: `reth-bench` additionally contains a `--benchmark.output` flag, which will output gas used benchmarks across the benchmark range in CSV format. This may be useful for further data analysis.
|
||||
- **Platform Information**: To ensure accurate and reproducible benchmarking, document the platform details, including hardware specifications, OS version, and any other relevant information before publishing any benchmarks.
|
||||
|
||||
@@ -163,7 +163,7 @@ impl AuthenticatedTransport {
|
||||
|
||||
// shift the iat forward by one second so there is some buffer time
|
||||
let mut shifted_claims = inner_and_claims.1;
|
||||
shifted_claims.iat -= 30;
|
||||
shifted_claims.iat -= 1;
|
||||
|
||||
// if the claims are out of date, reset the inner transport
|
||||
if !shifted_claims.is_within_time_window() {
|
||||
|
||||
@@ -7,7 +7,6 @@ use alloy_primitives::address;
|
||||
use alloy_provider::{network::AnyNetwork, Provider, RootProvider};
|
||||
use alloy_rpc_client::ClientBuilder;
|
||||
use alloy_rpc_types_engine::JwtSecret;
|
||||
use alloy_transport::layers::RetryBackoffLayer;
|
||||
use reqwest::Url;
|
||||
use reth_node_core::args::BenchmarkArgs;
|
||||
use tracing::info;
|
||||
@@ -50,9 +49,7 @@ impl BenchContext {
|
||||
}
|
||||
|
||||
// set up alloy client for blocks
|
||||
let client = ClientBuilder::default()
|
||||
.layer(RetryBackoffLayer::new(10, 800, u64::MAX))
|
||||
.http(rpc_url.parse()?);
|
||||
let client = ClientBuilder::default().http(rpc_url.parse()?);
|
||||
let block_provider = RootProvider::<AnyNetwork>::new(client);
|
||||
|
||||
// Check if this is an OP chain by checking code at a predeploy address.
|
||||
@@ -103,20 +100,14 @@ impl BenchContext {
|
||||
(bench_args.from, bench_args.to)
|
||||
};
|
||||
|
||||
// If `--to` are not provided, we will run the benchmark continuously,
|
||||
// If neither `--from` nor `--to` are provided, we will run the benchmark continuously,
|
||||
// starting at the latest block.
|
||||
let latest_block = block_provider
|
||||
.get_block_by_number(BlockNumberOrTag::Latest)
|
||||
.full()
|
||||
.await?
|
||||
.ok_or_else(|| eyre::eyre!("Failed to fetch latest block from RPC"))?;
|
||||
let mut benchmark_mode = BenchMode::new(from, to, latest_block.into_inner().number())?;
|
||||
let mut benchmark_mode = BenchMode::new(from, to)?;
|
||||
|
||||
let first_block = match benchmark_mode {
|
||||
BenchMode::Continuous(start) => {
|
||||
block_provider.get_block_by_number(start.into()).full().await?.ok_or_else(|| {
|
||||
eyre::eyre!("Failed to fetch block {} from RPC for continuous mode", start)
|
||||
})?
|
||||
BenchMode::Continuous => {
|
||||
// fetch Latest block
|
||||
block_provider.get_block_by_number(BlockNumberOrTag::Latest).full().await?.unwrap()
|
||||
}
|
||||
BenchMode::Range(ref mut range) => {
|
||||
match range.next() {
|
||||
@@ -126,9 +117,7 @@ impl BenchContext {
|
||||
.get_block_by_number(block_number.into())
|
||||
.full()
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
eyre::eyre!("Failed to fetch block {} from RPC", block_number)
|
||||
})?
|
||||
.unwrap()
|
||||
}
|
||||
None => {
|
||||
return Err(eyre::eyre!(
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
//! Runs the `reth bench` command, calling first newPayload for each block, then calling
|
||||
//! forkchoiceUpdated.
|
||||
//!
|
||||
//! Supports configurable waiting behavior:
|
||||
//! - **`--wait-time`**: Fixed sleep interval between blocks.
|
||||
//! - **`--wait-for-persistence`**: Waits for every Nth block to be persisted using the
|
||||
//! `reth_subscribePersistedBlock` subscription, where N matches the engine's persistence
|
||||
//! threshold. This ensures the benchmark doesn't outpace persistence.
|
||||
//!
|
||||
//! Both options can be used together or independently.
|
||||
|
||||
use crate::{
|
||||
bench::{
|
||||
@@ -19,26 +11,16 @@ use crate::{
|
||||
},
|
||||
valid_payload::{block_to_new_payload, call_forkchoice_updated, call_new_payload},
|
||||
};
|
||||
use alloy_eips::BlockNumHash;
|
||||
use alloy_network::Ethereum;
|
||||
use alloy_provider::{Provider, RootProvider};
|
||||
use alloy_pubsub::SubscriptionStream;
|
||||
use alloy_rpc_client::RpcClient;
|
||||
use alloy_provider::Provider;
|
||||
use alloy_rpc_types_engine::ForkchoiceState;
|
||||
use alloy_transport_ws::WsConnect;
|
||||
use clap::Parser;
|
||||
use csv::Writer;
|
||||
use eyre::{Context, OptionExt};
|
||||
use futures::StreamExt;
|
||||
use eyre::Context;
|
||||
use humantime::parse_duration;
|
||||
use reth_cli_runner::CliContext;
|
||||
use reth_engine_primitives::config::DEFAULT_PERSISTENCE_THRESHOLD;
|
||||
use reth_node_core::args::BenchmarkArgs;
|
||||
use std::time::{Duration, Instant};
|
||||
use tracing::{debug, info};
|
||||
use url::Url;
|
||||
|
||||
const PERSISTENCE_CHECKPOINT_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
|
||||
/// `reth benchmark new-payload-fcu` command
|
||||
#[derive(Debug, Parser)]
|
||||
@@ -51,39 +33,6 @@ pub struct Command {
|
||||
#[arg(long, value_name = "WAIT_TIME", value_parser = parse_duration, verbatim_doc_comment)]
|
||||
wait_time: Option<Duration>,
|
||||
|
||||
/// Wait for blocks to be persisted before sending the next batch.
|
||||
///
|
||||
/// When enabled, waits for every Nth block to be persisted using the
|
||||
/// `reth_subscribePersistedBlock` subscription. This ensures the benchmark
|
||||
/// doesn't outpace persistence.
|
||||
///
|
||||
/// The subscription uses the regular RPC websocket endpoint (no JWT required).
|
||||
#[arg(long, default_value = "false", verbatim_doc_comment)]
|
||||
wait_for_persistence: bool,
|
||||
|
||||
/// Engine persistence threshold used for deciding when to wait for persistence.
|
||||
///
|
||||
/// The benchmark waits after every `(threshold + 1)` blocks. By default this
|
||||
/// matches the engine's `DEFAULT_PERSISTENCE_THRESHOLD` (2), so waits occur
|
||||
/// at blocks 3, 6, 9, etc.
|
||||
#[arg(
|
||||
long = "persistence-threshold",
|
||||
value_name = "PERSISTENCE_THRESHOLD",
|
||||
default_value_t = DEFAULT_PERSISTENCE_THRESHOLD,
|
||||
verbatim_doc_comment
|
||||
)]
|
||||
persistence_threshold: u64,
|
||||
|
||||
/// The size of the block buffer (channel capacity) for prefetching blocks from the RPC
|
||||
/// endpoint.
|
||||
#[arg(
|
||||
long = "rpc-block-buffer-size",
|
||||
value_name = "RPC_BLOCK_BUFFER_SIZE",
|
||||
default_value = "20",
|
||||
verbatim_doc_comment
|
||||
)]
|
||||
rpc_block_buffer_size: usize,
|
||||
|
||||
#[command(flatten)]
|
||||
benchmark: BenchmarkArgs,
|
||||
}
|
||||
@@ -91,32 +40,6 @@ pub struct Command {
|
||||
impl Command {
|
||||
/// Execute `benchmark new-payload-fcu` command
|
||||
pub async fn execute(self, _ctx: CliContext) -> eyre::Result<()> {
|
||||
// Log mode configuration
|
||||
if let Some(duration) = self.wait_time {
|
||||
info!("Using wait-time mode with {}ms delay between blocks", duration.as_millis());
|
||||
}
|
||||
if self.wait_for_persistence {
|
||||
info!(
|
||||
"Persistence waiting enabled (waits after every {} blocks to match engine gap > {} behavior)",
|
||||
self.persistence_threshold + 1,
|
||||
self.persistence_threshold
|
||||
);
|
||||
}
|
||||
|
||||
// Set up waiter based on configured options (duration takes precedence)
|
||||
let mut waiter = match (self.wait_time, self.wait_for_persistence) {
|
||||
(Some(duration), _) => Some(PersistenceWaiter::with_duration(duration)),
|
||||
(None, true) => {
|
||||
let sub = self.setup_persistence_subscription().await?;
|
||||
Some(PersistenceWaiter::with_subscription(
|
||||
sub,
|
||||
self.persistence_threshold,
|
||||
PERSISTENCE_CHECKPOINT_TIMEOUT,
|
||||
))
|
||||
}
|
||||
(None, false) => None,
|
||||
};
|
||||
|
||||
let BenchContext {
|
||||
benchmark_mode,
|
||||
block_provider,
|
||||
@@ -125,12 +48,7 @@ impl Command {
|
||||
is_optimism,
|
||||
} = BenchContext::new(&self.benchmark, self.rpc_url).await?;
|
||||
|
||||
let buffer_size = self.rpc_block_buffer_size;
|
||||
|
||||
// Use a oneshot channel to propagate errors from the spawned task
|
||||
let (error_sender, mut error_receiver) = tokio::sync::oneshot::channel();
|
||||
let (sender, mut receiver) = tokio::sync::mpsc::channel(buffer_size);
|
||||
|
||||
let (sender, mut receiver) = tokio::sync::mpsc::channel(1000);
|
||||
tokio::task::spawn(async move {
|
||||
while benchmark_mode.contains(next_block) {
|
||||
let block_res = block_provider
|
||||
@@ -138,68 +56,62 @@ impl Command {
|
||||
.full()
|
||||
.await
|
||||
.wrap_err_with(|| format!("Failed to fetch block by number {next_block}"));
|
||||
let block = match block_res.and_then(|opt| opt.ok_or_eyre("Block not found")) {
|
||||
Ok(block) => block,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to fetch block {next_block}: {e}");
|
||||
let _ = error_sender.send(e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
let block = block_res.unwrap().unwrap();
|
||||
let header = block.header.clone();
|
||||
|
||||
let head_block_hash = block.header.hash;
|
||||
let safe_block_hash = block_provider
|
||||
.get_block_by_number(block.header.number.saturating_sub(32).into());
|
||||
let (version, params) = block_to_new_payload(block, is_optimism).unwrap();
|
||||
let head_block_hash = header.hash;
|
||||
let safe_block_hash =
|
||||
block_provider.get_block_by_number(header.number.saturating_sub(32).into());
|
||||
|
||||
let finalized_block_hash = block_provider
|
||||
.get_block_by_number(block.header.number.saturating_sub(64).into());
|
||||
let finalized_block_hash =
|
||||
block_provider.get_block_by_number(header.number.saturating_sub(64).into());
|
||||
|
||||
let (safe, finalized) = tokio::join!(safe_block_hash, finalized_block_hash,);
|
||||
|
||||
let safe_block_hash = match safe {
|
||||
Ok(Some(block)) => block.header.hash,
|
||||
Ok(None) | Err(_) => head_block_hash,
|
||||
};
|
||||
|
||||
let finalized_block_hash = match finalized {
|
||||
Ok(Some(block)) => block.header.hash,
|
||||
Ok(None) | Err(_) => head_block_hash,
|
||||
};
|
||||
let safe_block_hash = safe.unwrap().expect("finalized block exists").header.hash;
|
||||
let finalized_block_hash =
|
||||
finalized.unwrap().expect("finalized block exists").header.hash;
|
||||
|
||||
next_block += 1;
|
||||
if let Err(e) = sender
|
||||
.send((block, head_block_hash, safe_block_hash, finalized_block_hash))
|
||||
sender
|
||||
.send((
|
||||
header,
|
||||
version,
|
||||
params,
|
||||
head_block_hash,
|
||||
safe_block_hash,
|
||||
finalized_block_hash,
|
||||
))
|
||||
.await
|
||||
{
|
||||
tracing::error!("Failed to send block data: {e}");
|
||||
break;
|
||||
}
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
// put results in a summary vec so they can be printed at the end
|
||||
let mut results = Vec::new();
|
||||
let total_benchmark_duration = Instant::now();
|
||||
let mut total_wait_time = Duration::ZERO;
|
||||
|
||||
while let Some((block, head, safe, finalized)) = {
|
||||
while let Some((header, version, params, head, safe, finalized)) = {
|
||||
let wait_start = Instant::now();
|
||||
let result = receiver.recv().await;
|
||||
total_wait_time += wait_start.elapsed();
|
||||
result
|
||||
} {
|
||||
let gas_used = block.header.gas_used;
|
||||
let block_number = block.header.number;
|
||||
let transaction_count = block.transactions.len() as u64;
|
||||
// just put gas used here
|
||||
let gas_used = header.gas_used;
|
||||
let block_number = header.number;
|
||||
|
||||
debug!(target: "reth-bench", ?block_number, "Sending payload");
|
||||
debug!(target: "reth-bench", ?block_number, "Sending payload",);
|
||||
|
||||
// construct fcu to call
|
||||
let forkchoice_state = ForkchoiceState {
|
||||
head_block_hash: head,
|
||||
safe_block_hash: safe,
|
||||
finalized_block_hash: finalized,
|
||||
};
|
||||
|
||||
let (version, params) = block_to_new_payload(block, is_optimism)?;
|
||||
let start = Instant::now();
|
||||
call_new_payload(&auth_provider, version, params).await?;
|
||||
|
||||
@@ -207,55 +119,47 @@ impl Command {
|
||||
|
||||
call_forkchoice_updated(&auth_provider, version, forkchoice_state, None).await?;
|
||||
|
||||
// calculate the total duration and the fcu latency, record
|
||||
let total_latency = start.elapsed();
|
||||
let fcu_latency = total_latency - new_payload_result.latency;
|
||||
let combined_result = CombinedResult {
|
||||
block_number,
|
||||
transaction_count,
|
||||
new_payload_result,
|
||||
fcu_latency,
|
||||
total_latency,
|
||||
};
|
||||
let combined_result =
|
||||
CombinedResult { block_number, new_payload_result, fcu_latency, total_latency };
|
||||
|
||||
// Exclude time spent waiting on the block prefetch channel from the benchmark duration.
|
||||
// We want to measure engine throughput, not RPC fetch latency.
|
||||
// current duration since the start of the benchmark minus the time
|
||||
// waiting for blocks
|
||||
let current_duration = total_benchmark_duration.elapsed() - total_wait_time;
|
||||
|
||||
// convert gas used to gigagas, then compute gigagas per second
|
||||
info!(%combined_result);
|
||||
|
||||
if let Some(w) = &mut waiter {
|
||||
w.on_block(block_number).await?;
|
||||
// wait if we need to
|
||||
if let Some(wait_time) = self.wait_time {
|
||||
tokio::time::sleep(wait_time).await;
|
||||
}
|
||||
|
||||
let gas_row =
|
||||
TotalGasRow { block_number, transaction_count, gas_used, time: current_duration };
|
||||
// record the current result
|
||||
let gas_row = TotalGasRow { block_number, gas_used, time: current_duration };
|
||||
results.push((gas_row, combined_result));
|
||||
}
|
||||
|
||||
// Check if the spawned task encountered an error
|
||||
if let Ok(error) = error_receiver.try_recv() {
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
// Drop waiter - we don't need to wait for final blocks to persist
|
||||
// since the benchmark goal is measuring Ggas/s of newPayload/FCU, not persistence.
|
||||
drop(waiter);
|
||||
|
||||
let (gas_output_results, combined_results): (_, Vec<CombinedResult>) =
|
||||
results.into_iter().unzip();
|
||||
|
||||
// Write CSV output files
|
||||
if let Some(ref path) = self.benchmark.output {
|
||||
// write the csv output to files
|
||||
if let Some(path) = self.benchmark.output {
|
||||
// first write the combined results to a file
|
||||
let output_path = path.join(COMBINED_OUTPUT_SUFFIX);
|
||||
info!("Writing engine api call latency output to file: {:?}", output_path);
|
||||
let mut writer = Writer::from_path(&output_path)?;
|
||||
let mut writer = Writer::from_path(output_path)?;
|
||||
for result in combined_results {
|
||||
writer.serialize(result)?;
|
||||
}
|
||||
writer.flush()?;
|
||||
|
||||
// now write the gas output to a file
|
||||
let output_path = path.join(GAS_OUTPUT_SUFFIX);
|
||||
info!("Writing total gas output to file: {:?}", output_path);
|
||||
let mut writer = Writer::from_path(&output_path)?;
|
||||
let mut writer = Writer::from_path(output_path)?;
|
||||
for row in &gas_output_results {
|
||||
writer.serialize(row)?;
|
||||
}
|
||||
@@ -264,8 +168,8 @@ impl Command {
|
||||
info!("Finished writing benchmark output files to {:?}.", path);
|
||||
}
|
||||
|
||||
// accumulate the results and calculate the overall Ggas/s
|
||||
let gas_output = TotalGasOutput::new(gas_output_results)?;
|
||||
|
||||
info!(
|
||||
total_duration=?gas_output.total_duration,
|
||||
total_gas_used=?gas_output.total_gas_used,
|
||||
@@ -276,278 +180,4 @@ impl Command {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the websocket RPC URL used for the persistence subscription.
|
||||
///
|
||||
/// Preference:
|
||||
/// - If `--ws-rpc-url` is provided, use it directly.
|
||||
/// - Otherwise, derive a WS RPC URL from `--engine-rpc-url`.
|
||||
///
|
||||
/// The persistence subscription endpoint (`reth_subscribePersistedBlock`) is exposed on
|
||||
/// the regular RPC server (WS port, usually 8546), not on the engine API port (usually 8551).
|
||||
/// Since `BenchmarkArgs` only has the engine URL by default, we convert the scheme
|
||||
/// (http→ws, https→wss) and force the port to 8546.
|
||||
fn derive_ws_rpc_url(&self) -> eyre::Result<Url> {
|
||||
if let Some(ref ws_url) = self.benchmark.ws_rpc_url {
|
||||
let parsed: Url = ws_url
|
||||
.parse()
|
||||
.wrap_err_with(|| format!("Failed to parse WebSocket RPC URL: {ws_url}"))?;
|
||||
info!(target: "reth-bench", ws_url = %parsed, "Using provided WebSocket RPC URL");
|
||||
Ok(parsed)
|
||||
} else {
|
||||
let derived = engine_url_to_ws_url(&self.benchmark.engine_rpc_url)?;
|
||||
debug!(
|
||||
target: "reth-bench",
|
||||
engine_url = %self.benchmark.engine_rpc_url,
|
||||
%derived,
|
||||
"Derived WebSocket RPC URL from engine RPC URL"
|
||||
);
|
||||
Ok(derived)
|
||||
}
|
||||
}
|
||||
|
||||
/// Establishes a websocket connection and subscribes to `reth_subscribePersistedBlock`.
|
||||
async fn setup_persistence_subscription(&self) -> eyre::Result<PersistenceSubscription> {
|
||||
let ws_url = self.derive_ws_rpc_url()?;
|
||||
|
||||
info!("Connecting to WebSocket at {} for persistence subscription", ws_url);
|
||||
|
||||
let ws_connect = WsConnect::new(ws_url.to_string());
|
||||
let client = RpcClient::connect_pubsub(ws_connect)
|
||||
.await
|
||||
.wrap_err("Failed to connect to WebSocket RPC endpoint")?;
|
||||
let provider: RootProvider<Ethereum> = RootProvider::new(client);
|
||||
|
||||
let subscription = provider
|
||||
.subscribe_to::<BlockNumHash>("reth_subscribePersistedBlock")
|
||||
.await
|
||||
.wrap_err("Failed to subscribe to persistence notifications")?;
|
||||
|
||||
info!("Subscribed to persistence notifications");
|
||||
|
||||
Ok(PersistenceSubscription::new(provider, subscription.into_stream()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts an engine API URL to the default RPC websocket URL.
|
||||
///
|
||||
/// Transformations:
|
||||
/// - `http` → `ws`
|
||||
/// - `https` → `wss`
|
||||
/// - `ws` / `wss` keep their scheme
|
||||
/// - Port is always set to `8546`, reth's default RPC websocket port.
|
||||
///
|
||||
/// This is used when we only know the engine API URL (typically `:8551`) but
|
||||
/// need to connect to the node's WS RPC endpoint for persistence events.
|
||||
fn engine_url_to_ws_url(engine_url: &str) -> eyre::Result<Url> {
|
||||
let url: Url = engine_url
|
||||
.parse()
|
||||
.wrap_err_with(|| format!("Failed to parse engine RPC URL: {engine_url}"))?;
|
||||
|
||||
let mut ws_url = url.clone();
|
||||
|
||||
match ws_url.scheme() {
|
||||
"http" => ws_url
|
||||
.set_scheme("ws")
|
||||
.map_err(|_| eyre::eyre!("Failed to set WS scheme for URL: {url}"))?,
|
||||
"https" => ws_url
|
||||
.set_scheme("wss")
|
||||
.map_err(|_| eyre::eyre!("Failed to set WSS scheme for URL: {url}"))?,
|
||||
"ws" | "wss" => {}
|
||||
scheme => {
|
||||
return Err(eyre::eyre!(
|
||||
"Unsupported URL scheme '{scheme}' for URL: {url}. Expected http, https, ws, or wss."
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
ws_url.set_port(Some(8546)).map_err(|_| eyre::eyre!("Failed to set port for URL: {url}"))?;
|
||||
|
||||
Ok(ws_url)
|
||||
}
|
||||
|
||||
/// Waits until the persistence subscription reports that `target` has been persisted.
|
||||
///
|
||||
/// Consumes subscription events until `last_persisted >= target`, or returns an error if:
|
||||
/// - the subscription stream ends unexpectedly, or
|
||||
/// - `timeout` elapses before `target` is observed.
|
||||
async fn wait_for_persistence(
|
||||
stream: &mut SubscriptionStream<BlockNumHash>,
|
||||
target: u64,
|
||||
last_persisted: &mut u64,
|
||||
timeout: Duration,
|
||||
) -> eyre::Result<()> {
|
||||
tokio::time::timeout(timeout, async {
|
||||
while *last_persisted < target {
|
||||
match stream.next().await {
|
||||
Some(persisted) => {
|
||||
*last_persisted = persisted.number;
|
||||
debug!(
|
||||
target: "reth-bench",
|
||||
persisted_block = ?last_persisted,
|
||||
"Received persistence notification"
|
||||
);
|
||||
}
|
||||
None => {
|
||||
return Err(eyre::eyre!("Persistence subscription closed unexpectedly"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.map_err(|_| {
|
||||
eyre::eyre!(
|
||||
"Persistence timeout: target block {} not persisted within {:?}. Last persisted: {}",
|
||||
target,
|
||||
timeout,
|
||||
last_persisted
|
||||
)
|
||||
})?
|
||||
}
|
||||
|
||||
/// Wrapper that keeps both the subscription stream and the underlying provider alive.
|
||||
/// The provider must be kept alive for the subscription to continue receiving events.
|
||||
struct PersistenceSubscription {
|
||||
_provider: RootProvider<Ethereum>,
|
||||
stream: SubscriptionStream<BlockNumHash>,
|
||||
}
|
||||
|
||||
impl PersistenceSubscription {
|
||||
const fn new(
|
||||
provider: RootProvider<Ethereum>,
|
||||
stream: SubscriptionStream<BlockNumHash>,
|
||||
) -> Self {
|
||||
Self { _provider: provider, stream }
|
||||
}
|
||||
|
||||
const fn stream_mut(&mut self) -> &mut SubscriptionStream<BlockNumHash> {
|
||||
&mut self.stream
|
||||
}
|
||||
}
|
||||
|
||||
/// Encapsulates the block waiting logic.
|
||||
///
|
||||
/// Provides a simple `on_block()` interface that handles both:
|
||||
/// - Fixed duration waits (when `wait_time` is set)
|
||||
/// - Persistence-based waits (when `subscription` is set)
|
||||
///
|
||||
/// For persistence mode, waits after every `(threshold + 1)` blocks.
|
||||
struct PersistenceWaiter {
|
||||
wait_time: Option<Duration>,
|
||||
subscription: Option<PersistenceSubscription>,
|
||||
blocks_sent: u64,
|
||||
last_persisted: u64,
|
||||
threshold: u64,
|
||||
timeout: Duration,
|
||||
}
|
||||
|
||||
impl PersistenceWaiter {
|
||||
const fn with_duration(wait_time: Duration) -> Self {
|
||||
Self {
|
||||
wait_time: Some(wait_time),
|
||||
subscription: None,
|
||||
blocks_sent: 0,
|
||||
last_persisted: 0,
|
||||
threshold: 0,
|
||||
timeout: Duration::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
const fn with_subscription(
|
||||
subscription: PersistenceSubscription,
|
||||
threshold: u64,
|
||||
timeout: Duration,
|
||||
) -> Self {
|
||||
Self {
|
||||
wait_time: None,
|
||||
subscription: Some(subscription),
|
||||
blocks_sent: 0,
|
||||
last_persisted: 0,
|
||||
threshold,
|
||||
timeout,
|
||||
}
|
||||
}
|
||||
|
||||
/// Called once per block. Waits based on the configured mode.
|
||||
#[allow(clippy::manual_is_multiple_of)]
|
||||
async fn on_block(&mut self, block_number: u64) -> eyre::Result<()> {
|
||||
if let Some(wait_time) = self.wait_time {
|
||||
tokio::time::sleep(wait_time).await;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let Some(ref mut subscription) = self.subscription else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
self.blocks_sent += 1;
|
||||
|
||||
if self.blocks_sent % (self.threshold + 1) == 0 {
|
||||
debug!(
|
||||
target: "reth-bench",
|
||||
target_block = ?block_number,
|
||||
last_persisted = self.last_persisted,
|
||||
blocks_sent = self.blocks_sent,
|
||||
"Waiting for persistence"
|
||||
);
|
||||
|
||||
wait_for_persistence(
|
||||
subscription.stream_mut(),
|
||||
block_number,
|
||||
&mut self.last_persisted,
|
||||
self.timeout,
|
||||
)
|
||||
.await?;
|
||||
|
||||
debug!(
|
||||
target: "reth-bench",
|
||||
persisted = self.last_persisted,
|
||||
"Persistence caught up"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_engine_url_to_ws_url() {
|
||||
// http -> ws, always uses port 8546
|
||||
let result = engine_url_to_ws_url("http://localhost:8551").unwrap();
|
||||
assert_eq!(result.as_str(), "ws://localhost:8546/");
|
||||
|
||||
// https -> wss
|
||||
let result = engine_url_to_ws_url("https://localhost:8551").unwrap();
|
||||
assert_eq!(result.as_str(), "wss://localhost:8546/");
|
||||
|
||||
// Custom engine port still maps to 8546
|
||||
let result = engine_url_to_ws_url("http://localhost:9551").unwrap();
|
||||
assert_eq!(result.port(), Some(8546));
|
||||
|
||||
// Already ws passthrough
|
||||
let result = engine_url_to_ws_url("ws://localhost:8546").unwrap();
|
||||
assert_eq!(result.scheme(), "ws");
|
||||
|
||||
// Invalid inputs
|
||||
assert!(engine_url_to_ws_url("ftp://localhost:8551").is_err());
|
||||
assert!(engine_url_to_ws_url("not a valid url").is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_waiter_with_duration() {
|
||||
let mut waiter = PersistenceWaiter::with_duration(Duration::from_millis(1));
|
||||
|
||||
let start = Instant::now();
|
||||
waiter.on_block(1).await.unwrap();
|
||||
waiter.on_block(2).await.unwrap();
|
||||
waiter.on_block(3).await.unwrap();
|
||||
|
||||
// Should have waited ~3ms total
|
||||
assert!(start.elapsed() >= Duration::from_millis(3));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::{
|
||||
use alloy_provider::Provider;
|
||||
use clap::Parser;
|
||||
use csv::Writer;
|
||||
use eyre::{Context, OptionExt};
|
||||
use eyre::Context;
|
||||
use reth_cli_runner::CliContext;
|
||||
use reth_node_core::args::BenchmarkArgs;
|
||||
use std::time::{Duration, Instant};
|
||||
@@ -26,16 +26,6 @@ pub struct Command {
|
||||
#[arg(long, value_name = "RPC_URL", verbatim_doc_comment)]
|
||||
rpc_url: String,
|
||||
|
||||
/// The size of the block buffer (channel capacity) for prefetching blocks from the RPC
|
||||
/// endpoint.
|
||||
#[arg(
|
||||
long = "rpc-block-buffer-size",
|
||||
value_name = "RPC_BLOCK_BUFFER_SIZE",
|
||||
default_value = "20",
|
||||
verbatim_doc_comment
|
||||
)]
|
||||
rpc_block_buffer_size: usize,
|
||||
|
||||
#[command(flatten)]
|
||||
benchmark: BenchmarkArgs,
|
||||
}
|
||||
@@ -51,12 +41,7 @@ impl Command {
|
||||
is_optimism,
|
||||
} = BenchContext::new(&self.benchmark, self.rpc_url).await?;
|
||||
|
||||
let buffer_size = self.rpc_block_buffer_size;
|
||||
|
||||
// Use a oneshot channel to propagate errors from the spawned task
|
||||
let (error_sender, mut error_receiver) = tokio::sync::oneshot::channel();
|
||||
let (sender, mut receiver) = tokio::sync::mpsc::channel(buffer_size);
|
||||
|
||||
let (sender, mut receiver) = tokio::sync::mpsc::channel(1000);
|
||||
tokio::task::spawn(async move {
|
||||
while benchmark_mode.contains(next_block) {
|
||||
let block_res = block_provider
|
||||
@@ -64,20 +49,13 @@ impl Command {
|
||||
.full()
|
||||
.await
|
||||
.wrap_err_with(|| format!("Failed to fetch block by number {next_block}"));
|
||||
let block = match block_res.and_then(|opt| opt.ok_or_eyre("Block not found")) {
|
||||
Ok(block) => block,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to fetch block {next_block}: {e}");
|
||||
let _ = error_sender.send(e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
let block = block_res.unwrap().unwrap();
|
||||
let header = block.header.clone();
|
||||
|
||||
let (version, params) = block_to_new_payload(block, is_optimism).unwrap();
|
||||
|
||||
next_block += 1;
|
||||
if let Err(e) = sender.send(block).await {
|
||||
tracing::error!("Failed to send block data: {e}");
|
||||
break;
|
||||
}
|
||||
sender.send((header, version, params)).await.unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -86,24 +64,23 @@ impl Command {
|
||||
let total_benchmark_duration = Instant::now();
|
||||
let mut total_wait_time = Duration::ZERO;
|
||||
|
||||
while let Some(block) = {
|
||||
while let Some((header, version, params)) = {
|
||||
let wait_start = Instant::now();
|
||||
let result = receiver.recv().await;
|
||||
total_wait_time += wait_start.elapsed();
|
||||
result
|
||||
} {
|
||||
let block_number = block.header.number;
|
||||
let transaction_count = block.transactions.len() as u64;
|
||||
let gas_used = block.header.gas_used;
|
||||
// just put gas used here
|
||||
let gas_used = header.gas_used;
|
||||
|
||||
let block_number = header.number;
|
||||
|
||||
debug!(
|
||||
target: "reth-bench",
|
||||
number=?block.header.number,
|
||||
number=?header.number,
|
||||
"Sending payload to engine",
|
||||
);
|
||||
|
||||
let (version, params) = block_to_new_payload(block, is_optimism)?;
|
||||
|
||||
let start = Instant::now();
|
||||
call_new_payload(&auth_provider, version, params).await?;
|
||||
|
||||
@@ -115,16 +92,10 @@ impl Command {
|
||||
let current_duration = total_benchmark_duration.elapsed() - total_wait_time;
|
||||
|
||||
// record the current result
|
||||
let row =
|
||||
TotalGasRow { block_number, transaction_count, gas_used, time: current_duration };
|
||||
let row = TotalGasRow { block_number, gas_used, time: current_duration };
|
||||
results.push((row, new_payload_result));
|
||||
}
|
||||
|
||||
// Check if the spawned task encountered an error
|
||||
if let Ok(error) = error_receiver.try_recv() {
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
let (gas_output_results, new_payload_results): (_, Vec<NewPayloadResult>) =
|
||||
results.into_iter().unzip();
|
||||
|
||||
|
||||
@@ -67,8 +67,6 @@ impl Serialize for NewPayloadResult {
|
||||
pub(crate) struct CombinedResult {
|
||||
/// The block number of the block being processed.
|
||||
pub(crate) block_number: u64,
|
||||
/// The number of transactions in the block.
|
||||
pub(crate) transaction_count: u64,
|
||||
/// The `newPayload` result.
|
||||
pub(crate) new_payload_result: NewPayloadResult,
|
||||
/// The latency of the `forkchoiceUpdated` call.
|
||||
@@ -110,11 +108,10 @@ impl Serialize for CombinedResult {
|
||||
let fcu_latency = self.fcu_latency.as_micros();
|
||||
let new_payload_latency = self.new_payload_result.latency.as_micros();
|
||||
let total_latency = self.total_latency.as_micros();
|
||||
let mut state = serializer.serialize_struct("CombinedResult", 6)?;
|
||||
let mut state = serializer.serialize_struct("CombinedResult", 5)?;
|
||||
|
||||
// flatten the new payload result because this is meant for CSV writing
|
||||
state.serialize_field("block_number", &self.block_number)?;
|
||||
state.serialize_field("transaction_count", &self.transaction_count)?;
|
||||
state.serialize_field("gas_used", &self.new_payload_result.gas_used)?;
|
||||
state.serialize_field("new_payload_latency", &new_payload_latency)?;
|
||||
state.serialize_field("fcu_latency", &fcu_latency)?;
|
||||
@@ -128,8 +125,6 @@ impl Serialize for CombinedResult {
|
||||
pub(crate) struct TotalGasRow {
|
||||
/// The block number of the block being processed.
|
||||
pub(crate) block_number: u64,
|
||||
/// The number of transactions in the block.
|
||||
pub(crate) transaction_count: u64,
|
||||
/// The total gas used in the block.
|
||||
pub(crate) gas_used: u64,
|
||||
/// Time since the start of the benchmark.
|
||||
@@ -177,9 +172,8 @@ impl Serialize for TotalGasRow {
|
||||
{
|
||||
// convert the time to microseconds
|
||||
let time = self.time.as_micros();
|
||||
let mut state = serializer.serialize_struct("TotalGasRow", 4)?;
|
||||
let mut state = serializer.serialize_struct("TotalGasRow", 3)?;
|
||||
state.serialize_field("block_number", &self.block_number)?;
|
||||
state.serialize_field("transaction_count", &self.transaction_count)?;
|
||||
state.serialize_field("gas_used", &self.gas_used)?;
|
||||
state.serialize_field("time", &time)?;
|
||||
state.end()
|
||||
@@ -194,12 +188,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_write_total_gas_row_csv() {
|
||||
let row = TotalGasRow {
|
||||
block_number: 1,
|
||||
transaction_count: 10,
|
||||
gas_used: 1_000,
|
||||
time: Duration::from_secs(1),
|
||||
};
|
||||
let row = TotalGasRow { block_number: 1, gas_used: 1_000, time: Duration::from_secs(1) };
|
||||
|
||||
let mut writer = Writer::from_writer(vec![]);
|
||||
writer.serialize(row).unwrap();
|
||||
@@ -209,11 +198,11 @@ mod tests {
|
||||
let mut result = result.as_slice().lines();
|
||||
|
||||
// assert header
|
||||
let expected_first_line = "block_number,transaction_count,gas_used,time";
|
||||
let expected_first_line = "block_number,gas_used,time";
|
||||
let first_line = result.next().unwrap().unwrap();
|
||||
assert_eq!(first_line, expected_first_line);
|
||||
|
||||
let expected_second_line = "1,10,1000,1000000";
|
||||
let expected_second_line = "1,1000,1000000";
|
||||
let second_line = result.next().unwrap().unwrap();
|
||||
assert_eq!(second_line, expected_second_line);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@ use std::ops::RangeInclusive;
|
||||
/// Whether or not the benchmark should run as a continuous stream of payloads.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum BenchMode {
|
||||
// TODO: just include the start block in `Continuous`
|
||||
/// Run the benchmark as a continuous stream of payloads, until the benchmark is interrupted.
|
||||
Continuous(u64),
|
||||
Continuous,
|
||||
/// Run the benchmark for a specific range of blocks.
|
||||
Range(RangeInclusive<u64>),
|
||||
}
|
||||
@@ -15,19 +16,18 @@ impl BenchMode {
|
||||
/// Check if the block number is in the range
|
||||
pub fn contains(&self, block_number: u64) -> bool {
|
||||
match self {
|
||||
Self::Continuous(start) => block_number >= *start,
|
||||
Self::Continuous => true,
|
||||
Self::Range(range) => range.contains(&block_number),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [`BenchMode`] from optional `from` and `to` fields.
|
||||
pub fn new(from: Option<u64>, to: Option<u64>, latest_block: u64) -> Result<Self, eyre::Error> {
|
||||
pub fn new(from: Option<u64>, to: Option<u64>) -> Result<Self, eyre::Error> {
|
||||
// If neither `--from` nor `--to` are provided, we will run the benchmark continuously,
|
||||
// starting at the latest block.
|
||||
match (from, to) {
|
||||
(Some(from), Some(to)) => Ok(Self::Range(from..=to)),
|
||||
(None, None) => Ok(Self::Continuous(latest_block)),
|
||||
(Some(start), None) => Ok(Self::Continuous(start)),
|
||||
(None, None) => Ok(Self::Continuous),
|
||||
_ => {
|
||||
// both or neither are allowed, everything else is ambiguous
|
||||
Err(eyre::eyre!("`from` and `to` must be provided together, or not at all."))
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
|
||||
)]
|
||||
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator();
|
||||
@@ -23,7 +23,7 @@ use bench::BenchmarkCommand;
|
||||
use clap::Parser;
|
||||
use reth_cli_runner::CliRunner;
|
||||
|
||||
fn main() -> eyre::Result<()> {
|
||||
fn main() {
|
||||
// Enable backtraces unless a RUST_BACKTRACE value has already been explicitly provided.
|
||||
if std::env::var_os("RUST_BACKTRACE").is_none() {
|
||||
unsafe {
|
||||
@@ -31,11 +31,12 @@ fn main() -> eyre::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
color_eyre::install()?;
|
||||
|
||||
// Run until either exit or sigint or sigterm
|
||||
let runner = CliRunner::try_default_runtime()?;
|
||||
runner.run_command_until_exit(|ctx| BenchmarkCommand::parse().execute(ctx))?;
|
||||
|
||||
Ok(())
|
||||
let runner = CliRunner::try_default_runtime().unwrap();
|
||||
runner
|
||||
.run_command_until_exit(|ctx| {
|
||||
let command = BenchmarkCommand::parse();
|
||||
command.execute(ctx)
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -9,20 +9,6 @@ repository.workspace = true
|
||||
description = "Reth node implementation"
|
||||
default-run = "reth"
|
||||
|
||||
[package.metadata.deb]
|
||||
maintainer = "reth team"
|
||||
depends = "$auto"
|
||||
section = "network"
|
||||
priority = "optional"
|
||||
maintainer-scripts = "../../pkg/reth/debian/"
|
||||
assets = [
|
||||
"$auto",
|
||||
["../../README.md", "usr/share/doc/reth/", "644"],
|
||||
["../../LICENSE-APACHE", "usr/share/doc/reth/", "644"],
|
||||
["../../LICENSE-MIT", "usr/share/doc/reth/", "644"],
|
||||
]
|
||||
systemd-units = { enable = false, start = false, unit-name = "reth", unit-scripts = "../../pkg/reth/debian" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -54,7 +40,7 @@ reth-node-api.workspace = true
|
||||
reth-node-core.workspace = true
|
||||
reth-ethereum-payload-builder.workspace = true
|
||||
reth-ethereum-primitives.workspace = true
|
||||
reth-node-ethereum.workspace = true
|
||||
reth-node-ethereum = { workspace = true, features = ["js-tracer"] }
|
||||
reth-node-builder.workspace = true
|
||||
reth-node-metrics.workspace = true
|
||||
reth-consensus.workspace = true
|
||||
@@ -81,18 +67,7 @@ backon.workspace = true
|
||||
tempfile.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["jemalloc", "otlp", "reth-revm/portable", "js-tracer", "keccak-cache-global", "asm-keccak"]
|
||||
|
||||
otlp = [
|
||||
"reth-ethereum-cli/otlp",
|
||||
"reth-node-core/otlp",
|
||||
]
|
||||
js-tracer = [
|
||||
"reth-node-builder/js-tracer",
|
||||
"reth-node-ethereum/js-tracer",
|
||||
"reth-rpc/js-tracer",
|
||||
"reth-rpc-eth-types/js-tracer",
|
||||
]
|
||||
default = ["jemalloc", "reth-revm/portable"]
|
||||
|
||||
dev = ["reth-ethereum-cli/dev"]
|
||||
|
||||
@@ -102,10 +77,7 @@ asm-keccak = [
|
||||
"reth-ethereum-cli/asm-keccak",
|
||||
"reth-node-ethereum/asm-keccak",
|
||||
]
|
||||
keccak-cache-global = [
|
||||
"reth-node-core/keccak-cache-global",
|
||||
"reth-node-ethereum/keccak-cache-global",
|
||||
]
|
||||
|
||||
jemalloc = [
|
||||
"reth-cli-util/jemalloc",
|
||||
"reth-node-core/jemalloc",
|
||||
@@ -116,26 +88,10 @@ jemalloc-prof = [
|
||||
"reth-cli-util/jemalloc",
|
||||
"reth-cli-util/jemalloc-prof",
|
||||
"reth-ethereum-cli/jemalloc-prof",
|
||||
"reth-node-metrics/jemalloc-prof",
|
||||
]
|
||||
jemalloc-symbols = [
|
||||
"jemalloc-prof",
|
||||
"reth-ethereum-cli/jemalloc-symbols",
|
||||
]
|
||||
jemalloc-unprefixed = [
|
||||
"reth-cli-util/jemalloc-unprefixed",
|
||||
"reth-node-core/jemalloc",
|
||||
"reth-node-metrics/jemalloc",
|
||||
"reth-ethereum-cli/jemalloc",
|
||||
]
|
||||
tracy-allocator = [
|
||||
"reth-cli-util/tracy-allocator",
|
||||
"reth-ethereum-cli/tracy-allocator",
|
||||
"tracy",
|
||||
]
|
||||
tracy = [
|
||||
"reth-ethereum-cli/tracy",
|
||||
"reth-node-core/tracy",
|
||||
]
|
||||
|
||||
# Because jemalloc is default and preferred over snmalloc when both features are
|
||||
@@ -153,31 +109,24 @@ snmalloc-native = [
|
||||
min-error-logs = [
|
||||
"tracing/release_max_level_error",
|
||||
"reth-ethereum-cli/min-error-logs",
|
||||
"reth-node-core/min-error-logs",
|
||||
]
|
||||
min-warn-logs = [
|
||||
"tracing/release_max_level_warn",
|
||||
"reth-ethereum-cli/min-warn-logs",
|
||||
"reth-node-core/min-warn-logs",
|
||||
]
|
||||
min-info-logs = [
|
||||
"tracing/release_max_level_info",
|
||||
"reth-ethereum-cli/min-info-logs",
|
||||
"reth-node-core/min-info-logs",
|
||||
]
|
||||
min-debug-logs = [
|
||||
"tracing/release_max_level_debug",
|
||||
"reth-ethereum-cli/min-debug-logs",
|
||||
"reth-node-core/min-debug-logs",
|
||||
]
|
||||
min-trace-logs = [
|
||||
"tracing/release_max_level_trace",
|
||||
"reth-ethereum-cli/min-trace-logs",
|
||||
"reth-node-core/min-trace-logs",
|
||||
]
|
||||
|
||||
edge = ["reth-ethereum-cli/edge", "reth-node-core/edge"]
|
||||
|
||||
[[bin]]
|
||||
name = "reth"
|
||||
path = "src/main.rs"
|
||||
|
||||
@@ -2,46 +2,22 @@
|
||||
//!
|
||||
//! ## Feature Flags
|
||||
//!
|
||||
//! ### Default Features
|
||||
//!
|
||||
//! - `jemalloc`: Uses [jemallocator](https://github.com/tikv/jemallocator) as the global allocator.
|
||||
//! This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc)
|
||||
//! for more info.
|
||||
//! - `otlp`: Enables [OpenTelemetry](https://opentelemetry.io/) metrics export to a configured OTLP
|
||||
//! collector endpoint.
|
||||
//! - `js-tracer`: Enables the `JavaScript` tracer for the `debug_trace` endpoints, allowing custom
|
||||
//! `JavaScript`-based transaction tracing.
|
||||
//! - `keccak-cache-global`: Enables global caching for Keccak256 hashes to improve performance.
|
||||
//! - `asm-keccak`: Replaces the default, pure-Rust implementation of Keccak256 with one implemented
|
||||
//! in assembly; see [the `keccak-asm` crate](https://github.com/DaniPopes/keccak-asm) for more
|
||||
//! details and supported targets.
|
||||
//!
|
||||
//! ### Allocator Features
|
||||
//!
|
||||
//! - `jemalloc-prof`: Enables [jemallocator's](https://github.com/tikv/jemallocator) heap profiling
|
||||
//! and leak detection functionality. See [jemalloc's opt.prof](https://jemalloc.net/jemalloc.3.html#opt.prof)
|
||||
//! documentation for usage details. This is **not recommended on Windows**.
|
||||
//! - `jemalloc-symbols`: Enables jemalloc symbols for profiling. Includes `jemalloc-prof`.
|
||||
//! - `jemalloc-unprefixed`: Uses unprefixed jemalloc symbols.
|
||||
//! - `tracy-allocator`: Enables [Tracy](https://github.com/wolfpld/tracy) profiler allocator
|
||||
//! integration for memory profiling.
|
||||
//! - `snmalloc`: Uses [snmalloc](https://github.com/snmalloc/snmalloc) as the global allocator. Use
|
||||
//! `--no-default-features` when enabling this, as jemalloc takes precedence.
|
||||
//! - `snmalloc-native`: Uses snmalloc with native CPU optimizations. Use `--no-default-features`
|
||||
//! when enabling this.
|
||||
//!
|
||||
//! ### Log Level Features
|
||||
//!
|
||||
//! documentation for usage details. This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc)
|
||||
//! for more info.
|
||||
//! - `asm-keccak`: replaces the default, pure-Rust implementation of Keccak256 with one implemented
|
||||
//! in assembly; see [the `keccak-asm` crate](https://github.com/DaniPopes/keccak-asm) for more
|
||||
//! details and supported targets
|
||||
//! - `min-error-logs`: Disables all logs below `error` level.
|
||||
//! - `min-warn-logs`: Disables all logs below `warn` level.
|
||||
//! - `min-info-logs`: Disables all logs below `info` level. This can speed up the node, since fewer
|
||||
//! calls to the logging component are made.
|
||||
//! - `min-debug-logs`: Disables all logs below `debug` level.
|
||||
//! - `min-trace-logs`: Disables all logs below `trace` level.
|
||||
//!
|
||||
//! ### Development Features
|
||||
//!
|
||||
//! - `dev`: Enables development mode features, including test vector generation commands.
|
||||
|
||||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
|
||||
@@ -49,7 +25,7 @@
|
||||
issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
|
||||
)]
|
||||
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
|
||||
|
||||
pub mod cli;
|
||||
|
||||
@@ -194,7 +170,7 @@ pub mod rpc {
|
||||
pub use reth_rpc::eth::*;
|
||||
}
|
||||
|
||||
/// Re-exported from `reth_rpc_server_types::result`.
|
||||
/// Re-exported from `reth_rpc::rpc`.
|
||||
pub mod result {
|
||||
pub use reth_rpc_server_types::result::*;
|
||||
}
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
#[global_allocator]
|
||||
static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator();
|
||||
|
||||
#[cfg(all(feature = "jemalloc-prof", unix))]
|
||||
#[unsafe(export_name = "_rjem_malloc_conf")]
|
||||
static MALLOC_CONF: &[u8] = b"prof:true,prof_active:true,lg_prof_sample:19\0";
|
||||
|
||||
use clap::Parser;
|
||||
use reth::{args::RessArgs, cli::Cli, ress::install_ress_subprotocol};
|
||||
use reth_ethereum_cli::chainspec::EthereumChainSpecParser;
|
||||
|
||||
@@ -54,7 +54,6 @@ reth-testing-utils.workspace = true
|
||||
alloy-signer.workspace = true
|
||||
alloy-signer-local.workspace = true
|
||||
rand.workspace = true
|
||||
revm-state.workspace = true
|
||||
criterion.workspace = true
|
||||
|
||||
[features]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use reth_chain_state::{
|
||||
test_utils::TestBlockBuilder, ExecutedBlock, MemoryOverlayStateProviderRef,
|
||||
test_utils::TestBlockBuilder, ExecutedBlockWithTrieUpdates, MemoryOverlayStateProviderRef,
|
||||
};
|
||||
use reth_ethereum_primitives::EthPrimitives;
|
||||
use reth_storage_api::{noop::NoopProvider, BlockHashReader};
|
||||
@@ -84,7 +84,10 @@ fn bench_canonical_hashes_range(c: &mut Criterion) {
|
||||
|
||||
fn setup_provider_with_blocks(
|
||||
num_blocks: usize,
|
||||
) -> (MemoryOverlayStateProviderRef<'static, EthPrimitives>, Vec<ExecutedBlock<EthPrimitives>>) {
|
||||
) -> (
|
||||
MemoryOverlayStateProviderRef<'static, EthPrimitives>,
|
||||
Vec<ExecutedBlockWithTrieUpdates<EthPrimitives>>,
|
||||
) {
|
||||
let mut builder = TestBlockBuilder::<EthPrimitives>::default();
|
||||
|
||||
let blocks: Vec<_> = builder.get_executed_blocks(1000..1000 + num_blocks as u64).collect();
|
||||
|
||||
@@ -33,7 +33,6 @@ where
|
||||
) -> Self {
|
||||
let (finalized_block, _) = watch::channel(finalized);
|
||||
let (safe_block, _) = watch::channel(safe);
|
||||
let (persisted_block, _) = watch::channel(None);
|
||||
|
||||
Self {
|
||||
inner: Arc::new(ChainInfoInner {
|
||||
@@ -43,7 +42,6 @@ where
|
||||
canonical_head: RwLock::new(head),
|
||||
safe_block,
|
||||
finalized_block,
|
||||
persisted_block,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -79,31 +77,26 @@ where
|
||||
self.inner.finalized_block.borrow().clone()
|
||||
}
|
||||
|
||||
/// Returns the `BlockNumHash` of the canonical head.
|
||||
/// Returns the canonical head of the chain.
|
||||
pub fn get_canonical_num_hash(&self) -> BlockNumHash {
|
||||
self.inner.canonical_head.read().num_hash()
|
||||
}
|
||||
|
||||
/// Returns the block number of the canonical head.
|
||||
/// Returns the canonical head of the chain.
|
||||
pub fn get_canonical_block_number(&self) -> BlockNumber {
|
||||
self.inner.canonical_head_number.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Returns the `BlockNumHash` of the safe header.
|
||||
/// Returns the safe header of the chain.
|
||||
pub fn get_safe_num_hash(&self) -> Option<BlockNumHash> {
|
||||
self.inner.safe_block.borrow().as_ref().map(SealedHeader::num_hash)
|
||||
}
|
||||
|
||||
/// Returns the `BlockNumHash` of the finalized header.
|
||||
/// Returns the finalized header of the chain.
|
||||
pub fn get_finalized_num_hash(&self) -> Option<BlockNumHash> {
|
||||
self.inner.finalized_block.borrow().as_ref().map(SealedHeader::num_hash)
|
||||
}
|
||||
|
||||
/// Returns the `BlockNumHash` of the persisted block.
|
||||
pub fn get_persisted_num_hash(&self) -> Option<BlockNumHash> {
|
||||
*self.inner.persisted_block.borrow()
|
||||
}
|
||||
|
||||
/// Sets the canonical head of the chain.
|
||||
pub fn set_canonical_head(&self, header: SealedHeader<N::BlockHeader>) {
|
||||
let number = header.number();
|
||||
@@ -137,18 +130,6 @@ where
|
||||
});
|
||||
}
|
||||
|
||||
/// Sets the persisted block of the chain.
|
||||
pub fn set_persisted(&self, num_hash: BlockNumHash) {
|
||||
self.inner.persisted_block.send_if_modified(|current| {
|
||||
if current.map(|b| b.hash) != Some(num_hash.hash) {
|
||||
let _ = current.replace(num_hash);
|
||||
return true
|
||||
}
|
||||
|
||||
false
|
||||
});
|
||||
}
|
||||
|
||||
/// Subscribe to the finalized block.
|
||||
pub fn subscribe_finalized_block(
|
||||
&self,
|
||||
@@ -160,11 +141,6 @@ where
|
||||
pub fn subscribe_safe_block(&self) -> watch::Receiver<Option<SealedHeader<N::BlockHeader>>> {
|
||||
self.inner.safe_block.subscribe()
|
||||
}
|
||||
|
||||
/// Subscribe to the persisted block.
|
||||
pub fn subscribe_persisted_block(&self) -> watch::Receiver<Option<BlockNumHash>> {
|
||||
self.inner.persisted_block.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
/// Container type for all chain info fields
|
||||
@@ -183,14 +159,11 @@ struct ChainInfoInner<N: NodePrimitives = reth_ethereum_primitives::EthPrimitive
|
||||
safe_block: watch::Sender<Option<SealedHeader<N::BlockHeader>>>,
|
||||
/// The block that the beacon node considers finalized.
|
||||
finalized_block: watch::Sender<Option<SealedHeader<N::BlockHeader>>>,
|
||||
/// The last block that was persisted to disk.
|
||||
persisted_block: watch::Sender<Option<BlockNumHash>>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloy_primitives::B256;
|
||||
use reth_ethereum_primitives::EthPrimitives;
|
||||
use reth_testing_utils::{generators, generators::random_header};
|
||||
|
||||
@@ -365,28 +338,4 @@ mod tests {
|
||||
// Assert that the BlockNumHash returned matches the safe header
|
||||
assert_eq!(tracker.get_safe_num_hash(), Some(safe_header.num_hash()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_persisted() {
|
||||
let mut rng = generators::rng();
|
||||
let header = random_header(&mut rng, 10, None);
|
||||
let tracker: ChainInfoTracker<EthPrimitives> = ChainInfoTracker::new(header, None, None);
|
||||
|
||||
// Initial state: persisted block should be None
|
||||
assert!(tracker.get_persisted_num_hash().is_none());
|
||||
|
||||
// Set a persisted block
|
||||
let num_hash1 = BlockNumHash::new(10, B256::random());
|
||||
tracker.set_persisted(num_hash1);
|
||||
assert_eq!(tracker.get_persisted_num_hash(), Some(num_hash1));
|
||||
|
||||
// Setting the same block again should not change anything
|
||||
tracker.set_persisted(num_hash1);
|
||||
assert_eq!(tracker.get_persisted_num_hash(), Some(num_hash1));
|
||||
|
||||
// Set a different block
|
||||
let num_hash2 = BlockNumHash::new(20, B256::random());
|
||||
tracker.set_persisted(num_hash2);
|
||||
assert_eq!(tracker.get_persisted_num_hash(), Some(num_hash2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,871 +0,0 @@
|
||||
use alloy_primitives::B256;
|
||||
use parking_lot::Mutex;
|
||||
use reth_metrics::{metrics::Counter, Metrics};
|
||||
use reth_trie::{
|
||||
updates::{TrieUpdates, TrieUpdatesSorted},
|
||||
HashedPostState, HashedPostStateSorted, TrieInputSorted,
|
||||
};
|
||||
use std::{
|
||||
fmt,
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
use tracing::instrument;
|
||||
|
||||
/// Shared handle to asynchronously populated trie data.
|
||||
///
|
||||
/// Uses a try-lock + fallback computation approach for deadlock-free access.
|
||||
/// If the deferred task hasn't completed, computes trie data synchronously
|
||||
/// from stored unsorted inputs rather than blocking.
|
||||
#[derive(Clone)]
|
||||
pub struct DeferredTrieData {
|
||||
/// Shared deferred state holding either raw inputs (pending) or computed result (ready).
|
||||
state: Arc<Mutex<DeferredState>>,
|
||||
}
|
||||
|
||||
/// Sorted trie data computed for an executed block.
|
||||
/// These represent the complete set of sorted trie data required to persist
|
||||
/// block state for, and generate proofs on top of, a block.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ComputedTrieData {
|
||||
/// Sorted hashed post-state produced by execution.
|
||||
pub hashed_state: Arc<HashedPostStateSorted>,
|
||||
/// Sorted trie updates produced by state root computation.
|
||||
pub trie_updates: Arc<TrieUpdatesSorted>,
|
||||
/// Trie input bundled with its anchor hash, if available.
|
||||
pub anchored_trie_input: Option<AnchoredTrieInput>,
|
||||
}
|
||||
|
||||
/// Trie input bundled with its anchor hash.
|
||||
///
|
||||
/// The `trie_input` contains the **cumulative** overlay of all in-memory ancestor blocks,
|
||||
/// not just this block's changes. Child blocks reuse the parent's overlay in O(1) by
|
||||
/// cloning the Arc-wrapped data.
|
||||
///
|
||||
/// The `anchor_hash` is metadata indicating which persisted base state this overlay
|
||||
/// sits on top of. It is CRITICAL for overlay reuse decisions: an overlay built on top
|
||||
/// of Anchor A cannot be reused for a block anchored to Anchor B, as it would result
|
||||
/// in an incorrect state.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AnchoredTrieInput {
|
||||
/// The persisted ancestor hash this trie input is anchored to.
|
||||
pub anchor_hash: B256,
|
||||
/// Cumulative trie input overlay from all in-memory ancestors.
|
||||
pub trie_input: Arc<TrieInputSorted>,
|
||||
}
|
||||
|
||||
/// Metrics for deferred trie computation.
|
||||
#[derive(Metrics)]
|
||||
#[metrics(scope = "sync.block_validation")]
|
||||
struct DeferredTrieMetrics {
|
||||
/// Number of times deferred trie data was ready (async task completed first).
|
||||
deferred_trie_async_ready: Counter,
|
||||
/// Number of times deferred trie data required synchronous computation (fallback path).
|
||||
deferred_trie_sync_fallback: Counter,
|
||||
}
|
||||
|
||||
static DEFERRED_TRIE_METRICS: LazyLock<DeferredTrieMetrics> =
|
||||
LazyLock::new(DeferredTrieMetrics::default);
|
||||
|
||||
/// Internal state for deferred trie data.
|
||||
enum DeferredState {
|
||||
/// Data is not yet available; raw inputs stored for fallback computation.
|
||||
/// Wrapped in `Option` to allow taking ownership during computation.
|
||||
Pending(Option<PendingInputs>),
|
||||
/// Data has been computed and is ready.
|
||||
Ready(ComputedTrieData),
|
||||
}
|
||||
|
||||
/// Inputs kept while a deferred trie computation is pending.
|
||||
#[derive(Clone, Debug)]
|
||||
struct PendingInputs {
|
||||
/// Unsorted hashed post-state from execution.
|
||||
hashed_state: Arc<HashedPostState>,
|
||||
/// Unsorted trie updates from state root computation.
|
||||
trie_updates: Arc<TrieUpdates>,
|
||||
/// The persisted ancestor hash this trie input is anchored to.
|
||||
anchor_hash: B256,
|
||||
/// Deferred trie data from ancestor blocks for merging.
|
||||
ancestors: Vec<DeferredTrieData>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for DeferredTrieData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let state = self.state.lock();
|
||||
match &*state {
|
||||
DeferredState::Pending(_) => {
|
||||
f.debug_struct("DeferredTrieData").field("state", &"pending").finish()
|
||||
}
|
||||
DeferredState::Ready(_) => {
|
||||
f.debug_struct("DeferredTrieData").field("state", &"ready").finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DeferredTrieData {
|
||||
/// Create a new pending handle with fallback inputs for synchronous computation.
|
||||
///
|
||||
/// If the async task hasn't completed when `wait_cloned` is called, the trie data
|
||||
/// will be computed synchronously from these inputs. This eliminates deadlock risk.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `hashed_state` - Unsorted hashed post-state from execution
|
||||
/// * `trie_updates` - Unsorted trie updates from state root computation
|
||||
/// * `anchor_hash` - The persisted ancestor hash this trie input is anchored to
|
||||
/// * `ancestors` - Deferred trie data from ancestor blocks for merging
|
||||
pub fn pending(
|
||||
hashed_state: Arc<HashedPostState>,
|
||||
trie_updates: Arc<TrieUpdates>,
|
||||
anchor_hash: B256,
|
||||
ancestors: Vec<Self>,
|
||||
) -> Self {
|
||||
Self {
|
||||
state: Arc::new(Mutex::new(DeferredState::Pending(Some(PendingInputs {
|
||||
hashed_state,
|
||||
trie_updates,
|
||||
anchor_hash,
|
||||
ancestors,
|
||||
})))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a handle that is already populated with the given [`ComputedTrieData`].
|
||||
///
|
||||
/// Useful when trie data is available immediately.
|
||||
/// [`Self::wait_cloned`] will return without any computation.
|
||||
pub fn ready(bundle: ComputedTrieData) -> Self {
|
||||
Self { state: Arc::new(Mutex::new(DeferredState::Ready(bundle))) }
|
||||
}
|
||||
|
||||
/// Sort block execution outputs and build a [`TrieInputSorted`] overlay.
|
||||
///
|
||||
/// The trie input overlay accumulates sorted hashed state (account/storage changes) and
|
||||
/// trie node updates from all in-memory ancestor blocks. This overlay is required for:
|
||||
/// - Computing state roots on top of in-memory blocks
|
||||
/// - Generating storage/account proofs for unpersisted state
|
||||
///
|
||||
/// # Process
|
||||
/// 1. Sort the current block's hashed state and trie updates
|
||||
/// 2. Reuse parent's cached overlay if available (O(1) - the common case)
|
||||
/// 3. Otherwise, rebuild overlay from ancestors (rare fallback)
|
||||
/// 4. Extend the overlay with this block's sorted data
|
||||
///
|
||||
/// Used by both the async background task and the synchronous fallback path.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `hashed_state` - Unsorted hashed post-state (account/storage changes) from execution
|
||||
/// * `trie_updates` - Unsorted trie node updates from state root computation
|
||||
/// * `anchor_hash` - The persisted ancestor hash this trie input is anchored to
|
||||
/// * `ancestors` - Deferred trie data from ancestor blocks for merging (oldest -> newest)
|
||||
pub fn sort_and_build_trie_input(
|
||||
hashed_state: Arc<HashedPostState>,
|
||||
trie_updates: Arc<TrieUpdates>,
|
||||
anchor_hash: B256,
|
||||
ancestors: &[Self],
|
||||
) -> ComputedTrieData {
|
||||
let sorted_hashed_state = match Arc::try_unwrap(hashed_state) {
|
||||
Ok(state) => state.into_sorted(),
|
||||
Err(arc) => arc.clone_into_sorted(),
|
||||
};
|
||||
let sorted_trie_updates = match Arc::try_unwrap(trie_updates) {
|
||||
Ok(updates) => updates.into_sorted(),
|
||||
Err(arc) => arc.clone_into_sorted(),
|
||||
};
|
||||
|
||||
// Reuse parent's overlay if available and anchors match.
|
||||
// We can only reuse the parent's overlay if it was built on top of the same
|
||||
// persisted anchor. If the anchor has changed (e.g., due to persistence),
|
||||
// the parent's overlay is relative to an old state and cannot be used.
|
||||
let overlay = if let Some(parent) = ancestors.last() {
|
||||
let parent_data = parent.wait_cloned();
|
||||
|
||||
match &parent_data.anchored_trie_input {
|
||||
// Case 1: Parent has cached overlay AND anchors match.
|
||||
Some(AnchoredTrieInput { anchor_hash: parent_anchor, trie_input })
|
||||
if *parent_anchor == anchor_hash =>
|
||||
{
|
||||
// O(1): Reuse parent's overlay, extend with current block's data.
|
||||
let mut overlay = TrieInputSorted::new(
|
||||
Arc::clone(&trie_input.nodes),
|
||||
Arc::clone(&trie_input.state),
|
||||
Default::default(), // prefix_sets are per-block, not cumulative
|
||||
);
|
||||
// Only trigger COW clone if there's actually data to add.
|
||||
if !sorted_hashed_state.is_empty() {
|
||||
Arc::make_mut(&mut overlay.state).extend_ref(&sorted_hashed_state);
|
||||
}
|
||||
if !sorted_trie_updates.is_empty() {
|
||||
Arc::make_mut(&mut overlay.nodes).extend_ref(&sorted_trie_updates);
|
||||
}
|
||||
overlay
|
||||
}
|
||||
// Case 2: Parent exists but anchor mismatch or no cached overlay.
|
||||
// We must rebuild from the ancestors list (which only contains unpersisted blocks).
|
||||
_ => Self::merge_ancestors_into_overlay(
|
||||
ancestors,
|
||||
&sorted_hashed_state,
|
||||
&sorted_trie_updates,
|
||||
),
|
||||
}
|
||||
} else {
|
||||
// Case 3: No in-memory ancestors (first block after persisted anchor).
|
||||
// Build overlay with just this block's data.
|
||||
Self::merge_ancestors_into_overlay(&[], &sorted_hashed_state, &sorted_trie_updates)
|
||||
};
|
||||
|
||||
ComputedTrieData::with_trie_input(
|
||||
Arc::new(sorted_hashed_state),
|
||||
Arc::new(sorted_trie_updates),
|
||||
anchor_hash,
|
||||
Arc::new(overlay),
|
||||
)
|
||||
}
|
||||
|
||||
/// Merge all ancestors and current block's data into a single overlay.
|
||||
///
|
||||
/// This is a rare fallback path, only used when no ancestor has a cached
|
||||
/// `anchored_trie_input` (e.g., blocks created via alternative constructors).
|
||||
/// In normal operation, the parent always has a cached overlay and this
|
||||
/// function is never called.
|
||||
///
|
||||
/// Iterates ancestors oldest -> newest, then extends with current block's data,
|
||||
/// so later state takes precedence.
|
||||
fn merge_ancestors_into_overlay(
|
||||
ancestors: &[Self],
|
||||
sorted_hashed_state: &HashedPostStateSorted,
|
||||
sorted_trie_updates: &TrieUpdatesSorted,
|
||||
) -> TrieInputSorted {
|
||||
let mut overlay = TrieInputSorted::default();
|
||||
|
||||
let state_mut = Arc::make_mut(&mut overlay.state);
|
||||
let nodes_mut = Arc::make_mut(&mut overlay.nodes);
|
||||
|
||||
for ancestor in ancestors {
|
||||
let ancestor_data = ancestor.wait_cloned();
|
||||
state_mut.extend_ref(ancestor_data.hashed_state.as_ref());
|
||||
nodes_mut.extend_ref(ancestor_data.trie_updates.as_ref());
|
||||
}
|
||||
|
||||
// Extend with current block's sorted data last (takes precedence)
|
||||
state_mut.extend_ref(sorted_hashed_state);
|
||||
nodes_mut.extend_ref(sorted_trie_updates);
|
||||
|
||||
overlay
|
||||
}
|
||||
|
||||
/// Returns trie data, computing synchronously if the async task hasn't completed.
|
||||
///
|
||||
/// - If the async task has completed (`Ready`), returns the cached result.
|
||||
/// - If pending, computes synchronously from stored inputs.
|
||||
///
|
||||
/// Deadlock is avoided as long as the provided ancestors form a true ancestor chain (a DAG):
|
||||
/// - Each block only waits on its ancestors (blocks on the path to the persisted root)
|
||||
/// - Sibling blocks (forks) are never in each other's ancestor lists
|
||||
/// - A block never waits on its descendants
|
||||
///
|
||||
/// Given that invariant, circular wait dependencies are impossible.
|
||||
#[instrument(level = "debug", target = "engine::tree::deferred_trie", skip_all)]
|
||||
pub fn wait_cloned(&self) -> ComputedTrieData {
|
||||
let mut state = self.state.lock();
|
||||
match &mut *state {
|
||||
// If the deferred trie data is ready, return the cached result.
|
||||
DeferredState::Ready(bundle) => {
|
||||
DEFERRED_TRIE_METRICS.deferred_trie_async_ready.increment(1);
|
||||
bundle.clone()
|
||||
}
|
||||
// If the deferred trie data is pending, compute the trie data synchronously and return
|
||||
// the result. This is the fallback path if the async task hasn't completed.
|
||||
DeferredState::Pending(maybe_inputs) => {
|
||||
DEFERRED_TRIE_METRICS.deferred_trie_sync_fallback.increment(1);
|
||||
|
||||
let inputs = maybe_inputs.take().expect("inputs must be present in Pending state");
|
||||
|
||||
let computed = Self::sort_and_build_trie_input(
|
||||
inputs.hashed_state,
|
||||
inputs.trie_updates,
|
||||
inputs.anchor_hash,
|
||||
&inputs.ancestors,
|
||||
);
|
||||
*state = DeferredState::Ready(computed.clone());
|
||||
computed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ComputedTrieData {
|
||||
/// Construct a bundle that includes trie input anchored to a persisted ancestor.
|
||||
pub const fn with_trie_input(
|
||||
hashed_state: Arc<HashedPostStateSorted>,
|
||||
trie_updates: Arc<TrieUpdatesSorted>,
|
||||
anchor_hash: B256,
|
||||
trie_input: Arc<TrieInputSorted>,
|
||||
) -> Self {
|
||||
Self {
|
||||
hashed_state,
|
||||
trie_updates,
|
||||
anchored_trie_input: Some(AnchoredTrieInput { anchor_hash, trie_input }),
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a bundle without trie input or anchor information.
|
||||
///
|
||||
/// Unlike [`Self::with_trie_input`], this constructor omits the accumulated trie input overlay
|
||||
/// and its anchor hash. Use this when the trie input is not needed, such as in block builders
|
||||
/// or sequencers that don't require proof generation on top of in-memory state.
|
||||
///
|
||||
/// The trie input anchor identifies the persisted block hash from which the in-memory overlay
|
||||
/// was built. Without it, consumers cannot determine which on-disk state to combine with.
|
||||
pub const fn without_trie_input(
|
||||
hashed_state: Arc<HashedPostStateSorted>,
|
||||
trie_updates: Arc<TrieUpdatesSorted>,
|
||||
) -> Self {
|
||||
Self { hashed_state, trie_updates, anchored_trie_input: None }
|
||||
}
|
||||
|
||||
/// Returns the anchor hash, if present.
|
||||
pub fn anchor_hash(&self) -> Option<B256> {
|
||||
self.anchored_trie_input.as_ref().map(|anchored| anchored.anchor_hash)
|
||||
}
|
||||
|
||||
/// Returns the trie input, if present.
|
||||
pub fn trie_input(&self) -> Option<&Arc<TrieInputSorted>> {
|
||||
self.anchored_trie_input.as_ref().map(|anchored| &anchored.trie_input)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloy_primitives::{map::B256Map, U256};
|
||||
use reth_primitives_traits::Account;
|
||||
use reth_trie::updates::TrieUpdates;
|
||||
use std::{
|
||||
sync::Arc,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
fn empty_bundle() -> ComputedTrieData {
|
||||
ComputedTrieData {
|
||||
hashed_state: Arc::default(),
|
||||
trie_updates: Arc::default(),
|
||||
anchored_trie_input: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn empty_pending() -> DeferredTrieData {
|
||||
empty_pending_with_anchor(B256::ZERO)
|
||||
}
|
||||
|
||||
fn empty_pending_with_anchor(anchor: B256) -> DeferredTrieData {
|
||||
DeferredTrieData::pending(
|
||||
Arc::new(HashedPostState::default()),
|
||||
Arc::new(TrieUpdates::default()),
|
||||
anchor,
|
||||
Vec::new(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Verifies that a ready handle returns immediately without computation.
|
||||
#[test]
|
||||
fn ready_returns_immediately() {
|
||||
let bundle = empty_bundle();
|
||||
let deferred = DeferredTrieData::ready(bundle.clone());
|
||||
|
||||
let start = Instant::now();
|
||||
let result = deferred.wait_cloned();
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
assert_eq!(result.hashed_state, bundle.hashed_state);
|
||||
assert_eq!(result.trie_updates, bundle.trie_updates);
|
||||
assert_eq!(result.anchor_hash(), bundle.anchor_hash());
|
||||
assert!(elapsed < Duration::from_millis(20));
|
||||
}
|
||||
|
||||
/// Verifies that a pending handle computes trie data synchronously via fallback.
|
||||
#[test]
|
||||
fn pending_computes_fallback() {
|
||||
let deferred = empty_pending();
|
||||
|
||||
// wait_cloned should compute from inputs without blocking
|
||||
let start = Instant::now();
|
||||
let result = deferred.wait_cloned();
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
// Should return quickly (fallback computation)
|
||||
assert!(elapsed < Duration::from_millis(100));
|
||||
assert!(result.hashed_state.is_empty());
|
||||
}
|
||||
|
||||
/// Verifies that fallback computation result is cached for subsequent calls.
|
||||
#[test]
|
||||
fn fallback_result_is_cached() {
|
||||
let deferred = empty_pending();
|
||||
|
||||
// First call computes and should stash the result
|
||||
let first = deferred.wait_cloned();
|
||||
// Second call should reuse the cached result (same Arc pointer)
|
||||
let second = deferred.wait_cloned();
|
||||
|
||||
assert!(Arc::ptr_eq(&first.hashed_state, &second.hashed_state));
|
||||
assert!(Arc::ptr_eq(&first.trie_updates, &second.trie_updates));
|
||||
assert_eq!(first.anchor_hash(), second.anchor_hash());
|
||||
}
|
||||
|
||||
/// Verifies that concurrent `wait_cloned` calls result in only one computation,
|
||||
/// with all callers receiving the same cached result.
|
||||
#[test]
|
||||
fn concurrent_wait_cloned_computes_once() {
|
||||
let deferred = empty_pending();
|
||||
|
||||
// Spawn multiple threads that all call wait_cloned concurrently
|
||||
let handles: Vec<_> = (0..10)
|
||||
.map(|_| {
|
||||
let d = deferred.clone();
|
||||
thread::spawn(move || d.wait_cloned())
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Collect all results
|
||||
let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
|
||||
|
||||
// All results should share the same Arc pointers (same computed result)
|
||||
let first = &results[0];
|
||||
for result in &results[1..] {
|
||||
assert!(Arc::ptr_eq(&first.hashed_state, &result.hashed_state));
|
||||
assert!(Arc::ptr_eq(&first.trie_updates, &result.trie_updates));
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that ancestor trie data is merged during fallback computation and that the
|
||||
/// resulting `ComputedTrieData` uses the current block's anchor hash, not the ancestor's.
|
||||
#[test]
|
||||
fn ancestors_are_merged() {
|
||||
// Create ancestor with some data
|
||||
let ancestor_bundle = ComputedTrieData {
|
||||
hashed_state: Arc::default(),
|
||||
trie_updates: Arc::default(),
|
||||
anchored_trie_input: Some(AnchoredTrieInput {
|
||||
anchor_hash: B256::with_last_byte(1),
|
||||
trie_input: Arc::new(TrieInputSorted::default()),
|
||||
}),
|
||||
};
|
||||
let ancestor = DeferredTrieData::ready(ancestor_bundle);
|
||||
|
||||
// Create pending with ancestor
|
||||
let deferred = DeferredTrieData::pending(
|
||||
Arc::new(HashedPostState::default()),
|
||||
Arc::new(TrieUpdates::default()),
|
||||
B256::with_last_byte(2),
|
||||
vec![ancestor],
|
||||
);
|
||||
|
||||
let result = deferred.wait_cloned();
|
||||
// Should have the current block's anchor, not the ancestor's
|
||||
assert_eq!(result.anchor_hash(), Some(B256::with_last_byte(2)));
|
||||
}
|
||||
|
||||
/// Ensures ancestor overlays are merged oldest -> newest so latest state wins (no overwrite by
|
||||
/// older ancestors).
|
||||
#[test]
|
||||
fn ancestors_merge_in_chronological_order() {
|
||||
let key = B256::with_last_byte(1);
|
||||
// Oldest ancestor sets nonce to 1
|
||||
let oldest_state = HashedPostStateSorted::new(
|
||||
vec![(key, Some(Account { nonce: 1, balance: U256::ZERO, bytecode_hash: None }))],
|
||||
B256Map::default(),
|
||||
);
|
||||
// Newest ancestor overwrites nonce to 2
|
||||
let newest_state = HashedPostStateSorted::new(
|
||||
vec![(key, Some(Account { nonce: 2, balance: U256::ZERO, bytecode_hash: None }))],
|
||||
B256Map::default(),
|
||||
);
|
||||
|
||||
let oldest = ComputedTrieData {
|
||||
hashed_state: Arc::new(oldest_state),
|
||||
trie_updates: Arc::default(),
|
||||
anchored_trie_input: None,
|
||||
};
|
||||
let newest = ComputedTrieData {
|
||||
hashed_state: Arc::new(newest_state),
|
||||
trie_updates: Arc::default(),
|
||||
anchored_trie_input: None,
|
||||
};
|
||||
|
||||
// Pass ancestors oldest -> newest; newest should take precedence
|
||||
let deferred = DeferredTrieData::pending(
|
||||
Arc::new(HashedPostState::default()),
|
||||
Arc::new(TrieUpdates::default()),
|
||||
B256::ZERO,
|
||||
vec![DeferredTrieData::ready(oldest), DeferredTrieData::ready(newest)],
|
||||
);
|
||||
|
||||
let result = deferred.wait_cloned();
|
||||
let overlay_state = &result.anchored_trie_input.as_ref().unwrap().trie_input.state.accounts;
|
||||
assert_eq!(overlay_state.len(), 1);
|
||||
let (_, account) = &overlay_state[0];
|
||||
assert_eq!(account.unwrap().nonce, 2);
|
||||
}
|
||||
|
||||
/// Helper to create a ready block with anchored trie input containing specific state.
|
||||
fn ready_block_with_state(
|
||||
anchor_hash: B256,
|
||||
accounts: Vec<(B256, Option<Account>)>,
|
||||
) -> DeferredTrieData {
|
||||
let hashed_state = Arc::new(HashedPostStateSorted::new(accounts, B256Map::default()));
|
||||
let trie_updates = Arc::default();
|
||||
let mut overlay = TrieInputSorted::default();
|
||||
Arc::make_mut(&mut overlay.state).extend_ref(hashed_state.as_ref());
|
||||
|
||||
DeferredTrieData::ready(ComputedTrieData {
|
||||
hashed_state,
|
||||
trie_updates,
|
||||
anchored_trie_input: Some(AnchoredTrieInput {
|
||||
anchor_hash,
|
||||
trie_input: Arc::new(overlay),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
/// Verifies that first block after anchor (no ancestors) creates empty base overlay.
|
||||
#[test]
|
||||
fn first_block_after_anchor_creates_empty_base() {
|
||||
let anchor = B256::with_last_byte(1);
|
||||
let key = B256::with_last_byte(42);
|
||||
let account = Account { nonce: 1, balance: U256::ZERO, bytecode_hash: None };
|
||||
|
||||
// First block after anchor - no ancestors
|
||||
let first_block = DeferredTrieData::pending(
|
||||
Arc::new(HashedPostState::default().with_accounts([(key, Some(account))])),
|
||||
Arc::new(TrieUpdates::default()),
|
||||
anchor,
|
||||
vec![], // No ancestors
|
||||
);
|
||||
|
||||
let result = first_block.wait_cloned();
|
||||
|
||||
// Should have overlay with just this block's data
|
||||
let overlay = result.anchored_trie_input.as_ref().unwrap();
|
||||
assert_eq!(overlay.anchor_hash, anchor);
|
||||
assert_eq!(overlay.trie_input.state.accounts.len(), 1);
|
||||
let (found_key, found_account) = &overlay.trie_input.state.accounts[0];
|
||||
assert_eq!(*found_key, key);
|
||||
assert_eq!(found_account.unwrap().nonce, 1);
|
||||
}
|
||||
|
||||
/// Verifies that parent's overlay is reused regardless of anchor.
|
||||
#[test]
|
||||
fn reuses_parent_overlay() {
|
||||
let anchor = B256::with_last_byte(1);
|
||||
let key = B256::with_last_byte(42);
|
||||
let account = Account { nonce: 100, balance: U256::ZERO, bytecode_hash: None };
|
||||
|
||||
// Create parent with anchored trie input
|
||||
let parent = ready_block_with_state(anchor, vec![(key, Some(account))]);
|
||||
|
||||
// Create child - should reuse parent's overlay
|
||||
let child = DeferredTrieData::pending(
|
||||
Arc::new(HashedPostState::default()),
|
||||
Arc::new(TrieUpdates::default()),
|
||||
anchor,
|
||||
vec![parent],
|
||||
);
|
||||
|
||||
let result = child.wait_cloned();
|
||||
|
||||
// Verify parent's account is in the overlay
|
||||
let overlay = result.anchored_trie_input.as_ref().unwrap();
|
||||
assert_eq!(overlay.anchor_hash, anchor);
|
||||
assert_eq!(overlay.trie_input.state.accounts.len(), 1);
|
||||
let (found_key, found_account) = &overlay.trie_input.state.accounts[0];
|
||||
assert_eq!(*found_key, key);
|
||||
assert_eq!(found_account.unwrap().nonce, 100);
|
||||
}
|
||||
|
||||
/// Verifies that parent's overlay is NOT reused when anchor changes (after persist).
|
||||
/// The overlay data is dependent on the anchor, so it must be rebuilt from the
|
||||
/// remaining ancestors.
|
||||
#[test]
|
||||
fn rebuilds_overlay_when_anchor_changes() {
|
||||
let old_anchor = B256::with_last_byte(1);
|
||||
let new_anchor = B256::with_last_byte(2);
|
||||
let key = B256::with_last_byte(42);
|
||||
let account = Account { nonce: 50, balance: U256::ZERO, bytecode_hash: None };
|
||||
|
||||
// Create parent with OLD anchor
|
||||
let parent = ready_block_with_state(old_anchor, vec![(key, Some(account))]);
|
||||
|
||||
// Create child with NEW anchor (simulates after persist)
|
||||
// Should NOT reuse parent's overlay because anchor changed
|
||||
let child = DeferredTrieData::pending(
|
||||
Arc::new(HashedPostState::default()),
|
||||
Arc::new(TrieUpdates::default()),
|
||||
new_anchor,
|
||||
vec![parent],
|
||||
);
|
||||
|
||||
let result = child.wait_cloned();
|
||||
|
||||
// Verify result uses new anchor
|
||||
let overlay = result.anchored_trie_input.as_ref().unwrap();
|
||||
assert_eq!(overlay.anchor_hash, new_anchor);
|
||||
|
||||
// Crucially, since we provided `parent` in ancestors but it has a different anchor,
|
||||
// the code falls back to `merge_ancestors_into_overlay`.
|
||||
// `merge_ancestors_into_overlay` reads `parent.hashed_state` (which has the account).
|
||||
// So the account IS present, but it was obtained via REBUILD, not REUSE.
|
||||
// We can check `DEFERRED_TRIE_METRICS` if we want to be sure, but functionally:
|
||||
assert_eq!(overlay.trie_input.state.accounts.len(), 1);
|
||||
let (found_key, found_account) = &overlay.trie_input.state.accounts[0];
|
||||
assert_eq!(*found_key, key);
|
||||
assert_eq!(found_account.unwrap().nonce, 50);
|
||||
}
|
||||
|
||||
/// Verifies that parent without `anchored_trie_input` triggers rebuild path.
|
||||
#[test]
|
||||
fn rebuilds_when_parent_has_no_anchored_input() {
|
||||
let anchor = B256::with_last_byte(1);
|
||||
let key = B256::with_last_byte(42);
|
||||
let account = Account { nonce: 25, balance: U256::ZERO, bytecode_hash: None };
|
||||
|
||||
// Create parent WITHOUT anchored trie input (e.g., from without_trie_input constructor)
|
||||
let parent_state =
|
||||
HashedPostStateSorted::new(vec![(key, Some(account))], B256Map::default());
|
||||
let parent = DeferredTrieData::ready(ComputedTrieData {
|
||||
hashed_state: Arc::new(parent_state),
|
||||
trie_updates: Arc::default(),
|
||||
anchored_trie_input: None, // No anchored input
|
||||
});
|
||||
|
||||
// Create child - should rebuild from parent's hashed_state
|
||||
let child = DeferredTrieData::pending(
|
||||
Arc::new(HashedPostState::default()),
|
||||
Arc::new(TrieUpdates::default()),
|
||||
anchor,
|
||||
vec![parent],
|
||||
);
|
||||
|
||||
let result = child.wait_cloned();
|
||||
|
||||
// Verify overlay is built and contains parent's data
|
||||
let overlay = result.anchored_trie_input.as_ref().unwrap();
|
||||
assert_eq!(overlay.anchor_hash, anchor);
|
||||
assert_eq!(overlay.trie_input.state.accounts.len(), 1);
|
||||
}
|
||||
|
||||
/// Verifies that a chain of blocks with matching anchors builds correct cumulative overlay.
|
||||
#[test]
|
||||
fn chain_of_blocks_builds_cumulative_overlay() {
|
||||
let anchor = B256::with_last_byte(1);
|
||||
let key1 = B256::with_last_byte(1);
|
||||
let key2 = B256::with_last_byte(2);
|
||||
let key3 = B256::with_last_byte(3);
|
||||
|
||||
// Block 1: sets account at key1
|
||||
let block1 = ready_block_with_state(
|
||||
anchor,
|
||||
vec![(key1, Some(Account { nonce: 1, balance: U256::ZERO, bytecode_hash: None }))],
|
||||
);
|
||||
|
||||
// Block 2: adds account at key2, ancestor is block1
|
||||
let block2_hashed = HashedPostState::default().with_accounts([(
|
||||
key2,
|
||||
Some(Account { nonce: 2, balance: U256::ZERO, bytecode_hash: None }),
|
||||
)]);
|
||||
let block2 = DeferredTrieData::pending(
|
||||
Arc::new(block2_hashed),
|
||||
Arc::new(TrieUpdates::default()),
|
||||
anchor,
|
||||
vec![block1.clone()],
|
||||
);
|
||||
// Compute block2's trie data
|
||||
let block2_computed = block2.wait_cloned();
|
||||
let block2_ready = DeferredTrieData::ready(block2_computed);
|
||||
|
||||
// Block 3: adds account at key3, ancestor is block2 (which includes block1)
|
||||
let block3_hashed = HashedPostState::default().with_accounts([(
|
||||
key3,
|
||||
Some(Account { nonce: 3, balance: U256::ZERO, bytecode_hash: None }),
|
||||
)]);
|
||||
let block3 = DeferredTrieData::pending(
|
||||
Arc::new(block3_hashed),
|
||||
Arc::new(TrieUpdates::default()),
|
||||
anchor,
|
||||
vec![block1, block2_ready],
|
||||
);
|
||||
|
||||
let result = block3.wait_cloned();
|
||||
|
||||
// Verify all three accounts are in the cumulative overlay
|
||||
let overlay = result.anchored_trie_input.as_ref().unwrap();
|
||||
assert_eq!(overlay.trie_input.state.accounts.len(), 3);
|
||||
|
||||
// Accounts should be sorted by key (B256 ordering)
|
||||
let accounts = &overlay.trie_input.state.accounts;
|
||||
assert!(accounts.iter().any(|(k, a)| *k == key1 && a.unwrap().nonce == 1));
|
||||
assert!(accounts.iter().any(|(k, a)| *k == key2 && a.unwrap().nonce == 2));
|
||||
assert!(accounts.iter().any(|(k, a)| *k == key3 && a.unwrap().nonce == 3));
|
||||
}
|
||||
|
||||
/// Verifies that child block's state overwrites parent's state for the same key.
|
||||
#[test]
|
||||
fn child_state_overwrites_parent() {
|
||||
let anchor = B256::with_last_byte(1);
|
||||
let key = B256::with_last_byte(42);
|
||||
|
||||
// Parent sets nonce to 10
|
||||
let parent = ready_block_with_state(
|
||||
anchor,
|
||||
vec![(key, Some(Account { nonce: 10, balance: U256::ZERO, bytecode_hash: None }))],
|
||||
);
|
||||
|
||||
// Child overwrites nonce to 99
|
||||
let child_hashed = HashedPostState::default().with_accounts([(
|
||||
key,
|
||||
Some(Account { nonce: 99, balance: U256::ZERO, bytecode_hash: None }),
|
||||
)]);
|
||||
let child = DeferredTrieData::pending(
|
||||
Arc::new(child_hashed),
|
||||
Arc::new(TrieUpdates::default()),
|
||||
anchor,
|
||||
vec![parent],
|
||||
);
|
||||
|
||||
let result = child.wait_cloned();
|
||||
|
||||
// Verify child's value wins (extend_ref uses later value)
|
||||
let overlay = result.anchored_trie_input.as_ref().unwrap();
|
||||
// Note: extend_ref may result in duplicate keys; check the last occurrence
|
||||
let accounts = &overlay.trie_input.state.accounts;
|
||||
let last_account = accounts.iter().rfind(|(k, _)| *k == key).unwrap();
|
||||
assert_eq!(last_account.1.unwrap().nonce, 99);
|
||||
}
|
||||
|
||||
/// Stress test: verify O(N) behavior by building a chain of many blocks.
|
||||
/// This test ensures the fix doesn't regress - previously this would be O(N²).
|
||||
#[test]
|
||||
fn long_chain_builds_in_linear_time() {
|
||||
let anchor = B256::with_last_byte(1);
|
||||
let num_blocks = 50; // Enough to notice O(N²) vs O(N) difference
|
||||
|
||||
let mut ancestors: Vec<DeferredTrieData> = Vec::new();
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..num_blocks {
|
||||
let key = B256::with_last_byte(i as u8);
|
||||
let account = Account { nonce: i as u64, balance: U256::ZERO, bytecode_hash: None };
|
||||
let hashed = HashedPostState::default().with_accounts([(key, Some(account))]);
|
||||
|
||||
let block = DeferredTrieData::pending(
|
||||
Arc::new(hashed),
|
||||
Arc::new(TrieUpdates::default()),
|
||||
anchor,
|
||||
ancestors.clone(),
|
||||
);
|
||||
|
||||
// Compute and add to ancestors for next iteration
|
||||
let computed = block.wait_cloned();
|
||||
ancestors.push(DeferredTrieData::ready(computed));
|
||||
}
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
// With O(N) fix, 50 blocks should complete quickly (< 1 second)
|
||||
// With O(N²), this would take significantly longer
|
||||
assert!(
|
||||
elapsed < Duration::from_secs(2),
|
||||
"Chain of {num_blocks} blocks took {:?}, possible O(N²) regression",
|
||||
elapsed
|
||||
);
|
||||
|
||||
// Verify final overlay has all accounts
|
||||
let final_result = ancestors.last().unwrap().wait_cloned();
|
||||
let overlay = final_result.anchored_trie_input.as_ref().unwrap();
|
||||
assert_eq!(overlay.trie_input.state.accounts.len(), num_blocks);
|
||||
}
|
||||
|
||||
/// Verifies that a multi-ancestor overlay is rebuilt when anchor changes.
|
||||
/// This simulates the "persist prefix then keep building" scenario where:
|
||||
/// 1. A chain of blocks is built with anchor A
|
||||
/// 2. Some blocks are persisted, changing anchor to B
|
||||
/// 3. New blocks must rebuild the overlay from the remaining ancestors
|
||||
#[test]
|
||||
fn multi_ancestor_overlay_rebuilt_after_anchor_change() {
|
||||
let old_anchor = B256::with_last_byte(1);
|
||||
let new_anchor = B256::with_last_byte(2);
|
||||
let key1 = B256::with_last_byte(1);
|
||||
let key2 = B256::with_last_byte(2);
|
||||
let key3 = B256::with_last_byte(3);
|
||||
let key4 = B256::with_last_byte(4);
|
||||
|
||||
// Build a chain of 3 blocks with old_anchor
|
||||
let block1 = ready_block_with_state(
|
||||
old_anchor,
|
||||
vec![(key1, Some(Account { nonce: 1, balance: U256::ZERO, bytecode_hash: None }))],
|
||||
);
|
||||
|
||||
let block2_hashed = HashedPostState::default().with_accounts([(
|
||||
key2,
|
||||
Some(Account { nonce: 2, balance: U256::ZERO, bytecode_hash: None }),
|
||||
)]);
|
||||
let block2 = DeferredTrieData::pending(
|
||||
Arc::new(block2_hashed),
|
||||
Arc::new(TrieUpdates::default()),
|
||||
old_anchor,
|
||||
vec![block1.clone()],
|
||||
);
|
||||
let block2_ready = DeferredTrieData::ready(block2.wait_cloned());
|
||||
|
||||
let block3_hashed = HashedPostState::default().with_accounts([(
|
||||
key3,
|
||||
Some(Account { nonce: 3, balance: U256::ZERO, bytecode_hash: None }),
|
||||
)]);
|
||||
let block3 = DeferredTrieData::pending(
|
||||
Arc::new(block3_hashed),
|
||||
Arc::new(TrieUpdates::default()),
|
||||
old_anchor,
|
||||
vec![block1.clone(), block2_ready.clone()],
|
||||
);
|
||||
let block3_ready = DeferredTrieData::ready(block3.wait_cloned());
|
||||
|
||||
// Verify block3's overlay has all 3 accounts with old_anchor
|
||||
let block3_overlay = block3_ready.wait_cloned().anchored_trie_input.unwrap();
|
||||
assert_eq!(block3_overlay.anchor_hash, old_anchor);
|
||||
assert_eq!(block3_overlay.trie_input.state.accounts.len(), 3);
|
||||
|
||||
// Now simulate persist: create block4 with NEW anchor but same ancestors.
|
||||
// To verify correct rebuilding, we must provide ALL unpersisted ancestors.
|
||||
// If we only provided block3, the rebuild would only see block3's state.
|
||||
// We pass block1, block2, block3 to simulate that they are all still in memory
|
||||
// but the anchor check forces a rebuild (e.g. artificial anchor change).
|
||||
let block4_hashed = HashedPostState::default().with_accounts([(
|
||||
key4,
|
||||
Some(Account { nonce: 4, balance: U256::ZERO, bytecode_hash: None }),
|
||||
)]);
|
||||
let block4 = DeferredTrieData::pending(
|
||||
Arc::new(block4_hashed),
|
||||
Arc::new(TrieUpdates::default()),
|
||||
new_anchor, // Different anchor - simulates post-persist
|
||||
vec![block1, block2_ready, block3_ready],
|
||||
);
|
||||
|
||||
let result = block4.wait_cloned();
|
||||
|
||||
// Verify:
|
||||
// 1. New anchor is used in result
|
||||
assert_eq!(result.anchor_hash(), Some(new_anchor));
|
||||
|
||||
// 2. All 4 accounts are in the overlay (rebuilt from ancestors + extended)
|
||||
let overlay = result.anchored_trie_input.as_ref().unwrap();
|
||||
assert_eq!(overlay.trie_input.state.accounts.len(), 4);
|
||||
|
||||
// 3. All accounts have correct values
|
||||
let accounts = &overlay.trie_input.state.accounts;
|
||||
assert!(accounts.iter().any(|(k, a)| *k == key1 && a.unwrap().nonce == 1));
|
||||
assert!(accounts.iter().any(|(k, a)| *k == key2 && a.unwrap().nonce == 2));
|
||||
assert!(accounts.iter().any(|(k, a)| *k == key3 && a.unwrap().nonce == 3));
|
||||
assert!(accounts.iter().any(|(k, a)| *k == key4 && a.unwrap().nonce == 4));
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use crate::{
|
||||
CanonStateNotification, CanonStateNotificationSender, CanonStateNotifications,
|
||||
ChainInfoTracker, ComputedTrieData, DeferredTrieData, MemoryOverlayStateProvider,
|
||||
ChainInfoTracker, MemoryOverlayStateProvider,
|
||||
};
|
||||
use alloy_consensus::{transaction::TransactionMeta, BlockHeader};
|
||||
use alloy_eips::{BlockHashOrNumber, BlockNumHash};
|
||||
@@ -17,8 +17,8 @@ use reth_primitives_traits::{
|
||||
SignedTransaction,
|
||||
};
|
||||
use reth_storage_api::StateProviderBox;
|
||||
use reth_trie::{updates::TrieUpdatesSorted, HashedPostStateSorted, TrieInputSorted};
|
||||
use std::{collections::BTreeMap, ops::Deref, sync::Arc, time::Instant};
|
||||
use reth_trie::{updates::TrieUpdates, HashedPostState};
|
||||
use std::{collections::BTreeMap, sync::Arc, time::Instant};
|
||||
use tokio::sync::{broadcast, watch};
|
||||
|
||||
/// Size of the broadcast channel used to notify canonical state events.
|
||||
@@ -86,20 +86,14 @@ impl<N: NodePrimitives> InMemoryState<N> {
|
||||
///
|
||||
/// This tries to acquire a read lock. Drop any write locks before calling this.
|
||||
pub(crate) fn update_metrics(&self) {
|
||||
let (count, earliest, latest) = {
|
||||
let numbers = self.numbers.read();
|
||||
let count = numbers.len();
|
||||
let earliest = numbers.first_key_value().map(|(number, _)| *number);
|
||||
let latest = numbers.last_key_value().map(|(number, _)| *number);
|
||||
(count, earliest, latest)
|
||||
};
|
||||
if let Some(earliest_block_number) = earliest {
|
||||
self.metrics.earliest_block.set(earliest_block_number as f64);
|
||||
let numbers = self.numbers.read();
|
||||
if let Some((earliest_block_number, _)) = numbers.first_key_value() {
|
||||
self.metrics.earliest_block.set(*earliest_block_number as f64);
|
||||
}
|
||||
if let Some(latest_block_number) = latest {
|
||||
self.metrics.latest_block.set(latest_block_number as f64);
|
||||
if let Some((latest_block_number, _)) = numbers.last_key_value() {
|
||||
self.metrics.latest_block.set(*latest_block_number as f64);
|
||||
}
|
||||
self.metrics.num_blocks.set(count as f64);
|
||||
self.metrics.num_blocks.set(numbers.len() as f64);
|
||||
}
|
||||
|
||||
/// Returns the state for a given block hash.
|
||||
@@ -248,7 +242,7 @@ impl<N: NodePrimitives> CanonicalInMemoryState<N> {
|
||||
/// Updates the pending block with the given block.
|
||||
///
|
||||
/// Note: This assumes that the parent block of the pending block is canonical.
|
||||
pub fn set_pending_block(&self, pending: ExecutedBlock<N>) {
|
||||
pub fn set_pending_block(&self, pending: ExecutedBlockWithTrieUpdates<N>) {
|
||||
// fetch the state of the pending block's parent block
|
||||
let parent = self.state_by_hash(pending.recovered_block().parent_hash());
|
||||
let pending = BlockState::with_parent(pending, parent);
|
||||
@@ -264,7 +258,7 @@ impl<N: NodePrimitives> CanonicalInMemoryState<N> {
|
||||
/// them to their parent blocks.
|
||||
fn update_blocks<I, R>(&self, new_blocks: I, reorged: R)
|
||||
where
|
||||
I: IntoIterator<Item = ExecutedBlock<N>>,
|
||||
I: IntoIterator<Item = ExecutedBlockWithTrieUpdates<N>>,
|
||||
R: IntoIterator<Item = ExecutedBlock<N>>,
|
||||
{
|
||||
{
|
||||
@@ -317,7 +311,6 @@ impl<N: NodePrimitives> CanonicalInMemoryState<N> {
|
||||
/// This will update the links between blocks and remove all blocks that are [..
|
||||
/// `persisted_height`].
|
||||
pub fn remove_persisted_blocks(&self, persisted_num_hash: BlockNumHash) {
|
||||
self.set_persisted(persisted_num_hash);
|
||||
// if the persisted hash is not in the canonical in memory state, do nothing, because it
|
||||
// means canonical blocks were not actually persisted.
|
||||
//
|
||||
@@ -445,11 +438,6 @@ impl<N: NodePrimitives> CanonicalInMemoryState<N> {
|
||||
self.inner.chain_info_tracker.set_finalized(header);
|
||||
}
|
||||
|
||||
/// Persisted block setter.
|
||||
pub fn set_persisted(&self, num_hash: BlockNumHash) {
|
||||
self.inner.chain_info_tracker.set_persisted(num_hash);
|
||||
}
|
||||
|
||||
/// Canonical head getter.
|
||||
pub fn get_canonical_head(&self) -> SealedHeader<N::BlockHeader> {
|
||||
self.inner.chain_info_tracker.get_canonical_head()
|
||||
@@ -465,11 +453,6 @@ impl<N: NodePrimitives> CanonicalInMemoryState<N> {
|
||||
self.inner.chain_info_tracker.get_safe_header()
|
||||
}
|
||||
|
||||
/// Persisted block `BlockNumHash` getter.
|
||||
pub fn get_persisted_num_hash(&self) -> Option<BlockNumHash> {
|
||||
self.inner.chain_info_tracker.get_persisted_num_hash()
|
||||
}
|
||||
|
||||
/// Returns the `SealedHeader` corresponding to the pending state.
|
||||
pub fn pending_sealed_header(&self) -> Option<SealedHeader<N::BlockHeader>> {
|
||||
self.pending_state().map(|h| h.block_ref().recovered_block().clone_sealed_header())
|
||||
@@ -522,11 +505,6 @@ impl<N: NodePrimitives> CanonicalInMemoryState<N> {
|
||||
self.inner.chain_info_tracker.subscribe_finalized_block()
|
||||
}
|
||||
|
||||
/// Subscribe to new persisted block events.
|
||||
pub fn subscribe_persisted_block(&self) -> watch::Receiver<Option<BlockNumHash>> {
|
||||
self.inner.chain_info_tracker.subscribe_persisted_block()
|
||||
}
|
||||
|
||||
/// Attempts to send a new [`CanonStateNotification`] to all active Receiver handles.
|
||||
pub fn notify_canon_state(&self, event: CanonStateNotification<N>) {
|
||||
self.inner.canon_state_notification_sender.send(event).ok();
|
||||
@@ -587,28 +565,25 @@ impl<N: NodePrimitives> CanonicalInMemoryState<N> {
|
||||
|
||||
/// State after applying the given block, this block is part of the canonical chain that partially
|
||||
/// stored in memory and can be traced back to a canonical block on disk.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct BlockState<N: NodePrimitives = EthPrimitives> {
|
||||
/// The executed block that determines the state after this block has been executed.
|
||||
block: ExecutedBlock<N>,
|
||||
block: ExecutedBlockWithTrieUpdates<N>,
|
||||
/// The block's parent block if it exists.
|
||||
parent: Option<Arc<Self>>,
|
||||
}
|
||||
|
||||
impl<N: NodePrimitives> PartialEq for BlockState<N> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.block == other.block && self.parent == other.parent
|
||||
}
|
||||
parent: Option<Arc<BlockState<N>>>,
|
||||
}
|
||||
|
||||
impl<N: NodePrimitives> BlockState<N> {
|
||||
/// [`BlockState`] constructor.
|
||||
pub const fn new(block: ExecutedBlock<N>) -> Self {
|
||||
pub const fn new(block: ExecutedBlockWithTrieUpdates<N>) -> Self {
|
||||
Self { block, parent: None }
|
||||
}
|
||||
|
||||
/// [`BlockState`] constructor with parent.
|
||||
pub const fn with_parent(block: ExecutedBlock<N>, parent: Option<Arc<Self>>) -> Self {
|
||||
pub const fn with_parent(
|
||||
block: ExecutedBlockWithTrieUpdates<N>,
|
||||
parent: Option<Arc<Self>>,
|
||||
) -> Self {
|
||||
Self { block, parent }
|
||||
}
|
||||
|
||||
@@ -622,12 +597,12 @@ impl<N: NodePrimitives> BlockState<N> {
|
||||
}
|
||||
|
||||
/// Returns the executed block that determines the state.
|
||||
pub fn block(&self) -> ExecutedBlock<N> {
|
||||
pub fn block(&self) -> ExecutedBlockWithTrieUpdates<N> {
|
||||
self.block.clone()
|
||||
}
|
||||
|
||||
/// Returns a reference to the executed block that determines the state.
|
||||
pub const fn block_ref(&self) -> &ExecutedBlock<N> {
|
||||
pub const fn block_ref(&self) -> &ExecutedBlockWithTrieUpdates<N> {
|
||||
&self.block
|
||||
}
|
||||
|
||||
@@ -656,8 +631,6 @@ impl<N: NodePrimitives> BlockState<N> {
|
||||
/// We assume that the `Receipts` in the executed block `ExecutionOutcome`
|
||||
/// has only one element corresponding to the executed block associated to
|
||||
/// the state.
|
||||
///
|
||||
/// This clones the vector of receipts. To avoid it, use [`Self::executed_block_receipts_ref`].
|
||||
pub fn executed_block_receipts(&self) -> Vec<N::Receipt> {
|
||||
let receipts = self.receipts();
|
||||
|
||||
@@ -670,30 +643,22 @@ impl<N: NodePrimitives> BlockState<N> {
|
||||
receipts.first().cloned().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns a slice of `Receipt` of executed block that determines the state.
|
||||
/// We assume that the `Receipts` in the executed block `ExecutionOutcome`
|
||||
/// has only one element corresponding to the executed block associated to
|
||||
/// the state.
|
||||
pub fn executed_block_receipts_ref(&self) -> &[N::Receipt] {
|
||||
let receipts = self.receipts();
|
||||
|
||||
debug_assert!(
|
||||
receipts.len() <= 1,
|
||||
"Expected at most one block's worth of receipts, found {}",
|
||||
receipts.len()
|
||||
);
|
||||
|
||||
receipts.first().map(|receipts| receipts.deref()).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns an iterator over __parent__ `BlockStates`.
|
||||
/// Returns a vector of __parent__ `BlockStates`.
|
||||
///
|
||||
/// The block state order is newest to oldest (highest to lowest):
|
||||
/// The block state order in the output vector is newest to oldest (highest to lowest):
|
||||
/// `[5,4,3,2,1]`
|
||||
///
|
||||
/// Note: This does not include self.
|
||||
pub fn parent_state_chain(&self) -> impl Iterator<Item = &Self> + '_ {
|
||||
std::iter::successors(self.parent.as_deref(), |state| state.parent.as_deref())
|
||||
pub fn parent_state_chain(&self) -> Vec<&Self> {
|
||||
let mut parents = Vec::new();
|
||||
let mut current = self.parent.as_deref();
|
||||
|
||||
while let Some(parent) = current {
|
||||
parents.push(parent);
|
||||
current = parent.parent.as_deref();
|
||||
}
|
||||
|
||||
parents
|
||||
}
|
||||
|
||||
/// Returns a vector of `BlockStates` representing the entire in memory chain.
|
||||
@@ -704,11 +669,6 @@ impl<N: NodePrimitives> BlockState<N> {
|
||||
}
|
||||
|
||||
/// Appends the parent chain of this [`BlockState`] to the given vector.
|
||||
///
|
||||
/// Parents are appended in order from newest to oldest (highest to lowest).
|
||||
/// This does not include self, only the parent states.
|
||||
///
|
||||
/// This is a convenience method equivalent to `chain.extend(self.parent_state_chain())`.
|
||||
pub fn append_parent_chain<'a>(&'a self, chain: &mut Vec<&'a Self>) {
|
||||
chain.extend(self.parent_state_chain());
|
||||
}
|
||||
@@ -762,17 +722,14 @@ impl<N: NodePrimitives> BlockState<N> {
|
||||
}
|
||||
|
||||
/// Represents an executed block stored in-memory.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ExecutedBlock<N: NodePrimitives = EthPrimitives> {
|
||||
/// Recovered Block
|
||||
pub recovered_block: Arc<RecoveredBlock<N::Block>>,
|
||||
/// Block's execution outcome.
|
||||
pub execution_output: Arc<ExecutionOutcome<N::Receipt>>,
|
||||
/// Deferred trie data produced by execution.
|
||||
///
|
||||
/// This allows deferring the computation of the trie data which can be expensive.
|
||||
/// The data can be populated asynchronously after the block was validated.
|
||||
pub trie_data: DeferredTrieData,
|
||||
/// Block's hashed state.
|
||||
pub hashed_state: Arc<HashedPostState>,
|
||||
}
|
||||
|
||||
impl<N: NodePrimitives> Default for ExecutedBlock<N> {
|
||||
@@ -780,54 +737,12 @@ impl<N: NodePrimitives> Default for ExecutedBlock<N> {
|
||||
Self {
|
||||
recovered_block: Default::default(),
|
||||
execution_output: Default::default(),
|
||||
trie_data: DeferredTrieData::ready(ComputedTrieData::default()),
|
||||
hashed_state: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: NodePrimitives> PartialEq for ExecutedBlock<N> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// Trie data is computed asynchronously and doesn't define block identity.
|
||||
self.recovered_block == other.recovered_block &&
|
||||
self.execution_output == other.execution_output
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: NodePrimitives> ExecutedBlock<N> {
|
||||
/// Create a new [`ExecutedBlock`] with already-computed trie data.
|
||||
///
|
||||
/// Use this constructor when trie data is available immediately (e.g., sequencers,
|
||||
/// payload builders). This is the safe default path.
|
||||
pub fn new(
|
||||
recovered_block: Arc<RecoveredBlock<N::Block>>,
|
||||
execution_output: Arc<ExecutionOutcome<N::Receipt>>,
|
||||
trie_data: ComputedTrieData,
|
||||
) -> Self {
|
||||
Self { recovered_block, execution_output, trie_data: DeferredTrieData::ready(trie_data) }
|
||||
}
|
||||
|
||||
/// Create a new [`ExecutedBlock`] with deferred trie data.
|
||||
///
|
||||
/// This is useful if the trie data is populated somewhere else, e.g. asynchronously
|
||||
/// after the block was validated.
|
||||
///
|
||||
/// The [`DeferredTrieData`] handle allows expensive trie operations (sorting hashed state,
|
||||
/// sorting trie updates, and building the accumulated trie input overlay) to be performed
|
||||
/// outside the critical validation path. This can improve latency for time-sensitive
|
||||
/// operations like block validation.
|
||||
///
|
||||
/// If the data hasn't been populated when [`Self::trie_data()`] is called, computation
|
||||
/// occurs synchronously from stored inputs, so there is no blocking or deadlock risk.
|
||||
///
|
||||
/// Use [`Self::new()`] instead when trie data is already computed and available immediately.
|
||||
pub const fn with_deferred_trie_data(
|
||||
recovered_block: Arc<RecoveredBlock<N::Block>>,
|
||||
execution_output: Arc<ExecutionOutcome<N::Receipt>>,
|
||||
trie_data: DeferredTrieData,
|
||||
) -> Self {
|
||||
Self { recovered_block, execution_output, trie_data }
|
||||
}
|
||||
|
||||
/// Returns a reference to an inner [`SealedBlock`]
|
||||
#[inline]
|
||||
pub fn sealed_block(&self) -> &SealedBlock<N::Block> {
|
||||
@@ -846,55 +761,10 @@ impl<N: NodePrimitives> ExecutedBlock<N> {
|
||||
&self.execution_output
|
||||
}
|
||||
|
||||
/// Returns the trie data, computing it synchronously if not already cached.
|
||||
///
|
||||
/// Uses `OnceLock::get_or_init` internally:
|
||||
/// - If already computed: returns cached result immediately
|
||||
/// - If not computed: first caller computes, others wait for that result
|
||||
/// Returns a reference to the hashed state result of the execution outcome
|
||||
#[inline]
|
||||
#[tracing::instrument(level = "debug", target = "engine::tree", name = "trie_data", skip_all)]
|
||||
pub fn trie_data(&self) -> ComputedTrieData {
|
||||
self.trie_data.wait_cloned()
|
||||
}
|
||||
|
||||
/// Returns a clone of the deferred trie data handle.
|
||||
///
|
||||
/// A handle is a lightweight reference that can be passed to descendants without
|
||||
/// forcing trie data to be computed immediately. The actual work runs when
|
||||
/// `wait_cloned()` is called by a consumer (e.g. when merging overlays).
|
||||
#[inline]
|
||||
pub fn trie_data_handle(&self) -> DeferredTrieData {
|
||||
self.trie_data.clone()
|
||||
}
|
||||
|
||||
/// Returns the hashed state result of the execution outcome.
|
||||
///
|
||||
/// May compute trie data synchronously if the deferred task hasn't completed.
|
||||
#[inline]
|
||||
pub fn hashed_state(&self) -> Arc<HashedPostStateSorted> {
|
||||
self.trie_data().hashed_state
|
||||
}
|
||||
|
||||
/// Returns the trie updates resulting from the execution outcome.
|
||||
///
|
||||
/// May compute trie data synchronously if the deferred task hasn't completed.
|
||||
#[inline]
|
||||
pub fn trie_updates(&self) -> Arc<TrieUpdatesSorted> {
|
||||
self.trie_data().trie_updates
|
||||
}
|
||||
|
||||
/// Returns the trie input anchored to the persisted ancestor.
|
||||
///
|
||||
/// May compute trie data synchronously if the deferred task hasn't completed.
|
||||
#[inline]
|
||||
pub fn trie_input(&self) -> Option<Arc<TrieInputSorted>> {
|
||||
self.trie_data().trie_input().cloned()
|
||||
}
|
||||
|
||||
/// Returns the anchor hash of the trie input, if present.
|
||||
#[inline]
|
||||
pub fn anchor_hash(&self) -> Option<B256> {
|
||||
self.trie_data().anchor_hash()
|
||||
pub fn hashed_state(&self) -> &HashedPostState {
|
||||
&self.hashed_state
|
||||
}
|
||||
|
||||
/// Returns a [`BlockNumber`] of the block.
|
||||
@@ -904,20 +774,127 @@ impl<N: NodePrimitives> ExecutedBlock<N> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Trie updates that result from calculating the state root for the block.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ExecutedTrieUpdates {
|
||||
/// Trie updates present. State root was calculated, and the trie updates can be applied to the
|
||||
/// database.
|
||||
Present(Arc<TrieUpdates>),
|
||||
/// Trie updates missing. State root was calculated, but the trie updates cannot be applied to
|
||||
/// the current database state. To apply the updates, the state root must be recalculated, and
|
||||
/// new trie updates must be generated.
|
||||
///
|
||||
/// This can happen when processing fork chain blocks that are building on top of the
|
||||
/// historical database state. Since we don't store the historical trie state, we cannot
|
||||
/// generate the trie updates for it.
|
||||
Missing,
|
||||
}
|
||||
|
||||
impl ExecutedTrieUpdates {
|
||||
/// Creates a [`ExecutedTrieUpdates`] with present but empty trie updates.
|
||||
pub fn empty() -> Self {
|
||||
Self::Present(Arc::default())
|
||||
}
|
||||
|
||||
/// Sets the trie updates to the provided value as present.
|
||||
pub fn set_present(&mut self, updates: Arc<TrieUpdates>) {
|
||||
*self = Self::Present(updates);
|
||||
}
|
||||
|
||||
/// Takes the present trie updates, leaving the state as missing.
|
||||
pub fn take_present(&mut self) -> Option<Arc<TrieUpdates>> {
|
||||
match self {
|
||||
Self::Present(updates) => {
|
||||
let updates = core::mem::take(updates);
|
||||
*self = Self::Missing;
|
||||
Some(updates)
|
||||
}
|
||||
Self::Missing => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the trie updates if present.
|
||||
#[allow(clippy::missing_const_for_fn)] // false positive
|
||||
pub fn as_ref(&self) -> Option<&TrieUpdates> {
|
||||
match self {
|
||||
Self::Present(updates) => Some(updates),
|
||||
Self::Missing => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the trie updates are present.
|
||||
pub const fn is_present(&self) -> bool {
|
||||
matches!(self, Self::Present(_))
|
||||
}
|
||||
|
||||
/// Returns `true` if the trie updates are missing.
|
||||
pub const fn is_missing(&self) -> bool {
|
||||
matches!(self, Self::Missing)
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`ExecutedBlock`] with its [`TrieUpdates`].
|
||||
///
|
||||
/// We store it as separate type because [`TrieUpdates`] are only available for blocks stored in
|
||||
/// memory and can't be obtained for canonical persisted blocks.
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, derive_more::Deref, derive_more::DerefMut, derive_more::Into,
|
||||
)]
|
||||
pub struct ExecutedBlockWithTrieUpdates<N: NodePrimitives = EthPrimitives> {
|
||||
/// Inner [`ExecutedBlock`].
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
#[into]
|
||||
pub block: ExecutedBlock<N>,
|
||||
/// Trie updates that result from calculating the state root for the block.
|
||||
///
|
||||
/// If [`ExecutedTrieUpdates::Missing`], the trie updates should be computed when persisting
|
||||
/// the block **on top of the canonical parent**.
|
||||
pub trie: ExecutedTrieUpdates,
|
||||
}
|
||||
|
||||
impl<N: NodePrimitives> ExecutedBlockWithTrieUpdates<N> {
|
||||
/// [`ExecutedBlock`] constructor.
|
||||
pub const fn new(
|
||||
recovered_block: Arc<RecoveredBlock<N::Block>>,
|
||||
execution_output: Arc<ExecutionOutcome<N::Receipt>>,
|
||||
hashed_state: Arc<HashedPostState>,
|
||||
trie: ExecutedTrieUpdates,
|
||||
) -> Self {
|
||||
Self { block: ExecutedBlock { recovered_block, execution_output, hashed_state }, trie }
|
||||
}
|
||||
|
||||
/// Returns a reference to the trie updates for the block, if present.
|
||||
#[inline]
|
||||
pub fn trie_updates(&self) -> Option<&TrieUpdates> {
|
||||
self.trie.as_ref()
|
||||
}
|
||||
|
||||
/// Converts the value into [`SealedBlock`].
|
||||
pub fn into_sealed_block(self) -> SealedBlock<N::Block> {
|
||||
let block = Arc::unwrap_or_clone(self.block.recovered_block);
|
||||
block.into_sealed_block()
|
||||
}
|
||||
}
|
||||
|
||||
/// Non-empty chain of blocks.
|
||||
#[derive(Debug)]
|
||||
pub enum NewCanonicalChain<N: NodePrimitives = EthPrimitives> {
|
||||
/// A simple append to the current canonical head
|
||||
Commit {
|
||||
/// all blocks that lead back to the canonical head
|
||||
new: Vec<ExecutedBlock<N>>,
|
||||
new: Vec<ExecutedBlockWithTrieUpdates<N>>,
|
||||
},
|
||||
/// A reorged chain consists of two chains that trace back to a shared ancestor block at which
|
||||
/// point they diverge.
|
||||
Reorg {
|
||||
/// All blocks of the _new_ chain
|
||||
new: Vec<ExecutedBlock<N>>,
|
||||
new: Vec<ExecutedBlockWithTrieUpdates<N>>,
|
||||
/// All blocks of the _old_ chain
|
||||
///
|
||||
/// These are not [`ExecutedBlockWithTrieUpdates`] because we don't always have the trie
|
||||
/// updates for the old canonical chain. For example, in case of node being restarted right
|
||||
/// before the reorg [`TrieUpdates`] can't be fetched from database.
|
||||
old: Vec<ExecutedBlock<N>>,
|
||||
},
|
||||
}
|
||||
@@ -946,8 +923,6 @@ impl<N: NodePrimitives<SignedTx: SignedTransaction>> NewCanonicalChain<N> {
|
||||
chain.append_block(
|
||||
exec.recovered_block().clone(),
|
||||
exec.execution_outcome().clone(),
|
||||
exec.trie_updates(),
|
||||
exec.hashed_state(),
|
||||
);
|
||||
chain
|
||||
}));
|
||||
@@ -958,8 +933,6 @@ impl<N: NodePrimitives<SignedTx: SignedTransaction>> NewCanonicalChain<N> {
|
||||
chain.append_block(
|
||||
exec.recovered_block().clone(),
|
||||
exec.execution_outcome().clone(),
|
||||
exec.trie_updates(),
|
||||
exec.hashed_state(),
|
||||
);
|
||||
chain
|
||||
}));
|
||||
@@ -967,8 +940,6 @@ impl<N: NodePrimitives<SignedTx: SignedTransaction>> NewCanonicalChain<N> {
|
||||
chain.append_block(
|
||||
exec.recovered_block().clone(),
|
||||
exec.execution_outcome().clone(),
|
||||
exec.trie_updates(),
|
||||
exec.hashed_state(),
|
||||
);
|
||||
chain
|
||||
}));
|
||||
@@ -1005,8 +976,8 @@ mod tests {
|
||||
StateProofProvider, StateProvider, StateRootProvider, StorageRootProvider,
|
||||
};
|
||||
use reth_trie::{
|
||||
updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof,
|
||||
MultiProofTargets, StorageMultiProof, StorageProof, TrieInput,
|
||||
AccountProof, HashedStorage, MultiProof, MultiProofTargets, StorageMultiProof,
|
||||
StorageProof, TrieInput,
|
||||
};
|
||||
|
||||
fn create_mock_state(
|
||||
@@ -1286,7 +1257,7 @@ mod tests {
|
||||
block1.recovered_block().hash()
|
||||
);
|
||||
|
||||
let chain = NewCanonicalChain::Reorg { new: vec![block2.clone()], old: vec![block1] };
|
||||
let chain = NewCanonicalChain::Reorg { new: vec![block2.clone()], old: vec![block1.block] };
|
||||
state.update_chain(chain);
|
||||
assert_eq!(
|
||||
state.head_state().unwrap().block_ref().recovered_block().hash(),
|
||||
@@ -1409,7 +1380,8 @@ mod tests {
|
||||
#[test]
|
||||
fn test_canonical_in_memory_state_canonical_chain_empty() {
|
||||
let state: CanonicalInMemoryState = CanonicalInMemoryState::empty();
|
||||
assert!(state.canonical_chain().next().is_none());
|
||||
let chain: Vec<_> = state.canonical_chain().collect();
|
||||
assert!(chain.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1478,18 +1450,19 @@ mod tests {
|
||||
let mut test_block_builder: TestBlockBuilder = TestBlockBuilder::default();
|
||||
let chain = create_mock_state_chain(&mut test_block_builder, 4);
|
||||
|
||||
let parents: Vec<_> = chain[3].parent_state_chain().collect();
|
||||
let parents = chain[3].parent_state_chain();
|
||||
assert_eq!(parents.len(), 3);
|
||||
assert_eq!(parents[0].block().recovered_block().number, 3);
|
||||
assert_eq!(parents[1].block().recovered_block().number, 2);
|
||||
assert_eq!(parents[2].block().recovered_block().number, 1);
|
||||
|
||||
let parents: Vec<_> = chain[2].parent_state_chain().collect();
|
||||
let parents = chain[2].parent_state_chain();
|
||||
assert_eq!(parents.len(), 2);
|
||||
assert_eq!(parents[0].block().recovered_block().number, 2);
|
||||
assert_eq!(parents[1].block().recovered_block().number, 1);
|
||||
|
||||
assert_eq!(chain[0].parent_state_chain().count(), 0);
|
||||
let parents = chain[0].parent_state_chain();
|
||||
assert_eq!(parents.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1500,7 +1473,8 @@ mod tests {
|
||||
create_mock_state(&mut test_block_builder, single_block_number, B256::random());
|
||||
let single_block_hash = single_block.block().recovered_block().hash();
|
||||
|
||||
assert_eq!(single_block.parent_state_chain().count(), 0);
|
||||
let parents = single_block.parent_state_chain();
|
||||
assert_eq!(parents.len(), 0);
|
||||
|
||||
let block_state_chain = single_block.chain().collect::<Vec<_>>();
|
||||
assert_eq!(block_state_chain.len(), 1);
|
||||
@@ -1552,24 +1526,13 @@ mod tests {
|
||||
// Test commit notification
|
||||
let chain_commit = NewCanonicalChain::Commit { new: vec![block0.clone(), block1.clone()] };
|
||||
|
||||
// Build expected trie updates map
|
||||
let mut expected_trie_updates = BTreeMap::new();
|
||||
expected_trie_updates.insert(0, block0.trie_updates());
|
||||
expected_trie_updates.insert(1, block1.trie_updates());
|
||||
|
||||
// Build expected hashed state map
|
||||
let mut expected_hashed_state = BTreeMap::new();
|
||||
expected_hashed_state.insert(0, block0.hashed_state());
|
||||
expected_hashed_state.insert(1, block1.hashed_state());
|
||||
|
||||
assert_eq!(
|
||||
chain_commit.to_chain_notification(),
|
||||
CanonStateNotification::Commit {
|
||||
new: Arc::new(Chain::new(
|
||||
vec![block0.recovered_block().clone(), block1.recovered_block().clone()],
|
||||
sample_execution_outcome.clone(),
|
||||
expected_trie_updates,
|
||||
expected_hashed_state
|
||||
None
|
||||
))
|
||||
}
|
||||
);
|
||||
@@ -1577,43 +1540,21 @@ mod tests {
|
||||
// Test reorg notification
|
||||
let chain_reorg = NewCanonicalChain::Reorg {
|
||||
new: vec![block1a.clone(), block2a.clone()],
|
||||
old: vec![block1.clone(), block2.clone()],
|
||||
old: vec![block1.block.clone(), block2.block.clone()],
|
||||
};
|
||||
|
||||
// Build expected trie updates for old chain
|
||||
let mut old_trie_updates = BTreeMap::new();
|
||||
old_trie_updates.insert(1, block1.trie_updates());
|
||||
old_trie_updates.insert(2, block2.trie_updates());
|
||||
|
||||
// Build expected trie updates for new chain
|
||||
let mut new_trie_updates = BTreeMap::new();
|
||||
new_trie_updates.insert(1, block1a.trie_updates());
|
||||
new_trie_updates.insert(2, block2a.trie_updates());
|
||||
|
||||
// Build expected hashed state for old chain
|
||||
let mut old_hashed_state = BTreeMap::new();
|
||||
old_hashed_state.insert(1, block1.hashed_state());
|
||||
old_hashed_state.insert(2, block2.hashed_state());
|
||||
|
||||
// Build expected hashed state for new chain
|
||||
let mut new_hashed_state = BTreeMap::new();
|
||||
new_hashed_state.insert(1, block1a.hashed_state());
|
||||
new_hashed_state.insert(2, block2a.hashed_state());
|
||||
|
||||
assert_eq!(
|
||||
chain_reorg.to_chain_notification(),
|
||||
CanonStateNotification::Reorg {
|
||||
old: Arc::new(Chain::new(
|
||||
vec![block1.recovered_block().clone(), block2.recovered_block().clone()],
|
||||
sample_execution_outcome.clone(),
|
||||
old_trie_updates,
|
||||
old_hashed_state
|
||||
None
|
||||
)),
|
||||
new: Arc::new(Chain::new(
|
||||
vec![block1a.recovered_block().clone(), block2a.recovered_block().clone()],
|
||||
sample_execution_outcome,
|
||||
new_trie_updates,
|
||||
new_hashed_state
|
||||
None
|
||||
))
|
||||
}
|
||||
);
|
||||
|
||||
@@ -6,14 +6,11 @@
|
||||
issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
|
||||
)]
|
||||
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
|
||||
|
||||
mod in_memory;
|
||||
pub use in_memory::*;
|
||||
|
||||
mod deferred_trie;
|
||||
pub use deferred_trie::*;
|
||||
|
||||
mod noop;
|
||||
|
||||
mod chain_info;
|
||||
@@ -23,8 +20,7 @@ mod notifications;
|
||||
pub use notifications::{
|
||||
CanonStateNotification, CanonStateNotificationSender, CanonStateNotificationStream,
|
||||
CanonStateNotifications, CanonStateSubscriptions, ForkChoiceNotifications, ForkChoiceStream,
|
||||
ForkChoiceSubscriptions, PersistedBlockNotifications, PersistedBlockSubscriptions,
|
||||
WatchValueStream,
|
||||
ForkChoiceSubscriptions,
|
||||
};
|
||||
|
||||
mod memory_overlay;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
use super::ExecutedBlock;
|
||||
use super::ExecutedBlockWithTrieUpdates;
|
||||
use alloy_consensus::BlockHeader;
|
||||
use alloy_primitives::{keccak256, Address, BlockNumber, Bytes, StorageKey, StorageValue, B256};
|
||||
use reth_errors::ProviderResult;
|
||||
use reth_primitives_traits::{Account, Bytecode, NodePrimitives};
|
||||
use reth_storage_api::{
|
||||
AccountReader, BlockHashReader, BytecodeReader, HashedPostStateProvider, StateProofProvider,
|
||||
StateProvider, StateProviderBox, StateRootProvider, StorageRootProvider,
|
||||
StateProvider, StateRootProvider, StorageRootProvider,
|
||||
};
|
||||
use reth_trie::{
|
||||
updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof,
|
||||
MultiProofTargets, StorageMultiProof, TrieInput,
|
||||
};
|
||||
use revm_database::BundleState;
|
||||
use std::{borrow::Cow, sync::OnceLock};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
/// A state provider that stores references to in-memory blocks along with their state as well as a
|
||||
/// reference of the historical state provider for fallback lookups.
|
||||
@@ -24,11 +24,15 @@ pub struct MemoryOverlayStateProviderRef<
|
||||
/// Historical state provider for state lookups that are not found in memory blocks.
|
||||
pub(crate) historical: Box<dyn StateProvider + 'a>,
|
||||
/// The collection of executed parent blocks. Expected order is newest to oldest.
|
||||
pub(crate) in_memory: Cow<'a, [ExecutedBlock<N>]>,
|
||||
pub(crate) in_memory: Vec<ExecutedBlockWithTrieUpdates<N>>,
|
||||
/// Lazy-loaded in-memory trie data.
|
||||
pub(crate) trie_input: OnceLock<TrieInput>,
|
||||
}
|
||||
|
||||
/// A state provider that stores references to in-memory blocks along with their state as well as
|
||||
/// the historical state provider for fallback lookups.
|
||||
pub type MemoryOverlayStateProvider<N> = MemoryOverlayStateProviderRef<'static, N>;
|
||||
|
||||
impl<'a, N: NodePrimitives> MemoryOverlayStateProviderRef<'a, N> {
|
||||
/// Create new memory overlay state provider.
|
||||
///
|
||||
@@ -37,8 +41,11 @@ impl<'a, N: NodePrimitives> MemoryOverlayStateProviderRef<'a, N> {
|
||||
/// - `in_memory` - the collection of executed ancestor blocks in reverse.
|
||||
/// - `historical` - a historical state provider for the latest ancestor block stored in the
|
||||
/// database.
|
||||
pub fn new(historical: Box<dyn StateProvider + 'a>, in_memory: Vec<ExecutedBlock<N>>) -> Self {
|
||||
Self { historical, in_memory: Cow::Owned(in_memory), trie_input: OnceLock::new() }
|
||||
pub fn new(
|
||||
historical: Box<dyn StateProvider + 'a>,
|
||||
in_memory: Vec<ExecutedBlockWithTrieUpdates<N>>,
|
||||
) -> Self {
|
||||
Self { historical, in_memory, trie_input: OnceLock::new() }
|
||||
}
|
||||
|
||||
/// Turn this state provider into a state provider
|
||||
@@ -49,28 +56,19 @@ impl<'a, N: NodePrimitives> MemoryOverlayStateProviderRef<'a, N> {
|
||||
/// Return lazy-loaded trie state aggregated from in-memory blocks.
|
||||
fn trie_input(&self) -> &TrieInput {
|
||||
self.trie_input.get_or_init(|| {
|
||||
let mut input = TrieInput::default();
|
||||
// Iterate from oldest to newest
|
||||
for block in self.in_memory.iter().rev() {
|
||||
let data = block.trie_data();
|
||||
input.nodes.extend_from_sorted(&data.trie_updates);
|
||||
input.state.extend_from_sorted(&data.hashed_state);
|
||||
}
|
||||
input
|
||||
TrieInput::from_blocks(
|
||||
self.in_memory
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|block| (block.hashed_state.as_ref(), block.trie.as_ref())),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn merged_hashed_storage(&self, address: Address, storage: HashedStorage) -> HashedStorage {
|
||||
let state = &self.trie_input().state;
|
||||
let mut hashed = state.storages.get(&keccak256(address)).cloned().unwrap_or_default();
|
||||
hashed.extend(&storage);
|
||||
hashed
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: NodePrimitives> BlockHashReader for MemoryOverlayStateProviderRef<'_, N> {
|
||||
fn block_hash(&self, number: BlockNumber) -> ProviderResult<Option<B256>> {
|
||||
for block in self.in_memory.iter() {
|
||||
for block in &self.in_memory {
|
||||
if block.recovered_block().number() == number {
|
||||
return Ok(Some(block.recovered_block().hash()));
|
||||
}
|
||||
@@ -89,7 +87,7 @@ impl<N: NodePrimitives> BlockHashReader for MemoryOverlayStateProviderRef<'_, N>
|
||||
let mut in_memory_hashes = Vec::with_capacity(range.size_hint().0);
|
||||
|
||||
// iterate in ascending order (oldest to newest = low to high)
|
||||
for block in self.in_memory.iter() {
|
||||
for block in &self.in_memory {
|
||||
let block_num = block.recovered_block().number();
|
||||
if range.contains(&block_num) {
|
||||
in_memory_hashes.push(block.recovered_block().hash());
|
||||
@@ -111,7 +109,7 @@ impl<N: NodePrimitives> BlockHashReader for MemoryOverlayStateProviderRef<'_, N>
|
||||
|
||||
impl<N: NodePrimitives> AccountReader for MemoryOverlayStateProviderRef<'_, N> {
|
||||
fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>> {
|
||||
for block in self.in_memory.iter() {
|
||||
for block in &self.in_memory {
|
||||
if let Some(account) = block.execution_output.account(address) {
|
||||
return Ok(account);
|
||||
}
|
||||
@@ -150,8 +148,11 @@ impl<N: NodePrimitives> StateRootProvider for MemoryOverlayStateProviderRef<'_,
|
||||
impl<N: NodePrimitives> StorageRootProvider for MemoryOverlayStateProviderRef<'_, N> {
|
||||
// TODO: Currently this does not reuse available in-memory trie nodes.
|
||||
fn storage_root(&self, address: Address, storage: HashedStorage) -> ProviderResult<B256> {
|
||||
let merged = self.merged_hashed_storage(address, storage);
|
||||
self.historical.storage_root(address, merged)
|
||||
let state = &self.trie_input().state;
|
||||
let mut hashed_storage =
|
||||
state.storages.get(&keccak256(address)).cloned().unwrap_or_default();
|
||||
hashed_storage.extend(&storage);
|
||||
self.historical.storage_root(address, hashed_storage)
|
||||
}
|
||||
|
||||
// TODO: Currently this does not reuse available in-memory trie nodes.
|
||||
@@ -161,8 +162,11 @@ impl<N: NodePrimitives> StorageRootProvider for MemoryOverlayStateProviderRef<'_
|
||||
slot: B256,
|
||||
storage: HashedStorage,
|
||||
) -> ProviderResult<reth_trie::StorageProof> {
|
||||
let merged = self.merged_hashed_storage(address, storage);
|
||||
self.historical.storage_proof(address, slot, merged)
|
||||
let state = &self.trie_input().state;
|
||||
let mut hashed_storage =
|
||||
state.storages.get(&keccak256(address)).cloned().unwrap_or_default();
|
||||
hashed_storage.extend(&storage);
|
||||
self.historical.storage_proof(address, slot, hashed_storage)
|
||||
}
|
||||
|
||||
// TODO: Currently this does not reuse available in-memory trie nodes.
|
||||
@@ -172,8 +176,11 @@ impl<N: NodePrimitives> StorageRootProvider for MemoryOverlayStateProviderRef<'_
|
||||
slots: &[B256],
|
||||
storage: HashedStorage,
|
||||
) -> ProviderResult<StorageMultiProof> {
|
||||
let merged = self.merged_hashed_storage(address, storage);
|
||||
self.historical.storage_multiproof(address, slots, merged)
|
||||
let state = &self.trie_input().state;
|
||||
let mut hashed_storage =
|
||||
state.storages.get(&keccak256(address)).cloned().unwrap_or_default();
|
||||
hashed_storage.extend(&storage);
|
||||
self.historical.storage_multiproof(address, slots, hashed_storage)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +222,7 @@ impl<N: NodePrimitives> StateProvider for MemoryOverlayStateProviderRef<'_, N> {
|
||||
address: Address,
|
||||
storage_key: StorageKey,
|
||||
) -> ProviderResult<Option<StorageValue>> {
|
||||
for block in self.in_memory.iter() {
|
||||
for block in &self.in_memory {
|
||||
if let Some(value) = block.execution_output.storage(&address, storage_key.into()) {
|
||||
return Ok(Some(value));
|
||||
}
|
||||
@@ -227,7 +234,7 @@ impl<N: NodePrimitives> StateProvider for MemoryOverlayStateProviderRef<'_, N> {
|
||||
|
||||
impl<N: NodePrimitives> BytecodeReader for MemoryOverlayStateProviderRef<'_, N> {
|
||||
fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>> {
|
||||
for block in self.in_memory.iter() {
|
||||
for block in &self.in_memory {
|
||||
if let Some(contract) = block.execution_output.bytecode(code_hash) {
|
||||
return Ok(Some(contract));
|
||||
}
|
||||
@@ -236,46 +243,3 @@ impl<N: NodePrimitives> BytecodeReader for MemoryOverlayStateProviderRef<'_, N>
|
||||
self.historical.bytecode_by_hash(code_hash)
|
||||
}
|
||||
}
|
||||
|
||||
/// An owned state provider that stores references to in-memory blocks along with their state as
|
||||
/// well as a reference of the historical state provider for fallback lookups.
|
||||
#[expect(missing_debug_implementations)]
|
||||
pub struct MemoryOverlayStateProvider<N: NodePrimitives = reth_ethereum_primitives::EthPrimitives> {
|
||||
/// Historical state provider for state lookups that are not found in memory blocks.
|
||||
pub(crate) historical: StateProviderBox,
|
||||
/// The collection of executed parent blocks. Expected order is newest to oldest.
|
||||
pub(crate) in_memory: Vec<ExecutedBlock<N>>,
|
||||
/// Lazy-loaded in-memory trie data.
|
||||
pub(crate) trie_input: OnceLock<TrieInput>,
|
||||
}
|
||||
|
||||
impl<N: NodePrimitives> MemoryOverlayStateProvider<N> {
|
||||
/// Create new memory overlay state provider.
|
||||
///
|
||||
/// ## Arguments
|
||||
///
|
||||
/// - `in_memory` - the collection of executed ancestor blocks in reverse.
|
||||
/// - `historical` - a historical state provider for the latest ancestor block stored in the
|
||||
/// database.
|
||||
pub fn new(historical: StateProviderBox, in_memory: Vec<ExecutedBlock<N>>) -> Self {
|
||||
Self { historical, in_memory, trie_input: OnceLock::new() }
|
||||
}
|
||||
|
||||
/// Returns a new provider that takes the `TX` as reference
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> MemoryOverlayStateProviderRef<'_, N> {
|
||||
MemoryOverlayStateProviderRef {
|
||||
historical: Box::new(self.historical.as_ref()),
|
||||
in_memory: Cow::Borrowed(&self.in_memory),
|
||||
trie_input: self.trie_input.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps the [`Self`] in a `Box`.
|
||||
pub fn boxed(self) -> StateProviderBox {
|
||||
Box::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
// Delegates all provider impls to [`MemoryOverlayStateProviderRef`]
|
||||
reth_storage_api::macros::delegate_provider_impls!(MemoryOverlayStateProvider<N> where [N: NodePrimitives]);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use crate::{
|
||||
CanonStateNotifications, CanonStateSubscriptions, ForkChoiceNotifications,
|
||||
ForkChoiceSubscriptions, PersistedBlockNotifications, PersistedBlockSubscriptions,
|
||||
ForkChoiceSubscriptions,
|
||||
};
|
||||
use reth_primitives_traits::NodePrimitives;
|
||||
use reth_storage_api::noop::NoopProvider;
|
||||
@@ -27,10 +27,3 @@ impl<C: Send + Sync, N: NodePrimitives> ForkChoiceSubscriptions for NoopProvider
|
||||
ForkChoiceNotifications(rx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Send + Sync, N: NodePrimitives> PersistedBlockSubscriptions for NoopProvider<C, N> {
|
||||
fn subscribe_persisted_block(&self) -> PersistedBlockNotifications {
|
||||
let (_, rx) = watch::channel(None);
|
||||
PersistedBlockNotifications(rx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Canonical chain state notification trait and types.
|
||||
|
||||
use alloy_eips::{eip2718::Encodable2718, BlockNumHash};
|
||||
use alloy_eips::eip2718::Encodable2718;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use reth_execution_types::{BlockReceipts, Chain};
|
||||
use reth_primitives_traits::{NodePrimitives, RecoveredBlock, SealedHeader};
|
||||
@@ -205,22 +205,22 @@ pub trait ForkChoiceSubscriptions: Send + Sync {
|
||||
}
|
||||
}
|
||||
|
||||
/// A stream that yields values from a `watch::Receiver<Option<T>>`, filtering out `None` values.
|
||||
/// A stream for fork choice watch channels (pending, safe or finalized watchers)
|
||||
#[derive(Debug)]
|
||||
#[pin_project::pin_project]
|
||||
pub struct WatchValueStream<T> {
|
||||
pub struct ForkChoiceStream<T> {
|
||||
#[pin]
|
||||
st: WatchStream<Option<T>>,
|
||||
}
|
||||
|
||||
impl<T: Clone + Sync + Send + 'static> WatchValueStream<T> {
|
||||
/// Creates a new [`WatchValueStream`]
|
||||
impl<T: Clone + Sync + Send + 'static> ForkChoiceStream<T> {
|
||||
/// Creates a new `ForkChoiceStream`
|
||||
pub fn new(rx: watch::Receiver<Option<T>>) -> Self {
|
||||
Self { st: WatchStream::from_changes(rx) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Sync + Send + 'static> Stream for WatchValueStream<T> {
|
||||
impl<T: Clone + Sync + Send + 'static> Stream for ForkChoiceStream<T> {
|
||||
type Item = T;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
@@ -234,24 +234,6 @@ impl<T: Clone + Sync + Send + 'static> Stream for WatchValueStream<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Alias for [`WatchValueStream`] for fork choice watch channels.
|
||||
pub type ForkChoiceStream<T> = WatchValueStream<T>;
|
||||
|
||||
/// Wrapper around a watch receiver that receives persisted block notifications.
|
||||
#[derive(Debug, Deref, DerefMut)]
|
||||
pub struct PersistedBlockNotifications(pub watch::Receiver<Option<BlockNumHash>>);
|
||||
|
||||
/// A trait that allows subscribing to persisted block events.
|
||||
pub trait PersistedBlockSubscriptions: Send + Sync {
|
||||
/// Get notified when a new block is persisted to disk.
|
||||
fn subscribe_persisted_block(&self) -> PersistedBlockNotifications;
|
||||
|
||||
/// Convenience method to get a stream of the persisted blocks.
|
||||
fn persisted_block_stream(&self) -> WatchValueStream<BlockNumHash> {
|
||||
WatchValueStream::new(self.subscribe_persisted_block().0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -260,7 +242,6 @@ mod tests {
|
||||
use reth_ethereum_primitives::{Receipt, TransactionSigned, TxType};
|
||||
use reth_execution_types::ExecutionOutcome;
|
||||
use reth_primitives_traits::SealedBlock;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[test]
|
||||
fn test_commit_notification() {
|
||||
@@ -279,8 +260,7 @@ mod tests {
|
||||
let chain: Arc<Chain> = Arc::new(Chain::new(
|
||||
vec![block1.clone(), block2.clone()],
|
||||
ExecutionOutcome::default(),
|
||||
BTreeMap::new(),
|
||||
BTreeMap::new(),
|
||||
None,
|
||||
));
|
||||
|
||||
// Create a commit notification
|
||||
@@ -315,17 +295,12 @@ mod tests {
|
||||
block3.set_block_number(3);
|
||||
block3.set_hash(block3_hash);
|
||||
|
||||
let old_chain: Arc<Chain> = Arc::new(Chain::new(
|
||||
vec![block1.clone()],
|
||||
ExecutionOutcome::default(),
|
||||
BTreeMap::new(),
|
||||
BTreeMap::new(),
|
||||
));
|
||||
let old_chain: Arc<Chain> =
|
||||
Arc::new(Chain::new(vec![block1.clone()], ExecutionOutcome::default(), None));
|
||||
let new_chain = Arc::new(Chain::new(
|
||||
vec![block2.clone(), block3.clone()],
|
||||
ExecutionOutcome::default(),
|
||||
BTreeMap::new(),
|
||||
BTreeMap::new(),
|
||||
None,
|
||||
));
|
||||
|
||||
// Create a reorg notification
|
||||
@@ -387,12 +362,8 @@ mod tests {
|
||||
let execution_outcome = ExecutionOutcome { receipts, ..Default::default() };
|
||||
|
||||
// Create a new chain segment with `block1` and `block2` and the execution outcome.
|
||||
let new_chain: Arc<Chain> = Arc::new(Chain::new(
|
||||
vec![block1.clone(), block2.clone()],
|
||||
execution_outcome,
|
||||
BTreeMap::new(),
|
||||
BTreeMap::new(),
|
||||
));
|
||||
let new_chain: Arc<Chain> =
|
||||
Arc::new(Chain::new(vec![block1.clone(), block2.clone()], execution_outcome, None));
|
||||
|
||||
// Create a commit notification containing the new chain segment.
|
||||
let notification = CanonStateNotification::Commit { new: new_chain };
|
||||
@@ -449,12 +420,8 @@ mod tests {
|
||||
ExecutionOutcome { receipts: old_receipts, ..Default::default() };
|
||||
|
||||
// Create an old chain segment to be reverted, containing `old_block1`.
|
||||
let old_chain: Arc<Chain> = Arc::new(Chain::new(
|
||||
vec![old_block1.clone()],
|
||||
old_execution_outcome,
|
||||
BTreeMap::new(),
|
||||
BTreeMap::new(),
|
||||
));
|
||||
let old_chain: Arc<Chain> =
|
||||
Arc::new(Chain::new(vec![old_block1.clone()], old_execution_outcome, None));
|
||||
|
||||
// Define block2 for the new chain segment, which will be committed.
|
||||
let mut body = BlockBody::<TransactionSigned>::default();
|
||||
@@ -482,12 +449,7 @@ mod tests {
|
||||
ExecutionOutcome { receipts: new_receipts, ..Default::default() };
|
||||
|
||||
// Create a new chain segment to be committed, containing `new_block1`.
|
||||
let new_chain = Arc::new(Chain::new(
|
||||
vec![new_block1.clone()],
|
||||
new_execution_outcome,
|
||||
BTreeMap::new(),
|
||||
BTreeMap::new(),
|
||||
));
|
||||
let new_chain = Arc::new(Chain::new(vec![new_block1.clone()], new_execution_outcome, None));
|
||||
|
||||
// Create a reorg notification with both reverted (old) and committed (new) chain segments.
|
||||
let notification = CanonStateNotification::Reorg { old: old_chain, new: new_chain };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
in_memory::ExecutedBlock, CanonStateNotification, CanonStateNotifications,
|
||||
CanonStateSubscriptions, ComputedTrieData,
|
||||
in_memory::ExecutedBlockWithTrieUpdates, CanonStateNotification, CanonStateNotifications,
|
||||
CanonStateSubscriptions, ExecutedTrieUpdates,
|
||||
};
|
||||
use alloy_consensus::{Header, SignableTransaction, TxEip1559, TxReceipt, EMPTY_ROOT_HASH};
|
||||
use alloy_eips::{
|
||||
@@ -23,10 +23,11 @@ use reth_primitives_traits::{
|
||||
SignedTransaction,
|
||||
};
|
||||
use reth_storage_api::NodePrimitivesProvider;
|
||||
use reth_trie::root::state_root_unhashed;
|
||||
use reth_trie::{root::state_root_unhashed, HashedPostState};
|
||||
use revm_database::BundleState;
|
||||
use revm_state::AccountInfo;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ops::Range,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
@@ -92,7 +93,7 @@ impl<N: NodePrimitives> TestBlockBuilder<N> {
|
||||
&mut self,
|
||||
number: BlockNumber,
|
||||
parent_hash: B256,
|
||||
) -> SealedBlock<reth_ethereum_primitives::Block> {
|
||||
) -> RecoveredBlock<reth_ethereum_primitives::Block> {
|
||||
let mut rng = rand::rng();
|
||||
|
||||
let mock_tx = |nonce: u64| -> Recovered<_> {
|
||||
@@ -117,7 +118,7 @@ impl<N: NodePrimitives> TestBlockBuilder<N> {
|
||||
.map(|_| {
|
||||
let tx = mock_tx(self.signer_build_account_info.nonce);
|
||||
self.signer_build_account_info.nonce += 1;
|
||||
self.signer_build_account_info.balance -= Self::single_tx_cost();
|
||||
self.signer_build_account_info.balance -= signer_balance_decrease;
|
||||
tx
|
||||
})
|
||||
.collect();
|
||||
@@ -145,10 +146,12 @@ impl<N: NodePrimitives> TestBlockBuilder<N> {
|
||||
mix_hash: B256::random(),
|
||||
gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M,
|
||||
base_fee_per_gas: Some(INITIAL_BASE_FEE),
|
||||
transactions_root: calculate_transaction_root(&transactions),
|
||||
transactions_root: calculate_transaction_root(
|
||||
&transactions.clone().into_iter().map(|tx| tx.into_inner()).collect::<Vec<_>>(),
|
||||
),
|
||||
receipts_root: calculate_receipt_root(&receipts),
|
||||
beneficiary: Address::random(),
|
||||
state_root: state_root_unhashed([(
|
||||
state_root: state_root_unhashed(HashMap::from([(
|
||||
self.signer,
|
||||
Account {
|
||||
balance: initial_signer_balance - signer_balance_decrease,
|
||||
@@ -156,7 +159,7 @@ impl<N: NodePrimitives> TestBlockBuilder<N> {
|
||||
..Default::default()
|
||||
}
|
||||
.into_trie_account(EMPTY_ROOT_HASH),
|
||||
)]),
|
||||
)])),
|
||||
// use the number as the timestamp so it is monotonically increasing
|
||||
timestamp: number +
|
||||
EthereumHardfork::Cancun.activation_timestamp(self.chain_spec.chain).unwrap(),
|
||||
@@ -167,14 +170,17 @@ impl<N: NodePrimitives> TestBlockBuilder<N> {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
SealedBlock::from_sealed_parts(
|
||||
let block = SealedBlock::from_sealed_parts(
|
||||
SealedHeader::seal_slow(header),
|
||||
BlockBody {
|
||||
transactions: transactions.into_iter().map(|tx| tx.into_inner()).collect(),
|
||||
ommers: Vec::new(),
|
||||
withdrawals: Some(vec![].into()),
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
RecoveredBlock::try_recover_sealed_with_senders(block, vec![self.signer; num_txs as usize])
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Creates a fork chain with the given base block.
|
||||
@@ -188,26 +194,24 @@ impl<N: NodePrimitives> TestBlockBuilder<N> {
|
||||
|
||||
for _ in 0..length {
|
||||
let block = self.generate_random_block(parent.number + 1, parent.hash());
|
||||
parent = block.clone();
|
||||
let senders = vec![self.signer; block.body().transactions.len()];
|
||||
let block = block.with_senders(senders);
|
||||
parent = block.clone_sealed_block();
|
||||
fork.push(block);
|
||||
}
|
||||
|
||||
fork
|
||||
}
|
||||
|
||||
/// Gets an [`ExecutedBlock`] with [`BlockNumber`], receipts and parent hash.
|
||||
/// Gets an [`ExecutedBlockWithTrieUpdates`] with [`BlockNumber`], receipts and parent hash.
|
||||
fn get_executed_block(
|
||||
&mut self,
|
||||
block_number: BlockNumber,
|
||||
receipts: Vec<Vec<Receipt>>,
|
||||
parent_hash: B256,
|
||||
) -> ExecutedBlock {
|
||||
let block = self.generate_random_block(block_number, parent_hash);
|
||||
let senders = vec![self.signer; block.body().transactions.len()];
|
||||
let trie_data = ComputedTrieData::default();
|
||||
ExecutedBlock::new(
|
||||
) -> ExecutedBlockWithTrieUpdates {
|
||||
let block_with_senders = self.generate_random_block(block_number, parent_hash);
|
||||
|
||||
let (block, senders) = block_with_senders.split_sealed();
|
||||
ExecutedBlockWithTrieUpdates::new(
|
||||
Arc::new(RecoveredBlock::new_sealed(block, senders)),
|
||||
Arc::new(ExecutionOutcome::new(
|
||||
BundleState::default(),
|
||||
@@ -215,26 +219,27 @@ impl<N: NodePrimitives> TestBlockBuilder<N> {
|
||||
block_number,
|
||||
vec![Requests::default()],
|
||||
)),
|
||||
trie_data,
|
||||
Arc::new(HashedPostState::default()),
|
||||
ExecutedTrieUpdates::empty(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Generates an [`ExecutedBlock`] that includes the given receipts.
|
||||
/// Generates an [`ExecutedBlockWithTrieUpdates`] that includes the given receipts.
|
||||
pub fn get_executed_block_with_receipts(
|
||||
&mut self,
|
||||
receipts: Vec<Vec<Receipt>>,
|
||||
parent_hash: B256,
|
||||
) -> ExecutedBlock {
|
||||
) -> ExecutedBlockWithTrieUpdates {
|
||||
let number = rand::rng().random::<u64>();
|
||||
self.get_executed_block(number, receipts, parent_hash)
|
||||
}
|
||||
|
||||
/// Generates an [`ExecutedBlock`] with the given [`BlockNumber`].
|
||||
/// Generates an [`ExecutedBlockWithTrieUpdates`] with the given [`BlockNumber`].
|
||||
pub fn get_executed_block_with_number(
|
||||
&mut self,
|
||||
block_number: BlockNumber,
|
||||
parent_hash: B256,
|
||||
) -> ExecutedBlock {
|
||||
) -> ExecutedBlockWithTrieUpdates {
|
||||
self.get_executed_block(block_number, vec![vec![]], parent_hash)
|
||||
}
|
||||
|
||||
@@ -242,7 +247,7 @@ impl<N: NodePrimitives> TestBlockBuilder<N> {
|
||||
pub fn get_executed_blocks(
|
||||
&mut self,
|
||||
range: Range<u64>,
|
||||
) -> impl Iterator<Item = ExecutedBlock> + '_ {
|
||||
) -> impl Iterator<Item = ExecutedBlockWithTrieUpdates> + '_ {
|
||||
let mut parent_hash = B256::default();
|
||||
range.map(move |number| {
|
||||
let current_parent_hash = parent_hash;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{ChainSpec, DepositContract};
|
||||
use alloc::{boxed::Box, vec::Vec};
|
||||
use alloy_chains::Chain;
|
||||
use alloy_consensus::Header;
|
||||
use alloy_eips::{calc_next_block_base_fee, eip1559::BaseFeeParams, eip7840::BlobParams};
|
||||
use alloy_genesis::Genesis;
|
||||
use alloy_primitives::{B256, U256};
|
||||
@@ -74,8 +75,8 @@ pub trait EthChainSpec: Send + Sync + Unpin + Debug {
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: BlockHeader> EthChainSpec for ChainSpec<H> {
|
||||
type Header = H;
|
||||
impl EthChainSpec for ChainSpec {
|
||||
type Header = Header;
|
||||
|
||||
fn chain(&self) -> Chain {
|
||||
self.chain
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
|
||||
)]
|
||||
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
extern crate alloc;
|
||||
@@ -30,9 +30,8 @@ pub use info::ChainInfo;
|
||||
#[cfg(any(test, feature = "test-utils"))]
|
||||
pub use spec::test_fork_ids;
|
||||
pub use spec::{
|
||||
blob_params_to_schedule, create_chain_config, mainnet_chain_config, make_genesis_header,
|
||||
BaseFeeParams, BaseFeeParamsKind, ChainSpec, ChainSpecBuilder, ChainSpecProvider,
|
||||
DepositContract, ForkBaseFeeParams, DEV, HOLESKY, HOODI, MAINNET, SEPOLIA,
|
||||
make_genesis_header, BaseFeeParams, BaseFeeParamsKind, ChainSpec, ChainSpecBuilder,
|
||||
ChainSpecProvider, DepositContract, ForkBaseFeeParams, DEV, HOLESKY, HOODI, MAINNET, SEPOLIA,
|
||||
};
|
||||
|
||||
use reth_primitives_traits::sync::OnceLock;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -39,11 +39,6 @@ pub trait ChainSpecParser: Clone + Send + Sync + 'static {
|
||||
/// List of supported chains.
|
||||
const SUPPORTED_CHAINS: &'static [&'static str];
|
||||
|
||||
/// The default value for the chain spec parser.
|
||||
fn default_value() -> Option<&'static str> {
|
||||
Self::SUPPORTED_CHAINS.first().copied()
|
||||
}
|
||||
|
||||
/// Parses the given string into a chain spec.
|
||||
///
|
||||
/// # Arguments
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
|
||||
)]
|
||||
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
|
||||
|
||||
use clap::{Error, Parser};
|
||||
use reth_cli_runner::CliRunner;
|
||||
|
||||
@@ -21,7 +21,7 @@ reth-consensus.workspace = true
|
||||
reth-db = { workspace = true, features = ["mdbx"] }
|
||||
reth-db-api.workspace = true
|
||||
reth-db-common.workspace = true
|
||||
reth-downloaders = { workspace = true, features = ["file-client"] }
|
||||
reth-downloaders.workspace = true
|
||||
reth-ecies.workspace = true
|
||||
reth-eth-wire.workspace = true
|
||||
reth-era.workspace = true
|
||||
@@ -49,7 +49,6 @@ reth-stages.workspace = true
|
||||
reth-stages-types = { workspace = true, optional = true }
|
||||
reth-static-file-types = { workspace = true, features = ["clap"] }
|
||||
reth-static-file.workspace = true
|
||||
reth-tasks.workspace = true
|
||||
reth-trie = { workspace = true, features = ["metrics"] }
|
||||
reth-trie-db = { workspace = true, features = ["metrics"] }
|
||||
reth-trie-common.workspace = true
|
||||
@@ -83,8 +82,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
|
||||
fdlimit.workspace = true
|
||||
@@ -102,8 +99,6 @@ proptest-arbitrary-interop = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
reth-ethereum-cli.workspace = true
|
||||
reth-provider = { workspace = true, features = ["test-utils"] }
|
||||
tempfile.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
@@ -130,5 +125,3 @@ arbitrary = [
|
||||
"reth-primitives-traits/arbitrary",
|
||||
"reth-ethereum-primitives/arbitrary",
|
||||
]
|
||||
|
||||
edge = ["reth-db-common/edge", "reth-stages/rocksdb"]
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Contains common `reth` arguments
|
||||
|
||||
pub use reth_primitives_traits::header::HeaderMut;
|
||||
|
||||
use alloy_primitives::B256;
|
||||
use clap::Parser;
|
||||
use reth_chainspec::EthChainSpec;
|
||||
@@ -9,7 +7,7 @@ use reth_cli::chainspec::ChainSpecParser;
|
||||
use reth_config::{config::EtlConfig, Config};
|
||||
use reth_consensus::noop::NoopConsensus;
|
||||
use reth_db::{init_db, open_db_read_only, DatabaseEnv};
|
||||
use reth_db_common::init::init_genesis_with_settings;
|
||||
use reth_db_common::init::init_genesis;
|
||||
use reth_downloaders::{bodies::noop::NoopBodiesDownloader, headers::noop::NoopHeaderDownloader};
|
||||
use reth_eth_wire::NetPrimitivesFor;
|
||||
use reth_evm::{noop::NoopEvmConfig, ConfigureEvm};
|
||||
@@ -19,14 +17,11 @@ use reth_node_builder::{
|
||||
Node, NodeComponents, NodeComponentsBuilder, NodeTypes, NodeTypesWithDBAdapter,
|
||||
};
|
||||
use reth_node_core::{
|
||||
args::{DatabaseArgs, DatadirArgs, StaticFilesArgs},
|
||||
args::{DatabaseArgs, DatadirArgs},
|
||||
dirs::{ChainPath, DataDirPath},
|
||||
};
|
||||
use reth_provider::{
|
||||
providers::{
|
||||
BlockchainProvider, NodeTypesForProvider, RocksDBProvider, StaticFileProvider,
|
||||
StaticFileProviderBuilder,
|
||||
},
|
||||
providers::{BlockchainProvider, NodeTypesForProvider, StaticFileProvider},
|
||||
ProviderFactory, StaticFileProviderFactory,
|
||||
};
|
||||
use reth_stages::{sets::DefaultStages, Pipeline, PipelineTarget};
|
||||
@@ -53,7 +48,7 @@ pub struct EnvironmentArgs<C: ChainSpecParser> {
|
||||
long,
|
||||
value_name = "CHAIN_OR_PATH",
|
||||
long_help = C::help_message(),
|
||||
default_value = C::default_value(),
|
||||
default_value = C::SUPPORTED_CHAINS[0],
|
||||
value_parser = C::parser(),
|
||||
global = true
|
||||
)]
|
||||
@@ -62,10 +57,6 @@ pub struct EnvironmentArgs<C: ChainSpecParser> {
|
||||
/// All database related arguments
|
||||
#[command(flatten)]
|
||||
pub db: DatabaseArgs,
|
||||
|
||||
/// All static files related arguments
|
||||
#[command(flatten)]
|
||||
pub static_files: StaticFilesArgs,
|
||||
}
|
||||
|
||||
impl<C: ChainSpecParser> EnvironmentArgs<C> {
|
||||
@@ -78,12 +69,10 @@ impl<C: ChainSpecParser> EnvironmentArgs<C> {
|
||||
let data_dir = self.datadir.clone().resolve_datadir(self.chain.chain());
|
||||
let db_path = data_dir.db();
|
||||
let sf_path = data_dir.static_files();
|
||||
let rocksdb_path = data_dir.rocksdb();
|
||||
|
||||
if access.is_read_write() {
|
||||
reth_fs_util::create_dir_all(&db_path)?;
|
||||
reth_fs_util::create_dir_all(&sf_path)?;
|
||||
reth_fs_util::create_dir_all(&rocksdb_path)?;
|
||||
}
|
||||
|
||||
let config_path = self.config.clone().unwrap_or_else(|| data_dir.config());
|
||||
@@ -103,35 +92,21 @@ impl<C: ChainSpecParser> EnvironmentArgs<C> {
|
||||
}
|
||||
|
||||
info!(target: "reth::cli", ?db_path, ?sf_path, "Opening storage");
|
||||
let genesis_block_number = self.chain.genesis().number.unwrap_or_default();
|
||||
let (db, sfp) = match access {
|
||||
AccessRights::RW => (
|
||||
Arc::new(init_db(db_path, self.db.database_args())?),
|
||||
StaticFileProviderBuilder::read_write(sf_path)
|
||||
.with_genesis_block_number(genesis_block_number)
|
||||
.build()?,
|
||||
StaticFileProvider::read_write(sf_path)?,
|
||||
),
|
||||
AccessRights::RO => (
|
||||
Arc::new(open_db_read_only(&db_path, self.db.database_args())?),
|
||||
StaticFileProvider::read_only(sf_path, false)?,
|
||||
),
|
||||
AccessRights::RO | AccessRights::RoInconsistent => {
|
||||
(Arc::new(open_db_read_only(&db_path, self.db.database_args())?), {
|
||||
let provider = StaticFileProviderBuilder::read_only(sf_path)
|
||||
.with_genesis_block_number(genesis_block_number)
|
||||
.build()?;
|
||||
provider.watch_directory();
|
||||
provider
|
||||
})
|
||||
}
|
||||
};
|
||||
// TransactionDB only support read-write mode
|
||||
let rocksdb_provider = RocksDBProvider::builder(data_dir.rocksdb())
|
||||
.with_default_tables()
|
||||
.with_database_log_level(self.db.log_level)
|
||||
.build()?;
|
||||
|
||||
let provider_factory =
|
||||
self.create_provider_factory(&config, db, sfp, rocksdb_provider, access)?;
|
||||
let provider_factory = self.create_provider_factory(&config, db, sfp)?;
|
||||
if access.is_read_write() {
|
||||
debug!(target: "reth::cli", chain=%self.chain.chain(), genesis=?self.chain.genesis_hash(), "Initializing genesis");
|
||||
init_genesis_with_settings(&provider_factory, self.static_files.to_settings())?;
|
||||
init_genesis(&provider_factory)?;
|
||||
}
|
||||
|
||||
Ok(Environment { config, provider_factory, data_dir })
|
||||
@@ -147,25 +122,24 @@ impl<C: ChainSpecParser> EnvironmentArgs<C> {
|
||||
config: &Config,
|
||||
db: Arc<DatabaseEnv>,
|
||||
static_file_provider: StaticFileProvider<N::Primitives>,
|
||||
rocksdb_provider: RocksDBProvider,
|
||||
access: AccessRights,
|
||||
) -> eyre::Result<ProviderFactory<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>>
|
||||
where
|
||||
C: ChainSpecParser<ChainSpec = N::ChainSpec>,
|
||||
{
|
||||
let prune_modes = config.prune.segments.clone();
|
||||
let has_receipt_pruning = config.prune.as_ref().is_some_and(|a| a.has_receipts_pruning());
|
||||
let prune_modes =
|
||||
config.prune.as_ref().map(|prune| prune.segments.clone()).unwrap_or_default();
|
||||
let factory = ProviderFactory::<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>::new(
|
||||
db,
|
||||
self.chain.clone(),
|
||||
static_file_provider,
|
||||
rocksdb_provider,
|
||||
)?
|
||||
)
|
||||
.with_prune_modes(prune_modes.clone());
|
||||
|
||||
// Check for consistency between database and static files.
|
||||
if !access.is_read_only_inconsistent() &&
|
||||
let Some(unwind_target) =
|
||||
factory.static_file_provider().check_consistency(&factory.provider()?)?
|
||||
if let Some(unwind_target) = factory
|
||||
.static_file_provider()
|
||||
.check_consistency(&factory.provider()?, has_receipt_pruning)?
|
||||
{
|
||||
if factory.db_ref().is_read_only()? {
|
||||
warn!(target: "reth::cli", ?unwind_target, "Inconsistent storage. Restart node to heal.");
|
||||
@@ -226,8 +200,6 @@ pub enum AccessRights {
|
||||
RW,
|
||||
/// Read-only access
|
||||
RO,
|
||||
/// Read-only access with possibly inconsistent data
|
||||
RoInconsistent,
|
||||
}
|
||||
|
||||
impl AccessRights {
|
||||
@@ -235,12 +207,6 @@ impl AccessRights {
|
||||
pub const fn is_read_write(&self) -> bool {
|
||||
matches!(self, Self::RW)
|
||||
}
|
||||
|
||||
/// Returns `true` if it requires read-only access to the environment with possibly inconsistent
|
||||
/// data.
|
||||
pub const fn is_read_only_inconsistent(&self) -> bool {
|
||||
matches!(self, Self::RoInconsistent)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper alias to satisfy `FullNodeTypes` bound on [`Node`] trait generic.
|
||||
@@ -250,6 +216,17 @@ type FullTypesAdapter<T> = FullNodeTypesAdapter<
|
||||
BlockchainProvider<NodeTypesWithDBAdapter<T, Arc<DatabaseEnv>>>,
|
||||
>;
|
||||
|
||||
/// Trait for block headers that can be modified through CLI operations.
|
||||
pub trait CliHeader {
|
||||
fn set_number(&mut self, number: u64);
|
||||
}
|
||||
|
||||
impl CliHeader for alloy_consensus::Header {
|
||||
fn set_number(&mut self, number: u64) {
|
||||
self.number = number;
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper trait with a common set of requirements for the
|
||||
/// [`NodeTypes`] in CLI.
|
||||
pub trait CliNodeTypes: Node<FullTypesAdapter<Self>> + NodeTypesForProvider {
|
||||
|
||||
@@ -22,14 +22,13 @@ impl Command {
|
||||
let config = if self.default {
|
||||
Config::default()
|
||||
} else {
|
||||
let path = match self.config.as_ref() {
|
||||
Some(path) => path,
|
||||
None => bail!("No config file provided. Use --config <FILE> or pass --default"),
|
||||
};
|
||||
let path = self.config.clone().unwrap_or_default();
|
||||
// Check if the file exists
|
||||
if !path.exists() {
|
||||
bail!("Config file does not exist: {}", path.display());
|
||||
}
|
||||
Config::from_path(path)
|
||||
// Read the configuration file
|
||||
Config::from_path(&path)
|
||||
.wrap_err_with(|| format!("Could not load config file: {}", path.display()))?
|
||||
};
|
||||
println!("{}", toml::to_string_pretty(&config)?);
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
use alloy_primitives::{keccak256, Address};
|
||||
use clap::Parser;
|
||||
use human_bytes::human_bytes;
|
||||
use reth_codecs::Compact;
|
||||
use reth_db_api::{cursor::DbDupCursorRO, database::Database, tables, transaction::DbTx};
|
||||
use reth_db_common::DbTool;
|
||||
use reth_node_builder::NodeTypesWithDB;
|
||||
use std::time::{Duration, Instant};
|
||||
use tracing::info;
|
||||
|
||||
/// Log progress every 5 seconds
|
||||
const LOG_INTERVAL: Duration = Duration::from_secs(5);
|
||||
|
||||
/// The arguments for the `reth db account-storage` command
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct Command {
|
||||
/// The account address to check storage for
|
||||
address: Address,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
/// Execute `db account-storage` command
|
||||
pub fn execute<N: NodeTypesWithDB>(self, tool: &DbTool<N>) -> eyre::Result<()> {
|
||||
let address = self.address;
|
||||
let (slot_count, plain_size) = tool.provider_factory.db_ref().view(|tx| {
|
||||
let mut cursor = tx.cursor_dup_read::<tables::PlainStorageState>()?;
|
||||
let mut count = 0usize;
|
||||
let mut total_value_bytes = 0usize;
|
||||
let mut last_log = Instant::now();
|
||||
|
||||
// Walk all storage entries for this address
|
||||
let walker = cursor.walk_dup(Some(address), None)?;
|
||||
for entry in walker {
|
||||
let (_, storage_entry) = entry?;
|
||||
count += 1;
|
||||
// StorageEntry encodes as: 32 bytes (key/subkey uncompressed) + compressed U256
|
||||
let mut buf = Vec::new();
|
||||
let entry_len = storage_entry.to_compact(&mut buf);
|
||||
total_value_bytes += entry_len;
|
||||
|
||||
if last_log.elapsed() >= LOG_INTERVAL {
|
||||
info!(
|
||||
target: "reth::cli",
|
||||
address = %address,
|
||||
slots = count,
|
||||
key = %storage_entry.key,
|
||||
"Processing storage slots"
|
||||
);
|
||||
last_log = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
// Add 20 bytes for the Address key (stored once per account in dupsort)
|
||||
let total_size = if count > 0 { 20 + total_value_bytes } else { 0 };
|
||||
|
||||
Ok::<_, eyre::Report>((count, total_size))
|
||||
})??;
|
||||
|
||||
// Estimate hashed storage size: 32-byte B256 key instead of 20-byte Address
|
||||
let hashed_size_estimate = if slot_count > 0 { plain_size + 12 } else { 0 };
|
||||
let total_estimate = plain_size + hashed_size_estimate;
|
||||
|
||||
let hashed_address = keccak256(address);
|
||||
|
||||
println!("Account: {address}");
|
||||
println!("Hashed address: {hashed_address}");
|
||||
println!("Storage slots: {slot_count}");
|
||||
println!("Plain storage size: {} (estimated)", human_bytes(plain_size as f64));
|
||||
println!("Hashed storage size: {} (estimated)", human_bytes(hashed_size_estimate as f64));
|
||||
println!("Total estimated size: {}", human_bytes(total_estimate as f64));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_address_arg() {
|
||||
let cmd = Command::try_parse_from([
|
||||
"account-storage",
|
||||
"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
|
||||
])
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
cmd.address,
|
||||
"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".parse::<Address>().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,8 @@ use reth_db_api::{
|
||||
transaction::{DbTx, DbTxMut},
|
||||
TableViewer, Tables,
|
||||
};
|
||||
use reth_db_common::DbTool;
|
||||
use reth_node_builder::NodeTypesWithDB;
|
||||
use reth_provider::StaticFileProviderFactory;
|
||||
use reth_provider::{ProviderFactory, StaticFileProviderFactory};
|
||||
use reth_static_file_types::StaticFileSegment;
|
||||
|
||||
/// The arguments for the `reth db clear` command
|
||||
@@ -20,16 +19,19 @@ pub struct Command {
|
||||
|
||||
impl Command {
|
||||
/// Execute `db clear` command
|
||||
pub fn execute<N: NodeTypesWithDB>(self, tool: &DbTool<N>) -> eyre::Result<()> {
|
||||
pub fn execute<N: NodeTypesWithDB>(
|
||||
self,
|
||||
provider_factory: ProviderFactory<N>,
|
||||
) -> eyre::Result<()> {
|
||||
match self.subcommand {
|
||||
Subcommands::Mdbx { table } => {
|
||||
table.view(&ClearViewer { db: tool.provider_factory.db_ref() })?
|
||||
table.view(&ClearViewer { db: provider_factory.db_ref() })?
|
||||
}
|
||||
Subcommands::StaticFile { segment } => {
|
||||
let static_file_provider = tool.provider_factory.static_file_provider();
|
||||
let static_file_provider = provider_factory.static_file_provider();
|
||||
let static_files = iter_static_files(static_file_provider.directory())?;
|
||||
|
||||
if let Some(segment_static_files) = static_files.get(segment) {
|
||||
if let Some(segment_static_files) = static_files.get(&segment) {
|
||||
for (block_range, _) in segment_static_files {
|
||||
static_file_provider.delete_jar(segment, block_range.start())?;
|
||||
}
|
||||
|
||||
@@ -1,25 +1,20 @@
|
||||
use alloy_consensus::Header;
|
||||
use alloy_primitives::{hex, BlockHash};
|
||||
use clap::Parser;
|
||||
use reth_db::{
|
||||
static_file::{
|
||||
AccountChangesetMask, ColumnSelectorOne, ColumnSelectorTwo, HeaderWithHashMask,
|
||||
ReceiptMask, TransactionMask, TransactionSenderMask,
|
||||
ColumnSelectorOne, ColumnSelectorTwo, HeaderWithHashMask, ReceiptMask, TransactionMask,
|
||||
},
|
||||
RawDupSort,
|
||||
};
|
||||
use reth_db_api::{
|
||||
cursor::{DbCursorRO, DbDupCursorRO},
|
||||
database::Database,
|
||||
table::{Compress, Decompress, DupSort, Table},
|
||||
tables,
|
||||
transaction::DbTx,
|
||||
RawKey, RawTable, Receipts, TableViewer, Transactions,
|
||||
table::{Decompress, DupSort, Table},
|
||||
tables, RawKey, RawTable, Receipts, TableViewer, Transactions,
|
||||
};
|
||||
use reth_db_common::DbTool;
|
||||
use reth_node_api::{HeaderTy, ReceiptTy, TxTy};
|
||||
use reth_node_api::{ReceiptTy, TxTy};
|
||||
use reth_node_builder::NodeTypesWithDB;
|
||||
use reth_primitives_traits::ValueWithSubKey;
|
||||
use reth_provider::{providers::ProviderNodeTypes, ChangeSetReader, StaticFileProviderFactory};
|
||||
use reth_provider::{providers::ProviderNodeTypes, StaticFileProviderFactory};
|
||||
use reth_static_file_types::StaticFileSegment;
|
||||
use tracing::error;
|
||||
|
||||
@@ -44,14 +39,6 @@ enum Subcommand {
|
||||
#[arg(value_parser = maybe_json_value_parser)]
|
||||
subkey: Option<String>,
|
||||
|
||||
/// Optional end key for range query (exclusive upper bound)
|
||||
#[arg(value_parser = maybe_json_value_parser)]
|
||||
end_key: Option<String>,
|
||||
|
||||
/// Optional end subkey for range query (exclusive upper bound)
|
||||
#[arg(value_parser = maybe_json_value_parser)]
|
||||
end_subkey: Option<String>,
|
||||
|
||||
/// Output bytes instead of human-readable decoded value
|
||||
#[arg(long)]
|
||||
raw: bool,
|
||||
@@ -64,10 +51,6 @@ enum Subcommand {
|
||||
#[arg(value_parser = maybe_json_value_parser)]
|
||||
key: String,
|
||||
|
||||
/// The subkey to get content for, for example address in changeset
|
||||
#[arg(value_parser = maybe_json_value_parser)]
|
||||
subkey: Option<String>,
|
||||
|
||||
/// Output bytes instead of human-readable decoded value
|
||||
#[arg(long)]
|
||||
raw: bool,
|
||||
@@ -78,69 +61,22 @@ impl Command {
|
||||
/// Execute `db get` command
|
||||
pub fn execute<N: ProviderNodeTypes>(self, tool: &DbTool<N>) -> eyre::Result<()> {
|
||||
match self.subcommand {
|
||||
Subcommand::Mdbx { table, key, subkey, end_key, end_subkey, raw } => {
|
||||
table.view(&GetValueViewer { tool, key, subkey, end_key, end_subkey, raw })?
|
||||
Subcommand::Mdbx { table, key, subkey, raw } => {
|
||||
table.view(&GetValueViewer { tool, key, subkey, raw })?
|
||||
}
|
||||
Subcommand::StaticFile { segment, key, subkey, raw } => {
|
||||
let (key, subkey, mask): (u64, _, _) = match segment {
|
||||
StaticFileSegment::Headers => (
|
||||
table_key::<tables::Headers>(&key)?,
|
||||
None,
|
||||
<HeaderWithHashMask<HeaderTy<N>>>::MASK,
|
||||
),
|
||||
StaticFileSegment::Transactions => (
|
||||
table_key::<tables::Transactions>(&key)?,
|
||||
None,
|
||||
<TransactionMask<TxTy<N>>>::MASK,
|
||||
),
|
||||
StaticFileSegment::Receipts => (
|
||||
table_key::<tables::Receipts>(&key)?,
|
||||
None,
|
||||
<ReceiptMask<ReceiptTy<N>>>::MASK,
|
||||
),
|
||||
StaticFileSegment::TransactionSenders => (
|
||||
table_key::<tables::TransactionSenders>(&key)?,
|
||||
None,
|
||||
TransactionSenderMask::MASK,
|
||||
),
|
||||
StaticFileSegment::AccountChangeSets => {
|
||||
let subkey =
|
||||
table_subkey::<tables::AccountChangeSets>(subkey.as_deref()).ok();
|
||||
(
|
||||
table_key::<tables::AccountChangeSets>(&key)?,
|
||||
subkey,
|
||||
AccountChangesetMask::MASK,
|
||||
)
|
||||
Subcommand::StaticFile { segment, key, raw } => {
|
||||
let (key, mask): (u64, _) = match segment {
|
||||
StaticFileSegment::Headers => {
|
||||
(table_key::<tables::Headers>(&key)?, <HeaderWithHashMask<Header>>::MASK)
|
||||
}
|
||||
StaticFileSegment::Transactions => {
|
||||
(table_key::<tables::Transactions>(&key)?, <TransactionMask<TxTy<N>>>::MASK)
|
||||
}
|
||||
StaticFileSegment::Receipts => {
|
||||
(table_key::<tables::Receipts>(&key)?, <ReceiptMask<ReceiptTy<N>>>::MASK)
|
||||
}
|
||||
};
|
||||
|
||||
// handle account changesets differently if a subkey is provided.
|
||||
if let StaticFileSegment::AccountChangeSets = segment {
|
||||
let Some(subkey) = subkey else {
|
||||
// get all changesets for the block
|
||||
let changesets = tool
|
||||
.provider_factory
|
||||
.static_file_provider()
|
||||
.account_block_changeset(key)?;
|
||||
|
||||
println!("{}", serde_json::to_string_pretty(&changesets)?);
|
||||
return Ok(())
|
||||
};
|
||||
|
||||
let account = tool
|
||||
.provider_factory
|
||||
.static_file_provider()
|
||||
.get_account_before_block(key, subkey)?;
|
||||
|
||||
if let Some(account) = account {
|
||||
println!("{}", serde_json::to_string_pretty(&account)?);
|
||||
} else {
|
||||
error!(target: "reth::cli", "No content for the given table key.");
|
||||
}
|
||||
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let content = tool.provider_factory.static_file_provider().find_static_file(
|
||||
segment,
|
||||
|provider| {
|
||||
@@ -160,7 +96,7 @@ impl Command {
|
||||
} else {
|
||||
match segment {
|
||||
StaticFileSegment::Headers => {
|
||||
let header = HeaderTy::<N>::decompress(content[0].as_slice())?;
|
||||
let header = Header::decompress(content[0].as_slice())?;
|
||||
let block_hash = BlockHash::decompress(content[1].as_slice())?;
|
||||
println!(
|
||||
"Header\n{}\n\nBlockHash\n{}",
|
||||
@@ -180,16 +116,6 @@ impl Command {
|
||||
)?;
|
||||
println!("{}", serde_json::to_string_pretty(&receipt)?);
|
||||
}
|
||||
StaticFileSegment::TransactionSenders => {
|
||||
let sender =
|
||||
<<tables::TransactionSenders as Table>::Value>::decompress(
|
||||
content[0].as_slice(),
|
||||
)?;
|
||||
println!("{}", serde_json::to_string_pretty(&sender)?);
|
||||
}
|
||||
StaticFileSegment::AccountChangeSets => {
|
||||
unreachable!("account changeset static files are special cased before this match")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,8 +144,6 @@ struct GetValueViewer<'a, N: NodeTypesWithDB> {
|
||||
tool: &'a DbTool<N>,
|
||||
key: String,
|
||||
subkey: Option<String>,
|
||||
end_key: Option<String>,
|
||||
end_subkey: Option<String>,
|
||||
raw: bool,
|
||||
}
|
||||
|
||||
@@ -229,158 +153,53 @@ impl<N: ProviderNodeTypes> TableViewer<()> for GetValueViewer<'_, N> {
|
||||
fn view<T: Table>(&self) -> Result<(), Self::Error> {
|
||||
let key = table_key::<T>(&self.key)?;
|
||||
|
||||
// A non-dupsort table cannot have subkeys. The `subkey` arg becomes the `end_key`. First we
|
||||
// check that `end_key` and `end_subkey` weren't previously given, as that wouldn't be
|
||||
// valid.
|
||||
if self.end_key.is_some() || self.end_subkey.is_some() {
|
||||
return Err(eyre::eyre!("Only END_KEY can be given for non-DUPSORT tables"));
|
||||
}
|
||||
|
||||
let end_key = self.subkey.clone();
|
||||
|
||||
// Check if we're doing a range query
|
||||
if let Some(ref end_key_str) = end_key {
|
||||
let end_key = table_key::<T>(end_key_str)?;
|
||||
|
||||
// Use walk_range to iterate over the range
|
||||
self.tool.provider_factory.db_ref().view(|tx| {
|
||||
let mut cursor = tx.cursor_read::<T>()?;
|
||||
let walker = cursor.walk_range(key..end_key)?;
|
||||
|
||||
for result in walker {
|
||||
let (k, v) = result?;
|
||||
let json_val = if self.raw {
|
||||
let raw_key = RawKey::from(k);
|
||||
serde_json::json!({
|
||||
"key": hex::encode_prefixed(raw_key.raw_key()),
|
||||
"val": hex::encode_prefixed(v.compress().as_ref()),
|
||||
})
|
||||
} else {
|
||||
serde_json::json!({
|
||||
"key": &k,
|
||||
"val": &v,
|
||||
})
|
||||
};
|
||||
|
||||
println!("{}", serde_json::to_string_pretty(&json_val)?);
|
||||
}
|
||||
|
||||
Ok::<_, eyre::Report>(())
|
||||
})??;
|
||||
let content = if self.raw {
|
||||
self.tool
|
||||
.get::<RawTable<T>>(RawKey::from(key))?
|
||||
.map(|content| hex::encode_prefixed(content.raw_value()))
|
||||
} else {
|
||||
// Single key lookup
|
||||
let content = if self.raw {
|
||||
self.tool
|
||||
.get::<RawTable<T>>(RawKey::from(key))?
|
||||
.map(|content| hex::encode_prefixed(content.raw_value()))
|
||||
} else {
|
||||
self.tool.get::<T>(key)?.as_ref().map(serde_json::to_string_pretty).transpose()?
|
||||
};
|
||||
self.tool.get::<T>(key)?.as_ref().map(serde_json::to_string_pretty).transpose()?
|
||||
};
|
||||
|
||||
match content {
|
||||
Some(content) => {
|
||||
println!("{content}");
|
||||
}
|
||||
None => {
|
||||
error!(target: "reth::cli", "No content for the given table key.");
|
||||
}
|
||||
};
|
||||
}
|
||||
match content {
|
||||
Some(content) => {
|
||||
println!("{content}");
|
||||
}
|
||||
None => {
|
||||
error!(target: "reth::cli", "No content for the given table key.");
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn view_dupsort<T: DupSort>(&self) -> Result<(), Self::Error>
|
||||
where
|
||||
T::Value: reth_primitives_traits::ValueWithSubKey<SubKey = T::SubKey>,
|
||||
{
|
||||
fn view_dupsort<T: DupSort>(&self) -> Result<(), Self::Error> {
|
||||
// get a key for given table
|
||||
let key = table_key::<T>(&self.key)?;
|
||||
|
||||
// Check if we're doing a range query
|
||||
if let Some(ref end_key_str) = self.end_key {
|
||||
let end_key = table_key::<T>(end_key_str)?;
|
||||
let start_subkey = table_subkey::<T>(Some(
|
||||
self.subkey.as_ref().expect("must have been given if end_key is given").as_str(),
|
||||
))?;
|
||||
let end_subkey_parsed = self
|
||||
.end_subkey
|
||||
.as_ref()
|
||||
.map(|s| table_subkey::<T>(Some(s.as_str())))
|
||||
.transpose()?;
|
||||
// process dupsort table
|
||||
let subkey = table_subkey::<T>(self.subkey.as_deref())?;
|
||||
|
||||
self.tool.provider_factory.db_ref().view(|tx| {
|
||||
let mut cursor = tx.cursor_dup_read::<T>()?;
|
||||
|
||||
// Seek to the starting key. If there is actually a key at the starting key then
|
||||
// seek to the subkey within it.
|
||||
if let Some((decoded_key, _)) = cursor.seek(key.clone())? &&
|
||||
decoded_key == key
|
||||
{
|
||||
cursor.seek_by_key_subkey(key.clone(), start_subkey.clone())?;
|
||||
}
|
||||
|
||||
// Get the current position to start iteration
|
||||
let mut current = cursor.current()?;
|
||||
|
||||
while let Some((decoded_key, decoded_value)) = current {
|
||||
// Extract the subkey using the ValueWithSubKey trait
|
||||
let decoded_subkey = decoded_value.get_subkey();
|
||||
|
||||
// Check if we've reached the end (exclusive)
|
||||
if (&decoded_key, Some(&decoded_subkey)) >=
|
||||
(&end_key, end_subkey_parsed.as_ref())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Output the entry with both key and subkey
|
||||
let json_val = if self.raw {
|
||||
let raw_key = RawKey::from(decoded_key.clone());
|
||||
serde_json::json!({
|
||||
"key": hex::encode_prefixed(raw_key.raw_key()),
|
||||
"val": hex::encode_prefixed(decoded_value.compress().as_ref()),
|
||||
})
|
||||
} else {
|
||||
serde_json::json!({
|
||||
"key": &decoded_key,
|
||||
"val": &decoded_value,
|
||||
})
|
||||
};
|
||||
|
||||
println!("{}", serde_json::to_string_pretty(&json_val)?);
|
||||
|
||||
// Move to next entry
|
||||
current = cursor.next()?;
|
||||
}
|
||||
|
||||
Ok::<_, eyre::Report>(())
|
||||
})??;
|
||||
let content = if self.raw {
|
||||
self.tool
|
||||
.get_dup::<RawDupSort<T>>(RawKey::from(key), RawKey::from(subkey))?
|
||||
.map(|content| hex::encode_prefixed(content.raw_value()))
|
||||
} else {
|
||||
// Single key/subkey lookup
|
||||
let subkey = table_subkey::<T>(self.subkey.as_deref())?;
|
||||
self.tool
|
||||
.get_dup::<T>(key, subkey)?
|
||||
.as_ref()
|
||||
.map(serde_json::to_string_pretty)
|
||||
.transpose()?
|
||||
};
|
||||
|
||||
let content = if self.raw {
|
||||
self.tool
|
||||
.get_dup::<RawDupSort<T>>(RawKey::from(key), RawKey::from(subkey))?
|
||||
.map(|content| hex::encode_prefixed(content.raw_value()))
|
||||
} else {
|
||||
self.tool
|
||||
.get_dup::<T>(key, subkey)?
|
||||
.as_ref()
|
||||
.map(serde_json::to_string_pretty)
|
||||
.transpose()?
|
||||
};
|
||||
|
||||
match content {
|
||||
Some(content) => {
|
||||
println!("{content}");
|
||||
}
|
||||
None => {
|
||||
error!(target: "reth::cli", "No content for the given table subkey.");
|
||||
}
|
||||
};
|
||||
}
|
||||
match content {
|
||||
Some(content) => {
|
||||
println!("{content}");
|
||||
}
|
||||
None => {
|
||||
error!(target: "reth::cli", "No content for the given table subkey.");
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use alloy_primitives::hex;
|
||||
use clap::Parser;
|
||||
use eyre::WrapErr;
|
||||
use reth_chainspec::EthereumHardforks;
|
||||
use reth_db::{transaction::DbTx, DatabaseEnv};
|
||||
use reth_db::DatabaseEnv;
|
||||
use reth_db_api::{database::Database, table::Table, RawValue, TableViewer, Tables};
|
||||
use reth_db_common::{DbTool, ListFilter};
|
||||
use reth_node_builder::{NodeTypes, NodeTypesWithDBAdapter};
|
||||
@@ -96,11 +96,8 @@ impl<N: NodeTypes> TableViewer<()> for ListTableViewer<'_, N> {
|
||||
|
||||
fn view<T: Table>(&self) -> Result<(), Self::Error> {
|
||||
self.tool.provider_factory.db_ref().view(|tx| {
|
||||
// We may be using the tui for a long time
|
||||
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).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: {}", stringify!($table)))?;
|
||||
let total_entries = stats.entries();
|
||||
let final_entry_idx = total_entries.saturating_sub(1);
|
||||
if self.args.skip > final_entry_idx {
|
||||
|
||||
@@ -2,22 +2,18 @@ use crate::common::{AccessRights, CliNodeTypes, Environment, EnvironmentArgs};
|
||||
use clap::{Parser, Subcommand};
|
||||
use reth_chainspec::{EthChainSpec, EthereumHardforks};
|
||||
use reth_cli::chainspec::ChainSpecParser;
|
||||
use reth_cli_runner::CliContext;
|
||||
use reth_db::version::{get_db_version, DatabaseVersionError, DB_VERSION};
|
||||
use reth_db_common::DbTool;
|
||||
use std::{
|
||||
io::{self, Write},
|
||||
sync::Arc,
|
||||
};
|
||||
mod account_storage;
|
||||
mod checksum;
|
||||
mod clear;
|
||||
mod diff;
|
||||
mod get;
|
||||
mod list;
|
||||
mod repair_trie;
|
||||
mod settings;
|
||||
mod static_file_header;
|
||||
mod stats;
|
||||
/// DB List TUI
|
||||
mod tui;
|
||||
@@ -55,35 +51,25 @@ pub enum Subcommands {
|
||||
Clear(clear::Command),
|
||||
/// Verifies trie consistency and outputs any inconsistencies
|
||||
RepairTrie(repair_trie::Command),
|
||||
/// Reads and displays the static file segment header
|
||||
StaticFileHeader(static_file_header::Command),
|
||||
/// Lists current and local database versions
|
||||
Version,
|
||||
/// Returns the full database path
|
||||
Path,
|
||||
/// Manage storage settings
|
||||
Settings(settings::Command),
|
||||
/// Gets storage size information for an account
|
||||
AccountStorage(account_storage::Command),
|
||||
}
|
||||
|
||||
/// Initializes a provider factory with specified access rights, and then execute with the provided
|
||||
/// command
|
||||
macro_rules! db_exec {
|
||||
($env:expr, $tool:ident, $N:ident, $access_rights:expr, $command:block) => {
|
||||
let Environment { provider_factory, .. } = $env.init::<$N>($access_rights)?;
|
||||
/// `db_ro_exec` opens a database in read-only mode, and then execute with the provided command
|
||||
macro_rules! db_ro_exec {
|
||||
($env:expr, $tool:ident, $N:ident, $command:block) => {
|
||||
let Environment { provider_factory, .. } = $env.init::<$N>(AccessRights::RO)?;
|
||||
|
||||
let $tool = DbTool::new(provider_factory)?;
|
||||
let $tool = DbTool::new(provider_factory.clone())?;
|
||||
$command;
|
||||
};
|
||||
}
|
||||
|
||||
impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C> {
|
||||
/// Execute `db` command
|
||||
pub async fn execute<N: CliNodeTypes<ChainSpec = C::ChainSpec>>(
|
||||
self,
|
||||
ctx: CliContext,
|
||||
) -> eyre::Result<()> {
|
||||
pub async fn execute<N: CliNodeTypes<ChainSpec = C::ChainSpec>>(self) -> eyre::Result<()> {
|
||||
let data_dir = self.env.datadir.clone().resolve_datadir(self.env.chain.chain());
|
||||
let db_path = data_dir.db();
|
||||
let static_files_path = data_dir.static_files();
|
||||
@@ -102,32 +88,27 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C>
|
||||
match self.command {
|
||||
// TODO: We'll need to add this on the DB trait.
|
||||
Subcommands::Stats(command) => {
|
||||
let access_rights = if command.skip_consistency_checks {
|
||||
AccessRights::RoInconsistent
|
||||
} else {
|
||||
AccessRights::RO
|
||||
};
|
||||
db_exec!(self.env, tool, N, access_rights, {
|
||||
db_ro_exec!(self.env, tool, N, {
|
||||
command.execute(data_dir, &tool)?;
|
||||
});
|
||||
}
|
||||
Subcommands::List(command) => {
|
||||
db_exec!(self.env, tool, N, AccessRights::RO, {
|
||||
db_ro_exec!(self.env, tool, N, {
|
||||
command.execute(&tool)?;
|
||||
});
|
||||
}
|
||||
Subcommands::Checksum(command) => {
|
||||
db_exec!(self.env, tool, N, AccessRights::RO, {
|
||||
db_ro_exec!(self.env, tool, N, {
|
||||
command.execute(&tool)?;
|
||||
});
|
||||
}
|
||||
Subcommands::Diff(command) => {
|
||||
db_exec!(self.env, tool, N, AccessRights::RO, {
|
||||
db_ro_exec!(self.env, tool, N, {
|
||||
command.execute(&tool)?;
|
||||
});
|
||||
}
|
||||
Subcommands::Get(command) => {
|
||||
db_exec!(self.env, tool, N, AccessRights::RO, {
|
||||
db_ro_exec!(self.env, tool, N, {
|
||||
command.execute(&tool)?;
|
||||
});
|
||||
}
|
||||
@@ -149,26 +130,19 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C>
|
||||
}
|
||||
}
|
||||
|
||||
db_exec!(self.env, tool, N, AccessRights::RW, {
|
||||
tool.drop(db_path, static_files_path, exex_wal_path)?;
|
||||
});
|
||||
let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
|
||||
let tool = DbTool::new(provider_factory)?;
|
||||
tool.drop(db_path, static_files_path, exex_wal_path)?;
|
||||
}
|
||||
Subcommands::Clear(command) => {
|
||||
db_exec!(self.env, tool, N, AccessRights::RW, {
|
||||
command.execute(&tool)?;
|
||||
});
|
||||
let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
|
||||
command.execute(provider_factory)?;
|
||||
}
|
||||
Subcommands::RepairTrie(command) => {
|
||||
let access_rights =
|
||||
if command.dry_run { AccessRights::RO } else { AccessRights::RW };
|
||||
db_exec!(self.env, tool, N, access_rights, {
|
||||
command.execute(&tool, ctx.task_executor.clone(), &data_dir)?;
|
||||
});
|
||||
}
|
||||
Subcommands::StaticFileHeader(command) => {
|
||||
db_exec!(self.env, tool, N, AccessRights::RoInconsistent, {
|
||||
command.execute(&tool)?;
|
||||
});
|
||||
let Environment { provider_factory, .. } = self.env.init::<N>(access_rights)?;
|
||||
command.execute(provider_factory)?;
|
||||
}
|
||||
Subcommands::Version => {
|
||||
let local_db_version = match get_db_version(&db_path) {
|
||||
@@ -188,16 +162,6 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C>
|
||||
Subcommands::Path => {
|
||||
println!("{}", db_path.display());
|
||||
}
|
||||
Subcommands::Settings(command) => {
|
||||
db_exec!(self.env, tool, N, command.access_rights(), {
|
||||
command.execute(&tool)?;
|
||||
});
|
||||
}
|
||||
Subcommands::AccountStorage(command) => {
|
||||
db_exec!(self.env, tool, N, AccessRights::RO, {
|
||||
command.execute(&tool)?;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,37 +1,20 @@
|
||||
use clap::Parser;
|
||||
use metrics::{self, Counter};
|
||||
use reth_chainspec::EthChainSpec;
|
||||
use reth_cli_util::parse_socket_address;
|
||||
use reth_db_api::{
|
||||
cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO},
|
||||
database::Database,
|
||||
tables,
|
||||
transaction::{DbTx, DbTxMut},
|
||||
};
|
||||
use reth_db_common::DbTool;
|
||||
use reth_node_core::{
|
||||
dirs::{ChainPath, DataDirPath},
|
||||
version::version_metadata,
|
||||
};
|
||||
use reth_node_metrics::{
|
||||
chain::ChainSpecInfo,
|
||||
hooks::Hooks,
|
||||
server::{MetricServer, MetricServerConfig},
|
||||
version::VersionInfo,
|
||||
};
|
||||
use reth_provider::{providers::ProviderNodeTypes, ChainSpecProvider, StageCheckpointReader};
|
||||
use reth_node_builder::NodeTypesWithDB;
|
||||
use reth_provider::{providers::ProviderNodeTypes, ProviderFactory, StageCheckpointReader};
|
||||
use reth_stages::StageId;
|
||||
use reth_tasks::TaskExecutor;
|
||||
use reth_trie::{
|
||||
verify::{Output, Verifier},
|
||||
Nibbles,
|
||||
};
|
||||
use reth_trie_common::{StorageTrieEntry, StoredNibbles, StoredNibblesSubKey};
|
||||
use reth_trie_db::{DatabaseHashedCursorFactory, DatabaseTrieCursorFactory};
|
||||
use std::{
|
||||
net::SocketAddr,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
use tracing::{info, warn};
|
||||
|
||||
const PROGRESS_PERIOD: Duration = Duration::from_secs(5);
|
||||
@@ -42,86 +25,34 @@ pub struct Command {
|
||||
/// Only show inconsistencies without making any repairs
|
||||
#[arg(long)]
|
||||
pub(crate) dry_run: bool,
|
||||
|
||||
/// Enable Prometheus metrics.
|
||||
///
|
||||
/// The metrics will be served at the given interface and port.
|
||||
#[arg(long = "metrics", value_name = "ADDR:PORT", value_parser = parse_socket_address)]
|
||||
pub(crate) metrics: Option<SocketAddr>,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
/// Execute `db repair-trie` command
|
||||
pub fn execute<N: ProviderNodeTypes>(
|
||||
self,
|
||||
tool: &DbTool<N>,
|
||||
task_executor: TaskExecutor,
|
||||
data_dir: &ChainPath<DataDirPath>,
|
||||
provider_factory: ProviderFactory<N>,
|
||||
) -> eyre::Result<()> {
|
||||
// Set up metrics server if requested
|
||||
let _metrics_handle = if let Some(listen_addr) = self.metrics {
|
||||
let chain_name = tool.provider_factory.chain_spec().chain().to_string();
|
||||
let executor = task_executor.clone();
|
||||
let pprof_dump_dir = data_dir.pprof_dumps();
|
||||
|
||||
let handle = task_executor.spawn_critical("metrics server", async move {
|
||||
let config = MetricServerConfig::new(
|
||||
listen_addr,
|
||||
VersionInfo {
|
||||
version: version_metadata().cargo_pkg_version.as_ref(),
|
||||
build_timestamp: version_metadata().vergen_build_timestamp.as_ref(),
|
||||
cargo_features: version_metadata().vergen_cargo_features.as_ref(),
|
||||
git_sha: version_metadata().vergen_git_sha.as_ref(),
|
||||
target_triple: version_metadata().vergen_cargo_target_triple.as_ref(),
|
||||
build_profile: version_metadata().build_profile_name.as_ref(),
|
||||
},
|
||||
ChainSpecInfo { name: chain_name },
|
||||
executor,
|
||||
Hooks::builder().build(),
|
||||
pprof_dump_dir,
|
||||
);
|
||||
|
||||
// Spawn the metrics server
|
||||
if let Err(e) = MetricServer::new(config).serve().await {
|
||||
tracing::error!("Metrics server error: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
Some(handle)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if self.dry_run {
|
||||
verify_only(tool)?
|
||||
verify_only(provider_factory)?
|
||||
} else {
|
||||
verify_and_repair(tool)?
|
||||
verify_and_repair(provider_factory)?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_only<N: ProviderNodeTypes>(tool: &DbTool<N>) -> eyre::Result<()> {
|
||||
// Log the database block tip from Finish stage checkpoint
|
||||
let finish_checkpoint = tool
|
||||
.provider_factory
|
||||
.provider()?
|
||||
.get_stage_checkpoint(StageId::Finish)?
|
||||
.unwrap_or_default();
|
||||
info!("Database block tip: {}", finish_checkpoint.block_number);
|
||||
|
||||
fn verify_only<N: NodeTypesWithDB>(provider_factory: ProviderFactory<N>) -> eyre::Result<()> {
|
||||
// Get a database transaction directly from the database
|
||||
let db = tool.provider_factory.db_ref();
|
||||
let db = provider_factory.db_ref();
|
||||
let mut tx = db.tx()?;
|
||||
tx.disable_long_read_transaction_safety();
|
||||
|
||||
// Create the verifier
|
||||
let hashed_cursor_factory = DatabaseHashedCursorFactory::new(&tx);
|
||||
let trie_cursor_factory = DatabaseTrieCursorFactory::new(&tx);
|
||||
let verifier = Verifier::new(&trie_cursor_factory, hashed_cursor_factory)?;
|
||||
|
||||
let metrics = RepairTrieMetrics::new();
|
||||
let verifier = Verifier::new(trie_cursor_factory, hashed_cursor_factory)?;
|
||||
|
||||
let mut inconsistent_nodes = 0;
|
||||
let start_time = Instant::now();
|
||||
@@ -139,21 +70,6 @@ fn verify_only<N: ProviderNodeTypes>(tool: &DbTool<N>) -> eyre::Result<()> {
|
||||
} else {
|
||||
warn!("Inconsistency found: {output:?}");
|
||||
inconsistent_nodes += 1;
|
||||
|
||||
// Record metrics based on output type
|
||||
match output {
|
||||
Output::AccountExtra(_, _) |
|
||||
Output::AccountWrong { .. } |
|
||||
Output::AccountMissing(_, _) => {
|
||||
metrics.account_inconsistencies.increment(1);
|
||||
}
|
||||
Output::StorageExtra(_, _, _) |
|
||||
Output::StorageWrong { .. } |
|
||||
Output::StorageMissing(_, _, _) => {
|
||||
metrics.storage_inconsistencies.increment(1);
|
||||
}
|
||||
Output::Progress(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,33 +114,29 @@ fn verify_checkpoints(provider: impl StageCheckpointReader) -> eyre::Result<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_and_repair<N: ProviderNodeTypes>(tool: &DbTool<N>) -> eyre::Result<()> {
|
||||
fn verify_and_repair<N: ProviderNodeTypes>(
|
||||
provider_factory: ProviderFactory<N>,
|
||||
) -> eyre::Result<()> {
|
||||
// Get a read-write database provider
|
||||
let mut provider_rw = tool.provider_factory.provider_rw()?;
|
||||
|
||||
// Log the database block tip from Finish stage checkpoint
|
||||
let finish_checkpoint = provider_rw.get_stage_checkpoint(StageId::Finish)?.unwrap_or_default();
|
||||
info!("Database block tip: {}", finish_checkpoint.block_number);
|
||||
let mut provider_rw = provider_factory.provider_rw()?;
|
||||
|
||||
// Check that a pipeline sync isn't in progress.
|
||||
verify_checkpoints(provider_rw.as_ref())?;
|
||||
|
||||
// Create cursors for making modifications with
|
||||
let tx = provider_rw.tx_mut();
|
||||
tx.disable_long_read_transaction_safety();
|
||||
let mut account_trie_cursor = tx.cursor_write::<tables::AccountsTrie>()?;
|
||||
let mut storage_trie_cursor = tx.cursor_dup_write::<tables::StoragesTrie>()?;
|
||||
|
||||
// Create the cursor factories. These cannot accept the `&mut` tx above because they require it
|
||||
// to be AsRef.
|
||||
let tx = provider_rw.tx_ref();
|
||||
// Create the hashed cursor factory
|
||||
let hashed_cursor_factory = DatabaseHashedCursorFactory::new(tx);
|
||||
|
||||
// Create the trie cursor factory
|
||||
let trie_cursor_factory = DatabaseTrieCursorFactory::new(tx);
|
||||
|
||||
// Create the verifier
|
||||
let verifier = Verifier::new(&trie_cursor_factory, hashed_cursor_factory)?;
|
||||
let verifier = Verifier::new(trie_cursor_factory, hashed_cursor_factory)?;
|
||||
|
||||
let metrics = RepairTrieMetrics::new();
|
||||
let mut account_trie_cursor = tx.cursor_write::<tables::AccountsTrie>()?;
|
||||
let mut storage_trie_cursor = tx.cursor_dup_write::<tables::StoragesTrie>()?;
|
||||
|
||||
let mut inconsistent_nodes = 0;
|
||||
let start_time = Instant::now();
|
||||
@@ -237,21 +149,6 @@ fn verify_and_repair<N: ProviderNodeTypes>(tool: &DbTool<N>) -> eyre::Result<()>
|
||||
if !matches!(output, Output::Progress(_)) {
|
||||
warn!("Inconsistency found, will repair: {output:?}");
|
||||
inconsistent_nodes += 1;
|
||||
|
||||
// Record metrics based on output type
|
||||
match &output {
|
||||
Output::AccountExtra(_, _) |
|
||||
Output::AccountWrong { .. } |
|
||||
Output::AccountMissing(_, _) => {
|
||||
metrics.account_inconsistencies.increment(1);
|
||||
}
|
||||
Output::StorageExtra(_, _, _) |
|
||||
Output::StorageWrong { .. } |
|
||||
Output::StorageMissing(_, _, _) => {
|
||||
metrics.storage_inconsistencies.increment(1);
|
||||
}
|
||||
Output::Progress(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
match output {
|
||||
@@ -282,17 +179,8 @@ fn verify_and_repair<N: ProviderNodeTypes>(tool: &DbTool<N>) -> eyre::Result<()>
|
||||
Output::StorageWrong { account, path, expected: node, .. } |
|
||||
Output::StorageMissing(account, path, node) => {
|
||||
// Wrong/missing storage node value, upsert it
|
||||
// (We can't just use `upsert` method with a dup cursor, it's not properly
|
||||
// supported)
|
||||
let nibbles = StoredNibblesSubKey(path);
|
||||
let entry = StorageTrieEntry { nibbles: nibbles.clone(), node };
|
||||
if storage_trie_cursor
|
||||
.seek_by_key_subkey(account, nibbles.clone())?
|
||||
.filter(|v| v.nibbles == nibbles)
|
||||
.is_some()
|
||||
{
|
||||
storage_trie_cursor.delete_current()?;
|
||||
}
|
||||
let entry = StorageTrieEntry { nibbles, node };
|
||||
storage_trie_cursor.upsert(account, &entry)?;
|
||||
}
|
||||
Output::Progress(path) => {
|
||||
@@ -307,8 +195,8 @@ fn verify_and_repair<N: ProviderNodeTypes>(tool: &DbTool<N>) -> eyre::Result<()>
|
||||
if inconsistent_nodes == 0 {
|
||||
info!("No inconsistencies found");
|
||||
} else {
|
||||
info!("Repaired {} inconsistencies, committing changes", inconsistent_nodes);
|
||||
provider_rw.commit()?;
|
||||
info!("Repaired {} inconsistencies and committed changes", inconsistent_nodes);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -350,25 +238,3 @@ fn output_progress(last_account: Nibbles, start_time: Instant, inconsistent_node
|
||||
"Repairing trie tables",
|
||||
);
|
||||
}
|
||||
|
||||
/// Metrics for tracking trie repair inconsistencies
|
||||
#[derive(Debug)]
|
||||
struct RepairTrieMetrics {
|
||||
account_inconsistencies: Counter,
|
||||
storage_inconsistencies: Counter,
|
||||
}
|
||||
|
||||
impl RepairTrieMetrics {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
account_inconsistencies: metrics::counter!(
|
||||
"db.repair_trie.inconsistencies_found",
|
||||
"type" => "account"
|
||||
),
|
||||
storage_inconsistencies: metrics::counter!(
|
||||
"db.repair_trie.inconsistencies_found",
|
||||
"type" => "storage"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
//! `reth db settings` command for managing storage settings
|
||||
|
||||
use clap::{ArgAction, Parser, Subcommand};
|
||||
use reth_db_common::DbTool;
|
||||
use reth_provider::{
|
||||
providers::ProviderNodeTypes, DBProvider, DatabaseProviderFactory, MetadataProvider,
|
||||
MetadataWriter, StorageSettings,
|
||||
};
|
||||
|
||||
use crate::common::AccessRights;
|
||||
|
||||
/// `reth db settings` subcommand
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Command {
|
||||
#[command(subcommand)]
|
||||
command: Subcommands,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
/// Returns database access rights required for the command.
|
||||
pub fn access_rights(&self) -> AccessRights {
|
||||
match self.command {
|
||||
Subcommands::Get => AccessRights::RO,
|
||||
Subcommands::Set(_) => AccessRights::RW,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Subcommand)]
|
||||
enum Subcommands {
|
||||
/// Get current storage settings from database
|
||||
Get,
|
||||
/// Set storage settings in database
|
||||
#[clap(subcommand)]
|
||||
Set(SetCommand),
|
||||
}
|
||||
|
||||
/// Set storage settings
|
||||
#[derive(Debug, Clone, Copy, Subcommand)]
|
||||
#[clap(rename_all = "snake_case")]
|
||||
pub enum SetCommand {
|
||||
/// Store receipts in static files instead of the database
|
||||
Receipts {
|
||||
#[clap(action(ArgAction::Set))]
|
||||
value: bool,
|
||||
},
|
||||
/// Store transaction senders in static files instead of the database
|
||||
TransactionSenders {
|
||||
#[clap(action(ArgAction::Set))]
|
||||
value: bool,
|
||||
},
|
||||
/// Store account changesets in static files instead of the database
|
||||
AccountChangesets {
|
||||
#[clap(action(ArgAction::Set))]
|
||||
value: bool,
|
||||
},
|
||||
/// Store storages history in RocksDB instead of MDBX
|
||||
StoragesHistory {
|
||||
#[clap(action(ArgAction::Set))]
|
||||
value: bool,
|
||||
},
|
||||
/// Store account history in RocksDB instead of MDBX
|
||||
AccountHistory {
|
||||
#[clap(action(ArgAction::Set))]
|
||||
value: bool,
|
||||
},
|
||||
/// Store transaction hash numbers in RocksDB instead of MDBX
|
||||
TxHashNumbers {
|
||||
#[clap(action(ArgAction::Set))]
|
||||
value: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl Command {
|
||||
/// Execute the command
|
||||
pub fn execute<N: ProviderNodeTypes>(self, tool: &DbTool<N>) -> eyre::Result<()> {
|
||||
match self.command {
|
||||
Subcommands::Get => self.get(tool),
|
||||
Subcommands::Set(cmd) => self.set(cmd, tool),
|
||||
}
|
||||
}
|
||||
|
||||
fn get<N: ProviderNodeTypes>(&self, tool: &DbTool<N>) -> eyre::Result<()> {
|
||||
// Read storage settings
|
||||
let provider = tool.provider_factory.provider()?;
|
||||
let storage_settings = provider.storage_settings()?;
|
||||
|
||||
// Display settings
|
||||
match storage_settings {
|
||||
Some(settings) => {
|
||||
println!("Current storage settings:");
|
||||
println!("{settings:#?}");
|
||||
}
|
||||
None => {
|
||||
println!("No storage settings found.");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set<N: ProviderNodeTypes>(&self, cmd: SetCommand, tool: &DbTool<N>) -> eyre::Result<()> {
|
||||
// Read storage settings
|
||||
let provider_rw = tool.provider_factory.database_provider_rw()?;
|
||||
// Destruct settings struct to not miss adding support for new fields
|
||||
let settings = provider_rw.storage_settings()?;
|
||||
if settings.is_none() {
|
||||
println!("No storage settings found, creating new settings.");
|
||||
}
|
||||
|
||||
let mut settings @ StorageSettings {
|
||||
receipts_in_static_files: _,
|
||||
transaction_senders_in_static_files: _,
|
||||
storages_history_in_rocksdb: _,
|
||||
transaction_hash_numbers_in_rocksdb: _,
|
||||
account_history_in_rocksdb: _,
|
||||
account_changesets_in_static_files: _,
|
||||
} = settings.unwrap_or_else(StorageSettings::legacy);
|
||||
|
||||
// Update the setting based on the key
|
||||
match cmd {
|
||||
SetCommand::Receipts { value } => {
|
||||
if settings.receipts_in_static_files == value {
|
||||
println!("receipts_in_static_files is already set to {}", value);
|
||||
return Ok(());
|
||||
}
|
||||
settings.receipts_in_static_files = value;
|
||||
println!("Set receipts_in_static_files = {}", value);
|
||||
}
|
||||
SetCommand::TransactionSenders { value } => {
|
||||
if settings.transaction_senders_in_static_files == value {
|
||||
println!("transaction_senders_in_static_files is already set to {}", value);
|
||||
return Ok(());
|
||||
}
|
||||
settings.transaction_senders_in_static_files = value;
|
||||
println!("Set transaction_senders_in_static_files = {}", value);
|
||||
}
|
||||
SetCommand::AccountChangesets { value } => {
|
||||
if settings.account_changesets_in_static_files == value {
|
||||
println!("account_changesets_in_static_files is already set to {}", value);
|
||||
return Ok(());
|
||||
}
|
||||
settings.account_changesets_in_static_files = value;
|
||||
println!("Set account_changesets_in_static_files = {}", value);
|
||||
}
|
||||
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::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);
|
||||
}
|
||||
SetCommand::TxHashNumbers { 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Write updated settings
|
||||
provider_rw.write_storage_settings(settings)?;
|
||||
provider_rw.commit()?;
|
||||
|
||||
println!("Storage settings updated successfully.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
use reth_db_common::DbTool;
|
||||
use reth_provider::{providers::ProviderNodeTypes, StaticFileProviderFactory};
|
||||
use reth_static_file_types::StaticFileSegment;
|
||||
use std::path::PathBuf;
|
||||
use tracing::warn;
|
||||
|
||||
/// The arguments for the `reth db static-file-header` command
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct Command {
|
||||
#[command(subcommand)]
|
||||
source: Source,
|
||||
}
|
||||
|
||||
/// Source for locating the static file
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Source {
|
||||
/// Query by segment and block number
|
||||
Block {
|
||||
/// Static file segment
|
||||
#[arg(value_enum)]
|
||||
segment: StaticFileSegment,
|
||||
/// Block number to query
|
||||
block: u64,
|
||||
},
|
||||
/// Query by path to static file
|
||||
Path {
|
||||
/// Path to the static file
|
||||
path: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
impl Command {
|
||||
/// Execute `db static-file-header` command
|
||||
pub fn execute<N: ProviderNodeTypes>(self, tool: &DbTool<N>) -> eyre::Result<()> {
|
||||
let static_file_provider = tool.provider_factory.static_file_provider();
|
||||
if let Err(err) = static_file_provider.check_consistency(&tool.provider_factory.provider()?)
|
||||
{
|
||||
warn!("Error checking consistency of static files: {err}");
|
||||
}
|
||||
|
||||
// Get the provider based on the source
|
||||
let provider = match self.source {
|
||||
Source::Path { path } => {
|
||||
static_file_provider.get_segment_provider_for_path(&path)?.ok_or_else(|| {
|
||||
eyre::eyre!("Could not find static file segment for path: {}", path.display())
|
||||
})?
|
||||
}
|
||||
Source::Block { segment, block } => {
|
||||
static_file_provider.get_segment_provider(segment, block)?
|
||||
}
|
||||
};
|
||||
|
||||
let header = provider.user_header();
|
||||
|
||||
println!("Segment: {}", header.segment());
|
||||
println!("Expected Block Range: {}", header.expected_block_range());
|
||||
println!("Block Range: {:?}", header.block_range());
|
||||
println!("Transaction Range: {:?}", header.tx_range());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,6 @@ use std::{sync::Arc, time::Duration};
|
||||
#[derive(Parser, Debug)]
|
||||
/// The arguments for the `reth db stats` command
|
||||
pub struct Command {
|
||||
/// Skip consistency checks for static files.
|
||||
#[arg(long, default_value_t = false)]
|
||||
pub(crate) skip_consistency_checks: bool,
|
||||
|
||||
/// Show only the total size for static files.
|
||||
#[arg(long, default_value_t = false)]
|
||||
detailed_sizes: bool,
|
||||
@@ -195,11 +191,10 @@ impl Command {
|
||||
mut segment_config_size,
|
||||
) = (0, 0, 0, 0, 0, 0);
|
||||
|
||||
for (block_range, header) in &ranges {
|
||||
let fixed_block_range =
|
||||
static_file_provider.find_fixed_range(segment, block_range.start());
|
||||
for (block_range, tx_range) in &ranges {
|
||||
let fixed_block_range = static_file_provider.find_fixed_range(block_range.start());
|
||||
let jar_provider = static_file_provider
|
||||
.get_segment_provider_for_range(segment, || Some(fixed_block_range), None)?
|
||||
.get_segment_provider(segment, || Some(fixed_block_range), None)?
|
||||
.ok_or_else(|| {
|
||||
eyre::eyre!("Failed to get segment provider for segment: {}", segment)
|
||||
})?;
|
||||
@@ -225,7 +220,7 @@ impl Command {
|
||||
row.add_cell(Cell::new(segment))
|
||||
.add_cell(Cell::new(format!("{block_range}")))
|
||||
.add_cell(Cell::new(
|
||||
header.tx_range().map_or("N/A".to_string(), |range| format!("{range}")),
|
||||
tx_range.map_or("N/A".to_string(), |tx_range| format!("{tx_range}")),
|
||||
))
|
||||
.add_cell(Cell::new(format!("{columns} x {rows}")));
|
||||
if self.detailed_sizes {
|
||||
@@ -275,12 +270,10 @@ impl Command {
|
||||
let tx_range = {
|
||||
let start = ranges
|
||||
.iter()
|
||||
.find_map(|(_, header)| header.tx_range().map(|range| range.start()))
|
||||
.find_map(|(_, tx_range)| tx_range.map(|r| r.start()))
|
||||
.unwrap_or_default();
|
||||
let end = ranges
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|(_, header)| header.tx_range().map(|range| range.end()));
|
||||
let end =
|
||||
ranges.iter().rev().find_map(|(_, tx_range)| tx_range.map(|r| r.end()));
|
||||
end.map(|end| SegmentRangeInclusive::new(start, end))
|
||||
};
|
||||
|
||||
|
||||
@@ -7,16 +7,14 @@ use reth_chainspec::{EthChainSpec, EthereumHardforks};
|
||||
use reth_cli::chainspec::ChainSpecParser;
|
||||
use reth_fs_util as fs;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
io::{self, Read, Write},
|
||||
path::Path,
|
||||
sync::{Arc, OnceLock},
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
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"];
|
||||
@@ -24,109 +22,24 @@ const MERKLE_BASE_URL: &str = "https://downloads.merkle.io";
|
||||
const EXTENSION_TAR_LZ4: &str = ".tar.lz4";
|
||||
const EXTENSION_TAR_ZSTD: &str = ".tar.zst";
|
||||
|
||||
/// Global static download defaults
|
||||
static DOWNLOAD_DEFAULTS: OnceLock<DownloadDefaults> = OnceLock::new();
|
||||
|
||||
/// Download configuration defaults
|
||||
///
|
||||
/// Global defaults can be set via [`DownloadDefaults::try_init`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DownloadDefaults {
|
||||
/// List of available snapshot sources
|
||||
pub available_snapshots: Vec<Cow<'static, str>>,
|
||||
/// Default base URL for snapshots
|
||||
pub default_base_url: Cow<'static, str>,
|
||||
/// Optional custom long help text that overrides the generated help
|
||||
pub long_help: Option<String>,
|
||||
}
|
||||
|
||||
impl DownloadDefaults {
|
||||
/// Initialize the global download defaults with this configuration
|
||||
pub fn try_init(self) -> Result<(), Self> {
|
||||
DOWNLOAD_DEFAULTS.set(self)
|
||||
}
|
||||
|
||||
/// Get a reference to the global download defaults
|
||||
pub fn get_global() -> &'static DownloadDefaults {
|
||||
DOWNLOAD_DEFAULTS.get_or_init(DownloadDefaults::default_download_defaults)
|
||||
}
|
||||
|
||||
/// Default download configuration with defaults from merkle.io and publicnode
|
||||
pub fn default_download_defaults() -> Self {
|
||||
Self {
|
||||
available_snapshots: vec![
|
||||
Cow::Borrowed("https://www.merkle.io/snapshots (default, mainnet archive)"),
|
||||
Cow::Borrowed("https://publicnode.com/snapshots (full nodes & testnets)"),
|
||||
],
|
||||
default_base_url: Cow::Borrowed(MERKLE_BASE_URL),
|
||||
long_help: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the long help text for the download URL argument using these defaults.
|
||||
///
|
||||
/// If a custom long_help is set, it will be returned. Otherwise, help text is generated
|
||||
/// from the available_snapshots list.
|
||||
pub fn long_help(&self) -> String {
|
||||
if let Some(ref custom_help) = self.long_help {
|
||||
return custom_help.clone();
|
||||
}
|
||||
|
||||
let mut help = String::from(
|
||||
"Specify a snapshot URL or let the command propose a default one.\n\nAvailable snapshot sources:\n",
|
||||
);
|
||||
|
||||
for source in &self.available_snapshots {
|
||||
help.push_str("- ");
|
||||
help.push_str(source);
|
||||
help.push('\n');
|
||||
}
|
||||
|
||||
help.push_str(
|
||||
"\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
|
||||
}
|
||||
|
||||
/// Add a snapshot source to the list
|
||||
pub fn with_snapshot(mut self, source: impl Into<Cow<'static, str>>) -> Self {
|
||||
self.available_snapshots.push(source.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Replace all snapshot sources
|
||||
pub fn with_snapshots(mut self, sources: Vec<Cow<'static, str>>) -> Self {
|
||||
self.available_snapshots = sources;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default base URL, e.g. `https://downloads.merkle.io`.
|
||||
pub fn with_base_url(mut self, url: impl Into<Cow<'static, str>>) -> Self {
|
||||
self.default_base_url = url.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Builder: Set custom long help text, overriding the generated help
|
||||
pub fn with_long_help(mut self, help: impl Into<String>) -> Self {
|
||||
self.long_help = Some(help.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DownloadDefaults {
|
||||
fn default() -> Self {
|
||||
Self::default_download_defaults()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct DownloadCommand<C: ChainSpecParser> {
|
||||
#[command(flatten)]
|
||||
env: EnvironmentArgs<C>,
|
||||
|
||||
/// Custom URL to download the snapshot from
|
||||
#[arg(long, short, long_help = DownloadDefaults::get_global().long_help())]
|
||||
#[arg(
|
||||
long,
|
||||
short,
|
||||
help = "Custom URL to download the snapshot from",
|
||||
long_help = "Specify a snapshot URL or let the command propose a default one.\n\
|
||||
\n\
|
||||
Available snapshot sources:\n\
|
||||
- https://www.merkle.io/snapshots (default, mainnet archive)\n\
|
||||
- https://publicnode.com/snapshots (full nodes & testnets)\n\
|
||||
\n\
|
||||
If no URL is provided, the latest mainnet archive snapshot\n\
|
||||
will be proposed for download from merkle.io"
|
||||
)]
|
||||
url: Option<String>,
|
||||
}
|
||||
|
||||
@@ -171,14 +84,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)
|
||||
@@ -194,18 +105,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;
|
||||
@@ -216,24 +115,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();
|
||||
@@ -277,18 +160,12 @@ 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -330,10 +207,9 @@ async fn stream_and_extract(url: &str, target_dir: &Path) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Builds default URL for latest mainnet archive snapshot using configured defaults
|
||||
// Builds default URL for latest mainnet archive snapshot
|
||||
async fn get_latest_snapshot_url() -> Result<String> {
|
||||
let base_url = &DownloadDefaults::get_global().default_base_url;
|
||||
let latest_url = format!("{base_url}/latest.txt");
|
||||
let latest_url = format!("{MERKLE_BASE_URL}/latest.txt");
|
||||
let filename = Client::new()
|
||||
.get(latest_url)
|
||||
.send()
|
||||
@@ -344,64 +220,5 @@ async fn get_latest_snapshot_url() -> Result<String> {
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
Ok(format!("{base_url}/{filename}"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_download_defaults_builder() {
|
||||
let defaults = DownloadDefaults::default()
|
||||
.with_snapshot("https://example.com/snapshots (example)")
|
||||
.with_base_url("https://example.com");
|
||||
|
||||
assert_eq!(defaults.default_base_url, "https://example.com");
|
||||
assert_eq!(defaults.available_snapshots.len(), 3); // 2 defaults + 1 added
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_download_defaults_replace_snapshots() {
|
||||
let defaults = DownloadDefaults::default().with_snapshots(vec![
|
||||
Cow::Borrowed("https://custom1.com"),
|
||||
Cow::Borrowed("https://custom2.com"),
|
||||
]);
|
||||
|
||||
assert_eq!(defaults.available_snapshots.len(), 2);
|
||||
assert_eq!(defaults.available_snapshots[0], "https://custom1.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_long_help_generation() {
|
||||
let defaults = DownloadDefaults::default();
|
||||
let help = defaults.long_help();
|
||||
|
||||
assert!(help.contains("Available snapshot sources:"));
|
||||
assert!(help.contains("merkle.io"));
|
||||
assert!(help.contains("publicnode.com"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_long_help_override() {
|
||||
let custom_help = "This is custom help text for downloading snapshots.";
|
||||
let defaults = DownloadDefaults::default().with_long_help(custom_help);
|
||||
|
||||
let help = defaults.long_help();
|
||||
assert_eq!(help, custom_help);
|
||||
assert!(!help.contains("Available snapshot sources:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builder_chaining() {
|
||||
let defaults = DownloadDefaults::default()
|
||||
.with_base_url("https://custom.example.com")
|
||||
.with_snapshot("https://snapshot1.com")
|
||||
.with_snapshot("https://snapshot2.com")
|
||||
.with_long_help("Custom help for snapshots");
|
||||
|
||||
assert_eq!(defaults.default_base_url, "https://custom.example.com");
|
||||
assert_eq!(defaults.available_snapshots.len(), 4); // 2 defaults + 2 added
|
||||
assert_eq!(defaults.long_help, Some("Custom help for snapshots".to_string()));
|
||||
}
|
||||
Ok(format!("{MERKLE_BASE_URL}/{filename}"))
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ pub struct DumpGenesisCommand<C: ChainSpecParser> {
|
||||
long,
|
||||
value_name = "CHAIN_OR_PATH",
|
||||
long_help = C::help_message(),
|
||||
default_value = C::default_value(),
|
||||
default_value = C::SUPPORTED_CHAINS[0],
|
||||
value_parser = C::parser()
|
||||
)]
|
||||
chain: Arc<C::ChainSpec>,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user