mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-04-30 03:01:58 -04:00
Compare commits
46 Commits
devnet4
...
glamsterda
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fda7636c92 | ||
|
|
82f36c066d | ||
|
|
768977e4cf | ||
|
|
4a8a14e91a | ||
|
|
ad08829288 | ||
|
|
b89288582b | ||
|
|
87d878a979 | ||
|
|
473f85c558 | ||
|
|
a8eee6028f | ||
|
|
2fce3e701d | ||
|
|
671da55884 | ||
|
|
938313028d | ||
|
|
a8fc13dc25 | ||
|
|
4c1f6b6507 | ||
|
|
b850f2a81d | ||
|
|
0790359003 | ||
|
|
79144cb430 | ||
|
|
089c0e2629 | ||
|
|
b04346ffe5 | ||
|
|
674623f14e | ||
|
|
bc80e2a66b | ||
|
|
9af8265047 | ||
|
|
97b1b56b2d | ||
|
|
f3e30a3111 | ||
|
|
6715a093f1 | ||
|
|
d5bff1d478 | ||
|
|
af6d20b5ea | ||
|
|
64cf412aaf | ||
|
|
5b10e03c5c | ||
|
|
20141a2ea0 | ||
|
|
57962a1b95 | ||
|
|
60468fe256 | ||
|
|
ce3a171ce0 | ||
|
|
ce7e80ad33 | ||
|
|
0c6a10d3fa | ||
|
|
91d248e6fa | ||
|
|
d41a9a4078 | ||
|
|
b984ddd275 | ||
|
|
344037d04e | ||
|
|
aca6261107 | ||
|
|
b9c330e1a9 | ||
|
|
d4ca2e2687 | ||
|
|
e5e0abb47e | ||
|
|
225e3ae238 | ||
|
|
345fbbbfdb | ||
|
|
db17c899c3 |
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -4,10 +4,14 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
labels:
|
||||
- "A-dependencies"
|
||||
commit-message:
|
||||
|
||||
19
.github/scripts/bench-reth-run.sh
vendored
19
.github/scripts/bench-reth-run.sh
vendored
@@ -323,13 +323,18 @@ if [ "$BIG_BLOCKS" = "true" ]; then
|
||||
--output "$OUTPUT_DIR" 2>&1 | sed -u "s/^/[bench] /"
|
||||
else
|
||||
# Standard mode: warmup + new-payload-fcu
|
||||
# Warmup
|
||||
$BENCH_NICE "$RETH_BENCH" new-payload-fcu \
|
||||
--rpc-url "$BENCH_RPC_URL" \
|
||||
--engine-rpc-url http://127.0.0.1:8551 \
|
||||
--jwt-secret "$DATADIR/jwt.hex" \
|
||||
--advance "${BENCH_WARMUP_BLOCKS:-50}" \
|
||||
"${EXTRA_BENCH_ARGS[@]}" 2>&1 | sed -u "s/^/[bench] /"
|
||||
WARMUP="${BENCH_WARMUP_BLOCKS:-50}"
|
||||
if [ "$WARMUP" -gt 0 ] 2>/dev/null; then
|
||||
# Warm up the node before measuring the benchmark window.
|
||||
$BENCH_NICE "$RETH_BENCH" new-payload-fcu \
|
||||
--rpc-url "$BENCH_RPC_URL" \
|
||||
--engine-rpc-url http://127.0.0.1:8551 \
|
||||
--jwt-secret "$DATADIR/jwt.hex" \
|
||||
--advance "$WARMUP" \
|
||||
"${EXTRA_BENCH_ARGS[@]}" 2>&1 | sed -u "s/^/[bench] /"
|
||||
else
|
||||
echo "Skipping warmup (0 blocks)..."
|
||||
fi
|
||||
|
||||
# Start tracy-capture after warmup so profile only covers the benchmark
|
||||
if [ "${BENCH_TRACY:-off}" != "off" ]; then
|
||||
|
||||
25
.github/scripts/hive/build_simulators.sh
vendored
25
.github/scripts/hive/build_simulators.sh
vendored
@@ -1,6 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
|
||||
fixture_variant="${1:-osaka}"
|
||||
|
||||
case "${fixture_variant}" in
|
||||
amsterdam)
|
||||
eels_fixtures="https://github.com/ethereum/execution-spec-tests/releases/download/bal@v6.0.0/fixtures_bal.tar.gz"
|
||||
eels_branch="devnets/snøbal/4"
|
||||
;;
|
||||
osaka)
|
||||
eels_fixtures="https://github.com/ethereum/execution-spec-tests/releases/download/v5.3.0/fixtures_develop.tar.gz"
|
||||
eels_branch="forks/osaka"
|
||||
;;
|
||||
*)
|
||||
echo "unknown hive fixture variant: ${fixture_variant}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Create the hive_assets directory
|
||||
mkdir hive_assets/
|
||||
|
||||
@@ -12,12 +29,12 @@ go build .
|
||||
# Run each hive command in the background for each simulator and wait
|
||||
echo "Building images"
|
||||
./hive -client reth --sim "ethereum/eels/consume-engine" \
|
||||
--sim.buildarg fixtures=https://github.com/ethereum/execution-spec-tests/releases/download/v5.3.0/fixtures_develop.tar.gz \
|
||||
--sim.buildarg branch=forks/osaka \
|
||||
--sim.buildarg fixtures="${eels_fixtures}" \
|
||||
--sim.buildarg branch="${eels_branch}" \
|
||||
--sim.timelimit 1s || true &
|
||||
./hive -client reth --sim "ethereum/eels/consume-rlp" \
|
||||
--sim.buildarg fixtures=https://github.com/ethereum/execution-spec-tests/releases/download/v5.3.0/fixtures_develop.tar.gz \
|
||||
--sim.buildarg branch=forks/osaka \
|
||||
--sim.buildarg fixtures="${eels_fixtures}" \
|
||||
--sim.buildarg branch="${eels_branch}" \
|
||||
--sim.timelimit 1s || true &
|
||||
./hive -client reth --sim "ethereum/engine" -sim.timelimit 1s || true &
|
||||
./hive -client reth --sim "devp2p" -sim.timelimit 1s || true &
|
||||
|
||||
78
.github/scripts/hive/expected_failures.yaml
vendored
78
.github/scripts/hive/expected_failures.yaml
vendored
@@ -13,15 +13,15 @@ rpc-compat:
|
||||
# syncing mode, the test expects syncing to be false on start
|
||||
- eth_syncing/check-syncing (reth)
|
||||
|
||||
engine-withdrawals: [ ]
|
||||
engine-withdrawals: []
|
||||
|
||||
engine-api: [ ]
|
||||
engine-api: []
|
||||
|
||||
engine-cancun: [ ]
|
||||
engine-cancun: []
|
||||
|
||||
sync: [ ]
|
||||
sync: []
|
||||
|
||||
engine-auth: [ ]
|
||||
engine-auth: []
|
||||
|
||||
# EIP-7610 related tests (Revert creation in case of non-empty storage):
|
||||
#
|
||||
@@ -99,6 +99,40 @@ eels/consume-engine:
|
||||
- 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
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Amsterdam-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Amsterdam-blockchain_test_engine_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Amsterdam-blockchain_test_engine_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Amsterdam-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Amsterdam-blockchain_test_engine_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Amsterdam-blockchain_test_engine_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-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_Amsterdam-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_Amsterdam-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_Amsterdam-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_opcode[fork_Amsterdam-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_Amsterdam-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_Amsterdam-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_opcode[fork_Amsterdam-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Prague-blockchain_test_engine_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Prague-blockchain_test_engine_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Prague-blockchain_test_engine_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Shanghai-blockchain_test_engine_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Paris-blockchain_test_engine_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Shanghai-blockchain_test_engine_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Cancun-blockchain_test_engine_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Shanghai-blockchain_test_engine_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Paris-blockchain_test_engine_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Cancun-blockchain_test_engine_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Shanghai-blockchain_test_engine_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Osaka-blockchain_test_engine_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Paris-blockchain_test_engine_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Prague-blockchain_test_engine_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Osaka-blockchain_test_engine_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Cancun-blockchain_test_engine_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Paris-blockchain_test_engine_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Osaka-blockchain_test_engine_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Osaka-blockchain_test_engine_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Cancun-blockchain_test_engine_from_state_test-empty-initcode]-reth
|
||||
|
||||
# Blob limit tests:
|
||||
#
|
||||
@@ -193,3 +227,37 @@ eels/consume-rlp:
|
||||
- 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
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Amsterdam-blockchain_test_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Amsterdam-blockchain_test_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-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_Amsterdam-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_Amsterdam-tx_type_1-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Amsterdam-blockchain_test_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-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_Amsterdam-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_Amsterdam-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_Amsterdam-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_Amsterdam-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_Amsterdam-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_Amsterdam-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Amsterdam-blockchain_test_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Prague-blockchain_test_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Prague-blockchain_test_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Prague-blockchain_test_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Shanghai-blockchain_test_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Cancun-blockchain_test_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Paris-blockchain_test_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Shanghai-blockchain_test_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Shanghai-blockchain_test_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Paris-blockchain_test_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Cancun-blockchain_test_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Shanghai-blockchain_test_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Cancun-blockchain_test_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Osaka-blockchain_test_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Paris-blockchain_test_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Osaka-blockchain_test_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Prague-blockchain_test_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Cancun-blockchain_test_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Paris-blockchain_test_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Osaka-blockchain_test_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Osaka-blockchain_test_from_state_test-initcode-with-deploy]-reth
|
||||
|
||||
6
.github/scripts/hive/run_simulator.sh
vendored
6
.github/scripts/hive/run_simulator.sh
vendored
@@ -5,6 +5,12 @@ cd hivetests/
|
||||
|
||||
sim="${1}"
|
||||
limit="${2}"
|
||||
fixture_variant="${3:-}"
|
||||
|
||||
if [[ "${fixture_variant}" == "osaka" && "${sim}" == *"eels"* && "${limit}" == *"tests/amsterdam"* ]]; then
|
||||
echo "osaka fixtures do not support amsterdam tests"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Use lower parallelism for eels tests to avoid OOM-killing the runner
|
||||
parallelism=16
|
||||
|
||||
54
.github/workflows/bench-scheduled.yml
vendored
54
.github/workflows/bench-scheduled.yml
vendored
@@ -54,9 +54,7 @@ env:
|
||||
|
||||
name: bench-scheduled
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -65,6 +63,9 @@ jobs:
|
||||
resolve-refs:
|
||||
name: resolve-refs
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
outputs:
|
||||
mode: ${{ steps.mode.outputs.mode }}
|
||||
baseline-ref: ${{ steps.refs.outputs.baseline-ref }}
|
||||
@@ -76,21 +77,26 @@ jobs:
|
||||
long-running: ${{ steps.refs.outputs.long-running }}
|
||||
release-tag: ${{ steps.refs.outputs.release-tag }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
sparse-checkout: .github/scripts
|
||||
sparse-checkout-cone-mode: true
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Detect mode
|
||||
id: mode
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
INPUT_MODE: ${{ inputs.mode }}
|
||||
SCHEDULE: ${{ github.event.schedule }}
|
||||
run: |
|
||||
# Maps cron schedules to modes (must match the schedule entries above)
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
MODE="${{ inputs.mode || 'nightly' }}"
|
||||
elif [ "${{ github.event.schedule }}" = "30 5 * * *" ]; then
|
||||
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
|
||||
MODE="${INPUT_MODE:-nightly}"
|
||||
elif [ "$SCHEDULE" = "30 5 * * *" ]; then
|
||||
MODE="nightly"
|
||||
elif [ "${{ github.event.schedule }}" = "0 9 * * *" ]; then
|
||||
elif [ "$SCHEDULE" = "0 9 * * *" ]; then
|
||||
MODE="release"
|
||||
else
|
||||
MODE="hourly"
|
||||
@@ -105,14 +111,15 @@ jobs:
|
||||
DEREK_TOKEN: ${{ secrets.DEREK_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
INPUT_FORCE: ${{ inputs.force || 'false' }}
|
||||
run: |
|
||||
FORCE="${{ inputs.force || 'false' }}"
|
||||
FORCE="${INPUT_FORCE:-false}"
|
||||
MODE="${{ steps.mode.outputs.mode }}"
|
||||
.github/scripts/bench-scheduled-refs.sh "$FORCE" "$MODE"
|
||||
|
||||
- name: Alert on long-running hourly
|
||||
if: steps.mode.outputs.mode == 'hourly' && steps.refs.outputs.long-running == 'true' && !(github.event_name == 'workflow_dispatch' && inputs.slack == 'never')
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
SLACK_BENCH_BOT_TOKEN: ${{ secrets.SLACK_BENCH_BOT_TOKEN }}
|
||||
SLACK_BENCH_CHANNEL: ${{ secrets.SLACK_BENCH_CHANNEL }}
|
||||
@@ -154,7 +161,7 @@ jobs:
|
||||
|
||||
- name: Alert on stale nightly
|
||||
if: steps.mode.outputs.mode == 'nightly' && steps.refs.outputs.is-stale == 'true' && !(github.event_name == 'workflow_dispatch' && inputs.slack == 'never')
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
SLACK_BENCH_BOT_TOKEN: ${{ secrets.SLACK_BENCH_BOT_TOKEN }}
|
||||
SLACK_BENCH_CHANNEL: ${{ secrets.SLACK_BENCH_CHANNEL }}
|
||||
@@ -242,6 +249,9 @@ jobs:
|
||||
needs.resolve-refs.outputs.is-stale != 'true'
|
||||
name: bench-scheduled
|
||||
runs-on: [self-hosted, Linux, X64, available]
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
timeout-minutes: 120
|
||||
env:
|
||||
BENCH_RPC_URL: https://ethereum.reth.rs/rpc
|
||||
@@ -270,15 +280,16 @@ jobs:
|
||||
- name: Clean up previous bench-work
|
||||
run: sudo rm -rf "$BENCH_WORK_DIR" 2>/dev/null || true
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
ref: ${{ needs.resolve-refs.outputs.feature-ref }}
|
||||
|
||||
- name: Resolve job URL
|
||||
id: job-url
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const { data: jobs } = await github.rest.actions.listJobsForWorkflowRun({
|
||||
@@ -291,8 +302,9 @@ jobs:
|
||||
core.exportVariable('BENCH_JOB_URL', jobUrl);
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
continue-on-error: true
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- name: Install dependencies
|
||||
env:
|
||||
@@ -628,7 +640,7 @@ jobs:
|
||||
|
||||
- name: Upload results
|
||||
if: "!cancelled()"
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: bench-scheduled-results
|
||||
path: ${{ env.BENCH_WORK_DIR }}
|
||||
@@ -636,10 +648,12 @@ jobs:
|
||||
- name: Push charts
|
||||
id: push-charts
|
||||
if: success() && env.BENCH_MODE != 'hourly'
|
||||
env:
|
||||
DEREK_TOKEN: ${{ secrets.DEREK_TOKEN }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
run: |
|
||||
RUN_ID=${{ github.run_id }}
|
||||
CHART_DIR="${BENCH_MODE}/${RUN_ID}"
|
||||
CHARTS_REPO="https://x-access-token:${{ secrets.DEREK_TOKEN }}@github.com/decofe/reth-bench-charts.git"
|
||||
CHARTS_REPO="https://x-access-token:${DEREK_TOKEN}@github.com/decofe/reth-bench-charts.git"
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
if git clone --depth 1 "${CHARTS_REPO}" "${TMP_DIR}" 2>/dev/null; then
|
||||
@@ -660,7 +674,7 @@ jobs:
|
||||
|
||||
- name: Write job summary
|
||||
if: success()
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
@@ -739,7 +753,7 @@ jobs:
|
||||
|
||||
- name: Send Slack notification (success)
|
||||
if: success() && (env.BENCH_SLACK == 'always' || env.BENCH_SLACK == 'on-win')
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
SLACK_BENCH_BOT_TOKEN: ${{ secrets.SLACK_BENCH_BOT_TOKEN }}
|
||||
SLACK_BENCH_CHANNEL: ${{ secrets.SLACK_BENCH_CHANNEL }}
|
||||
@@ -894,7 +908,7 @@ jobs:
|
||||
|
||||
- name: Send Slack notification (failure)
|
||||
if: failure() && env.BENCH_SLACK != 'never' && env.BENCH_SLACK != 'on-win'
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
SLACK_BENCH_BOT_TOKEN: ${{ secrets.SLACK_BENCH_BOT_TOKEN }}
|
||||
SLACK_BENCH_CHANNEL: ${{ secrets.SLACK_BENCH_CHANNEL }}
|
||||
|
||||
130
.github/workflows/bench.yml
vendored
130
.github/workflows/bench.yml
vendored
@@ -82,7 +82,7 @@ on:
|
||||
- on-error
|
||||
- never
|
||||
abba:
|
||||
description: "Run ABBA (BFFB) interleaved order; false = single AB pass"
|
||||
description: "Run ABBA (FBBF) interleaved order; false = single FB pass"
|
||||
required: false
|
||||
default: "true"
|
||||
type: boolean
|
||||
@@ -99,9 +99,7 @@ env:
|
||||
|
||||
name: bench
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
reth-bench-ack:
|
||||
@@ -110,6 +108,9 @@ jobs:
|
||||
github.event_name == 'workflow_dispatch'
|
||||
name: reth-bench-ack
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
outputs:
|
||||
pr: ${{ steps.args.outputs.pr }}
|
||||
actor: ${{ steps.args.outputs.actor }}
|
||||
@@ -133,7 +134,7 @@ jobs:
|
||||
steps:
|
||||
- name: Check org membership
|
||||
if: github.event_name == 'issue_comment'
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
@@ -152,7 +153,7 @@ jobs:
|
||||
|
||||
- name: Parse arguments
|
||||
id: args
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEREK_PAT }}
|
||||
script: |
|
||||
@@ -359,7 +360,7 @@ jobs:
|
||||
|
||||
- name: Acknowledge request
|
||||
id: ack
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEREK_PAT }}
|
||||
script: |
|
||||
@@ -445,7 +446,7 @@ jobs:
|
||||
|
||||
- name: Poll queue position
|
||||
if: steps.ack.outputs.comment-id && steps.ack.outputs.queue-position != '0'
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEREK_PAT }}
|
||||
script: |
|
||||
@@ -529,6 +530,9 @@ jobs:
|
||||
needs: reth-bench-ack
|
||||
name: reth-bench
|
||||
runs-on: [self-hosted, Linux, X64, available]
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
timeout-minutes: 120
|
||||
env:
|
||||
BENCH_RPC_URL: https://ethereum.reth.rs/rpc
|
||||
@@ -560,7 +564,7 @@ jobs:
|
||||
|
||||
- name: Resolve checkout ref
|
||||
id: checkout-ref
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
if (!process.env.BENCH_PR) {
|
||||
@@ -578,15 +582,16 @@ jobs:
|
||||
core.info(`PR #${process.env.BENCH_PR} (${pr.state}), using head SHA ${pr.head.sha}`);
|
||||
core.setOutput('ref', pr.head.sha);
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
ref: ${{ steps.checkout-ref.outputs.ref }}
|
||||
|
||||
- name: Resolve job URL and update status
|
||||
if: env.BENCH_COMMENT_ID
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEREK_PAT }}
|
||||
script: |
|
||||
@@ -634,8 +639,9 @@ jobs:
|
||||
});
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
continue-on-error: true
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- name: Install dependencies
|
||||
env:
|
||||
@@ -696,7 +702,7 @@ jobs:
|
||||
# Build binaries
|
||||
- name: Resolve PR head branch
|
||||
id: pr-info
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
if (process.env.BENCH_PR) {
|
||||
@@ -714,7 +720,7 @@ jobs:
|
||||
|
||||
- name: Resolve refs
|
||||
id: refs
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const { execSync } = require('child_process');
|
||||
@@ -937,28 +943,15 @@ jobs:
|
||||
|
||||
- name: Update status (running benchmarks)
|
||||
if: success() && env.BENCH_COMMENT_ID
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEREK_PAT }}
|
||||
script: |
|
||||
const s = require('./.github/scripts/bench-update-status.js');
|
||||
await s({github, context, status: 'Running benchmarks...'});
|
||||
|
||||
# Interleaved run order (B-F-F-B) to reduce systematic bias from
|
||||
# Interleaved run order (F-B-B-F) to reduce systematic bias from
|
||||
# thermal drift and cache warming.
|
||||
- name: "Run benchmark: baseline (1/2)"
|
||||
id: run-baseline-1
|
||||
env:
|
||||
BASELINE_REF: ${{ steps.refs.outputs.baseline-ref }}
|
||||
OTEL_RESOURCE_ATTRIBUTES: "benchmark_id=${{ env.BENCH_ID }},benchmark_run=baseline-1,run_type=baseline,git_ref=${{ steps.refs.outputs.baseline-ref }}"
|
||||
run: |
|
||||
LAST_RUN_START=$(date +%s)
|
||||
echo "BENCH_LAST_RUN_START=${LAST_RUN_START}" >> "$GITHUB_ENV"
|
||||
cat > "$BENCH_LABELS_FILE" <<LABELS
|
||||
{"benchmark_run":"baseline-1","run_type":"baseline","git_ref":"${BASELINE_REF}","bench_sha":"${BASELINE_REF}","benchmark_id":"${BENCH_ID}","run_start_epoch":"${LAST_RUN_START}","reference_epoch":"${BENCH_REFERENCE_EPOCH}"}
|
||||
LABELS
|
||||
taskset -c 0 .github/scripts/bench-reth-run.sh baseline "../reth-baseline/target/profiling/${BENCH_NODE_BIN}" "$BENCH_WORK_DIR/baseline-1"
|
||||
|
||||
- name: "Run benchmark: feature (1/2)"
|
||||
id: run-feature-1
|
||||
env:
|
||||
@@ -972,19 +965,18 @@ jobs:
|
||||
LABELS
|
||||
taskset -c 0 .github/scripts/bench-reth-run.sh feature "../reth-feature/target/profiling/${BENCH_NODE_BIN}" "$BENCH_WORK_DIR/feature-1"
|
||||
|
||||
- name: "Run benchmark: feature (2/2)"
|
||||
if: env.BENCH_ABBA != 'false'
|
||||
id: run-feature-2
|
||||
- name: "Run benchmark: baseline (1/2)"
|
||||
id: run-baseline-1
|
||||
env:
|
||||
FEATURE_REF: ${{ steps.refs.outputs.feature-ref }}
|
||||
OTEL_RESOURCE_ATTRIBUTES: "benchmark_id=${{ env.BENCH_ID }},benchmark_run=feature-2,run_type=feature,git_ref=${{ steps.refs.outputs.feature-ref }}"
|
||||
BASELINE_REF: ${{ steps.refs.outputs.baseline-ref }}
|
||||
OTEL_RESOURCE_ATTRIBUTES: "benchmark_id=${{ env.BENCH_ID }},benchmark_run=baseline-1,run_type=baseline,git_ref=${{ steps.refs.outputs.baseline-ref }}"
|
||||
run: |
|
||||
LAST_RUN_START=$(date +%s)
|
||||
echo "BENCH_LAST_RUN_START=${LAST_RUN_START}" >> "$GITHUB_ENV"
|
||||
cat > "$BENCH_LABELS_FILE" <<LABELS
|
||||
{"benchmark_run":"feature-2","run_type":"feature","git_ref":"${FEATURE_REF}","bench_sha":"${FEATURE_REF}","benchmark_id":"${BENCH_ID}","run_start_epoch":"${LAST_RUN_START}","reference_epoch":"${BENCH_REFERENCE_EPOCH}"}
|
||||
{"benchmark_run":"baseline-1","run_type":"baseline","git_ref":"${BASELINE_REF}","bench_sha":"${BASELINE_REF}","benchmark_id":"${BENCH_ID}","run_start_epoch":"${LAST_RUN_START}","reference_epoch":"${BENCH_REFERENCE_EPOCH}"}
|
||||
LABELS
|
||||
taskset -c 0 .github/scripts/bench-reth-run.sh feature "../reth-feature/target/profiling/${BENCH_NODE_BIN}" "$BENCH_WORK_DIR/feature-2"
|
||||
taskset -c 0 .github/scripts/bench-reth-run.sh baseline "../reth-baseline/target/profiling/${BENCH_NODE_BIN}" "$BENCH_WORK_DIR/baseline-1"
|
||||
|
||||
- name: "Run benchmark: baseline (2/2)"
|
||||
if: env.BENCH_ABBA != 'false'
|
||||
@@ -1000,6 +992,20 @@ jobs:
|
||||
LABELS
|
||||
taskset -c 0 .github/scripts/bench-reth-run.sh baseline "../reth-baseline/target/profiling/${BENCH_NODE_BIN}" "$BENCH_WORK_DIR/baseline-2"
|
||||
|
||||
- name: "Run benchmark: feature (2/2)"
|
||||
if: env.BENCH_ABBA != 'false'
|
||||
id: run-feature-2
|
||||
env:
|
||||
FEATURE_REF: ${{ steps.refs.outputs.feature-ref }}
|
||||
OTEL_RESOURCE_ATTRIBUTES: "benchmark_id=${{ env.BENCH_ID }},benchmark_run=feature-2,run_type=feature,git_ref=${{ steps.refs.outputs.feature-ref }}"
|
||||
run: |
|
||||
LAST_RUN_START=$(date +%s)
|
||||
echo "BENCH_LAST_RUN_START=${LAST_RUN_START}" >> "$GITHUB_ENV"
|
||||
cat > "$BENCH_LABELS_FILE" <<LABELS
|
||||
{"benchmark_run":"feature-2","run_type":"feature","git_ref":"${FEATURE_REF}","bench_sha":"${FEATURE_REF}","benchmark_id":"${BENCH_ID}","run_start_epoch":"${LAST_RUN_START}","reference_epoch":"${BENCH_REFERENCE_EPOCH}"}
|
||||
LABELS
|
||||
taskset -c 0 .github/scripts/bench-reth-run.sh feature "../reth-feature/target/profiling/${BENCH_NODE_BIN}" "$BENCH_WORK_DIR/feature-2"
|
||||
|
||||
- name: Stop metrics proxy & generate Grafana URL
|
||||
id: metrics
|
||||
if: "!cancelled()"
|
||||
@@ -1166,7 +1172,7 @@ jobs:
|
||||
|
||||
- name: Upload results
|
||||
if: "!cancelled()"
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: bench-reth-results
|
||||
path: ${{ env.BENCH_WORK_DIR }}
|
||||
@@ -1174,11 +1180,13 @@ jobs:
|
||||
- name: Push charts
|
||||
id: push-charts
|
||||
if: success()
|
||||
env:
|
||||
DEREK_TOKEN: ${{ secrets.DEREK_TOKEN }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
run: |
|
||||
PR_NUMBER="${BENCH_PR:-0}"
|
||||
RUN_ID=${{ github.run_id }}
|
||||
CHART_DIR="pr/${PR_NUMBER}/${RUN_ID}"
|
||||
CHARTS_REPO="https://x-access-token:${{ secrets.DEREK_TOKEN }}@github.com/decofe/reth-bench-charts.git"
|
||||
CHARTS_REPO="https://x-access-token:${DEREK_TOKEN}@github.com/decofe/reth-bench-charts.git"
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
if git clone --depth 1 "${CHARTS_REPO}" "${TMP_DIR}" 2>/dev/null; then
|
||||
@@ -1191,15 +1199,35 @@ jobs:
|
||||
mkdir -p "${TMP_DIR}/${CHART_DIR}"
|
||||
cp "$BENCH_WORK_DIR"/charts/*.png "${TMP_DIR}/${CHART_DIR}/"
|
||||
git -C "${TMP_DIR}" add "${CHART_DIR}"
|
||||
if git -C "${TMP_DIR}" diff --cached --quiet; then
|
||||
echo "Charts for ${CHART_DIR} are already present, skipping push"
|
||||
echo "sha=$(git -C "${TMP_DIR}" rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
rm -rf "${TMP_DIR}"
|
||||
exit 0
|
||||
fi
|
||||
git -C "${TMP_DIR}" -c user.name="github-actions" -c user.email="github-actions@github.com" \
|
||||
commit -m "bench charts for PR #${PR_NUMBER} run ${RUN_ID}"
|
||||
git -C "${TMP_DIR}" push origin HEAD:main
|
||||
|
||||
for attempt in 1 2 3 4 5; do
|
||||
if git -C "${TMP_DIR}" push origin HEAD:main; then
|
||||
break
|
||||
fi
|
||||
if [ "$attempt" -eq 5 ]; then
|
||||
echo "::error::Failed to push charts after ${attempt} attempts"
|
||||
rm -rf "${TMP_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
sleep "$attempt"
|
||||
git -C "${TMP_DIR}" fetch origin main
|
||||
git -C "${TMP_DIR}" rebase origin/main
|
||||
done
|
||||
|
||||
echo "sha=$(git -C "${TMP_DIR}" rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
rm -rf "${TMP_DIR}"
|
||||
|
||||
- name: Compare & comment
|
||||
if: success() && env.BENCH_COMMENT_ID
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEREK_PAT }}
|
||||
script: |
|
||||
@@ -1235,7 +1263,7 @@ jobs:
|
||||
// Samply profile links (URLs point directly to Firefox Profiler)
|
||||
if (process.env.BENCH_SAMPLY === 'true') {
|
||||
const abba = (process.env.BENCH_ABBA || 'true') !== 'false';
|
||||
const runs = abba ? ['baseline-1', 'feature-1', 'feature-2', 'baseline-2'] : ['baseline-1', 'feature-1'];
|
||||
const runs = abba ? ['feature-1', 'baseline-1', 'baseline-2', 'feature-2'] : ['feature-1', 'baseline-1'];
|
||||
const links = [];
|
||||
for (const run of runs) {
|
||||
try {
|
||||
@@ -1279,7 +1307,7 @@ jobs:
|
||||
|
||||
- name: Write job summary
|
||||
if: success()
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const jobSummary = require('./.github/scripts/bench-job-summary.js');
|
||||
@@ -1293,7 +1321,7 @@ jobs:
|
||||
|
||||
- name: Send Slack notification (success)
|
||||
if: success() && (env.BENCH_SLACK == 'always' || env.BENCH_SLACK == 'on-win')
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
SLACK_BENCH_BOT_TOKEN: ${{ secrets.SLACK_BENCH_BOT_TOKEN }}
|
||||
SLACK_BENCH_CHANNEL: ${{ secrets.SLACK_BENCH_CHANNEL }}
|
||||
@@ -1304,7 +1332,7 @@ jobs:
|
||||
|
||||
- name: Update status (failed)
|
||||
if: failure() && env.BENCH_COMMENT_ID
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEREK_PAT }}
|
||||
script: |
|
||||
@@ -1314,10 +1342,10 @@ jobs:
|
||||
...(bigBlocks ? [['validating local big-block data', '${{ steps.big-blocks-check.outcome }}']] : []),
|
||||
['validating local snapshot', '${{ steps.snapshot-check.outcome }}'],
|
||||
['building binaries', '${{ steps.build.outcome }}'],
|
||||
['running baseline benchmark (1/2)', '${{ steps.run-baseline-1.outcome }}'],
|
||||
['running feature benchmark (1/2)', '${{ steps.run-feature-1.outcome }}'],
|
||||
...(abba ? [['running feature benchmark (2/2)', '${{ steps.run-feature-2.outcome }}']] : []),
|
||||
['running baseline benchmark (1/2)', '${{ steps.run-baseline-1.outcome }}'],
|
||||
...(abba ? [['running baseline benchmark (2/2)', '${{ steps.run-baseline-2.outcome }}']] : []),
|
||||
...(abba ? [['running feature benchmark (2/2)', '${{ steps.run-feature-2.outcome }}']] : []),
|
||||
];
|
||||
const failed = steps_status.find(([, o]) => o === 'failure');
|
||||
const failedStep = failed ? failed[0] : 'unknown step';
|
||||
@@ -1340,7 +1368,7 @@ jobs:
|
||||
|
||||
- name: Send Slack notification (failure)
|
||||
if: failure() && env.BENCH_SLACK != 'never' && env.BENCH_SLACK != 'on-win'
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
SLACK_BENCH_BOT_TOKEN: ${{ secrets.SLACK_BENCH_BOT_TOKEN }}
|
||||
SLACK_BENCH_CHANNEL: ${{ secrets.SLACK_BENCH_CHANNEL }}
|
||||
@@ -1352,10 +1380,10 @@ jobs:
|
||||
...(bigBlocks ? [['validating local big-block data', '${{ steps.big-blocks-check.outcome }}']] : []),
|
||||
['validating local snapshot', '${{ steps.snapshot-check.outcome }}'],
|
||||
['building binaries', '${{ steps.build.outcome }}'],
|
||||
['running baseline benchmark (1/2)', '${{ steps.run-baseline-1.outcome }}'],
|
||||
['running feature benchmark (1/2)', '${{ steps.run-feature-1.outcome }}'],
|
||||
...(abba ? [['running feature benchmark (2/2)', '${{ steps.run-feature-2.outcome }}']] : []),
|
||||
['running baseline benchmark (1/2)', '${{ steps.run-baseline-1.outcome }}'],
|
||||
...(abba ? [['running baseline benchmark (2/2)', '${{ steps.run-baseline-2.outcome }}']] : []),
|
||||
...(abba ? [['running feature benchmark (2/2)', '${{ steps.run-feature-2.outcome }}']] : []),
|
||||
];
|
||||
const failed = steps_status.find(([, o]) => o === 'failure');
|
||||
const failedStep = failed ? failed[0] : 'unknown step';
|
||||
@@ -1364,7 +1392,7 @@ jobs:
|
||||
|
||||
- name: Update status (cancelled)
|
||||
if: cancelled() && env.BENCH_COMMENT_ID
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEREK_PAT }}
|
||||
script: |
|
||||
|
||||
19
.github/workflows/book.yml
vendored
19
.github/workflows/book.yml
vendored
@@ -10,19 +10,22 @@ on:
|
||||
types: [opened, reopened, synchronize, closed]
|
||||
merge_group:
|
||||
|
||||
env:
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-8' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
||||
with:
|
||||
bun-version: v1.2.23
|
||||
|
||||
@@ -36,8 +39,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
|
||||
|
||||
@@ -47,10 +48,10 @@ jobs:
|
||||
echo "Vocs Build Complete"
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v6
|
||||
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v5
|
||||
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
|
||||
with:
|
||||
path: "./docs/vocs/docs/dist"
|
||||
|
||||
@@ -74,4 +75,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v5
|
||||
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|
||||
|
||||
26
.github/workflows/check-alloy.yml
vendored
26
.github/workflows/check-alloy.yml
vendored
@@ -22,31 +22,41 @@ on:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check compilation with patched alloy
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-16' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
|
||||
- name: Apply alloy patches
|
||||
env:
|
||||
ALLOY_BRANCH: ${{ inputs.alloy_branch }}
|
||||
ALLOY_EVM_BRANCH: ${{ inputs.alloy_evm_branch }}
|
||||
OP_ALLOY_BRANCH: ${{ inputs.op_alloy_branch }}
|
||||
run: |
|
||||
ARGS=""
|
||||
if [ -n "${{ inputs.alloy_branch }}" ]; then
|
||||
ARGS="$ARGS --alloy ${{ inputs.alloy_branch }}"
|
||||
if [ -n "$ALLOY_BRANCH" ]; then
|
||||
ARGS="$ARGS --alloy $ALLOY_BRANCH"
|
||||
fi
|
||||
if [ -n "${{ inputs.alloy_evm_branch }}" ]; then
|
||||
ARGS="$ARGS --evm ${{ inputs.alloy_evm_branch }}"
|
||||
if [ -n "$ALLOY_EVM_BRANCH" ]; then
|
||||
ARGS="$ARGS --evm $ALLOY_EVM_BRANCH"
|
||||
fi
|
||||
if [ -n "${{ inputs.op_alloy_branch }}" ]; then
|
||||
ARGS="$ARGS --op ${{ inputs.op_alloy_branch }}"
|
||||
if [ -n "$OP_ALLOY_BRANCH" ]; then
|
||||
ARGS="$ARGS --op $OP_ALLOY_BRANCH"
|
||||
fi
|
||||
|
||||
if [ -z "$ARGS" ]; then
|
||||
|
||||
16
.github/workflows/compact.yml
vendored
16
.github/workflows/compact.yml
vendored
@@ -16,32 +16,38 @@ env:
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
name: compact-codec
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
compact-codec:
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
matrix:
|
||||
bin:
|
||||
- cargo run --bin reth --features "dev"
|
||||
steps:
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Checkout base
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.base_ref || 'main' }}
|
||||
persist-credentials: false
|
||||
# On `main` branch, generates test vectors and serializes them to disk using `Compact`.
|
||||
- name: Generate compact vectors
|
||||
run: |
|
||||
${{ matrix.bin }} -- test-vectors compact --write
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
clean: false
|
||||
persist-credentials: false
|
||||
# On incoming merge try to read and decode previously generated vectors with `Compact`
|
||||
- name: Read vectors
|
||||
run: ${{ matrix.bin }} -- test-vectors compact --read
|
||||
|
||||
9
.github/workflows/dependencies.yml
vendored
9
.github/workflows/dependencies.yml
vendored
@@ -9,13 +9,14 @@ on:
|
||||
workflow_dispatch:
|
||||
# Needed so we can run it manually
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
update:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
uses: tempoxyz/ci/.github/workflows/cargo-update-pr.yml@main
|
||||
secrets:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
14
.github/workflows/docker-tag-latest.yml
vendored
14
.github/workflows/docker-tag-latest.yml
vendored
@@ -17,6 +17,8 @@ on:
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ github.actor }}
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
tag-reth-latest:
|
||||
name: Tag reth as latest
|
||||
@@ -27,16 +29,22 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Log in to Docker
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username ${DOCKER_USERNAME} --password-stdin
|
||||
echo "$DOCKER_PASSWORD" | docker login ghcr.io --username "${DOCKER_USERNAME}" --password-stdin
|
||||
|
||||
- name: Pull reth release image
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
docker pull ghcr.io/${{ github.repository_owner }}/reth:${{ inputs.version }}
|
||||
docker pull ghcr.io/${{ github.repository_owner }}/reth:${VERSION}
|
||||
|
||||
- name: Tag reth as latest
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
docker tag ghcr.io/${{ github.repository_owner }}/reth:${{ inputs.version }} ghcr.io/${{ github.repository_owner }}/reth:latest
|
||||
docker tag ghcr.io/${{ github.repository_owner }}/reth:${VERSION} ghcr.io/${{ github.repository_owner }}/reth:latest
|
||||
|
||||
- name: Push reth latest tag
|
||||
run: |
|
||||
|
||||
22
.github/workflows/docker-test.yml
vendored
22
.github/workflows/docker-test.yml
vendored
@@ -13,6 +13,8 @@ on:
|
||||
default: "artifacts"
|
||||
description: "Name for the uploaded artifact"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 45
|
||||
@@ -21,7 +23,9 @@ jobs:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- run: mkdir -p artifacts
|
||||
|
||||
- name: Get git info
|
||||
@@ -32,8 +36,12 @@ jobs:
|
||||
|
||||
- name: Detect fork
|
||||
id: fork
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "pull_request" ] && [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then
|
||||
if [ "$EVENT_NAME" = "pull_request" ] && [ "$HEAD_REPO" != "$REPO" ]; then
|
||||
echo "is_fork=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "is_fork=false" >> "$GITHUB_OUTPUT"
|
||||
@@ -42,11 +50,11 @@ jobs:
|
||||
# Depot build (upstream only)
|
||||
- name: Set up Depot CLI
|
||||
if: steps.fork.outputs.is_fork == 'false'
|
||||
uses: depot/setup-action@v1
|
||||
uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.7.1
|
||||
|
||||
- name: Build reth image (Depot)
|
||||
if: steps.fork.outputs.is_fork == 'false'
|
||||
uses: depot/bake-action@v1
|
||||
uses: depot/bake-action@1d58c2668346981089b088b7ef36755b206b20e9 # v1.13.0
|
||||
env:
|
||||
DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }}
|
||||
VERGEN_GIT_SHA: ${{ steps.git.outputs.sha }}
|
||||
@@ -60,11 +68,11 @@ jobs:
|
||||
# Docker build (forks)
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.fork.outputs.is_fork == 'true'
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Build reth image (Docker)
|
||||
if: steps.fork.outputs.is_fork == 'true'
|
||||
uses: docker/bake-action@v6
|
||||
uses: docker/bake-action@82490499d2e5613fcead7e128237ef0b0ea210f7 # v7.0.0
|
||||
env:
|
||||
VERGEN_GIT_SHA: ${{ steps.git.outputs.sha }}
|
||||
VERGEN_GIT_DESCRIBE: ${{ steps.git.outputs.describe }}
|
||||
@@ -76,7 +84,7 @@ jobs:
|
||||
*.dockerfile=Dockerfile
|
||||
|
||||
- name: Upload reth image
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: ${{ inputs.artifact_name }}
|
||||
path: ./artifacts
|
||||
|
||||
23
.github/workflows/docker.yml
vendored
23
.github/workflows/docker.yml
vendored
@@ -29,6 +29,8 @@ on:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
@@ -39,13 +41,15 @@ jobs:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Depot CLI
|
||||
uses: depot/setup-action@v1
|
||||
uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.7.1
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -60,10 +64,13 @@ jobs:
|
||||
|
||||
- name: Determine build parameters
|
||||
id: params
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
BUILD_TYPE: ${{ inputs.build_type }}
|
||||
run: |
|
||||
REGISTRY="ghcr.io/${{ github.repository_owner }}"
|
||||
|
||||
if [[ "${{ github.event_name }}" == "push" ]]; then
|
||||
if [[ "${EVENT_NAME}" == "push" ]]; then
|
||||
VERSION="${GITHUB_REF#refs/tags/}"
|
||||
echo "targets=ethereum" >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -81,7 +88,7 @@ jobs:
|
||||
echo "ethereum_set=ethereum.tags=${REGISTRY}/reth:${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
elif [[ "${{ github.event_name }}" == "schedule" ]] || [[ "${{ inputs.build_type }}" == "nightly" ]]; then
|
||||
elif [[ "${EVENT_NAME}" == "schedule" ]] || [[ "${BUILD_TYPE}" == "nightly" ]]; then
|
||||
echo "targets=nightly" >> "$GITHUB_OUTPUT"
|
||||
echo "ethereum_tags=${REGISTRY}/reth:nightly" >> "$GITHUB_OUTPUT"
|
||||
echo "ethereum_set=ethereum.tags=${REGISTRY}/reth:nightly" >> "$GITHUB_OUTPUT"
|
||||
@@ -94,7 +101,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Build and push images
|
||||
uses: depot/bake-action@v1
|
||||
uses: depot/bake-action@1d58c2668346981089b088b7ef36755b206b20e9 # v1.13.0
|
||||
env:
|
||||
VERGEN_GIT_SHA: ${{ steps.git.outputs.sha }}
|
||||
VERGEN_GIT_DESCRIBE: ${{ steps.git.outputs.describe }}
|
||||
@@ -105,6 +112,8 @@ jobs:
|
||||
files: docker-bake.hcl
|
||||
targets: ${{ steps.params.outputs.targets }}
|
||||
push: ${{ !(github.event_name == 'workflow_dispatch' && inputs.dry_run) }}
|
||||
save: false
|
||||
load: false
|
||||
set: |
|
||||
${{ steps.params.outputs.ethereum_set }}
|
||||
|
||||
@@ -124,7 +133,7 @@ jobs:
|
||||
if: failure() && github.event_name == 'schedule'
|
||||
steps:
|
||||
- name: Slack Webhook Action
|
||||
uses: rtCamp/action-slack-notify@v2
|
||||
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
|
||||
env:
|
||||
SLACK_COLOR: danger
|
||||
SLACK_ICON_EMOJI: ":rotating_light:"
|
||||
|
||||
30
.github/workflows/e2e.yml
vendored
30
.github/workflows/e2e.yml
vendored
@@ -17,19 +17,27 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: e2e-testsuite
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-4' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93 # v2.75.24
|
||||
with:
|
||||
tool: nextest
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Run e2e tests
|
||||
@@ -48,15 +56,21 @@ jobs:
|
||||
rocksdb:
|
||||
name: e2e-rocksdb
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-4' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93 # v2.75.24
|
||||
with:
|
||||
tool: nextest
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Run RocksDB e2e tests
|
||||
|
||||
24
.github/workflows/fetch-grafana-dashboard.yml
vendored
24
.github/workflows/fetch-grafana-dashboard.yml
vendored
@@ -12,6 +12,8 @@ on:
|
||||
required: true
|
||||
default: "etc/grafana/dashboards/overview.json"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
fetch:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -19,9 +21,11 @@ jobs:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
@@ -29,14 +33,18 @@ jobs:
|
||||
env:
|
||||
FETCH_GRAFANA_DASHBOARD_URL: ${{ secrets.FETCH_GRAFANA_DASHBOARD_URL }}
|
||||
FETCH_GRAFANA_DASHBOARD_TOKEN: ${{ secrets.FETCH_GRAFANA_DASHBOARD_TOKEN }}
|
||||
DASHBOARD_UID: ${{ inputs.dashboard_uid }}
|
||||
TARGET_PATH: ${{ inputs.target_path }}
|
||||
run: |
|
||||
python3 .github/scripts/fetch-grafana-dashboard.py "${{ inputs.dashboard_uid }}" \
|
||||
> "${{ inputs.target_path }}"
|
||||
python3 .github/scripts/fetch-grafana-dashboard.py "${DASHBOARD_UID}" \
|
||||
> "${TARGET_PATH}"
|
||||
|
||||
- name: Check for changes
|
||||
id: diff
|
||||
env:
|
||||
TARGET_PATH: ${{ inputs.target_path }}
|
||||
run: |
|
||||
if git diff --quiet "${{ inputs.target_path }}"; then
|
||||
if git diff --quiet "${TARGET_PATH}"; then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
echo "No changes detected."
|
||||
else
|
||||
@@ -47,8 +55,10 @@ jobs:
|
||||
if: steps.diff.outputs.changed == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DASHBOARD_UID: ${{ inputs.dashboard_uid }}
|
||||
TARGET_PATH: ${{ inputs.target_path }}
|
||||
run: |
|
||||
TARGET="${{ inputs.target_path }}"
|
||||
TARGET="${TARGET_PATH}"
|
||||
FILENAME="$(basename "$TARGET")"
|
||||
BRANCH="chore/sync-grafana-${FILENAME%.*}-$(date +%Y%m%d-%H%M%S)"
|
||||
git config user.name "github-actions[bot]"
|
||||
@@ -59,4 +69,4 @@ jobs:
|
||||
git push origin "$BRANCH"
|
||||
gh pr create \
|
||||
--title "chore: update Grafana dashboard ${FILENAME}" \
|
||||
--body "Automated export from Grafana (dashboard UID: \`${{ inputs.dashboard_uid }}\`, target: \`${TARGET}\`)."
|
||||
--body "Automated export from Grafana (dashboard UID: \`${DASHBOARD_UID}\`, target: \`${TARGET}\`)."
|
||||
|
||||
8
.github/workflows/grafana.yml
vendored
8
.github/workflows/grafana.yml
vendored
@@ -6,11 +6,17 @@ on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check-dashboard:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Validate dashboard format
|
||||
run: |
|
||||
python3 -c "
|
||||
|
||||
259
.github/workflows/hive.yml
vendored
259
.github/workflows/hive.yml
vendored
@@ -6,6 +6,9 @@ on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
pull_request:
|
||||
branches:
|
||||
- "**"
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -14,8 +17,13 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build-reth:
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
uses: ./.github/workflows/docker-test.yml
|
||||
with:
|
||||
hive_target: hive
|
||||
@@ -23,36 +31,48 @@ jobs:
|
||||
secrets: inherit
|
||||
|
||||
prepare-hive:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
if: github.repository == 'paradigmxyz/reth-oss' || github.repository == 'paradigmxyz/reth'
|
||||
timeout-minutes: 45
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-4' || 'ubuntu-latest' }}
|
||||
runs-on: ${{ (github.repository == 'paradigmxyz/reth-oss' || github.repository == 'paradigmxyz/reth') && 'depot-ubuntu-latest-16' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
variant:
|
||||
- amsterdam
|
||||
- osaka
|
||||
name: Prepare Hive - ${{ matrix.variant == 'amsterdam' && 'Amsterdam' || 'Osaka' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Checkout hive tests
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ethereum/hive
|
||||
path: hivetests
|
||||
persist-credentials: false
|
||||
|
||||
- name: Get hive commit hash
|
||||
id: hive-commit
|
||||
run: echo "hash=$(cd hivetests && git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: "^1.13.1"
|
||||
- run: go version
|
||||
|
||||
- name: Restore hive assets cache
|
||||
id: cache-hive
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ./hive_assets
|
||||
key: hive-assets-${{ steps.hive-commit.outputs.hash }}-${{ hashFiles('.github/scripts/hive/build_simulators.sh') }}
|
||||
key: hive-assets-${{ matrix.variant }}-${{ steps.hive-commit.outputs.hash }}-${{ hashFiles('.github/scripts/hive/build_simulators.sh') }}
|
||||
|
||||
- name: Build hive assets
|
||||
if: steps.cache-hive.outputs.cache-hit != 'true'
|
||||
run: .github/scripts/hive/build_simulators.sh
|
||||
run: .github/scripts/hive/build_simulators.sh ${{ matrix.variant }}
|
||||
|
||||
- name: Load cached Docker images
|
||||
if: steps.cache-hive.outputs.cache-hit == 'true'
|
||||
@@ -68,11 +88,195 @@ jobs:
|
||||
chmod +x hive
|
||||
|
||||
- name: Upload hive assets
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: hive_assets
|
||||
name: hive_assets_${{ matrix.variant }}
|
||||
path: ./hive_assets
|
||||
test:
|
||||
test-amsterdam:
|
||||
timeout-minutes: 120
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# ethereum/rpc to be deprecated:
|
||||
# https://github.com/ethereum/hive/pull/1117
|
||||
scenario:
|
||||
- sim: smoke/genesis
|
||||
- sim: smoke/network
|
||||
- sim: ethereum/sync
|
||||
- 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
|
||||
# - sim: devp2p
|
||||
# limit: eth
|
||||
# include:
|
||||
# - MaliciousHandshake
|
||||
# # failures tracked in https://github.com/paradigmxyz/reth/issues/14825
|
||||
# - Status
|
||||
# - GetBlockHeaders
|
||||
# - ZeroRequestID
|
||||
# - GetBlockBodies
|
||||
# - Transaction
|
||||
# - NewPooledTxs
|
||||
- sim: devp2p
|
||||
limit: discv5
|
||||
include:
|
||||
# failures tracked at https://github.com/paradigmxyz/reth/issues/14825
|
||||
- PingLargeRequestID
|
||||
- sim: ethereum/engine
|
||||
limit: engine-exchange-capabilities
|
||||
- sim: ethereum/engine
|
||||
limit: engine-withdrawals
|
||||
- sim: ethereum/engine
|
||||
limit: engine-auth
|
||||
- sim: ethereum/engine
|
||||
limit: engine-api
|
||||
- sim: ethereum/engine
|
||||
limit: cancun
|
||||
# eth_ rpc methods
|
||||
- sim: ethereum/rpc-compat
|
||||
include:
|
||||
- eth_blockNumber
|
||||
- eth_call
|
||||
- eth_chainId
|
||||
- eth_createAccessList
|
||||
- eth_estimateGas
|
||||
- eth_feeHistory
|
||||
- eth_getBalance
|
||||
- eth_getBlockBy
|
||||
- eth_getBlockTransactionCountBy
|
||||
- eth_getCode
|
||||
- eth_getProof
|
||||
- eth_getStorage
|
||||
- eth_getTransactionBy
|
||||
- eth_getTransactionCount
|
||||
- eth_getTransactionReceipt
|
||||
- eth_sendRawTransaction
|
||||
- eth_syncing
|
||||
# debug_ rpc methods
|
||||
- debug_
|
||||
|
||||
# consume-engine
|
||||
- sim: ethereum/eels/consume-engine
|
||||
limit: .*tests/amsterdam.*
|
||||
- sim: ethereum/eels/consume-engine
|
||||
limit: .*tests/osaka.*
|
||||
- sim: ethereum/eels/consume-engine
|
||||
limit: .*tests/prague.*
|
||||
- sim: ethereum/eels/consume-engine
|
||||
limit: .*tests/cancun.*
|
||||
- sim: ethereum/eels/consume-engine
|
||||
limit: .*tests/shanghai.*
|
||||
- sim: ethereum/eels/consume-engine
|
||||
limit: .*tests/berlin.*
|
||||
- sim: ethereum/eels/consume-engine
|
||||
limit: .*tests/istanbul.*
|
||||
- sim: ethereum/eels/consume-engine
|
||||
limit: .*tests/homestead.*
|
||||
- sim: ethereum/eels/consume-engine
|
||||
limit: .*tests/frontier.*
|
||||
- sim: ethereum/eels/consume-engine
|
||||
limit: .*tests/paris.*
|
||||
|
||||
# consume-rlp
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
limit: .*tests/amsterdam.*
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
limit: .*tests/osaka.*
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
limit: .*tests/prague.*
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
limit: .*tests/cancun.*
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
limit: .*tests/shanghai.*
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
limit: .*tests/berlin.*
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
limit: .*tests/istanbul.*
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
limit: .*tests/homestead.*
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
limit: .*tests/frontier.*
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
limit: .*tests/paris.*
|
||||
needs:
|
||||
- build-reth
|
||||
- prepare-hive
|
||||
name: Hive-Amsterdam / ${{ matrix.scenario.sim }}${{ matrix.scenario.limit && format(' - {0}', matrix.scenario.limit) }}
|
||||
# Use larger runners for eels tests to avoid OOM runner crashes
|
||||
runs-on: ${{ (github.repository == 'paradigmxyz/reth-oss' || github.repository == 'paradigmxyz/reth') && (contains(matrix.scenario.sim, 'eels') && 'depot-ubuntu-latest-8' || 'depot-ubuntu-latest-4') || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download hive assets
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: hive_assets_amsterdam
|
||||
path: /tmp
|
||||
|
||||
- name: Download reth image
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: reth
|
||||
path: /tmp
|
||||
|
||||
- name: Load Docker images
|
||||
run: .github/scripts/hive/load_images.sh
|
||||
|
||||
- name: Move hive binary
|
||||
run: |
|
||||
mv /tmp/hive /usr/local/bin
|
||||
chmod +x /usr/local/bin/hive
|
||||
|
||||
- name: Checkout hive tests
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ethereum/hive
|
||||
ref: master
|
||||
path: hivetests
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run simulator
|
||||
env:
|
||||
SCENARIO_SIM: ${{ matrix.scenario.sim }}
|
||||
SCENARIO_LIMIT: ${{ matrix.scenario.limit }}
|
||||
SCENARIO_TESTS: ${{ join(matrix.scenario.include, '|') }}
|
||||
run: |
|
||||
LIMIT="$SCENARIO_LIMIT"
|
||||
TESTS="$SCENARIO_TESTS"
|
||||
if [ -n "$LIMIT" ] && [ -n "$TESTS" ]; then
|
||||
FILTER="$LIMIT/$TESTS"
|
||||
elif [ -n "$LIMIT" ]; then
|
||||
FILTER="$LIMIT"
|
||||
elif [ -n "$TESTS" ]; then
|
||||
FILTER="/$TESTS"
|
||||
else
|
||||
FILTER="/"
|
||||
fi
|
||||
echo "filter: $FILTER"
|
||||
.github/scripts/hive/run_simulator.sh "$SCENARIO_SIM" "$FILTER" "amsterdam"
|
||||
|
||||
- name: Parse hive output
|
||||
run: |
|
||||
find hivetests/workspace/logs -type f -name "*.json" ! -name "hive.json" | xargs -I {} python .github/scripts/hive/parse.py {} --exclusion .github/scripts/hive/expected_failures.yaml --ignored .github/scripts/hive/ignored_tests.yaml
|
||||
|
||||
- name: Print simulator output
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
cat hivetests/workspace/logs/*simulator*.log
|
||||
|
||||
- name: Print reth client logs
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
cat hivetests/workspace/logs/reth/client-*.log
|
||||
|
||||
test-osaka:
|
||||
timeout-minutes: 120
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -178,24 +382,26 @@ jobs:
|
||||
needs:
|
||||
- build-reth
|
||||
- prepare-hive
|
||||
name: ${{ matrix.scenario.sim }}${{ matrix.scenario.limit && format(' - {0}', matrix.scenario.limit) }}
|
||||
name: Hive-Osaka / ${{ matrix.scenario.sim }}${{ matrix.scenario.limit && format(' - {0}', matrix.scenario.limit) }}
|
||||
# Use larger runners for eels tests to avoid OOM runner crashes
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && (contains(matrix.scenario.sim, 'eels') && 'depot-ubuntu-latest-8' || 'depot-ubuntu-latest-4') || 'ubuntu-latest' }}
|
||||
runs-on: ${{ (github.repository == 'paradigmxyz/reth-oss' || github.repository == 'paradigmxyz/reth') && (contains(matrix.scenario.sim, 'eels') && 'depot-ubuntu-latest-8' || 'depot-ubuntu-latest-4') || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download hive assets
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: hive_assets
|
||||
name: hive_assets_osaka
|
||||
path: /tmp
|
||||
|
||||
- name: Download reth image
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: reth
|
||||
path: /tmp
|
||||
@@ -209,16 +415,21 @@ jobs:
|
||||
chmod +x /usr/local/bin/hive
|
||||
|
||||
- name: Checkout hive tests
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ethereum/hive
|
||||
ref: master
|
||||
path: hivetests
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run simulator
|
||||
env:
|
||||
SCENARIO_SIM: ${{ matrix.scenario.sim }}
|
||||
SCENARIO_LIMIT: ${{ matrix.scenario.limit }}
|
||||
SCENARIO_TESTS: ${{ join(matrix.scenario.include, '|') }}
|
||||
run: |
|
||||
LIMIT="${{ matrix.scenario.limit }}"
|
||||
TESTS="${{ join(matrix.scenario.include, '|') }}"
|
||||
LIMIT="$SCENARIO_LIMIT"
|
||||
TESTS="$SCENARIO_TESTS"
|
||||
if [ -n "$LIMIT" ] && [ -n "$TESTS" ]; then
|
||||
FILTER="$LIMIT/$TESTS"
|
||||
elif [ -n "$LIMIT" ]; then
|
||||
@@ -229,7 +440,7 @@ jobs:
|
||||
FILTER="/"
|
||||
fi
|
||||
echo "filter: $FILTER"
|
||||
.github/scripts/hive/run_simulator.sh "${{ matrix.scenario.sim }}" "$FILTER"
|
||||
.github/scripts/hive/run_simulator.sh "$SCENARIO_SIM" "$FILTER" "osaka"
|
||||
|
||||
- name: Parse hive output
|
||||
run: |
|
||||
@@ -245,12 +456,14 @@ jobs:
|
||||
run: |
|
||||
cat hivetests/workspace/logs/reth/client-*.log
|
||||
notify-on-error:
|
||||
needs: test
|
||||
needs:
|
||||
- test-amsterdam
|
||||
- test-osaka
|
||||
if: failure()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Slack Webhook Action
|
||||
uses: rtCamp/action-slack-notify@v2
|
||||
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
|
||||
env:
|
||||
SLACK_COLOR: ${{ job.status }}
|
||||
SLACK_MESSAGE: "Failed run: https://github.com/paradigmxyz/reth/actions/runs/${{ github.run_id }}"
|
||||
|
||||
36
.github/workflows/integration.yml
vendored
36
.github/workflows/integration.yml
vendored
@@ -20,11 +20,15 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: test / ${{ matrix.network }}
|
||||
if: github.event_name != 'schedule'
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-4' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
strategy:
|
||||
@@ -32,14 +36,18 @@ jobs:
|
||||
network: ["ethereum"]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Install Geth
|
||||
run: .github/scripts/install_geth.sh
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93 # v2.75.24
|
||||
with:
|
||||
tool: nextest
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Run tests
|
||||
@@ -58,7 +66,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Decide whether the needed jobs succeeded or failed
|
||||
uses: re-actors/alls-green@release/v1
|
||||
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
|
||||
with:
|
||||
jobs: ${{ toJSON(needs) }}
|
||||
|
||||
@@ -66,13 +74,19 @@ jobs:
|
||||
name: era1 file integration tests once a day
|
||||
if: github.event_name == 'schedule' && github.repository == 'paradigmxyz/reth'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # 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
|
||||
- uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93 # v2.75.24
|
||||
with:
|
||||
tool: nextest
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: run era1 files integration tests
|
||||
|
||||
16
.github/workflows/kurtosis.yml
vendored
16
.github/workflows/kurtosis.yml
vendored
@@ -18,9 +18,14 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build-reth:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
uses: ./.github/workflows/docker-test.yml
|
||||
with:
|
||||
hive_target: kurtosis
|
||||
@@ -32,15 +37,18 @@ jobs:
|
||||
fail-fast: false
|
||||
name: run kurtosis
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
needs:
|
||||
- build-reth
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download reth image
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: artifacts
|
||||
path: /tmp
|
||||
@@ -52,7 +60,7 @@ jobs:
|
||||
docker image ls -a
|
||||
|
||||
- name: Run kurtosis
|
||||
uses: ethpandaops/kurtosis-assertoor-github-action@v1
|
||||
uses: ethpandaops/kurtosis-assertoor-github-action@f64942cbc780df731a731ea9f45765b161d2c8df # v1.0.1
|
||||
with:
|
||||
ethereum_package_args: ".github/assets/kurtosis_network_params.yaml"
|
||||
|
||||
@@ -62,7 +70,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Slack Webhook Action
|
||||
uses: rtCamp/action-slack-notify@v2
|
||||
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
|
||||
env:
|
||||
SLACK_COLOR: ${{ job.status }}
|
||||
SLACK_MESSAGE: "Failed run: https://github.com/paradigmxyz/reth/actions/runs/${{ github.run_id }}"
|
||||
|
||||
8
.github/workflows/label-pr.yml
vendored
8
.github/workflows/label-pr.yml
vendored
@@ -4,19 +4,23 @@ on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
label_prs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Label PRs
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const label_pr = require('./.github/scripts/label_pr.js')
|
||||
|
||||
8
.github/workflows/lint-actions.yml
vendored
8
.github/workflows/lint-actions.yml
vendored
@@ -8,11 +8,17 @@ on:
|
||||
paths:
|
||||
- '.github/**'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
actionlint:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Download actionlint
|
||||
id: get_actionlint
|
||||
run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
|
||||
|
||||
190
.github/workflows/lint.yml
vendored
190
.github/workflows/lint.yml
vendored
@@ -10,10 +10,14 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
clippy-binaries:
|
||||
name: clippy binaries / ${{ matrix.type }}
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -22,17 +26,19 @@ 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: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@clippy
|
||||
with:
|
||||
components: clippy
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- if: "${{ matrix.type == 'book' }}"
|
||||
uses: arduino/setup-protoc@v3
|
||||
uses: arduino/setup-protoc@c65c819552d16ad3c9b72d9dfd5ba5237b9c906b # v3.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Run clippy on binaries
|
||||
@@ -43,15 +49,19 @@ jobs:
|
||||
clippy:
|
||||
name: clippy
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: clippy
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- run: cargo clippy --workspace --lib --examples --tests --benches --all-features --locked
|
||||
@@ -60,19 +70,25 @@ jobs:
|
||||
|
||||
wasm:
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # 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
|
||||
- uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93 # v2.75.24
|
||||
with:
|
||||
tool: cargo-hack
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- uses: dcarbone/install-jq-action@v3
|
||||
- uses: dcarbone/install-jq-action@b7ef57d46ece78760b4019dbc4080a1ba2a40b45 # v3.2.0
|
||||
- name: Run Wasm checks
|
||||
run: |
|
||||
sudo apt update && sudo apt install gcc-multilib
|
||||
@@ -80,37 +96,49 @@ jobs:
|
||||
|
||||
riscv:
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # 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
|
||||
- uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93 # v2.75.24
|
||||
with:
|
||||
tool: cargo-hack
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- uses: dcarbone/install-jq-action@v3
|
||||
- uses: dcarbone/install-jq-action@b7ef57d46ece78760b4019dbc4080a1ba2a40b45 # v3.2.0
|
||||
- name: Run RISC-V checks
|
||||
run: .github/scripts/check_rv32imac.sh
|
||||
|
||||
crate-checks:
|
||||
name: crate-checks (${{ matrix.partition }}/${{ matrix.total_partitions }})
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-4' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
matrix:
|
||||
partition: [1, 2, 3]
|
||||
total_partitions: [3]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # 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
|
||||
- uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93 # v2.75.24
|
||||
with:
|
||||
tool: cargo-hack
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- run: cargo hack check --workspace --partition ${{ matrix.partition }}/${{ matrix.total_partitions }}
|
||||
@@ -118,15 +146,19 @@ jobs:
|
||||
msrv:
|
||||
name: MSRV
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-8' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: "1.93" # MSRV
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- run: cargo build --bin reth --workspace
|
||||
@@ -136,13 +168,17 @@ jobs:
|
||||
docs:
|
||||
name: docs
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-4' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- run: cargo docs --document-private-items
|
||||
@@ -154,42 +190,56 @@ jobs:
|
||||
fmt:
|
||||
name: fmt
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rustfmt
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- name: Run fmt
|
||||
run: cargo fmt --all --check
|
||||
|
||||
udeps:
|
||||
name: udeps
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- uses: taiki-e/install-action@cargo-udeps
|
||||
- uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93 # v2.75.24
|
||||
with:
|
||||
tool: cargo-udeps
|
||||
- run: cargo udeps --workspace --lib --examples --tests --benches --all-features --locked
|
||||
|
||||
book:
|
||||
name: book
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- run: cargo build --bin reth --workspace
|
||||
@@ -201,38 +251,54 @@ jobs:
|
||||
|
||||
typos:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: crate-ci/typos@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: crate-ci/typos@02ea592e44b3a53c302f697cddca7641cd051c3d # v1.45.0
|
||||
|
||||
check-toml:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run dprint
|
||||
uses: dprint/check@v2.3
|
||||
uses: dprint/check@9cb3a2b17a8e606d37aae341e49df3654933fc23 # v2.3
|
||||
with:
|
||||
config-path: dprint.json
|
||||
|
||||
grafana:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check dashboard JSON with jq
|
||||
uses: sergeysova/jq-action@v2
|
||||
uses: sergeysova/jq-action@a3f0d4ff59cc1dddf023fc0b325dd75b10deec58 # v2.3.0
|
||||
with:
|
||||
cmd: jq empty etc/grafana/dashboards/overview.json
|
||||
|
||||
no-test-deps:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # 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
|
||||
@@ -240,13 +306,17 @@ jobs:
|
||||
# Check crates correctly propagate features
|
||||
feature-propagation:
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: taiki-e/cache-cargo-install-action@v3
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: taiki-e/cache-cargo-install-action@a8b9ecf8e0c0ea09d7481cfc583a5203ecd585b5 # v3.0.5
|
||||
with:
|
||||
tool: zepter
|
||||
- name: Eagerly pull dependencies
|
||||
@@ -254,6 +324,8 @@ jobs:
|
||||
- run: zepter run check
|
||||
|
||||
deny:
|
||||
permissions:
|
||||
contents: read
|
||||
uses: tempoxyz/ci/.github/workflows/deny.yml@main
|
||||
|
||||
lint-success:
|
||||
@@ -277,6 +349,6 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Decide whether the needed jobs succeeded or failed
|
||||
uses: re-actors/alls-green@release/v1
|
||||
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
|
||||
with:
|
||||
jobs: ${{ toJSON(needs) }}
|
||||
|
||||
23
.github/workflows/pr-audit.yml
vendored
23
.github/workflows/pr-audit.yml
vendored
@@ -4,27 +4,36 @@ on:
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'cyclops'
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Publish event
|
||||
env:
|
||||
EVENTS_KEY: ${{ secrets.EVENTS_KEY }}
|
||||
EVENTS_CERT: ${{ secrets.EVENTS_CERT }}
|
||||
EVENTS_ARGS: ${{ secrets.EVENTS_ARGS }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
PR_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
echo "${{ secrets.EVENTS_KEY }}" > ${{ runner.temp }}/key
|
||||
echo "${{ secrets.EVENTS_CERT }}" > ${{ runner.temp }}/cert
|
||||
echo "$EVENTS_KEY" > "${{ runner.temp }}/key"
|
||||
echo "$EVENTS_CERT" > "${{ runner.temp }}/cert"
|
||||
|
||||
curl -sf -o /dev/null -X POST ${{ secrets.EVENTS_ARGS }} \
|
||||
curl -sf -o /dev/null -X POST $EVENTS_ARGS \
|
||||
-H "Content-Type: application/json" \
|
||||
--key ${{ runner.temp }}/key \
|
||||
--cert ${{ runner.temp }}/cert \
|
||||
--key "${{ runner.temp }}/key" \
|
||||
--cert "${{ runner.temp }}/cert" \
|
||||
-d '{
|
||||
"repository": "${{ github.repository }}",
|
||||
"event": "pr_audit",
|
||||
"data": {
|
||||
"pr_number": ${{ github.event.pull_request.number }},
|
||||
"sha": "${{ github.event.pull_request.head.sha }}"
|
||||
"pr_number": '"$PR_NUMBER"',
|
||||
"sha": "'"$PR_SHA"'"
|
||||
}
|
||||
}'
|
||||
|
||||
11
.github/workflows/pr-title.yml
vendored
11
.github/workflows/pr-title.yml
vendored
@@ -8,20 +8,19 @@ on:
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
permissions:
|
||||
pull-requests: read
|
||||
contents: read
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
conventional-title:
|
||||
name: Validate PR title is Conventional Commit
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Check title
|
||||
id: lint_pr_title
|
||||
uses: amannn/action-semantic-pull-request@v6
|
||||
uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -40,7 +39,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: Add PR Comment for Invalid Title
|
||||
if: steps.lint_pr_title.outcome == 'failure'
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
uses: marocchino/sticky-pull-request-comment@d4d6b0936434b21bc8345ad45a440c5f7d2c40ff # v3.0.3
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
message: |
|
||||
@@ -76,7 +75,7 @@ jobs:
|
||||
|
||||
- name: Remove Comment for Valid Title
|
||||
if: steps.lint_pr_title.outcome == 'success'
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
uses: marocchino/sticky-pull-request-comment@d4d6b0936434b21bc8345ad45a440c5f7d2c40ff # v3.0.3
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
delete: true
|
||||
|
||||
5
.github/workflows/release-dist.yml
vendored
5
.github/workflows/release-dist.yml
vendored
@@ -7,12 +7,15 @@ on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
release-homebrew:
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Update Homebrew formula
|
||||
uses: dawidd6/action-homebrew-bump-formula@v7
|
||||
uses: dawidd6/action-homebrew-bump-formula@1446dca236b0440c6f02723a3f14f13be2c04ab0 # v7
|
||||
with:
|
||||
token: ${{ secrets.HOMEBREW }}
|
||||
no_fork: true
|
||||
|
||||
24
.github/workflows/release-reproducible.yml
vendored
24
.github/workflows/release-reproducible.yml
vendored
@@ -2,6 +2,8 @@
|
||||
|
||||
name: release-reproducible
|
||||
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [release]
|
||||
@@ -15,20 +17,23 @@ jobs:
|
||||
name: extract version
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Extract version from triggering tag
|
||||
id: extract_version
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
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' \
|
||||
--jq ".[] | select(.object.sha == \"${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 }}"
|
||||
echo "No tag found for SHA ${HEAD_SHA}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -44,15 +49,16 @@ jobs:
|
||||
packages: write
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ needs.extract-version.outputs.VERSION }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -65,7 +71,7 @@ jobs:
|
||||
echo "RUST_TOOLCHAIN=$RUST_TOOLCHAIN" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build reproducible artifacts
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||
id: docker_build
|
||||
with:
|
||||
context: .
|
||||
@@ -75,13 +81,11 @@ jobs:
|
||||
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
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.reproducible
|
||||
@@ -92,8 +96,6 @@ jobs:
|
||||
tags: |
|
||||
${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:${{ needs.extract-version.outputs.VERSION }}
|
||||
${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:latest
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
provenance: false
|
||||
env:
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
|
||||
48
.github/workflows/release.yml
vendored
48
.github/workflows/release.yml
vendored
@@ -3,6 +3,8 @@
|
||||
|
||||
name: release
|
||||
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
@@ -20,21 +22,24 @@ env:
|
||||
REPRODUCIBLE_IMAGE_NAME: ${{ github.repository_owner }}/reth-reproducible
|
||||
CARGO_TERM_COLOR: always
|
||||
DOCKER_IMAGE_NAME_URL: https://ghcr.io/${{ github.repository_owner }}/reth
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
jobs:
|
||||
dry-run:
|
||||
name: check dry run
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
steps:
|
||||
- run: |
|
||||
echo "Dry run value: ${{ github.event.inputs.dry_run }}"
|
||||
echo "Dry run enabled: ${{ github.event.inputs.dry_run == 'true'}}"
|
||||
echo "Dry run disabled: ${{ github.event.inputs.dry_run != 'true'}}"
|
||||
- env:
|
||||
DRY_RUN: ${{ github.event.inputs.dry_run }}
|
||||
run: |
|
||||
echo "Dry run value: ${DRY_RUN}"
|
||||
echo "Dry run enabled: $( [ "${DRY_RUN}" = 'true' ] && echo true || echo false )"
|
||||
echo "Dry run disabled: $( [ "${DRY_RUN}" != 'true' ] && echo true || echo false )"
|
||||
|
||||
extract-version:
|
||||
name: extract version
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Extract version
|
||||
run: echo "VERSION=${GITHUB_REF_NAME//\//-}" >> $GITHUB_OUTPUT
|
||||
@@ -45,12 +50,15 @@ jobs:
|
||||
check-version:
|
||||
name: check version
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
needs: extract-version
|
||||
if: ${{ github.event.inputs.dry_run != 'true' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- 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
|
||||
@@ -63,6 +71,8 @@ jobs:
|
||||
build:
|
||||
name: build release
|
||||
runs-on: ${{ matrix.configs.os }}
|
||||
permissions:
|
||||
contents: read
|
||||
needs: extract-version
|
||||
continue-on-error: ${{ matrix.configs.allow_fail }}
|
||||
strategy:
|
||||
@@ -95,20 +105,20 @@ jobs:
|
||||
- command: build
|
||||
binary: reth
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
target: ${{ matrix.configs.target }}
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: Install cross main
|
||||
if: ${{ !matrix.configs.native }}
|
||||
id: cross_main
|
||||
run: |
|
||||
cargo install cross --locked --git https://github.com/cross-rs/cross
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
cargo install cross --locked \
|
||||
--git https://github.com/cross-rs/cross \
|
||||
--rev 65fe72b0cdb1e7e0cc0652517498d4389cc8f5cf
|
||||
|
||||
- name: Apple M1 setup
|
||||
if: matrix.configs.target == 'aarch64-apple-darwin'
|
||||
@@ -145,14 +155,14 @@ jobs:
|
||||
|
||||
- name: Upload artifact
|
||||
if: ${{ github.event.inputs.dry_run != 'true' }}
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
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@v7
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
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
|
||||
@@ -171,11 +181,12 @@ 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
- name: Generate full changelog
|
||||
id: changelog
|
||||
run: |
|
||||
@@ -261,6 +272,7 @@ jobs:
|
||||
dry-run-summary:
|
||||
name: dry run summary
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
needs: [build, extract-version]
|
||||
if: ${{ github.event.inputs.dry_run == 'true' }}
|
||||
env:
|
||||
|
||||
16
.github/workflows/reproducible-build.yml
vendored
16
.github/workflows/reproducible-build.yml
vendored
@@ -5,11 +5,15 @@ on:
|
||||
schedule:
|
||||
- cron: "0 1 */2 * *"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
name: build reproducible binaries
|
||||
runs-on: ${{ matrix.runner }}
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -18,14 +22,16 @@ jobs:
|
||||
- runner: ubuntu-22.04
|
||||
machine: machine-2
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
target: x86_64-unknown-linux-gnu
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Build reproducible binary with Docker
|
||||
run: |
|
||||
@@ -43,7 +49,7 @@ jobs:
|
||||
echo "Binaries SHA256 on ${{ matrix.machine }}: $(cat checksum.sha256)"
|
||||
|
||||
- name: Upload the hash
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: checksum-${{ matrix.machine }}
|
||||
path: |
|
||||
@@ -56,12 +62,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifacts from machine-1
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: checksum-machine-1
|
||||
path: machine-1/
|
||||
- name: Download artifacts from machine-2
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: checksum-machine-2
|
||||
path: machine-2/
|
||||
|
||||
14
.github/workflows/stage.yml
vendored
14
.github/workflows/stage.yml
vendored
@@ -18,22 +18,28 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
stage:
|
||||
name: stage-run-test
|
||||
# Only run stage commands test in merge groups
|
||||
if: github.event_name == 'merge_group'
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
RUST_LOG: info,sync=error
|
||||
RUST_BACKTRACE: 1
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Build reth
|
||||
|
||||
4
.github/workflows/stale.yml
vendored
4
.github/workflows/stale.yml
vendored
@@ -7,6 +7,8 @@ on:
|
||||
schedule:
|
||||
- cron: "30 1 * * *"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
close-issues:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
@@ -15,7 +17,7 @@ jobs:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
|
||||
with:
|
||||
days-before-stale: 21
|
||||
days-before-close: 7
|
||||
|
||||
14
.github/workflows/sync-era.yml
vendored
14
.github/workflows/sync-era.yml
vendored
@@ -15,11 +15,15 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
name: sync (${{ matrix.chain.bin }})
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
RUST_LOG: info,sync=error
|
||||
RUST_BACKTRACE: 1
|
||||
@@ -34,11 +38,13 @@ jobs:
|
||||
block: 100000
|
||||
unwind-target: "0x52e0509d33a988ef807058e2980099ee3070187f7333aae12b64d4d675f34c5a"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Build ${{ matrix.chain.bin }}
|
||||
|
||||
14
.github/workflows/sync.yml
vendored
14
.github/workflows/sync.yml
vendored
@@ -15,11 +15,15 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
name: sync (${{ matrix.chain.bin }})
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
RUST_LOG: info,sync=error
|
||||
RUST_BACKTRACE: 1
|
||||
@@ -34,11 +38,13 @@ jobs:
|
||||
block: 100000
|
||||
unwind-target: "0x52e0509d33a988ef807058e2980099ee3070187f7333aae12b64d4d675f34c5a"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Build ${{ matrix.chain.bin }}
|
||||
|
||||
53
.github/workflows/unit.yml
vendored
53
.github/workflows/unit.yml
vendored
@@ -17,10 +17,14 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: test / ${{ matrix.type }}
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-4' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
strategy:
|
||||
@@ -32,16 +36,20 @@ jobs:
|
||||
exclude_args: ""
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93 # v2.75.24
|
||||
with:
|
||||
tool: nextest
|
||||
- if: "${{ matrix.type == 'book' }}"
|
||||
uses: arduino/setup-protoc@v3
|
||||
uses: arduino/setup-protoc@c65c819552d16ad3c9b72d9dfd5ba5237b9c906b # v3.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Run tests
|
||||
@@ -56,20 +64,25 @@ jobs:
|
||||
state:
|
||||
name: Ethereum state tests
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-8' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
RUST_LOG: info,sync=error
|
||||
RUST_BACKTRACE: 1
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Checkout ethereum/tests
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ethereum/tests
|
||||
ref: 81862e4848585a438d64f911a19b3825f0f4cd95
|
||||
path: testing/ef-tests/ethereum-tests
|
||||
submodules: recursive
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
- name: Download & extract EEST fixtures (public)
|
||||
shell: bash
|
||||
env:
|
||||
@@ -79,11 +92,13 @@ jobs:
|
||||
mkdir -p testing/ef-tests/execution-spec-tests
|
||||
URL="https://github.com/ethereum/execution-spec-tests/releases/download/${EEST_TESTS_TAG}/fixtures_stable.tar.gz"
|
||||
curl -L "$URL" | tar -xz --strip-components=1 -C testing/ef-tests/execution-spec-tests
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # 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
|
||||
- uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93 # v2.75.24
|
||||
with:
|
||||
tool: nextest
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- run: cargo nextest run --no-fail-fast --cargo-profile hivetests -p ef-tests --features "asm-keccak ef-tests"
|
||||
@@ -91,15 +106,19 @@ jobs:
|
||||
doc:
|
||||
name: doc tests
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Run doctests
|
||||
@@ -113,6 +132,6 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Decide whether the needed jobs succeeded or failed
|
||||
uses: re-actors/alls-green@release/v1
|
||||
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
|
||||
with:
|
||||
jobs: ${{ toJSON(needs) }}
|
||||
|
||||
69
Cargo.lock
generated
69
Cargo.lock
generated
@@ -290,8 +290,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-evm"
|
||||
version = "0.33.2"
|
||||
source = "git+https://github.com/alloy-rs/evm?rev=da7633f6bc9554f5a6e60773ef21b8e9d6e0cca6#da7633f6bc9554f5a6e60773ef21b8e9d6e0cca6"
|
||||
version = "0.34.0"
|
||||
source = "git+https://github.com/alloy-rs/evm?branch=bal-devnet-4#aedf0f4a621ba3e63f0db6a570bdc9509296d1a6"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -3093,8 +3093,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "discv5"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c7999df38d0bd8f688212e1a4fae31fd2fea6d218649b9cd7c40bf3ec1318fc"
|
||||
source = "git+https://github.com/sigp/discv5?rev=7663c00#7663c00ee0837ea98547caaedede95d9d6736f4d"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes-gcm",
|
||||
@@ -3715,6 +3714,7 @@ dependencies = [
|
||||
"eyre",
|
||||
"futures",
|
||||
"reth-ethereum",
|
||||
"reth-metrics",
|
||||
"reth-tracing",
|
||||
"tokio",
|
||||
]
|
||||
@@ -7678,7 +7678,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "reth-codecs"
|
||||
version = "0.3.1"
|
||||
source = "git+https://github.com/paradigmxyz/reth-core?rev=c763480b9fa51957fbdb69b7caead5dfc4e3752c#c763480b9fa51957fbdb69b7caead5dfc4e3752c"
|
||||
source = "git+https://github.com/paradigmxyz/reth-core?rev=8612239c4f3dda83cc389f577b9eb04f10ebf81d#8612239c4f3dda83cc389f577b9eb04f10ebf81d"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -7698,7 +7698,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "reth-codecs-derive"
|
||||
version = "0.3.1"
|
||||
source = "git+https://github.com/paradigmxyz/reth-core?rev=c763480b9fa51957fbdb69b7caead5dfc4e3752c#c763480b9fa51957fbdb69b7caead5dfc4e3752c"
|
||||
source = "git+https://github.com/paradigmxyz/reth-core?rev=8612239c4f3dda83cc389f577b9eb04f10ebf81d#8612239c4f3dda83cc389f577b9eb04f10ebf81d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -7728,6 +7728,7 @@ name = "reth-consensus"
|
||||
version = "2.1.0"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eip7928",
|
||||
"alloy-primitives",
|
||||
"auto_impl",
|
||||
"reth-execution-types",
|
||||
@@ -8825,6 +8826,7 @@ dependencies = [
|
||||
"futures",
|
||||
"metrics",
|
||||
"metrics-derive",
|
||||
"reth-primitives-traits",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
@@ -8904,6 +8906,7 @@ dependencies = [
|
||||
"secp256k1 0.30.0",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"socket2",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@@ -9382,7 +9385,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "reth-primitives-traits"
|
||||
version = "0.3.1"
|
||||
source = "git+https://github.com/paradigmxyz/reth-core?rev=c763480b9fa51957fbdb69b7caead5dfc4e3752c#c763480b9fa51957fbdb69b7caead5dfc4e3752c"
|
||||
source = "git+https://github.com/paradigmxyz/reth-core?rev=8612239c4f3dda83cc389f577b9eb04f10ebf81d#8612239c4f3dda83cc389f577b9eb04f10ebf81d"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -9926,7 +9929,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "reth-rpc-traits"
|
||||
version = "0.3.1"
|
||||
source = "git+https://github.com/paradigmxyz/reth-core?rev=c763480b9fa51957fbdb69b7caead5dfc4e3752c#c763480b9fa51957fbdb69b7caead5dfc4e3752c"
|
||||
source = "git+https://github.com/paradigmxyz/reth-core?rev=8612239c4f3dda83cc389f577b9eb04f10ebf81d#8612239c4f3dda83cc389f577b9eb04f10ebf81d"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-network",
|
||||
@@ -10403,6 +10406,8 @@ dependencies = [
|
||||
"proptest-arbitrary-interop",
|
||||
"rand 0.9.4",
|
||||
"rayon",
|
||||
"reth-chainspec",
|
||||
"reth-ethereum-primitives",
|
||||
"reth-execution-errors",
|
||||
"reth-metrics",
|
||||
"reth-primitives-traits",
|
||||
@@ -10454,15 +10459,15 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "reth-zstd-compressors"
|
||||
version = "0.3.1"
|
||||
source = "git+https://github.com/paradigmxyz/reth-core?rev=c763480b9fa51957fbdb69b7caead5dfc4e3752c#c763480b9fa51957fbdb69b7caead5dfc4e3752c"
|
||||
source = "git+https://github.com/paradigmxyz/reth-core?rev=8612239c4f3dda83cc389f577b9eb04f10ebf81d#8612239c4f3dda83cc389f577b9eb04f10ebf81d"
|
||||
dependencies = [
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "revm"
|
||||
version = "37.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fe2549d85fb9e201e7b629f8b47bcca46d49aa1d#fe2549d85fb9e201e7b629f8b47bcca46d49aa1d"
|
||||
version = "38.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be#3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be"
|
||||
dependencies = [
|
||||
"revm-bytecode",
|
||||
"revm-context",
|
||||
@@ -10480,7 +10485,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "revm-bytecode"
|
||||
version = "10.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fe2549d85fb9e201e7b629f8b47bcca46d49aa1d#fe2549d85fb9e201e7b629f8b47bcca46d49aa1d"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be#3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"phf",
|
||||
@@ -10490,8 +10495,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revm-context"
|
||||
version = "16.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fe2549d85fb9e201e7b629f8b47bcca46d49aa1d#fe2549d85fb9e201e7b629f8b47bcca46d49aa1d"
|
||||
version = "16.0.1"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be#3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"cfg-if",
|
||||
@@ -10506,8 +10511,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revm-context-interface"
|
||||
version = "17.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fe2549d85fb9e201e7b629f8b47bcca46d49aa1d#fe2549d85fb9e201e7b629f8b47bcca46d49aa1d"
|
||||
version = "17.0.1"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be#3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be"
|
||||
dependencies = [
|
||||
"alloy-eip2930",
|
||||
"alloy-eip7702",
|
||||
@@ -10521,8 +10526,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revm-database"
|
||||
version = "13.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fe2549d85fb9e201e7b629f8b47bcca46d49aa1d#fe2549d85fb9e201e7b629f8b47bcca46d49aa1d"
|
||||
version = "13.0.1"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be#3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be"
|
||||
dependencies = [
|
||||
"alloy-eips",
|
||||
"revm-bytecode",
|
||||
@@ -10534,8 +10539,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revm-database-interface"
|
||||
version = "11.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fe2549d85fb9e201e7b629f8b47bcca46d49aa1d#fe2549d85fb9e201e7b629f8b47bcca46d49aa1d"
|
||||
version = "11.0.1"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be#3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be"
|
||||
dependencies = [
|
||||
"auto_impl",
|
||||
"either",
|
||||
@@ -10547,8 +10552,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revm-handler"
|
||||
version = "18.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fe2549d85fb9e201e7b629f8b47bcca46d49aa1d#fe2549d85fb9e201e7b629f8b47bcca46d49aa1d"
|
||||
version = "18.1.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be#3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be"
|
||||
dependencies = [
|
||||
"auto_impl",
|
||||
"derive-where",
|
||||
@@ -10565,8 +10570,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revm-inspector"
|
||||
version = "18.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fe2549d85fb9e201e7b629f8b47bcca46d49aa1d#fe2549d85fb9e201e7b629f8b47bcca46d49aa1d"
|
||||
version = "19.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be#3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be"
|
||||
dependencies = [
|
||||
"auto_impl",
|
||||
"either",
|
||||
@@ -10583,7 +10588,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "revm-inspectors"
|
||||
version = "0.39.0"
|
||||
source = "git+https://github.com/paradigmxyz/revm-inspectors?rev=a2c7a41977b468d016a339f560acb76e002766f3#a2c7a41977b468d016a339f560acb76e002766f3"
|
||||
source = "git+https://github.com/paradigmxyz/revm-inspectors?rev=5eebb56819ee6bec5bfbc69a415276ee1a784fec#5eebb56819ee6bec5bfbc69a415276ee1a784fec"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-types-eth",
|
||||
@@ -10601,8 +10606,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revm-interpreter"
|
||||
version = "35.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fe2549d85fb9e201e7b629f8b47bcca46d49aa1d#fe2549d85fb9e201e7b629f8b47bcca46d49aa1d"
|
||||
version = "35.0.1"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be#3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be"
|
||||
dependencies = [
|
||||
"revm-bytecode",
|
||||
"revm-context-interface",
|
||||
@@ -10613,8 +10618,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revm-precompile"
|
||||
version = "33.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fe2549d85fb9e201e7b629f8b47bcca46d49aa1d#fe2549d85fb9e201e7b629f8b47bcca46d49aa1d"
|
||||
version = "34.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be#3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be"
|
||||
dependencies = [
|
||||
"ark-bls12-381",
|
||||
"ark-bn254",
|
||||
@@ -10639,7 +10644,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "revm-primitives"
|
||||
version = "23.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fe2549d85fb9e201e7b629f8b47bcca46d49aa1d#fe2549d85fb9e201e7b629f8b47bcca46d49aa1d"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be#3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"once_cell",
|
||||
@@ -10648,8 +10653,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revm-state"
|
||||
version = "11.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fe2549d85fb9e201e7b629f8b47bcca46d49aa1d#fe2549d85fb9e201e7b629f8b47bcca46d49aa1d"
|
||||
version = "11.0.1"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be#3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be"
|
||||
dependencies = [
|
||||
"alloy-eip7928",
|
||||
"bitflags 2.11.1",
|
||||
|
||||
52
Cargo.toml
52
Cargo.toml
@@ -433,13 +433,13 @@ reth-trie-sparse = { path = "crates/trie/sparse", default-features = false }
|
||||
reth-zstd-compressors = { version = "0.3.1", default-features = false }
|
||||
|
||||
# revm
|
||||
revm = { version = "=37.0.0", default-features = false }
|
||||
revm = { version = "=38.0.0", default-features = false }
|
||||
revm-bytecode = { version = "=10.0.0", default-features = false }
|
||||
revm-database = { version = "=13.0.0", default-features = false }
|
||||
revm-state = { version = "=11.0.0", default-features = false }
|
||||
revm-database = { version = "=13.0.1", default-features = false }
|
||||
revm-state = { version = "=11.0.1", default-features = false }
|
||||
revm-primitives = { version = "=23.0.0", default-features = false }
|
||||
revm-interpreter = { version = "=35.0.0", default-features = false }
|
||||
revm-database-interface = { version = "=11.0.0", default-features = false }
|
||||
revm-interpreter = { version = "=35.0.1", default-features = false }
|
||||
revm-database-interface = { version = "=11.0.1", default-features = false }
|
||||
revm-inspectors = "=0.39.0"
|
||||
|
||||
# eth
|
||||
@@ -450,7 +450,7 @@ alloy-sol-types = { version = "1.5.6", default-features = false }
|
||||
alloy-chains = { version = "0.2.33", default-features = false }
|
||||
alloy-eip2124 = { version = "0.2.0", default-features = false }
|
||||
alloy-eip7928 = { version = "0.3.4", default-features = false }
|
||||
alloy-evm = { version = "0.33.0", default-features = false }
|
||||
alloy-evm = { version = "0.34.0", default-features = false }
|
||||
alloy-rlp = { version = "0.3.13", default-features = false, features = ["core-net"] }
|
||||
alloy-trie = { version = "0.9.4", default-features = false }
|
||||
|
||||
@@ -581,7 +581,7 @@ tower = "0.5"
|
||||
tower-http = "0.6"
|
||||
|
||||
# p2p
|
||||
discv5 = "0.10"
|
||||
discv5 = { git = "https://github.com/sigp/discv5", rev = "7663c00" }
|
||||
if-addrs = "0.14"
|
||||
|
||||
# rpc
|
||||
@@ -702,22 +702,22 @@ vergen-git2 = "9.1.0"
|
||||
ipnet = "2.11"
|
||||
|
||||
[patch.crates-io]
|
||||
revm = { git = "https://github.com/bluealloy/revm", rev = "fe2549d85fb9e201e7b629f8b47bcca46d49aa1d" }
|
||||
revm-bytecode = { git = "https://github.com/bluealloy/revm", rev = "fe2549d85fb9e201e7b629f8b47bcca46d49aa1d" }
|
||||
revm-context = { git = "https://github.com/bluealloy/revm", rev = "fe2549d85fb9e201e7b629f8b47bcca46d49aa1d" }
|
||||
revm-context-interface = { git = "https://github.com/bluealloy/revm", rev = "fe2549d85fb9e201e7b629f8b47bcca46d49aa1d" }
|
||||
revm-database = { git = "https://github.com/bluealloy/revm", rev = "fe2549d85fb9e201e7b629f8b47bcca46d49aa1d" }
|
||||
revm-database-interface = { git = "https://github.com/bluealloy/revm", rev = "fe2549d85fb9e201e7b629f8b47bcca46d49aa1d" }
|
||||
revm-handler = { git = "https://github.com/bluealloy/revm", rev = "fe2549d85fb9e201e7b629f8b47bcca46d49aa1d" }
|
||||
revm-inspector = { git = "https://github.com/bluealloy/revm", rev = "fe2549d85fb9e201e7b629f8b47bcca46d49aa1d" }
|
||||
revm-interpreter = { git = "https://github.com/bluealloy/revm", rev = "fe2549d85fb9e201e7b629f8b47bcca46d49aa1d" }
|
||||
revm-precompile = { git = "https://github.com/bluealloy/revm", rev = "fe2549d85fb9e201e7b629f8b47bcca46d49aa1d" }
|
||||
revm-primitives = { git = "https://github.com/bluealloy/revm", rev = "fe2549d85fb9e201e7b629f8b47bcca46d49aa1d" }
|
||||
revm-state = { git = "https://github.com/bluealloy/revm", rev = "fe2549d85fb9e201e7b629f8b47bcca46d49aa1d" }
|
||||
revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "a2c7a41977b468d016a339f560acb76e002766f3" }
|
||||
alloy-evm = { git = "https://github.com/alloy-rs/evm", rev = "da7633f6bc9554f5a6e60773ef21b8e9d6e0cca6" }
|
||||
reth-codecs = { git = "https://github.com/paradigmxyz/reth-core", rev = "c763480b9fa51957fbdb69b7caead5dfc4e3752c" }
|
||||
reth-codecs-derive = { git = "https://github.com/paradigmxyz/reth-core", rev = "c763480b9fa51957fbdb69b7caead5dfc4e3752c" }
|
||||
reth-primitives-traits = { git = "https://github.com/paradigmxyz/reth-core", rev = "c763480b9fa51957fbdb69b7caead5dfc4e3752c" }
|
||||
reth-rpc-traits = { git = "https://github.com/paradigmxyz/reth-core", rev = "c763480b9fa51957fbdb69b7caead5dfc4e3752c" }
|
||||
reth-zstd-compressors = { git = "https://github.com/paradigmxyz/reth-core", rev = "c763480b9fa51957fbdb69b7caead5dfc4e3752c" }
|
||||
revm = { git = "https://github.com/bluealloy/revm", rev = "3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be" }
|
||||
revm-bytecode = { git = "https://github.com/bluealloy/revm", rev = "3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be" }
|
||||
revm-context = { git = "https://github.com/bluealloy/revm", rev = "3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be" }
|
||||
revm-context-interface = { git = "https://github.com/bluealloy/revm", rev = "3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be" }
|
||||
revm-database = { git = "https://github.com/bluealloy/revm", rev = "3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be" }
|
||||
revm-database-interface = { git = "https://github.com/bluealloy/revm", rev = "3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be" }
|
||||
revm-handler = { git = "https://github.com/bluealloy/revm", rev = "3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be" }
|
||||
revm-inspector = { git = "https://github.com/bluealloy/revm", rev = "3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be" }
|
||||
revm-interpreter = { git = "https://github.com/bluealloy/revm", rev = "3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be" }
|
||||
revm-precompile = { git = "https://github.com/bluealloy/revm", rev = "3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be" }
|
||||
revm-primitives = { git = "https://github.com/bluealloy/revm", rev = "3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be" }
|
||||
revm-state = { git = "https://github.com/bluealloy/revm", rev = "3ed3bdfed9ad6e5ba37f4e1f015436ab89ca98be" }
|
||||
revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "5eebb56819ee6bec5bfbc69a415276ee1a784fec" }
|
||||
alloy-evm = { git = "https://github.com/alloy-rs/evm", branch = "bal-devnet-4" }
|
||||
reth-codecs = { git = "https://github.com/paradigmxyz/reth-core", rev = "8612239c4f3dda83cc389f577b9eb04f10ebf81d" }
|
||||
reth-codecs-derive = { git = "https://github.com/paradigmxyz/reth-core", rev = "8612239c4f3dda83cc389f577b9eb04f10ebf81d" }
|
||||
reth-primitives-traits = { git = "https://github.com/paradigmxyz/reth-core", rev = "8612239c4f3dda83cc389f577b9eb04f10ebf81d" }
|
||||
reth-rpc-traits = { git = "https://github.com/paradigmxyz/reth-core", rev = "8612239c4f3dda83cc389f577b9eb04f10ebf81d" }
|
||||
reth-zstd-compressors = { git = "https://github.com/paradigmxyz/reth-core", rev = "8612239c4f3dda83cc389f577b9eb04f10ebf81d" }
|
||||
|
||||
13
Dockerfile
13
Dockerfile
@@ -33,15 +33,24 @@ ENV FEATURES=$FEATURES
|
||||
RUN cargo chef cook --profile $BUILD_PROFILE --features "$FEATURES" --recipe-path recipe.json
|
||||
|
||||
# Build application
|
||||
# Platform-specific RUSTFLAGS: amd64 uses x86-64-v3 (Haswell+) with pclmulqdq for rocksdb
|
||||
#
|
||||
# TARGETPLATFORM is set by BuildKit: https://docs.docker.com/reference/dockerfile#automatic-platform-args-in-the-global-scope
|
||||
ARG TARGETPLATFORM
|
||||
COPY --exclude=dist . .
|
||||
RUN cargo build --profile $BUILD_PROFILE --features "$FEATURES" --locked --bin reth
|
||||
RUN if [ -n "$RUSTFLAGS" ]; then \
|
||||
export RUSTFLAGS="$RUSTFLAGS"; \
|
||||
elif [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
|
||||
export RUSTFLAGS="-C target-cpu=x86-64-v3 -C target-feature=+pclmulqdq"; \
|
||||
fi && \
|
||||
cargo build --profile $BUILD_PROFILE --features "$FEATURES" --locked --bin reth
|
||||
|
||||
# ARG is not resolved in COPY so we have to hack around it by copying the
|
||||
# binary to a temporary location
|
||||
RUN cp /app/target/$BUILD_PROFILE/reth /app/reth
|
||||
|
||||
# Use Ubuntu as the release image
|
||||
FROM ubuntu AS runtime
|
||||
FROM ubuntu:24.04 AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
# Copy reth over from the build stage
|
||||
|
||||
@@ -7,15 +7,16 @@
|
||||
//! `execute_transaction` to apply segment-boundary changes.
|
||||
|
||||
use crate::evm_config::BigBlockSegment;
|
||||
use alloy_consensus::TransactionEnvelope;
|
||||
use alloy_eips::eip7685::Requests;
|
||||
use alloy_evm::{
|
||||
block::{
|
||||
BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockExecutorFactory,
|
||||
BlockExecutorFor, ExecutableTx, GasOutput, OnStateHook, StateChangeSource, StateDB,
|
||||
ExecutableTx, GasOutput, OnStateHook, StateChangeSource, StateDB,
|
||||
},
|
||||
eth::{EthBlockExecutionCtx, EthBlockExecutor, EthEvmContext, EthTxResult},
|
||||
precompiles::PrecompilesMap,
|
||||
Database, EthEvm, EthEvmFactory, Evm, FromRecoveredTx, FromTxWithEncoded,
|
||||
Database, EthEvm, EthEvmFactory, Evm, EvmFactory, FromRecoveredTx, FromTxWithEncoded,
|
||||
};
|
||||
use alloy_primitives::B256;
|
||||
use reth_ethereum_primitives::{Receipt, TransactionSigned};
|
||||
@@ -36,6 +37,7 @@ use tracing::{debug, trace};
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Runtime state for segment boundary tracking.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct BbEvmPlan {
|
||||
/// The segment boundaries and environments.
|
||||
pub(crate) segments: Vec<BigBlockSegment>,
|
||||
@@ -73,6 +75,10 @@ impl BbEvmPlan {
|
||||
.filter(|(n, _)| *n >= min && *n < block_number)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn segment_index_for_tx(&self, tx_index: usize) -> usize {
|
||||
self.segments.partition_point(|segment| segment.start_tx <= tx_index).saturating_sub(1)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for BbEvmPlan {
|
||||
@@ -97,6 +103,9 @@ impl std::fmt::Debug for BbEvmPlan {
|
||||
/// segment boundaries without requiring additional trait bounds on `DB`.
|
||||
pub(crate) type BlockHashSeeder<DB> = fn(&mut DB, &[(u64, B256)]);
|
||||
|
||||
/// Function pointer that reads the BAL index from the DB.
|
||||
pub(crate) type BalIndexReader<DB> = fn(&DB) -> u64;
|
||||
|
||||
/// Block executor that wraps [`EthBlockExecutor`] and handles segment-boundary
|
||||
/// changes for big-block execution.
|
||||
///
|
||||
@@ -108,7 +117,8 @@ pub(crate) type BlockHashSeeder<DB> = fn(&mut DB, &[(u64, B256)]);
|
||||
/// Gas counters reset at each boundary so that each segment's real gas limit
|
||||
/// is used (preserving correct GASLIMIT opcode behavior). Accumulated offsets
|
||||
/// are applied to receipts and totals in `finish()`.
|
||||
pub(crate) struct BbBlockExecutor<'a, DB, I, P, Spec>
|
||||
#[expect(missing_debug_implementations)]
|
||||
pub struct BbBlockExecutor<'a, DB, I, P, Spec>
|
||||
where
|
||||
DB: Database,
|
||||
{
|
||||
@@ -131,6 +141,10 @@ where
|
||||
/// Callback to reseed block hashes into the DB's cache at segment
|
||||
/// boundaries. See [`BlockHashSeeder`].
|
||||
block_hash_seeder: Option<BlockHashSeeder<DB>>,
|
||||
/// Callback to read the BAL index from the DB.
|
||||
bal_index_reader: Option<BalIndexReader<DB>>,
|
||||
/// Whether the executor has selected its starting segment.
|
||||
initialized: bool,
|
||||
}
|
||||
|
||||
impl<'a, DB, I, P, Spec> BbBlockExecutor<'a, DB, I, P, Spec>
|
||||
@@ -156,6 +170,7 @@ where
|
||||
receipt_builder: RethReceiptBuilder,
|
||||
plan: Option<BbEvmPlan>,
|
||||
block_hash_seeder: Option<BlockHashSeeder<DB>>,
|
||||
bal_index_reader: Option<BalIndexReader<DB>>,
|
||||
) -> Self {
|
||||
let inner = EthBlockExecutor::new(evm, ctx, spec, receipt_builder);
|
||||
Self {
|
||||
@@ -166,9 +181,63 @@ where
|
||||
blob_gas_used_offset: 0,
|
||||
shared_hook: Arc::new(Mutex::new(None)),
|
||||
block_hash_seeder,
|
||||
bal_index_reader,
|
||||
initialized: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize(&mut self) -> Result<(), BlockExecutionError> {
|
||||
if self.initialized {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let plan = match &self.plan {
|
||||
Some(plan) => plan,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
self.initialized = true;
|
||||
|
||||
let bal_index =
|
||||
self.bal_index_reader.map(|reader| reader(self.inner().evm().db())).unwrap_or(0);
|
||||
let segment_idx =
|
||||
if bal_index == 0 { 0 } else { plan.segment_index_for_tx((bal_index - 1) as usize) };
|
||||
let segment = &plan.segments[segment_idx];
|
||||
|
||||
// Swap the EVM's block_env and executor ctx to the selected segment's
|
||||
// values so that EIP-2935/EIP-4788 system calls use the correct block
|
||||
// number and parent hash. Without this, the outer big block header's
|
||||
// block_number (which is synthetic) would be used, writing to wrong
|
||||
// EIP-2935 slots and corrupting state.
|
||||
let block_env = segment.evm_env.block_env.clone();
|
||||
let block_number = block_env.number.saturating_to::<u64>();
|
||||
let mut cfg_env = segment.evm_env.cfg_env.clone();
|
||||
cfg_env.disable_base_fee = true;
|
||||
let ctx = EthBlockExecutionCtx {
|
||||
parent_hash: segment.ctx.parent_hash,
|
||||
parent_beacon_block_root: segment.ctx.parent_beacon_block_root,
|
||||
ommers: segment.ctx.ommers,
|
||||
withdrawals: segment.ctx.withdrawals.clone(),
|
||||
extra_data: segment.ctx.extra_data.clone(),
|
||||
tx_count_hint: segment.ctx.tx_count_hint,
|
||||
slot_number: segment.ctx.slot_number,
|
||||
};
|
||||
|
||||
let inner = self.inner_mut();
|
||||
let evm_ctx = inner.evm.ctx_mut();
|
||||
evm_ctx.block = block_env;
|
||||
evm_ctx.cfg = cfg_env;
|
||||
inner.ctx = ctx;
|
||||
|
||||
self.reseed_block_hashes_for(block_number);
|
||||
|
||||
if bal_index > 0 {
|
||||
self.plan = None;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates a forwarding `OnStateHook` that delegates to the shared hook.
|
||||
fn forwarding_hook(&self) -> Option<Box<dyn OnStateHook>> {
|
||||
let shared = self.shared_hook.clone();
|
||||
@@ -349,35 +418,9 @@ where
|
||||
type Result = EthTxResult<HaltReason, alloy_consensus::TxType>;
|
||||
|
||||
fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
|
||||
// Swap the EVM's block_env and executor ctx to the first segment's
|
||||
// values so that the initial EIP-2935/EIP-4788 system calls use the
|
||||
// correct block number and parent hash. Without this, the outer big
|
||||
// block header's block_number (which is synthetic) would be used,
|
||||
// writing to wrong EIP-2935 slots and corrupting state.
|
||||
if let Some(seg0) = self.plan.as_ref().map(|p| &p.segments[0]) {
|
||||
let block_env = seg0.evm_env.block_env.clone();
|
||||
let block_number = block_env.number.saturating_to::<u64>();
|
||||
let mut cfg_env = seg0.evm_env.cfg_env.clone();
|
||||
cfg_env.disable_base_fee = true;
|
||||
let seg0_ctx = EthBlockExecutionCtx {
|
||||
parent_hash: seg0.ctx.parent_hash,
|
||||
parent_beacon_block_root: seg0.ctx.parent_beacon_block_root,
|
||||
ommers: seg0.ctx.ommers,
|
||||
withdrawals: seg0.ctx.withdrawals.clone(),
|
||||
extra_data: seg0.ctx.extra_data.clone(),
|
||||
tx_count_hint: seg0.ctx.tx_count_hint,
|
||||
slot_number: seg0.ctx.slot_number,
|
||||
};
|
||||
|
||||
let inner = self.inner_mut();
|
||||
let evm_ctx = inner.evm.ctx_mut();
|
||||
evm_ctx.block = block_env;
|
||||
evm_ctx.cfg = cfg_env;
|
||||
inner.ctx = seg0_ctx;
|
||||
|
||||
self.reseed_block_hashes_for(block_number);
|
||||
}
|
||||
|
||||
// The outer big-block header uses a synthetic block number, so start
|
||||
// system calls must run against the selected real segment env.
|
||||
self.initialize()?;
|
||||
self.inner_mut().apply_pre_execution_changes()
|
||||
}
|
||||
|
||||
@@ -385,15 +428,16 @@ where
|
||||
&mut self,
|
||||
tx: impl ExecutableTx<Self>,
|
||||
) -> Result<Self::Result, BlockExecutionError> {
|
||||
self.initialize()?;
|
||||
self.maybe_apply_boundary()?;
|
||||
self.inner_mut().execute_transaction_without_commit(tx)
|
||||
}
|
||||
|
||||
fn commit_transaction(
|
||||
&mut self,
|
||||
output: Self::Result,
|
||||
) -> Result<GasOutput, BlockExecutionError> {
|
||||
let gas_used = self.inner_mut().commit_transaction(output)?;
|
||||
fn commit_transaction(&mut self, output: Self::Result) -> GasOutput {
|
||||
self.maybe_apply_boundary()
|
||||
.expect("segment boundary application must succeed before committing transaction");
|
||||
|
||||
let gas_used = self.inner_mut().commit_transaction(output);
|
||||
|
||||
// Fix up cumulative_gas_used on the just-committed receipt so that
|
||||
// the receipt root task (which reads receipts incrementally) sees
|
||||
@@ -408,7 +452,7 @@ where
|
||||
if let Some(plan) = &mut self.plan {
|
||||
plan.tx_counter += 1;
|
||||
}
|
||||
Ok(gas_used)
|
||||
gas_used
|
||||
}
|
||||
|
||||
fn finish(
|
||||
@@ -499,7 +543,7 @@ pub struct BbBlockExecutorFactory<Spec> {
|
||||
receipt_builder: RethReceiptBuilder,
|
||||
spec: Spec,
|
||||
evm_factory: EthEvmFactory,
|
||||
/// Staged plan consumed by the next [`BbBlockExecutor`].
|
||||
/// Staged plan cloned into each [`BbBlockExecutor`].
|
||||
pub(crate) staged_plan: Arc<Mutex<Option<BbEvmPlan>>>,
|
||||
}
|
||||
|
||||
@@ -528,8 +572,12 @@ impl<Spec> BbBlockExecutorFactory<Spec> {
|
||||
*self.staged_plan.lock().unwrap() = Some(plan);
|
||||
}
|
||||
|
||||
fn take_plan(&self) -> Option<BbEvmPlan> {
|
||||
self.staged_plan.lock().unwrap().take()
|
||||
pub(crate) fn clear_staged_plan(&self) {
|
||||
*self.staged_plan.lock().unwrap() = None;
|
||||
}
|
||||
|
||||
fn peek_plan(&self) -> Option<BbEvmPlan> {
|
||||
self.staged_plan.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
pub(crate) fn create_executor_with_seeder<'a, DB, I>(
|
||||
@@ -537,14 +585,23 @@ impl<Spec> BbBlockExecutorFactory<Spec> {
|
||||
evm: EthEvm<DB, I, PrecompilesMap>,
|
||||
ctx: EthBlockExecutionCtx<'a>,
|
||||
block_hash_seeder: Option<BlockHashSeeder<DB>>,
|
||||
bal_index_reader: Option<BalIndexReader<DB>>,
|
||||
) -> BbBlockExecutor<'a, DB, I, PrecompilesMap, &'a Spec>
|
||||
where
|
||||
Spec: alloy_evm::eth::spec::EthExecutorSpec,
|
||||
DB: StateDB + 'a,
|
||||
I: Inspector<EthEvmContext<DB>> + 'a,
|
||||
{
|
||||
let plan = self.take_plan();
|
||||
BbBlockExecutor::new(evm, ctx, &self.spec, self.receipt_builder, plan, block_hash_seeder)
|
||||
let plan = self.peek_plan();
|
||||
BbBlockExecutor::new(
|
||||
evm,
|
||||
ctx,
|
||||
&self.spec,
|
||||
self.receipt_builder,
|
||||
plan,
|
||||
block_hash_seeder,
|
||||
bal_index_reader,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,6 +614,12 @@ where
|
||||
type ExecutionCtx<'a> = EthBlockExecutionCtx<'a>;
|
||||
type Transaction = TransactionSigned;
|
||||
type Receipt = Receipt;
|
||||
type TxExecutionResult = EthTxResult<
|
||||
<EthEvmFactory as EvmFactory>::HaltReason,
|
||||
<TransactionSigned as TransactionEnvelope>::TxType,
|
||||
>;
|
||||
type Executor<'a, DB: StateDB, I: Inspector<EthEvmContext<DB>>> =
|
||||
BbBlockExecutor<'a, DB, I, PrecompilesMap, &'a Spec>;
|
||||
|
||||
fn evm_factory(&self) -> &Self::EvmFactory {
|
||||
&self.evm_factory
|
||||
@@ -566,12 +629,12 @@ where
|
||||
&'a self,
|
||||
evm: EthEvm<DB, I, PrecompilesMap>,
|
||||
ctx: EthBlockExecutionCtx<'a>,
|
||||
) -> impl BlockExecutorFor<'a, Self, DB, I>
|
||||
) -> Self::Executor<'a, DB, I>
|
||||
where
|
||||
DB: StateDB + 'a,
|
||||
I: Inspector<EthEvmContext<DB>> + 'a,
|
||||
DB: StateDB,
|
||||
I: Inspector<EthEvmContext<DB>>,
|
||||
{
|
||||
let plan = self.take_plan();
|
||||
BbBlockExecutor::new(evm, ctx, &self.spec, self.receipt_builder, plan, None)
|
||||
let plan = self.peek_plan();
|
||||
BbBlockExecutor::new(evm, ctx, &self.spec, self.receipt_builder, plan, None, None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,14 @@
|
||||
pub(crate) use reth_engine_primitives::BigBlockData;
|
||||
|
||||
use crate::{
|
||||
evm::{BbBlockExecutorFactory, BbEvmPlan},
|
||||
evm::{BalIndexReader, BbBlockExecutorFactory, BbEvmPlan},
|
||||
BigBlockMap,
|
||||
};
|
||||
use alloy_consensus::Header;
|
||||
use alloy_evm::eth::EthBlockExecutionCtx;
|
||||
use alloy_evm::{
|
||||
eth::{spec::EthExecutorSpec, EthBlockExecutionCtx},
|
||||
EthEvmFactory,
|
||||
};
|
||||
use alloy_primitives::B256;
|
||||
use alloy_rpc_types::engine::ExecutionData;
|
||||
use core::convert::Infallible;
|
||||
@@ -20,8 +23,8 @@ use reth_chainspec::{ChainSpec, EthChainSpec};
|
||||
use reth_ethereum_forks::Hardforks;
|
||||
use reth_ethereum_primitives::EthPrimitives;
|
||||
use reth_evm::{
|
||||
ConfigureEngineEvm, ConfigureEvm, Database, EvmEnv, ExecutableTxIterator,
|
||||
NextBlockEnvAttributes,
|
||||
ConfigureEngineEvm, ConfigureEvm, Database, EvmEnv, EvmEnvFor, ExecutableTxIterator,
|
||||
ExecutionCtxFor, NextBlockEnvAttributes,
|
||||
};
|
||||
use reth_evm_ethereum::{EthBlockAssembler, EthEvmConfig, RethReceiptBuilder};
|
||||
use reth_primitives_traits::{SealedBlock, SealedHeader};
|
||||
@@ -29,9 +32,6 @@ use revm::primitives::hardfork::SpecId;
|
||||
use std::sync::Arc;
|
||||
use tracing::debug;
|
||||
|
||||
use alloy_evm::{eth::spec::EthExecutorSpec, EthEvmFactory};
|
||||
use reth_evm::{EvmEnvFor, ExecutionCtxFor};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Execution plan types
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -55,7 +55,7 @@ pub(crate) struct BigBlockSegment {
|
||||
///
|
||||
/// Wraps [`EthEvmConfig`] and a shared [`BigBlockMap`]. When a big-block
|
||||
/// payload is received, the plan is staged on the [`BbBlockExecutorFactory`]
|
||||
/// and consumed when the executor is created. Block hashes for inter-segment
|
||||
/// and cloned when executors are created. Block hashes for inter-segment
|
||||
/// BLOCKHASH resolution are reseeded into `State::block_hashes` at each
|
||||
/// segment boundary via a [`BlockHashSeeder`](crate::evm::BlockHashSeeder)
|
||||
/// callback injected in [`ConfigureEvm::create_executor`].
|
||||
@@ -106,6 +106,10 @@ fn seed_state_block_hashes<DB>(state: &mut &mut revm::database::State<DB>, hashe
|
||||
}
|
||||
}
|
||||
|
||||
fn read_bal_index<DB>(state: &&mut revm::database::State<DB>) -> u64 {
|
||||
state.bal_state.bal_index()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ConfigureEvm
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -144,6 +148,12 @@ where
|
||||
&self,
|
||||
block: &'a SealedBlock<reth_ethereum_primitives::Block>,
|
||||
) -> Result<EthBlockExecutionCtx<'a>, Self::Error> {
|
||||
if let Some(plan) = self.plan_for_payload_hash(&block.hash()) {
|
||||
self.executor_factory.stage_plan(plan);
|
||||
} else {
|
||||
self.executor_factory.clear_staged_plan();
|
||||
}
|
||||
|
||||
self.inner.context_for_block(block)
|
||||
}
|
||||
|
||||
@@ -159,7 +169,7 @@ where
|
||||
&'a self,
|
||||
evm: reth_evm::EvmFor<Self, &'a mut revm::database::State<DB>, I>,
|
||||
ctx: EthBlockExecutionCtx<'a>,
|
||||
) -> impl alloy_evm::block::BlockExecutorFor<
|
||||
) -> alloy_evm::block::BlockExecutorFor<
|
||||
'a,
|
||||
Self::BlockExecutorFactory,
|
||||
&'a mut revm::database::State<DB>,
|
||||
@@ -169,15 +179,16 @@ where
|
||||
DB: Database,
|
||||
I: reth_evm::InspectorFor<Self, &'a mut revm::database::State<DB>> + 'a,
|
||||
{
|
||||
// Use create_executor_with_seeder to inject a concrete seeder that
|
||||
// can reseed State::block_hashes at segment boundaries. The seeder
|
||||
// is a function pointer that knows the concrete State<DB> type,
|
||||
// allowing the generic BbBlockExecutor to reseed without additional
|
||||
// trait bounds on DB.
|
||||
let bal_index_reader: Option<BalIndexReader<&'a mut revm::database::State<DB>>> =
|
||||
Some(read_bal_index::<DB>);
|
||||
|
||||
// Inject concrete function pointers that know the `State<DB>` type so
|
||||
// the generic executor can reseed block hashes and read `bal_index`.
|
||||
self.executor_factory.create_executor_with_seeder(
|
||||
evm,
|
||||
ctx,
|
||||
Some(seed_state_block_hashes::<DB>),
|
||||
bal_index_reader,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -214,6 +225,7 @@ where
|
||||
|
||||
Ok(env)
|
||||
} else {
|
||||
self.executor_factory.clear_staged_plan();
|
||||
self.inner.evm_env_for_payload(payload)
|
||||
}
|
||||
}
|
||||
@@ -248,10 +260,12 @@ where
|
||||
/// In practice, this is called from `evm_env_for_payload` in the
|
||||
/// engine pipeline.
|
||||
pub fn stage_plan_for_payload(&self, payload_hash: &B256) {
|
||||
let bb = match self.pending.lock().unwrap().remove(payload_hash) {
|
||||
Some(bb) => bb,
|
||||
None => return,
|
||||
};
|
||||
let Some(plan) = self.plan_for_payload_hash(payload_hash) else { return };
|
||||
self.executor_factory.stage_plan(plan);
|
||||
}
|
||||
|
||||
fn plan_for_payload_hash(&self, payload_hash: &B256) -> Option<BbEvmPlan> {
|
||||
let bb = self.pending.lock().unwrap().remove(payload_hash)?;
|
||||
|
||||
let segments: Vec<_> = bb
|
||||
.env_switches
|
||||
@@ -287,6 +301,6 @@ where
|
||||
|
||||
plan.block_hashes_to_seed.sort_unstable_by_key(|(n, _)| *n);
|
||||
|
||||
self.executor_factory.stage_plan(plan);
|
||||
Some(plan)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,10 @@ use reth_ethereum_cli::chainspec::EthereumChainSpecParser;
|
||||
use reth_ethereum_primitives::Receipt;
|
||||
use reth_primitives_traits::proofs;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, future::Future};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
future::Future,
|
||||
};
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::bench::helpers::fetch_block_access_list;
|
||||
@@ -717,6 +720,17 @@ fn merge_account_changes(existing: &mut AccountChanges, incoming: AccountChanges
|
||||
existing.balance_changes.extend(incoming.balance_changes);
|
||||
existing.nonce_changes.extend(incoming.nonce_changes);
|
||||
existing.code_changes.extend(incoming.code_changes);
|
||||
|
||||
// EIP-7928 invariant: a slot must appear in either storage_changes or storage_reads,
|
||||
// not both. Per-block BALs respect this, but merging blocks can produce a slot
|
||||
// that is read in one block and changed in another. Without this normalization,
|
||||
// an empty read entry can shadow the real writes during BAL deserialization,
|
||||
// making reads of that slot fall through to stale snapshot state.
|
||||
let written: HashSet<_> =
|
||||
existing.storage_changes.iter().map(|slot_changes| slot_changes.slot).collect();
|
||||
existing.storage_reads.retain(|slot| !written.contains(slot));
|
||||
let mut seen = HashSet::with_capacity(existing.storage_reads.len());
|
||||
existing.storage_reads.retain(|slot| seen.insert(*slot));
|
||||
}
|
||||
|
||||
fn merge_slot_changes(existing: &mut Vec<SlotChanges>, incoming: Vec<SlotChanges>) {
|
||||
@@ -836,4 +850,54 @@ mod tests {
|
||||
assert_eq!(other.address, Address::repeat_byte(0x22));
|
||||
assert_eq!(other.storage_changes[0].changes[0].block_access_index, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_account_changes_normalizes_storage_reads_after_cross_block_merge() {
|
||||
let address = Address::repeat_byte(0x33);
|
||||
const A: U256 = U256::from_limbs([1, 0, 0, 0]);
|
||||
const B: U256 = U256::from_limbs([2, 0, 0, 0]);
|
||||
const C: U256 = U256::from_limbs([3, 0, 0, 0]);
|
||||
const D: U256 = U256::from_limbs([4, 0, 0, 0]);
|
||||
|
||||
// Each AccountChanges value is valid on its own: storage slots only appear in
|
||||
// either reads or changes. The invalid read/change overlap is introduced when
|
||||
// these per-block BAL entries are merged for a standalone big block.
|
||||
let mut existing = AccountChanges {
|
||||
address,
|
||||
storage_changes: vec![SlotChanges::new(A, vec![StorageChange::new(0, U256::from(10))])],
|
||||
storage_reads: vec![B, C],
|
||||
balance_changes: vec![],
|
||||
nonce_changes: vec![],
|
||||
code_changes: vec![],
|
||||
};
|
||||
|
||||
// B is read before it is written by the incoming block, and A is written before
|
||||
// it appears as a read in the incoming block. C is read in both blocks, so the
|
||||
// merge should also dedupe it. D remains read-only.
|
||||
let incoming = AccountChanges {
|
||||
address,
|
||||
storage_changes: vec![SlotChanges::new(B, vec![StorageChange::new(1, U256::from(20))])],
|
||||
storage_reads: vec![A, C, D],
|
||||
balance_changes: vec![],
|
||||
nonce_changes: vec![],
|
||||
code_changes: vec![],
|
||||
};
|
||||
|
||||
merge_account_changes(&mut existing, incoming);
|
||||
|
||||
// Written slots remain represented by storage_changes, while storage_reads only
|
||||
// keeps unique read-only slots in first-seen order.
|
||||
assert_eq!(
|
||||
existing
|
||||
.storage_changes
|
||||
.iter()
|
||||
.map(|slot_changes| slot_changes.slot)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![A, B]
|
||||
);
|
||||
assert_eq!(existing.storage_reads, vec![C, D]);
|
||||
assert!(existing.storage_reads.iter().all(|read_slot| {
|
||||
!existing.storage_changes.iter().any(|slot_changes| slot_changes.slot == *read_slot)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ reth-errors.workspace = true
|
||||
reth-execution-types.workspace = true
|
||||
reth-metrics.workspace = true
|
||||
reth-ethereum-primitives.workspace = true
|
||||
reth-primitives-traits.workspace = true
|
||||
reth-primitives-traits = { workspace = true, features = ["dashmap"] }
|
||||
reth-storage-api.workspace = true
|
||||
reth-trie.workspace = true
|
||||
|
||||
|
||||
@@ -4,26 +4,32 @@
|
||||
//! lazily on first access. This allows execution to start before the trie overlay
|
||||
//! is fully computed.
|
||||
|
||||
use crate::DeferredTrieData;
|
||||
use crate::{EthPrimitives, ExecutedBlock};
|
||||
use alloy_primitives::B256;
|
||||
use reth_primitives_traits::{
|
||||
dashmap::{self, DashMap},
|
||||
AlloyBlockHeader, NodePrimitives,
|
||||
};
|
||||
use reth_trie::{updates::TrieUpdatesSorted, HashedPostStateSorted, TrieInputSorted};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use std::sync::Arc;
|
||||
use tracing::{debug, trace};
|
||||
|
||||
/// Inputs captured for lazy overlay computation.
|
||||
#[derive(Clone)]
|
||||
struct LazyOverlayInputs {
|
||||
/// The persisted ancestor hash (anchor) this overlay should be built on.
|
||||
anchor_hash: B256,
|
||||
/// Deferred trie data handles for all in-memory blocks (newest to oldest).
|
||||
blocks: Vec<DeferredTrieData>,
|
||||
struct LazyOverlayInputs<N: NodePrimitives = EthPrimitives> {
|
||||
/// In-memory blocks from tip to anchor child.
|
||||
///
|
||||
/// Blocks must be provided in reverse chain order (newest to oldest).
|
||||
blocks: Vec<ExecutedBlock<N>>,
|
||||
}
|
||||
|
||||
/// Lazily computed trie overlay.
|
||||
///
|
||||
/// Captures the inputs needed to compute a [`TrieInputSorted`] and defers the actual
|
||||
/// computation until first access. This is conceptually similar to [`DeferredTrieData`]
|
||||
/// but for overlay computation.
|
||||
/// computation until first access.
|
||||
///
|
||||
/// Blocks must be provided in reverse chain order (newest to oldest), so the first block is the
|
||||
/// chain tip and the last block is the oldest in-memory block in the chain segment.
|
||||
///
|
||||
/// # Fast Path vs Slow Path
|
||||
///
|
||||
@@ -31,37 +37,41 @@ struct LazyOverlayInputs {
|
||||
/// matches our expected anchor, we can reuse it directly (O(1)).
|
||||
/// - **Slow path**: Otherwise, we merge all ancestor blocks' trie data into a new overlay.
|
||||
#[derive(Clone)]
|
||||
pub struct LazyOverlay {
|
||||
/// Computed result, cached after first access.
|
||||
inner: Arc<OnceLock<TrieInputSorted>>,
|
||||
pub struct LazyOverlay<N: NodePrimitives = EthPrimitives> {
|
||||
/// Computed results, cached by requested anchor hash.
|
||||
inner: Arc<DashMap<B256, Arc<TrieInputSorted>>>,
|
||||
/// Inputs for lazy computation.
|
||||
inputs: LazyOverlayInputs,
|
||||
inputs: LazyOverlayInputs<N>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for LazyOverlay {
|
||||
impl<N: NodePrimitives> std::fmt::Debug for LazyOverlay<N> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("LazyOverlay")
|
||||
.field("anchor_hash", &self.inputs.anchor_hash)
|
||||
.field(
|
||||
"oldest_block_parent_hash",
|
||||
&self.inputs.blocks.last().map(|block| block.recovered_block().parent_hash()),
|
||||
)
|
||||
.field("num_blocks", &self.inputs.blocks.len())
|
||||
.field("computed", &self.inner.get().is_some())
|
||||
.field("cached_anchors", &self.inner.len())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl LazyOverlay {
|
||||
/// Create a new lazy overlay with the given anchor hash and block handles.
|
||||
impl<N: NodePrimitives> LazyOverlay<N> {
|
||||
/// Create a new lazy overlay from in-memory blocks.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `anchor_hash` - The persisted ancestor hash this overlay is built on top of
|
||||
/// * `blocks` - Deferred trie data handles for in-memory blocks (newest to oldest)
|
||||
pub fn new(anchor_hash: B256, blocks: Vec<DeferredTrieData>) -> Self {
|
||||
Self { inner: Arc::new(OnceLock::new()), inputs: LazyOverlayInputs { anchor_hash, blocks } }
|
||||
}
|
||||
/// * `blocks` - Executed blocks in reverse chain order (newest to oldest)
|
||||
pub fn new(blocks: Vec<ExecutedBlock<N>>) -> Self {
|
||||
debug_assert!(
|
||||
blocks.windows(2).all(|window| {
|
||||
window[0].recovered_block().parent_hash() == window[1].recovered_block().hash()
|
||||
}),
|
||||
"LazyOverlay blocks must be ordered newest to oldest along a single chain"
|
||||
);
|
||||
|
||||
/// Returns the anchor hash this overlay is built on.
|
||||
pub const fn anchor_hash(&self) -> B256 {
|
||||
self.inputs.anchor_hash
|
||||
Self { inner: Default::default(), inputs: LazyOverlayInputs { blocks } }
|
||||
}
|
||||
|
||||
/// Returns the number of in-memory blocks this overlay covers.
|
||||
@@ -69,43 +79,75 @@ impl LazyOverlay {
|
||||
self.inputs.blocks.len()
|
||||
}
|
||||
|
||||
/// Returns true if the overlay has already been computed.
|
||||
pub fn is_computed(&self) -> bool {
|
||||
self.inner.get().is_some()
|
||||
/// Returns the oldest anchor hash this overlay can serve.
|
||||
///
|
||||
/// This is the parent hash of the oldest block in the stored newest-to-oldest chain segment.
|
||||
pub fn anchor_hash(&self) -> Option<B256> {
|
||||
self.inputs.blocks.last().map(|block| block.recovered_block().parent_hash())
|
||||
}
|
||||
|
||||
/// Returns the computed trie input, computing it if necessary.
|
||||
/// Returns true if there are no blocks in the overlay, or if one of the blocks has the given
|
||||
/// hash as a parent hash.
|
||||
pub fn has_anchor_hash(&self, hash: B256) -> bool {
|
||||
self.inputs.blocks.is_empty() ||
|
||||
self.inputs.blocks.iter().any(|b| b.recovered_block().parent_hash() == hash)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// Returns true if the overlay has already been computed for the requested anchor.
|
||||
pub fn is_computed(&self, anchor_hash: B256) -> bool {
|
||||
self.inner.contains_key(&anchor_hash)
|
||||
}
|
||||
|
||||
/// Returns the computed trie input for the requested anchor, computing it if necessary.
|
||||
///
|
||||
/// The first call triggers computation (which may block waiting for deferred data).
|
||||
/// Subsequent calls return the cached result immediately.
|
||||
pub fn get(&self) -> &TrieInputSorted {
|
||||
self.inner.get_or_init(|| self.compute())
|
||||
/// Subsequent calls for the same anchor return the cached result immediately.
|
||||
pub fn get(&self, anchor_hash: B256) -> Arc<TrieInputSorted> {
|
||||
match self.inner.entry(anchor_hash) {
|
||||
dashmap::Entry::Occupied(entry) => Arc::clone(entry.get()),
|
||||
dashmap::Entry::Vacant(entry) => {
|
||||
let input = self.compute(anchor_hash);
|
||||
entry.insert(Arc::clone(&input));
|
||||
input
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the overlay as (nodes, state) tuple for use with `OverlayStateProviderFactory`.
|
||||
pub fn as_overlay(&self) -> (Arc<TrieUpdatesSorted>, Arc<HashedPostStateSorted>) {
|
||||
let input = self.get();
|
||||
pub fn as_overlay(
|
||||
&self,
|
||||
anchor_hash: B256,
|
||||
) -> (Arc<TrieUpdatesSorted>, Arc<HashedPostStateSorted>) {
|
||||
let input = self.get(anchor_hash);
|
||||
(Arc::clone(&input.nodes), Arc::clone(&input.state))
|
||||
}
|
||||
|
||||
/// Compute the trie input overlay.
|
||||
fn compute(&self) -> TrieInputSorted {
|
||||
let anchor_hash = self.inputs.anchor_hash;
|
||||
fn compute(&self, anchor_hash: B256) -> Arc<TrieInputSorted> {
|
||||
let blocks = &self.inputs.blocks;
|
||||
|
||||
if blocks.is_empty() {
|
||||
debug!(target: "chain_state::lazy_overlay", "No in-memory blocks, returning empty overlay");
|
||||
return TrieInputSorted::default();
|
||||
return Default::default()
|
||||
}
|
||||
|
||||
let Some(last_index) =
|
||||
blocks.iter().position(|block| block.recovered_block().parent_hash() == anchor_hash)
|
||||
else {
|
||||
panic!(
|
||||
"LazyOverlay does not contain a block whose parent hash matches requested anchor {anchor_hash}"
|
||||
);
|
||||
};
|
||||
let blocks = &blocks[..=last_index];
|
||||
|
||||
// Fast path: Check if tip block's overlay is ready and anchor matches.
|
||||
// The tip block (first in list) has the cumulative overlay from all ancestors.
|
||||
// The tip block (first in list) has the cumulative overlay from all ancestors up to the
|
||||
// requested anchor.
|
||||
if let Some(tip) = blocks.first() {
|
||||
let data = tip.wait_cloned();
|
||||
let data = tip.trie_data();
|
||||
if let Some(anchored) = &data.anchored_trie_input {
|
||||
if anchored.anchor_hash == anchor_hash {
|
||||
trace!(target: "chain_state::lazy_overlay", %anchor_hash, "Reusing tip block's cached overlay (fast path)");
|
||||
return (*anchored.trie_input).clone();
|
||||
return Arc::clone(&anchored.trie_input);
|
||||
}
|
||||
debug!(
|
||||
target: "chain_state::lazy_overlay",
|
||||
@@ -116,23 +158,30 @@ impl LazyOverlay {
|
||||
}
|
||||
}
|
||||
|
||||
// Slow path: Merge all blocks' trie data into a new overlay.
|
||||
debug!(target: "chain_state::lazy_overlay", num_blocks = blocks.len(), "Merging blocks (slow path)");
|
||||
Self::merge_blocks(blocks)
|
||||
// Slow path: Merge the prefix of blocks from the tip back to the requested anchor.
|
||||
debug!(
|
||||
target: "chain_state::lazy_overlay",
|
||||
%anchor_hash,
|
||||
num_blocks = blocks.len(),
|
||||
"Merging blocks (slow path)"
|
||||
);
|
||||
Arc::new(Self::merge_blocks(blocks))
|
||||
}
|
||||
|
||||
/// Merge all blocks' trie data into a single [`TrieInputSorted`].
|
||||
///
|
||||
/// Blocks are ordered newest to oldest.
|
||||
fn merge_blocks(blocks: &[DeferredTrieData]) -> TrieInputSorted {
|
||||
fn merge_blocks(blocks: &[ExecutedBlock<N>]) -> TrieInputSorted {
|
||||
if blocks.is_empty() {
|
||||
return TrieInputSorted::default();
|
||||
}
|
||||
|
||||
let state =
|
||||
HashedPostStateSorted::merge_batch(blocks.iter().map(|b| b.wait_cloned().hashed_state));
|
||||
let nodes =
|
||||
TrieUpdatesSorted::merge_batch(blocks.iter().map(|b| b.wait_cloned().trie_updates));
|
||||
let state = HashedPostStateSorted::merge_batch(
|
||||
blocks.iter().map(|block| block.trie_data().hashed_state),
|
||||
);
|
||||
let nodes = TrieUpdatesSorted::merge_batch(
|
||||
blocks.iter().map(|block| block.trie_data().trie_updates),
|
||||
);
|
||||
|
||||
TrieInputSorted { state, nodes, prefix_sets: Default::default() }
|
||||
}
|
||||
@@ -141,46 +190,138 @@ impl LazyOverlay {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use reth_trie::{updates::TrieUpdates, HashedPostState};
|
||||
use crate::{test_utils::TestBlockBuilder, ComputedTrieData, EthPrimitives, ExecutedBlock};
|
||||
use alloy_primitives::U256;
|
||||
use reth_primitives_traits::Account;
|
||||
use reth_trie::{updates::TrieUpdatesSorted, HashedPostState, HashedStorage};
|
||||
use std::sync::Arc;
|
||||
|
||||
fn empty_deferred(anchor: B256) -> DeferredTrieData {
|
||||
DeferredTrieData::pending(
|
||||
Arc::new(HashedPostState::default()),
|
||||
Arc::new(TrieUpdates::default()),
|
||||
anchor,
|
||||
Vec::new(),
|
||||
fn with_unique_state(
|
||||
block: &ExecutedBlock<EthPrimitives>,
|
||||
id: u8,
|
||||
) -> ExecutedBlock<EthPrimitives> {
|
||||
let hashed_address = B256::with_last_byte(id);
|
||||
let hashed_slot = B256::with_last_byte(id.saturating_add(32));
|
||||
let hashed_state = HashedPostState::default()
|
||||
.with_accounts([(hashed_address, Some(Account::default()))])
|
||||
.with_storages([(
|
||||
hashed_address,
|
||||
HashedStorage::from_iter(false, [(hashed_slot, U256::from(id))]),
|
||||
)])
|
||||
.into_sorted();
|
||||
|
||||
ExecutedBlock::new(
|
||||
Arc::clone(&block.recovered_block),
|
||||
Arc::clone(&block.execution_output),
|
||||
ComputedTrieData::without_trie_input(
|
||||
Arc::new(hashed_state),
|
||||
Arc::new(TrieUpdatesSorted::default()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_blocks_returns_default() {
|
||||
let overlay = LazyOverlay::new(B256::ZERO, vec![]);
|
||||
let result = overlay.get();
|
||||
assert!(result.state.is_empty());
|
||||
assert!(result.nodes.is_empty());
|
||||
fn test_blocks() -> Vec<ExecutedBlock<EthPrimitives>> {
|
||||
TestBlockBuilder::eth()
|
||||
.get_executed_blocks(1..4)
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.rev()
|
||||
.enumerate()
|
||||
.map(|(index, block)| with_unique_state(&block, index as u8 + 1))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_block_uses_data_directly() {
|
||||
let anchor = B256::random();
|
||||
let deferred = empty_deferred(anchor);
|
||||
let overlay = LazyOverlay::new(anchor, vec![deferred]);
|
||||
let block = TestBlockBuilder::eth().get_executed_block_with_number(1, B256::random());
|
||||
let anchor_hash = block.recovered_block().parent_hash();
|
||||
let overlay = LazyOverlay::new(vec![block]);
|
||||
|
||||
assert!(!overlay.is_computed());
|
||||
let _ = overlay.get();
|
||||
assert!(overlay.is_computed());
|
||||
assert!(!overlay.is_computed(anchor_hash));
|
||||
let _ = overlay.get(anchor_hash);
|
||||
assert!(overlay.is_computed(anchor_hash));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cached_after_first_access() {
|
||||
let overlay = LazyOverlay::new(B256::ZERO, vec![]);
|
||||
fn caches_results_per_anchor() {
|
||||
let blocks = test_blocks();
|
||||
let prefix_anchor = blocks[2].recovered_block().hash();
|
||||
let full_anchor = blocks[2].recovered_block().parent_hash();
|
||||
let overlay = LazyOverlay::new(blocks);
|
||||
|
||||
// First access computes
|
||||
let _ = overlay.get();
|
||||
assert!(overlay.is_computed());
|
||||
let prefix = overlay.get(prefix_anchor);
|
||||
let full = overlay.get(full_anchor);
|
||||
|
||||
// Second access uses cache
|
||||
let _ = overlay.get();
|
||||
assert!(overlay.is_computed());
|
||||
assert!(overlay.is_computed(prefix_anchor));
|
||||
assert!(overlay.is_computed(full_anchor));
|
||||
assert!(!Arc::ptr_eq(&prefix, &full));
|
||||
assert!(Arc::ptr_eq(&prefix, &overlay.get(prefix_anchor)));
|
||||
assert!(Arc::ptr_eq(&full, &overlay.get(full_anchor)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requested_anchor_limits_the_merged_prefix() {
|
||||
let blocks = test_blocks();
|
||||
let prefix_anchor = blocks[2].recovered_block().hash();
|
||||
let expected = LazyOverlay::merge_blocks(&blocks[..2]);
|
||||
let overlay = LazyOverlay::new(blocks);
|
||||
let actual = overlay.get(prefix_anchor);
|
||||
|
||||
assert_eq!(actual.nodes.as_ref(), expected.nodes.as_ref());
|
||||
assert_eq!(actual.state.as_ref(), expected.state.as_ref());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn anchor_hash_returns_oldest_served_anchor() {
|
||||
let blocks = test_blocks();
|
||||
let expected_anchor = blocks.last().unwrap().recovered_block().parent_hash();
|
||||
let overlay = LazyOverlay::new(blocks);
|
||||
|
||||
assert_eq!(overlay.anchor_hash(), Some(expected_anchor));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reuses_tip_overlay_when_anchor_matches() {
|
||||
let mut blocks = test_blocks();
|
||||
let prefix_anchor = blocks[2].recovered_block().hash();
|
||||
let tip_overlay = Arc::new(LazyOverlay::merge_blocks(&blocks[..2]));
|
||||
let tip_data = blocks[0].trie_data();
|
||||
|
||||
blocks[0] = ExecutedBlock::new(
|
||||
Arc::clone(&blocks[0].recovered_block),
|
||||
Arc::clone(&blocks[0].execution_output),
|
||||
ComputedTrieData::with_trie_input(
|
||||
tip_data.hashed_state,
|
||||
tip_data.trie_updates,
|
||||
prefix_anchor,
|
||||
Arc::clone(&tip_overlay),
|
||||
),
|
||||
);
|
||||
|
||||
let overlay = LazyOverlay::new(blocks);
|
||||
let actual = overlay.get(prefix_anchor);
|
||||
|
||||
assert!(Arc::ptr_eq(&actual, &tip_overlay));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "LazyOverlay does not contain a block whose parent hash matches requested anchor"
|
||||
)]
|
||||
fn missing_anchor_panics() {
|
||||
let blocks = test_blocks();
|
||||
let missing_anchor = blocks[0].recovered_block().hash();
|
||||
let overlay = LazyOverlay::new(blocks);
|
||||
|
||||
let _ = overlay.get(missing_anchor);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "LazyOverlay blocks must be ordered newest to oldest along a single chain"
|
||||
)]
|
||||
fn misordered_blocks_panic() {
|
||||
let blocks: Vec<_> = TestBlockBuilder::eth().get_executed_blocks(1..3).collect();
|
||||
let _ = LazyOverlay::new(blocks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
//! state), compacts MDBX, then runs the pipeline to rebuild them.
|
||||
|
||||
use crate::common::CliNodeTypes;
|
||||
use alloy_primitives::Address;
|
||||
use clap::Parser;
|
||||
use reth_db::{
|
||||
mdbx::{self, ffi},
|
||||
@@ -132,8 +133,12 @@ impl Command {
|
||||
.and_then(|cp| cp.block_number)
|
||||
.map_or(0, |b| b + 1);
|
||||
|
||||
let mut writer =
|
||||
sf_provider.get_writer(first_block, StaticFileSegment::AccountChangeSets)?;
|
||||
// The writer always starts at the fixed range boundary (e.g. 2500000) which may be
|
||||
// earlier than first_block (e.g. 2603897 from prune checkpoint).
|
||||
let mut writer = sf_provider.latest_writer(StaticFileSegment::AccountChangeSets)?;
|
||||
if first_block > 0 {
|
||||
writer.ensure_at_block(first_block - 1)?;
|
||||
}
|
||||
|
||||
let mut count = 0u64;
|
||||
let mut walker = cursor.walk(Some(first_block))?.peekable();
|
||||
@@ -174,11 +179,15 @@ impl Command {
|
||||
.and_then(|cp| cp.block_number)
|
||||
.map_or(0, |b| b + 1);
|
||||
|
||||
let mut writer =
|
||||
sf_provider.get_writer(first_block, StaticFileSegment::StorageChangeSets)?;
|
||||
// The writer always starts at the fixed range boundary (e.g. 2500000) which may be
|
||||
// earlier than first_block (e.g. 2603897 from prune checkpoint).
|
||||
let mut writer = sf_provider.latest_writer(StaticFileSegment::StorageChangeSets)?;
|
||||
if first_block > 0 {
|
||||
writer.ensure_at_block(first_block - 1)?;
|
||||
}
|
||||
|
||||
let mut count = 0u64;
|
||||
let mut walker = cursor.walk(Some(Default::default()))?.peekable();
|
||||
let mut walker = cursor.walk(Some((first_block, Address::ZERO).into()))?.peekable();
|
||||
|
||||
for block in first_block..=tip {
|
||||
let mut entries = Vec::new();
|
||||
@@ -238,6 +247,18 @@ impl Command {
|
||||
.map_or(0, |b| b + 1);
|
||||
let first_block = prune_start.max(existing.map_or(0, |b| b + 1));
|
||||
|
||||
// The writer always starts at the fixed range boundary (e.g. 2500000) which may be
|
||||
// earlier than first_block (e.g. 2603897 from prune checkpoint).
|
||||
if first_block > 0 {
|
||||
let mut writer = sf_provider.latest_writer(StaticFileSegment::Receipts)?;
|
||||
writer.ensure_at_block(first_block - 1)?;
|
||||
writer.commit()?;
|
||||
}
|
||||
|
||||
let before = sf_provider
|
||||
.get_highest_static_file_tx(StaticFileSegment::Receipts)
|
||||
.map_or(0, |tx| tx + 1);
|
||||
|
||||
let block_range = first_block..=tip;
|
||||
|
||||
let segment = reth_static_file::segments::Receipts;
|
||||
@@ -245,7 +266,11 @@ impl Command {
|
||||
|
||||
sf_provider.commit()?;
|
||||
|
||||
info!(target: "reth::cli", "Receipts migrated");
|
||||
let after = sf_provider
|
||||
.get_highest_static_file_tx(StaticFileSegment::Receipts)
|
||||
.map_or(0, |tx| tx + 1);
|
||||
let count = after - before;
|
||||
info!(target: "reth::cli", count, "Receipts migrated");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -191,8 +191,10 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>
|
||||
}
|
||||
};
|
||||
|
||||
let bal= executor.take_bal();
|
||||
|
||||
if let Err(err) = consensus
|
||||
.validate_block_post_execution(&block, &result, None)
|
||||
.validate_block_post_execution(&block, &result, None,bal)
|
||||
.wrap_err_with(|| {
|
||||
format!(
|
||||
"Failed to validate block {} {}",
|
||||
|
||||
@@ -18,6 +18,7 @@ reth-primitives-traits.workspace = true
|
||||
# ethereum
|
||||
alloy-primitives.workspace = true
|
||||
alloy-consensus.workspace = true
|
||||
alloy-eip7928.workspace = true
|
||||
|
||||
# misc
|
||||
auto_impl.workspace = true
|
||||
@@ -29,10 +30,9 @@ std = [
|
||||
"reth-primitives-traits/std",
|
||||
"alloy-primitives/std",
|
||||
"alloy-consensus/std",
|
||||
"reth-primitives-traits/std",
|
||||
"alloy-eip7928/std",
|
||||
"reth-execution-types/std",
|
||||
"thiserror/std",
|
||||
"alloy-eip7928/std",
|
||||
]
|
||||
test-utils = [
|
||||
"reth-primitives-traits/test-utils",
|
||||
]
|
||||
test-utils = ["reth-primitives-traits/test-utils"]
|
||||
|
||||
@@ -38,6 +38,7 @@ use alloc::{
|
||||
vec::Vec,
|
||||
};
|
||||
use alloy_consensus::Header;
|
||||
use alloy_eip7928::BlockAccessList;
|
||||
use alloy_primitives::{BlockHash, BlockNumber, Bloom, B256};
|
||||
use core::{error::Error, fmt::Display};
|
||||
|
||||
@@ -85,6 +86,7 @@ pub trait FullConsensus<N: NodePrimitives>: Consensus<N::Block> {
|
||||
block: &RecoveredBlock<N::Block>,
|
||||
result: &BlockExecutionResult<N::Receipt>,
|
||||
receipt_root_bloom: Option<ReceiptRootBloom>,
|
||||
block_access_list: Option<BlockAccessList>,
|
||||
) -> Result<(), ConsensusError>;
|
||||
}
|
||||
|
||||
@@ -474,6 +476,12 @@ pub enum ConsensusError {
|
||||
/// EIP-7825: Transaction gas limit exceeds maximum allowed
|
||||
#[error(transparent)]
|
||||
TransactionGasLimitTooHigh(Box<TxGasLimitTooHighErr>),
|
||||
/// Error when an unexpected block access list cost is encountered.
|
||||
#[error("block access list exceeds gas limit")]
|
||||
BlockAccessListExceedsGasLimit,
|
||||
/// Error when the block access list hash doesn't match the expected value.
|
||||
#[error("block access list hash mismatch: {0}")]
|
||||
BlockAccessListHashMismatch(GotExpectedBoxed<B256>),
|
||||
/// Any additional consensus error, for example L2-specific errors.
|
||||
#[error(transparent)]
|
||||
Other(#[from] Arc<dyn Error + Send + Sync>),
|
||||
@@ -519,6 +527,23 @@ impl ConsensusError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates the block access list against the gas limit.
|
||||
///
|
||||
/// EIP-7925 specifies that the total cost of the block access list items must not exceed
|
||||
/// the gas limit. Each item costs `ITEM_COST` gas.
|
||||
pub fn validate_block_access_list_gas(
|
||||
block_access_list: Option<&alloy_eip7928::BlockAccessList>,
|
||||
gas_limit: u64,
|
||||
) -> Result<(), ConsensusError> {
|
||||
if let Some(bal) = block_access_list {
|
||||
let bal_items = alloy_eip7928::total_bal_items(bal);
|
||||
if bal_items > gas_limit / alloy_eip7928::ITEM_COST as u64 {
|
||||
return Err(ConsensusError::BlockAccessListExceedsGasLimit)
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl From<InvalidTransactionError> for ConsensusError {
|
||||
fn from(value: InvalidTransactionError) -> Self {
|
||||
Self::InvalidTransaction(value)
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
use crate::{Consensus, ConsensusError, FullConsensus, HeaderValidator, ReceiptRootBloom};
|
||||
use alloc::sync::Arc;
|
||||
use alloy_eip7928::BlockAccessList;
|
||||
use reth_execution_types::BlockExecutionResult;
|
||||
use reth_primitives_traits::{Block, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader};
|
||||
|
||||
@@ -77,6 +78,7 @@ impl<N: NodePrimitives> FullConsensus<N> for NoopConsensus {
|
||||
_block: &RecoveredBlock<N::Block>,
|
||||
_result: &BlockExecutionResult<N::Receipt>,
|
||||
_receipt_root_bloom: Option<ReceiptRootBloom>,
|
||||
_block_access_list: Option<BlockAccessList>,
|
||||
) -> Result<(), ConsensusError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{Consensus, ConsensusError, FullConsensus, HeaderValidator, ReceiptRootBloom};
|
||||
use alloy_eip7928::BlockAccessList;
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
use reth_execution_types::BlockExecutionResult;
|
||||
use reth_primitives_traits::{Block, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader};
|
||||
@@ -52,6 +53,7 @@ impl<N: NodePrimitives> FullConsensus<N> for TestConsensus {
|
||||
_block: &RecoveredBlock<N::Block>,
|
||||
_result: &BlockExecutionResult<N::Receipt>,
|
||||
_receipt_root_bloom: Option<ReceiptRootBloom>,
|
||||
_block_access_list: Option<BlockAccessList>,
|
||||
) -> Result<(), ConsensusError> {
|
||||
if self.fail_validation() {
|
||||
Err(ConsensusError::BaseFeeMissing)
|
||||
|
||||
@@ -239,7 +239,7 @@ impl Default for TreeConfig {
|
||||
share_execution_cache_with_payload_builder: false,
|
||||
share_sparse_trie_with_payload_builder: false,
|
||||
suppress_persistence_during_build: false,
|
||||
disable_bal_parallel_execution: false,
|
||||
disable_bal_parallel_execution: true,
|
||||
disable_bal_parallel_state_root: false,
|
||||
disable_bal_batch_io: false,
|
||||
#[cfg(feature = "trie-debug")]
|
||||
@@ -316,7 +316,7 @@ impl TreeConfig {
|
||||
share_execution_cache_with_payload_builder,
|
||||
share_sparse_trie_with_payload_builder,
|
||||
suppress_persistence_during_build: false,
|
||||
disable_bal_parallel_execution: false,
|
||||
disable_bal_parallel_execution: true,
|
||||
disable_bal_parallel_state_root: false,
|
||||
disable_bal_batch_io: false,
|
||||
#[cfg(feature = "trie-debug")]
|
||||
|
||||
@@ -1511,9 +1511,9 @@ where
|
||||
// Re-prepare overlay for the current canonical head with the new anchor.
|
||||
// Spawn a background task to trigger computation so it's ready when the next payload
|
||||
// arrives.
|
||||
if let Some(overlay) = self.state.tree_state.prepare_canonical_overlay() {
|
||||
if let Some(prepared) = self.state.tree_state.prepare_canonical_overlay() {
|
||||
self.runtime.spawn_blocking_named("prepare-overlay", move || {
|
||||
let _ = overlay.get();
|
||||
let _ = prepared.overlay.get(prepared.anchor_hash);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -970,7 +970,7 @@ mod tests {
|
||||
use rand::Rng;
|
||||
use reth_chainspec::ChainSpec;
|
||||
use reth_db_common::init::init_genesis;
|
||||
use reth_ethereum_primitives::TransactionSigned;
|
||||
use reth_ethereum_primitives::{EthPrimitives, TransactionSigned};
|
||||
use reth_evm::OnStateHook;
|
||||
use reth_evm_ethereum::EthEvmConfig;
|
||||
use reth_primitives_traits::{Account, Recovered, StorageEntry};
|
||||
@@ -1249,7 +1249,7 @@ mod tests {
|
||||
StateProviderBuilder::new(provider_factory.clone(), genesis_hash, None),
|
||||
OverlayStateProviderFactory::new(
|
||||
provider_factory,
|
||||
OverlayBuilder::new(ChangesetCache::new()),
|
||||
OverlayBuilder::<EthPrimitives>::new(genesis_hash, ChangesetCache::new()),
|
||||
),
|
||||
&TreeConfig::default(),
|
||||
);
|
||||
|
||||
@@ -361,8 +361,44 @@ where
|
||||
let (prefetch_tx, prefetch_rx) = oneshot::channel();
|
||||
let (stream_tx, stream_rx) = oneshot::channel();
|
||||
|
||||
if let Some(to_sparse_trie_task) = to_sparse_trie_task {
|
||||
let stream_ctx = ctx.clone();
|
||||
executor.bal_streaming_pool().spawn(move || {
|
||||
let branch_span = debug_span!(
|
||||
target: "engine::tree::payload_processor::prewarm",
|
||||
parent: &stream_parent_span,
|
||||
"bal_hashed_state_stream",
|
||||
bal_accounts = stream_bal.as_bal().len(),
|
||||
);
|
||||
let provider_parent_span = branch_span.clone();
|
||||
let _span = branch_span.entered();
|
||||
|
||||
stream_bal.as_bal().par_iter().for_each_init(
|
||||
|| {
|
||||
(
|
||||
stream_ctx.clone(),
|
||||
None::<Box<dyn AccountReader>>,
|
||||
provider_parent_span.clone(),
|
||||
)
|
||||
},
|
||||
|(ctx, provider, parent_span), account_changes| {
|
||||
ctx.send_bal_hashed_state(
|
||||
parent_span,
|
||||
provider,
|
||||
account_changes,
|
||||
&to_sparse_trie_task,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
let _ = to_sparse_trie_task.send(StateRootMessage::FinishedStateUpdates);
|
||||
let _ = stream_tx.send(());
|
||||
});
|
||||
} else {
|
||||
let _ = stream_tx.send(());
|
||||
}
|
||||
|
||||
if ctx.saved_cache.is_some() {
|
||||
let prefetch_ctx = ctx.clone();
|
||||
executor.prewarming_pool().spawn(move || {
|
||||
let branch_span = debug_span!(
|
||||
target: "engine::tree::payload_processor::prewarm",
|
||||
@@ -376,7 +412,7 @@ where
|
||||
prefetch_bal.as_bal().par_iter().for_each_init(
|
||||
|| {
|
||||
(
|
||||
prefetch_ctx.clone(),
|
||||
ctx.clone(),
|
||||
None::<CachedStateProvider<reth_provider::StateProviderBox, true>>,
|
||||
provider_parent_span.clone(),
|
||||
)
|
||||
@@ -395,36 +431,6 @@ where
|
||||
let _ = prefetch_tx.send(());
|
||||
}
|
||||
|
||||
if let Some(to_sparse_trie_task) = to_sparse_trie_task {
|
||||
executor.bal_streaming_pool().spawn(move || {
|
||||
let branch_span = debug_span!(
|
||||
target: "engine::tree::payload_processor::prewarm",
|
||||
parent: &stream_parent_span,
|
||||
"bal_hashed_state_stream",
|
||||
bal_accounts = stream_bal.as_bal().len(),
|
||||
);
|
||||
let provider_parent_span = branch_span.clone();
|
||||
let _span = branch_span.entered();
|
||||
|
||||
stream_bal.as_bal().par_iter().for_each_init(
|
||||
|| (ctx.clone(), None::<Box<dyn AccountReader>>, provider_parent_span.clone()),
|
||||
|(ctx, provider, parent_span), account_changes| {
|
||||
ctx.send_bal_hashed_state(
|
||||
parent_span,
|
||||
provider,
|
||||
account_changes,
|
||||
&to_sparse_trie_task,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
let _ = to_sparse_trie_task.send(StateRootMessage::FinishedStateUpdates);
|
||||
let _ = stream_tx.send(());
|
||||
});
|
||||
} else {
|
||||
let _ = stream_tx.send(());
|
||||
}
|
||||
|
||||
prefetch_rx
|
||||
.blocking_recv()
|
||||
.expect("BAL prefetch task dropped without signaling completion");
|
||||
@@ -751,7 +757,9 @@ where
|
||||
provider: &mut Option<CachedStateProvider<reth_provider::StateProviderBox, true>>,
|
||||
account: &alloy_eip7928::AccountChanges,
|
||||
) {
|
||||
if account.storage_changes.is_empty() && account.storage_reads.is_empty() {
|
||||
if self.disable_bal_batch_io ||
|
||||
(account.storage_changes.is_empty() && account.storage_reads.is_empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -896,6 +896,7 @@ mod tests {
|
||||
use reth_provider::{
|
||||
providers::{OverlayBuilder, OverlayStateProviderFactory},
|
||||
test_utils::create_test_provider_factory,
|
||||
ChainSpecProvider,
|
||||
};
|
||||
use reth_trie_db::ChangesetCache;
|
||||
use reth_trie_parallel::proof_task::ProofTaskCtx;
|
||||
@@ -984,9 +985,13 @@ mod tests {
|
||||
fn run_returns_parent_root_without_revealing_blind_trie_when_no_state_updates() {
|
||||
let runtime = reth_tasks::Runtime::test();
|
||||
let provider_factory = create_test_provider_factory();
|
||||
let anchor_hash = provider_factory.chain_spec().genesis_hash();
|
||||
let overlay_factory = OverlayStateProviderFactory::new(
|
||||
provider_factory,
|
||||
OverlayBuilder::new(ChangesetCache::new()),
|
||||
OverlayBuilder::<reth_chain_state::EthPrimitives>::new(
|
||||
anchor_hash,
|
||||
ChangesetCache::new(),
|
||||
),
|
||||
);
|
||||
let proof_worker_handle =
|
||||
ProofWorkerHandle::new(&runtime, ProofTaskCtx::new(overlay_factory), false);
|
||||
|
||||
@@ -62,7 +62,9 @@ use crate::tree::payload_processor::receipt_root_task::{IndexedReceipt, ReceiptR
|
||||
use reth_chain_state::{
|
||||
CanonicalInMemoryState, DeferredTrieData, ExecutedBlock, ExecutionTimingStats, LazyOverlay,
|
||||
};
|
||||
use reth_consensus::{ConsensusError, FullConsensus, ReceiptRootBloom};
|
||||
use reth_consensus::{
|
||||
validate_block_access_list_gas, ConsensusError, FullConsensus, ReceiptRootBloom,
|
||||
};
|
||||
use reth_engine_primitives::{
|
||||
ConfigureEngineEvm, ExecutableTxIterator, ExecutionPayload, InvalidBlockHook, PayloadValidator,
|
||||
};
|
||||
@@ -527,8 +529,7 @@ where
|
||||
// Create overlay factory for payload processor (StateRootTask path needs it for
|
||||
// multiproofs)
|
||||
let provider_factory = self.provider.clone();
|
||||
let overlay_builder = OverlayBuilder::new(self.changeset_cache.clone())
|
||||
.with_block_hash(Some(anchor_hash))
|
||||
let overlay_builder = OverlayBuilder::<N>::new(anchor_hash, self.changeset_cache.clone())
|
||||
.with_lazy_overlay(lazy_overlay);
|
||||
let overlay_factory =
|
||||
OverlayStateProviderFactory::new(provider_factory.clone(), overlay_builder.clone());
|
||||
@@ -569,7 +570,7 @@ where
|
||||
// The receipt root task is spawned before execution and receives receipts incrementally
|
||||
// as transactions complete, allowing parallel computation during execution.
|
||||
let execute_block_start = Instant::now();
|
||||
let (output, senders, receipt_root_rx) =
|
||||
let (output, senders, receipt_root_rx, built_bal) =
|
||||
match self.execute_block(state_provider, env, &input, &mut handle) {
|
||||
Ok(output) => output,
|
||||
Err(err) => return self.handle_execution_error(input, err, &parent_block),
|
||||
@@ -651,6 +652,7 @@ where
|
||||
transaction_root,
|
||||
receipt_root_bloom,
|
||||
hashed_state,
|
||||
built_bal
|
||||
),
|
||||
block
|
||||
);
|
||||
@@ -907,6 +909,7 @@ where
|
||||
BlockExecutionOutput<N::Receipt>,
|
||||
Vec<Address>,
|
||||
tokio::sync::oneshot::Receiver<(B256, alloy_primitives::Bloom)>,
|
||||
Option<BlockAccessList>,
|
||||
),
|
||||
InsertBlockErrorKind,
|
||||
>
|
||||
@@ -914,15 +917,29 @@ where
|
||||
S: StateProvider + Send,
|
||||
Err: core::error::Error + Send + Sync + 'static,
|
||||
V: PayloadValidator<T, Block = N::Block>,
|
||||
T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
|
||||
T: PayloadTypes<
|
||||
BuiltPayload: BuiltPayload<Primitives = N>,
|
||||
ExecutionData: ExecutionPayload,
|
||||
>,
|
||||
Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
|
||||
{
|
||||
debug!(target: "engine::tree::payload_validator", "Executing block");
|
||||
|
||||
if let Some(bal_opt) = input.block_access_list() {
|
||||
let bal = bal_opt.map_err(BlockExecutionError::other)?;
|
||||
validate_block_access_list_gas(Some(&bal), input.gas_limit())
|
||||
.map_err(|e| {
|
||||
debug!(target: "engine::tree::payload_validator", "BAL is invalid since it contains more items than the gas limit allows");
|
||||
InsertBlockErrorKind::Consensus(e)
|
||||
})?
|
||||
}
|
||||
|
||||
let has_bal = input.block_access_list().is_some();
|
||||
let mut db = debug_span!(target: "engine::tree", "build_state_db").in_scope(|| {
|
||||
State::builder()
|
||||
.with_database(StateProviderDatabase::new(state_provider))
|
||||
.with_bundle_update()
|
||||
.with_bal_builder_if(has_bal)
|
||||
.build()
|
||||
});
|
||||
|
||||
@@ -981,6 +998,7 @@ where
|
||||
handle.iter_transactions(),
|
||||
&receipt_tx,
|
||||
&executed_tx_index,
|
||||
has_bal,
|
||||
)?;
|
||||
drop(receipt_tx);
|
||||
|
||||
@@ -995,6 +1013,11 @@ where
|
||||
debug_span!(target: "engine::tree", "merge_transitions")
|
||||
.in_scope(|| db.merge_transitions(BundleRetention::Reverts));
|
||||
|
||||
// Extract the built bal if payload has bal
|
||||
let built_bal = if has_bal { db.take_built_alloy_bal() } else { None };
|
||||
|
||||
tracing::debug!(has_bal = built_bal.is_some(), "Built BAL");
|
||||
|
||||
let output = BlockExecutionOutput { result, state: db.take_bundle() };
|
||||
|
||||
let execution_duration = execution_start.elapsed();
|
||||
@@ -1002,7 +1025,7 @@ where
|
||||
self.metrics.record_block_execution_gas_bucket(output.result.gas_used, execution_duration);
|
||||
debug!(target: "engine::tree::payload_validator", elapsed = ?execution_duration, "Executed block");
|
||||
|
||||
Ok((output, senders, result_rx))
|
||||
Ok((output, senders, result_rx, built_bal))
|
||||
}
|
||||
|
||||
/// Executes transactions and collects senders, streaming receipts to a background task.
|
||||
@@ -1014,18 +1037,20 @@ where
|
||||
/// - Collecting transaction senders for later use
|
||||
///
|
||||
/// Returns the executor (for finalization) and the collected senders.
|
||||
fn execute_transactions<E, Tx, InnerTx, Err>(
|
||||
fn execute_transactions<'a, E, Tx, InnerTx, Err, DB>(
|
||||
&self,
|
||||
mut executor: E,
|
||||
transaction_count: usize,
|
||||
transactions: impl Iterator<Item = Result<Tx, Err>>,
|
||||
receipt_tx: &crossbeam_channel::Sender<IndexedReceipt<N::Receipt>>,
|
||||
executed_tx_index: &AtomicUsize,
|
||||
has_bal: bool,
|
||||
) -> Result<(E, Vec<Address>), BlockExecutionError>
|
||||
where
|
||||
E: BlockExecutor<Receipt = N::Receipt>,
|
||||
E: BlockExecutor<Receipt = N::Receipt, Evm: alloy_evm::Evm<DB = &'a mut State<DB>>>,
|
||||
Tx: alloy_evm::block::ExecutableTx<E> + alloy_evm::RecoveredTx<InnerTx>,
|
||||
InnerTx: TxHashRef,
|
||||
DB: revm::Database + 'a,
|
||||
Err: core::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
let mut senders = Vec::with_capacity(transaction_count);
|
||||
@@ -1036,6 +1061,11 @@ where
|
||||
.in_scope(|| executor.apply_pre_execution_changes())?;
|
||||
self.metrics.record_pre_execution(pre_exec_start.elapsed());
|
||||
|
||||
// Bump BAL index after pre-execution changes (EIP-7928: index 0 is pre-execution)
|
||||
if has_bal {
|
||||
executor.evm_mut().db_mut().bump_bal_index();
|
||||
}
|
||||
|
||||
// Execute transactions
|
||||
let exec_span = debug_span!(target: "engine::tree", "execution").entered();
|
||||
let mut transactions = transactions.into_iter();
|
||||
@@ -1080,6 +1110,10 @@ where
|
||||
let _ = receipt_tx.send(IndexedReceipt::new(tx_index, receipt.clone()));
|
||||
}
|
||||
}
|
||||
// Bump BAL index after each transaction (EIP-7928)
|
||||
if has_bal {
|
||||
executor.evm_mut().db_mut().bump_bal_index();
|
||||
}
|
||||
}
|
||||
drop(exec_span);
|
||||
|
||||
@@ -1100,7 +1134,7 @@ where
|
||||
fn compute_state_root_parallel(
|
||||
&self,
|
||||
provider_factory: P,
|
||||
overlay_builder: OverlayBuilder,
|
||||
overlay_builder: OverlayBuilder<N>,
|
||||
hashed_state: &LazyHashedPostState,
|
||||
) -> Result<(B256, TrieUpdates), ParallelStateRootError> {
|
||||
let hashed_state = hashed_state.get();
|
||||
@@ -1245,7 +1279,7 @@ where
|
||||
&self,
|
||||
state_provider_builder: StateProviderBuilder<N, P>,
|
||||
provider_factory: P,
|
||||
overlay_builder: OverlayBuilder,
|
||||
overlay_builder: OverlayBuilder<N>,
|
||||
hashed_state: &LazyHashedPostState,
|
||||
task_trie_updates: TrieUpdates,
|
||||
) -> bool {
|
||||
@@ -1363,6 +1397,7 @@ where
|
||||
transaction_root: Option<B256>,
|
||||
receipt_root_bloom: Option<ReceiptRootBloom>,
|
||||
hashed_state: LazyHashedPostState,
|
||||
built_bal: Option<BlockAccessList>,
|
||||
) -> Result<LazyHashedPostState, InsertBlockErrorKind>
|
||||
where
|
||||
V: PayloadValidator<T, Block = N::Block>,
|
||||
@@ -1389,9 +1424,13 @@ where
|
||||
let _enter =
|
||||
debug_span!(target: "engine::tree::payload_validator", "validate_block_post_execution")
|
||||
.entered();
|
||||
if let Err(err) =
|
||||
self.consensus.validate_block_post_execution(block, output, receipt_root_bloom)
|
||||
{
|
||||
|
||||
if let Err(err) = self.consensus.validate_block_post_execution(
|
||||
block,
|
||||
output,
|
||||
receipt_root_bloom,
|
||||
built_bal,
|
||||
) {
|
||||
// call post-block hook
|
||||
self.on_invalid_block(parent_block, block, output, None, ctx.state_mut());
|
||||
return Err(err.into())
|
||||
@@ -1448,7 +1487,7 @@ where
|
||||
env: ExecutionEnv<Evm>,
|
||||
txs: T,
|
||||
provider_builder: StateProviderBuilder<N, P>,
|
||||
overlay_factory: OverlayStateProviderFactory<P>,
|
||||
overlay_factory: OverlayStateProviderFactory<P, N>,
|
||||
strategy: StateRootStrategy,
|
||||
) -> Result<
|
||||
PayloadHandle<
|
||||
@@ -1568,7 +1607,7 @@ where
|
||||
fn get_parent_lazy_overlay(
|
||||
parent_hash: B256,
|
||||
state: &EngineApiTreeState<N>,
|
||||
) -> (Option<LazyOverlay>, B256) {
|
||||
) -> (Option<LazyOverlay<N>>, B256) {
|
||||
// Get blocks leading to the parent to determine the anchor
|
||||
let (anchor_hash, blocks) =
|
||||
state.tree_state.blocks_by_hash(parent_hash).unwrap_or_else(|| (parent_hash, vec![]));
|
||||
@@ -1596,10 +1635,7 @@ where
|
||||
"Creating lazy overlay for in-memory blocks"
|
||||
);
|
||||
|
||||
// Extract deferred trie data handles (non-blocking)
|
||||
let handles: Vec<DeferredTrieData> = blocks.iter().map(|b| b.trie_data_handle()).collect();
|
||||
|
||||
(Some(LazyOverlay::new(anchor_hash, handles)), anchor_hash)
|
||||
(Some(LazyOverlay::new(blocks)), anchor_hash)
|
||||
}
|
||||
|
||||
/// Spawns a background task to compute and sort trie data for the executed block.
|
||||
@@ -2033,8 +2069,7 @@ where
|
||||
let (lazy_overlay, anchor_hash) = Self::get_parent_lazy_overlay(parent_hash, state);
|
||||
let overlay_factory = OverlayStateProviderFactory::new(
|
||||
self.provider.clone(),
|
||||
OverlayBuilder::new(self.changeset_cache.clone())
|
||||
.with_block_hash(Some(anchor_hash))
|
||||
OverlayBuilder::<N>::new(anchor_hash, self.changeset_cache.clone())
|
||||
.with_lazy_overlay(lazy_overlay),
|
||||
);
|
||||
|
||||
|
||||
@@ -266,6 +266,7 @@ mod tests {
|
||||
state_gas_used: 0,
|
||||
reservoir: 0,
|
||||
gas_refunded: 0,
|
||||
refill_amount: 0,
|
||||
bytes: Bytes::default(),
|
||||
})
|
||||
})
|
||||
@@ -280,6 +281,7 @@ mod tests {
|
||||
state_gas_used: 0,
|
||||
reservoir: 0,
|
||||
gas_refunded: 0,
|
||||
refill_amount: 0,
|
||||
bytes: alloy_primitives::Bytes::copy_from_slice(b"cached_result"),
|
||||
};
|
||||
|
||||
@@ -314,6 +316,7 @@ mod tests {
|
||||
state_gas_used: 0,
|
||||
reservoir: 0,
|
||||
gas_refunded: 0,
|
||||
refill_amount: 0,
|
||||
bytes: alloy_primitives::Bytes::copy_from_slice(b"output_from_precompile_1"),
|
||||
})
|
||||
}
|
||||
@@ -331,6 +334,7 @@ mod tests {
|
||||
state_gas_used: 0,
|
||||
reservoir: 0,
|
||||
gas_refunded: 0,
|
||||
refill_amount: 0,
|
||||
bytes: alloy_primitives::Bytes::copy_from_slice(b"output_from_precompile_2"),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use alloy_primitives::{
|
||||
map::{B256Map, B256Set},
|
||||
BlockNumber, B256,
|
||||
};
|
||||
use reth_chain_state::{DeferredTrieData, EthPrimitives, ExecutedBlock, LazyOverlay};
|
||||
use reth_chain_state::{EthPrimitives, ExecutedBlock, LazyOverlay};
|
||||
use reth_primitives_traits::{AlloyBlockHeader, NodePrimitives, SealedHeader};
|
||||
use std::{
|
||||
collections::{btree_map, hash_map, BTreeMap, VecDeque},
|
||||
@@ -43,7 +43,7 @@ pub struct TreeState<N: NodePrimitives = EthPrimitives> {
|
||||
/// This is optimistically prepared after the canonical head changes, so that
|
||||
/// the next payload building on the canonical head can use it immediately
|
||||
/// without recomputing.
|
||||
pub(crate) cached_canonical_overlay: Option<PreparedCanonicalOverlay>,
|
||||
pub(crate) cached_canonical_overlay: Option<PreparedCanonicalOverlay<N>>,
|
||||
}
|
||||
|
||||
impl<N: NodePrimitives> TreeState<N> {
|
||||
@@ -106,10 +106,10 @@ impl<N: NodePrimitives> TreeState<N> {
|
||||
/// This should be called after the canonical head changes to optimistically
|
||||
/// prepare the overlay for the next payload that will likely build on it.
|
||||
///
|
||||
/// Returns a clone of the [`LazyOverlay`] so the caller can spawn a background
|
||||
/// task to trigger computation via [`LazyOverlay::get`]. This ensures the overlay
|
||||
/// is actually computed before the next payload arrives.
|
||||
pub(crate) fn prepare_canonical_overlay(&mut self) -> Option<LazyOverlay> {
|
||||
/// Returns a clone of the prepared overlay so the caller can spawn a background
|
||||
/// task to trigger computation via [`LazyOverlay::get`] for the cached anchor.
|
||||
/// This ensures the overlay is actually computed before the next payload arrives.
|
||||
pub(crate) fn prepare_canonical_overlay(&mut self) -> Option<PreparedCanonicalOverlay<N>> {
|
||||
let canonical_hash = self.current_canonical_head.hash;
|
||||
|
||||
// Get blocks leading to the canonical head
|
||||
@@ -119,25 +119,23 @@ impl<N: NodePrimitives> TreeState<N> {
|
||||
return None;
|
||||
};
|
||||
|
||||
// Extract deferred trie data handles from blocks (newest to oldest)
|
||||
let handles: Vec<DeferredTrieData> = blocks.iter().map(|b| b.trie_data_handle()).collect();
|
||||
|
||||
let overlay = LazyOverlay::new(anchor_hash, handles);
|
||||
self.cached_canonical_overlay = Some(PreparedCanonicalOverlay {
|
||||
let num_blocks = blocks.len();
|
||||
let prepared = PreparedCanonicalOverlay {
|
||||
parent_hash: canonical_hash,
|
||||
overlay: overlay.clone(),
|
||||
overlay: LazyOverlay::new(blocks),
|
||||
anchor_hash,
|
||||
});
|
||||
};
|
||||
self.cached_canonical_overlay = Some(prepared.clone());
|
||||
|
||||
debug!(
|
||||
target: "engine::tree",
|
||||
%canonical_hash,
|
||||
%anchor_hash,
|
||||
num_blocks = blocks.len(),
|
||||
num_blocks,
|
||||
"Prepared cached canonical overlay"
|
||||
);
|
||||
|
||||
Some(overlay)
|
||||
Some(prepared)
|
||||
}
|
||||
|
||||
/// Returns the cached overlay if it matches the requested parent hash and anchor.
|
||||
@@ -148,7 +146,7 @@ impl<N: NodePrimitives> TreeState<N> {
|
||||
&self,
|
||||
parent_hash: B256,
|
||||
expected_anchor: B256,
|
||||
) -> Option<&PreparedCanonicalOverlay> {
|
||||
) -> Option<&PreparedCanonicalOverlay<N>> {
|
||||
self.cached_canonical_overlay.as_ref().filter(|cached| {
|
||||
cached.parent_hash == parent_hash && cached.anchor_hash == expected_anchor
|
||||
})
|
||||
@@ -429,10 +427,10 @@ impl<N: NodePrimitives> TreeState<N> {
|
||||
/// the next payload (which typically builds on the canonical head) to reuse
|
||||
/// the pre-computed overlay immediately without re-traversing in-memory blocks.
|
||||
///
|
||||
/// The overlay captures deferred trie data handles from all in-memory blocks
|
||||
/// The overlay captures executed blocks from all in-memory blocks
|
||||
/// between the canonical head and the persisted anchor. When a new payload
|
||||
/// arrives building on the canonical head, this cached overlay can be used
|
||||
/// directly instead of calling `blocks_by_hash` and collecting handles again.
|
||||
/// directly instead of calling `blocks_by_hash` again.
|
||||
///
|
||||
/// # Invalidation
|
||||
///
|
||||
@@ -440,16 +438,16 @@ impl<N: NodePrimitives> TreeState<N> {
|
||||
/// - Persistence completes (anchor changes)
|
||||
/// - The canonical head changes to a different block
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PreparedCanonicalOverlay {
|
||||
pub struct PreparedCanonicalOverlay<N: NodePrimitives = EthPrimitives> {
|
||||
/// The block hash for which this overlay is prepared as a parent.
|
||||
///
|
||||
/// When a payload arrives with this parent hash, the overlay can be reused.
|
||||
pub parent_hash: B256,
|
||||
/// The pre-computed lazy overlay containing deferred trie data handles.
|
||||
/// The pre-computed lazy overlay containing executed blocks for the canonical segment.
|
||||
///
|
||||
/// This is computed optimistically after `set_canonical_head` so subsequent
|
||||
/// payloads don't need to re-collect the handles.
|
||||
pub overlay: LazyOverlay,
|
||||
/// This is computed optimistically after `set_canonical_head` so subsequent payloads don't
|
||||
/// need to walk the in-memory chain again.
|
||||
pub overlay: LazyOverlay<N>,
|
||||
/// The anchor hash (persisted ancestor) this overlay is based on.
|
||||
///
|
||||
/// Used to verify the overlay is still valid (anchor hasn't changed due to persistence).
|
||||
|
||||
@@ -287,7 +287,7 @@ where
|
||||
let tx_recovered =
|
||||
tx.try_into_recovered().map_err(|_| ProviderError::SenderRecoveryError)?;
|
||||
let gas_used = match builder.execute_transaction(tx_recovered) {
|
||||
Ok(gas_used) => gas_used,
|
||||
Ok(gas_used) => gas_used.tx_gas_used(),
|
||||
Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
|
||||
hash,
|
||||
error,
|
||||
|
||||
@@ -13,7 +13,7 @@ extern crate alloc;
|
||||
|
||||
use alloc::{fmt::Debug, sync::Arc};
|
||||
use alloy_consensus::{constants::MAXIMUM_EXTRA_DATA_SIZE, EMPTY_OMMER_ROOT_HASH};
|
||||
use alloy_eips::eip7840::BlobParams;
|
||||
use alloy_eips::{eip7840::BlobParams, eip7928::BlockAccessList};
|
||||
use reth_chainspec::{EthChainSpec, EthereumHardforks};
|
||||
use reth_consensus::{
|
||||
Consensus, ConsensusError, FullConsensus, HeaderValidator, ReceiptRootBloom, TransactionRoot,
|
||||
@@ -108,9 +108,15 @@ where
|
||||
block: &RecoveredBlock<N::Block>,
|
||||
result: &BlockExecutionResult<N::Receipt>,
|
||||
receipt_root_bloom: Option<ReceiptRootBloom>,
|
||||
block_access_list: Option<BlockAccessList>,
|
||||
) -> Result<(), ConsensusError> {
|
||||
let res =
|
||||
validate_block_post_execution(block, &self.chain_spec, result, receipt_root_bloom);
|
||||
let res = validate_block_post_execution(
|
||||
block,
|
||||
&self.chain_spec,
|
||||
result,
|
||||
receipt_root_bloom,
|
||||
block_access_list,
|
||||
);
|
||||
|
||||
if self.skip_requests_hash_check &&
|
||||
let Err(ConsensusError::BodyRequestsHashDiff(_)) = &res
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use alloc::vec::Vec;
|
||||
use alloy_consensus::{proofs::calculate_receipt_root, BlockHeader, TxReceipt};
|
||||
use alloy_eips::Encodable2718;
|
||||
use alloy_eips::{
|
||||
eip7928::{compute_block_access_list_hash, BlockAccessList},
|
||||
Encodable2718,
|
||||
};
|
||||
use alloy_primitives::{Bloom, Bytes, B256};
|
||||
use reth_chainspec::EthereumHardforks;
|
||||
use reth_consensus::ConsensusError;
|
||||
@@ -21,6 +24,7 @@ pub fn validate_block_post_execution<B, R, ChainSpec>(
|
||||
chain_spec: &ChainSpec,
|
||||
result: &BlockExecutionResult<R>,
|
||||
receipt_root_bloom: Option<(B256, Bloom)>,
|
||||
block_access_list: Option<BlockAccessList>,
|
||||
) -> Result<(), ConsensusError>
|
||||
where
|
||||
B: Block,
|
||||
@@ -79,6 +83,21 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that the block access list hash matches the calculated block access list hash
|
||||
if chain_spec.is_amsterdam_active_at_timestamp(block.header().timestamp()) &&
|
||||
block_access_list.is_some()
|
||||
{
|
||||
let block_bal_hash = block.header().block_access_list_hash().unwrap_or_default();
|
||||
let default_bal = BlockAccessList::default();
|
||||
let block_access_list_hash =
|
||||
compute_block_access_list_hash(block_access_list.as_ref().unwrap_or(&default_bal));
|
||||
if block_access_list_hash != block_bal_hash {
|
||||
return Err(ConsensusError::BlockAccessListHashMismatch(
|
||||
(block_access_list_hash, block_bal_hash).into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ where
|
||||
transactions,
|
||||
output: BlockExecutionResult { receipts, requests, gas_used, blob_gas_used },
|
||||
state_root,
|
||||
block_access_list_hash,
|
||||
..
|
||||
} = input;
|
||||
|
||||
@@ -90,6 +91,12 @@ where
|
||||
};
|
||||
}
|
||||
|
||||
let bal_hash = if self.chain_spec.is_amsterdam_active_at_timestamp(timestamp) {
|
||||
block_access_list_hash
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let header = Header {
|
||||
parent_hash: ctx.parent_hash,
|
||||
ommers_hash: EMPTY_OMMER_ROOT_HASH,
|
||||
@@ -112,8 +119,8 @@ where
|
||||
blob_gas_used: block_blob_gas_used,
|
||||
excess_blob_gas,
|
||||
requests_hash,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
block_access_list_hash: bal_hash,
|
||||
slot_number: ctx.slot_number,
|
||||
};
|
||||
|
||||
Ok(Block {
|
||||
|
||||
@@ -12,7 +12,7 @@ use alloy_rpc_types_eth::TransactionRequest;
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use reth_chainspec::{ChainSpecBuilder, EthChainSpec, MAINNET};
|
||||
use reth_e2e_test_utils::setup_engine;
|
||||
use reth_network::types::NatResolver;
|
||||
use reth_network::{types::NatResolver, PeersInfo};
|
||||
use reth_node_builder::{NodeBuilder, NodeHandle};
|
||||
use reth_node_core::{
|
||||
args::{NetworkArgs, RpcServerArgs},
|
||||
@@ -375,3 +375,47 @@ async fn test_admin_external_ip() -> eyre::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_admin_node_info_uses_discv5_port_when_discv4_is_disabled() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let runtime = Runtime::test();
|
||||
|
||||
let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json")).unwrap();
|
||||
let chain_spec =
|
||||
Arc::new(ChainSpecBuilder::default().chain(MAINNET.chain).genesis(genesis).build());
|
||||
|
||||
let mut network = NetworkArgs::default().with_unused_ports();
|
||||
network.bootnodes = Some(Vec::new());
|
||||
network.discovery.disable_dns_discovery = true;
|
||||
network.discovery.disable_discv4_discovery = true;
|
||||
network = network.with_nat_resolver(NatResolver::ExternalIp("127.0.0.1".parse().unwrap()));
|
||||
|
||||
let node_config = NodeConfig::test()
|
||||
.with_chain(chain_spec)
|
||||
.with_network(network)
|
||||
.with_rpc(RpcServerArgs::default().with_unused_ports().with_http());
|
||||
|
||||
let NodeHandle { node, node_exit_future: _ } = NodeBuilder::new(node_config)
|
||||
.testing_node(runtime)
|
||||
.node(EthereumNode::default())
|
||||
.launch()
|
||||
.await?;
|
||||
|
||||
assert!(node.network.discv4().is_none());
|
||||
let discv5_port = node.network.discv5().expect("discv5 should be enabled").local_port();
|
||||
|
||||
let local_record = node.network.local_node_record();
|
||||
let local_enr = node.network.local_enr();
|
||||
let info = node.add_ons_handle.admin_api().node_info().await.unwrap();
|
||||
|
||||
assert_eq!(local_record.udp_port, discv5_port);
|
||||
assert_eq!(local_enr.udp4(), Some(discv5_port));
|
||||
assert_eq!(info.ports.discovery, discv5_port);
|
||||
assert_eq!(info.ports.listener, local_record.tcp_port);
|
||||
assert_eq!(info.enode, local_record.to_string());
|
||||
assert!(info.enode.contains(&format!("?discport={discv5_port}")));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
use alloy_consensus::Transaction;
|
||||
use alloy_primitives::U256;
|
||||
use alloy_primitives::{Bytes, U256};
|
||||
use alloy_rlp::Encodable;
|
||||
use alloy_rpc_types_engine::PayloadAttributes as EthPayloadAttributes;
|
||||
use reth_basic_payload_builder::{
|
||||
@@ -21,6 +21,7 @@ use reth_consensus_common::validation::MAX_RLP_BLOCK_SIZE;
|
||||
use reth_errors::{BlockExecutionError, BlockValidationError, ConsensusError};
|
||||
use reth_ethereum_primitives::{EthPrimitives, TransactionSigned};
|
||||
use reth_evm::{
|
||||
block::TxResult,
|
||||
execute::{BlockBuilder, BlockBuilderOutcome, BlockExecutor},
|
||||
ConfigureEvm, Evm, NextBlockEnvAttributes,
|
||||
};
|
||||
@@ -37,7 +38,7 @@ use reth_transaction_pool::{
|
||||
BestTransactions, BestTransactionsAttributes, PoolTransaction, TransactionPool,
|
||||
ValidPoolTransaction,
|
||||
};
|
||||
use revm::context_interface::Block as _;
|
||||
use revm::context_interface::{Block as _, Cfg as _};
|
||||
use std::sync::Arc;
|
||||
use tracing::{debug, trace, warn};
|
||||
|
||||
@@ -204,8 +205,11 @@ where
|
||||
.map_err(PayloadBuilderError::other)?;
|
||||
|
||||
debug!(target: "payload_builder", id=%payload_id, parent_header = ?parent_header.hash(), parent_number = parent_header.number, "building new payload");
|
||||
let mut cumulative_gas_used = 0;
|
||||
let mut cumulative_tx_gas_used = 0;
|
||||
let mut block_regular_gas_used = 0;
|
||||
let mut block_state_gas_used = 0;
|
||||
let block_gas_limit: u64 = builder.evm_mut().block().gas_limit();
|
||||
let tx_gas_limit_cap = builder.evm_mut().cfg_env().tx_gas_limit_cap();
|
||||
let base_fee = builder.evm_mut().block().basefee();
|
||||
|
||||
let mut best_txs = best_txs(BestTransactionsAttributes::new(
|
||||
@@ -250,13 +254,34 @@ where
|
||||
|
||||
while let Some(pool_tx) = best_txs.next() {
|
||||
// ensure we still have capacity for this transaction
|
||||
if cumulative_gas_used + pool_tx.gas_limit() > block_gas_limit {
|
||||
let exceeds_gas_limit = if is_amsterdam {
|
||||
let regular_available_gas = block_gas_limit.saturating_sub(block_regular_gas_used);
|
||||
let state_available_gas = block_gas_limit.saturating_sub(block_state_gas_used);
|
||||
let regular_tx_gas_limit = pool_tx.gas_limit().min(tx_gas_limit_cap);
|
||||
|
||||
if regular_tx_gas_limit > regular_available_gas {
|
||||
Some((regular_tx_gas_limit, regular_available_gas))
|
||||
} else if pool_tx.gas_limit() > state_available_gas {
|
||||
Some((pool_tx.gas_limit(), state_available_gas))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
let block_available_gas = block_gas_limit.saturating_sub(cumulative_tx_gas_used);
|
||||
(pool_tx.gas_limit() > block_available_gas)
|
||||
.then_some((pool_tx.gas_limit(), block_available_gas))
|
||||
};
|
||||
|
||||
if let Some((transaction_gas_limit, block_available_gas)) = exceeds_gas_limit {
|
||||
// we can't fit this transaction into the block, so we need to mark it as invalid
|
||||
// which also removes all dependent transaction from the iterator before we can
|
||||
// continue
|
||||
best_txs.mark_invalid(
|
||||
&pool_tx,
|
||||
&InvalidPoolTransactionError::ExceedsGasLimit(pool_tx.gas_limit(), block_gas_limit),
|
||||
&InvalidPoolTransactionError::ExceedsGasLimit(
|
||||
transaction_gas_limit,
|
||||
block_available_gas,
|
||||
),
|
||||
);
|
||||
continue
|
||||
}
|
||||
@@ -341,8 +366,11 @@ where
|
||||
let miner_fee = tx.effective_tip_per_gas(base_fee);
|
||||
let tx_hash = *tx.tx_hash();
|
||||
|
||||
let gas_used = match builder.execute_transaction(tx) {
|
||||
Ok(gas_used) => gas_used,
|
||||
let mut tx_regular_gas_used = 0;
|
||||
let gas_output = match builder.execute_transaction_with_result_closure(tx, |result| {
|
||||
tx_regular_gas_used = result.result().result.gas().block_regular_gas_used();
|
||||
}) {
|
||||
Ok(gas_output) => gas_output,
|
||||
Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
|
||||
error, ..
|
||||
})) => {
|
||||
@@ -362,9 +390,8 @@ where
|
||||
}
|
||||
continue
|
||||
}
|
||||
// EIP-7778: the executor tracks gas_before_refund while the payload builder's
|
||||
// pre-check uses gas_after_refund. Near-full blocks can pass the pre-check but
|
||||
// fail the executor's check. Skip the tx and continue building.
|
||||
// The executor is the source of truth for block gas availability. Keep this
|
||||
// non-fatal in case local builder accounting diverges from executor rules.
|
||||
Err(BlockExecutionError::Validation(
|
||||
BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
|
||||
transaction_gas_limit,
|
||||
@@ -398,9 +425,12 @@ where
|
||||
block_transactions_rlp_length += tx_rlp_len;
|
||||
|
||||
// update and add to total fees
|
||||
let gas_used = gas_output.tx_gas_used();
|
||||
let miner_fee = miner_fee.expect("fee is always valid; execution succeeded");
|
||||
total_fees += U256::from(miner_fee) * U256::from(gas_used);
|
||||
cumulative_gas_used += gas_used;
|
||||
cumulative_tx_gas_used += gas_used;
|
||||
block_regular_gas_used += tx_regular_gas_used;
|
||||
block_state_gas_used += gas_output.state_gas_used();
|
||||
|
||||
// Add blob tx sidecar to the payload.
|
||||
if let Some(sidecar) = blob_tx_sidecar {
|
||||
@@ -416,7 +446,9 @@ where
|
||||
return Ok(BuildOutcome::Aborted { fees: total_fees, cached_reads })
|
||||
}
|
||||
|
||||
let BlockBuilderOutcome { execution_result, block, .. } = if let Some(mut handle) = trie_handle
|
||||
let BlockBuilderOutcome { execution_result, block, block_access_list, .. } = if let Some(
|
||||
mut handle,
|
||||
) = trie_handle
|
||||
{
|
||||
// Drop the state hook, which drops the StateHookSender and triggers
|
||||
// FinishedStateUpdates via its Drop impl, signaling the trie task to finalize.
|
||||
@@ -455,8 +487,10 @@ where
|
||||
max_rlp_length: MAX_RLP_BLOCK_SIZE,
|
||||
}));
|
||||
}
|
||||
let block_access_list: Option<Bytes> =
|
||||
block_access_list.map(|block_access_list| alloy_rlp::encode(&block_access_list).into());
|
||||
|
||||
let payload = EthBuiltPayload::new(sealed_block, total_fees, requests, None)
|
||||
let payload = EthBuiltPayload::new(sealed_block, total_fees, requests, block_access_list)
|
||||
// add blob sidecars from the executed txs
|
||||
.with_sidecars(blob_sidecars);
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
//! Helper aliases when working with [`ConfigureEvm`] and the traits in this crate.
|
||||
|
||||
use crate::ConfigureEvm;
|
||||
use alloy_evm::{block::BlockExecutorFactory, Database, EvmEnv, EvmFactory};
|
||||
use revm::{inspector::NoOpInspector, Inspector};
|
||||
use alloy_evm::{
|
||||
block::{BlockExecutorFactory, BlockExecutorFor},
|
||||
Database, EvmEnv, EvmFactory,
|
||||
};
|
||||
use revm::{database::State, inspector::NoOpInspector, Inspector};
|
||||
|
||||
/// Helper to access [`EvmFactory`] for a given [`ConfigureEvm`].
|
||||
pub type EvmFactoryFor<Evm> =
|
||||
@@ -33,6 +36,10 @@ pub type TxEnvFor<Evm> = <EvmFactoryFor<Evm> as EvmFactory>::Tx;
|
||||
pub type ExecutionCtxFor<'a, Evm> =
|
||||
<<Evm as ConfigureEvm>::BlockExecutorFactory as BlockExecutorFactory>::ExecutionCtx<'a>;
|
||||
|
||||
/// Helper to access [`alloy_evm::block::BlockExecutor`] for a given [`ConfigureEvm`].
|
||||
pub type BlockExecutorForEvm<'a, Evm, DB, I = NoOpInspector> =
|
||||
BlockExecutorFor<'a, <Evm as ConfigureEvm>::BlockExecutorFactory, &'a mut State<DB>, I>;
|
||||
|
||||
/// Type alias for [`EvmEnv`] for a given [`ConfigureEvm`].
|
||||
pub type EvmEnvFor<Evm> = EvmEnv<SpecFor<Evm>, BlockEnvFor<Evm>>;
|
||||
|
||||
|
||||
@@ -79,4 +79,11 @@ where
|
||||
Self::Right(b) => b.size_hint(),
|
||||
}
|
||||
}
|
||||
|
||||
fn take_bal(&mut self) -> Option<alloy_eips::eip7928::BlockAccessList> {
|
||||
match self {
|
||||
Self::Left(a) => a.take_bal(),
|
||||
Self::Right(b) => b.take_bal(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
use crate::{ConfigureEvm, Database, OnStateHook, TxEnvFor};
|
||||
use alloc::{boxed::Box, sync::Arc, vec::Vec};
|
||||
use alloy_consensus::{BlockHeader, Header};
|
||||
use alloy_eips::eip2718::WithEncoded;
|
||||
pub use alloy_evm::block::{BlockExecutor, BlockExecutorFactory};
|
||||
use alloy_eips::{
|
||||
eip2718::WithEncoded,
|
||||
eip7928::{compute_block_access_list_hash, BlockAccessList},
|
||||
};
|
||||
pub use alloy_evm::block::{BlockExecutor, BlockExecutorFactory, GasOutput};
|
||||
use alloy_evm::{
|
||||
block::{CommitChanges, ExecutableTxParts},
|
||||
Evm, EvmEnv, EvmFactory, RecoveredTx, ToTxEnv,
|
||||
@@ -21,7 +24,10 @@ use reth_primitives_traits::{
|
||||
use reth_storage_api::StateProvider;
|
||||
pub use reth_storage_errors::provider::ProviderError;
|
||||
use reth_trie_common::{updates::TrieUpdates, HashedPostState};
|
||||
use revm::database::{states::bundle_state::BundleRetention, BundleState, State};
|
||||
use revm::{
|
||||
database::{states::bundle_state::BundleRetention, BundleState, State},
|
||||
state::bal::Bal,
|
||||
};
|
||||
|
||||
/// A type that knows how to execute a block. It is assumed to operate on a
|
||||
/// [`crate::Evm`] internally and use [`State`] as database.
|
||||
@@ -145,6 +151,9 @@ pub trait Executor<DB: Database>: Sized {
|
||||
///
|
||||
/// This is used to optimize DB commits depending on the size of the state.
|
||||
fn size_hint(&self) -> usize;
|
||||
|
||||
/// Take built [`BlockAccessList`] from executor
|
||||
fn take_bal(&mut self) -> Option<BlockAccessList>;
|
||||
}
|
||||
|
||||
/// Input for block building. Consumed by [`BlockAssembler`].
|
||||
@@ -162,6 +171,7 @@ pub trait Executor<DB: Database>: Sized {
|
||||
/// - `bundle_state`: Accumulated state changes from all transactions
|
||||
/// - `state_provider`: Access to the current state for additional lookups
|
||||
/// - `state_root`: The calculated state root after all changes
|
||||
/// - `block_access_list_hash`: Block access list hash (EIP-7928, Amsterdam)
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
@@ -178,6 +188,7 @@ pub trait Executor<DB: Database>: Sized {
|
||||
/// bundle_state: &state_changes,
|
||||
/// state_provider: &state,
|
||||
/// state_root: calculated_root,
|
||||
/// block_access_list_hash: Some(calculated_bal_hash),
|
||||
/// };
|
||||
///
|
||||
/// let block = assembler.assemble_block(input)?;
|
||||
@@ -205,6 +216,8 @@ pub struct BlockAssemblerInput<'a, 'b, F: BlockExecutorFactory, H = Header> {
|
||||
pub state_provider: &'b dyn StateProvider,
|
||||
/// State root for this block.
|
||||
pub state_root: B256,
|
||||
/// Block access list hash (EIP-7928, Amsterdam).
|
||||
pub block_access_list_hash: Option<B256>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, F: BlockExecutorFactory, H> BlockAssemblerInput<'a, 'b, F, H> {
|
||||
@@ -222,6 +235,7 @@ impl<'a, 'b, F: BlockExecutorFactory, H> BlockAssemblerInput<'a, 'b, F, H> {
|
||||
bundle_state: &'a BundleState,
|
||||
state_provider: &'b dyn StateProvider,
|
||||
state_root: B256,
|
||||
block_access_list_hash: Option<B256>,
|
||||
) -> Self {
|
||||
Self {
|
||||
evm_env,
|
||||
@@ -232,6 +246,7 @@ impl<'a, 'b, F: BlockExecutorFactory, H> BlockAssemblerInput<'a, 'b, F, H> {
|
||||
bundle_state,
|
||||
state_provider,
|
||||
state_root,
|
||||
block_access_list_hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -301,6 +316,8 @@ pub struct BlockBuilderOutcome<N: NodePrimitives> {
|
||||
pub trie_updates: TrieUpdates,
|
||||
/// The built block.
|
||||
pub block: RecoveredBlock<N::Block>,
|
||||
/// Block access list built during execution (EIP-7928, Amsterdam).
|
||||
pub block_access_list: Option<BlockAccessList>,
|
||||
}
|
||||
|
||||
/// A type that knows how to execute and build a block.
|
||||
@@ -327,7 +344,7 @@ pub trait BlockBuilder {
|
||||
&mut self,
|
||||
tx: impl ExecutorTx<Self::Executor>,
|
||||
f: impl FnOnce(&<Self::Executor as BlockExecutor>::Result) -> CommitChanges,
|
||||
) -> Result<Option<u64>, BlockExecutionError>;
|
||||
) -> Result<Option<GasOutput>, BlockExecutionError>;
|
||||
|
||||
/// Invokes [`BlockExecutor::execute_transaction_with_result_closure`] and saves the
|
||||
/// transaction in internal state.
|
||||
@@ -335,7 +352,7 @@ pub trait BlockBuilder {
|
||||
&mut self,
|
||||
tx: impl ExecutorTx<Self::Executor>,
|
||||
f: impl FnOnce(&<Self::Executor as BlockExecutor>::Result),
|
||||
) -> Result<u64, BlockExecutionError> {
|
||||
) -> Result<GasOutput, BlockExecutionError> {
|
||||
self.execute_transaction_with_commit_condition(tx, |res| {
|
||||
f(res);
|
||||
CommitChanges::Yes
|
||||
@@ -348,7 +365,7 @@ pub trait BlockBuilder {
|
||||
fn execute_transaction(
|
||||
&mut self,
|
||||
tx: impl ExecutorTx<Self::Executor>,
|
||||
) -> Result<u64, BlockExecutionError> {
|
||||
) -> Result<GasOutput, BlockExecutionError> {
|
||||
self.execute_transaction_with_result_closure(tx, |_| ())
|
||||
}
|
||||
|
||||
@@ -453,20 +470,26 @@ where
|
||||
type Executor = Executor;
|
||||
|
||||
fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
|
||||
self.executor.apply_pre_execution_changes()
|
||||
self.executor.apply_pre_execution_changes()?;
|
||||
// Bump BAL index after pre-execution changes (EIP-7928: index 0 is pre-execution)
|
||||
self.executor.evm_mut().db_mut().bump_bal_index();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_transaction_with_commit_condition(
|
||||
&mut self,
|
||||
tx: impl ExecutorTx<Self::Executor>,
|
||||
f: impl FnOnce(&<Self::Executor as BlockExecutor>::Result) -> CommitChanges,
|
||||
) -> Result<Option<u64>, BlockExecutionError> {
|
||||
) -> Result<Option<GasOutput>, BlockExecutionError> {
|
||||
let (tx_env, tx) = tx.into_parts();
|
||||
if let Some(gas_used) =
|
||||
self.executor.execute_transaction_with_commit_condition((tx_env, &tx), f)?
|
||||
{
|
||||
self.transactions.push(tx);
|
||||
Ok(Some(gas_used.tx_gas_used()))
|
||||
// Bump BAL index after each committed transaction (EIP-7928)
|
||||
self.executor.evm_mut().db_mut().bump_bal_index();
|
||||
Ok(Some(gas_used))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
@@ -483,6 +506,11 @@ where
|
||||
// merge all transitions into bundle state
|
||||
db.merge_transitions(BundleRetention::Reverts);
|
||||
|
||||
// extract the built block access list (EIP-7928, Amsterdam) and compute its hash
|
||||
let block_access_list = db.take_built_alloy_bal();
|
||||
let block_access_list_hash =
|
||||
block_access_list.as_ref().map(|bal| compute_block_access_list_hash(bal));
|
||||
|
||||
let hashed_state = state.hashed_post_state(&db.bundle_state);
|
||||
let (state_root, trie_updates) = match state_root_precomputed {
|
||||
Some(precomputed) => precomputed,
|
||||
@@ -503,11 +531,18 @@ where
|
||||
bundle_state: &db.bundle_state,
|
||||
state_provider: &state,
|
||||
state_root,
|
||||
block_access_list_hash,
|
||||
})?;
|
||||
|
||||
let block = RecoveredBlock::new_unhashed(block, senders);
|
||||
|
||||
Ok(BlockBuilderOutcome { execution_result: result, hashed_state, trie_updates, block })
|
||||
Ok(BlockBuilderOutcome {
|
||||
execution_result: result,
|
||||
hashed_state,
|
||||
trie_updates,
|
||||
block,
|
||||
block_access_list,
|
||||
})
|
||||
}
|
||||
|
||||
fn executor_mut(&mut self) -> &mut Self::Executor {
|
||||
@@ -554,11 +589,33 @@ where
|
||||
block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
|
||||
) -> Result<BlockExecutionResult<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
|
||||
{
|
||||
let result = self
|
||||
let mut executor = self
|
||||
.strategy_factory
|
||||
.executor_for_block(&mut self.db, block)
|
||||
.map_err(BlockExecutionError::other)?
|
||||
.execute_block(block.transactions_recovered())?;
|
||||
.map_err(BlockExecutionError::other)?;
|
||||
|
||||
let has_bal = block.header().block_access_list_hash().is_some();
|
||||
|
||||
if has_bal {
|
||||
executor.evm_mut().db_mut().bal_state.bal_builder = Some(Bal::new());
|
||||
} else {
|
||||
executor.evm_mut().db_mut().bal_state.bal_builder = None;
|
||||
}
|
||||
|
||||
executor.apply_pre_execution_changes()?;
|
||||
|
||||
if has_bal {
|
||||
executor.evm_mut().db_mut().bump_bal_index();
|
||||
}
|
||||
|
||||
for tx in block.transactions_recovered() {
|
||||
executor.execute_transaction(tx)?;
|
||||
if has_bal {
|
||||
executor.evm_mut().db_mut().bump_bal_index();
|
||||
}
|
||||
}
|
||||
|
||||
let result = executor.apply_post_execution_changes()?;
|
||||
|
||||
self.db.merge_transitions(BundleRetention::Reverts);
|
||||
|
||||
@@ -592,6 +649,10 @@ where
|
||||
fn size_hint(&self) -> usize {
|
||||
self.db.bundle_state.size_hint()
|
||||
}
|
||||
|
||||
fn take_bal(&mut self) -> Option<BlockAccessList> {
|
||||
self.db.take_built_alloy_bal()
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper trait marking a 'static type that can be converted into an [`ExecutableTxParts`] for
|
||||
@@ -697,6 +758,10 @@ mod tests {
|
||||
fn size_hint(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn take_bal(&mut self) -> Option<BlockAccessList> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -20,10 +20,7 @@ extern crate alloc;
|
||||
use crate::execute::{BasicBlockBuilder, Executor};
|
||||
use alloc::vec::Vec;
|
||||
use alloy_eips::eip4895::Withdrawals;
|
||||
use alloy_evm::{
|
||||
block::{BlockExecutorFactory, BlockExecutorFor},
|
||||
precompiles::PrecompilesMap,
|
||||
};
|
||||
use alloy_evm::{block::BlockExecutorFactory, precompiles::PrecompilesMap};
|
||||
use alloy_primitives::{Address, Bytes, B256};
|
||||
use core::{error::Error, fmt::Debug};
|
||||
use execute::{BasicBlockExecutor, BlockAssembler, BlockBuilder};
|
||||
@@ -312,7 +309,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
|
||||
&'a self,
|
||||
evm: EvmFor<Self, &'a mut State<DB>, I>,
|
||||
ctx: <Self::BlockExecutorFactory as BlockExecutorFactory>::ExecutionCtx<'a>,
|
||||
) -> impl BlockExecutorFor<'a, Self::BlockExecutorFactory, &'a mut State<DB>, I>
|
||||
) -> BlockExecutorForEvm<'a, Self, DB, I>
|
||||
where
|
||||
DB: Database,
|
||||
I: InspectorFor<Self, &'a mut State<DB>> + 'a,
|
||||
@@ -325,8 +322,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
|
||||
&'a self,
|
||||
db: &'a mut State<DB>,
|
||||
block: &'a SealedBlock<<Self::Primitives as NodePrimitives>::Block>,
|
||||
) -> Result<impl BlockExecutorFor<'a, Self::BlockExecutorFactory, &'a mut State<DB>>, Self::Error>
|
||||
{
|
||||
) -> Result<BlockExecutorForEvm<'a, Self, DB>, Self::Error> {
|
||||
let evm = self.evm_for_block(db, block.header())?;
|
||||
let ctx = self.context_for_block(block)?;
|
||||
Ok(self.create_executor(evm, ctx))
|
||||
@@ -352,10 +348,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
|
||||
evm: EvmFor<Self, &'a mut State<DB>, I>,
|
||||
parent: &'a SealedHeader<HeaderTy<Self::Primitives>>,
|
||||
ctx: <Self::BlockExecutorFactory as BlockExecutorFactory>::ExecutionCtx<'a>,
|
||||
) -> impl BlockBuilder<
|
||||
Primitives = Self::Primitives,
|
||||
Executor: BlockExecutorFor<'a, Self::BlockExecutorFactory, &'a mut State<DB>, I>,
|
||||
>
|
||||
) -> impl BlockBuilder<Primitives = Self::Primitives, Executor = BlockExecutorForEvm<'a, Self, DB, I>>
|
||||
where
|
||||
DB: Database,
|
||||
I: InspectorFor<Self, &'a mut State<DB>> + 'a,
|
||||
@@ -404,10 +397,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
|
||||
parent: &'a SealedHeader<<Self::Primitives as NodePrimitives>::BlockHeader>,
|
||||
attributes: Self::NextBlockEnvCtx,
|
||||
) -> Result<
|
||||
impl BlockBuilder<
|
||||
Primitives = Self::Primitives,
|
||||
Executor: BlockExecutorFor<'a, Self::BlockExecutorFactory, &'a mut State<DB>>,
|
||||
>,
|
||||
impl BlockBuilder<Primitives = Self::Primitives, Executor = BlockExecutorForEvm<'a, Self, DB>>,
|
||||
Self::Error,
|
||||
> {
|
||||
let evm_env = self.next_evm_env(parent, &attributes)?;
|
||||
|
||||
@@ -16,10 +16,13 @@ workspace = true
|
||||
metrics.workspace = true
|
||||
metrics-derive.workspace = true
|
||||
|
||||
# reth
|
||||
reth-primitives-traits = { workspace = true, optional = true }
|
||||
|
||||
# async
|
||||
tokio = { workspace = true, features = ["full"], optional = true }
|
||||
futures = { workspace = true, optional = true }
|
||||
tokio-util = { workspace = true, optional = true }
|
||||
|
||||
[features]
|
||||
common = ["tokio", "futures", "tokio-util"]
|
||||
common = ["tokio", "futures", "tokio-util", "reth-primitives-traits"]
|
||||
|
||||
@@ -4,8 +4,13 @@
|
||||
use crate::Metrics;
|
||||
use futures::Stream;
|
||||
use metrics::Counter;
|
||||
use reth_primitives_traits::InMemorySize;
|
||||
use std::{
|
||||
pin::Pin,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
task::{ready, Context, Poll},
|
||||
};
|
||||
use tokio::sync::mpsc::{
|
||||
@@ -399,3 +404,147 @@ struct MeteredPollSenderMetrics {
|
||||
/// Number of delayed message deliveries caused by a full channel
|
||||
back_pressure_total: Counter,
|
||||
}
|
||||
|
||||
/// Shared state for tracking memory budget across sender and receiver.
|
||||
///
|
||||
/// `used` is a pure accounting counter — it does not gate access to any other
|
||||
/// shared memory, so all operations on it use [`Ordering::Relaxed`]. Cross-thread
|
||||
/// publication of message contents is handled by the underlying mpsc channel.
|
||||
#[derive(Debug)]
|
||||
struct MemoryBudget {
|
||||
/// Current number of bytes used by buffered messages.
|
||||
used: AtomicUsize,
|
||||
/// Maximum allowed bytes.
|
||||
max_bytes: usize,
|
||||
}
|
||||
|
||||
/// Guard that releases memory budget when dropped.
|
||||
///
|
||||
/// Holds the size of the message and a reference to the shared budget counter.
|
||||
/// When dropped, it atomically decreases the used counter.
|
||||
#[derive(Debug)]
|
||||
struct BudgetGuard {
|
||||
size: usize,
|
||||
budget: Arc<MemoryBudget>,
|
||||
}
|
||||
|
||||
impl Drop for BudgetGuard {
|
||||
fn drop(&mut self) {
|
||||
self.budget.used.fetch_sub(self.size, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Message envelope that holds the memory budget while the message sits in the channel.
|
||||
///
|
||||
/// The guard is dropped (releasing the budget) as soon as the receiver dequeues
|
||||
/// the message via [`MemoryBoundedReceiver::recv`] / [`MemoryBoundedReceiver::poll_recv`],
|
||||
/// so the budget tracks bytes *currently in the channel queue*, not bytes in flight
|
||||
/// downstream of the receiver.
|
||||
#[derive(Debug)]
|
||||
struct Budgeted<T> {
|
||||
msg: T,
|
||||
_guard: BudgetGuard,
|
||||
}
|
||||
|
||||
/// A sender that enforces a byte budget before enqueueing messages.
|
||||
///
|
||||
/// Uses a shared atomic counter to track memory usage. Each message's size is added
|
||||
/// to the counter on send and subtracted when the message is dequeued by the receiver.
|
||||
///
|
||||
/// The current call sites (specifically [`crate::common::mpsc::MemoryBoundedSender`] used
|
||||
/// for the `NetworkManager → TransactionsManager` channel) have a single producer driven
|
||||
/// from a single `poll`, so the `fetch_add → check → fetch_sub-on-overflow` reservation
|
||||
/// pattern can never race with itself. The atomic is still used so the receiver can
|
||||
/// release budget from a different task.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MemoryBoundedSender<T: InMemorySize> {
|
||||
/// The underlying unbounded metered sender
|
||||
inner: UnboundedMeteredSender<Budgeted<T>>,
|
||||
/// Shared memory budget tracker
|
||||
budget: Arc<MemoryBudget>,
|
||||
}
|
||||
|
||||
impl<T: InMemorySize> MemoryBoundedSender<T> {
|
||||
/// Tries to send a message if there is sufficient budget.
|
||||
///
|
||||
/// Returns `TrySendError::Full` if insufficient budget is available.
|
||||
pub fn try_send(&self, msg: T) -> Result<(), TrySendError<T>> {
|
||||
let size = msg.size();
|
||||
|
||||
// Reserve budget: add first, check after
|
||||
let prev = self.budget.used.fetch_add(size, Ordering::Relaxed);
|
||||
if prev.saturating_add(size) > self.budget.max_bytes {
|
||||
// Over budget, undo
|
||||
self.budget.used.fetch_sub(size, Ordering::Relaxed);
|
||||
return Err(TrySendError::Full(msg));
|
||||
}
|
||||
|
||||
let guard = BudgetGuard { size, budget: Arc::clone(&self.budget) };
|
||||
let budgeted = Budgeted { msg, _guard: guard };
|
||||
|
||||
self.inner.send(budgeted).map_err(|e| {
|
||||
// Guard will be dropped here, releasing the budget
|
||||
TrySendError::Closed(e.0.msg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A receiver for memory-bounded messages.
|
||||
///
|
||||
/// On receive, the budget reserved for the message is released immediately and the
|
||||
/// inner `T` is yielded — callers do not need to opt into any wrapper type.
|
||||
#[derive(Debug)]
|
||||
pub struct MemoryBoundedReceiver<T> {
|
||||
/// The underlying unbounded metered receiver
|
||||
inner: UnboundedMeteredReceiver<Budgeted<T>>,
|
||||
}
|
||||
|
||||
impl<T> MemoryBoundedReceiver<T> {
|
||||
/// Receives the next message, returning `None` if the channel is closed.
|
||||
///
|
||||
/// Releases the message's reserved budget before returning.
|
||||
pub async fn recv(&mut self) -> Option<T> {
|
||||
self.inner.recv().await.map(unwrap_budgeted)
|
||||
}
|
||||
|
||||
/// Polls to receive the next message on this channel.
|
||||
///
|
||||
/// Releases the message's reserved budget before returning.
|
||||
pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<T>> {
|
||||
self.inner.poll_recv(cx).map(|opt| opt.map(unwrap_budgeted))
|
||||
}
|
||||
}
|
||||
|
||||
/// Releases the budget guard and returns the inner message.
|
||||
fn unwrap_budgeted<T>(b: Budgeted<T>) -> T {
|
||||
// Destructuring binds `_guard` so it is dropped when this function returns,
|
||||
// which runs `BudgetGuard::drop` and releases the reserved bytes.
|
||||
let Budgeted { msg, _guard } = b;
|
||||
msg
|
||||
}
|
||||
|
||||
impl<T> Stream for MemoryBoundedReceiver<T> {
|
||||
type Item = T;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
self.poll_recv(cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new memory-bounded channel with the given byte budget.
|
||||
///
|
||||
/// The budget tracks bytes currently buffered in the channel; it is reserved on
|
||||
/// [`MemoryBoundedSender::try_send`] and released as soon as the receiver dequeues
|
||||
/// the message.
|
||||
pub fn memory_bounded_channel<T: InMemorySize>(
|
||||
max_bytes: usize,
|
||||
scope: &'static str,
|
||||
) -> (MemoryBoundedSender<T>, MemoryBoundedReceiver<T>) {
|
||||
let (tx, rx) = metered_unbounded_channel(scope);
|
||||
let budget = Arc::new(MemoryBudget { used: AtomicUsize::new(0), max_bytes });
|
||||
|
||||
let sender = MemoryBoundedSender { inner: tx, budget };
|
||||
let receiver = MemoryBoundedReceiver { inner: rx };
|
||||
|
||||
(sender, receiver)
|
||||
}
|
||||
|
||||
@@ -47,9 +47,7 @@ use secp256k1::SecretKey;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::{btree_map, hash_map::Entry, BTreeMap, HashMap, VecDeque},
|
||||
fmt,
|
||||
future::poll_fn,
|
||||
io,
|
||||
fmt, io,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4},
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
@@ -243,17 +241,56 @@ impl Discv4 {
|
||||
/// ```
|
||||
pub async fn bind(
|
||||
local_address: SocketAddr,
|
||||
local_node_record: NodeRecord,
|
||||
secret_key: SecretKey,
|
||||
config: Discv4Config,
|
||||
) -> io::Result<(Self, Discv4Service)> {
|
||||
let socket = Arc::new(UdpSocket::bind(local_address).await?);
|
||||
trace!(target: "discv4", local_addr=?socket.local_addr(), "opened UDP socket");
|
||||
let (tx, rx) = mpsc::channel(config.udp_ingress_message_buffer);
|
||||
|
||||
Self::bind_with_socket(socket, Some(tx), rx, local_node_record, secret_key, config)
|
||||
}
|
||||
|
||||
/// Creates a new `Discv4` instance using a pre-bound shared socket. No receive loop is
|
||||
/// spawned; instead returns an [`IngressHandler`] that should be used to forward raw packets
|
||||
/// received by the socket owner (e.g. discv5 unrecognized frames).
|
||||
pub fn bind_shared(
|
||||
socket: Arc<UdpSocket>,
|
||||
local_node_record: NodeRecord,
|
||||
secret_key: SecretKey,
|
||||
config: Discv4Config,
|
||||
) -> io::Result<(Self, Discv4Service, IngressHandler)> {
|
||||
let (tx, rx) = mpsc::channel(config.udp_ingress_message_buffer);
|
||||
let local_id = local_node_record.id;
|
||||
let (discv4, service) =
|
||||
Self::bind_with_socket(socket, None, rx, local_node_record, secret_key, config)?;
|
||||
|
||||
let handler = IngressHandler::new(tx, local_id);
|
||||
|
||||
Ok((discv4, service, handler))
|
||||
}
|
||||
|
||||
fn bind_with_socket(
|
||||
socket: Arc<UdpSocket>,
|
||||
ingress_tx: Option<IngressSender>,
|
||||
ingress_rx: IngressReceiver,
|
||||
mut local_node_record: NodeRecord,
|
||||
secret_key: SecretKey,
|
||||
config: Discv4Config,
|
||||
) -> io::Result<(Self, Discv4Service)> {
|
||||
let socket = UdpSocket::bind(local_address).await?;
|
||||
let local_addr = socket.local_addr()?;
|
||||
local_node_record.udp_port = local_addr.port();
|
||||
trace!(target: "discv4", ?local_addr,"opened UDP socket");
|
||||
|
||||
let mut service =
|
||||
Discv4Service::new(socket, local_addr, local_node_record, secret_key, config);
|
||||
let mut service = Discv4Service::new(
|
||||
socket,
|
||||
ingress_tx,
|
||||
ingress_rx,
|
||||
local_addr,
|
||||
local_node_record,
|
||||
secret_key,
|
||||
config,
|
||||
);
|
||||
|
||||
// resolve the external address immediately
|
||||
service.resolve_external_ip();
|
||||
@@ -520,20 +557,25 @@ pub struct Discv4Service {
|
||||
|
||||
impl Discv4Service {
|
||||
/// Create a new instance for a bound [`UdpSocket`].
|
||||
///
|
||||
/// If `ingress_tx` is `Some`, the receive loop is spawned to read from the socket. If `None`,
|
||||
/// the caller feeds packets into `ingress_rx` externally (shared socket mode).
|
||||
pub(crate) fn new(
|
||||
socket: UdpSocket,
|
||||
socket: Arc<UdpSocket>,
|
||||
ingress_tx: Option<IngressSender>,
|
||||
ingress_rx: IngressReceiver,
|
||||
local_address: SocketAddr,
|
||||
local_node_record: NodeRecord,
|
||||
secret_key: SecretKey,
|
||||
config: Discv4Config,
|
||||
) -> Self {
|
||||
let socket = Arc::new(socket);
|
||||
let (ingress_tx, ingress_rx) = mpsc::channel(config.udp_ingress_message_buffer);
|
||||
let (egress_tx, egress_rx) = mpsc::channel(config.udp_egress_message_buffer);
|
||||
let mut tasks = JoinSet::<()>::new();
|
||||
|
||||
let udp = Arc::clone(&socket);
|
||||
tasks.spawn(receive_loop(udp, ingress_tx, local_node_record.id));
|
||||
if let Some(ingress_tx) = ingress_tx {
|
||||
let udp = Arc::clone(&socket);
|
||||
tasks.spawn(receive_loop(udp, ingress_tx, local_node_record.id));
|
||||
}
|
||||
|
||||
let udp = Arc::clone(&socket);
|
||||
tasks.spawn(send_loop(udp, egress_rx));
|
||||
@@ -947,7 +989,7 @@ impl Discv4Service {
|
||||
let key = kad_key(peer_id);
|
||||
match self.kbuckets.entry(&key) {
|
||||
BucketEntry::Present(entry, _) => Some(f(entry.value())),
|
||||
BucketEntry::Pending(mut entry, _) => Some(f(entry.value())),
|
||||
BucketEntry::Pending(entry, _) => Some(f(entry.value())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -973,7 +1015,9 @@ impl Discv4Service {
|
||||
kbucket::Entry::Present(mut entry, _) => {
|
||||
entry.value_mut().update_with_enr(last_enr_seq)
|
||||
}
|
||||
kbucket::Entry::Pending(mut entry, _) => entry.value().update_with_enr(last_enr_seq),
|
||||
kbucket::Entry::Pending(mut entry, _) => {
|
||||
entry.value_mut().update_with_enr(last_enr_seq)
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
@@ -1025,8 +1069,8 @@ impl Discv4Service {
|
||||
}
|
||||
kbucket::Entry::Pending(mut entry, mut status) => {
|
||||
// endpoint is now proven
|
||||
entry.value().establish_proof();
|
||||
entry.value().update_with_enr(last_enr_seq);
|
||||
entry.value_mut().establish_proof();
|
||||
entry.value_mut().update_with_enr(last_enr_seq);
|
||||
|
||||
if !status.is_connected() {
|
||||
status.state = ConnectionState::Connected;
|
||||
@@ -1158,7 +1202,7 @@ impl Discv4Service {
|
||||
} else {
|
||||
is_proven = entry.value().has_endpoint_proof;
|
||||
}
|
||||
entry.value().update_with_enr(ping.enr_sq)
|
||||
entry.value_mut().update_with_enr(ping.enr_sq)
|
||||
}
|
||||
kbucket::Entry::Absent(entry) => {
|
||||
let mut node = NodeEntry::new(record);
|
||||
@@ -1388,7 +1432,7 @@ impl Discv4Service {
|
||||
(entry.value().record, id)
|
||||
}
|
||||
kbucket::Entry::Pending(mut entry, _) => {
|
||||
let id = entry.value().update_with_fork_id(fork_id);
|
||||
let id = entry.value_mut().update_with_fork_id(fork_id);
|
||||
(entry.value().record, id)
|
||||
}
|
||||
_ => return,
|
||||
@@ -1538,7 +1582,7 @@ impl Discv4Service {
|
||||
}
|
||||
}
|
||||
}
|
||||
BucketEntry::Pending(mut entry, _) => {
|
||||
BucketEntry::Pending(entry, _) => {
|
||||
if entry.value().has_endpoint_proof {
|
||||
if entry
|
||||
.value()
|
||||
@@ -1642,7 +1686,7 @@ impl Discv4Service {
|
||||
entry.value().find_node_failures
|
||||
}
|
||||
kbucket::Entry::Pending(mut entry, _) => {
|
||||
entry.value().inc_failed_request();
|
||||
entry.value_mut().inc_failed_request();
|
||||
entry.value().find_node_failures
|
||||
}
|
||||
_ => continue,
|
||||
@@ -1962,80 +2006,100 @@ const MAX_INCOMING_PACKETS_PER_MINUTE_BY_IP: usize = 60usize;
|
||||
|
||||
/// Continuously awaits new incoming messages and sends them back through the channel.
|
||||
///
|
||||
/// The receive loop enforce primitive rate limiting for ips to prevent message spams from
|
||||
/// individual IPs
|
||||
/// The receive loop enforces primitive rate limiting for IPs to prevent message spams from
|
||||
/// individual IPs.
|
||||
pub(crate) async fn receive_loop(udp: Arc<UdpSocket>, tx: IngressSender, local_id: PeerId) {
|
||||
let send = |event: IngressEvent| async {
|
||||
let _ = tx.send(event).await.map_err(|err| {
|
||||
debug!(
|
||||
target: "discv4",
|
||||
%err,
|
||||
"failed send incoming packet",
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
let mut cache = ReceiveCache::default();
|
||||
|
||||
// tick at half the rate of the limit
|
||||
let tick = MAX_INCOMING_PACKETS_PER_MINUTE_BY_IP / 2;
|
||||
let mut interval = tokio::time::interval(Duration::from_secs(tick as u64));
|
||||
|
||||
let mut handler = IngressHandler::new(tx, local_id);
|
||||
let mut buf = [0; MAX_PACKET_SIZE];
|
||||
loop {
|
||||
let res = udp.recv_from(&mut buf).await;
|
||||
match res {
|
||||
Err(err) => {
|
||||
debug!(target: "discv4", %err, "Failed to read datagram.");
|
||||
send(IngressEvent::RecvError(err)).await;
|
||||
handler.send(IngressEvent::RecvError(err)).await;
|
||||
}
|
||||
Ok((read, remote_addr)) => {
|
||||
// rate limit incoming packets by IP
|
||||
if cache.inc_ip(remote_addr.ip()) > MAX_INCOMING_PACKETS_PER_MINUTE_BY_IP {
|
||||
trace!(target: "discv4", ?remote_addr, "Too many incoming packets from IP.");
|
||||
continue
|
||||
}
|
||||
|
||||
let packet = &buf[..read];
|
||||
match Message::decode(packet) {
|
||||
Ok(packet) => {
|
||||
if packet.node_id == local_id {
|
||||
// received our own message
|
||||
debug!(target: "discv4", ?remote_addr, "Received own packet.");
|
||||
continue
|
||||
}
|
||||
|
||||
// skip if we've already received the same packet
|
||||
if cache.contains_packet(packet.hash) {
|
||||
debug!(target: "discv4", ?remote_addr, "Received duplicate packet.");
|
||||
continue
|
||||
}
|
||||
|
||||
send(IngressEvent::Packet(remote_addr, packet)).await;
|
||||
}
|
||||
Err(err) => {
|
||||
trace!(target: "discv4", %err,"Failed to decode packet");
|
||||
send(IngressEvent::BadPacket(remote_addr, err, packet.to_vec())).await
|
||||
}
|
||||
}
|
||||
handler.handle_packet(&buf[..read], remote_addr).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reset the tracked ips if the interval has passed
|
||||
if poll_fn(|cx| match interval.poll_tick(cx) {
|
||||
Poll::Ready(_) => Poll::Ready(true),
|
||||
Poll::Pending => Poll::Ready(false),
|
||||
})
|
||||
.await
|
||||
{
|
||||
cache.tick_ips(tick);
|
||||
/// Handles decoding, rate-limiting, and deduplication of incoming discv4 packets.
|
||||
///
|
||||
/// Used by both the standalone receive loop and the shared-port mode via
|
||||
/// [`Discv4::bind_shared`].
|
||||
#[derive(Debug)]
|
||||
pub struct IngressHandler {
|
||||
tx: IngressSender,
|
||||
local_id: PeerId,
|
||||
tick: usize,
|
||||
tick_interval: Duration,
|
||||
cache: ReceiveCache,
|
||||
last_tick: Instant,
|
||||
}
|
||||
|
||||
impl IngressHandler {
|
||||
fn new(tx: IngressSender, local_id: PeerId) -> Self {
|
||||
let tick = MAX_INCOMING_PACKETS_PER_MINUTE_BY_IP / 2;
|
||||
Self {
|
||||
tx,
|
||||
local_id,
|
||||
tick,
|
||||
tick_interval: Duration::from_secs(tick as u64),
|
||||
cache: ReceiveCache::default(),
|
||||
last_tick: Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn send(&self, event: IngressEvent) {
|
||||
let _ = self.tx.send(event).await.map_err(|err| {
|
||||
debug!(target: "discv4", %err, "failed send incoming packet");
|
||||
});
|
||||
}
|
||||
|
||||
/// Handles an incoming raw packet: decodes, rate-limits, deduplicates, and forwards to the
|
||||
/// discv4 service. Used in shared-port mode to process unrecognized frames from discv5.
|
||||
pub async fn handle_packet(&mut self, data: &[u8], src: SocketAddr) {
|
||||
if self.last_tick.elapsed() >= self.tick_interval {
|
||||
self.cache.tick_ips(self.tick);
|
||||
self.last_tick = Instant::now();
|
||||
}
|
||||
|
||||
// rate limit incoming packets by IP
|
||||
if self.cache.inc_ip(src.ip()) > MAX_INCOMING_PACKETS_PER_MINUTE_BY_IP {
|
||||
trace!(target: "discv4", ?src, "Too many incoming packets from IP.");
|
||||
return
|
||||
}
|
||||
|
||||
let event = match Message::decode(data) {
|
||||
Ok(packet) => {
|
||||
if packet.node_id == self.local_id {
|
||||
debug!(target: "discv4", ?src, "Received own packet.");
|
||||
return
|
||||
}
|
||||
|
||||
if self.cache.contains_packet(packet.hash) {
|
||||
debug!(target: "discv4", ?src, "Received duplicate packet.");
|
||||
return
|
||||
}
|
||||
|
||||
IngressEvent::Packet(src, packet)
|
||||
}
|
||||
Err(err) => {
|
||||
trace!(target: "discv4", %err, "Failed to decode packet");
|
||||
IngressEvent::BadPacket(src, err, data.to_vec())
|
||||
}
|
||||
};
|
||||
|
||||
self.send(event).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// A cache for received packets and their source address.
|
||||
///
|
||||
/// This is used to discard duplicated packets and rate limit messages from the same source.
|
||||
#[derive(Debug)]
|
||||
struct ReceiveCache {
|
||||
/// keeps track of how many messages we've received from a given IP address since the last
|
||||
/// tick.
|
||||
|
||||
@@ -308,6 +308,18 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner [`discv5::Config`]. This allows overriding
|
||||
/// the listen config after the config has been built.
|
||||
pub const fn discv5_config_mut(&mut self) -> &mut discv5::Config {
|
||||
&mut self.discv5_config
|
||||
}
|
||||
|
||||
/// Returns `true` if any socket in the discv5 listen config matches the given address.
|
||||
pub fn has_matching_socket(&self, addr: SocketAddr) -> bool {
|
||||
ipv4(&self.discv5_config.listen_config).is_some_and(|v4| SocketAddr::V4(v4) == addr) ||
|
||||
ipv6(&self.discv5_config.listen_config).is_some_and(|v6| SocketAddr::V6(v6) == addr)
|
||||
}
|
||||
|
||||
/// Inserts a new boot node to the list of boot nodes.
|
||||
pub fn insert_boot_node(&mut self, boot_node: BootNode) {
|
||||
self.bootstrap_nodes.insert(boot_node);
|
||||
@@ -333,11 +345,11 @@ impl Config {
|
||||
/// socket, if both IPv4 and v6 are configured. This socket will be advertised to peers in the
|
||||
/// local [`Enr`](discv5::enr::Enr).
|
||||
pub fn discovery_socket(&self) -> SocketAddr {
|
||||
match self.discv5_config.listen_config {
|
||||
ListenConfig::Ipv4 { ip, port } => (ip, port).into(),
|
||||
ListenConfig::Ipv6 { ip, port } => (ip, port).into(),
|
||||
ListenConfig::DualStack { ipv6, ipv6_port, .. } => (ipv6, ipv6_port).into(),
|
||||
}
|
||||
// Prefer v6 when both are configured (matches original `DualStack` behavior).
|
||||
ipv6(&self.discv5_config.listen_config)
|
||||
.map(SocketAddr::V6)
|
||||
.or_else(|| ipv4(&self.discv5_config.listen_config).map(SocketAddr::V4))
|
||||
.unwrap_or_else(|| SocketAddr::from((std::net::Ipv4Addr::UNSPECIFIED, 0)))
|
||||
}
|
||||
|
||||
/// Returns the `RLPx` (TCP) socket contained in the [`discv5::Config`]. This socket will be
|
||||
@@ -348,24 +360,32 @@ impl Config {
|
||||
}
|
||||
|
||||
/// Returns the IPv4 discovery socket if one is configured.
|
||||
pub const fn ipv4(listen_config: &ListenConfig) -> Option<SocketAddrV4> {
|
||||
pub fn ipv4(listen_config: &ListenConfig) -> Option<SocketAddrV4> {
|
||||
match listen_config {
|
||||
ListenConfig::Ipv4 { ip, port } |
|
||||
ListenConfig::DualStack { ipv4: ip, ipv4_port: port, .. } => {
|
||||
Some(SocketAddrV4::new(*ip, *port))
|
||||
}
|
||||
ListenConfig::Ipv6 { .. } => None,
|
||||
ListenConfig::FromSockets { ipv4: Some(s), .. } => match s.local_addr().ok()? {
|
||||
SocketAddr::V4(addr) => Some(addr),
|
||||
SocketAddr::V6(_) => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the IPv6 discovery socket if one is configured.
|
||||
pub const fn ipv6(listen_config: &ListenConfig) -> Option<SocketAddrV6> {
|
||||
pub fn ipv6(listen_config: &ListenConfig) -> Option<SocketAddrV6> {
|
||||
match listen_config {
|
||||
ListenConfig::Ipv4 { .. } => None,
|
||||
ListenConfig::Ipv6 { ip, port } |
|
||||
ListenConfig::DualStack { ipv6: ip, ipv6_port: port, .. } => {
|
||||
Some(SocketAddrV6::new(*ip, *port, 0, 0))
|
||||
}
|
||||
ListenConfig::FromSockets { ipv6: Some(s), .. } => match s.local_addr().ok()? {
|
||||
SocketAddr::V6(addr) => Some(addr),
|
||||
SocketAddr::V4(_) => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ use std::{
|
||||
|
||||
use ::enr::Enr;
|
||||
use alloy_primitives::bytes::Bytes;
|
||||
use discv5::ListenConfig;
|
||||
use enr::{discv4_id_to_discv5_id, EnrCombinedKeyWrapper};
|
||||
use futures::future::join_all;
|
||||
use itertools::Itertools;
|
||||
@@ -247,7 +246,9 @@ impl Discv5 {
|
||||
match update {
|
||||
discv5::Event::SocketUpdated(_) | discv5::Event::TalkRequest(_) |
|
||||
// `Discovered` not unique discovered peers
|
||||
discv5::Event::Discovered(_) => None,
|
||||
discv5::Event::Discovered(_) |
|
||||
// Unrecognized frames are handled separately by the discovery layer
|
||||
discv5::Event::UnrecognizedFrame(_) => None,
|
||||
discv5::Event::NodeInserted { .. } => {
|
||||
|
||||
// node has been inserted into kbuckets
|
||||
@@ -472,39 +473,33 @@ pub fn build_local_enr(
|
||||
|
||||
let Config { discv5_config, fork, tcp_socket, other_enr_kv_pairs, .. } = config;
|
||||
|
||||
let socket = match discv5_config.listen_config {
|
||||
ListenConfig::Ipv4 { ip, port } => {
|
||||
if ip != Ipv4Addr::UNSPECIFIED {
|
||||
builder.ip4(ip);
|
||||
}
|
||||
builder.udp4(port);
|
||||
builder.tcp4(tcp_socket.port());
|
||||
let socket = {
|
||||
let v4 = crate::config::ipv4(&discv5_config.listen_config);
|
||||
let v6 = crate::config::ipv6(&discv5_config.listen_config);
|
||||
|
||||
(ip, port).into()
|
||||
}
|
||||
ListenConfig::Ipv6 { ip, port } => {
|
||||
if ip != Ipv6Addr::UNSPECIFIED {
|
||||
builder.ip6(ip);
|
||||
if let Some(addr) = v4 {
|
||||
if *addr.ip() != Ipv4Addr::UNSPECIFIED {
|
||||
builder.ip4(*addr.ip());
|
||||
}
|
||||
builder.udp6(port);
|
||||
builder.udp4(addr.port());
|
||||
}
|
||||
if let Some(addr) = v6 {
|
||||
if *addr.ip() != Ipv6Addr::UNSPECIFIED {
|
||||
builder.ip6(*addr.ip());
|
||||
}
|
||||
builder.udp6(addr.port());
|
||||
}
|
||||
// Advertise tcp4 when v4 is configured, else tcp6.
|
||||
if v4.is_some() {
|
||||
builder.tcp4(tcp_socket.port());
|
||||
} else if v6.is_some() {
|
||||
builder.tcp6(tcp_socket.port());
|
||||
|
||||
(ip, port).into()
|
||||
}
|
||||
ListenConfig::DualStack { ipv4, ipv4_port, ipv6, ipv6_port } => {
|
||||
if ipv4 != Ipv4Addr::UNSPECIFIED {
|
||||
builder.ip4(ipv4);
|
||||
}
|
||||
builder.udp4(ipv4_port);
|
||||
builder.tcp4(tcp_socket.port());
|
||||
|
||||
if ipv6 != Ipv6Addr::UNSPECIFIED {
|
||||
builder.ip6(ipv6);
|
||||
}
|
||||
builder.udp6(ipv6_port);
|
||||
|
||||
(ipv6, ipv6_port).into()
|
||||
}
|
||||
// Prefer v6 when both are configured
|
||||
v6.map(SocketAddr::V6)
|
||||
.or_else(|| v4.map(SocketAddr::V4))
|
||||
.unwrap_or_else(|| SocketAddr::from((Ipv4Addr::UNSPECIFIED, 0)))
|
||||
};
|
||||
|
||||
let rlpx_ip_mode = if tcp_socket.is_ipv4() { IpMode::Ip4 } else { IpMode::Ip6 };
|
||||
@@ -711,6 +706,7 @@ mod test {
|
||||
#![allow(deprecated)]
|
||||
use super::*;
|
||||
use ::enr::{CombinedKey, EnrKey};
|
||||
use discv5::ListenConfig;
|
||||
use rand_08::thread_rng;
|
||||
use reth_chainspec::MAINNET;
|
||||
use std::{
|
||||
|
||||
@@ -7,13 +7,14 @@ use alloy_primitives::{
|
||||
Bytes, TxHash, B256, U128,
|
||||
};
|
||||
use alloy_rlp::{
|
||||
Decodable, Encodable, RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper,
|
||||
Decodable, Encodable, Header, RlpDecodable, RlpDecodableWrapper, RlpEncodable,
|
||||
RlpEncodableWrapper,
|
||||
};
|
||||
use core::{fmt::Debug, mem};
|
||||
use derive_more::{Constructor, Deref, DerefMut, From, IntoIterator};
|
||||
use reth_codecs_derive::{add_arbitrary_tests, generate_tests};
|
||||
use reth_ethereum_primitives::TransactionSigned;
|
||||
use reth_primitives_traits::{Block, SignedTransaction};
|
||||
use reth_primitives_traits::{Block, InMemorySize, SignedTransaction};
|
||||
|
||||
/// This informs peers of new blocks that have appeared on the network.
|
||||
#[derive(
|
||||
@@ -143,6 +144,53 @@ impl<T> From<Transactions<T>> for Vec<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Decodable + InMemorySize> Transactions<T> {
|
||||
/// Decodes the RLP list of transactions, stopping once the cumulative
|
||||
/// [`InMemorySize`] of decoded transactions exceeds `memory_budget` bytes.
|
||||
/// Any remaining transactions in the payload are skipped.
|
||||
pub fn decode_with_memory_budget(
|
||||
buf: &mut &[u8],
|
||||
memory_budget: usize,
|
||||
) -> alloy_rlp::Result<Self> {
|
||||
decode_list_with_memory_budget(buf, memory_budget).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decodes an RLP list, stopping once the cumulative [`InMemorySize`] of decoded items exceeds
|
||||
/// `memory_budget` bytes. Any remaining items in the payload are skipped.
|
||||
pub fn decode_list_with_memory_budget<T: Decodable + InMemorySize>(
|
||||
buf: &mut &[u8],
|
||||
memory_budget: usize,
|
||||
) -> alloy_rlp::Result<Vec<T>> {
|
||||
let header = Header::decode(buf)?;
|
||||
if !header.list {
|
||||
return Err(alloy_rlp::Error::UnexpectedString);
|
||||
}
|
||||
if buf.len() < header.payload_length {
|
||||
return Err(alloy_rlp::Error::InputTooShort);
|
||||
}
|
||||
|
||||
let (payload, rest) = buf.split_at(header.payload_length);
|
||||
let mut payload = payload;
|
||||
|
||||
let mut txs = Vec::new();
|
||||
let mut total_size = 0usize;
|
||||
|
||||
while !payload.is_empty() {
|
||||
let item = T::decode(&mut payload)?;
|
||||
total_size = total_size.saturating_add(item.size());
|
||||
|
||||
if total_size > memory_budget {
|
||||
break;
|
||||
}
|
||||
|
||||
txs.push(item);
|
||||
}
|
||||
|
||||
*buf = rest;
|
||||
Ok(txs)
|
||||
}
|
||||
|
||||
/// Same as [`Transactions`] but this is intended as egress message send from local to _many_ peers.
|
||||
///
|
||||
/// The list of transactions is constructed on per-peers basis, but the underlying transaction
|
||||
@@ -837,6 +885,19 @@ pub struct BlockRangeUpdate {
|
||||
pub latest_hash: B256,
|
||||
}
|
||||
|
||||
impl InMemorySize for NewPooledTransactionHashes {
|
||||
fn size(&self) -> usize {
|
||||
match self {
|
||||
Self::Eth66(msg) => msg.0.len() * core::mem::size_of::<B256>(),
|
||||
Self::Eth68(msg) => {
|
||||
msg.types.len() * core::mem::size_of::<u8>() +
|
||||
msg.sizes.len() * core::mem::size_of::<usize>() +
|
||||
msg.hashes.len() * core::mem::size_of::<B256>()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -28,6 +28,15 @@ use core::fmt::Debug;
|
||||
// https://github.com/ethereum/go-ethereum/blob/30602163d5d8321fbc68afdcbbaf2362b2641bde/eth/protocols/eth/protocol.go#L50
|
||||
pub const MAX_MESSAGE_SIZE: usize = 10 * 1024 * 1024;
|
||||
|
||||
/// Multiplier applied to `max_message_size` to derive the in-memory budget for decoding
|
||||
/// `Transactions` and `PooledTransactions` messages.
|
||||
///
|
||||
/// Decoded transactions expand relative to their RLP encoding due to struct overhead and heap
|
||||
/// allocations. With many peers in flight this can cause significant memory pressure, so we
|
||||
/// stop decoding once the cumulative in-memory size of decoded transactions exceeds
|
||||
/// `max_message_size * TX_MEMORY_BUDGET_MULTIPLIER`. Remaining transactions are silently dropped.
|
||||
pub const TX_MEMORY_BUDGET_MULTIPLIER: usize = 2;
|
||||
|
||||
/// Error when sending/receiving a message
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum MessageError {
|
||||
@@ -87,6 +96,19 @@ impl<N: NetworkPrimitives> ProtocolMessage<N> {
|
||||
///
|
||||
/// This will enforce decoding according to the given [`EthVersion`] of the connection.
|
||||
pub fn decode_message(version: EthVersion, buf: &mut &[u8]) -> Result<Self, MessageError> {
|
||||
Self::decode_message_with_tx_memory_budget(version, buf, usize::MAX)
|
||||
}
|
||||
|
||||
/// Like [`Self::decode_message`], but caps the cumulative in-memory size of decoded
|
||||
/// transactions in `Transactions` and `PooledTransactions` messages. Once exceeded,
|
||||
/// remaining transactions are silently dropped.
|
||||
///
|
||||
/// Use [`TX_MEMORY_BUDGET_MULTIPLIER`] to derive a reasonable default.
|
||||
pub fn decode_message_with_tx_memory_budget(
|
||||
version: EthVersion,
|
||||
buf: &mut &[u8],
|
||||
tx_memory_budget: usize,
|
||||
) -> Result<Self, MessageError> {
|
||||
let message_type = EthMessageID::decode(buf)?;
|
||||
|
||||
// For EIP-7642 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7642.md):
|
||||
@@ -103,7 +125,9 @@ impl<N: NetworkPrimitives> ProtocolMessage<N> {
|
||||
EthMessageID::NewBlock => {
|
||||
EthMessage::NewBlock(Box::new(N::NewBlockPayload::decode(buf)?))
|
||||
}
|
||||
EthMessageID::Transactions => EthMessage::Transactions(Transactions::decode(buf)?),
|
||||
EthMessageID::Transactions => EthMessage::Transactions(
|
||||
Transactions::decode_with_memory_budget(buf, tx_memory_budget)?,
|
||||
),
|
||||
EthMessageID::NewPooledTransactionHashes => {
|
||||
if version >= EthVersion::Eth68 {
|
||||
EthMessage::NewPooledTransactionHashes68(NewPooledTransactionHashes68::decode(
|
||||
@@ -123,7 +147,9 @@ impl<N: NetworkPrimitives> ProtocolMessage<N> {
|
||||
EthMessage::GetPooledTransactions(RequestPair::decode(buf)?)
|
||||
}
|
||||
EthMessageID::PooledTransactions => {
|
||||
EthMessage::PooledTransactions(RequestPair::decode(buf)?)
|
||||
EthMessage::PooledTransactions(RequestPair::decode_with(buf, |buf| {
|
||||
PooledTransactions::decode_with_memory_budget(buf, tx_memory_budget)
|
||||
})?)
|
||||
}
|
||||
EthMessageID::GetNodeData => {
|
||||
if version >= EthVersion::Eth67 {
|
||||
@@ -732,6 +758,25 @@ impl<T> RequestPair<T> {
|
||||
let Self { request_id, message } = self;
|
||||
RequestPair { request_id, message: f(message) }
|
||||
}
|
||||
|
||||
/// Decodes the request id and then decodes the message payload using `decode_msg`.
|
||||
pub fn decode_with<F>(buf: &mut &[u8], decode_msg: F) -> alloy_rlp::Result<Self>
|
||||
where
|
||||
F: FnOnce(&mut &[u8]) -> alloy_rlp::Result<T>,
|
||||
{
|
||||
let header = Header::decode(buf)?;
|
||||
|
||||
let initial_length = buf.len();
|
||||
let request_id = u64::decode(buf)?;
|
||||
let message = decode_msg(buf)?;
|
||||
|
||||
let consumed_len = initial_length - buf.len();
|
||||
if consumed_len != header.payload_length {
|
||||
return Err(alloy_rlp::Error::UnexpectedLength)
|
||||
}
|
||||
|
||||
Ok(Self { request_id, message })
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows messages with request ids to be serialized into RLP bytes.
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
//! Implements the `GetPooledTransactions` and `PooledTransactions` message types.
|
||||
|
||||
use crate::broadcast::decode_list_with_memory_budget;
|
||||
use alloc::vec::Vec;
|
||||
use alloy_consensus::transaction::PooledTransaction;
|
||||
use alloy_eips::eip2718::Encodable2718;
|
||||
use alloy_primitives::B256;
|
||||
use alloy_rlp::{RlpDecodableWrapper, RlpEncodableWrapper};
|
||||
use alloy_rlp::{Decodable, RlpDecodableWrapper, RlpEncodableWrapper};
|
||||
use derive_more::{Constructor, Deref, IntoIterator};
|
||||
use reth_codecs_derive::add_arbitrary_tests;
|
||||
use reth_primitives_traits::InMemorySize;
|
||||
|
||||
/// A list of transaction hashes that the peer would like transaction bodies for.
|
||||
#[derive(
|
||||
@@ -37,6 +39,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl InMemorySize for GetPooledTransactions {
|
||||
fn size(&self) -> usize {
|
||||
self.0.len() * core::mem::size_of::<B256>()
|
||||
}
|
||||
}
|
||||
|
||||
/// The response to [`GetPooledTransactions`], containing the transaction bodies associated with
|
||||
/// the requested hashes.
|
||||
///
|
||||
@@ -62,6 +70,18 @@ pub struct PooledTransactions<T = PooledTransaction>(
|
||||
pub Vec<T>,
|
||||
);
|
||||
|
||||
impl<T: Decodable + InMemorySize> PooledTransactions<T> {
|
||||
/// Decodes the RLP list of transactions, stopping once the cumulative
|
||||
/// [`InMemorySize`] of decoded transactions exceeds `memory_budget` bytes.
|
||||
/// Any remaining transactions in the payload are skipped.
|
||||
pub fn decode_with_memory_budget(
|
||||
buf: &mut &[u8],
|
||||
memory_budget: usize,
|
||||
) -> alloy_rlp::Result<Self> {
|
||||
decode_list_with_memory_budget(buf, memory_budget).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Encodable2718> PooledTransactions<T> {
|
||||
/// Returns an iterator over the transaction hashes in this response.
|
||||
pub fn hashes(&self) -> impl Iterator<Item = B256> + '_ {
|
||||
|
||||
@@ -35,10 +35,11 @@ pub enum EthVersion {
|
||||
|
||||
impl EthVersion {
|
||||
/// The latest known eth version
|
||||
pub const LATEST: Self = Self::Eth69;
|
||||
pub const LATEST: Self = Self::Eth70;
|
||||
|
||||
/// All known eth versions
|
||||
pub const ALL_VERSIONS: &'static [Self] = &[Self::Eth69, Self::Eth68, Self::Eth67, Self::Eth66];
|
||||
pub const ALL_VERSIONS: &'static [Self] =
|
||||
&[Self::Eth70, Self::Eth69, Self::Eth68, Self::Eth67, Self::Eth66];
|
||||
|
||||
/// Returns true if the version is eth/66
|
||||
pub const fn is_eth66(&self) -> bool {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
use super::message::MAX_MESSAGE_SIZE;
|
||||
use crate::{
|
||||
message::{EthBroadcastMessage, ProtocolBroadcastMessage},
|
||||
message::{EthBroadcastMessage, ProtocolBroadcastMessage, TX_MEMORY_BUDGET_MULTIPLIER},
|
||||
EthMessage, EthMessageID, EthNetworkPrimitives, EthVersion, NetworkPrimitives, ProtocolMessage,
|
||||
RawCapabilityMessage, SnapProtocolMessage, SnapVersion,
|
||||
};
|
||||
@@ -298,7 +298,11 @@ where
|
||||
// See also <https://github.com/paradigmxyz/reth/blob/main/crates/net/eth-wire/src/capability.rs#L272-L283>.
|
||||
if message_id <= EthMessageID::max(self.eth_version) {
|
||||
let mut buf = bytes.as_ref();
|
||||
match ProtocolMessage::decode_message(self.eth_version, &mut buf) {
|
||||
match ProtocolMessage::decode_message_with_tx_memory_budget(
|
||||
self.eth_version,
|
||||
&mut buf,
|
||||
self.max_message_size * TX_MEMORY_BUDGET_MULTIPLIER,
|
||||
) {
|
||||
Ok(protocol_msg) => {
|
||||
if matches!(protocol_msg.message, EthMessage::Status(_)) {
|
||||
return Err(EthSnapStreamError::StatusNotInHandshake);
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
use crate::{
|
||||
errors::{EthHandshakeError, EthStreamError},
|
||||
handshake::EthereumEthHandshake,
|
||||
message::{EthBroadcastMessage, EthMessageID, ProtocolBroadcastMessage, MAX_MESSAGE_SIZE},
|
||||
message::{
|
||||
EthBroadcastMessage, ProtocolBroadcastMessage, MAX_MESSAGE_SIZE,
|
||||
TX_MEMORY_BUDGET_MULTIPLIER,
|
||||
},
|
||||
p2pstream::HANDSHAKE_TIMEOUT,
|
||||
CanDisconnect, DisconnectReason, EthMessage, EthNetworkPrimitives, EthVersion, ProtocolMessage,
|
||||
UnifiedStatus,
|
||||
@@ -16,7 +19,7 @@ use alloy_primitives::bytes::{Bytes, BytesMut};
|
||||
use alloy_rlp::Encodable;
|
||||
use futures::{ready, Sink, SinkExt};
|
||||
use pin_project::pin_project;
|
||||
use reth_eth_wire_types::{NetworkPrimitives, RawCapabilityMessage};
|
||||
use reth_eth_wire_types::{EthMessageID, NetworkPrimitives, RawCapabilityMessage};
|
||||
use reth_ethereum_forks::ForkFilter;
|
||||
use std::{
|
||||
future::Future,
|
||||
@@ -158,7 +161,11 @@ where
|
||||
return Err(EthStreamError::UnsupportedMessage { message_id: id });
|
||||
}
|
||||
|
||||
let msg = match ProtocolMessage::decode_message(self.version, &mut bytes.as_ref()) {
|
||||
let msg = match ProtocolMessage::decode_message_with_tx_memory_budget(
|
||||
self.version,
|
||||
&mut bytes.as_ref(),
|
||||
self.max_message_size * TX_MEMORY_BUDGET_MULTIPLIER,
|
||||
) {
|
||||
Ok(m) => m,
|
||||
Err(err) => {
|
||||
let msg = if bytes.len() > 50 {
|
||||
|
||||
@@ -50,6 +50,7 @@ reth-ethereum-primitives.workspace = true
|
||||
futures.workspace = true
|
||||
pin-project.workspace = true
|
||||
tokio = { workspace = true, features = ["io-util", "net", "macros", "rt-multi-thread", "time"] }
|
||||
socket2 = { workspace = true, features = ["all"] }
|
||||
tokio-stream.workspace = true
|
||||
tokio-util = { workspace = true, features = ["codec"] }
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use crate::{
|
||||
eth_requests::EthRequestHandler,
|
||||
metrics::NETWORK_POOL_TRANSACTIONS_SCOPE,
|
||||
transactions::{
|
||||
config::{
|
||||
AnnouncementFilteringPolicy, StrictEthAnnouncementFilter, TransactionPropagationKind,
|
||||
@@ -12,6 +13,7 @@ use crate::{
|
||||
NetworkHandle, NetworkManager,
|
||||
};
|
||||
use reth_eth_wire::{EthNetworkPrimitives, NetworkPrimitives};
|
||||
use reth_metrics::common::mpsc::memory_bounded_channel;
|
||||
use reth_network_api::test_utils::PeersHandleProvider;
|
||||
use reth_storage_api::BalProvider;
|
||||
use reth_transaction_pool::TransactionPool;
|
||||
@@ -122,7 +124,10 @@ impl<Tx, Eth, N: NetworkPrimitives> NetworkBuilder<Tx, Eth, N> {
|
||||
announcement_policy: A,
|
||||
) -> NetworkBuilder<TransactionsManager<Pool, N>, Eth, N> {
|
||||
let Self { mut network, request_handler, .. } = self;
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
let (tx, rx) = memory_bounded_channel(
|
||||
transactions_manager_config.tx_channel_memory_limit_bytes,
|
||||
NETWORK_POOL_TRANSACTIONS_SCOPE,
|
||||
);
|
||||
network.set_transactions(tx);
|
||||
let handle = network.handle().clone();
|
||||
let policies = NetworkPolicies::new(propagation_policy, announcement_policy);
|
||||
|
||||
@@ -23,7 +23,7 @@ use std::{
|
||||
sync::Arc,
|
||||
task::{ready, Context, Poll},
|
||||
};
|
||||
use tokio::{sync::mpsc, task::JoinHandle};
|
||||
use tokio::{net::UdpSocket, sync::mpsc, task::JoinHandle};
|
||||
use tokio_stream::{wrappers::ReceiverStream, Stream};
|
||||
use tracing::{debug, trace};
|
||||
|
||||
@@ -54,6 +54,9 @@ pub struct Discovery {
|
||||
discv5: Option<Discv5>,
|
||||
/// All KAD table updates from the discv5 service.
|
||||
discv5_updates: Option<ReceiverStream<discv5::Event>>,
|
||||
/// Background task that, in shared-port mode, drains `UnrecognizedFrame`s from discv5 and
|
||||
/// feeds them into the discv4 ingress so packets advance without polling `Discovery`.
|
||||
_discv5_forwarder: Option<JoinHandle<()>>,
|
||||
/// Handler to interact with the DNS discovery service
|
||||
_dns_discovery: Option<DnsDiscoveryHandle>,
|
||||
/// Updates from the DNS discovery service.
|
||||
@@ -76,39 +79,138 @@ impl Discovery {
|
||||
discovery_v4_addr: SocketAddr,
|
||||
sk: SecretKey,
|
||||
discv4_config: Option<Discv4Config>,
|
||||
discv5_config: Option<reth_discv5::Config>, // contains discv5 listen address
|
||||
mut discv5_config: Option<reth_discv5::Config>, // contains discv5 listen address
|
||||
dns_discovery_config: Option<DnsDiscoveryConfig>,
|
||||
) -> Result<Self, NetworkError> {
|
||||
// setup discv4 with the discovery address and tcp port
|
||||
let local_enr =
|
||||
NodeRecord::from_secret_key(discovery_v4_addr, &sk).with_tcp_port(tcp_addr.port());
|
||||
|
||||
let discv4_future = async {
|
||||
let Some(disc_config) = discv4_config else { return Ok((None, None, None)) };
|
||||
let (discv4, mut discv4_service) =
|
||||
Discv4::bind(discovery_v4_addr, local_enr, sk, disc_config).await.map_err(
|
||||
|err| {
|
||||
// For IPv6 we set IPV6_V6ONLY=true so an IPv4 sibling socket on the same port doesn't
|
||||
// clash with the IPv6 one (Linux's default of V6ONLY=0 has IPv6 also claim the IPv4
|
||||
// port via mapped addresses), matching how discv5 binds its `DualStack` sockets.
|
||||
let bind_socket = async |addr: SocketAddr| {
|
||||
let result = match addr {
|
||||
SocketAddr::V4(_) => UdpSocket::bind(addr).await,
|
||||
SocketAddr::V6(_) => {
|
||||
use socket2::{Domain, Protocol, Socket, Type};
|
||||
(|| {
|
||||
let socket = Socket::new(Domain::IPV6, Type::DGRAM, Some(Protocol::UDP))?;
|
||||
socket.set_only_v6(true)?;
|
||||
socket.set_nonblocking(true)?;
|
||||
socket.bind(&addr.into())?;
|
||||
UdpSocket::from_std(socket.into())
|
||||
})()
|
||||
}
|
||||
};
|
||||
result
|
||||
.map(Arc::new)
|
||||
.map_err(|err| NetworkError::from_io_error(err, ServiceKind::Discovery(addr)))
|
||||
};
|
||||
|
||||
// In shared-port mode, bind the shared socket and start discv4 without its own receive
|
||||
// loop. Unrecognized frames from discv5 will be forwarded to the ingress handler.
|
||||
let (discv4, discv4_updates, _discv4_service, discv4_ingress, shared_socket) =
|
||||
if let Some(config) = discv4_config {
|
||||
if let Some(discv5_config) = &mut discv5_config &&
|
||||
discv5_config.has_matching_socket(discovery_v4_addr)
|
||||
{
|
||||
let socket = bind_socket(discovery_v4_addr).await?;
|
||||
|
||||
let (discv4, mut discv4_service, ingress) = Discv4::bind_shared(
|
||||
socket.clone(),
|
||||
local_enr,
|
||||
sk,
|
||||
config,
|
||||
)
|
||||
.map_err(|err| {
|
||||
NetworkError::from_io_error(err, ServiceKind::Discovery(discovery_v4_addr))
|
||||
},
|
||||
)?;
|
||||
let discv4_updates = discv4_service.update_stream();
|
||||
// spawn the service
|
||||
let discv4_service = discv4_service.spawn();
|
||||
})?;
|
||||
|
||||
debug!(target:"net", ?discovery_v4_addr, "started discovery v4");
|
||||
let discv4_updates = discv4_service.update_stream();
|
||||
let discv4_service = discv4_service.spawn();
|
||||
debug!(target:"net", ?discovery_v4_addr, "started discovery v4 (shared port)");
|
||||
(
|
||||
Some(discv4),
|
||||
Some(discv4_updates),
|
||||
Some(discv4_service),
|
||||
Some(ingress),
|
||||
Some(socket),
|
||||
)
|
||||
} else {
|
||||
let (discv4, mut discv4_service) =
|
||||
Discv4::bind(discovery_v4_addr, local_enr, sk, config).await.map_err(
|
||||
|err| {
|
||||
NetworkError::from_io_error(
|
||||
err,
|
||||
ServiceKind::Discovery(discovery_v4_addr),
|
||||
)
|
||||
},
|
||||
)?;
|
||||
let discv4_updates = discv4_service.update_stream();
|
||||
// spawn the service
|
||||
let discv4_service = discv4_service.spawn();
|
||||
|
||||
Ok((Some(discv4), Some(discv4_updates), Some(discv4_service)))
|
||||
};
|
||||
debug!(target:"net", ?discovery_v4_addr, "started discovery v4");
|
||||
|
||||
(Some(discv4), Some(discv4_updates), Some(discv4_service), None, None)
|
||||
}
|
||||
} else {
|
||||
(None, None, None, None, None)
|
||||
};
|
||||
|
||||
// Start discv5, wiring in the shared socket if in shared-port mode.
|
||||
let (discv5, discv5_updates) = if let Some(mut config) = discv5_config {
|
||||
if let Some(socket) = shared_socket {
|
||||
let discv5_cfg = config.discv5_config_mut();
|
||||
|
||||
// The shared socket covers discv4's address family; bind the opposite family
|
||||
// only if discv5 was configured for dual-stack.
|
||||
let (mut ipv4, mut ipv6) = (None, None);
|
||||
if discovery_v4_addr.is_ipv4() {
|
||||
ipv4 = Some(socket);
|
||||
if let Some(addr) = reth_discv5::config::ipv6(&discv5_cfg.listen_config) {
|
||||
ipv6 = Some(bind_socket(SocketAddr::V6(addr)).await?);
|
||||
}
|
||||
} else {
|
||||
ipv6 = Some(socket);
|
||||
if let Some(addr) = reth_discv5::config::ipv4(&discv5_cfg.listen_config) {
|
||||
ipv4 = Some(bind_socket(SocketAddr::V4(addr)).await?);
|
||||
}
|
||||
}
|
||||
|
||||
discv5_cfg.listen_config = discv5::ListenConfig::FromSockets { ipv4, ipv6 };
|
||||
}
|
||||
|
||||
let discv5_future = async {
|
||||
let Some(config) = discv5_config else { return Ok::<_, NetworkError>((None, None)) };
|
||||
let (discv5, discv5_updates) = Discv5::start(&sk, config).await?;
|
||||
debug!(target:"net", discovery_v5_enr=? discv5.local_enr(), "started discovery v5");
|
||||
Ok((Some(discv5), Some(discv5_updates.into())))
|
||||
debug!(target:"net", discovery_v5_enr=?discv5.local_enr(), "started discovery v5");
|
||||
(Some(discv5), Some(discv5_updates))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let ((discv4, discv4_updates, _discv4_service), (discv5, discv5_updates)) =
|
||||
tokio::try_join!(discv4_future, discv5_future)?;
|
||||
// In shared-port mode, spawn a task that peels `UnrecognizedFrame` events off the discv5
|
||||
// update stream and feeds them into discv4's ingress. Other events are forwarded through
|
||||
// a new channel that `Discovery::poll` reads. This keeps both protocols moving without
|
||||
// requiring the main `Discovery::poll` loop to be driven for packets to be routed.
|
||||
let (discv5_updates, _discv5_forwarder) = match (discv4_ingress, discv5_updates) {
|
||||
(Some(mut ingress), Some(mut updates)) => {
|
||||
let (tx, rx) = mpsc::channel(updates.max_capacity());
|
||||
let handle = tokio::spawn(async move {
|
||||
while let Some(event) = updates.recv().await {
|
||||
if let discv5::Event::UnrecognizedFrame(frame) = &event {
|
||||
ingress.handle_packet(&frame.packet, frame.src_address).await;
|
||||
continue;
|
||||
}
|
||||
if tx.send(event).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
(Some(ReceiverStream::new(rx)), Some(handle))
|
||||
}
|
||||
(_, updates) => (updates.map(ReceiverStream::new), None),
|
||||
};
|
||||
|
||||
// setup DNS discovery
|
||||
let (_dns_discovery, dns_discovery_updates, _dns_disc_service) =
|
||||
@@ -132,6 +234,7 @@ impl Discovery {
|
||||
_discv4_service,
|
||||
discv5,
|
||||
discv5_updates,
|
||||
_discv5_forwarder,
|
||||
discovered_nodes: LruMap::new(DEFAULT_MAX_CAPACITY_DISCOVERED_PEERS_CACHE),
|
||||
queued_events: Default::default(),
|
||||
_dns_disc_service,
|
||||
@@ -309,6 +412,9 @@ impl Drop for Discovery {
|
||||
if let Some(handle) = self._discv4_service.take() {
|
||||
handle.abort();
|
||||
}
|
||||
if let Some(handle) = self._discv5_forwarder.take() {
|
||||
handle.abort();
|
||||
}
|
||||
if let Some(handle) = self._dns_disc_service.take() {
|
||||
handle.abort();
|
||||
}
|
||||
@@ -342,10 +448,11 @@ impl Discovery {
|
||||
},
|
||||
discv4: Default::default(),
|
||||
discv4_updates: Default::default(),
|
||||
_discv4_service: Default::default(),
|
||||
_discv5_forwarder: None,
|
||||
discv5: None,
|
||||
discv5_updates: None,
|
||||
queued_events: Default::default(),
|
||||
_discv4_service: Default::default(),
|
||||
_dns_discovery: None,
|
||||
dns_discovery_updates: None,
|
||||
_dns_disc_service: None,
|
||||
@@ -487,4 +594,179 @@ mod tests {
|
||||
assert_eq!(1, node_1.discovered_nodes.len());
|
||||
assert_eq!(1, node_2.discovered_nodes.len());
|
||||
}
|
||||
|
||||
/// Starts a discovery node with discv4 and discv5 sharing the same UDP port.
|
||||
async fn start_shared_port_node(port: u16) -> Discovery {
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let disc_addr: SocketAddr = format!("127.0.0.1:{port}").parse().unwrap();
|
||||
// Use a non-zero TCP port so the node record isn't filtered out by
|
||||
// `on_node_record_update` (which drops peers with tcp port == 0).
|
||||
let tcp_addr: SocketAddr = "127.0.0.1:30303".parse().unwrap();
|
||||
|
||||
let discv4_config = Discv4ConfigBuilder::default().external_ip_resolver(None).build();
|
||||
|
||||
let discv5_listen_config = discv5::ListenConfig::from(disc_addr);
|
||||
let discv5_config = reth_discv5::Config::builder(tcp_addr)
|
||||
.discv5_config(discv5::ConfigBuilder::new(discv5_listen_config).build())
|
||||
.build();
|
||||
|
||||
// Both protocols use the same address, triggering shared-port mode
|
||||
Discovery::new(
|
||||
tcp_addr,
|
||||
disc_addr,
|
||||
secret_key,
|
||||
Some(discv4_config),
|
||||
Some(discv5_config),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("should start with shared port")
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_shared_port_setup() {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
// Use port 0 so the OS picks a free port
|
||||
let node = start_shared_port_node(0).await;
|
||||
|
||||
// Both protocols should be active
|
||||
assert!(node.discv4.is_some(), "discv4 should be running");
|
||||
assert!(node.discv5.is_some(), "discv5 should be running");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_shared_port_discv5_discovery() {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let mut node_1 = start_shared_port_node(0).await;
|
||||
let mut node_2 = start_shared_port_node(0).await;
|
||||
|
||||
let discv5_enr_1 = node_1.discv5.as_ref().unwrap().with_discv5(|discv5| discv5.local_enr());
|
||||
let discv5_enr_2 = node_2.discv5.as_ref().unwrap().with_discv5(|discv5| discv5.local_enr());
|
||||
|
||||
let peer_id_1 = enr_to_discv4_id(&discv5_enr_1).unwrap();
|
||||
let peer_id_2 = enr_to_discv4_id(&discv5_enr_2).unwrap();
|
||||
|
||||
// Add node_2's ENR to node_1's discv5 kbuckets and trigger a ping to establish a session.
|
||||
// send_ping awaits the PONG, so the handshake completes before we poll the Discovery
|
||||
// stream. The discv5 service runs its own background task.
|
||||
node_1.add_discv5_node(EnrCombinedKeyWrapper(discv5_enr_2.clone()).into()).unwrap();
|
||||
node_1
|
||||
.discv5
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.with_discv5(|discv5| discv5.send_ping(discv5_enr_2))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Both SessionEstablished events should now be buffered in the update channels.
|
||||
// Drive both nodes concurrently to collect them.
|
||||
let mut event_1 = None;
|
||||
let mut event_2 = None;
|
||||
let timeout = tokio::time::sleep(std::time::Duration::from_secs(5));
|
||||
tokio::pin!(timeout);
|
||||
loop {
|
||||
tokio::select! {
|
||||
ev = node_1.next(), if event_1.is_none() => {
|
||||
event_1 = ev;
|
||||
}
|
||||
ev = node_2.next(), if event_2.is_none() => {
|
||||
event_2 = ev;
|
||||
}
|
||||
_ = &mut timeout => {
|
||||
panic!("timed out waiting for discv5 discovery events");
|
||||
}
|
||||
}
|
||||
if event_1.is_some() && event_2.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert!(matches!(
|
||||
event_1.unwrap(),
|
||||
DiscoveryEvent::NewNode(DiscoveredEvent::EventQueued { peer_id, .. })
|
||||
if peer_id == peer_id_2
|
||||
));
|
||||
assert!(matches!(
|
||||
event_2.unwrap(),
|
||||
DiscoveryEvent::NewNode(DiscoveredEvent::EventQueued { peer_id, .. })
|
||||
if peer_id == peer_id_1
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_shared_port_discv4_discovery() {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let mut node_1 = start_shared_port_node(0).await;
|
||||
let mut node_2 = start_shared_port_node(0).await;
|
||||
|
||||
let enr_1 = node_1.discv4.as_ref().unwrap().node_record();
|
||||
let enr_2 = node_2.discv4.as_ref().unwrap().node_record();
|
||||
|
||||
// Introduce node_2 to node_1 via discv4
|
||||
node_1.add_discv4_node(enr_2);
|
||||
|
||||
// Both nodes should discover each other via discv4 ping/pong
|
||||
let event_1 = node_1.next().await.unwrap();
|
||||
let event_2 = node_2.next().await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
DiscoveryEvent::NewNode(DiscoveredEvent::EventQueued {
|
||||
peer_id: enr_2.id,
|
||||
addr: PeerAddr::new(enr_2.tcp_addr(), Some(enr_2.udp_addr())),
|
||||
fork_id: None
|
||||
}),
|
||||
event_1
|
||||
);
|
||||
assert_eq!(
|
||||
DiscoveryEvent::NewNode(DiscoveredEvent::EventQueued {
|
||||
peer_id: enr_1.id,
|
||||
addr: PeerAddr::new(enr_1.tcp_addr(), Some(enr_1.udp_addr())),
|
||||
fork_id: None
|
||||
}),
|
||||
event_2
|
||||
);
|
||||
}
|
||||
|
||||
/// Verifies that shared-port mode binds correctly when discv5 is configured for dual-stack.
|
||||
/// On Linux this exercises the IPv6 V6ONLY path: without it, the IPv4 sibling would clash
|
||||
/// with the IPv6 socket bound to the same port.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_shared_port_dual_stack() {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
// Find a port that's free on the v4 wildcard so we can use it for both v4 and v6.
|
||||
let probe = UdpSocket::bind("0.0.0.0:0").await.expect("probe bind");
|
||||
let port = probe.local_addr().unwrap().port();
|
||||
drop(probe);
|
||||
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let v4_addr: SocketAddr = format!("0.0.0.0:{port}").parse().unwrap();
|
||||
let tcp_addr: SocketAddr = "0.0.0.0:30303".parse().unwrap();
|
||||
|
||||
let discv4_config = Discv4ConfigBuilder::default().external_ip_resolver(None).build();
|
||||
|
||||
let discv5_listen_config = discv5::ListenConfig::DualStack {
|
||||
ipv4: std::net::Ipv4Addr::UNSPECIFIED,
|
||||
ipv4_port: port,
|
||||
ipv6: std::net::Ipv6Addr::UNSPECIFIED,
|
||||
ipv6_port: port,
|
||||
};
|
||||
let discv5_config = reth_discv5::Config::builder(tcp_addr)
|
||||
.discv5_config(discv5::ConfigBuilder::new(discv5_listen_config).build())
|
||||
.build();
|
||||
|
||||
Discovery::new(
|
||||
tcp_addr,
|
||||
v4_addr,
|
||||
secret_key,
|
||||
Some(discv4_config),
|
||||
Some(discv5_config),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("discovery should start with shared port + dual-stack");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,11 @@ pub const MAX_HEADERS_SERVE: usize = 1024;
|
||||
/// `SOFT_RESPONSE_LIMIT`.
|
||||
pub const MAX_BODIES_SERVE: usize = 1024;
|
||||
|
||||
/// Maximum number of block access lists to serve.
|
||||
///
|
||||
/// Used to limit lookups.
|
||||
pub const MAX_BLOCK_ACCESS_LISTS_SERVE: usize = 1024;
|
||||
|
||||
/// Maximum size of replies to data retrievals: 2MB
|
||||
pub const SOFT_RESPONSE_LIMIT: usize = 2 * 1024 * 1024;
|
||||
|
||||
@@ -323,9 +328,11 @@ where
|
||||
fn on_block_access_lists_request(
|
||||
&self,
|
||||
_peer_id: PeerId,
|
||||
request: GetBlockAccessLists,
|
||||
mut request: GetBlockAccessLists,
|
||||
response: oneshot::Sender<RequestResult<BlockAccessLists>>,
|
||||
) {
|
||||
request.0.truncate(MAX_BLOCK_ACCESS_LISTS_SERVE);
|
||||
|
||||
let limit = GetBlockAccessListLimit::ResponseSizeSoftLimit(SOFT_RESPONSE_LIMIT);
|
||||
let access_lists = self
|
||||
.client
|
||||
|
||||
@@ -26,7 +26,7 @@ use crate::{
|
||||
message::{NewBlockMessage, PeerMessage},
|
||||
metrics::{
|
||||
BackedOffPeersMetrics, ClosedSessionsMetrics, DirectionalDisconnectMetrics, NetworkMetrics,
|
||||
PendingSessionFailureMetrics, NETWORK_POOL_TRANSACTIONS_SCOPE,
|
||||
PendingSessionFailureMetrics,
|
||||
},
|
||||
network::{NetworkHandle, NetworkHandleMessage},
|
||||
peers::{BackoffReason, PeersManager},
|
||||
@@ -44,7 +44,7 @@ use parking_lot::Mutex;
|
||||
use reth_chainspec::EnrForkIdEntry;
|
||||
use reth_eth_wire::{DisconnectReason, EthNetworkPrimitives, NetworkPrimitives};
|
||||
use reth_fs_util::{self as fs, FsPathError};
|
||||
use reth_metrics::common::mpsc::UnboundedMeteredSender;
|
||||
use reth_metrics::common::mpsc::MemoryBoundedSender;
|
||||
use reth_network_api::{
|
||||
events::{PeerEvent, SessionInfo},
|
||||
test_utils::PeersHandle,
|
||||
@@ -118,7 +118,7 @@ pub struct NetworkManager<N: NetworkPrimitives = EthNetworkPrimitives> {
|
||||
event_sender: EventSender<NetworkEvent<PeerRequest<N>>>,
|
||||
/// Sender half to send events to the
|
||||
/// [`TransactionsManager`](crate::transactions::TransactionsManager) task, if configured.
|
||||
to_transactions_manager: Option<UnboundedMeteredSender<NetworkTransactionEvent<N>>>,
|
||||
to_transactions_manager: Option<MemoryBoundedSender<NetworkTransactionEvent<N>>>,
|
||||
/// Sender half to send events to the
|
||||
/// [`EthRequestHandler`](crate::eth_requests::EthRequestHandler) task, if configured.
|
||||
///
|
||||
@@ -175,7 +175,7 @@ impl<N: NetworkPrimitives> NetworkManager<N> {
|
||||
/// [`TransactionsManager`](crate::transactions::TransactionsManager).
|
||||
pub fn with_transactions(
|
||||
mut self,
|
||||
tx: mpsc::UnboundedSender<NetworkTransactionEvent<N>>,
|
||||
tx: MemoryBoundedSender<NetworkTransactionEvent<N>>,
|
||||
) -> Self {
|
||||
self.set_transactions(tx);
|
||||
self
|
||||
@@ -183,9 +183,8 @@ impl<N: NetworkPrimitives> NetworkManager<N> {
|
||||
|
||||
/// Sets the dedicated channel for events intended for the
|
||||
/// [`TransactionsManager`](crate::transactions::TransactionsManager).
|
||||
pub fn set_transactions(&mut self, tx: mpsc::UnboundedSender<NetworkTransactionEvent<N>>) {
|
||||
self.to_transactions_manager =
|
||||
Some(UnboundedMeteredSender::new(tx, NETWORK_POOL_TRANSACTIONS_SCOPE));
|
||||
pub fn set_transactions(&mut self, tx: MemoryBoundedSender<NetworkTransactionEvent<N>>) {
|
||||
self.to_transactions_manager = Some(tx);
|
||||
}
|
||||
|
||||
/// Sets the dedicated channel for events intended for the
|
||||
@@ -496,8 +495,16 @@ impl<N: NetworkPrimitives> NetworkManager<N> {
|
||||
/// Sends an event to the [`TransactionsManager`](crate::transactions::TransactionsManager) if
|
||||
/// configured.
|
||||
fn notify_tx_manager(&self, event: NetworkTransactionEvent<N>) {
|
||||
if let Some(ref tx) = self.to_transactions_manager {
|
||||
let _ = tx.send(event);
|
||||
if let Some(ref tx) = self.to_transactions_manager &&
|
||||
let Err(e) = tx.try_send(event)
|
||||
{
|
||||
match e {
|
||||
TrySendError::Full(_) => {
|
||||
trace!(target: "net", "Transaction events channel at capacity, dropping event");
|
||||
self.metrics.total_dropped_tx_events_at_full_capacity.increment(1);
|
||||
}
|
||||
TrySendError::Closed(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -765,7 +772,7 @@ impl<N: NetworkPrimitives> NetworkManager<N> {
|
||||
NetworkHandleMessage::AddRlpxSubProtocol(proto) => self.add_rlpx_sub_protocol(proto),
|
||||
NetworkHandleMessage::GetTransactionsHandle(tx) => {
|
||||
if let Some(ref tx_inner) = self.to_transactions_manager {
|
||||
let _ = tx_inner.send(NetworkTransactionEvent::GetTransactionsHandle(tx));
|
||||
let _ = tx_inner.try_send(NetworkTransactionEvent::GetTransactionsHandle(tx));
|
||||
} else {
|
||||
let _ = tx.send(None);
|
||||
}
|
||||
|
||||
@@ -46,6 +46,9 @@ pub struct NetworkMetrics {
|
||||
/// Number of Eth Requests dropped due to channel being at full capacity
|
||||
pub(crate) total_dropped_eth_requests_at_full_capacity: Counter,
|
||||
|
||||
/// Number of transaction events dropped due to the tx manager channel being at full capacity
|
||||
pub(crate) total_dropped_tx_events_at_full_capacity: Counter,
|
||||
|
||||
/* ================ POLL DURATION ================ */
|
||||
|
||||
/* -- Total poll duration of `NetworksManager` future -- */
|
||||
|
||||
@@ -20,6 +20,7 @@ use reth_eth_wire::{
|
||||
};
|
||||
use reth_ethereum_primitives::{PooledTransactionVariant, TransactionSigned};
|
||||
use reth_evm_ethereum::EthEvmConfig;
|
||||
use reth_metrics::common::mpsc::memory_bounded_channel;
|
||||
use reth_network_api::{
|
||||
events::{PeerEvent, SessionInfo},
|
||||
test_utils::{PeersHandle, PeersHandleProvider},
|
||||
@@ -46,13 +47,12 @@ use std::{
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tokio::{
|
||||
sync::{
|
||||
mpsc::{channel, unbounded_channel},
|
||||
oneshot,
|
||||
},
|
||||
sync::{mpsc::channel, oneshot},
|
||||
task::JoinHandle,
|
||||
};
|
||||
|
||||
use crate::transactions::constants::tx_manager::DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES;
|
||||
|
||||
/// A test network consisting of multiple peers.
|
||||
pub struct Testnet<C, Pool> {
|
||||
/// All running peers in the network.
|
||||
@@ -478,7 +478,10 @@ where
|
||||
|
||||
/// Set a new transactions manager that's connected to the peer's network
|
||||
pub fn install_transactions_manager(&mut self, pool: Pool) {
|
||||
let (tx, rx) = unbounded_channel();
|
||||
let (tx, rx) = memory_bounded_channel(
|
||||
DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
|
||||
"test_tx_channel",
|
||||
);
|
||||
self.network.set_transactions(tx);
|
||||
let transactions_manager = TransactionsManager::new(
|
||||
self.handle(),
|
||||
@@ -496,7 +499,10 @@ where
|
||||
P: TransactionPool,
|
||||
{
|
||||
let Self { mut network, request_handler, client, secret_key, .. } = self;
|
||||
let (tx, rx) = unbounded_channel();
|
||||
let (tx, rx) = memory_bounded_channel(
|
||||
DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
|
||||
"test_tx_channel",
|
||||
);
|
||||
network.set_transactions(tx);
|
||||
let transactions_manager = TransactionsManager::new(
|
||||
network.handle().clone(),
|
||||
@@ -537,7 +543,10 @@ where
|
||||
P: TransactionPool,
|
||||
{
|
||||
let Self { mut network, request_handler, client, secret_key, .. } = self;
|
||||
let (tx, rx) = unbounded_channel();
|
||||
let (tx, rx) = memory_bounded_channel(
|
||||
DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
|
||||
"test_tx_channel",
|
||||
);
|
||||
network.set_transactions(tx);
|
||||
|
||||
let announcement_policy = StrictEthAnnouncementFilter::default();
|
||||
|
||||
@@ -6,9 +6,12 @@ use super::{
|
||||
DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
|
||||
SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
|
||||
};
|
||||
use crate::transactions::constants::tx_fetcher::{
|
||||
DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
|
||||
DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
|
||||
use crate::transactions::constants::{
|
||||
tx_fetcher::{
|
||||
DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
|
||||
DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
|
||||
},
|
||||
tx_manager::DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
|
||||
};
|
||||
use alloy_eips::eip2718::IsTyped2718;
|
||||
use alloy_primitives::B256;
|
||||
@@ -30,6 +33,17 @@ pub struct TransactionsManagerConfig {
|
||||
/// Which peers we accept incoming transactions or announcements from.
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub ingress_policy: TransactionIngressPolicy,
|
||||
/// Memory limit (in bytes) for the channel that carries
|
||||
/// `NetworkTransactionEvent`s from the `NetworkManager` to the `TransactionsManager`.
|
||||
///
|
||||
/// When the budget is exhausted, new events are dropped.
|
||||
#[cfg_attr(feature = "serde", serde(default = "default_tx_channel_memory_limit_bytes"))]
|
||||
pub tx_channel_memory_limit_bytes: usize,
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
const fn default_tx_channel_memory_limit_bytes() -> usize {
|
||||
DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES
|
||||
}
|
||||
|
||||
impl Default for TransactionsManagerConfig {
|
||||
@@ -39,6 +53,7 @@ impl Default for TransactionsManagerConfig {
|
||||
max_transactions_seen_by_peer_history: DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
|
||||
propagation_mode: TransactionPropagationMode::default(),
|
||||
ingress_policy: TransactionIngressPolicy::default(),
|
||||
tx_channel_memory_limit_bytes: DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,15 @@ pub mod tx_manager {
|
||||
///
|
||||
/// Default is 100 KiB, i.e. 3 200 transaction hashes.
|
||||
pub const DEFAULT_MAX_COUNT_BAD_IMPORTS: u32 = 100 * 1024 / 32;
|
||||
|
||||
/// Default memory limit (in bytes) for the channel between
|
||||
/// [`NetworkManager`](crate::NetworkManager) and
|
||||
/// [`TransactionsManager`](crate::transactions::TransactionsManager).
|
||||
///
|
||||
/// Caps the total in-flight bytes of `NetworkTransactionEvent`s buffered between the two
|
||||
/// tasks. When the budget is exhausted, new events are dropped (see metric
|
||||
/// `total_dropped_tx_events_at_full_capacity`).
|
||||
pub const DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES: usize = 1024 * 1024 * 1024;
|
||||
}
|
||||
|
||||
/// Constants used by [`TransactionFetcher`](super::TransactionFetcher).
|
||||
|
||||
@@ -33,9 +33,7 @@ use crate::{
|
||||
},
|
||||
cache::LruCache,
|
||||
duration_metered_exec, metered_poll_nested_stream_with_budget,
|
||||
metrics::{
|
||||
AnnouncedTxTypesMetrics, TransactionsManagerMetrics, NETWORK_POOL_TRANSACTIONS_SCOPE,
|
||||
},
|
||||
metrics::{AnnouncedTxTypesMetrics, TransactionsManagerMetrics},
|
||||
transactions::config::{StrictEthAnnouncementFilter, TransactionPropagationKind},
|
||||
NetworkHandle, TxTypesCounter,
|
||||
};
|
||||
@@ -49,7 +47,7 @@ use reth_eth_wire::{
|
||||
RequestTxHashes, Transactions, ValidAnnouncementData,
|
||||
};
|
||||
use reth_ethereum_primitives::{TransactionSigned, TxType};
|
||||
use reth_metrics::common::mpsc::UnboundedMeteredReceiver;
|
||||
use reth_metrics::common::mpsc::MemoryBoundedReceiver;
|
||||
use reth_network_api::{
|
||||
events::{PeerEvent, SessionInfo},
|
||||
NetworkEvent, NetworkEventListenerProvider, PeerKind, PeerRequest, PeerRequestSender, Peers,
|
||||
@@ -60,7 +58,7 @@ use reth_network_p2p::{
|
||||
};
|
||||
use reth_network_peers::PeerId;
|
||||
use reth_network_types::ReputationChangeKind;
|
||||
use reth_primitives_traits::SignedTransaction;
|
||||
use reth_primitives_traits::{InMemorySize, SignedTransaction};
|
||||
use reth_tokio_util::EventStream;
|
||||
use reth_transaction_pool::{
|
||||
error::{PoolError, PoolResult},
|
||||
@@ -333,7 +331,7 @@ pub struct TransactionsManager<Pool, N: NetworkPrimitives = EthNetworkPrimitives
|
||||
/// - account has enough balance to cover the transaction's gas
|
||||
pending_transactions: mpsc::Receiver<TxHash>,
|
||||
/// Incoming events from the [`NetworkManager`](crate::NetworkManager).
|
||||
transaction_events: UnboundedMeteredReceiver<NetworkTransactionEvent<N>>,
|
||||
transaction_events: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
|
||||
/// How the `TransactionsManager` is configured.
|
||||
config: TransactionsManagerConfig,
|
||||
/// Network Policies
|
||||
@@ -351,7 +349,7 @@ impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
|
||||
pub fn new(
|
||||
network: NetworkHandle<N>,
|
||||
pool: Pool,
|
||||
from_network: mpsc::UnboundedReceiver<NetworkTransactionEvent<N>>,
|
||||
from_network: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
|
||||
transactions_manager_config: TransactionsManagerConfig,
|
||||
) -> Self {
|
||||
Self::with_policy(
|
||||
@@ -374,7 +372,7 @@ impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
|
||||
pub fn with_policy(
|
||||
network: NetworkHandle<N>,
|
||||
pool: Pool,
|
||||
from_network: mpsc::UnboundedReceiver<NetworkTransactionEvent<N>>,
|
||||
from_network: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
|
||||
transactions_manager_config: TransactionsManagerConfig,
|
||||
policies: NetworkPolicies<N>,
|
||||
) -> Self {
|
||||
@@ -409,10 +407,7 @@ impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
|
||||
command_tx,
|
||||
command_rx: UnboundedReceiverStream::new(command_rx),
|
||||
pending_transactions: pending,
|
||||
transaction_events: UnboundedMeteredReceiver::new(
|
||||
from_network,
|
||||
NETWORK_POOL_TRANSACTIONS_SCOPE,
|
||||
),
|
||||
transaction_events: from_network,
|
||||
config: transactions_manager_config,
|
||||
policies,
|
||||
metrics,
|
||||
@@ -1626,7 +1621,7 @@ where
|
||||
"Network transaction events stream",
|
||||
DEFAULT_BUDGET_TRY_DRAIN_NETWORK_TRANSACTION_EVENTS,
|
||||
this.transaction_events.poll_next_unpin(cx),
|
||||
|event| this.on_network_tx_event(event),
|
||||
|event: NetworkTransactionEvent<N>| this.on_network_tx_event(event),
|
||||
);
|
||||
|
||||
// Advance inflight fetch requests (flush transaction fetcher and queue for
|
||||
@@ -2174,6 +2169,28 @@ struct TxManagerPollDurations {
|
||||
acc_cmds: Duration,
|
||||
}
|
||||
|
||||
impl<N: NetworkPrimitives> InMemorySize for NetworkTransactionEvent<N> {
|
||||
// `N::BroadcastedTransaction` and `N::PooledTransaction` already implement
|
||||
// `InMemorySize` via `SignedTransaction: InMemorySize`, so no extra bound is needed.
|
||||
fn size(&self) -> usize {
|
||||
match self {
|
||||
Self::IncomingTransactions { peer_id, msg } => {
|
||||
core::mem::size_of_val(peer_id) +
|
||||
msg.0.iter().map(InMemorySize::size).sum::<usize>()
|
||||
}
|
||||
Self::IncomingPooledTransactionHashes { peer_id, msg } => {
|
||||
core::mem::size_of_val(peer_id) + msg.size()
|
||||
}
|
||||
Self::GetPooledTransactions { peer_id, request, response } => {
|
||||
core::mem::size_of_val(peer_id) +
|
||||
request.0.len() * core::mem::size_of::<TxHash>() +
|
||||
core::mem::size_of_val(response)
|
||||
}
|
||||
Self::GetTransactionsHandle(_) => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -3106,7 +3123,12 @@ mod tests {
|
||||
|
||||
let mut network_manager = NetworkManager::new(network_config).await.unwrap();
|
||||
let (to_tx_manager_tx, from_network_rx) =
|
||||
mpsc::unbounded_channel::<NetworkTransactionEvent<EthNetworkPrimitives>>();
|
||||
reth_metrics::common::mpsc::memory_bounded_channel::<
|
||||
NetworkTransactionEvent<EthNetworkPrimitives>,
|
||||
>(
|
||||
crate::transactions::constants::tx_manager::DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
|
||||
"test_tx_channel",
|
||||
);
|
||||
network_manager.set_transactions(to_tx_manager_tx);
|
||||
let network_handle = network_manager.handle().clone();
|
||||
let network_service_handle = tokio::spawn(network_manager);
|
||||
|
||||
@@ -7,7 +7,7 @@ use rand::Rng;
|
||||
use reth_eth_wire::{BlockAccessLists, EthVersion, GetBlockAccessLists, HeadersDirection};
|
||||
use reth_ethereum_primitives::Block;
|
||||
use reth_network::{
|
||||
eth_requests::SOFT_RESPONSE_LIMIT,
|
||||
eth_requests::{MAX_BLOCK_ACCESS_LISTS_SERVE, SOFT_RESPONSE_LIMIT},
|
||||
test_utils::{NetworkEventStream, PeerConfig, Testnet, TestnetHandle},
|
||||
BlockDownloaderProvider, NetworkEventListenerProvider,
|
||||
};
|
||||
@@ -607,6 +607,26 @@ async fn test_eth71_get_block_access_lists_empty_request() {
|
||||
assert_eq!(response, BlockAccessLists(Vec::new()));
|
||||
}
|
||||
|
||||
// Ensures BAL responses are capped at MAX_BLOCK_ACCESS_LISTS_SERVE entries.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_eth71_get_block_access_lists_caps_count() {
|
||||
reth_tracing::init_test_tracing();
|
||||
let (net, bal_store) = spawn_eth71_bal_testnet().await;
|
||||
|
||||
// Request more hashes than the count cap.
|
||||
let request_count = MAX_BLOCK_ACCESS_LISTS_SERVE + 100;
|
||||
let hashes: Vec<B256> = (0..request_count).map(|_| B256::random()).collect();
|
||||
|
||||
// Insert one BAL so the store isn't entirely empty (not strictly needed,
|
||||
// but keeps the test path closer to real usage).
|
||||
let bal = Bytes::from_static(&[0xc1, 0x01]);
|
||||
bal_store.insert(hashes[0], 1, bal).unwrap();
|
||||
|
||||
let response = request_block_access_lists(&net, hashes).await;
|
||||
|
||||
assert_eq!(response.0.len(), MAX_BLOCK_ACCESS_LISTS_SERVE);
|
||||
}
|
||||
|
||||
// Ensures the fetch client can request BALs through an eth/71 peer.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_eth71_fetch_client_get_block_access_lists() {
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::{
|
||||
};
|
||||
|
||||
use reth_chainspec::MAINNET;
|
||||
use reth_discv4::{Discv4Config, NatResolver, DEFAULT_DISCOVERY_ADDR, DEFAULT_DISCOVERY_PORT};
|
||||
use reth_discv4::{Discv4Config, NatResolver, DEFAULT_DISCOVERY_ADDR};
|
||||
use reth_network::{
|
||||
error::{NetworkError, ServiceKind},
|
||||
Discovery, NetworkConfigBuilder, NetworkManager,
|
||||
@@ -73,27 +73,31 @@ async fn test_discovery_addr_in_use() {
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_discv5_and_discv4_same_socket_fails() {
|
||||
async fn test_discv5_and_discv4_same_socket_ok() {
|
||||
// Pick a free port for the shared UDP discovery socket and TCP RLPx listener.
|
||||
let test_port: u16 = TcpListener::bind("127.0.0.1:0")
|
||||
.await
|
||||
.expect("Failed to bind to a port")
|
||||
.local_addr()
|
||||
.unwrap()
|
||||
.port();
|
||||
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.listener_port(DEFAULT_DISCOVERY_PORT)
|
||||
.listener_port(test_port)
|
||||
.discovery_port(test_port)
|
||||
.discovery_v5(
|
||||
reth_discv5::Config::builder((DEFAULT_DISCOVERY_ADDR, DEFAULT_DISCOVERY_PORT).into())
|
||||
.discv5_config(
|
||||
discv5::ConfigBuilder::new(discv5::ListenConfig::from_ip(
|
||||
DEFAULT_DISCOVERY_ADDR,
|
||||
DEFAULT_DISCOVERY_PORT,
|
||||
))
|
||||
.build(),
|
||||
),
|
||||
reth_discv5::Config::builder((DEFAULT_DISCOVERY_ADDR, test_port).into()).discv5_config(
|
||||
discv5::ConfigBuilder::new(discv5::ListenConfig::from_ip(
|
||||
DEFAULT_DISCOVERY_ADDR,
|
||||
test_port,
|
||||
))
|
||||
.build(),
|
||||
),
|
||||
)
|
||||
.disable_dns_discovery()
|
||||
.build(NoopProvider::default());
|
||||
let addr = config.listener_addr;
|
||||
let result = NetworkManager::new(config).await;
|
||||
let err = result.err().unwrap();
|
||||
|
||||
assert!(is_addr_in_use_kind(&err, ServiceKind::Listener(addr)), "{err:?}")
|
||||
let _network = NetworkManager::new(config).await.expect("shared port discovery should start");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
|
||||
@@ -289,7 +289,7 @@ impl Default for DefaultEngineValues {
|
||||
share_execution_cache_with_payload_builder: false,
|
||||
share_sparse_trie_with_payload_builder: false,
|
||||
suppress_persistence_during_build: false,
|
||||
bal_parallel_execution_disabled: false,
|
||||
bal_parallel_execution_disabled: true,
|
||||
bal_parallel_state_root_disabled: false,
|
||||
}
|
||||
}
|
||||
@@ -511,8 +511,8 @@ pub struct EngineArgs {
|
||||
)]
|
||||
pub suppress_persistence_during_build: bool,
|
||||
|
||||
/// Disable BAL (Block Access List, EIP-7928) based parallel execution. When set, falls back
|
||||
/// to transaction-based prewarming even when a BAL is available.
|
||||
/// Disable BAL (Block Access List, EIP-7928) based parallel execution. Defaults to disabled,
|
||||
/// falling back to transaction-based prewarming even when a BAL is available.
|
||||
#[arg(long = "engine.disable-bal-parallel-execution", default_value_t = DefaultEngineValues::get_global().bal_parallel_execution_disabled)]
|
||||
pub bal_parallel_execution_disabled: bool,
|
||||
|
||||
|
||||
@@ -31,7 +31,9 @@ use reth_network::{
|
||||
DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
|
||||
},
|
||||
tx_manager::{
|
||||
DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS, DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
|
||||
DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS,
|
||||
DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
|
||||
DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
|
||||
},
|
||||
},
|
||||
TransactionFetcherConfig, TransactionPropagationMode, TransactionsManagerConfig,
|
||||
@@ -76,6 +78,8 @@ pub struct DefaultNetworkArgs {
|
||||
pub soft_limit_byte_size_pooled_transactions_response_on_pack_request: usize,
|
||||
/// Default max capacity of cache of hashes for transactions pending fetch.
|
||||
pub max_capacity_cache_txns_pending_fetch: u32,
|
||||
/// Default memory limit (in bytes) for the network manager → transactions manager channel.
|
||||
pub tx_channel_memory_limit_bytes: usize,
|
||||
/// Default transaction propagation policy.
|
||||
pub tx_propagation_policy: TransactionPropagationKind,
|
||||
/// Default transaction ingress policy.
|
||||
@@ -169,6 +173,13 @@ impl DefaultNetworkArgs {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default memory limit (in bytes) for the network manager → transactions
|
||||
/// manager channel.
|
||||
pub const fn with_tx_channel_memory_limit_bytes(mut self, v: usize) -> Self {
|
||||
self.tx_channel_memory_limit_bytes = v;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default transaction propagation policy.
|
||||
pub const fn with_tx_propagation_policy(mut self, v: TransactionPropagationKind) -> Self {
|
||||
self.tx_propagation_policy = v;
|
||||
@@ -210,6 +221,7 @@ impl Default for DefaultNetworkArgs {
|
||||
soft_limit_byte_size_pooled_transactions_response_on_pack_request:
|
||||
DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
|
||||
max_capacity_cache_txns_pending_fetch: DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH,
|
||||
tx_channel_memory_limit_bytes: DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
|
||||
tx_propagation_policy: TransactionPropagationKind::default(),
|
||||
tx_ingress_policy: TransactionIngressPolicy::default(),
|
||||
propagation_mode: TransactionPropagationMode::Sqrt,
|
||||
@@ -348,6 +360,15 @@ pub struct NetworkArgs {
|
||||
#[arg(long = "max-tx-pending-fetch", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_capacity_cache_txns_pending_fetch, verbatim_doc_comment)]
|
||||
pub max_capacity_cache_txns_pending_fetch: u32,
|
||||
|
||||
/// Memory limit (in bytes) for the channel that buffers transaction events flowing
|
||||
/// from the network manager to the transactions manager.
|
||||
///
|
||||
/// When the budget is exhausted, new events are dropped (see metric
|
||||
/// `total_dropped_tx_events_at_full_capacity`). Acts as a backstop against unbounded
|
||||
/// memory growth under sustained P2P transaction flooding.
|
||||
#[arg(long = "tx-channel-memory-limit", value_name = "BYTES", default_value_t = DefaultNetworkArgs::get_global().tx_channel_memory_limit_bytes, verbatim_doc_comment)]
|
||||
pub tx_channel_memory_limit_bytes: usize,
|
||||
|
||||
/// Name of network interface used to communicate with peers.
|
||||
///
|
||||
/// If flag is set, but no value is passed, the default interface for docker `eth0` is tried.
|
||||
@@ -485,6 +506,7 @@ impl NetworkArgs {
|
||||
max_transactions_seen_by_peer_history: self.max_seen_tx_history,
|
||||
propagation_mode: self.propagation_mode,
|
||||
ingress_policy: self.tx_ingress_policy,
|
||||
tx_channel_memory_limit_bytes: self.tx_channel_memory_limit_bytes,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -660,6 +682,7 @@ impl Default for NetworkArgs {
|
||||
soft_limit_byte_size_pooled_transactions_response,
|
||||
soft_limit_byte_size_pooled_transactions_response_on_pack_request,
|
||||
max_capacity_cache_txns_pending_fetch,
|
||||
tx_channel_memory_limit_bytes,
|
||||
tx_propagation_policy,
|
||||
tx_ingress_policy,
|
||||
propagation_mode,
|
||||
@@ -689,6 +712,7 @@ impl Default for NetworkArgs {
|
||||
max_pending_pool_imports,
|
||||
max_seen_tx_history,
|
||||
max_capacity_cache_txns_pending_fetch,
|
||||
tx_channel_memory_limit_bytes,
|
||||
net_if: None,
|
||||
tx_propagation_policy,
|
||||
tx_ingress_policy,
|
||||
|
||||
@@ -9,9 +9,10 @@ use alloy_primitives::{B256, U256};
|
||||
use alloy_rpc_types_eth::BlockNumberOrTag;
|
||||
use futures::Future;
|
||||
use reth_chain_state::{BlockState, ComputedTrieData, ExecutedBlock};
|
||||
use reth_chainspec::{ChainSpecProvider, EthChainSpec};
|
||||
use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks};
|
||||
use reth_errors::{BlockExecutionError, BlockValidationError, ProviderError, RethError};
|
||||
use reth_evm::{
|
||||
block::TxResult,
|
||||
execute::{BlockBuilder, BlockBuilderOutcome, BlockExecutionOutput},
|
||||
ConfigureEvm, Evm, EvmEnvFor, NextBlockEnvAttributes,
|
||||
};
|
||||
@@ -30,7 +31,7 @@ use reth_transaction_pool::{
|
||||
error::InvalidPoolTransactionError, BestTransactions, BestTransactionsAttributes,
|
||||
PoolTransaction, TransactionPool,
|
||||
};
|
||||
use revm::context_interface::Block;
|
||||
use revm::context_interface::{Block, Cfg as _};
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
@@ -263,6 +264,10 @@ pub trait LoadPendingBlock:
|
||||
builder.apply_pre_execution_changes().map_err(Self::Error::from_eth_err)?;
|
||||
|
||||
let block_gas_limit: u64 = builder.evm().block().gas_limit();
|
||||
let is_amsterdam = self
|
||||
.provider()
|
||||
.chain_spec()
|
||||
.is_amsterdam_active_at_timestamp(builder.evm().block().timestamp().saturating_to());
|
||||
let basefee = builder.evm().block().basefee();
|
||||
let blob_gasprice = builder.evm().block().blob_gasprice().map(|p| p as u64);
|
||||
|
||||
@@ -271,8 +276,11 @@ pub trait LoadPendingBlock:
|
||||
.chain_spec()
|
||||
.blob_params_at_timestamp(parent.timestamp())
|
||||
.unwrap_or_else(BlobParams::cancun);
|
||||
let mut cumulative_gas_used = 0;
|
||||
let mut cumulative_tx_gas_used = 0;
|
||||
let mut block_regular_gas_used = 0;
|
||||
let mut block_state_gas_used = 0;
|
||||
let mut sum_blob_gas_used = 0;
|
||||
let tx_gas_limit_cap = builder.evm().cfg_env().tx_gas_limit_cap();
|
||||
|
||||
// Only include transactions if not configured as Empty
|
||||
if !self.pending_block_kind().is_empty() {
|
||||
@@ -287,15 +295,35 @@ pub trait LoadPendingBlock:
|
||||
|
||||
while let Some(pool_tx) = best_txs.next() {
|
||||
// ensure we still have capacity for this transaction
|
||||
if cumulative_gas_used + pool_tx.gas_limit() > block_gas_limit {
|
||||
let exceeds_gas_limit = if is_amsterdam {
|
||||
let regular_available_gas =
|
||||
block_gas_limit.saturating_sub(block_regular_gas_used);
|
||||
let state_available_gas = block_gas_limit.saturating_sub(block_state_gas_used);
|
||||
let regular_tx_gas_limit = pool_tx.gas_limit().min(tx_gas_limit_cap);
|
||||
|
||||
if regular_tx_gas_limit > regular_available_gas {
|
||||
Some((regular_tx_gas_limit, regular_available_gas))
|
||||
} else if pool_tx.gas_limit() > state_available_gas {
|
||||
Some((pool_tx.gas_limit(), state_available_gas))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
let block_available_gas =
|
||||
block_gas_limit.saturating_sub(cumulative_tx_gas_used);
|
||||
(pool_tx.gas_limit() > block_available_gas)
|
||||
.then_some((pool_tx.gas_limit(), block_available_gas))
|
||||
};
|
||||
|
||||
if let Some((transaction_gas_limit, block_available_gas)) = exceeds_gas_limit {
|
||||
// we can't fit this transaction into the block, so we need to mark it as
|
||||
// invalid which also removes all dependent transaction from
|
||||
// the iterator before we can continue
|
||||
best_txs.mark_invalid(
|
||||
&pool_tx,
|
||||
&InvalidPoolTransactionError::ExceedsGasLimit(
|
||||
pool_tx.gas_limit(),
|
||||
block_gas_limit,
|
||||
transaction_gas_limit,
|
||||
block_available_gas,
|
||||
),
|
||||
);
|
||||
continue
|
||||
@@ -337,29 +365,48 @@ pub trait LoadPendingBlock:
|
||||
continue
|
||||
}
|
||||
|
||||
let gas_used = match builder.execute_transaction(tx) {
|
||||
Ok(gas_used) => gas_used,
|
||||
Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
|
||||
error,
|
||||
..
|
||||
})) => {
|
||||
if error.is_nonce_too_low() {
|
||||
// if the nonce is too low, we can skip this transaction
|
||||
} else {
|
||||
// if the transaction is invalid, we can skip it and all of its
|
||||
// descendants
|
||||
let mut tx_regular_gas_used = 0;
|
||||
let gas_output =
|
||||
match builder.execute_transaction_with_result_closure(tx, |result| {
|
||||
tx_regular_gas_used = result.result().result.gas().block_regular_gas_used();
|
||||
}) {
|
||||
Ok(gas_output) => gas_output,
|
||||
Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
|
||||
error,
|
||||
..
|
||||
})) => {
|
||||
if error.is_nonce_too_low() {
|
||||
// if the nonce is too low, we can skip this transaction
|
||||
} else {
|
||||
// if the transaction is invalid, we can skip it and all of its
|
||||
// descendants
|
||||
best_txs.mark_invalid(
|
||||
&pool_tx,
|
||||
&InvalidPoolTransactionError::Consensus(
|
||||
InvalidTransactionError::TxTypeNotSupported,
|
||||
),
|
||||
);
|
||||
}
|
||||
continue
|
||||
}
|
||||
Err(BlockExecutionError::Validation(
|
||||
BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
|
||||
transaction_gas_limit,
|
||||
block_available_gas,
|
||||
},
|
||||
)) => {
|
||||
best_txs.mark_invalid(
|
||||
&pool_tx,
|
||||
&InvalidPoolTransactionError::Consensus(
|
||||
InvalidTransactionError::TxTypeNotSupported,
|
||||
&InvalidPoolTransactionError::ExceedsGasLimit(
|
||||
transaction_gas_limit,
|
||||
block_available_gas,
|
||||
),
|
||||
);
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
// this is an error that we should treat as fatal for this attempt
|
||||
Err(err) => return Err(Self::Error::from_eth_err(err)),
|
||||
};
|
||||
// this is an error that we should treat as fatal for this attempt
|
||||
Err(err) => return Err(Self::Error::from_eth_err(err)),
|
||||
};
|
||||
|
||||
// add to the total blob gas used if the transaction successfully executed
|
||||
if let Some(tx_blob_gas) = tx_blob_gas {
|
||||
@@ -371,9 +418,11 @@ pub trait LoadPendingBlock:
|
||||
}
|
||||
}
|
||||
|
||||
// add gas used by the transaction to cumulative gas used, before creating the
|
||||
// receipt
|
||||
cumulative_gas_used += gas_used;
|
||||
// Track receipt gas and the Amsterdam block-capacity counter separately.
|
||||
let gas_used = gas_output.tx_gas_used();
|
||||
cumulative_tx_gas_used += gas_used;
|
||||
block_regular_gas_used += tx_regular_gas_used;
|
||||
block_state_gas_used += gas_output.state_gas_used();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ where
|
||||
|
||||
let tip = tx.effective_tip_per_gas(base_fee).unwrap_or_default();
|
||||
let gas_used = match builder.execute_transaction(tx) {
|
||||
Ok(gas_used) => gas_used,
|
||||
Ok(gas_used) => gas_used.tx_gas_used(),
|
||||
Err(err) => {
|
||||
if skip_invalid_transactions {
|
||||
debug!(
|
||||
|
||||
@@ -202,7 +202,7 @@ where
|
||||
// update the cached reads
|
||||
self.update_cached_reads(parent_header_hash, request_cache).await;
|
||||
|
||||
self.consensus.validate_block_post_execution(&block, &output, None)?;
|
||||
self.consensus.validate_block_post_execution(&block, &output, None, None)?;
|
||||
|
||||
self.ensure_payment(&block, &output, &message)?;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use alloy_primitives::BlockNumber;
|
||||
use num_traits::Zero;
|
||||
use reth_chainspec::{ChainSpecProvider, EthereumHardforks};
|
||||
use reth_config::config::ExecutionConfig;
|
||||
use reth_consensus::FullConsensus;
|
||||
use reth_consensus::{validate_block_access_list_gas, FullConsensus};
|
||||
use reth_db::{static_file::HeaderMask, tables};
|
||||
use reth_evm::{execute::Executor, metrics::ExecutorMetrics, ConfigureEvm};
|
||||
use reth_execution_types::Chain;
|
||||
@@ -357,7 +357,19 @@ where
|
||||
})
|
||||
})?;
|
||||
|
||||
if let Err(err) = self.consensus.validate_block_post_execution(&block, &result, None) {
|
||||
let bal = executor.take_bal().unwrap_or_default();
|
||||
if block.header().block_access_list_hash().is_some() &&
|
||||
let Err(err) = validate_block_access_list_gas(Some(&bal), block.gas_limit())
|
||||
{
|
||||
return Err(StageError::Block {
|
||||
block: Box::new(block.block_with_parent()),
|
||||
error: BlockErrorKind::Validation(err),
|
||||
})
|
||||
}
|
||||
|
||||
if let Err(err) =
|
||||
self.consensus.validate_block_post_execution(&block, &result, None, Some(bal))
|
||||
{
|
||||
return Err(StageError::Block {
|
||||
block: Box::new(block.block_with_parent()),
|
||||
error: BlockErrorKind::Validation(err),
|
||||
|
||||
@@ -12,7 +12,7 @@ use reth_db_api::{
|
||||
transaction::DbTx,
|
||||
BlockNumberList,
|
||||
};
|
||||
use reth_primitives_traits::{Account, Bytecode};
|
||||
use reth_primitives_traits::{Account, Bytecode, NodePrimitives};
|
||||
use reth_storage_api::{
|
||||
BlockNumReader, BytecodeReader, DBProvider, NodePrimitivesProvider, PruneCheckpointReader,
|
||||
StageCheckpointReader, StateProofProvider, StorageChangeSetReader, StorageRootProvider,
|
||||
@@ -33,7 +33,7 @@ use reth_trie_db::{
|
||||
ChangesetCache, DatabaseProof, DatabaseStateRoot, DatabaseStorageProof, DatabaseStorageRoot,
|
||||
};
|
||||
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
use std::{fmt::Debug, marker::PhantomData, sync::Arc};
|
||||
|
||||
type DbStateRoot<'a, TX, A> = StateRoot<
|
||||
reth_trie_db::DatabaseTrieCursorFactory<&'a TX, A>,
|
||||
@@ -121,7 +121,13 @@ impl HistoryInfo {
|
||||
/// - [`tables::AccountChangeSets`]
|
||||
/// - [`tables::StorageChangeSets`]
|
||||
#[derive(Debug)]
|
||||
pub struct HistoricalStateProviderRef<'b, Provider> {
|
||||
pub struct HistoricalStateProviderRef<
|
||||
'b,
|
||||
Provider,
|
||||
N: NodePrimitives = <Provider as NodePrimitivesProvider>::Primitives,
|
||||
> where
|
||||
Provider: NodePrimitivesProvider<Primitives = N>,
|
||||
{
|
||||
/// Database provider
|
||||
provider: &'b Provider,
|
||||
/// Changeset cache handle for retrieving trie changesets.
|
||||
@@ -130,10 +136,18 @@ pub struct HistoricalStateProviderRef<'b, Provider> {
|
||||
block_number: BlockNumber,
|
||||
/// Lowest blocks at which different parts of the state are available.
|
||||
lowest_available_blocks: LowestAvailableBlocks,
|
||||
/// Marker for the provider's node primitives.
|
||||
_primitives: PhantomData<N>,
|
||||
}
|
||||
|
||||
impl<'b, Provider: DBProvider + ChangeSetReader + StorageChangeSetReader + BlockNumReader>
|
||||
HistoricalStateProviderRef<'b, Provider>
|
||||
impl<'b, Provider, N> HistoricalStateProviderRef<'b, Provider, N>
|
||||
where
|
||||
Provider: DBProvider
|
||||
+ ChangeSetReader
|
||||
+ StorageChangeSetReader
|
||||
+ BlockNumReader
|
||||
+ NodePrimitivesProvider<Primitives = N>,
|
||||
N: NodePrimitives,
|
||||
{
|
||||
/// Create new `StateProvider` for historical block number
|
||||
pub fn new(
|
||||
@@ -146,6 +160,7 @@ impl<'b, Provider: DBProvider + ChangeSetReader + StorageChangeSetReader + Block
|
||||
changeset_cache,
|
||||
block_number,
|
||||
lowest_available_blocks: Default::default(),
|
||||
_primitives: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +172,13 @@ impl<'b, Provider: DBProvider + ChangeSetReader + StorageChangeSetReader + Block
|
||||
lowest_available_blocks: LowestAvailableBlocks,
|
||||
changeset_cache: ChangesetCache,
|
||||
) -> Self {
|
||||
Self { provider, changeset_cache, block_number, lowest_available_blocks }
|
||||
Self {
|
||||
provider,
|
||||
changeset_cache,
|
||||
block_number,
|
||||
lowest_available_blocks,
|
||||
_primitives: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Lookup an account in the `AccountsHistory` table using `EitherReader`.
|
||||
@@ -282,14 +303,13 @@ impl<'b, Provider: DBProvider + ChangeSetReader + StorageChangeSetReader + Block
|
||||
// Historical providers expose state at the start of `self.block_number`, so the overlay
|
||||
// builder needs the previous canonical block hash to preserve those semantics.
|
||||
let target_block = self.block_number.saturating_sub(1);
|
||||
let block_hash = self
|
||||
let anchor_hash = self
|
||||
.provider
|
||||
.block_hash(target_block)?
|
||||
.ok_or_else(|| ProviderError::HeaderNotFound(target_block.into()))?;
|
||||
|
||||
let TrieInputSorted { nodes, state, prefix_sets } = input;
|
||||
let overlay_builder = OverlayBuilder::new(self.changeset_cache.clone())
|
||||
.with_block_hash(Some(block_hash))
|
||||
let overlay_builder = OverlayBuilder::<N>::new(anchor_hash, self.changeset_cache.clone())
|
||||
.with_overlay_source(Some(OverlaySource::Immediate { trie: nodes, state }));
|
||||
let Overlay { trie_updates, hashed_post_state } =
|
||||
overlay_builder.build_overlay(self.provider)?;
|
||||
@@ -316,21 +336,26 @@ impl<'b, Provider: DBProvider + ChangeSetReader + StorageChangeSetReader + Block
|
||||
}
|
||||
}
|
||||
|
||||
impl<Provider: DBProvider + BlockNumReader> HistoricalStateProviderRef<'_, Provider> {
|
||||
impl<Provider, N> HistoricalStateProviderRef<'_, Provider, N>
|
||||
where
|
||||
Provider: DBProvider + BlockNumReader + NodePrimitivesProvider<Primitives = N>,
|
||||
N: NodePrimitives,
|
||||
{
|
||||
fn tx(&self) -> &Provider::Tx {
|
||||
self.provider.tx_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Provider: DBProvider
|
||||
+ BlockNumReader
|
||||
+ ChangeSetReader
|
||||
+ StorageChangeSetReader
|
||||
+ StorageSettingsCache
|
||||
+ RocksDBProviderFactory
|
||||
+ NodePrimitivesProvider,
|
||||
> AccountReader for HistoricalStateProviderRef<'_, Provider>
|
||||
impl<Provider, N> AccountReader for HistoricalStateProviderRef<'_, Provider, N>
|
||||
where
|
||||
Provider: DBProvider
|
||||
+ BlockNumReader
|
||||
+ ChangeSetReader
|
||||
+ StorageChangeSetReader
|
||||
+ StorageSettingsCache
|
||||
+ RocksDBProviderFactory
|
||||
+ NodePrimitivesProvider<Primitives = N>,
|
||||
N: NodePrimitives,
|
||||
{
|
||||
/// Get basic account information.
|
||||
fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>> {
|
||||
@@ -358,8 +383,11 @@ impl<
|
||||
}
|
||||
}
|
||||
|
||||
impl<Provider: DBProvider + BlockNumReader + BlockHashReader> BlockHashReader
|
||||
for HistoricalStateProviderRef<'_, Provider>
|
||||
impl<Provider, N> BlockHashReader for HistoricalStateProviderRef<'_, Provider, N>
|
||||
where
|
||||
Provider:
|
||||
DBProvider + BlockNumReader + BlockHashReader + NodePrimitivesProvider<Primitives = N>,
|
||||
N: NodePrimitives,
|
||||
{
|
||||
/// Get block hash by number.
|
||||
fn block_hash(&self, number: u64) -> ProviderResult<Option<B256>> {
|
||||
@@ -375,16 +403,18 @@ impl<Provider: DBProvider + BlockNumReader + BlockHashReader> BlockHashReader
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Provider: DBProvider
|
||||
+ ChangeSetReader
|
||||
+ StorageChangeSetReader
|
||||
+ BlockNumReader
|
||||
+ BlockHashReader
|
||||
+ PruneCheckpointReader
|
||||
+ StageCheckpointReader
|
||||
+ StorageSettingsCache,
|
||||
> StateRootProvider for HistoricalStateProviderRef<'_, Provider>
|
||||
impl<Provider, N> StateRootProvider for HistoricalStateProviderRef<'_, Provider, N>
|
||||
where
|
||||
Provider: DBProvider
|
||||
+ ChangeSetReader
|
||||
+ StorageChangeSetReader
|
||||
+ BlockNumReader
|
||||
+ BlockHashReader
|
||||
+ PruneCheckpointReader
|
||||
+ StageCheckpointReader
|
||||
+ StorageSettingsCache
|
||||
+ NodePrimitivesProvider<Primitives = N>,
|
||||
N: NodePrimitives,
|
||||
{
|
||||
fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult<B256> {
|
||||
reth_trie_db::with_adapter!(self.provider, |A| {
|
||||
@@ -425,16 +455,18 @@ impl<
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Provider: DBProvider
|
||||
+ ChangeSetReader
|
||||
+ StorageChangeSetReader
|
||||
+ BlockNumReader
|
||||
+ BlockHashReader
|
||||
+ PruneCheckpointReader
|
||||
+ StageCheckpointReader
|
||||
+ StorageSettingsCache,
|
||||
> StorageRootProvider for HistoricalStateProviderRef<'_, Provider>
|
||||
impl<Provider, N> StorageRootProvider for HistoricalStateProviderRef<'_, Provider, N>
|
||||
where
|
||||
Provider: DBProvider
|
||||
+ ChangeSetReader
|
||||
+ StorageChangeSetReader
|
||||
+ BlockNumReader
|
||||
+ BlockHashReader
|
||||
+ PruneCheckpointReader
|
||||
+ StageCheckpointReader
|
||||
+ StorageSettingsCache
|
||||
+ NodePrimitivesProvider<Primitives = N>,
|
||||
N: NodePrimitives,
|
||||
{
|
||||
fn storage_root(
|
||||
&self,
|
||||
@@ -521,16 +553,18 @@ impl<
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Provider: DBProvider
|
||||
+ ChangeSetReader
|
||||
+ StorageChangeSetReader
|
||||
+ BlockNumReader
|
||||
+ BlockHashReader
|
||||
+ PruneCheckpointReader
|
||||
+ StageCheckpointReader
|
||||
+ StorageSettingsCache,
|
||||
> StateProofProvider for HistoricalStateProviderRef<'_, Provider>
|
||||
impl<Provider, N> StateProofProvider for HistoricalStateProviderRef<'_, Provider, N>
|
||||
where
|
||||
Provider: DBProvider
|
||||
+ ChangeSetReader
|
||||
+ StorageChangeSetReader
|
||||
+ BlockNumReader
|
||||
+ BlockHashReader
|
||||
+ PruneCheckpointReader
|
||||
+ StageCheckpointReader
|
||||
+ StorageSettingsCache
|
||||
+ NodePrimitivesProvider<Primitives = N>,
|
||||
N: NodePrimitives,
|
||||
{
|
||||
/// Get account and storage proofs.
|
||||
fn proof(
|
||||
@@ -604,24 +638,29 @@ impl<
|
||||
}
|
||||
}
|
||||
|
||||
impl<Provider> HashedPostStateProvider for HistoricalStateProviderRef<'_, Provider> {
|
||||
impl<Provider, N> HashedPostStateProvider for HistoricalStateProviderRef<'_, Provider, N>
|
||||
where
|
||||
Provider: NodePrimitivesProvider<Primitives = N>,
|
||||
N: NodePrimitives,
|
||||
{
|
||||
fn hashed_post_state(&self, bundle_state: &revm_database::BundleState) -> HashedPostState {
|
||||
HashedPostState::from_bundle_state::<KeccakKeyHasher>(bundle_state.state())
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Provider: DBProvider
|
||||
+ BlockNumReader
|
||||
+ BlockHashReader
|
||||
+ ChangeSetReader
|
||||
+ StorageChangeSetReader
|
||||
+ PruneCheckpointReader
|
||||
+ StageCheckpointReader
|
||||
+ StorageSettingsCache
|
||||
+ RocksDBProviderFactory
|
||||
+ NodePrimitivesProvider,
|
||||
> StateProvider for HistoricalStateProviderRef<'_, Provider>
|
||||
impl<Provider, N> StateProvider for HistoricalStateProviderRef<'_, Provider, N>
|
||||
where
|
||||
Provider: DBProvider
|
||||
+ BlockNumReader
|
||||
+ BlockHashReader
|
||||
+ ChangeSetReader
|
||||
+ StorageChangeSetReader
|
||||
+ PruneCheckpointReader
|
||||
+ StageCheckpointReader
|
||||
+ StorageSettingsCache
|
||||
+ RocksDBProviderFactory
|
||||
+ NodePrimitivesProvider<Primitives = N>,
|
||||
N: NodePrimitives,
|
||||
{
|
||||
/// Expects a plain (unhashed) storage key slot.
|
||||
fn storage(
|
||||
@@ -633,8 +672,10 @@ impl<
|
||||
}
|
||||
}
|
||||
|
||||
impl<Provider: DBProvider + BlockNumReader> BytecodeReader
|
||||
for HistoricalStateProviderRef<'_, Provider>
|
||||
impl<Provider, N> BytecodeReader for HistoricalStateProviderRef<'_, Provider, N>
|
||||
where
|
||||
Provider: DBProvider + BlockNumReader + NodePrimitivesProvider<Primitives = N>,
|
||||
N: NodePrimitives,
|
||||
{
|
||||
/// Get account code by its hash
|
||||
fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>> {
|
||||
@@ -690,7 +731,16 @@ impl<Provider: DBProvider + ChangeSetReader + StorageChangeSetReader + BlockNumR
|
||||
self.lowest_available_blocks.storage_history_block_number = Some(block_number);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Provider: DBProvider
|
||||
+ ChangeSetReader
|
||||
+ StorageChangeSetReader
|
||||
+ BlockNumReader
|
||||
+ NodePrimitivesProvider,
|
||||
> HistoricalStateProvider<Provider>
|
||||
{
|
||||
/// Returns a new provider that takes the `TX` as reference
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> HistoricalStateProviderRef<'_, Provider> {
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
use alloy_primitives::{BlockNumber, B256};
|
||||
use alloy_eips::BlockNumHash;
|
||||
use alloy_primitives::{BlockHash, BlockNumber, B256};
|
||||
use metrics::{Counter, Histogram};
|
||||
use reth_chain_state::LazyOverlay;
|
||||
use reth_chain_state::{EthPrimitives, LazyOverlay};
|
||||
use reth_db_api::{tables, transaction::DbTx, DatabaseError};
|
||||
use reth_errors::{ProviderError, ProviderResult};
|
||||
use reth_metrics::Metrics;
|
||||
use reth_primitives_traits::dashmap::{self, DashMap};
|
||||
use reth_primitives_traits::{
|
||||
dashmap::{self, DashMap},
|
||||
NodePrimitives,
|
||||
};
|
||||
use reth_prune_types::PruneSegment;
|
||||
use reth_stages_types::StageId;
|
||||
use reth_storage_api::{
|
||||
@@ -24,6 +28,7 @@ use reth_trie_db::{
|
||||
PackedStoragesTrie,
|
||||
};
|
||||
use std::{
|
||||
ops::RangeInclusive,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
@@ -61,7 +66,7 @@ pub(super) struct Overlay {
|
||||
/// Either provides immediate pre-computed overlay data, or a lazy overlay that computes
|
||||
/// on first access.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum OverlaySource {
|
||||
pub(super) enum OverlaySource<N: NodePrimitives = EthPrimitives> {
|
||||
/// Immediate overlay with already-computed data.
|
||||
Immediate {
|
||||
/// Trie updates overlay.
|
||||
@@ -70,19 +75,7 @@ pub enum OverlaySource {
|
||||
state: Arc<HashedPostStateSorted>,
|
||||
},
|
||||
/// Lazy overlay computed on first access.
|
||||
Lazy(LazyOverlay),
|
||||
}
|
||||
|
||||
impl OverlaySource {
|
||||
/// Resolve the overlay source into (trie, state) tuple.
|
||||
///
|
||||
/// For lazy overlays, this may block waiting for deferred data.
|
||||
fn resolve(&self) -> (Arc<TrieUpdatesSorted>, Arc<HashedPostStateSorted>) {
|
||||
match self {
|
||||
Self::Immediate { trie, state } => (Arc::clone(trie), Arc::clone(state)),
|
||||
Self::Lazy(lazy) => lazy.as_overlay(),
|
||||
}
|
||||
}
|
||||
Lazy(LazyOverlay<N>),
|
||||
}
|
||||
|
||||
/// Builder for calculating trie and hashed-state overlays.
|
||||
@@ -90,54 +83,61 @@ impl OverlaySource {
|
||||
/// This stores the overlay configuration and the logic for resolving immediate/lazy overlays and
|
||||
/// collecting reverts. It is intentionally independent from any provider factory or overlay cache.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OverlayBuilder {
|
||||
/// Optional block hash for collecting reverts
|
||||
block_hash: Option<B256>,
|
||||
pub struct OverlayBuilder<N: NodePrimitives = EthPrimitives> {
|
||||
/// Anchor hash to revert the DB state to before applying overlays.
|
||||
anchor_hash: B256,
|
||||
/// Optional overlay source (lazy or immediate).
|
||||
overlay_source: Option<OverlaySource>,
|
||||
overlay_source: Option<OverlaySource<N>>,
|
||||
/// Changeset cache handle for retrieving trie changesets
|
||||
changeset_cache: ChangesetCache,
|
||||
/// Metrics for tracking provider operations
|
||||
metrics: OverlayStateProviderMetrics,
|
||||
}
|
||||
|
||||
impl OverlayBuilder {
|
||||
impl<N: NodePrimitives> OverlayBuilder<N> {
|
||||
/// Create a new overlay builder.
|
||||
pub fn new(changeset_cache: ChangesetCache) -> Self {
|
||||
pub fn new(anchor_hash: B256, changeset_cache: ChangesetCache) -> Self {
|
||||
Self {
|
||||
block_hash: None,
|
||||
anchor_hash,
|
||||
overlay_source: None,
|
||||
changeset_cache,
|
||||
metrics: OverlayStateProviderMetrics::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the block hash for collecting reverts. All state will be reverted to the point
|
||||
/// _after_ this block has been processed.
|
||||
pub const fn with_block_hash(mut self, block_hash: Option<B256>) -> Self {
|
||||
self.block_hash = block_hash;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the overlay source (lazy or immediate).
|
||||
///
|
||||
/// This overlay will be applied on top of any reverts applied via `with_block_hash`.
|
||||
pub fn with_overlay_source(mut self, source: Option<OverlaySource>) -> Self {
|
||||
/// This overlay will be applied on top of any reverts applied via `anchor_hash`.
|
||||
pub(super) fn with_overlay_source(mut self, source: Option<OverlaySource<N>>) -> Self {
|
||||
if let Some(OverlaySource::Lazy(lazy_overlay)) = source.as_ref() {
|
||||
self.assert_lazy_overlay_anchor(lazy_overlay);
|
||||
}
|
||||
self.overlay_source = source;
|
||||
self
|
||||
}
|
||||
|
||||
fn assert_lazy_overlay_anchor(&self, lazy_overlay: &LazyOverlay<N>) {
|
||||
let Some(lazy_overlay_anchor) = lazy_overlay.anchor_hash() else { return };
|
||||
assert!(
|
||||
lazy_overlay_anchor == self.anchor_hash,
|
||||
"LazyOverlay's anchor ({}) != OverlayBuilder's anchor ({})",
|
||||
lazy_overlay_anchor,
|
||||
self.anchor_hash,
|
||||
);
|
||||
}
|
||||
|
||||
/// Set a lazy overlay that will be computed on first access.
|
||||
///
|
||||
/// Convenience method that wraps the lazy overlay in `OverlaySource::Lazy`.
|
||||
pub fn with_lazy_overlay(mut self, lazy_overlay: Option<LazyOverlay>) -> Self {
|
||||
/// Panics if the [`LazyOverlay`]'s anchor hash does not match [`Self`]'s `anchor_hash`.
|
||||
pub fn with_lazy_overlay(mut self, lazy_overlay: Option<LazyOverlay<N>>) -> Self {
|
||||
if let Some(lazy_overlay) = lazy_overlay.as_ref() {
|
||||
self.assert_lazy_overlay_anchor(lazy_overlay);
|
||||
}
|
||||
self.overlay_source = lazy_overlay.map(OverlaySource::Lazy);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the hashed state overlay.
|
||||
///
|
||||
/// This overlay will be applied on top of any reverts applied via `with_block_hash`.
|
||||
pub fn with_hashed_state_overlay(
|
||||
mut self,
|
||||
hashed_state_overlay: Option<Arc<HashedPostStateSorted>>,
|
||||
@@ -160,9 +160,9 @@ impl OverlayBuilder {
|
||||
Some(OverlaySource::Immediate { state, .. }) => {
|
||||
Arc::make_mut(state).extend_ref_and_sort(&other);
|
||||
}
|
||||
Some(OverlaySource::Lazy(lazy)) => {
|
||||
Some(OverlaySource::Lazy(overlay)) => {
|
||||
// Resolve lazy overlay and convert to immediate with extension
|
||||
let (trie, mut state) = lazy.as_overlay();
|
||||
let (trie, mut state) = overlay.as_overlay(self.anchor_hash);
|
||||
Arc::make_mut(&mut state).extend_ref_and_sort(&other);
|
||||
self.overlay_source = Some(OverlaySource::Immediate { trie, state });
|
||||
}
|
||||
@@ -180,45 +180,56 @@ impl OverlayBuilder {
|
||||
///
|
||||
/// If an overlay source is set, it is resolved (blocking if lazy).
|
||||
/// Otherwise, returns empty defaults.
|
||||
fn resolve_overlays(&self) -> (Arc<TrieUpdatesSorted>, Arc<HashedPostStateSorted>) {
|
||||
fn resolve_overlays(
|
||||
&self,
|
||||
anchor_hash: BlockHash,
|
||||
) -> ProviderResult<(Arc<TrieUpdatesSorted>, Arc<HashedPostStateSorted>)> {
|
||||
match &self.overlay_source {
|
||||
Some(source) => source.resolve(),
|
||||
None => {
|
||||
(Arc::new(TrieUpdatesSorted::default()), Arc::new(HashedPostStateSorted::default()))
|
||||
Some(OverlaySource::Lazy(lazy_overlay)) => Ok(lazy_overlay.as_overlay(anchor_hash)),
|
||||
Some(OverlaySource::Immediate { trie, state }) => {
|
||||
if anchor_hash != self.anchor_hash {
|
||||
return Err(ProviderError::other(std::io::Error::other(format!(
|
||||
"anchor_hash {anchor_hash} doesn't match OverlayBuilder's configured anchor ({})",
|
||||
self.anchor_hash
|
||||
))))
|
||||
}
|
||||
Ok((Arc::clone(trie), Arc::clone(state)))
|
||||
}
|
||||
None => Ok((
|
||||
Arc::new(TrieUpdatesSorted::default()),
|
||||
Arc::new(HashedPostStateSorted::default()),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the block number for [`Self`]'s `block_hash` field, if any.
|
||||
fn get_requested_block_number<Provider>(
|
||||
&self,
|
||||
provider: &Provider,
|
||||
) -> ProviderResult<Option<BlockNumber>>
|
||||
/// Returns the block number for [`Self`]'s `anchor_hash` field.
|
||||
fn get_block_number<Provider>(&self, provider: &Provider) -> ProviderResult<BlockNumber>
|
||||
where
|
||||
Provider: BlockNumReader,
|
||||
{
|
||||
if let Some(block_hash) = self.block_hash {
|
||||
Ok(Some(
|
||||
provider
|
||||
.convert_hash_or_number(block_hash.into())?
|
||||
.ok_or_else(|| ProviderError::BlockHashNotFound(block_hash))?,
|
||||
))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
provider
|
||||
.convert_hash_or_number(self.anchor_hash.into())?
|
||||
.ok_or(ProviderError::BlockHashNotFound(self.anchor_hash))
|
||||
}
|
||||
|
||||
/// Returns the block which is at the tip of the DB, i.e. the block which the state tables of
|
||||
/// the DB are currently synced to.
|
||||
fn get_db_tip_block_number<Provider>(&self, provider: &Provider) -> ProviderResult<BlockNumber>
|
||||
fn get_db_tip_block<Provider>(&self, provider: &Provider) -> ProviderResult<BlockNumHash>
|
||||
where
|
||||
Provider: StageCheckpointReader,
|
||||
Provider: StageCheckpointReader + BlockNumReader,
|
||||
{
|
||||
provider
|
||||
let block_number = provider
|
||||
.get_stage_checkpoint(StageId::Finish)?
|
||||
.as_ref()
|
||||
.map(|chk| chk.block_number)
|
||||
.ok_or_else(|| ProviderError::InsufficientChangesets { requested: 0, available: 0..=0 })
|
||||
.ok_or_else(|| ProviderError::InsufficientChangesets {
|
||||
requested: 0,
|
||||
available: 0..=0,
|
||||
})?;
|
||||
let hash = provider
|
||||
.convert_number(block_number.into())?
|
||||
.ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?;
|
||||
Ok(BlockNumHash::new(block_number, hash))
|
||||
}
|
||||
|
||||
/// Returns whether or not it is required to collect reverts, and validates that there are
|
||||
@@ -229,18 +240,27 @@ impl OverlayBuilder {
|
||||
fn reverts_required<Provider>(
|
||||
&self,
|
||||
provider: &Provider,
|
||||
db_tip_block: BlockNumber,
|
||||
requested_block: BlockNumber,
|
||||
) -> ProviderResult<bool>
|
||||
db_tip_block: BlockNumHash,
|
||||
) -> ProviderResult<Option<RangeInclusive<BlockNumber>>>
|
||||
where
|
||||
Provider: PruneCheckpointReader,
|
||||
Provider: BlockNumReader + PruneCheckpointReader,
|
||||
{
|
||||
// If the requested block is the DB tip then there won't be any reverts necessary, and we
|
||||
// can simply return Ok.
|
||||
if db_tip_block == requested_block {
|
||||
return Ok(false)
|
||||
// If the anchor is the DB tip then there won't be any reverts necessary.
|
||||
if db_tip_block.hash == self.anchor_hash {
|
||||
return Ok(None)
|
||||
}
|
||||
|
||||
// If the DB tip has moved forward into the `LazyOverlay` then we still don't need to
|
||||
// revert, the `LazyOverlay` will generate a new in-memory overlay using only the relevant
|
||||
// blocks data.
|
||||
if let Some(OverlaySource::Lazy(lazy_overlay)) = &self.overlay_source &&
|
||||
lazy_overlay.has_anchor_hash(db_tip_block.hash)
|
||||
{
|
||||
return Ok(None)
|
||||
}
|
||||
|
||||
let anchor_number = self.get_block_number(provider)?;
|
||||
|
||||
// Check account history prune checkpoint to determine the lower bound of available data.
|
||||
// The prune checkpoint's block_number is the highest pruned block, so data is available
|
||||
// starting from the next block.
|
||||
@@ -250,17 +270,17 @@ impl OverlayBuilder {
|
||||
.map(|block_number| block_number + 1)
|
||||
.unwrap_or_default();
|
||||
|
||||
let available_range = lower_bound..=db_tip_block;
|
||||
let available_range = lower_bound..=db_tip_block.number;
|
||||
|
||||
// Check if the requested block is within the available range
|
||||
if !available_range.contains(&requested_block) {
|
||||
if !available_range.contains(&anchor_number) {
|
||||
return Err(ProviderError::InsufficientChangesets {
|
||||
requested: requested_block,
|
||||
requested: anchor_number,
|
||||
available: available_range,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
Ok(Some(anchor_number + 1..=db_tip_block.number))
|
||||
}
|
||||
|
||||
/// Calculates a new [`Overlay`] given a transaction and the current db tip.
|
||||
@@ -268,12 +288,12 @@ impl OverlayBuilder {
|
||||
level = "debug",
|
||||
target = "providers::state::overlay",
|
||||
skip_all,
|
||||
fields(%db_tip_block)
|
||||
fields(?db_tip_block, anchor_hash = ?self.anchor_hash)
|
||||
)]
|
||||
fn calculate_overlay<Provider>(
|
||||
&self,
|
||||
provider: &Provider,
|
||||
db_tip_block: BlockNumber,
|
||||
db_tip_block: BlockNumHash,
|
||||
) -> ProviderResult<Overlay>
|
||||
where
|
||||
Provider: ChangeSetReader
|
||||
@@ -292,18 +312,13 @@ impl OverlayBuilder {
|
||||
let trie_updates_total_len;
|
||||
let hashed_state_updates_total_len;
|
||||
|
||||
// If block_hash is provided, collect reverts
|
||||
let (trie_updates, hashed_post_state) = if let Some(from_block) =
|
||||
self.get_requested_block_number(provider)? &&
|
||||
self.reverts_required(provider, db_tip_block, from_block)?
|
||||
// Collect any reverts which are required to bring the DB view back to the anchor hash.
|
||||
let (trie_updates, hashed_post_state) = if let Some(revert_blocks) =
|
||||
self.reverts_required(provider, db_tip_block)?
|
||||
{
|
||||
debug!(
|
||||
target: "providers::state::overlay",
|
||||
block_hash = ?self.block_hash,
|
||||
from_block,
|
||||
db_tip_block,
|
||||
range_start = from_block + 1,
|
||||
range_end = db_tip_block,
|
||||
?revert_blocks,
|
||||
"Collecting trie reverts for overlay state provider"
|
||||
);
|
||||
|
||||
@@ -317,9 +332,8 @@ impl OverlayBuilder {
|
||||
|
||||
// Use changeset cache to retrieve and accumulate reverts to restore state after
|
||||
// from_block
|
||||
let accumulated_reverts = self
|
||||
.changeset_cache
|
||||
.get_or_compute_range(provider, (from_block + 1)..=db_tip_block)?;
|
||||
let accumulated_reverts =
|
||||
self.changeset_cache.get_or_compute_range(provider, revert_blocks.clone())?;
|
||||
|
||||
retrieve_trie_reverts_duration = start.elapsed();
|
||||
accumulated_reverts
|
||||
@@ -330,14 +344,14 @@ impl OverlayBuilder {
|
||||
let _guard = debug_span!(target: "providers::state::overlay", "retrieving_hashed_state_reverts").entered();
|
||||
|
||||
let start = Instant::now();
|
||||
let res = reth_trie_db::from_reverts_auto(provider, from_block + 1..)?;
|
||||
let res = reth_trie_db::from_reverts_auto(provider, revert_blocks)?;
|
||||
retrieve_hashed_state_reverts_duration = start.elapsed();
|
||||
res
|
||||
};
|
||||
|
||||
// Resolve overlays (lazy or immediate) and extend reverts with them.
|
||||
// If reverts are empty, use overlays directly to avoid cloning.
|
||||
let (overlay_trie, overlay_state) = self.resolve_overlays();
|
||||
let (overlay_trie, overlay_state) = self.resolve_overlays(self.anchor_hash)?;
|
||||
|
||||
let trie_updates = if trie_reverts.is_empty() {
|
||||
overlay_trie
|
||||
@@ -362,8 +376,6 @@ impl OverlayBuilder {
|
||||
|
||||
debug!(
|
||||
target: "providers::state::overlay",
|
||||
block_hash = ?self.block_hash,
|
||||
?from_block,
|
||||
num_trie_updates = ?trie_updates_total_len,
|
||||
num_state_updates = ?hashed_state_updates_total_len,
|
||||
"Reverted to target block",
|
||||
@@ -371,8 +383,9 @@ impl OverlayBuilder {
|
||||
|
||||
(trie_updates, hashed_state_updates)
|
||||
} else {
|
||||
// If no block_hash, use overlays directly (resolving lazy if set)
|
||||
let (trie_updates, hashed_state) = self.resolve_overlays();
|
||||
// If no reverts are needed then we can assume that the db tip is the anchor hash or
|
||||
// overlaps with the `LazyOverlay`. Use overlays directly.
|
||||
let (trie_updates, hashed_state) = self.resolve_overlays(db_tip_block.hash)?;
|
||||
|
||||
retrieve_trie_reverts_duration = Duration::ZERO;
|
||||
retrieve_hashed_state_reverts_duration = Duration::ZERO;
|
||||
@@ -407,12 +420,7 @@ impl OverlayBuilder {
|
||||
+ BlockNumReader
|
||||
+ StorageSettingsCache,
|
||||
{
|
||||
if self.block_hash.is_none() {
|
||||
let (trie_updates, hashed_post_state) = self.resolve_overlays();
|
||||
return Ok(Overlay { trie_updates, hashed_post_state })
|
||||
}
|
||||
|
||||
let db_tip_block = self.get_db_tip_block_number(provider)?;
|
||||
let db_tip_block = self.get_db_tip_block(provider)?;
|
||||
self.calculate_overlay(provider, db_tip_block)
|
||||
}
|
||||
}
|
||||
@@ -422,22 +430,46 @@ impl OverlayBuilder {
|
||||
/// This factory allows building an `OverlayStateProvider` whose DB state has been reverted to a
|
||||
/// particular block, and/or with additional overlay information added on top.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OverlayStateProviderFactory<F> {
|
||||
pub struct OverlayStateProviderFactory<F, N: NodePrimitives = EthPrimitives> {
|
||||
/// The underlying database provider factory
|
||||
factory: F,
|
||||
/// Overlay builder containing the configuration and overlay calculation logic.
|
||||
overlay_builder: OverlayBuilder,
|
||||
overlay_builder: OverlayBuilder<N>,
|
||||
/// A cache which maps `db_tip -> Overlay`. If the db tip changes during usage of the factory
|
||||
/// then a new entry will get added to this, but in most cases only one entry is present.
|
||||
overlay_cache: Arc<DashMap<BlockNumber, Overlay>>,
|
||||
overlay_cache: Arc<DashMap<BlockHash, Overlay>>,
|
||||
}
|
||||
|
||||
impl<F> OverlayStateProviderFactory<F> {
|
||||
impl<F, N: NodePrimitives> OverlayStateProviderFactory<F, N> {
|
||||
/// Create a new overlay state provider factory
|
||||
pub fn new(factory: F, overlay_builder: OverlayBuilder) -> Self {
|
||||
pub fn new(factory: F, overlay_builder: OverlayBuilder<N>) -> Self {
|
||||
Self { factory, overlay_builder, overlay_cache: Default::default() }
|
||||
}
|
||||
|
||||
/// Set a lazy overlay that will be computed on first access.
|
||||
pub fn with_lazy_overlay(mut self, lazy_overlay: Option<LazyOverlay<N>>) -> Self {
|
||||
self.overlay_builder = self.overlay_builder.with_lazy_overlay(lazy_overlay);
|
||||
self.overlay_cache = Default::default();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the hashed state overlay.
|
||||
pub fn with_hashed_state_overlay(
|
||||
mut self,
|
||||
hashed_state_overlay: Option<Arc<HashedPostStateSorted>>,
|
||||
) -> Self {
|
||||
self.overlay_builder = self.overlay_builder.with_hashed_state_overlay(hashed_state_overlay);
|
||||
self.overlay_cache = Default::default();
|
||||
self
|
||||
}
|
||||
|
||||
/// Extends the existing hashed state overlay with the given [`HashedPostStateSorted`].
|
||||
pub fn with_extended_hashed_state_overlay(mut self, other: HashedPostStateSorted) -> Self {
|
||||
self.overlay_builder = self.overlay_builder.with_extended_hashed_state_overlay(other);
|
||||
self.overlay_cache = Default::default();
|
||||
self
|
||||
}
|
||||
|
||||
/// Fetches an [`Overlay`] from the cache based on the current db tip block. If there is no
|
||||
/// cached value then this calculates the [`Overlay`] and populates the cache.
|
||||
#[instrument(level = "debug", target = "providers::state::overlay", skip_all)]
|
||||
@@ -451,14 +483,9 @@ impl<F> OverlayStateProviderFactory<F> {
|
||||
+ BlockNumReader
|
||||
+ StorageSettingsCache,
|
||||
{
|
||||
// No anchor block — just resolve the in-memory overlay directly.
|
||||
if self.overlay_builder.block_hash.is_none() {
|
||||
return self.overlay_builder.build_overlay(provider)
|
||||
}
|
||||
let db_tip_block = self.overlay_builder.get_db_tip_block(provider)?;
|
||||
|
||||
let db_tip_block = self.overlay_builder.get_db_tip_block_number(provider)?;
|
||||
|
||||
let overlay = match self.overlay_cache.entry(db_tip_block) {
|
||||
let overlay = match self.overlay_cache.entry(db_tip_block.hash) {
|
||||
dashmap::Entry::Occupied(entry) => entry.get().clone(),
|
||||
dashmap::Entry::Vacant(entry) => {
|
||||
self.overlay_builder.metrics.overlay_cache_misses.increment(1);
|
||||
@@ -472,11 +499,13 @@ impl<F> OverlayStateProviderFactory<F> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> DatabaseProviderROFactory for OverlayStateProviderFactory<F>
|
||||
impl<F, N> DatabaseProviderROFactory for OverlayStateProviderFactory<F, N>
|
||||
where
|
||||
N: NodePrimitives,
|
||||
F: DatabaseProviderFactory,
|
||||
F::Provider: StageCheckpointReader
|
||||
+ PruneCheckpointReader
|
||||
+ DBProvider
|
||||
+ BlockNumReader
|
||||
+ ChangeSetReader
|
||||
+ StorageChangeSetReader
|
||||
|
||||
@@ -696,14 +696,15 @@ impl<N: NodePrimitives> StaticFileProviderRW<N> {
|
||||
|
||||
/// Updates the `self.reader` internal index.
|
||||
fn update_index(&self) -> ProviderResult<()> {
|
||||
let segment = self.writer.user_header().segment();
|
||||
|
||||
// We find the maximum block of the segment by checking this writer's last block.
|
||||
//
|
||||
// However if there's no block range (because there's no data), we try to calculate it by
|
||||
// subtracting 1 from the expected block start, resulting on the last block of the
|
||||
// previous file.
|
||||
//
|
||||
// If that expected block start is 0, then it means that there's no actual block data, and
|
||||
// there's no block data in static files.
|
||||
// previous file — but only if that file actually exists. If the previous file doesn't
|
||||
// exist (e.g. first-ever file for a segment starting past range boundary), there's
|
||||
// nothing to index.
|
||||
let segment_max_block = self
|
||||
.writer
|
||||
.user_header()
|
||||
@@ -711,12 +712,18 @@ impl<N: NodePrimitives> StaticFileProviderRW<N> {
|
||||
.as_ref()
|
||||
.map(|block_range| block_range.end())
|
||||
.or_else(|| {
|
||||
(self.writer.user_header().expected_block_start() >
|
||||
self.reader().genesis_block_number())
|
||||
.then(|| self.writer.user_header().expected_block_start() - 1)
|
||||
let expected_start = self.writer.user_header().expected_block_start();
|
||||
if expected_start <= self.reader().genesis_block_number() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let prev_block = expected_start - 1;
|
||||
let prev_range = self.reader().find_fixed_range(segment, prev_block);
|
||||
let prev_path = self.reader().directory().join(segment.filename(&prev_range));
|
||||
prev_path.exists().then_some(prev_block)
|
||||
});
|
||||
|
||||
self.reader().update_index(self.writer.user_header().segment(), segment_max_block)
|
||||
self.reader().update_index(segment, segment_max_block)
|
||||
}
|
||||
|
||||
/// Ensures that the writer is positioned at the specified block number.
|
||||
|
||||
@@ -809,4 +809,78 @@ mod tests {
|
||||
"Should have 7 blocks * 5 changes = 35 rows"
|
||||
);
|
||||
}
|
||||
|
||||
/// Opening a writer for a block past the first range boundary should succeed
|
||||
/// even when no previous static file exists for the segment.
|
||||
#[test]
|
||||
fn test_get_writer_no_previous_file() {
|
||||
let (static_dir, _) = create_test_static_files_dir();
|
||||
let provider = setup_test_provider(&static_dir, 100);
|
||||
|
||||
// Request a writer starting at block 250, which falls into range 200..=299.
|
||||
// No file exists for range 100..=199 (the "previous" range).
|
||||
// This must not panic or error.
|
||||
let mut writer = provider
|
||||
.get_writer(250, StaticFileSegment::AccountChangeSets)
|
||||
.expect("get_writer should succeed without previous file");
|
||||
|
||||
// The index should have no entry for AccountChangeSets yet (empty jar).
|
||||
assert!(
|
||||
provider.get_highest_static_file_block(StaticFileSegment::AccountChangeSets).is_none(),
|
||||
"Empty jar should not create an index entry"
|
||||
);
|
||||
|
||||
// Writing data requires padding from the range start (200) to block 250,
|
||||
// same as the migration code does.
|
||||
let writer_start = writer.next_block_number();
|
||||
for block in writer_start..250 {
|
||||
writer.append_account_changeset(vec![], block).unwrap();
|
||||
}
|
||||
let changeset = generate_test_changeset(250, 2);
|
||||
writer.append_account_changeset(changeset, 250).unwrap();
|
||||
writer.commit().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
provider.get_highest_static_file_block(StaticFileSegment::AccountChangeSets).unwrap(),
|
||||
250,
|
||||
"After writing block 250, highest block should be 250"
|
||||
);
|
||||
}
|
||||
|
||||
/// When a previous file DOES exist, opening a new empty writer for the next
|
||||
/// range should still update the index to point at the previous file.
|
||||
#[test]
|
||||
fn test_get_writer_with_previous_file() {
|
||||
let (static_dir, _) = create_test_static_files_dir();
|
||||
let provider = setup_test_provider(&static_dir, 100);
|
||||
|
||||
// Write blocks 0..=99 to fill the first file completely.
|
||||
{
|
||||
let mut writer = provider.get_writer(0, StaticFileSegment::AccountChangeSets).unwrap();
|
||||
for block in 0..100 {
|
||||
writer.append_account_changeset(generate_test_changeset(block, 1), block).unwrap();
|
||||
}
|
||||
writer.commit().unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
provider.get_highest_static_file_block(StaticFileSegment::AccountChangeSets).unwrap(),
|
||||
99
|
||||
);
|
||||
|
||||
// Now get a writer for block 100 (next range 100..=199).
|
||||
// The previous file (0..=99) exists, so this should succeed.
|
||||
let writer = provider
|
||||
.get_writer(100, StaticFileSegment::AccountChangeSets)
|
||||
.expect("get_writer should succeed with previous file");
|
||||
|
||||
// The index should still reflect the previous file's max block.
|
||||
assert_eq!(
|
||||
provider.get_highest_static_file_block(StaticFileSegment::AccountChangeSets).unwrap(),
|
||||
99,
|
||||
"Index should still point at previous file's max block"
|
||||
);
|
||||
|
||||
drop(writer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,7 +547,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
ensure_intrinsic_gas(transaction, &self.fork_tracker, block_gas_limit)?;
|
||||
ensure_intrinsic_gas(transaction, &self.fork_tracker)?;
|
||||
|
||||
// light blob tx pre-checks
|
||||
if transaction.is_eip4844() {
|
||||
@@ -1404,7 +1404,6 @@ impl ForkTracker {
|
||||
pub fn ensure_intrinsic_gas<T: EthPoolTransaction>(
|
||||
transaction: &T,
|
||||
fork_tracker: &ForkTracker,
|
||||
block_gas_limit: u64,
|
||||
) -> Result<(), InvalidPoolTransactionError> {
|
||||
use revm_primitives::hardfork::SpecId;
|
||||
let spec_id = if fork_tracker.is_prague_activated() {
|
||||
@@ -1425,7 +1424,7 @@ pub fn ensure_intrinsic_gas<T: EthPoolTransaction>(
|
||||
.map(|l| l.iter().map(|i| i.storage_keys.len()).sum::<usize>())
|
||||
.unwrap_or_default() as u64,
|
||||
transaction.authorization_list().map(|l| l.len()).unwrap_or_default() as u64,
|
||||
revm_primitives::eip8037::cost_per_state_byte(block_gas_limit),
|
||||
revm_primitives::eip8037::CPSB_GLAMSTERDAM,
|
||||
);
|
||||
|
||||
let gas_limit = transaction.gas_limit();
|
||||
@@ -1480,11 +1479,11 @@ mod tests {
|
||||
tx_gas_limit_cap: AtomicU64::new(0),
|
||||
};
|
||||
|
||||
let res = ensure_intrinsic_gas(&transaction, &fork_tracker, 30_000_000);
|
||||
let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
|
||||
assert!(res.is_ok());
|
||||
|
||||
fork_tracker.shanghai = true.into();
|
||||
let res = ensure_intrinsic_gas(&transaction, &fork_tracker, 30_000_000);
|
||||
let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
|
||||
assert!(res.is_ok());
|
||||
|
||||
let provider = MockEthProvider::default().with_genesis_block();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user