mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-04-30 03:01:58 -04:00
Compare commits
101 Commits
eip8037
...
bal-devnet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
473deed90d | ||
|
|
61dd104871 | ||
|
|
b5581bd6c2 | ||
|
|
439f1f9af2 | ||
|
|
303ea0ff61 | ||
|
|
70ed24ac38 | ||
|
|
43e08f1539 | ||
|
|
3c63fb6b1f | ||
|
|
72d0e04d85 | ||
|
|
9906da5504 | ||
|
|
cf3028a52f | ||
|
|
6259cb86f8 | ||
|
|
89ad00601e | ||
|
|
88bc262bd1 | ||
|
|
d63518d18c | ||
|
|
b8baaf6aa7 | ||
|
|
2a94eedd61 | ||
|
|
20cce0a6df | ||
|
|
6736b2ad65 | ||
|
|
1e17f7cf67 | ||
|
|
bd476289fa | ||
|
|
7f12c9d993 | ||
|
|
7758afd75d | ||
|
|
eae7813aca | ||
|
|
0632bc72c9 | ||
|
|
2fb2246579 | ||
|
|
4174045d47 | ||
|
|
81262c0057 | ||
|
|
4250314722 | ||
|
|
b07c223530 | ||
|
|
49c05aed05 | ||
|
|
a600f08593 | ||
|
|
b5c1e0d08e | ||
|
|
accf15e2e4 | ||
|
|
e794626df6 | ||
|
|
2e9d969033 | ||
|
|
3e85ec2670 | ||
|
|
8c5b6c9b15 | ||
|
|
a66d49c190 | ||
|
|
ab2252c33d | ||
|
|
8dbb015770 | ||
|
|
96be836679 | ||
|
|
ab5f2db594 | ||
|
|
6a633a42f0 | ||
|
|
08fd55d8c9 | ||
|
|
fab95c1f3a | ||
|
|
e4191ccea8 | ||
|
|
080ff004e3 | ||
|
|
18599f1732 | ||
|
|
9fd35e2917 | ||
|
|
c1a5e20b50 | ||
|
|
0ff16ea053 | ||
|
|
3541bd7f65 | ||
|
|
a3aec0c662 | ||
|
|
0dfdaca3f0 | ||
|
|
c535a7fb5b | ||
|
|
bf6270b8a3 | ||
|
|
1e78685a6c | ||
|
|
1f1e320643 | ||
|
|
74ea20400e | ||
|
|
ffff5fbce2 | ||
|
|
f514892b41 | ||
|
|
d0ad4b0e18 | ||
|
|
cb6ed16485 | ||
|
|
a88eef91f4 | ||
|
|
f7e7afd51f | ||
|
|
102764285b | ||
|
|
4679c86003 | ||
|
|
7671838c61 | ||
|
|
8f4461c060 | ||
|
|
0119f3c612 | ||
|
|
094aaef5a1 | ||
|
|
32d03ff4d7 | ||
|
|
3368ce6485 | ||
|
|
9bc2388871 | ||
|
|
1728fa97c0 | ||
|
|
868248ec54 | ||
|
|
16ab4b8518 | ||
|
|
ce74466b93 | ||
|
|
992fc30ff5 | ||
|
|
3ece6b6047 | ||
|
|
dec9f93ad1 | ||
|
|
03484f76ec | ||
|
|
b870f04509 | ||
|
|
5277e59cc4 | ||
|
|
6271c2702f | ||
|
|
ce15ab9f55 | ||
|
|
0b1ec2dc89 | ||
|
|
5862c72880 | ||
|
|
8a7655ca5d | ||
|
|
cd20adc1d4 | ||
|
|
f0fe45d6bf | ||
|
|
00422207f4 | ||
|
|
28a94829e9 | ||
|
|
e081249f65 | ||
|
|
99fedf01f8 | ||
|
|
179e1bfc34 | ||
|
|
3adb5b9e58 | ||
|
|
57d7c98f66 | ||
|
|
5d9a43f2d4 | ||
|
|
defd0e8e5c |
5
.changelog/safe-waves-cook.md
Normal file
5
.changelog/safe-waves-cook.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
reth-engine-tree: patch
|
||||
---
|
||||
|
||||
Downgraded per-transaction prewarm span from `debug_span!` to `trace_span!` to reduce noise in debug-level logging.
|
||||
7
.changelog/warm-trees-grow.md
Normal file
7
.changelog/warm-trees-grow.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
reth-engine-primitives: patch
|
||||
reth-engine-tree: patch
|
||||
reth-node-core: patch
|
||||
---
|
||||
|
||||
Removed `--engine.enable-arena-sparse-trie` CLI flag and made the arena-based sparse trie the default implementation. The hash-map-based `ParallelSparseTrie` variant is no longer selectable.
|
||||
45
.github/scripts/bench-reth-run.sh
vendored
45
.github/scripts/bench-reth-run.sh
vendored
@@ -120,9 +120,18 @@ RETH_ARGS=(
|
||||
--no-persist-peers
|
||||
)
|
||||
|
||||
# Big blocks mode requires the testing API and skip-invalid-transactions
|
||||
# Gate flag on binary support (older baselines may not have it).
|
||||
# Uses --help which exits immediately via clap without node init.
|
||||
SYNC_STATE_IDLE=false
|
||||
if "$BINARY" node --help 2>/dev/null | grep -qF -- '--debug.startup-sync-state-idle'; then
|
||||
RETH_ARGS+=(--debug.startup-sync-state-idle)
|
||||
SYNC_STATE_IDLE=true
|
||||
fi
|
||||
|
||||
# Big blocks mode requires the testing API, skip-invalid-transactions, and
|
||||
# skip-gas-limit-ramp-check + gas-limit override to avoid the 6800-block ramp.
|
||||
if [ "$BIG_BLOCKS" = "true" ]; then
|
||||
RETH_ARGS+=(--http.api eth,net,web3,reth,testing --testing.skip-invalid-transactions)
|
||||
RETH_ARGS+=(--http.api eth,net,web3,reth,testing --rpc.max-request-size max --testing.skip-invalid-transactions --testing.skip-gas-limit-ramp-check --testing.gas-limit 1000000000)
|
||||
fi
|
||||
|
||||
# Append per-label extra node args (baseline or feature)
|
||||
@@ -194,7 +203,7 @@ for i in $(seq 1 60); do
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
|
||||
> /dev/null 2>&1; then
|
||||
echo "reth (${LABEL}) is ready after ${i}s"
|
||||
echo "reth (${LABEL}) RPC is up after ${i}s"
|
||||
break
|
||||
fi
|
||||
if [ "$i" -eq 60 ]; then
|
||||
@@ -205,6 +214,29 @@ for i in $(seq 1 60); do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Wait for the pipeline to finish (eth_syncing returns false) so the
|
||||
# engine is in live mode and can accept newPayload calls.
|
||||
# Only possible when --debug.startup-sync-state-idle is supported.
|
||||
if [ "$SYNC_STATE_IDLE" = "true" ]; then
|
||||
for i in $(seq 1 300); do
|
||||
SYNC_RESULT=$(curl -sf http://127.0.0.1:8545 -X POST \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' 2>/dev/null || true)
|
||||
if [ -n "$SYNC_RESULT" ] && jq -e '.result == false' <<< "$SYNC_RESULT" > /dev/null 2>&1; then
|
||||
echo "reth (${LABEL}) pipeline finished after ${i}s, engine is live"
|
||||
break
|
||||
fi
|
||||
if [ "$i" -eq 300 ]; then
|
||||
echo "::error::reth (${LABEL}) pipeline did not finish within 300s"
|
||||
cat "$LOG"
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
else
|
||||
echo "reth (${LABEL}) binary does not support --debug.startup-sync-state-idle, skipping sync wait"
|
||||
fi
|
||||
|
||||
# Run reth-bench with high priority but as the current user so output
|
||||
# files are not root-owned (avoids EACCES on next checkout).
|
||||
BENCH_NICE="sudo nice -n -20 sudo -u $(id -un)"
|
||||
@@ -219,12 +251,8 @@ if [ -n "${BENCH_WAIT_TIME:-}" ]; then
|
||||
fi
|
||||
|
||||
if [ "$BIG_BLOCKS" = "true" ]; then
|
||||
# Big blocks mode: replay pre-generated payloads with gas ramp
|
||||
# Big blocks mode: replay pre-generated payloads
|
||||
BIG_BLOCKS_DIR="${BENCH_WORK_DIR}/big-blocks"
|
||||
# Count gas ramp blocks for reporting
|
||||
GAS_RAMP_COUNT=$(find "$BIG_BLOCKS_DIR/gas-ramp-dir" -name '*.json' | wc -l)
|
||||
echo "$GAS_RAMP_COUNT" > "$OUTPUT_DIR/gas_ramp_blocks.txt"
|
||||
echo "Gas ramp blocks: $GAS_RAMP_COUNT"
|
||||
|
||||
# Start tracy-capture so profile only covers the benchmark
|
||||
if [ "${BENCH_TRACY:-off}" != "off" ]; then
|
||||
@@ -237,7 +265,6 @@ if [ "$BIG_BLOCKS" = "true" ]; then
|
||||
echo "Running big blocks benchmark (replay-payloads)..."
|
||||
$BENCH_NICE "$RETH_BENCH" replay-payloads \
|
||||
"${EXTRA_BENCH_ARGS[@]}" \
|
||||
--gas-ramp-dir "$BIG_BLOCKS_DIR/gas-ramp-dir" \
|
||||
--payload-dir "$BIG_BLOCKS_DIR/payloads" \
|
||||
--engine-rpc-url http://127.0.0.1:8551 \
|
||||
--jwt-secret "$DATADIR/jwt.hex" \
|
||||
|
||||
129
.github/scripts/bench-reth-snapshot.sh
vendored
129
.github/scripts/bench-reth-snapshot.sh
vendored
@@ -1,15 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Downloads the latest nightly snapshot into the schelk volume with
|
||||
# progress reporting to the GitHub PR comment.
|
||||
# Downloads the latest snapshot into the schelk volume using
|
||||
# `reth download` with progress reporting to the GitHub PR comment.
|
||||
#
|
||||
# Skips the download if the local ETag marker matches the remote one.
|
||||
# Skips the download if the manifest content hasn't changed since
|
||||
# the last successful download (checked via SHA-256 of the manifest).
|
||||
#
|
||||
# Usage: bench-reth-snapshot.sh [--check]
|
||||
# --check Only check if a download is needed; exits 0 if up-to-date, 1 if not.
|
||||
# --check Only check if a download is needed; exits 0 if up-to-date, 10 if not.
|
||||
#
|
||||
# Required env:
|
||||
# SCHELK_MOUNT – schelk mount point (e.g. /reth-bench)
|
||||
# BENCH_RETH_BINARY – path to the reth binary
|
||||
# GITHUB_TOKEN – token for GitHub API calls (only for download)
|
||||
# BENCH_COMMENT_ID – PR comment ID to update (optional)
|
||||
# BENCH_REPO – owner/repo (e.g. paradigmxyz/reth)
|
||||
@@ -18,52 +20,65 @@
|
||||
# BENCH_CONFIG – config summary line
|
||||
set -euo pipefail
|
||||
|
||||
BUCKET="minio/reth-snapshots/reth-1-minimal-nightly-previous.tar.zst"
|
||||
MC="mc"
|
||||
BUCKET="minio/reth-snapshots"
|
||||
MANIFEST_PATH="reth-1-minimal-stable/manifest.json"
|
||||
DATADIR="$SCHELK_MOUNT/datadir"
|
||||
ETAG_FILE="$HOME/.reth-bench-snapshot-etag"
|
||||
HASH_FILE="$HOME/.reth-bench-snapshot-hash"
|
||||
|
||||
# Get remote metadata via JSON for reliable parsing
|
||||
MC_STAT=$(mc stat --json "$BUCKET" 2>/dev/null || true)
|
||||
REMOTE_ETAG=$(echo "$MC_STAT" | jq -r '.etag // empty')
|
||||
if [ -z "$REMOTE_ETAG" ]; then
|
||||
echo "::warning::Failed to get ETag from mc stat, will re-download"
|
||||
REMOTE_ETAG="unknown-$(date +%s)"
|
||||
fi
|
||||
# Fetch manifest and compute content hash for reliable freshness check
|
||||
MANIFEST_CONTENT=$($MC cat "${BUCKET}/${MANIFEST_PATH}" 2>/dev/null) || {
|
||||
echo "::error::Failed to fetch snapshot manifest from ${BUCKET}/${MANIFEST_PATH}"
|
||||
exit 2
|
||||
}
|
||||
REMOTE_HASH=$(echo "$MANIFEST_CONTENT" | sha256sum | awk '{print $1}')
|
||||
|
||||
LOCAL_ETAG=""
|
||||
[ -f "$ETAG_FILE" ] && LOCAL_ETAG=$(cat "$ETAG_FILE")
|
||||
LOCAL_HASH=""
|
||||
[ -f "$HASH_FILE" ] && LOCAL_HASH=$(cat "$HASH_FILE")
|
||||
|
||||
if [ "$REMOTE_ETAG" = "$LOCAL_ETAG" ]; then
|
||||
echo "Snapshot is up-to-date (ETag: ${REMOTE_ETAG})"
|
||||
if [ "${1:-}" = "--check" ]; then
|
||||
exit 0
|
||||
fi
|
||||
if [ "$REMOTE_HASH" = "$LOCAL_HASH" ]; then
|
||||
echo "Snapshot is up-to-date (manifest hash: ${REMOTE_HASH:0:16}…)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Snapshot needs update (local: ${LOCAL_ETAG:-<none>}, remote: ${REMOTE_ETAG})"
|
||||
echo "Snapshot needs update (local: ${LOCAL_HASH:+${LOCAL_HASH:0:16}…}${LOCAL_HASH:-<none>}, remote: ${REMOTE_HASH:0:16}…)"
|
||||
if [ "${1:-}" = "--check" ]; then
|
||||
exit 10
|
||||
fi
|
||||
|
||||
RETH="${BENCH_RETH_BINARY:?BENCH_RETH_BINARY must be set}"
|
||||
if [ ! -x "$RETH" ]; then
|
||||
echo "::error::reth binary not found or not executable at $RETH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get compressed size for progress tracking
|
||||
TOTAL_BYTES=$(echo "$MC_STAT" | jq -r '.size // empty')
|
||||
if [ -z "$TOTAL_BYTES" ] || [ "$TOTAL_BYTES" = "0" ]; then
|
||||
echo "::error::Failed to get snapshot size from mc stat"
|
||||
# Resolve the MinIO HTTP endpoint from the mc alias so reth can
|
||||
# fetch archives over HTTP (the manifest's embedded base_url points
|
||||
# to the cluster-internal address which is unreachable from runners).
|
||||
MINIO_ENDPOINT=$($MC alias list minio --json 2>/dev/null | jq -r '.URL // empty') || true
|
||||
if [ -z "$MINIO_ENDPOINT" ]; then
|
||||
echo "::error::Failed to resolve MinIO endpoint from mc alias 'minio'"
|
||||
exit 1
|
||||
fi
|
||||
echo "Snapshot size: $TOTAL_BYTES bytes ($(numfmt --to=iec "$TOTAL_BYTES"))"
|
||||
BASE_URL="${MINIO_ENDPOINT}/reth-snapshots/reth-1-minimal-stable"
|
||||
|
||||
# Rewrite manifest's base_url with the runner-reachable endpoint
|
||||
MANIFEST_TMP=$(mktemp --suffix=.json)
|
||||
trap 'rm -f -- "$MANIFEST_TMP"' EXIT
|
||||
echo "$MANIFEST_CONTENT" \
|
||||
| jq --arg base "$BASE_URL" '.base_url = $base' > "$MANIFEST_TMP"
|
||||
|
||||
# Prepare mount
|
||||
mountpoint -q "$SCHELK_MOUNT" && sudo schelk recover -y || true
|
||||
sudo schelk mount -y
|
||||
sudo rm -rf "$DATADIR"
|
||||
sudo mkdir -p "$DATADIR"
|
||||
# reth download runs as current user (not root), needs write access
|
||||
sudo chown -R "$(id -u):$(id -g)" "$DATADIR"
|
||||
|
||||
update_comment() {
|
||||
local pct="$1"
|
||||
local status="$1"
|
||||
[ -z "${BENCH_COMMENT_ID:-}" ] && return 0
|
||||
local status="Building binaries & downloading snapshot… ${pct}%"
|
||||
local body
|
||||
body="$(printf 'cc @%s\n\n🚀 Benchmark started! [View job](%s)\n\n⏳ **Status:** %s\n\n%s' \
|
||||
"$BENCH_ACTOR" "$BENCH_JOB_URL" "$status" "$BENCH_CONFIG")"
|
||||
@@ -75,53 +90,31 @@ update_comment() {
|
||||
> /dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
# Track compressed bytes flowing through the pipe
|
||||
DL_BYTES_FILE=$(mktemp)
|
||||
echo 0 > "$DL_BYTES_FILE"
|
||||
update_comment "Downloading snapshot…"
|
||||
|
||||
# Start progress reporter in background
|
||||
(
|
||||
while true; do
|
||||
sleep 10
|
||||
CURRENT=$(cat "$DL_BYTES_FILE" 2>/dev/null || echo 0)
|
||||
if [ "$TOTAL_BYTES" -gt 0 ]; then
|
||||
PCT=$(( CURRENT * 100 / TOTAL_BYTES ))
|
||||
[ "$PCT" -gt 100 ] && PCT=100
|
||||
echo "Snapshot download: $(numfmt --to=iec "$CURRENT") / $(numfmt --to=iec "$TOTAL_BYTES") (${PCT}%)"
|
||||
update_comment "$PCT"
|
||||
fi
|
||||
done
|
||||
) &
|
||||
PROGRESS_PID=$!
|
||||
trap 'kill $PROGRESS_PID 2>/dev/null || true; rm -f "$DL_BYTES_FILE"' EXIT
|
||||
# Download using reth download (manifest-path with rewritten base_url)
|
||||
"$RETH" download \
|
||||
--manifest-path "$MANIFEST_TMP" \
|
||||
-y \
|
||||
--minimal \
|
||||
--datadir "$DATADIR"
|
||||
|
||||
# Download and extract; python byte counter tracks compressed bytes received
|
||||
mc cat "$BUCKET" | python3 -c "
|
||||
import sys
|
||||
count = 0
|
||||
while True:
|
||||
data = sys.stdin.buffer.read(1048576)
|
||||
if not data:
|
||||
break
|
||||
count += len(data)
|
||||
sys.stdout.buffer.write(data)
|
||||
with open('$DL_BYTES_FILE', 'w') as f:
|
||||
f.write(str(count))
|
||||
" | pzstd -d -p 6 | sudo tar -xf - -C "$DATADIR"
|
||||
|
||||
# Stop progress reporter
|
||||
kill $PROGRESS_PID 2>/dev/null || true
|
||||
wait $PROGRESS_PID 2>/dev/null || true
|
||||
|
||||
update_comment "100"
|
||||
update_comment "Downloading snapshot… done"
|
||||
echo "Snapshot download complete"
|
||||
|
||||
# Sanity check: verify expected directories exist
|
||||
if [ ! -d "$DATADIR/db" ] || [ ! -d "$DATADIR/static_files" ]; then
|
||||
echo "::error::Snapshot download did not produce expected directory layout (missing db/ or static_files/)"
|
||||
ls -la "$DATADIR" || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Promote the new snapshot to become the schelk baseline (virgin volume).
|
||||
# This copies changed blocks from scratch → virgin so that future
|
||||
# `schelk recover` calls restore to this new state.
|
||||
sync
|
||||
sudo schelk promote -y
|
||||
|
||||
# Save ETag marker
|
||||
echo "$REMOTE_ETAG" > "$ETAG_FILE"
|
||||
echo "Snapshot promoted to schelk baseline (ETag: ${REMOTE_ETAG})"
|
||||
# Save manifest hash
|
||||
echo "$REMOTE_HASH" > "$HASH_FILE"
|
||||
echo "Snapshot promoted to schelk baseline (manifest hash: ${REMOTE_HASH:0:16}…)"
|
||||
|
||||
2
.github/scripts/bench-reth-summary.py
vendored
2
.github/scripts/bench-reth-summary.py
vendored
@@ -472,7 +472,6 @@ def main():
|
||||
parser.add_argument("--feature-ref", "--branch-sha", "--feature-sha", default=None, help="Feature commit SHA")
|
||||
parser.add_argument("--behind-baseline", "--behind-main", type=int, default=0, help="Commits behind baseline")
|
||||
parser.add_argument("--big-blocks", action="store_true", default=False, help="Big blocks mode")
|
||||
parser.add_argument("--gas-ramp-blocks", type=int, default=0, help="Number of gas ramp blocks (big blocks mode)")
|
||||
parser.add_argument("--grafana-url", default=None, help="Grafana dashboard URL for this benchmark run")
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -554,7 +553,6 @@ def main():
|
||||
summary = {
|
||||
"blocks": paired_stats["blocks"],
|
||||
"big_blocks": args.big_blocks,
|
||||
"gas_ramp_blocks": args.gas_ramp_blocks,
|
||||
"baseline": {
|
||||
"name": baseline_name,
|
||||
"ref": baseline_ref,
|
||||
|
||||
1
.github/scripts/bench-utils.js
vendored
1
.github/scripts/bench-utils.js
vendored
@@ -42,7 +42,6 @@ function loadSamplyUrls(workDir) {
|
||||
function blocksLabel(summary) {
|
||||
const parts = [];
|
||||
if (summary.big_blocks) {
|
||||
if (summary.gas_ramp_blocks) parts.push({ key: 'Gas Ramp', value: summary.gas_ramp_blocks });
|
||||
parts.push({ key: 'Big Blocks', value: summary.blocks });
|
||||
} else {
|
||||
const warmup = summary.warmup_blocks || process.env.BENCH_WARMUP_BLOCKS || '';
|
||||
|
||||
2
.github/scripts/hive/Dockerfile
vendored
2
.github/scripts/hive/Dockerfile
vendored
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# We'll use cargo-chef to speed up the build
|
||||
#
|
||||
FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef
|
||||
FROM lukemathwalker/cargo-chef:latest-rust-1.94.0-trixie AS chef
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies
|
||||
|
||||
11
.github/scripts/hive/build_simulators.sh
vendored
11
.github/scripts/hive/build_simulators.sh
vendored
@@ -9,10 +9,17 @@ go build .
|
||||
|
||||
./hive -client reth # first builds and caches the client
|
||||
|
||||
# Run each hive command in the background for each simulator and wait
|
||||
# Run each hive command in the background for each simulator and wait
|
||||
echo "Building images"
|
||||
# TODO: test code has been moved from https://github.com/ethereum/execution-spec-tests to https://github.com/ethereum/execution-specs we need to pin eels branch with `--sim.buildarg branch=<release-branch-name>` once we have the fusaka release tagged on the new repo
|
||||
./hive -client reth --sim "ethereum/eels" --sim.buildarg fixtures=https://github.com/ethereum/execution-spec-tests/releases/download/v5.3.0/fixtures_develop.tar.gz -sim.timelimit 1s || true &
|
||||
./hive -client reth --sim "ethereum/eels/consume-engine" \
|
||||
--sim.buildarg fixtures=https://github.com/ethereum/execution-spec-tests/releases/download/bal@v5.1.0/fixtures_bal.tar.gz \
|
||||
--sim.buildarg branch=err-map-3 \
|
||||
--sim.timelimit 1s || true &
|
||||
./hive -client reth --sim "ethereum/eels/consume-rlp" \
|
||||
--sim.buildarg fixtures=https://github.com/ethereum/execution-spec-tests/releases/download/bal@v5.1.0/fixtures_bal.tar.gz \
|
||||
--sim.buildarg branch=err-map-3 \
|
||||
--sim.timelimit 1s || true &
|
||||
./hive -client reth --sim "ethereum/engine" -sim.timelimit 1s || true &
|
||||
./hive -client reth --sim "devp2p" -sim.timelimit 1s || true &
|
||||
./hive -client reth --sim "ethereum/rpc-compat" -sim.timelimit 1s || true &
|
||||
|
||||
82
.github/scripts/hive/expected_failures.yaml
vendored
82
.github/scripts/hive/expected_failures.yaml
vendored
@@ -16,15 +16,19 @@ 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:
|
||||
- Invalid PayloadAttributes, Missing BeaconRoot, Syncing=True (Cancun) (reth)
|
||||
# the test fails with older versions of the code for which it passed before, probably related to changes
|
||||
# in hive or its dependencies
|
||||
- Blob Transaction Ordering, Multiple Clients (Cancun) (reth)
|
||||
|
||||
sync: [ ]
|
||||
sync: []
|
||||
|
||||
engine-auth: [ ]
|
||||
engine-auth: []
|
||||
|
||||
# EIP-7610 related tests (Revert creation in case of non-empty storage):
|
||||
#
|
||||
@@ -42,6 +46,10 @@ engine-auth: [ ]
|
||||
#
|
||||
# System contract tests (already fixed and deployed):
|
||||
#
|
||||
# tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout and test_invalid_log_length
|
||||
# System contract is already fixed and deployed; tests cover scenarios where contract is
|
||||
# malformed which can't happen retroactively. No point in adding checks.
|
||||
#
|
||||
# tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py::test_system_contract_deployment
|
||||
# tests/prague/eip7251_consolidations/test_contract_deployment.py::test_system_contract_deployment
|
||||
# Post-fork system contract deployment tests. Should fix for spec compliance but not realistic
|
||||
@@ -50,8 +58,44 @@ eels/consume-engine:
|
||||
- tests/prague/eip7702_set_code_tx/test_set_code_txs.py::test_set_code_to_non_empty_storage[fork_Prague-blockchain_test_engine-zero_nonce]-reth
|
||||
- tests/prague/eip7251_consolidations/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test_engine-deploy_after_fork-nonzero_balance]-reth
|
||||
- tests/prague/eip7251_consolidations/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test_engine-deploy_after_fork-zero_balance]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_amount_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_amount_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_index_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_index_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_pubkey_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_pubkey_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_signature_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_signature_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_withdrawal_credentials_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_withdrawal_credentials_size-value_zero]-reth
|
||||
- tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test_engine-deploy_after_fork-nonzero_balance]-reth
|
||||
- tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test_engine-deploy_after_fork-zero_balance]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Prague-blockchain_test_engine-slice_bytes_False]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Prague-blockchain_test_engine-slice_bytes_True]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_amount_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_amount_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_index_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_index_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_pubkey_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_pubkey_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_signature_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_signature_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_withdrawal_credentials_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_withdrawal_credentials_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Osaka-blockchain_test_engine-slice_bytes_False]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Osaka-blockchain_test_engine-slice_bytes_True]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Amsterdam-blockchain_test_engine-log_argument_index_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Amsterdam-blockchain_test_engine-log_argument_index_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Amsterdam-blockchain_test_engine-slice_bytes_True]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Amsterdam-blockchain_test_engine-slice_bytes_False]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Amsterdam-blockchain_test_engine-log_argument_pubkey_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Amsterdam-blockchain_test_engine-log_argument_pubkey_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Amsterdam-blockchain_test_engine-log_argument_withdrawal_credentials_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Amsterdam-blockchain_test_engine-log_argument_withdrawal_credentials_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Amsterdam-blockchain_test_engine-log_argument_amount_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Amsterdam-blockchain_test_engine-log_argument_amount_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Amsterdam-blockchain_test_engine-log_argument_signature_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Amsterdam-blockchain_test_engine-log_argument_signature_offset-value_zero]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
@@ -102,6 +146,20 @@ 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
|
||||
|
||||
# Blob limit tests:
|
||||
#
|
||||
@@ -196,3 +254,17 @@ 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
|
||||
|
||||
8
.github/scripts/hive/run_simulator.sh
vendored
8
.github/scripts/hive/run_simulator.sh
vendored
@@ -13,7 +13,13 @@ if [[ "${sim}" == *"eels"* ]]; then
|
||||
fi
|
||||
|
||||
run_hive() {
|
||||
hive --sim "${sim}" --sim.limit "${limit}" --sim.parallelism "${parallelism}" --client reth 2>&1 | tee /tmp/log || true
|
||||
hive \
|
||||
--sim "${sim}" \
|
||||
--sim.limit "${limit}" \
|
||||
--sim.limit.exact=false \
|
||||
--sim.parallelism "${parallelism}" \
|
||||
--client reth \
|
||||
2>&1 | tee /tmp/log || true
|
||||
}
|
||||
|
||||
check_log() {
|
||||
|
||||
39
.github/workflows/bench-scheduled.yml
vendored
39
.github/workflows/bench-scheduled.yml
vendored
@@ -281,11 +281,16 @@ jobs:
|
||||
- name: Check if snapshot needs update
|
||||
id: snapshot-check
|
||||
run: |
|
||||
if .github/scripts/bench-reth-snapshot.sh --check; then
|
||||
echo "needed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "needed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
set +e
|
||||
.github/scripts/bench-reth-snapshot.sh --check
|
||||
rc=$?
|
||||
set -e
|
||||
case "$rc" in
|
||||
0) echo "needed=false" >> "$GITHUB_OUTPUT" ;;
|
||||
10) echo "needed=true" >> "$GITHUB_OUTPUT" ;;
|
||||
*) echo "::error::Snapshot check failed (exit $rc)"
|
||||
exit "$rc" ;;
|
||||
esac
|
||||
|
||||
- name: Prepare source dirs
|
||||
run: |
|
||||
@@ -303,12 +308,11 @@ jobs:
|
||||
fi
|
||||
git -C ../reth-feature checkout "$FEATURE_REF"
|
||||
|
||||
- name: Build binaries and download snapshot in parallel
|
||||
- name: Build binaries
|
||||
id: build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BENCH_REPO: ${{ github.repository }}
|
||||
SNAPSHOT_NEEDED: ${{ steps.snapshot-check.outputs.needed }}
|
||||
run: |
|
||||
BASELINE_DIR="$(cd ../reth-baseline && pwd)"
|
||||
FEATURE_DIR="$(cd ../reth-feature && pwd)"
|
||||
@@ -318,21 +322,23 @@ jobs:
|
||||
.github/scripts/bench-reth-build.sh feature "${FEATURE_DIR}" "$FEATURE_REF" &
|
||||
PID_FEATURE=$!
|
||||
|
||||
PID_SNAPSHOT=
|
||||
if [ "$SNAPSHOT_NEEDED" = "true" ]; then
|
||||
.github/scripts/bench-reth-snapshot.sh &
|
||||
PID_SNAPSHOT=$!
|
||||
fi
|
||||
|
||||
FAIL=0
|
||||
wait $PID_BASELINE || FAIL=1
|
||||
wait $PID_FEATURE || FAIL=1
|
||||
[ -n "$PID_SNAPSHOT" ] && { wait $PID_SNAPSHOT || FAIL=1; }
|
||||
if [ $FAIL -ne 0 ]; then
|
||||
echo "::error::One or more parallel tasks failed (builds / snapshot download)"
|
||||
echo "::error::One or more build tasks failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Download snapshot
|
||||
id: snapshot-download
|
||||
if: steps.snapshot-check.outputs.needed == 'true'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BENCH_REPO: ${{ github.repository }}
|
||||
BENCH_RETH_BINARY: ${{ github.workspace }}/../reth-feature/target/profiling/reth
|
||||
run: .github/scripts/bench-reth-snapshot.sh
|
||||
|
||||
# System tuning for reproducible benchmarks
|
||||
- name: System setup
|
||||
run: |
|
||||
@@ -793,7 +799,8 @@ jobs:
|
||||
if (!token || !channel) return;
|
||||
|
||||
const steps_status = [
|
||||
['building binaries${{ steps.snapshot-check.outputs.needed == 'true' && ' & downloading snapshot' || '' }}', '${{ steps.build.outcome }}'],
|
||||
['building binaries', '${{ steps.build.outcome }}'],
|
||||
['downloading snapshot', '${{ steps.snapshot-download.outcome }}'],
|
||||
['running baseline benchmark (1/2)', '${{ steps.run-baseline-1.outcome }}'],
|
||||
['running feature benchmark (1/2)', '${{ steps.run-feature-1.outcome }}'],
|
||||
['running feature benchmark (2/2)', '${{ steps.run-feature-2.outcome }}'],
|
||||
|
||||
57
.github/workflows/bench.yml
vendored
57
.github/workflows/bench.yml
vendored
@@ -79,8 +79,6 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
BASELINE: base
|
||||
SEED: reth
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
BENCH_RUNNERS: 2
|
||||
|
||||
@@ -728,11 +726,16 @@ jobs:
|
||||
- name: Check if snapshot needs update
|
||||
id: snapshot-check
|
||||
run: |
|
||||
if .github/scripts/bench-reth-snapshot.sh --check; then
|
||||
echo "needed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "needed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
set +e
|
||||
.github/scripts/bench-reth-snapshot.sh --check
|
||||
rc=$?
|
||||
set -e
|
||||
case "$rc" in
|
||||
0) echo "needed=false" >> "$GITHUB_OUTPUT" ;;
|
||||
10) echo "needed=true" >> "$GITHUB_OUTPUT" ;;
|
||||
*) echo "::error::Snapshot check failed (exit $rc)"
|
||||
exit "$rc" ;;
|
||||
esac
|
||||
|
||||
- name: Update status (snapshot needed)
|
||||
if: env.BENCH_COMMENT_ID && steps.snapshot-check.outputs.needed == 'true'
|
||||
@@ -741,7 +744,7 @@ jobs:
|
||||
github-token: ${{ secrets.DEREK_PAT }}
|
||||
script: |
|
||||
const s = require('./.github/scripts/bench-update-status.js');
|
||||
await s({github, context, status: 'Building binaries & downloading snapshot...'});
|
||||
await s({github, context, status: 'Building binaries (snapshot update pending)...'});
|
||||
|
||||
- name: Prepare source dirs
|
||||
run: |
|
||||
@@ -761,12 +764,11 @@ jobs:
|
||||
fi
|
||||
git -C ../reth-feature checkout "$FEATURE_REF"
|
||||
|
||||
- name: Build binaries and download snapshot in parallel
|
||||
- name: Build binaries
|
||||
id: build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BENCH_REPO: ${{ github.repository }}
|
||||
SNAPSHOT_NEEDED: ${{ steps.snapshot-check.outputs.needed }}
|
||||
run: |
|
||||
BASELINE_DIR="$(cd ../reth-baseline && pwd)"
|
||||
FEATURE_DIR="$(cd ../reth-feature && pwd)"
|
||||
@@ -776,21 +778,23 @@ jobs:
|
||||
.github/scripts/bench-reth-build.sh feature "${FEATURE_DIR}" "${{ steps.refs.outputs.feature-ref }}" &
|
||||
PID_FEATURE=$!
|
||||
|
||||
PID_SNAPSHOT=
|
||||
if [ "$SNAPSHOT_NEEDED" = "true" ]; then
|
||||
.github/scripts/bench-reth-snapshot.sh &
|
||||
PID_SNAPSHOT=$!
|
||||
fi
|
||||
|
||||
FAIL=0
|
||||
wait $PID_BASELINE || FAIL=1
|
||||
wait $PID_FEATURE || FAIL=1
|
||||
[ -n "$PID_SNAPSHOT" ] && { wait $PID_SNAPSHOT || FAIL=1; }
|
||||
if [ $FAIL -ne 0 ]; then
|
||||
echo "::error::One or more parallel tasks failed (builds / snapshot download)"
|
||||
echo "::error::One or more build tasks failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Download snapshot
|
||||
id: snapshot-download
|
||||
if: steps.snapshot-check.outputs.needed == 'true'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BENCH_REPO: ${{ github.repository }}
|
||||
BENCH_RETH_BINARY: ${{ github.workspace }}/../reth-feature/target/profiling/reth
|
||||
run: .github/scripts/bench-reth-snapshot.sh
|
||||
|
||||
# System tuning for reproducible benchmarks
|
||||
- name: System setup
|
||||
run: |
|
||||
@@ -847,15 +851,15 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
MC="mc --config-dir /home/ubuntu/.mc"
|
||||
BUCKET="minio/reth-snapshots/reth-1-minimal-nightly-previous-big-blocks.tar.zst"
|
||||
BUCKET="minio/reth-snapshots/reth-1-minimal-stable-big-blocks.tar.zst"
|
||||
BIG_BLOCKS_DIR="${BENCH_WORK_DIR}/big-blocks"
|
||||
rm -rf "$BIG_BLOCKS_DIR"; mkdir -p "$BIG_BLOCKS_DIR"
|
||||
echo "Downloading big blocks from $BUCKET..."
|
||||
$MC cat "$BUCKET" | pzstd -d -p 6 | tar -xf - -C "$BIG_BLOCKS_DIR"
|
||||
echo "Big blocks downloaded to $BIG_BLOCKS_DIR"
|
||||
# Verify expected directory structure
|
||||
if [ ! -d "$BIG_BLOCKS_DIR/gas-ramp-dir" ] || [ ! -d "$BIG_BLOCKS_DIR/payloads" ]; then
|
||||
echo "::error::Big blocks archive missing expected gas-ramp-dir/ or payloads/ directories"
|
||||
if [ ! -d "$BIG_BLOCKS_DIR/payloads" ]; then
|
||||
echo "::error::Big blocks archive missing expected payloads/ directory"
|
||||
ls -laR "$BIG_BLOCKS_DIR"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1067,11 +1071,6 @@ jobs:
|
||||
fi
|
||||
if [ "${BENCH_BIG_BLOCKS:-false}" = "true" ]; then
|
||||
SUMMARY_ARGS="$SUMMARY_ARGS --big-blocks"
|
||||
# Read gas ramp blocks count from first baseline run (same for all runs)
|
||||
GAS_RAMP_FILE="$BENCH_WORK_DIR/baseline-1/gas_ramp_blocks.txt"
|
||||
if [ -f "$GAS_RAMP_FILE" ]; then
|
||||
SUMMARY_ARGS="$SUMMARY_ARGS --gas-ramp-blocks $(cat "$GAS_RAMP_FILE" | tr -d '[:space:]')"
|
||||
fi
|
||||
fi
|
||||
GRAFANA_URL='${{ steps.metrics.outputs.grafana-url }}'
|
||||
if [ -n "$GRAFANA_URL" ]; then
|
||||
@@ -1246,7 +1245,8 @@ jobs:
|
||||
script: |
|
||||
const abba = (process.env.BENCH_ABBA || 'true') !== 'false';
|
||||
const steps_status = [
|
||||
['building binaries${{ steps.snapshot-check.outputs.needed == 'true' && ' & downloading snapshot' || '' }}', '${{ steps.build.outcome }}'],
|
||||
['building binaries', '${{ steps.build.outcome }}'],
|
||||
['downloading snapshot', '${{ steps.snapshot-download.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 }}']] : []),
|
||||
@@ -1281,7 +1281,8 @@ jobs:
|
||||
script: |
|
||||
const abba = (process.env.BENCH_ABBA || 'true') !== 'false';
|
||||
const steps_status = [
|
||||
['building binaries${{ steps.snapshot-check.outputs.needed == 'true' && ' & downloading snapshot' || '' }}', '${{ steps.build.outcome }}'],
|
||||
['building binaries', '${{ steps.build.outcome }}'],
|
||||
['downloading snapshot', '${{ steps.snapshot-download.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 }}']] : []),
|
||||
|
||||
2
.github/workflows/docker-test.yml
vendored
2
.github/workflows/docker-test.yml
vendored
@@ -79,4 +79,4 @@ jobs:
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ inputs.artifact_name }}
|
||||
path: ./artifacts
|
||||
path: ./artifacts
|
||||
71
.github/workflows/hive.yml
vendored
71
.github/workflows/hive.yml
vendored
@@ -5,7 +5,10 @@ name: hive
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
- cron: "0 */6 * * *"
|
||||
pull_request:
|
||||
branches:
|
||||
- "**"
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -15,23 +18,26 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-reth:
|
||||
uses: ./.github/workflows/docker-test.yml
|
||||
prepare-reth-stable:
|
||||
uses: ./.github/workflows/prepare-reth.yml
|
||||
with:
|
||||
hive_target: hive
|
||||
artifact_name: "reth"
|
||||
secrets: inherit
|
||||
image_tag: ghcr.io/paradigmxyz/reth:latest
|
||||
binary_name: reth
|
||||
cargo_features: "asm-keccak"
|
||||
artifact_name: "reth-stable"
|
||||
|
||||
prepare-hive:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
timeout-minutes: 45
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-4' || 'ubuntu-latest' }}
|
||||
runs-on:
|
||||
group: Reth
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Checkout hive tests
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ethereum/hive
|
||||
repository: Soubhik-10/hive # rm later and use ethereum/hive
|
||||
ref: master
|
||||
path: hivetests
|
||||
|
||||
- name: Get hive commit hash
|
||||
@@ -77,6 +83,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
storage: [stable]
|
||||
# ethereum/rpc to be deprecated:
|
||||
# https://github.com/ethereum/hive/pull/1117
|
||||
scenario:
|
||||
@@ -117,26 +124,28 @@ jobs:
|
||||
- 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_
|
||||
# - 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
|
||||
@@ -157,6 +166,8 @@ jobs:
|
||||
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
|
||||
@@ -176,9 +187,9 @@ jobs:
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
limit: .*tests/paris.*
|
||||
needs:
|
||||
- build-reth
|
||||
- prepare-reth-stable
|
||||
- prepare-hive
|
||||
name: ${{ matrix.scenario.sim }}${{ matrix.scenario.limit && format(' - {0}', matrix.scenario.limit) }}
|
||||
name: ${{ matrix.storage }} / ${{ 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' }}
|
||||
permissions:
|
||||
@@ -197,7 +208,7 @@ jobs:
|
||||
- name: Download reth image
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: reth
|
||||
name: reth-${{ matrix.storage }}
|
||||
path: /tmp
|
||||
|
||||
- name: Load Docker images
|
||||
@@ -211,7 +222,7 @@ jobs:
|
||||
- name: Checkout hive tests
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ethereum/hive
|
||||
repository: Soubhik-10/hive
|
||||
ref: master
|
||||
path: hivetests
|
||||
|
||||
|
||||
61
.github/workflows/prepare-reth.yml
vendored
Normal file
61
.github/workflows/prepare-reth.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Prepare Reth Image
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
image_tag:
|
||||
required: true
|
||||
type: string
|
||||
description: "Docker image tag to use"
|
||||
binary_name:
|
||||
required: false
|
||||
type: string
|
||||
default: "reth"
|
||||
description: "Binary name to build (reth or op-reth)"
|
||||
cargo_features:
|
||||
required: false
|
||||
type: string
|
||||
default: "asm-keccak"
|
||||
description: "Cargo features to enable"
|
||||
cargo_package:
|
||||
required: false
|
||||
type: string
|
||||
description: "Optional cargo package path"
|
||||
artifact_name:
|
||||
required: false
|
||||
type: string
|
||||
default: "artifacts"
|
||||
description: "Name for the uploaded artifact"
|
||||
|
||||
jobs:
|
||||
prepare-reth:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
timeout-minutes: 45
|
||||
runs-on: depot-ubuntu-latest-4
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- run: mkdir artifacts
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and export reth image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: .github/scripts/hive/Dockerfile
|
||||
tags: ${{ inputs.image_tag }}
|
||||
outputs: type=docker,dest=./artifacts/reth_image.tar
|
||||
build-args: |
|
||||
CARGO_BIN=${{ inputs.binary_name }}
|
||||
MANIFEST_PATH=${{ inputs.cargo_package }}
|
||||
FEATURES=${{ inputs.cargo_features }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Upload reth image
|
||||
id: upload
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ inputs.artifact_name }}
|
||||
path: ./artifacts
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -227,14 +227,14 @@ jobs:
|
||||
|
||||
- name: Upload artifact
|
||||
if: ${{ github.event.inputs.dry_run != 'true' }}
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: reth-${{ needs.extract-version.outputs.VERSION }}-x86_64-unknown-linux-gnu.tar.gz
|
||||
path: reth-${{ needs.extract-version.outputs.VERSION }}-x86_64-unknown-linux-gnu.tar.gz
|
||||
|
||||
- name: Upload signature
|
||||
if: ${{ github.event.inputs.dry_run != 'true' }}
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: reth-${{ needs.extract-version.outputs.VERSION }}-x86_64-unknown-linux-gnu.tar.gz.asc
|
||||
path: reth-${{ needs.extract-version.outputs.VERSION }}-x86_64-unknown-linux-gnu.tar.gz.asc
|
||||
|
||||
400
Cargo.lock
generated
400
Cargo.lock
generated
@@ -106,9 +106,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "alloy-chains"
|
||||
version = "0.2.31"
|
||||
version = "0.2.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d9d22005bf31b018f31ef9ecadb5d2c39cf4f6acc8db0456f72c815f3d7f757"
|
||||
checksum = "9247f0a399ef71aeb68f497b2b8fb348014f742b50d3b83b1e00dfe1b7d64b3d"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rlp",
|
||||
@@ -122,8 +122,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-consensus"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c0dc44157867da82c469c13186015b86abef209bf0e41625e4b68bac61d728"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-eips",
|
||||
"alloy-primitives",
|
||||
@@ -150,8 +149,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-consensus-any"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba4cdb42df3871cd6b346d6a938ec2ba69a9a0f49d1f82714bc5c48349268434"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -165,8 +163,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-contract"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca63b7125a981415898ffe2a2a696c83696c9c6bdb1671c8a912946bbd8e49e7"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-dyn-abi",
|
||||
@@ -198,7 +195,7 @@ dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"winnow",
|
||||
"winnow 0.7.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -263,8 +260,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-eips"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9f7ef09f21bd1e9cb8a686f168cb4a206646804567f0889eadb8dcc4c9288c8"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-eip2124",
|
||||
"alloy-eip2930",
|
||||
@@ -290,17 +286,20 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-evm"
|
||||
version = "0.29.2"
|
||||
source = "git+https://github.com/alloy-rs/evm?rev=b0eb7e6#b0eb7e617f964f7090c504f21a5977cc440117f7"
|
||||
source = "git+https://github.com/alloy-rs/evm?branch=bal-devnet2#5ca2989a1fc18079ca29387c61c5e5b6d6150030"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
"alloy-hardforks 0.4.7",
|
||||
"alloy-op-hardforks",
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-types-engine",
|
||||
"alloy-rpc-types-eth",
|
||||
"alloy-sol-types",
|
||||
"auto_impl",
|
||||
"derive_more",
|
||||
"op-alloy",
|
||||
"op-revm",
|
||||
"revm",
|
||||
"thiserror 2.0.18",
|
||||
"tracing",
|
||||
@@ -309,8 +308,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-genesis"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c9cf3b99f46615fbf7dc1add0c96553abb7bf88fc9ec70dfbe7ad0b47ba7fe8"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-eips",
|
||||
"alloy-primitives",
|
||||
@@ -363,8 +361,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-json-rpc"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff42cd777eea61f370c0b10f2648a1c81e0b783066cd7269228aa993afd487f7"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-sol-types",
|
||||
@@ -378,8 +375,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-network"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cbca04f9b410fdc51aaaf88433cbac761213905a65fe832058bcf6690585762"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-consensus-any",
|
||||
@@ -404,8 +400,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-network-primitives"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42d6d15e069a8b11f56bef2eccbad2a873c6dd4d4c81d04dda29710f5ea52f04"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -417,8 +412,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-node-bindings"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "091dc8117d84de3a9ac7ec97f2c4d83987e24d485b478d26aa1ec455d7d52f7d"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-genesis",
|
||||
"alloy-hardforks 0.2.13",
|
||||
@@ -436,6 +430,18 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alloy-op-hardforks"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6472c610150c4c4c15be9e1b964c9b78068f933bda25fb9cdf09b9ac2bb66f36"
|
||||
dependencies = [
|
||||
"alloy-chains",
|
||||
"alloy-hardforks 0.4.7",
|
||||
"alloy-primitives",
|
||||
"auto_impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alloy-primitives"
|
||||
version = "1.5.7"
|
||||
@@ -470,8 +476,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-provider"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d181c8cc7cf4805d7e589bf4074d56d55064fa1a979f005a45a62b047616d870"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-chains",
|
||||
"alloy-consensus",
|
||||
@@ -515,8 +520,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-pubsub"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8bd82953194dec221aa4cbbbb0b1e2df46066fe9d0333ac25b43a311e122d13"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-json-rpc",
|
||||
"alloy-primitives",
|
||||
@@ -559,8 +563,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-rpc-client"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2792758a93ae32a32e9047c843d536e1448044f78422d71bf7d7c05149e103f"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-json-rpc",
|
||||
"alloy-primitives",
|
||||
@@ -585,8 +588,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-rpc-types"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bdcbf9dfd5eea8bfeb078b1d906da8cd3a39c4d4dbe7a628025648e323611f6"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-types-engine",
|
||||
@@ -598,8 +600,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-admin"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42325c117af3a9e49013f881c1474168db57978e02085fc9853a1c89e0562740"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-genesis",
|
||||
"alloy-primitives",
|
||||
@@ -610,8 +611,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-anvil"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a3100b76987c1b1dc81f3abe592b7edc29e92b1242067a69d65e0030b35cf9"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-types-eth",
|
||||
@@ -622,8 +622,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-any"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd720b63f82b457610f2eaaf1f32edf44efffe03ae25d537632e7d23e7929e1a"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-consensus-any",
|
||||
"alloy-rpc-types-eth",
|
||||
@@ -633,8 +632,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-beacon"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a22e13215866f5dfd5d3278f4c41f1fad9410dc68ce39022f58593c873c26f8"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-eips",
|
||||
"alloy-primitives",
|
||||
@@ -653,8 +651,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-debug"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1b21e1ad18ff1b31ff1030e046462ab8168cf8894e6778cd805c8bdfe2bd649"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"derive_more",
|
||||
@@ -665,8 +662,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-engine"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4ac61f03f1edabccde1c687b5b25fff28f183afee64eaa2e767def3929e4457"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -686,8 +682,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-eth"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b2dc411f13092f237d2bf6918caf80977fc2f51485f9b90cb2a2f956912c8c9"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-consensus-any",
|
||||
@@ -708,8 +703,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-mev"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe85bf3be739126aa593dca9fb3ab13ca93fa7873e6f2247be64d7f2cb15f34a"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -723,8 +717,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-trace"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad79f1e27e161943b5a4f99fe5534ef0849876214be411e0032c12f38e94daa"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-types-eth",
|
||||
@@ -737,8 +730,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-txpool"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d459f902a2313737bc66d18ed094c25d2aeb268b74d98c26bbbda2aa44182ab0"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-types-eth",
|
||||
@@ -749,8 +741,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-serde"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2ce1e0dbf7720eee747700e300c99aac01b1a95bb93f493a01e78ee28bb1a37"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"arbitrary",
|
||||
@@ -761,8 +752,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-signer"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2425c6f314522c78e8198979c8cbf6769362be4da381d4152ea8eefce383535d"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"async-trait",
|
||||
@@ -776,8 +766,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-signer-local"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3ecb71ee53d8d9c3fa7bac17542c8116ebc7a9726c91b1bf333ec3d04f5a789"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-network",
|
||||
@@ -847,7 +836,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6df77fea9d6a2a75c0ef8d2acbdfd92286cc599983d3175ccdc170d3433d249"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"winnow",
|
||||
"winnow 0.7.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -865,8 +854,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-transport"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa186e560d523d196580c48bf00f1bf62e63041f28ecf276acc22f8b27bb9f53"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-json-rpc",
|
||||
"auto_impl",
|
||||
@@ -888,8 +876,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-transport-http"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa501ad58dd20acddbfebc65b52e60f05ebf97c52fa40d1b35e91f5e2da0ad0e"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-json-rpc",
|
||||
"alloy-transport",
|
||||
@@ -904,8 +891,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-transport-ipc"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2ef85688e5ac2da72afc804e0a1f153a1f309f05a864b1998bbbed7804dbaab"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-json-rpc",
|
||||
"alloy-pubsub",
|
||||
@@ -924,8 +910,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-transport-ws"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9f00445db69d63298e2b00a0ea1d859f00e6424a3144ffc5eba9c31da995e16"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"alloy-pubsub",
|
||||
"alloy-transport",
|
||||
@@ -961,8 +946,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-tx-macros"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fa0c53e8c1e1ef4d01066b01c737fb62fc9397ab52c6e7bb5669f97d281b9bc"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=bal-devnet2#1fd12c4107dcf3ad3fbbc404004a710bf91db3e6"
|
||||
dependencies = [
|
||||
"darling 0.21.3",
|
||||
"proc-macro2",
|
||||
@@ -1021,7 +1005,7 @@ version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1032,7 +1016,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1715,15 +1699,6 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blst"
|
||||
version = "0.3.16"
|
||||
@@ -1879,19 +1854,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "borsh"
|
||||
version = "1.6.0"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f"
|
||||
checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a"
|
||||
dependencies = [
|
||||
"borsh-derive",
|
||||
"bytes",
|
||||
"cfg_aliases",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "borsh-derive"
|
||||
version = "1.6.0"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c"
|
||||
checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro-crate",
|
||||
@@ -2759,17 +2735,6 @@ dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162"
|
||||
dependencies = [
|
||||
"dispatch2",
|
||||
"nix",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "4.1.3"
|
||||
@@ -3162,18 +3127,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dispatch2"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2",
|
||||
"libc",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
@@ -3187,9 +3140,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "doctest-file"
|
||||
version = "1.0.0"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562"
|
||||
checksum = "c2db04e74f0a9a93103b50e90b96024c9b2bdca8bce6a632ec71b88736d3d359"
|
||||
|
||||
[[package]]
|
||||
name = "document-features"
|
||||
@@ -3456,7 +3409,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3769,19 +3722,6 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "example-migrate-trie-to-packed"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"eyre",
|
||||
"reth-db",
|
||||
"reth-db-api",
|
||||
"reth-trie-common",
|
||||
"reth-trie-db",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "example-network"
|
||||
version = "0.0.0"
|
||||
@@ -4270,8 +4210,8 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"rustversion",
|
||||
"windows-link 0.1.3",
|
||||
"windows-result 0.3.4",
|
||||
"windows-link 0.2.1",
|
||||
"windows-result 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4827,7 +4767,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core 0.61.2",
|
||||
"windows-core 0.62.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5099,9 +5039,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "instability"
|
||||
version = "0.3.11"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d"
|
||||
checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971"
|
||||
dependencies = [
|
||||
"darling 0.23.0",
|
||||
"indoc",
|
||||
@@ -5179,7 +5119,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6134,7 +6074,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6230,9 +6170,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.5"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c"
|
||||
checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
"rustversion",
|
||||
@@ -6240,9 +6180,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.7.5"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7"
|
||||
checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
@@ -6274,15 +6214,6 @@ dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
|
||||
dependencies = [
|
||||
"objc2-encode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-foundation"
|
||||
version = "0.3.2"
|
||||
@@ -6292,12 +6223,6 @@ dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-encode"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-kit"
|
||||
version = "0.3.2"
|
||||
@@ -6340,10 +6265,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||
|
||||
[[package]]
|
||||
name = "op-alloy-consensus"
|
||||
version = "0.24.0"
|
||||
name = "op-alloy"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fadcb964b0aa645d12e952d470c7585d23286d8bcf1ac41589410b6724216b68"
|
||||
checksum = "e9b8fee21003dd4f076563de9b9d26f8c97840157ef78593cd7f262c5ca99848"
|
||||
dependencies = [
|
||||
"op-alloy-consensus",
|
||||
"op-alloy-network",
|
||||
"op-alloy-provider",
|
||||
"op-alloy-rpc-types",
|
||||
"op-alloy-rpc-types-engine",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "op-alloy-consensus"
|
||||
version = "0.23.1"
|
||||
source = "git+https://github.com/alloy-rs/op-alloy?branch=bal-devnet2#d3c6e661ce58697327f53508f9514a75ca68eb60"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -6360,10 +6297,39 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "op-alloy-rpc-types"
|
||||
version = "0.24.0"
|
||||
name = "op-alloy-network"
|
||||
version = "0.23.1"
|
||||
source = "git+https://github.com/alloy-rs/op-alloy?branch=bal-devnet2#d3c6e661ce58697327f53508f9514a75ca68eb60"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-network",
|
||||
"alloy-primitives",
|
||||
"alloy-provider",
|
||||
"alloy-rpc-types-eth",
|
||||
"alloy-signer",
|
||||
"op-alloy-consensus",
|
||||
"op-alloy-rpc-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "op-alloy-provider"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be9d16ec9f7810e0623a37edc293d1c05fe9c58a5647f6973fdd93e0b4795015"
|
||||
checksum = "6753d90efbaa8ea8bcb89c1737408ca85fa60d7adb875049d3f382c063666f86"
|
||||
dependencies = [
|
||||
"alloy-network",
|
||||
"alloy-primitives",
|
||||
"alloy-provider",
|
||||
"alloy-rpc-types-engine",
|
||||
"alloy-transport",
|
||||
"async-trait",
|
||||
"op-alloy-rpc-types-engine",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "op-alloy-rpc-types"
|
||||
version = "0.23.1"
|
||||
source = "git+https://github.com/alloy-rs/op-alloy?branch=bal-devnet2#d3c6e661ce58697327f53508f9514a75ca68eb60"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -6380,9 +6346,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "op-alloy-rpc-types-engine"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c0ac5c5ddcbffadcd097d278c717d34849bcc629f6e4f8514eb000ec862def8"
|
||||
version = "0.23.1"
|
||||
source = "git+https://github.com/alloy-rs/op-alloy?branch=bal-devnet2#d3c6e661ce58697327f53508f9514a75ca68eb60"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -6400,6 +6365,16 @@ dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "op-revm"
|
||||
version = "17.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fa5a6914398c9b178422efc1edd1d2ab33dad923#fa5a6914398c9b178422efc1edd1d2ab33dad923"
|
||||
dependencies = [
|
||||
"auto_impl",
|
||||
"revm",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.1"
|
||||
@@ -7581,10 +7556,8 @@ dependencies = [
|
||||
name = "reth-bench"
|
||||
version = "1.11.3"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
"alloy-json-rpc",
|
||||
"alloy-network",
|
||||
"alloy-primitives",
|
||||
"alloy-provider",
|
||||
"alloy-pubsub",
|
||||
@@ -7604,11 +7577,9 @@ dependencies = [
|
||||
"op-alloy-consensus",
|
||||
"op-alloy-rpc-types-engine",
|
||||
"reqwest",
|
||||
"reth-chainspec",
|
||||
"reth-cli-runner",
|
||||
"reth-cli-util",
|
||||
"reth-engine-primitives",
|
||||
"reth-ethereum-primitives",
|
||||
"reth-fs-util",
|
||||
"reth-node-api",
|
||||
"reth-node-core",
|
||||
@@ -7624,33 +7595,6 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reth-bench-compare"
|
||||
version = "1.11.3"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-provider",
|
||||
"alloy-rpc-client",
|
||||
"alloy-rpc-types-eth",
|
||||
"alloy-transport-ws",
|
||||
"chrono",
|
||||
"clap",
|
||||
"csv",
|
||||
"ctrlc",
|
||||
"eyre",
|
||||
"nix",
|
||||
"reth-chainspec",
|
||||
"reth-cli-runner",
|
||||
"reth-cli-util",
|
||||
"reth-node-core",
|
||||
"reth-tracing",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shlex",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reth-chain-state"
|
||||
version = "1.11.3"
|
||||
@@ -7891,6 +7835,7 @@ name = "reth-consensus"
|
||||
version = "1.11.3"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eip7928",
|
||||
"alloy-primitives",
|
||||
"auto_impl",
|
||||
"reth-execution-types",
|
||||
@@ -8314,6 +8259,7 @@ dependencies = [
|
||||
"eyre",
|
||||
"fixed-cache",
|
||||
"futures",
|
||||
"itertools 0.14.0",
|
||||
"metrics",
|
||||
"metrics-util",
|
||||
"moka",
|
||||
@@ -9847,7 +9793,6 @@ dependencies = [
|
||||
"reth-node-core",
|
||||
"reth-node-ethereum",
|
||||
"reth-payload-builder",
|
||||
"reth-payload-primitives",
|
||||
"reth-primitives-traits",
|
||||
"reth-provider",
|
||||
"reth-rpc",
|
||||
@@ -10592,7 +10537,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "revm"
|
||||
version = "36.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=712dac7a#712dac7a3461f5f80682627ee8fc9032c337c51b"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fa5a6914398c9b178422efc1edd1d2ab33dad923#fa5a6914398c9b178422efc1edd1d2ab33dad923"
|
||||
dependencies = [
|
||||
"revm-bytecode",
|
||||
"revm-context",
|
||||
@@ -10610,7 +10555,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "revm-bytecode"
|
||||
version = "9.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=712dac7a#712dac7a3461f5f80682627ee8fc9032c337c51b"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fa5a6914398c9b178422efc1edd1d2ab33dad923#fa5a6914398c9b178422efc1edd1d2ab33dad923"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"phf",
|
||||
@@ -10621,7 +10566,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "revm-context"
|
||||
version = "15.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=712dac7a#712dac7a3461f5f80682627ee8fc9032c337c51b"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fa5a6914398c9b178422efc1edd1d2ab33dad923#fa5a6914398c9b178422efc1edd1d2ab33dad923"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"cfg-if",
|
||||
@@ -10637,7 +10582,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "revm-context-interface"
|
||||
version = "16.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=712dac7a#712dac7a3461f5f80682627ee8fc9032c337c51b"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fa5a6914398c9b178422efc1edd1d2ab33dad923#fa5a6914398c9b178422efc1edd1d2ab33dad923"
|
||||
dependencies = [
|
||||
"alloy-eip2930",
|
||||
"alloy-eip7702",
|
||||
@@ -10652,7 +10597,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "revm-database"
|
||||
version = "12.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=712dac7a#712dac7a3461f5f80682627ee8fc9032c337c51b"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fa5a6914398c9b178422efc1edd1d2ab33dad923#fa5a6914398c9b178422efc1edd1d2ab33dad923"
|
||||
dependencies = [
|
||||
"alloy-eips",
|
||||
"revm-bytecode",
|
||||
@@ -10665,7 +10610,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "revm-database-interface"
|
||||
version = "10.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=712dac7a#712dac7a3461f5f80682627ee8fc9032c337c51b"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fa5a6914398c9b178422efc1edd1d2ab33dad923#fa5a6914398c9b178422efc1edd1d2ab33dad923"
|
||||
dependencies = [
|
||||
"auto_impl",
|
||||
"either",
|
||||
@@ -10678,7 +10623,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "revm-handler"
|
||||
version = "17.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=712dac7a#712dac7a3461f5f80682627ee8fc9032c337c51b"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fa5a6914398c9b178422efc1edd1d2ab33dad923#fa5a6914398c9b178422efc1edd1d2ab33dad923"
|
||||
dependencies = [
|
||||
"auto_impl",
|
||||
"derive-where",
|
||||
@@ -10696,7 +10641,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "revm-inspector"
|
||||
version = "17.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=712dac7a#712dac7a3461f5f80682627ee8fc9032c337c51b"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fa5a6914398c9b178422efc1edd1d2ab33dad923#fa5a6914398c9b178422efc1edd1d2ab33dad923"
|
||||
dependencies = [
|
||||
"auto_impl",
|
||||
"either",
|
||||
@@ -10713,7 +10658,8 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "revm-inspectors"
|
||||
version = "0.36.1"
|
||||
source = "git+https://github.com/paradigmxyz/revm-inspectors?rev=24becc3#24becc35973c6c1d4e1c1475fa51a83d36d50d48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9487362b728f80dd2033ef5f4d0688453435bbe7caa721fa7e3b8fa25d89242b"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-types-eth",
|
||||
@@ -10732,7 +10678,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "revm-interpreter"
|
||||
version = "34.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=712dac7a#712dac7a3461f5f80682627ee8fc9032c337c51b"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fa5a6914398c9b178422efc1edd1d2ab33dad923#fa5a6914398c9b178422efc1edd1d2ab33dad923"
|
||||
dependencies = [
|
||||
"revm-bytecode",
|
||||
"revm-context-interface",
|
||||
@@ -10744,7 +10690,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "revm-precompile"
|
||||
version = "32.1.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=712dac7a#712dac7a3461f5f80682627ee8fc9032c337c51b"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fa5a6914398c9b178422efc1edd1d2ab33dad923#fa5a6914398c9b178422efc1edd1d2ab33dad923"
|
||||
dependencies = [
|
||||
"ark-bls12-381",
|
||||
"ark-bn254",
|
||||
@@ -10758,7 +10704,6 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"k256",
|
||||
"p256",
|
||||
"revm-context-interface",
|
||||
"revm-primitives",
|
||||
"ripemd",
|
||||
"secp256k1 0.31.1",
|
||||
@@ -10768,7 +10713,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "revm-primitives"
|
||||
version = "22.1.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=712dac7a#712dac7a3461f5f80682627ee8fc9032c337c51b"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fa5a6914398c9b178422efc1edd1d2ab33dad923#fa5a6914398c9b178422efc1edd1d2ab33dad923"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"num_enum",
|
||||
@@ -10779,7 +10724,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "revm-state"
|
||||
version = "10.0.0"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=712dac7a#712dac7a3461f5f80682627ee8fc9032c337c51b"
|
||||
source = "git+https://github.com/bluealloy/revm?rev=fa5a6914398c9b178422efc1edd1d2ab33dad923#fa5a6914398c9b178422efc1edd1d2ab33dad923"
|
||||
dependencies = [
|
||||
"alloy-eip7928",
|
||||
"bitflags 2.11.0",
|
||||
@@ -11022,7 +10967,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11680,7 +11625,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11880,7 +11825,7 @@ dependencies = [
|
||||
"getrandom 0.4.2",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -12256,7 +12201,7 @@ dependencies = [
|
||||
"toml_datetime 0.7.5+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
"winnow 0.7.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -12270,39 +12215,39 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "1.0.0+spec-1.1.0"
|
||||
version = "1.0.1+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e"
|
||||
checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.25.4+spec-1.1.0"
|
||||
version = "0.25.5+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2"
|
||||
checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1"
|
||||
dependencies = [
|
||||
"indexmap 2.13.0",
|
||||
"toml_datetime 1.0.0+spec-1.1.0",
|
||||
"toml_datetime 1.0.1+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"winnow",
|
||||
"winnow 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.9+spec-1.1.0"
|
||||
version = "1.0.10+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
|
||||
checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
"winnow 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.0.6+spec-1.1.0"
|
||||
version = "1.0.7+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
|
||||
checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d"
|
||||
|
||||
[[package]]
|
||||
name = "tonic"
|
||||
@@ -12605,7 +12550,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5f7c95348f20c1c913d72157b3c6dee6ea3e30b3d19502c5a7f6d3f160dacbf"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"windows-targets 0.48.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -13184,7 +13129,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -13698,6 +13643,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.50.0"
|
||||
@@ -13883,18 +13837,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.42"
|
||||
version = "0.8.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3"
|
||||
checksum = "5c5030500cb2d66bdfbb4ebc9563be6ce7005a4b5d0f26be0c523870fe372ca6"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.42"
|
||||
version = "0.8.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f"
|
||||
checksum = "a5f86989a046a79640b9d8867c823349a139367bda96549794fcc3313ce91f4e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
153
Cargo.toml
153
Cargo.toml
@@ -10,7 +10,6 @@ exclude = [".github/"]
|
||||
[workspace]
|
||||
members = [
|
||||
"bin/reth-bench/",
|
||||
"bin/reth-bench-compare/",
|
||||
"bin/reth/",
|
||||
"crates/storage/rpc-provider/",
|
||||
"crates/chain-state/",
|
||||
@@ -138,7 +137,6 @@ members = [
|
||||
"examples/exex-subscription",
|
||||
"examples/exex-test",
|
||||
"examples/full-contract-state",
|
||||
"examples/migrate-trie-to-packed",
|
||||
"examples/manual-p2p/",
|
||||
"examples/network-txpool/",
|
||||
"examples/network/",
|
||||
@@ -324,7 +322,6 @@ reth = { path = "bin/reth" }
|
||||
reth-storage-rpc-provider = { path = "crates/storage/rpc-provider" }
|
||||
reth-basic-payload-builder = { path = "crates/payload/basic" }
|
||||
reth-bench = { path = "bin/reth-bench" }
|
||||
reth-bench-compare = { path = "bin/reth-bench-compare" }
|
||||
reth-chain-state = { path = "crates/chain-state" }
|
||||
reth-chainspec = { path = "crates/chainspec", default-features = false }
|
||||
reth-cli = { path = "crates/cli/cli" }
|
||||
@@ -399,9 +396,7 @@ reth-payload-builder-primitives = { path = "crates/payload/builder-primitives" }
|
||||
reth-payload-primitives = { path = "crates/payload/primitives" }
|
||||
reth-payload-validator = { path = "crates/payload/validator" }
|
||||
reth-payload-util = { path = "crates/payload/util" }
|
||||
reth-primitives = { path = "crates/primitives", default-features = false, features = [
|
||||
"__internal",
|
||||
] }
|
||||
reth-primitives = { path = "crates/primitives", default-features = false, features = ["__internal"] }
|
||||
reth-primitives-traits = { path = "crates/primitives-traits", default-features = false }
|
||||
reth-provider = { path = "crates/storage/provider" }
|
||||
reth-prune = { path = "crates/prune/prune" }
|
||||
@@ -446,22 +441,18 @@ revm-state = { version = "10.0.0", default-features = false }
|
||||
revm-primitives = { version = "22.1.0", default-features = false }
|
||||
revm-interpreter = { version = "34.0.0", default-features = false }
|
||||
revm-database-interface = { version = "10.0.0", default-features = false }
|
||||
revm-inspectors = "0.36.1"
|
||||
revm-inspectors = "0.36.0"
|
||||
|
||||
# eth
|
||||
alloy-dyn-abi = "1.5.6"
|
||||
alloy-primitives = { version = "1.5.6", default-features = false, features = [
|
||||
"map-foldhash",
|
||||
] }
|
||||
alloy-sol-types = { version = "1.5.6", default-features = false }
|
||||
alloy-dyn-abi = "1.5.0"
|
||||
alloy-primitives = { version = "1.5.0", default-features = false, features = ["map-foldhash"] }
|
||||
alloy-sol-types = { version = "1.5.0", default-features = false }
|
||||
|
||||
alloy-chains = { version = "0.2.5", default-features = false }
|
||||
alloy-eip2124 = { version = "0.2.0", default-features = false }
|
||||
alloy-eip7928 = { version = "0.3.0", default-features = false }
|
||||
alloy-eip7928 = { version = "0.3.3", default-features = false, features = ["rlp"] }
|
||||
alloy-evm = { version = "0.29.2", default-features = false }
|
||||
alloy-rlp = { version = "0.3.13", default-features = false, features = [
|
||||
"core-net",
|
||||
] }
|
||||
alloy-rlp = { version = "0.3.13", default-features = false, features = ["core-net"] }
|
||||
alloy-trie = { version = "0.9.4", default-features = false }
|
||||
|
||||
alloy-hardforks = "0.4.5"
|
||||
@@ -473,15 +464,10 @@ alloy-genesis = { version = "1.7.3", default-features = false }
|
||||
alloy-json-rpc = { version = "1.7.3", default-features = false }
|
||||
alloy-network = { version = "1.7.3", default-features = false }
|
||||
alloy-network-primitives = { version = "1.7.3", default-features = false }
|
||||
alloy-provider = { version = "1.7.3", features = [
|
||||
"reqwest",
|
||||
"debug-api",
|
||||
], default-features = false }
|
||||
alloy-provider = { version = "1.7.3", features = ["reqwest", "debug-api"], default-features = false }
|
||||
alloy-pubsub = { version = "1.7.3", default-features = false }
|
||||
alloy-rpc-client = { version = "1.7.3", default-features = false }
|
||||
alloy-rpc-types = { version = "1.7.3", features = [
|
||||
"eth",
|
||||
], default-features = false }
|
||||
alloy-rpc-types = { version = "1.7.3", features = ["eth"], default-features = false }
|
||||
alloy-rpc-types-admin = { version = "1.7.3", default-features = false }
|
||||
alloy-rpc-types-anvil = { version = "1.7.3", default-features = false }
|
||||
alloy-rpc-types-beacon = { version = "1.7.3", default-features = false }
|
||||
@@ -495,26 +481,25 @@ alloy-serde = { version = "1.7.3", default-features = false }
|
||||
alloy-signer = { version = "1.7.3", default-features = false }
|
||||
alloy-signer-local = { version = "1.7.3", default-features = false }
|
||||
alloy-transport = { version = "1.7.3" }
|
||||
alloy-transport-http = { version = "1.7.3", features = [
|
||||
"reqwest-rustls-tls",
|
||||
], default-features = false }
|
||||
alloy-transport-http = { version = "1.7.3", features = ["reqwest-rustls-tls"], default-features = false }
|
||||
alloy-transport-ipc = { version = "1.7.3", default-features = false }
|
||||
alloy-transport-ws = { version = "1.7.3", default-features = false }
|
||||
|
||||
# op
|
||||
op-alloy-rpc-types = { version = "0.24.0", default-features = false }
|
||||
op-alloy-rpc-types-engine = { version = "0.24.0", default-features = false }
|
||||
op-alloy-consensus = { version = "0.24.0", default-features = false }
|
||||
alloy-op-hardforks = "0.4.4"
|
||||
op-alloy-rpc-types = { version = "0.23.1", default-features = false }
|
||||
op-alloy-rpc-types-engine = { version = "0.23.1", default-features = false }
|
||||
op-alloy-network = { version = "0.23.1", default-features = false }
|
||||
op-alloy-consensus = { version = "0.23.1", default-features = false }
|
||||
op-alloy-rpc-jsonrpsee = { version = "0.23.1", default-features = false }
|
||||
op-alloy-flz = { version = "0.13.1", default-features = false }
|
||||
|
||||
# misc
|
||||
either = { version = "1.15.0", default-features = false }
|
||||
arrayvec = { version = "0.7.6", default-features = false }
|
||||
aquamarine = "0.6"
|
||||
auto_impl = "1"
|
||||
backon = { version = "1.2", default-features = false, features = [
|
||||
"std-blocking-sleep",
|
||||
"tokio-sleep",
|
||||
] }
|
||||
backon = { version = "1.2", default-features = false, features = ["std-blocking-sleep", "tokio-sleep"] }
|
||||
bincode = "1.3"
|
||||
bitflags = "2.4"
|
||||
boyer-moore-magiclen = "0.2.16"
|
||||
@@ -538,25 +523,21 @@ linked_hash_set = "0.1"
|
||||
libc = "0.2"
|
||||
lz4 = "1.28.1"
|
||||
modular-bitfield = "0.13.1"
|
||||
notify = { version = "8.0.0", default-features = false, features = [
|
||||
"macos_fsevent",
|
||||
] }
|
||||
notify = { version = "8.0.0", default-features = false, features = ["macos_fsevent"] }
|
||||
nybbles = { version = "0.4.8", default-features = false }
|
||||
once_cell = { version = "1.19", default-features = false, features = [
|
||||
"critical-section",
|
||||
] }
|
||||
once_cell = { version = "1.19", default-features = false, features = ["critical-section"] }
|
||||
parking_lot = "0.12"
|
||||
quanta = "0.12"
|
||||
paste = "1.0"
|
||||
rand = "0.9"
|
||||
rayon = "1.7"
|
||||
thread-priority = "3.0.0"
|
||||
rustc-hash = { version = "2.0", default-features = false }
|
||||
schnellru = "0.2"
|
||||
serde = { version = "1.0", default-features = false }
|
||||
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
|
||||
serde_with = { version = "3", default-features = false, features = ["macros"] }
|
||||
sha2 = { version = "0.10", default-features = false }
|
||||
shellexpand = "3.0.0"
|
||||
shlex = "1.3"
|
||||
slotmap = "1"
|
||||
smallvec = "1"
|
||||
@@ -564,10 +545,9 @@ strum = { version = "0.27", default-features = false }
|
||||
strum_macros = "0.27"
|
||||
syn = "2.0"
|
||||
thiserror = { version = "2.0.0", default-features = false }
|
||||
thread-priority = "3.0.0"
|
||||
tar = "0.4.44"
|
||||
tracing = { version = "0.1.0", default-features = false, features = [
|
||||
"attributes",
|
||||
] }
|
||||
tracing = { version = "0.1.0", default-features = false, features = ["attributes"] }
|
||||
tracing-appender = "0.2"
|
||||
url = { version = "2.3", default-features = false }
|
||||
zstd = "0.13"
|
||||
@@ -605,11 +585,7 @@ futures-util = { version = "0.3", default-features = false }
|
||||
hyper = "1.3"
|
||||
hyper-util = "0.1.5"
|
||||
pin-project = "1.0.12"
|
||||
reqwest = { version = "0.12", default-features = false, features = [
|
||||
"rustls-tls",
|
||||
"rustls-tls-native-roots",
|
||||
"stream",
|
||||
] }
|
||||
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "rustls-tls-native-roots", "stream"] }
|
||||
tracing-futures = "0.2"
|
||||
tower = "0.5"
|
||||
tower-http = "0.6"
|
||||
@@ -634,10 +610,7 @@ proptest-arbitrary-interop = "0.1.0"
|
||||
# crypto
|
||||
enr = { version = "0.13", default-features = false }
|
||||
k256 = { version = "0.13", default-features = false, features = ["ecdsa"] }
|
||||
secp256k1 = { version = "0.30", default-features = false, features = [
|
||||
"global-context",
|
||||
"recovery",
|
||||
] }
|
||||
secp256k1 = { version = "0.30", default-features = false, features = ["global-context", "recovery"] }
|
||||
# rand 8 for secp256k1
|
||||
rand_08 = { package = "rand", version = "0.8" }
|
||||
|
||||
@@ -780,15 +753,71 @@ ipnet = "2.11"
|
||||
# jsonrpsee-http-client = { git = "https://github.com/paradigmxyz/jsonrpsee", branch = "matt/make-rpc-service-pub" }
|
||||
# jsonrpsee-types = { git = "https://github.com/paradigmxyz/jsonrpsee", branch = "matt/make-rpc-service-pub" }
|
||||
|
||||
alloy-evm = { git = "https://github.com/alloy-rs/evm", rev = "b0eb7e6" }
|
||||
# alloy-evm = { git = "https://github.com/alloy-rs/evm", rev = "9bc2dba" }
|
||||
|
||||
revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "24becc3" }
|
||||
# revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "3020ea8" }
|
||||
|
||||
# revm from rakita/state-gas branch
|
||||
revm = { git = "https://github.com/bluealloy/revm", rev = "712dac7a" }
|
||||
revm-bytecode = { git = "https://github.com/bluealloy/revm", rev = "712dac7a" }
|
||||
revm-database = { git = "https://github.com/bluealloy/revm", rev = "712dac7a" }
|
||||
revm-state = { git = "https://github.com/bluealloy/revm", rev = "712dac7a" }
|
||||
revm-primitives = { git = "https://github.com/bluealloy/revm", rev = "712dac7a" }
|
||||
revm-interpreter = { git = "https://github.com/bluealloy/revm", rev = "712dac7a" }
|
||||
revm-database-interface = { git = "https://github.com/bluealloy/revm", rev = "712dac7a" }
|
||||
# alloy-evm = { git = "https://github.com/alloy-rs/evm", rev = "072c248" }
|
||||
# alloy-op-evm = { git = "https://github.com/alloy-rs/evm", rev = "072c248" }
|
||||
|
||||
# =============================================================================
|
||||
# BAL devnet patches (EIP-7778, EIP-7928 block access lists)
|
||||
# =============================================================================
|
||||
|
||||
# revm staging patches
|
||||
revm = { git = "https://github.com/bluealloy/revm", rev = "fa5a6914398c9b178422efc1edd1d2ab33dad923" }
|
||||
revm-bytecode = { git = "https://github.com/bluealloy/revm", rev = "fa5a6914398c9b178422efc1edd1d2ab33dad923" }
|
||||
revm-database = { git = "https://github.com/bluealloy/revm", rev = "fa5a6914398c9b178422efc1edd1d2ab33dad923" }
|
||||
revm-database-interface = { git = "https://github.com/bluealloy/revm", rev = "fa5a6914398c9b178422efc1edd1d2ab33dad923" }
|
||||
revm-state = { git = "https://github.com/bluealloy/revm", rev = "fa5a6914398c9b178422efc1edd1d2ab33dad923" }
|
||||
revm-primitives = { git = "https://github.com/bluealloy/revm", rev = "fa5a6914398c9b178422efc1edd1d2ab33dad923" }
|
||||
revm-interpreter = { git = "https://github.com/bluealloy/revm", rev = "fa5a6914398c9b178422efc1edd1d2ab33dad923" }
|
||||
revm-precompile = { git = "https://github.com/bluealloy/revm", rev = "fa5a6914398c9b178422efc1edd1d2ab33dad923" }
|
||||
revm-context = { git = "https://github.com/bluealloy/revm", rev = "fa5a6914398c9b178422efc1edd1d2ab33dad923" }
|
||||
revm-context-interface = { git = "https://github.com/bluealloy/revm", rev = "fa5a6914398c9b178422efc1edd1d2ab33dad923" }
|
||||
revm-handler = { git = "https://github.com/bluealloy/revm", rev = "fa5a6914398c9b178422efc1edd1d2ab33dad923" }
|
||||
revm-inspector = { git = "https://github.com/bluealloy/revm", rev = "fa5a6914398c9b178422efc1edd1d2ab33dad923" }
|
||||
op-revm = { git = "https://github.com/bluealloy/revm", rev = "fa5a6914398c9b178422efc1edd1d2ab33dad923" }
|
||||
# revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", branch = "staging" }
|
||||
|
||||
# alloy-evm bal-devnet2 patches
|
||||
alloy-evm = { git = "https://github.com/alloy-rs/evm", branch = "bal-devnet2" }
|
||||
|
||||
# alloy bal-devnet2 patches
|
||||
alloy-consensus = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-consensus-any = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-contract = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-eips = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-genesis = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-network = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-provider = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types-debug = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types-any = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types-mev = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-serde = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-signer = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-transport = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-tx-macros = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
|
||||
# op-alloy bal-devnet2 patches
|
||||
op-alloy-consensus = { git = "https://github.com/alloy-rs/op-alloy", branch = "bal-devnet2" }
|
||||
op-alloy-network = { git = "https://github.com/alloy-rs/op-alloy", branch = "bal-devnet2" }
|
||||
op-alloy-rpc-types = { git = "https://github.com/alloy-rs/op-alloy", branch = "bal-devnet2" }
|
||||
op-alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/op-alloy", branch = "bal-devnet2" }
|
||||
|
||||
2
Makefile
2
Makefile
@@ -29,7 +29,7 @@ EF_TESTS_URL := https://github.com/ethereum/tests/archive/refs/tags/$(EF_TESTS_T
|
||||
EF_TESTS_DIR := ./testing/ef-tests/ethereum-tests
|
||||
|
||||
# The release tag of https://github.com/ethereum/execution-spec-tests to use for EEST tests
|
||||
EEST_TESTS_TAG := v4.5.0
|
||||
EEST_TESTS_TAG := bal@v5.0.0
|
||||
EEST_TESTS_URL := https://github.com/ethereum/execution-spec-tests/releases/download/$(EEST_TESTS_TAG)/fixtures_stable.tar.gz
|
||||
EEST_TESTS_DIR := ./testing/ef-tests/execution-spec-tests
|
||||
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
[package]
|
||||
name = "reth-bench-compare"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Automated reth benchmark comparison between git references"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "reth-bench-compare"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
# reth
|
||||
reth-cli-runner.workspace = true
|
||||
reth-cli-util.workspace = true
|
||||
reth-node-core.workspace = true
|
||||
reth-tracing.workspace = true
|
||||
reth-chainspec.workspace = true
|
||||
|
||||
# alloy
|
||||
alloy-provider = { workspace = true, features = ["reqwest-rustls-tls"], default-features = false }
|
||||
alloy-rpc-client = { workspace = true, features = ["pubsub"] }
|
||||
alloy-rpc-types-eth.workspace = true
|
||||
alloy-transport-ws.workspace = true
|
||||
alloy-primitives.workspace = true
|
||||
|
||||
# CLI and argument parsing
|
||||
clap = { workspace = true, features = ["derive", "env"] }
|
||||
eyre.workspace = true
|
||||
|
||||
# Async runtime
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
tracing.workspace = true
|
||||
|
||||
# Serialization
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
|
||||
# Time handling
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
|
||||
# CSV handling
|
||||
csv.workspace = true
|
||||
|
||||
# Process management
|
||||
ctrlc.workspace = true
|
||||
shlex.workspace = true
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = { version = "0.31", features = ["signal", "process"] }
|
||||
|
||||
[features]
|
||||
default = ["jemalloc"]
|
||||
|
||||
asm-keccak = [
|
||||
"reth-node-core/asm-keccak",
|
||||
"alloy-primitives/asm-keccak",
|
||||
]
|
||||
|
||||
jemalloc = [
|
||||
"reth-cli-util/jemalloc",
|
||||
"reth-node-core/jemalloc",
|
||||
]
|
||||
jemalloc-prof = ["reth-cli-util/jemalloc-prof"]
|
||||
tracy-allocator = ["reth-cli-util/tracy-allocator", "tracy"]
|
||||
tracy = [
|
||||
"reth-node-core/tracy",
|
||||
"reth-tracing/tracy",
|
||||
]
|
||||
|
||||
min-error-logs = [
|
||||
"tracing/release_max_level_error",
|
||||
"reth-node-core/min-error-logs",
|
||||
]
|
||||
min-warn-logs = [
|
||||
"tracing/release_max_level_warn",
|
||||
"reth-node-core/min-warn-logs",
|
||||
]
|
||||
min-info-logs = [
|
||||
"tracing/release_max_level_info",
|
||||
"reth-node-core/min-info-logs",
|
||||
]
|
||||
min-debug-logs = [
|
||||
"tracing/release_max_level_debug",
|
||||
"reth-node-core/min-debug-logs",
|
||||
]
|
||||
min-trace-logs = [
|
||||
"tracing/release_max_level_trace",
|
||||
"reth-node-core/min-trace-logs",
|
||||
]
|
||||
|
||||
# no-op feature flag for CI matrices
|
||||
ethereum = []
|
||||
@@ -1,50 +0,0 @@
|
||||
# reth-bench-compare
|
||||
|
||||
Compare reth performance between two git references.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
reth-bench-compare \
|
||||
--baseline-ref main \
|
||||
--feature-ref my-feature \
|
||||
--blocks 100 \
|
||||
--wait-for-persistence
|
||||
```
|
||||
|
||||
## Arguments
|
||||
|
||||
| Argument | Description | Default | Required |
|
||||
|----------|-------------|---------|----------|
|
||||
| `--baseline-ref <REF>` | Git reference for baseline | - | Yes |
|
||||
| `--feature-ref <REF>` | Git reference to compare | - | Yes |
|
||||
| `--blocks <N>` | Number of blocks to benchmark | `100` | No |
|
||||
| `--chain <CHAIN>` | Chain to benchmark | `mainnet` | No |
|
||||
| `--datadir <PATH>` | Data directory path | OS-specific | No |
|
||||
| `--rpc-url <URL>` | RPC endpoint for block data | Chain default | No |
|
||||
| `--output-dir <PATH>` | Output directory | `./reth-bench-compare` | No |
|
||||
| `--wait-for-persistence` | Wait for block persistence | `false` | No |
|
||||
| `--persistence-threshold <N>` | Wait after every N+1 blocks | `2` | No |
|
||||
| `--wait-time <DURATION>` | Fixed delay (legacy) | - | No |
|
||||
| `--warmup-blocks <N>` | Cache warmup blocks | Same as `--blocks` | No |
|
||||
| `--draw` | Generate charts (needs Python/uv) | `false` | No |
|
||||
| `--profile` | Enable CPU profiling (needs samply) | `false` | No |
|
||||
| `-vvvv` | Debug logging | Info | No |
|
||||
| `--features <FEATURES>` | Extra Rust features for both builds | - | No |
|
||||
| `--rustflags <FLAGS>` | RUSTFLAGS for both builds | `-C target-cpu=native` | No |
|
||||
| `--baseline-features <FEATURES>` | Features for baseline only | Inherits `--features` | No |
|
||||
| `--feature-features <FEATURES>` | Features for feature only | Inherits `--features` | No |
|
||||
| `--baseline-rustflags <FLAGS>` | RUSTFLAGS for baseline only | Inherits `--rustflags` | No |
|
||||
| `--feature-rustflags <FLAGS>` | RUSTFLAGS for feature only | Inherits `--rustflags` | No |
|
||||
| `--baseline-args <ARGS>` | Extra args for baseline node | - | No |
|
||||
| `--feature-args <ARGS>` | Extra args for feature node | - | No |
|
||||
| `--metrics-port <PORT>` | Metrics endpoint port | `5005` | No |
|
||||
| `--sudo` | Run with elevated privileges | `false` | No |
|
||||
|
||||
## Output
|
||||
|
||||
Results in `./reth-bench-compare/results/<timestamp>/`:
|
||||
- `comparison_report.json` - Metrics comparison
|
||||
- `per_block_comparison.csv` - Per-block statistics
|
||||
- `baseline/` and `feature/` - Individual run results
|
||||
- `latency_comparison.png` - Chart (if `--draw` used)
|
||||
@@ -1,307 +0,0 @@
|
||||
//! Benchmark execution using reth-bench.
|
||||
|
||||
use crate::cli::Args;
|
||||
use eyre::{eyre, Result, WrapErr};
|
||||
use std::{
|
||||
path::Path,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use tokio::{
|
||||
fs::File as AsyncFile,
|
||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
|
||||
process::Command,
|
||||
};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
/// Manages benchmark execution using reth-bench
|
||||
pub(crate) struct BenchmarkRunner {
|
||||
rpc_url: String,
|
||||
jwt_secret: String,
|
||||
wait_time: Option<String>,
|
||||
wait_for_persistence: bool,
|
||||
persistence_threshold: Option<u64>,
|
||||
warmup_blocks: u64,
|
||||
}
|
||||
|
||||
impl BenchmarkRunner {
|
||||
/// Create a new `BenchmarkRunner` from CLI arguments
|
||||
pub(crate) fn new(args: &Args) -> Self {
|
||||
Self {
|
||||
rpc_url: args.get_rpc_url(),
|
||||
jwt_secret: args.jwt_secret_path().to_string_lossy().to_string(),
|
||||
wait_time: args.wait_time.clone(),
|
||||
wait_for_persistence: args.wait_for_persistence,
|
||||
persistence_threshold: args.persistence_threshold,
|
||||
warmup_blocks: args.get_warmup_blocks(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear filesystem caches (page cache, dentries, and inodes)
|
||||
pub(crate) async fn clear_fs_caches() -> Result<()> {
|
||||
info!("Clearing filesystem caches...");
|
||||
|
||||
// First sync to ensure all pending writes are flushed
|
||||
let sync_output =
|
||||
Command::new("sync").output().await.wrap_err("Failed to execute sync command")?;
|
||||
|
||||
if !sync_output.status.success() {
|
||||
return Err(eyre!("sync command failed"));
|
||||
}
|
||||
|
||||
// Drop caches - requires sudo/root permissions
|
||||
// 3 = drop pagecache, dentries, and inodes
|
||||
let drop_caches_cmd = Command::new("sudo")
|
||||
.args(["-n", "sh", "-c", "echo 3 > /proc/sys/vm/drop_caches"])
|
||||
.output()
|
||||
.await;
|
||||
|
||||
match drop_caches_cmd {
|
||||
Ok(output) if output.status.success() => {
|
||||
info!("Successfully cleared filesystem caches");
|
||||
Ok(())
|
||||
}
|
||||
Ok(output) => {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
if stderr.contains("sudo: a password is required") {
|
||||
warn!("Unable to clear filesystem caches: sudo password required");
|
||||
warn!(
|
||||
"For optimal benchmarking, configure passwordless sudo for cache clearing:"
|
||||
);
|
||||
warn!(" echo '$USER ALL=(ALL) NOPASSWD: /bin/sh -c echo\\\\ [0-9]\\\\ \\\\>\\\\ /proc/sys/vm/drop_caches' | sudo tee /etc/sudoers.d/drop_caches");
|
||||
Ok(())
|
||||
} else {
|
||||
Err(eyre!("Failed to clear filesystem caches: {}", stderr))
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Unable to clear filesystem caches: {}", e);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a warmup benchmark for cache warming
|
||||
pub(crate) async fn run_warmup(&self, from_block: u64) -> Result<()> {
|
||||
let to_block = from_block + self.warmup_blocks;
|
||||
info!(
|
||||
"Running warmup benchmark from block {} to {} ({} blocks)",
|
||||
from_block, to_block, self.warmup_blocks
|
||||
);
|
||||
|
||||
// Build the reth-bench command for warmup (no output flag)
|
||||
let mut cmd = Command::new("reth-bench");
|
||||
cmd.args([
|
||||
"new-payload-fcu",
|
||||
"--rpc-url",
|
||||
&self.rpc_url,
|
||||
"--jwt-secret",
|
||||
&self.jwt_secret,
|
||||
"--from",
|
||||
&from_block.to_string(),
|
||||
"--to",
|
||||
&to_block.to_string(),
|
||||
"--wait-time=0ms", // Warmup should avoid persistence waits.
|
||||
]);
|
||||
|
||||
cmd.env("RUST_LOG_STYLE", "never")
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.kill_on_drop(true);
|
||||
|
||||
// Set process group for consistent signal handling
|
||||
#[cfg(unix)]
|
||||
{
|
||||
cmd.process_group(0);
|
||||
}
|
||||
|
||||
debug!("Executing warmup reth-bench command: {:?}", cmd);
|
||||
|
||||
// Execute the warmup benchmark
|
||||
let mut child = cmd.spawn().wrap_err("Failed to start warmup reth-bench process")?;
|
||||
|
||||
// Stream output at debug level
|
||||
if let Some(stdout) = child.stdout.take() {
|
||||
tokio::spawn(async move {
|
||||
let reader = BufReader::new(stdout);
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
debug!("[WARMUP] {}", line);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(stderr) = child.stderr.take() {
|
||||
tokio::spawn(async move {
|
||||
let reader = BufReader::new(stderr);
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
debug!("[WARMUP] {}", line);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let status = child.wait().await.wrap_err("Failed to wait for warmup reth-bench")?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(eyre!("Warmup reth-bench failed with exit code: {:?}", status.code()));
|
||||
}
|
||||
|
||||
info!("Warmup completed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run a benchmark for the specified block range
|
||||
pub(crate) async fn run_benchmark(
|
||||
&self,
|
||||
from_block: u64,
|
||||
to_block: u64,
|
||||
output_dir: &Path,
|
||||
) -> Result<()> {
|
||||
info!(
|
||||
"Running benchmark from block {} to {} (output: {:?})",
|
||||
from_block, to_block, output_dir
|
||||
);
|
||||
|
||||
// Ensure output directory exists
|
||||
std::fs::create_dir_all(output_dir)
|
||||
.wrap_err_with(|| format!("Failed to create output directory: {output_dir:?}"))?;
|
||||
|
||||
// Create log file path for reth-bench output
|
||||
let log_file_path = output_dir.join("reth_bench.log");
|
||||
info!("reth-bench logs will be saved to: {:?}", log_file_path);
|
||||
|
||||
// Build the reth-bench command
|
||||
let mut cmd = Command::new("reth-bench");
|
||||
cmd.args([
|
||||
"new-payload-fcu",
|
||||
"--rpc-url",
|
||||
&self.rpc_url,
|
||||
"--jwt-secret",
|
||||
&self.jwt_secret,
|
||||
"--from",
|
||||
&from_block.to_string(),
|
||||
"--to",
|
||||
&to_block.to_string(),
|
||||
"--output",
|
||||
&output_dir.to_string_lossy(),
|
||||
]);
|
||||
|
||||
// Configure wait mode: both can be used together
|
||||
// When both are set: wait at least wait_time, and also wait for persistence if needed
|
||||
if let Some(ref wait_time) = self.wait_time {
|
||||
cmd.args(["--wait-time", wait_time]);
|
||||
}
|
||||
if self.wait_for_persistence {
|
||||
cmd.arg("--wait-for-persistence");
|
||||
|
||||
// Add persistence threshold if specified
|
||||
if let Some(threshold) = self.persistence_threshold {
|
||||
cmd.args(["--persistence-threshold", &threshold.to_string()]);
|
||||
}
|
||||
}
|
||||
|
||||
cmd.env("RUST_LOG_STYLE", "never")
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.kill_on_drop(true);
|
||||
|
||||
// Set process group for consistent signal handling
|
||||
#[cfg(unix)]
|
||||
{
|
||||
cmd.process_group(0);
|
||||
}
|
||||
|
||||
// Debug log the command
|
||||
debug!("Executing reth-bench command: {:?}", cmd);
|
||||
|
||||
// Execute the benchmark
|
||||
let mut child = cmd.spawn().wrap_err("Failed to start reth-bench process")?;
|
||||
|
||||
// Capture stdout and stderr for error reporting
|
||||
let stdout_lines = Arc::new(Mutex::new(Vec::new()));
|
||||
let stderr_lines = Arc::new(Mutex::new(Vec::new()));
|
||||
|
||||
// Stream stdout with prefix at debug level, capture for error reporting, and write to log
|
||||
// file
|
||||
if let Some(stdout) = child.stdout.take() {
|
||||
let stdout_lines_clone = stdout_lines.clone();
|
||||
let log_file = AsyncFile::create(&log_file_path)
|
||||
.await
|
||||
.wrap_err(format!("Failed to create log file: {:?}", log_file_path))?;
|
||||
tokio::spawn(async move {
|
||||
let reader = BufReader::new(stdout);
|
||||
let mut lines = reader.lines();
|
||||
let mut log_file = log_file;
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
debug!("[RETH-BENCH] {}", line);
|
||||
if let Ok(mut captured) = stdout_lines_clone.lock() {
|
||||
captured.push(line.clone());
|
||||
}
|
||||
// Write to log file (reth-bench output already has timestamps if needed)
|
||||
let log_line = format!("{}\n", line);
|
||||
if let Err(e) = log_file.write_all(log_line.as_bytes()).await {
|
||||
debug!("Failed to write to log file: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Stream stderr with prefix at debug level, capture for error reporting, and write to log
|
||||
// file
|
||||
if let Some(stderr) = child.stderr.take() {
|
||||
let stderr_lines_clone = stderr_lines.clone();
|
||||
let log_file = AsyncFile::options()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&log_file_path)
|
||||
.await
|
||||
.wrap_err(format!("Failed to open log file for stderr: {:?}", log_file_path))?;
|
||||
tokio::spawn(async move {
|
||||
let reader = BufReader::new(stderr);
|
||||
let mut lines = reader.lines();
|
||||
let mut log_file = log_file;
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
debug!("[RETH-BENCH] {}", line);
|
||||
if let Ok(mut captured) = stderr_lines_clone.lock() {
|
||||
captured.push(line.clone());
|
||||
}
|
||||
// Write to log file (reth-bench output already has timestamps if needed)
|
||||
let log_line = format!("{}\n", line);
|
||||
if let Err(e) = log_file.write_all(log_line.as_bytes()).await {
|
||||
debug!("Failed to write to log file: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let status = child.wait().await.wrap_err("Failed to wait for reth-bench")?;
|
||||
|
||||
if !status.success() {
|
||||
// Print all captured output when command fails
|
||||
error!("reth-bench failed with exit code: {:?}", status.code());
|
||||
|
||||
if let Ok(stdout) = stdout_lines.lock() &&
|
||||
!stdout.is_empty()
|
||||
{
|
||||
error!("reth-bench stdout:");
|
||||
for line in stdout.iter() {
|
||||
error!(" {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(stderr) = stderr_lines.lock() &&
|
||||
!stderr.is_empty()
|
||||
{
|
||||
error!("reth-bench stderr:");
|
||||
for line in stderr.iter() {
|
||||
error!(" {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
return Err(eyre!("reth-bench failed with exit code: {:?}", status.code()));
|
||||
}
|
||||
|
||||
info!("Benchmark completed");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,723 +0,0 @@
|
||||
//! Results comparison and report generation.
|
||||
|
||||
use crate::cli::Args;
|
||||
use chrono::{DateTime, Utc};
|
||||
use csv::Reader;
|
||||
use eyre::{eyre, Result, WrapErr};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::HashMap,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use tracing::{info, warn};
|
||||
|
||||
/// Manages comparison between baseline and feature reference results
|
||||
pub(crate) struct ComparisonGenerator {
|
||||
output_dir: PathBuf,
|
||||
timestamp: String,
|
||||
baseline_ref_name: String,
|
||||
feature_ref_name: String,
|
||||
baseline_results: Option<BenchmarkResults>,
|
||||
feature_results: Option<BenchmarkResults>,
|
||||
baseline_command: Option<String>,
|
||||
feature_command: Option<String>,
|
||||
}
|
||||
|
||||
/// Represents the results from a single benchmark run
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct BenchmarkResults {
|
||||
pub ref_name: String,
|
||||
pub combined_latency_data: Vec<CombinedLatencyRow>,
|
||||
pub summary: BenchmarkSummary,
|
||||
pub start_timestamp: Option<DateTime<Utc>>,
|
||||
pub end_timestamp: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
/// Combined latency CSV row structure
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub(crate) struct CombinedLatencyRow {
|
||||
pub block_number: u64,
|
||||
#[serde(default)]
|
||||
pub transaction_count: Option<u64>,
|
||||
pub gas_used: u64,
|
||||
pub new_payload_latency: u128,
|
||||
}
|
||||
|
||||
/// Total gas CSV row structure
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub(crate) struct TotalGasRow {
|
||||
pub block_number: u64,
|
||||
#[serde(default)]
|
||||
pub transaction_count: Option<u64>,
|
||||
pub gas_used: u64,
|
||||
pub time: u128,
|
||||
}
|
||||
|
||||
/// Summary statistics for a benchmark run.
|
||||
///
|
||||
/// Latencies are derived from per-block `engine_newPayload` timings (converted from µs to ms):
|
||||
/// - `mean_new_payload_latency_ms`: arithmetic mean latency across blocks.
|
||||
/// - `median_new_payload_latency_ms`: p50 latency across blocks.
|
||||
/// - `p90_new_payload_latency_ms` / `p99_new_payload_latency_ms`: tail latencies across blocks.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub(crate) struct BenchmarkSummary {
|
||||
pub total_blocks: u64,
|
||||
pub total_gas_used: u64,
|
||||
pub total_duration_ms: u128,
|
||||
pub mean_new_payload_latency_ms: f64,
|
||||
pub median_new_payload_latency_ms: f64,
|
||||
pub p90_new_payload_latency_ms: f64,
|
||||
pub p99_new_payload_latency_ms: f64,
|
||||
pub gas_per_second: f64,
|
||||
pub blocks_per_second: f64,
|
||||
pub min_block_number: u64,
|
||||
pub max_block_number: u64,
|
||||
}
|
||||
|
||||
/// Comparison report between two benchmark runs
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct ComparisonReport {
|
||||
pub timestamp: String,
|
||||
pub baseline: RefInfo,
|
||||
pub feature: RefInfo,
|
||||
pub comparison_summary: ComparisonSummary,
|
||||
pub per_block_comparisons: Vec<BlockComparison>,
|
||||
}
|
||||
|
||||
/// Information about a reference in the comparison
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct RefInfo {
|
||||
pub ref_name: String,
|
||||
pub summary: BenchmarkSummary,
|
||||
pub start_timestamp: Option<DateTime<Utc>>,
|
||||
pub end_timestamp: Option<DateTime<Utc>>,
|
||||
pub reth_command: Option<String>,
|
||||
}
|
||||
|
||||
/// Summary of the comparison between references.
|
||||
///
|
||||
/// Percent deltas are `(feature - baseline) / baseline * 100`:
|
||||
/// - `new_payload_latency_mean_change_percent`: percent changes of the per-block means.
|
||||
/// - `new_payload_latency_p50_change_percent` / p90 / p99: percent changes of the respective
|
||||
/// per-block percentiles.
|
||||
/// - `per_block_latency_change_mean_percent` / `per_block_latency_change_median_percent` are the
|
||||
/// mean and median of per-block percent deltas (feature vs baseline), capturing block-level
|
||||
/// drift.
|
||||
/// - `per_block_latency_change_std_dev_percent`: standard deviation of per-block percent changes,
|
||||
/// measuring consistency of performance changes across blocks.
|
||||
/// - `new_payload_total_latency_change_percent` is the percent change of the total newPayload time
|
||||
/// across the run.
|
||||
///
|
||||
/// Positive means slower/higher; negative means faster/lower.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct ComparisonSummary {
|
||||
pub per_block_latency_change_mean_percent: f64,
|
||||
pub per_block_latency_change_median_percent: f64,
|
||||
pub per_block_latency_change_std_dev_percent: f64,
|
||||
pub new_payload_total_latency_change_percent: f64,
|
||||
pub new_payload_latency_mean_change_percent: f64,
|
||||
pub new_payload_latency_p50_change_percent: f64,
|
||||
pub new_payload_latency_p90_change_percent: f64,
|
||||
pub new_payload_latency_p99_change_percent: f64,
|
||||
pub gas_per_second_change_percent: f64,
|
||||
pub blocks_per_second_change_percent: f64,
|
||||
}
|
||||
|
||||
/// Per-block comparison data
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct BlockComparison {
|
||||
pub block_number: u64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub transaction_count: Option<u64>,
|
||||
pub gas_used: u64,
|
||||
pub baseline_new_payload_latency: u128,
|
||||
pub feature_new_payload_latency: u128,
|
||||
pub new_payload_latency_change_percent: f64,
|
||||
}
|
||||
|
||||
impl ComparisonGenerator {
|
||||
/// Create a new comparison generator
|
||||
pub(crate) fn new(args: &Args) -> Self {
|
||||
let now: DateTime<Utc> = Utc::now();
|
||||
let timestamp = now.format("%Y%m%d_%H%M%S").to_string();
|
||||
|
||||
Self {
|
||||
output_dir: args.output_dir_path(),
|
||||
timestamp,
|
||||
baseline_ref_name: args.baseline_ref.clone(),
|
||||
feature_ref_name: args.feature_ref.clone(),
|
||||
baseline_results: None,
|
||||
feature_results: None,
|
||||
baseline_command: None,
|
||||
feature_command: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the output directory for a specific reference
|
||||
pub(crate) fn get_ref_output_dir(&self, ref_type: &str) -> PathBuf {
|
||||
self.output_dir.join("results").join(&self.timestamp).join(ref_type)
|
||||
}
|
||||
|
||||
/// Get the main output directory for this comparison run
|
||||
pub(crate) fn get_output_dir(&self) -> PathBuf {
|
||||
self.output_dir.join("results").join(&self.timestamp)
|
||||
}
|
||||
|
||||
/// Add benchmark results for a reference
|
||||
pub(crate) fn add_ref_results(&mut self, ref_type: &str, output_path: &Path) -> Result<()> {
|
||||
let ref_name = match ref_type {
|
||||
"baseline" => &self.baseline_ref_name,
|
||||
"feature" => &self.feature_ref_name,
|
||||
_ => return Err(eyre!("Unknown reference type: {}", ref_type)),
|
||||
};
|
||||
|
||||
let results = self.load_benchmark_results(ref_name, output_path)?;
|
||||
|
||||
match ref_type {
|
||||
"baseline" => self.baseline_results = Some(results),
|
||||
"feature" => self.feature_results = Some(results),
|
||||
_ => return Err(eyre!("Unknown reference type: {}", ref_type)),
|
||||
}
|
||||
|
||||
info!("Loaded benchmark results for {} reference", ref_type);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the benchmark run timestamps for a reference
|
||||
pub(crate) fn set_ref_timestamps(
|
||||
&mut self,
|
||||
ref_type: &str,
|
||||
start: DateTime<Utc>,
|
||||
end: DateTime<Utc>,
|
||||
) -> Result<()> {
|
||||
match ref_type {
|
||||
"baseline" => {
|
||||
if let Some(ref mut results) = self.baseline_results {
|
||||
results.start_timestamp = Some(start);
|
||||
results.end_timestamp = Some(end);
|
||||
} else {
|
||||
return Err(eyre!("Baseline results not loaded yet"));
|
||||
}
|
||||
}
|
||||
"feature" => {
|
||||
if let Some(ref mut results) = self.feature_results {
|
||||
results.start_timestamp = Some(start);
|
||||
results.end_timestamp = Some(end);
|
||||
} else {
|
||||
return Err(eyre!("Feature results not loaded yet"));
|
||||
}
|
||||
}
|
||||
_ => return Err(eyre!("Unknown reference type: {}", ref_type)),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the reth command for a reference
|
||||
pub(crate) fn set_ref_command(&mut self, ref_type: &str, command: String) -> Result<()> {
|
||||
match ref_type {
|
||||
"baseline" => {
|
||||
self.baseline_command = Some(command);
|
||||
}
|
||||
"feature" => {
|
||||
self.feature_command = Some(command);
|
||||
}
|
||||
_ => return Err(eyre!("Unknown reference type: {}", ref_type)),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate the final comparison report
|
||||
pub(crate) async fn generate_comparison_report(&self) -> Result<()> {
|
||||
info!("Generating comparison report...");
|
||||
|
||||
let baseline =
|
||||
self.baseline_results.as_ref().ok_or_else(|| eyre!("Baseline results not loaded"))?;
|
||||
|
||||
let feature =
|
||||
self.feature_results.as_ref().ok_or_else(|| eyre!("Feature results not loaded"))?;
|
||||
|
||||
let per_block_comparisons = self.calculate_per_block_comparisons(baseline, feature)?;
|
||||
let comparison_summary = self.calculate_comparison_summary(
|
||||
&baseline.summary,
|
||||
&feature.summary,
|
||||
&per_block_comparisons,
|
||||
)?;
|
||||
|
||||
let report = ComparisonReport {
|
||||
timestamp: self.timestamp.clone(),
|
||||
baseline: RefInfo {
|
||||
ref_name: baseline.ref_name.clone(),
|
||||
summary: baseline.summary.clone(),
|
||||
start_timestamp: baseline.start_timestamp,
|
||||
end_timestamp: baseline.end_timestamp,
|
||||
reth_command: self.baseline_command.clone(),
|
||||
},
|
||||
feature: RefInfo {
|
||||
ref_name: feature.ref_name.clone(),
|
||||
summary: feature.summary.clone(),
|
||||
start_timestamp: feature.start_timestamp,
|
||||
end_timestamp: feature.end_timestamp,
|
||||
reth_command: self.feature_command.clone(),
|
||||
},
|
||||
comparison_summary,
|
||||
per_block_comparisons,
|
||||
};
|
||||
|
||||
// Write reports
|
||||
self.write_comparison_reports(&report).await?;
|
||||
|
||||
// Print summary to console
|
||||
self.print_comparison_summary(&report);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load benchmark results from CSV files
|
||||
fn load_benchmark_results(
|
||||
&self,
|
||||
ref_name: &str,
|
||||
output_path: &Path,
|
||||
) -> Result<BenchmarkResults> {
|
||||
let combined_latency_path = output_path.join("combined_latency.csv");
|
||||
let total_gas_path = output_path.join("total_gas.csv");
|
||||
|
||||
let combined_latency_data = self.load_combined_latency_csv(&combined_latency_path)?;
|
||||
let total_gas_data = self.load_total_gas_csv(&total_gas_path)?;
|
||||
|
||||
let summary = self.calculate_summary(&combined_latency_data, &total_gas_data)?;
|
||||
|
||||
Ok(BenchmarkResults {
|
||||
ref_name: ref_name.to_string(),
|
||||
combined_latency_data,
|
||||
summary,
|
||||
start_timestamp: None,
|
||||
end_timestamp: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Load combined latency CSV data
|
||||
fn load_combined_latency_csv(&self, path: &Path) -> Result<Vec<CombinedLatencyRow>> {
|
||||
let mut reader = Reader::from_path(path)
|
||||
.wrap_err_with(|| format!("Failed to open combined latency CSV: {path:?}"))?;
|
||||
|
||||
let mut rows = Vec::new();
|
||||
for result in reader.deserialize() {
|
||||
let row: CombinedLatencyRow = result
|
||||
.wrap_err_with(|| format!("Failed to parse combined latency row in {path:?}"))?;
|
||||
rows.push(row);
|
||||
}
|
||||
|
||||
if rows.is_empty() {
|
||||
return Err(eyre!("No data found in combined latency CSV: {:?}", path));
|
||||
}
|
||||
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
/// Load total gas CSV data
|
||||
fn load_total_gas_csv(&self, path: &Path) -> Result<Vec<TotalGasRow>> {
|
||||
let mut reader = Reader::from_path(path)
|
||||
.wrap_err_with(|| format!("Failed to open total gas CSV: {path:?}"))?;
|
||||
|
||||
let mut rows = Vec::new();
|
||||
for result in reader.deserialize() {
|
||||
let row: TotalGasRow =
|
||||
result.wrap_err_with(|| format!("Failed to parse total gas row in {path:?}"))?;
|
||||
rows.push(row);
|
||||
}
|
||||
|
||||
if rows.is_empty() {
|
||||
return Err(eyre!("No data found in total gas CSV: {:?}", path));
|
||||
}
|
||||
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
/// Calculate summary statistics for a benchmark run.
|
||||
///
|
||||
/// Computes latency statistics from per-block `new_payload_latency` values in `combined_data`
|
||||
/// (converting from µs to ms), and throughput metrics using the total run duration from
|
||||
/// `total_gas_data`. Percentiles (p50/p90/p99) use linear interpolation on sorted latencies.
|
||||
fn calculate_summary(
|
||||
&self,
|
||||
combined_data: &[CombinedLatencyRow],
|
||||
total_gas_data: &[TotalGasRow],
|
||||
) -> Result<BenchmarkSummary> {
|
||||
if combined_data.is_empty() || total_gas_data.is_empty() {
|
||||
return Err(eyre!("Cannot calculate summary for empty data"));
|
||||
}
|
||||
|
||||
let total_blocks = combined_data.len() as u64;
|
||||
let total_gas_used: u64 = combined_data.iter().map(|r| r.gas_used).sum();
|
||||
|
||||
let total_duration_ms = total_gas_data.last().unwrap().time / 1000; // Convert microseconds to milliseconds
|
||||
|
||||
let latencies_ms: Vec<f64> =
|
||||
combined_data.iter().map(|r| r.new_payload_latency as f64 / 1000.0).collect();
|
||||
let mean_new_payload_latency_ms: f64 =
|
||||
latencies_ms.iter().sum::<f64>() / total_blocks as f64;
|
||||
|
||||
let mut sorted_latencies_ms = latencies_ms;
|
||||
sorted_latencies_ms.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
|
||||
let median_new_payload_latency_ms = percentile(&sorted_latencies_ms, 0.5);
|
||||
let p90_new_payload_latency_ms = percentile(&sorted_latencies_ms, 0.9);
|
||||
let p99_new_payload_latency_ms = percentile(&sorted_latencies_ms, 0.99);
|
||||
|
||||
let total_duration_seconds = total_duration_ms as f64 / 1000.0;
|
||||
let gas_per_second = if total_duration_seconds > f64::EPSILON {
|
||||
total_gas_used as f64 / total_duration_seconds
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let blocks_per_second = if total_duration_seconds > f64::EPSILON {
|
||||
total_blocks as f64 / total_duration_seconds
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let min_block_number = combined_data.first().unwrap().block_number;
|
||||
let max_block_number = combined_data.last().unwrap().block_number;
|
||||
|
||||
Ok(BenchmarkSummary {
|
||||
total_blocks,
|
||||
total_gas_used,
|
||||
total_duration_ms,
|
||||
mean_new_payload_latency_ms,
|
||||
median_new_payload_latency_ms,
|
||||
p90_new_payload_latency_ms,
|
||||
p99_new_payload_latency_ms,
|
||||
gas_per_second,
|
||||
blocks_per_second,
|
||||
min_block_number,
|
||||
max_block_number,
|
||||
})
|
||||
}
|
||||
|
||||
/// Calculate comparison summary between baseline and feature
|
||||
fn calculate_comparison_summary(
|
||||
&self,
|
||||
baseline: &BenchmarkSummary,
|
||||
feature: &BenchmarkSummary,
|
||||
per_block_comparisons: &[BlockComparison],
|
||||
) -> Result<ComparisonSummary> {
|
||||
let calc_percent_change = |baseline: f64, feature: f64| -> f64 {
|
||||
if baseline.abs() > f64::EPSILON {
|
||||
((feature - baseline) / baseline) * 100.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate per-block statistics. "Per-block" means: for each block, compute the percent
|
||||
// change (feature - baseline) / baseline * 100, then calculate statistics across those
|
||||
// per-block percent changes. This captures how consistently the feature performs relative
|
||||
// to baseline across all blocks.
|
||||
let per_block_percent_changes: Vec<f64> =
|
||||
per_block_comparisons.iter().map(|c| c.new_payload_latency_change_percent).collect();
|
||||
let per_block_latency_change_mean_percent = if per_block_percent_changes.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
per_block_percent_changes.iter().sum::<f64>() / per_block_percent_changes.len() as f64
|
||||
};
|
||||
let per_block_latency_change_median_percent = if per_block_percent_changes.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
let mut sorted = per_block_percent_changes.clone();
|
||||
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
|
||||
percentile(&sorted, 0.5)
|
||||
};
|
||||
let per_block_latency_change_std_dev_percent =
|
||||
calculate_std_dev(&per_block_percent_changes, per_block_latency_change_mean_percent);
|
||||
|
||||
let baseline_total_latency_ms =
|
||||
baseline.mean_new_payload_latency_ms * baseline.total_blocks as f64;
|
||||
let feature_total_latency_ms =
|
||||
feature.mean_new_payload_latency_ms * feature.total_blocks as f64;
|
||||
let new_payload_total_latency_change_percent =
|
||||
calc_percent_change(baseline_total_latency_ms, feature_total_latency_ms);
|
||||
|
||||
Ok(ComparisonSummary {
|
||||
per_block_latency_change_mean_percent,
|
||||
per_block_latency_change_median_percent,
|
||||
per_block_latency_change_std_dev_percent,
|
||||
new_payload_total_latency_change_percent,
|
||||
new_payload_latency_mean_change_percent: calc_percent_change(
|
||||
baseline.mean_new_payload_latency_ms,
|
||||
feature.mean_new_payload_latency_ms,
|
||||
),
|
||||
new_payload_latency_p50_change_percent: calc_percent_change(
|
||||
baseline.median_new_payload_latency_ms,
|
||||
feature.median_new_payload_latency_ms,
|
||||
),
|
||||
new_payload_latency_p90_change_percent: calc_percent_change(
|
||||
baseline.p90_new_payload_latency_ms,
|
||||
feature.p90_new_payload_latency_ms,
|
||||
),
|
||||
new_payload_latency_p99_change_percent: calc_percent_change(
|
||||
baseline.p99_new_payload_latency_ms,
|
||||
feature.p99_new_payload_latency_ms,
|
||||
),
|
||||
gas_per_second_change_percent: calc_percent_change(
|
||||
baseline.gas_per_second,
|
||||
feature.gas_per_second,
|
||||
),
|
||||
blocks_per_second_change_percent: calc_percent_change(
|
||||
baseline.blocks_per_second,
|
||||
feature.blocks_per_second,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
/// Calculate per-block comparisons
|
||||
fn calculate_per_block_comparisons(
|
||||
&self,
|
||||
baseline: &BenchmarkResults,
|
||||
feature: &BenchmarkResults,
|
||||
) -> Result<Vec<BlockComparison>> {
|
||||
let mut baseline_map: HashMap<u64, &CombinedLatencyRow> = HashMap::new();
|
||||
for row in &baseline.combined_latency_data {
|
||||
baseline_map.insert(row.block_number, row);
|
||||
}
|
||||
|
||||
let mut comparisons = Vec::new();
|
||||
for feature_row in &feature.combined_latency_data {
|
||||
if let Some(baseline_row) = baseline_map.get(&feature_row.block_number) {
|
||||
let calc_percent_change = |baseline: u128, feature: u128| -> f64 {
|
||||
if baseline > 0 {
|
||||
((feature as f64 - baseline as f64) / baseline as f64) * 100.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
};
|
||||
|
||||
let comparison = BlockComparison {
|
||||
block_number: feature_row.block_number,
|
||||
transaction_count: feature_row.transaction_count,
|
||||
gas_used: feature_row.gas_used,
|
||||
baseline_new_payload_latency: baseline_row.new_payload_latency,
|
||||
feature_new_payload_latency: feature_row.new_payload_latency,
|
||||
new_payload_latency_change_percent: calc_percent_change(
|
||||
baseline_row.new_payload_latency,
|
||||
feature_row.new_payload_latency,
|
||||
),
|
||||
};
|
||||
comparisons.push(comparison);
|
||||
} else {
|
||||
warn!("Block {} not found in baseline data", feature_row.block_number);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(comparisons)
|
||||
}
|
||||
|
||||
/// Write comparison reports to files
|
||||
async fn write_comparison_reports(&self, report: &ComparisonReport) -> Result<()> {
|
||||
let report_dir = self.output_dir.join("results").join(&self.timestamp);
|
||||
fs::create_dir_all(&report_dir)
|
||||
.wrap_err_with(|| format!("Failed to create report directory: {report_dir:?}"))?;
|
||||
|
||||
// Write JSON report
|
||||
let json_path = report_dir.join("comparison_report.json");
|
||||
let json_content = serde_json::to_string_pretty(report)
|
||||
.wrap_err("Failed to serialize comparison report to JSON")?;
|
||||
fs::write(&json_path, json_content)
|
||||
.wrap_err_with(|| format!("Failed to write JSON report: {json_path:?}"))?;
|
||||
|
||||
// Write CSV report for per-block comparisons
|
||||
let csv_path = report_dir.join("per_block_comparison.csv");
|
||||
let mut writer = csv::Writer::from_path(&csv_path)
|
||||
.wrap_err_with(|| format!("Failed to create CSV writer: {csv_path:?}"))?;
|
||||
|
||||
for comparison in &report.per_block_comparisons {
|
||||
writer.serialize(comparison).wrap_err("Failed to write comparison row to CSV")?;
|
||||
}
|
||||
writer.flush().wrap_err("Failed to flush CSV writer")?;
|
||||
|
||||
info!("Comparison reports written to: {:?}", report_dir);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Print comparison summary to console
|
||||
fn print_comparison_summary(&self, report: &ComparisonReport) {
|
||||
// Parse and format timestamp nicely
|
||||
let formatted_timestamp = if let Ok(dt) = chrono::DateTime::parse_from_str(
|
||||
&format!("{} +0000", report.timestamp.replace('_', " ")),
|
||||
"%Y%m%d %H%M%S %z",
|
||||
) {
|
||||
dt.format("%Y-%m-%d %H:%M:%S UTC").to_string()
|
||||
} else {
|
||||
// Fallback to original if parsing fails
|
||||
report.timestamp.clone()
|
||||
};
|
||||
|
||||
println!("\n=== BENCHMARK COMPARISON SUMMARY ===");
|
||||
println!("Timestamp: {formatted_timestamp}");
|
||||
println!("Baseline: {}", report.baseline.ref_name);
|
||||
println!("Feature: {}", report.feature.ref_name);
|
||||
println!();
|
||||
|
||||
let summary = &report.comparison_summary;
|
||||
|
||||
println!("Performance Changes:");
|
||||
println!(
|
||||
" NewPayload Latency per-block mean change: {:+.2}%",
|
||||
summary.per_block_latency_change_mean_percent
|
||||
);
|
||||
println!(
|
||||
" NewPayload Latency per-block median change: {:+.2}%",
|
||||
summary.per_block_latency_change_median_percent
|
||||
);
|
||||
println!(
|
||||
" NewPayload Latency per-block std dev: {:.2}%",
|
||||
summary.per_block_latency_change_std_dev_percent
|
||||
);
|
||||
println!(
|
||||
" Total newPayload time change: {:+.2}%",
|
||||
summary.new_payload_total_latency_change_percent
|
||||
);
|
||||
println!(
|
||||
" NewPayload Latency mean: {:+.2}%",
|
||||
summary.new_payload_latency_mean_change_percent
|
||||
);
|
||||
println!(
|
||||
" NewPayload Latency p50: {:+.2}%",
|
||||
summary.new_payload_latency_p50_change_percent
|
||||
);
|
||||
println!(
|
||||
" NewPayload Latency p90: {:+.2}%",
|
||||
summary.new_payload_latency_p90_change_percent
|
||||
);
|
||||
println!(
|
||||
" NewPayload Latency p99: {:+.2}%",
|
||||
summary.new_payload_latency_p99_change_percent
|
||||
);
|
||||
println!(
|
||||
" Gas/Second: {:+.2}%",
|
||||
summary.gas_per_second_change_percent
|
||||
);
|
||||
println!(
|
||||
" Blocks/Second: {:+.2}%",
|
||||
summary.blocks_per_second_change_percent
|
||||
);
|
||||
println!();
|
||||
|
||||
println!("Baseline Summary:");
|
||||
let baseline = &report.baseline.summary;
|
||||
println!(
|
||||
" Blocks: {} (blocks {} to {}), Gas: {}, Duration: {:.2}s",
|
||||
baseline.total_blocks,
|
||||
baseline.min_block_number,
|
||||
baseline.max_block_number,
|
||||
baseline.total_gas_used,
|
||||
baseline.total_duration_ms as f64 / 1000.0
|
||||
);
|
||||
println!(" NewPayload latency (ms):");
|
||||
println!(
|
||||
" mean: {:.2}, p50: {:.2}, p90: {:.2}, p99: {:.2}",
|
||||
baseline.mean_new_payload_latency_ms,
|
||||
baseline.median_new_payload_latency_ms,
|
||||
baseline.p90_new_payload_latency_ms,
|
||||
baseline.p99_new_payload_latency_ms
|
||||
);
|
||||
if let (Some(start), Some(end)) =
|
||||
(&report.baseline.start_timestamp, &report.baseline.end_timestamp)
|
||||
{
|
||||
println!(
|
||||
" Started: {}, Ended: {}",
|
||||
start.format("%Y-%m-%d %H:%M:%S UTC"),
|
||||
end.format("%Y-%m-%d %H:%M:%S UTC")
|
||||
);
|
||||
}
|
||||
if let Some(ref cmd) = report.baseline.reth_command {
|
||||
println!(" Command: {}", cmd);
|
||||
}
|
||||
println!();
|
||||
|
||||
println!("Feature Summary:");
|
||||
let feature = &report.feature.summary;
|
||||
println!(
|
||||
" Blocks: {} (blocks {} to {}), Gas: {}, Duration: {:.2}s",
|
||||
feature.total_blocks,
|
||||
feature.min_block_number,
|
||||
feature.max_block_number,
|
||||
feature.total_gas_used,
|
||||
feature.total_duration_ms as f64 / 1000.0
|
||||
);
|
||||
println!(" NewPayload latency (ms):");
|
||||
println!(
|
||||
" mean: {:.2}, p50: {:.2}, p90: {:.2}, p99: {:.2}",
|
||||
feature.mean_new_payload_latency_ms,
|
||||
feature.median_new_payload_latency_ms,
|
||||
feature.p90_new_payload_latency_ms,
|
||||
feature.p99_new_payload_latency_ms
|
||||
);
|
||||
if let (Some(start), Some(end)) =
|
||||
(&report.feature.start_timestamp, &report.feature.end_timestamp)
|
||||
{
|
||||
println!(
|
||||
" Started: {}, Ended: {}",
|
||||
start.format("%Y-%m-%d %H:%M:%S UTC"),
|
||||
end.format("%Y-%m-%d %H:%M:%S UTC")
|
||||
);
|
||||
}
|
||||
if let Some(ref cmd) = report.feature.reth_command {
|
||||
println!(" Command: {}", cmd);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate standard deviation from a set of values and their mean.
|
||||
///
|
||||
/// Computes the population standard deviation using the formula:
|
||||
/// `sqrt(sum((x - mean)²) / n)`
|
||||
///
|
||||
/// Returns 0.0 for empty input.
|
||||
fn calculate_std_dev(values: &[f64], mean: f64) -> f64 {
|
||||
if values.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let variance = values
|
||||
.iter()
|
||||
.map(|x| {
|
||||
let diff = x - mean;
|
||||
diff * diff
|
||||
})
|
||||
.sum::<f64>() /
|
||||
values.len() as f64;
|
||||
|
||||
variance.sqrt()
|
||||
}
|
||||
|
||||
/// Calculate percentile using linear interpolation on a sorted slice.
|
||||
///
|
||||
/// Computes `rank = percentile × (n - 1)` where n is the array length. If the rank falls
|
||||
/// between two indices, linearly interpolates between those values. For example, with 100 values,
|
||||
/// p90 computes rank = 0.9 × 99 = 89.1, then returns `values[89] × 0.9 + values[90] × 0.1`.
|
||||
///
|
||||
/// Returns 0.0 for empty input.
|
||||
fn percentile(sorted_values: &[f64], percentile: f64) -> f64 {
|
||||
if sorted_values.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let clamped = percentile.clamp(0.0, 1.0);
|
||||
let max_index = sorted_values.len() - 1;
|
||||
let rank = clamped * max_index as f64;
|
||||
let lower = rank.floor() as usize;
|
||||
let upper = rank.ceil() as usize;
|
||||
|
||||
if lower == upper {
|
||||
sorted_values[lower]
|
||||
} else {
|
||||
let weight = rank - lower as f64;
|
||||
sorted_values[lower].mul_add(1.0 - weight, sorted_values[upper] * weight)
|
||||
}
|
||||
}
|
||||
@@ -1,305 +0,0 @@
|
||||
//! Compilation operations for reth and reth-bench.
|
||||
|
||||
use crate::git::GitManager;
|
||||
use eyre::{eyre, Result, WrapErr};
|
||||
use std::{fs, path::PathBuf, process::Command};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
/// Manages compilation operations for reth components
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CompilationManager {
|
||||
repo_root: String,
|
||||
output_dir: PathBuf,
|
||||
git_manager: GitManager,
|
||||
}
|
||||
|
||||
impl CompilationManager {
|
||||
/// Create a new `CompilationManager`
|
||||
pub(crate) const fn new(
|
||||
repo_root: String,
|
||||
output_dir: PathBuf,
|
||||
git_manager: GitManager,
|
||||
) -> Result<Self> {
|
||||
Ok(Self { repo_root, output_dir, git_manager })
|
||||
}
|
||||
|
||||
/// Get the path to the cached binary using explicit commit hash
|
||||
pub(crate) fn get_cached_binary_path_for_commit(&self, commit: &str) -> PathBuf {
|
||||
let identifier = &commit[..8]; // Use first 8 chars of commit
|
||||
self.output_dir.join("bin").join(format!("reth_{identifier}"))
|
||||
}
|
||||
|
||||
/// Compile reth using cargo build and cache the binary
|
||||
pub(crate) fn compile_reth(&self, commit: &str, features: &str, rustflags: &str) -> Result<()> {
|
||||
// Validate that current git commit matches the expected commit
|
||||
let current_commit = self.git_manager.get_current_commit()?;
|
||||
if current_commit != commit {
|
||||
return Err(eyre!(
|
||||
"Git commit mismatch! Expected: {}, but currently at: {}",
|
||||
&commit[..8],
|
||||
¤t_commit[..8]
|
||||
));
|
||||
}
|
||||
|
||||
let cached_path = self.get_cached_binary_path_for_commit(commit);
|
||||
|
||||
// Check if cached binary already exists (since path contains commit hash, it's valid)
|
||||
if cached_path.exists() {
|
||||
info!("Using cached binary (commit: {})", &commit[..8]);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
info!("No cached binary found, compiling (commit: {})...", &commit[..8]);
|
||||
|
||||
let binary_name = "reth";
|
||||
|
||||
info!(
|
||||
"Compiling {} with profiling configuration (commit: {})...",
|
||||
binary_name,
|
||||
&commit[..8]
|
||||
);
|
||||
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.arg("build").arg("--profile").arg("profiling");
|
||||
|
||||
cmd.arg("--features").arg(features);
|
||||
info!("Using features: {features}");
|
||||
|
||||
cmd.current_dir(&self.repo_root);
|
||||
|
||||
// Set RUSTFLAGS
|
||||
cmd.env("RUSTFLAGS", rustflags);
|
||||
info!("Using RUSTFLAGS: {rustflags}");
|
||||
|
||||
info!("Compiling {binary_name} with {cmd:?}");
|
||||
|
||||
let output = cmd.output().wrap_err("Failed to execute cargo build command")?;
|
||||
|
||||
// Print stdout and stderr with prefixes at debug level
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
for line in stdout.lines() {
|
||||
if !line.trim().is_empty() {
|
||||
debug!("[CARGO] {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
for line in stderr.lines() {
|
||||
if !line.trim().is_empty() {
|
||||
debug!("[CARGO] {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
if !output.status.success() {
|
||||
// Print all output when compilation fails
|
||||
error!("Cargo build failed with exit code: {:?}", output.status.code());
|
||||
|
||||
if !stdout.trim().is_empty() {
|
||||
error!("Cargo stdout:");
|
||||
for line in stdout.lines() {
|
||||
error!(" {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
if !stderr.trim().is_empty() {
|
||||
error!("Cargo stderr:");
|
||||
for line in stderr.lines() {
|
||||
error!(" {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
return Err(eyre!("Compilation failed with exit code: {:?}", output.status.code()));
|
||||
}
|
||||
|
||||
info!("{} compilation completed", binary_name);
|
||||
|
||||
// Copy the compiled binary to cache
|
||||
let source_path =
|
||||
PathBuf::from(&self.repo_root).join(format!("target/profiling/{}", binary_name));
|
||||
if !source_path.exists() {
|
||||
return Err(eyre!("Compiled binary not found at {:?}", source_path));
|
||||
}
|
||||
|
||||
// Create bin directory if it doesn't exist
|
||||
let bin_dir = self.output_dir.join("bin");
|
||||
fs::create_dir_all(&bin_dir).wrap_err("Failed to create bin directory")?;
|
||||
|
||||
// Copy binary to cache
|
||||
fs::copy(&source_path, &cached_path).wrap_err("Failed to copy binary to cache")?;
|
||||
|
||||
// Make the cached binary executable
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = fs::metadata(&cached_path)?.permissions();
|
||||
perms.set_mode(0o755);
|
||||
fs::set_permissions(&cached_path, perms)?;
|
||||
}
|
||||
|
||||
info!("Cached compiled binary at: {:?}", cached_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if reth-bench is available in PATH
|
||||
pub(crate) fn is_reth_bench_available(&self) -> bool {
|
||||
match Command::new("which").arg("reth-bench").output() {
|
||||
Ok(output) => {
|
||||
if output.status.success() {
|
||||
let path = String::from_utf8_lossy(&output.stdout);
|
||||
info!("Found reth-bench: {}", path.trim());
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if samply is available in PATH
|
||||
pub(crate) fn is_samply_available(&self) -> bool {
|
||||
match Command::new("which").arg("samply").output() {
|
||||
Ok(output) => {
|
||||
if output.status.success() {
|
||||
let path = String::from_utf8_lossy(&output.stdout);
|
||||
info!("Found samply: {}", path.trim());
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Install samply using cargo
|
||||
pub(crate) fn install_samply(&self) -> Result<()> {
|
||||
info!("Installing samply via cargo...");
|
||||
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.args(["install", "--locked", "samply"]);
|
||||
|
||||
info!("Installing samply with {cmd:?}");
|
||||
|
||||
let output = cmd.output().wrap_err("Failed to execute cargo install samply command")?;
|
||||
|
||||
// Print stdout and stderr with prefixes at debug level
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
for line in stdout.lines() {
|
||||
if !line.trim().is_empty() {
|
||||
debug!("[CARGO-SAMPLY] {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
for line in stderr.lines() {
|
||||
if !line.trim().is_empty() {
|
||||
debug!("[CARGO-SAMPLY] {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
if !output.status.success() {
|
||||
// Print all output when installation fails
|
||||
error!("Cargo install samply failed with exit code: {:?}", output.status.code());
|
||||
|
||||
if !stdout.trim().is_empty() {
|
||||
error!("Cargo stdout:");
|
||||
for line in stdout.lines() {
|
||||
error!(" {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
if !stderr.trim().is_empty() {
|
||||
error!("Cargo stderr:");
|
||||
for line in stderr.lines() {
|
||||
error!(" {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
return Err(eyre!(
|
||||
"samply installation failed with exit code: {:?}",
|
||||
output.status.code()
|
||||
));
|
||||
}
|
||||
|
||||
info!("Samply installation completed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensure samply is available, installing if necessary
|
||||
pub(crate) fn ensure_samply_available(&self) -> Result<()> {
|
||||
if self.is_samply_available() {
|
||||
Ok(())
|
||||
} else {
|
||||
warn!("samply not found in PATH, installing...");
|
||||
self.install_samply()
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure reth-bench is available, compiling if necessary
|
||||
pub(crate) fn ensure_reth_bench_available(&self) -> Result<()> {
|
||||
if self.is_reth_bench_available() {
|
||||
Ok(())
|
||||
} else {
|
||||
warn!("reth-bench not found in PATH, compiling and installing...");
|
||||
self.compile_reth_bench()
|
||||
}
|
||||
}
|
||||
|
||||
/// Compile and install reth-bench using `make install-reth-bench`
|
||||
pub(crate) fn compile_reth_bench(&self) -> Result<()> {
|
||||
info!("Compiling and installing reth-bench...");
|
||||
|
||||
let mut cmd = Command::new("make");
|
||||
cmd.arg("install-reth-bench").current_dir(&self.repo_root);
|
||||
|
||||
info!("Compiling reth-bench with {cmd:?}");
|
||||
|
||||
let output = cmd.output().wrap_err("Failed to execute make install-reth-bench command")?;
|
||||
|
||||
// Print stdout and stderr with prefixes at debug level
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
for line in stdout.lines() {
|
||||
if !line.trim().is_empty() {
|
||||
debug!("[MAKE-BENCH] {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
for line in stderr.lines() {
|
||||
if !line.trim().is_empty() {
|
||||
debug!("[MAKE-BENCH] {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
if !output.status.success() {
|
||||
// Print all output when compilation fails
|
||||
error!("Make install-reth-bench failed with exit code: {:?}", output.status.code());
|
||||
|
||||
if !stdout.trim().is_empty() {
|
||||
error!("Make stdout:");
|
||||
for line in stdout.lines() {
|
||||
error!(" {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
if !stderr.trim().is_empty() {
|
||||
error!("Make stderr:");
|
||||
for line in stderr.lines() {
|
||||
error!(" {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
return Err(eyre!(
|
||||
"reth-bench compilation failed with exit code: {:?}",
|
||||
output.status.code()
|
||||
));
|
||||
}
|
||||
|
||||
info!("Reth-bench compilation completed");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,328 +0,0 @@
|
||||
//! Git operations for branch management.
|
||||
|
||||
use eyre::{eyre, Result, WrapErr};
|
||||
use std::process::Command;
|
||||
use tracing::{info, warn};
|
||||
|
||||
/// Manages git operations for branch switching
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct GitManager {
|
||||
repo_root: String,
|
||||
}
|
||||
|
||||
impl GitManager {
|
||||
/// Create a new `GitManager`, detecting the repository root
|
||||
pub(crate) fn new() -> Result<Self> {
|
||||
let output = Command::new("git")
|
||||
.args(["rev-parse", "--show-toplevel"])
|
||||
.output()
|
||||
.wrap_err("Failed to execute git command - is git installed?")?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(eyre!("Not in a git repository or git command failed"));
|
||||
}
|
||||
|
||||
let repo_root = String::from_utf8(output.stdout)
|
||||
.wrap_err("Git output is not valid UTF-8")?
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
let manager = Self { repo_root };
|
||||
info!(
|
||||
"Detected git repository at: {}, current reference: {}",
|
||||
manager.repo_root(),
|
||||
manager.get_current_ref()?
|
||||
);
|
||||
|
||||
Ok(manager)
|
||||
}
|
||||
|
||||
/// Get the current git branch name
|
||||
pub(crate) fn get_current_branch(&self) -> Result<String> {
|
||||
let output = Command::new("git")
|
||||
.args(["branch", "--show-current"])
|
||||
.current_dir(&self.repo_root)
|
||||
.output()
|
||||
.wrap_err("Failed to get current branch")?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(eyre!("Failed to determine current branch"));
|
||||
}
|
||||
|
||||
let branch = String::from_utf8(output.stdout)
|
||||
.wrap_err("Branch name is not valid UTF-8")?
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
if branch.is_empty() {
|
||||
return Err(eyre!("Not on a named branch (detached HEAD?)"));
|
||||
}
|
||||
|
||||
Ok(branch)
|
||||
}
|
||||
|
||||
/// Get the current git reference (branch name, tag, or commit hash)
|
||||
pub(crate) fn get_current_ref(&self) -> Result<String> {
|
||||
// First try to get branch name
|
||||
if let Ok(branch) = self.get_current_branch() {
|
||||
return Ok(branch);
|
||||
}
|
||||
|
||||
// If not on a branch, check if we're on a tag
|
||||
let tag_output = Command::new("git")
|
||||
.args(["describe", "--exact-match", "--tags", "HEAD"])
|
||||
.current_dir(&self.repo_root)
|
||||
.output()
|
||||
.wrap_err("Failed to check for tag")?;
|
||||
|
||||
if tag_output.status.success() {
|
||||
let tag = String::from_utf8(tag_output.stdout)
|
||||
.wrap_err("Tag name is not valid UTF-8")?
|
||||
.trim()
|
||||
.to_string();
|
||||
return Ok(tag);
|
||||
}
|
||||
|
||||
// If not on a branch or tag, return the commit hash
|
||||
let commit_output = Command::new("git")
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.current_dir(&self.repo_root)
|
||||
.output()
|
||||
.wrap_err("Failed to get current commit")?;
|
||||
|
||||
if !commit_output.status.success() {
|
||||
return Err(eyre!("Failed to get current commit hash"));
|
||||
}
|
||||
|
||||
let commit_hash = String::from_utf8(commit_output.stdout)
|
||||
.wrap_err("Commit hash is not valid UTF-8")?
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
Ok(commit_hash)
|
||||
}
|
||||
|
||||
/// Check if the git working directory has uncommitted changes to tracked files
|
||||
pub(crate) fn validate_clean_state(&self) -> Result<()> {
|
||||
let output = Command::new("git")
|
||||
.args(["status", "--porcelain"])
|
||||
.current_dir(&self.repo_root)
|
||||
.output()
|
||||
.wrap_err("Failed to check git status")?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(eyre!("Git status command failed"));
|
||||
}
|
||||
|
||||
let status_output =
|
||||
String::from_utf8(output.stdout).wrap_err("Git status output is not valid UTF-8")?;
|
||||
|
||||
// Check for uncommitted changes to tracked files
|
||||
// Status codes: M = modified, A = added, D = deleted, R = renamed, C = copied, U = updated
|
||||
// ?? = untracked files (we want to ignore these)
|
||||
let has_uncommitted_changes = status_output.lines().any(|line| {
|
||||
if line.len() >= 2 {
|
||||
let status = &line[0..2];
|
||||
// Ignore untracked files (??) and ignored files (!!)
|
||||
!matches!(status, "??" | "!!")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
if has_uncommitted_changes {
|
||||
warn!("Git working directory has uncommitted changes to tracked files:");
|
||||
for line in status_output.lines() {
|
||||
if line.len() >= 2 && !matches!(&line[0..2], "??" | "!!") {
|
||||
warn!(" {}", line);
|
||||
}
|
||||
}
|
||||
return Err(eyre!(
|
||||
"Git working directory has uncommitted changes to tracked files. Please commit or stash changes before running benchmark comparison."
|
||||
));
|
||||
}
|
||||
|
||||
// Check if there are untracked files and log them as info
|
||||
let untracked_files: Vec<&str> =
|
||||
status_output.lines().filter(|line| line.starts_with("??")).collect();
|
||||
|
||||
if !untracked_files.is_empty() {
|
||||
info!(
|
||||
"Git working directory has {} untracked files (this is OK)",
|
||||
untracked_files.len()
|
||||
);
|
||||
}
|
||||
|
||||
info!("Git working directory is clean (no uncommitted changes to tracked files)");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fetch all refs from remote to ensure we have latest branches and tags
|
||||
pub(crate) fn fetch_all(&self) -> Result<()> {
|
||||
let output = Command::new("git")
|
||||
.args(["fetch", "--all", "--tags", "--quiet", "--force"])
|
||||
.current_dir(&self.repo_root)
|
||||
.output()
|
||||
.wrap_err("Failed to fetch latest refs")?;
|
||||
|
||||
if output.status.success() {
|
||||
info!("Fetched latest refs");
|
||||
} else {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
// Only warn if there's actual error content, not just fetch progress
|
||||
if !stderr.trim().is_empty() && !stderr.contains("-> origin/") {
|
||||
warn!("Git fetch encountered issues (continuing anyway): {}", stderr);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate that the specified git references exist (branches, tags, or commits)
|
||||
pub(crate) fn validate_refs(&self, refs: &[&str]) -> Result<()> {
|
||||
for &git_ref in refs {
|
||||
// Try to resolve the ref similar to `git checkout` by peeling to a commit.
|
||||
// First try the ref as-is with ^{commit}, then fall back to origin/{ref}^{commit}.
|
||||
let as_is = format!("{git_ref}^{{commit}}");
|
||||
let ref_check = Command::new("git")
|
||||
.args(["rev-parse", "--verify", &as_is])
|
||||
.current_dir(&self.repo_root)
|
||||
.output();
|
||||
|
||||
let found = if let Ok(output) = ref_check &&
|
||||
output.status.success()
|
||||
{
|
||||
info!("Validated reference exists: {}", git_ref);
|
||||
true
|
||||
} else {
|
||||
// Try remote-only branches via origin/{ref}
|
||||
let origin_ref = format!("origin/{git_ref}^{{commit}}");
|
||||
let origin_check = Command::new("git")
|
||||
.args(["rev-parse", "--verify", &origin_ref])
|
||||
.current_dir(&self.repo_root)
|
||||
.output();
|
||||
|
||||
if let Ok(output) = origin_check &&
|
||||
output.status.success()
|
||||
{
|
||||
info!("Validated remote reference exists: origin/{}", git_ref);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if !found {
|
||||
return Err(eyre!(
|
||||
"Git reference '{}' does not exist as branch, tag, or commit (tried '{}' and 'origin/{}^{{commit}}')",
|
||||
git_ref,
|
||||
format!("{git_ref}^{{commit}}"),
|
||||
git_ref,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Switch to the specified git reference (branch, tag, or commit)
|
||||
pub(crate) fn switch_ref(&self, git_ref: &str) -> Result<()> {
|
||||
// First checkout the reference
|
||||
let output = Command::new("git")
|
||||
.args(["checkout", git_ref])
|
||||
.current_dir(&self.repo_root)
|
||||
.output()
|
||||
.wrap_err_with(|| format!("Failed to switch to reference '{git_ref}'"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(eyre!("Failed to switch to reference '{}': {}", git_ref, stderr));
|
||||
}
|
||||
|
||||
// Check if this is a branch that tracks a remote and pull latest changes
|
||||
let is_branch = Command::new("git")
|
||||
.args(["show-ref", "--verify", "--quiet", &format!("refs/heads/{git_ref}")])
|
||||
.current_dir(&self.repo_root)
|
||||
.status()
|
||||
.map(|s| s.success())
|
||||
.unwrap_or(false);
|
||||
|
||||
if is_branch {
|
||||
// Check if the branch tracks a remote
|
||||
let tracking_output = Command::new("git")
|
||||
.args([
|
||||
"rev-parse",
|
||||
"--abbrev-ref",
|
||||
"--symbolic-full-name",
|
||||
&format!("{git_ref}@{{upstream}}"),
|
||||
])
|
||||
.current_dir(&self.repo_root)
|
||||
.output();
|
||||
|
||||
if let Ok(output) = tracking_output &&
|
||||
output.status.success()
|
||||
{
|
||||
let upstream = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
if !upstream.is_empty() && upstream != format!("{git_ref}@{{upstream}}") {
|
||||
// Branch tracks a remote, pull latest changes
|
||||
info!("Pulling latest changes for branch: {}", git_ref);
|
||||
|
||||
let pull_output = Command::new("git")
|
||||
.args(["pull", "--ff-only"])
|
||||
.current_dir(&self.repo_root)
|
||||
.output()
|
||||
.wrap_err_with(|| {
|
||||
format!("Failed to pull latest changes for branch '{git_ref}'")
|
||||
})?;
|
||||
|
||||
if pull_output.status.success() {
|
||||
info!("Successfully pulled latest changes for branch: {}", git_ref);
|
||||
} else {
|
||||
let stderr = String::from_utf8_lossy(&pull_output.stderr);
|
||||
warn!("Failed to pull latest changes for branch '{}': {}", git_ref, stderr);
|
||||
// Continue anyway, we'll use whatever version we have
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the checkout succeeded by checking the current commit
|
||||
let current_commit_output = Command::new("git")
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.current_dir(&self.repo_root)
|
||||
.output()
|
||||
.wrap_err("Failed to get current commit")?;
|
||||
|
||||
if !current_commit_output.status.success() {
|
||||
return Err(eyre!("Failed to verify git checkout"));
|
||||
}
|
||||
|
||||
info!("Switched to reference: {}", git_ref);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the current commit hash
|
||||
pub(crate) fn get_current_commit(&self) -> Result<String> {
|
||||
let output = Command::new("git")
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.current_dir(&self.repo_root)
|
||||
.output()
|
||||
.wrap_err("Failed to get current commit")?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(eyre!("Failed to get current commit hash"));
|
||||
}
|
||||
|
||||
let commit_hash = String::from_utf8(output.stdout)
|
||||
.wrap_err("Commit hash is not valid UTF-8")?
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
Ok(commit_hash)
|
||||
}
|
||||
|
||||
/// Get the repository root path
|
||||
pub(crate) fn repo_root(&self) -> &str {
|
||||
&self.repo_root
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
//! # reth-bench-compare
|
||||
//!
|
||||
//! Automated tool for comparing reth performance between two git branches.
|
||||
//! This tool automates the complete workflow of compiling, running, and benchmarking
|
||||
//! reth on different branches to provide meaningful performance comparisons.
|
||||
|
||||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
|
||||
html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
|
||||
issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
|
||||
)]
|
||||
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator();
|
||||
|
||||
use alloy_primitives as _;
|
||||
|
||||
mod benchmark;
|
||||
mod cli;
|
||||
mod comparison;
|
||||
mod compilation;
|
||||
mod git;
|
||||
mod node;
|
||||
|
||||
use clap::Parser;
|
||||
use cli::{run_comparison, Args};
|
||||
use eyre::Result;
|
||||
use reth_cli_runner::CliRunner;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// Enable backtraces unless a RUST_BACKTRACE value has already been explicitly provided.
|
||||
if std::env::var_os("RUST_BACKTRACE").is_none() {
|
||||
unsafe {
|
||||
std::env::set_var("RUST_BACKTRACE", "1");
|
||||
}
|
||||
}
|
||||
|
||||
let args = Args::parse();
|
||||
|
||||
// Initialize tracing
|
||||
let _guard = args.init_tracing()?;
|
||||
|
||||
// Run until either exit or sigint or sigterm
|
||||
let runner = CliRunner::try_default_runtime()?;
|
||||
runner.run_command_until_exit(|ctx| run_comparison(args, ctx))
|
||||
}
|
||||
@@ -1,695 +0,0 @@
|
||||
//! Node management for starting, stopping, and controlling reth instances.
|
||||
|
||||
use crate::cli::Args;
|
||||
use alloy_provider::{Provider, ProviderBuilder};
|
||||
use alloy_rpc_client::RpcClient;
|
||||
use alloy_rpc_types_eth::SyncStatus;
|
||||
use alloy_transport_ws::WsConnect;
|
||||
use eyre::{eyre, OptionExt, Result, WrapErr};
|
||||
#[cfg(unix)]
|
||||
use nix::sys::signal::{killpg, Signal};
|
||||
#[cfg(unix)]
|
||||
use nix::unistd::Pid;
|
||||
use reth_chainspec::Chain;
|
||||
use std::{fs, path::PathBuf, time::Duration};
|
||||
use tokio::{
|
||||
fs::File as AsyncFile,
|
||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader as AsyncBufReader},
|
||||
process::Command,
|
||||
time::{sleep, timeout},
|
||||
};
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
/// Default websocket RPC port used by reth
|
||||
const DEFAULT_WS_RPC_PORT: u16 = 8546;
|
||||
|
||||
/// Manages reth node lifecycle and operations
|
||||
pub(crate) struct NodeManager {
|
||||
datadir: Option<String>,
|
||||
metrics_port: u16,
|
||||
chain: Chain,
|
||||
use_sudo: bool,
|
||||
binary_path: Option<std::path::PathBuf>,
|
||||
enable_profiling: bool,
|
||||
output_dir: PathBuf,
|
||||
additional_reth_args: Vec<String>,
|
||||
comparison_dir: Option<PathBuf>,
|
||||
tracing_endpoint: Option<String>,
|
||||
otlp_max_queue_size: usize,
|
||||
}
|
||||
|
||||
impl NodeManager {
|
||||
/// Create a new `NodeManager` with configuration from CLI args
|
||||
pub(crate) fn new(args: &Args) -> Self {
|
||||
Self {
|
||||
datadir: Some(args.datadir_path().to_string_lossy().to_string()),
|
||||
metrics_port: args.metrics_port,
|
||||
chain: args.chain,
|
||||
use_sudo: args.sudo,
|
||||
binary_path: None,
|
||||
enable_profiling: args.profile,
|
||||
output_dir: args.output_dir_path(),
|
||||
// Filter out empty strings to prevent invalid arguments being passed to reth node
|
||||
additional_reth_args: args
|
||||
.reth_args
|
||||
.iter()
|
||||
.filter(|s| !s.is_empty())
|
||||
.cloned()
|
||||
.collect(),
|
||||
comparison_dir: None,
|
||||
tracing_endpoint: args.traces.otlp.as_ref().map(|u| u.to_string()),
|
||||
otlp_max_queue_size: args.otlp_max_queue_size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the comparison directory path for logging
|
||||
pub(crate) fn set_comparison_dir(&mut self, dir: PathBuf) {
|
||||
self.comparison_dir = Some(dir);
|
||||
}
|
||||
|
||||
/// Get the log file path for a given reference type
|
||||
fn get_log_file_path(&self, ref_type: &str) -> Result<PathBuf> {
|
||||
let comparison_dir = self
|
||||
.comparison_dir
|
||||
.as_ref()
|
||||
.ok_or_eyre("Comparison directory not set. Call set_comparison_dir first.")?;
|
||||
|
||||
// The comparison directory already contains the full path to results/<timestamp>
|
||||
let log_dir = comparison_dir.join(ref_type);
|
||||
|
||||
// Create the directory if it doesn't exist
|
||||
fs::create_dir_all(&log_dir)
|
||||
.wrap_err(format!("Failed to create log directory: {:?}", log_dir))?;
|
||||
|
||||
let log_file = log_dir.join("reth_node.log");
|
||||
Ok(log_file)
|
||||
}
|
||||
|
||||
/// Get the perf event max sample rate from the system, capped at 10000
|
||||
fn get_perf_sample_rate(&self) -> Option<String> {
|
||||
let perf_rate_file = "/proc/sys/kernel/perf_event_max_sample_rate";
|
||||
if let Ok(content) = fs::read_to_string(perf_rate_file) {
|
||||
let rate_str = content.trim();
|
||||
if !rate_str.is_empty() {
|
||||
if let Ok(system_rate) = rate_str.parse::<u32>() {
|
||||
let capped_rate = std::cmp::min(system_rate, 10000);
|
||||
info!(
|
||||
"Detected perf_event_max_sample_rate: {}, using: {}",
|
||||
system_rate, capped_rate
|
||||
);
|
||||
return Some(capped_rate.to_string());
|
||||
}
|
||||
warn!("Failed to parse perf_event_max_sample_rate: {}", rate_str);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the absolute path to samply using 'which' command
|
||||
async fn get_samply_path(&self) -> Result<String> {
|
||||
let output = Command::new("which")
|
||||
.arg("samply")
|
||||
.output()
|
||||
.await
|
||||
.wrap_err("Failed to execute 'which samply' command")?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(eyre!("samply not found in PATH"));
|
||||
}
|
||||
|
||||
let samply_path = String::from_utf8(output.stdout)
|
||||
.wrap_err("samply path is not valid UTF-8")?
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
if samply_path.is_empty() {
|
||||
return Err(eyre!("which samply returned empty path"));
|
||||
}
|
||||
|
||||
Ok(samply_path)
|
||||
}
|
||||
|
||||
/// Build reth arguments as a vector of strings
|
||||
fn build_reth_args(
|
||||
&self,
|
||||
binary_path_str: &str,
|
||||
additional_args: &[String],
|
||||
ref_type: &str,
|
||||
) -> (Vec<String>, String) {
|
||||
let mut reth_args = vec![binary_path_str.to_string(), "node".to_string()];
|
||||
|
||||
// Add chain argument (skip for mainnet as it's the default)
|
||||
let chain_str = self.chain.to_string();
|
||||
if chain_str != "mainnet" {
|
||||
reth_args.extend_from_slice(&["--chain".to_string(), chain_str.clone()]);
|
||||
}
|
||||
|
||||
// Add datadir if specified
|
||||
if let Some(ref datadir) = self.datadir {
|
||||
reth_args.extend_from_slice(&["--datadir".to_string(), datadir.clone()]);
|
||||
}
|
||||
|
||||
// Add reth-specific arguments
|
||||
let metrics_arg = format!("0.0.0.0:{}", self.metrics_port);
|
||||
reth_args.extend_from_slice(&[
|
||||
"--engine.accept-execution-requests-hash".to_string(),
|
||||
"--metrics".to_string(),
|
||||
metrics_arg,
|
||||
"--http".to_string(),
|
||||
"--http.api".to_string(),
|
||||
"eth,reth".to_string(),
|
||||
"--ws".to_string(),
|
||||
"--ws.api".to_string(),
|
||||
"eth,reth".to_string(),
|
||||
"--disable-discovery".to_string(),
|
||||
"--trusted-only".to_string(),
|
||||
"--disable-tx-gossip".to_string(),
|
||||
]);
|
||||
|
||||
// Add tracing arguments if OTLP endpoint is configured
|
||||
if let Some(ref endpoint) = self.tracing_endpoint {
|
||||
info!("Enabling OTLP tracing export to: {} (service: reth-{})", endpoint, ref_type);
|
||||
// Endpoint requires equals per clap settings in reth
|
||||
reth_args.push(format!("--tracing-otlp={}", endpoint));
|
||||
}
|
||||
|
||||
// Add any additional arguments passed via command line (common to both baseline and
|
||||
// feature)
|
||||
reth_args.extend_from_slice(&self.additional_reth_args);
|
||||
|
||||
// Add reference-specific additional arguments
|
||||
reth_args.extend_from_slice(additional_args);
|
||||
|
||||
(reth_args, chain_str)
|
||||
}
|
||||
|
||||
/// Create a command for profiling mode
|
||||
async fn create_profiling_command(
|
||||
&self,
|
||||
ref_type: &str,
|
||||
reth_args: &[String],
|
||||
) -> Result<Command> {
|
||||
// Create profiles directory if it doesn't exist
|
||||
let profile_dir = self.output_dir.join("profiles");
|
||||
fs::create_dir_all(&profile_dir).wrap_err("Failed to create profiles directory")?;
|
||||
|
||||
let profile_path = profile_dir.join(format!("{}.json.gz", ref_type));
|
||||
info!("Starting reth node with samply profiling...");
|
||||
info!("Profile output: {:?}", profile_path);
|
||||
|
||||
// Get absolute path to samply
|
||||
let samply_path = self.get_samply_path().await?;
|
||||
|
||||
let mut cmd = if self.use_sudo {
|
||||
let mut sudo_cmd = Command::new("sudo");
|
||||
sudo_cmd.arg(&samply_path);
|
||||
sudo_cmd
|
||||
} else {
|
||||
Command::new(&samply_path)
|
||||
};
|
||||
|
||||
// Add samply arguments
|
||||
cmd.args(["record", "--save-only", "-o", &profile_path.to_string_lossy()]);
|
||||
|
||||
// Add rate argument if available
|
||||
if let Some(rate) = self.get_perf_sample_rate() {
|
||||
cmd.args(["--rate", &rate]);
|
||||
}
|
||||
|
||||
// Add separator and complete reth command
|
||||
cmd.arg("--");
|
||||
cmd.args(reth_args);
|
||||
|
||||
// Enable tracing-samply
|
||||
if supports_samply_flags(&reth_args[0]) {
|
||||
cmd.arg("--log.samply");
|
||||
}
|
||||
|
||||
// Set environment variable to disable log styling
|
||||
cmd.env("RUST_LOG_STYLE", "never");
|
||||
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
/// Create a command for direct reth execution
|
||||
fn create_direct_command(&self, reth_args: &[String]) -> Command {
|
||||
let binary_path = &reth_args[0];
|
||||
|
||||
let mut cmd = if self.use_sudo {
|
||||
info!("Starting reth node with sudo...");
|
||||
let mut sudo_cmd = Command::new("sudo");
|
||||
sudo_cmd.args(reth_args);
|
||||
sudo_cmd
|
||||
} else {
|
||||
info!("Starting reth node...");
|
||||
let mut reth_cmd = Command::new(binary_path);
|
||||
reth_cmd.args(&reth_args[1..]); // Skip the binary path since it's the command
|
||||
reth_cmd
|
||||
};
|
||||
|
||||
// Set environment variable to disable log styling
|
||||
cmd.env("RUST_LOG_STYLE", "never");
|
||||
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Start a reth node using the specified binary path and return the process handle
|
||||
/// along with the formatted reth command string for reporting.
|
||||
pub(crate) async fn start_node(
|
||||
&mut self,
|
||||
binary_path: &std::path::Path,
|
||||
_git_ref: &str,
|
||||
ref_type: &str,
|
||||
additional_args: &[String],
|
||||
) -> Result<(tokio::process::Child, String)> {
|
||||
// Store the binary path for later use (e.g., in unwind_to_block)
|
||||
self.binary_path = Some(binary_path.to_path_buf());
|
||||
|
||||
let binary_path_str = binary_path.to_string_lossy();
|
||||
let (reth_args, _) = self.build_reth_args(&binary_path_str, additional_args, ref_type);
|
||||
|
||||
// Format the reth command string for reporting
|
||||
let reth_command = shlex::try_join(reth_args.iter().map(|s| s.as_str()))
|
||||
.wrap_err("Failed to format reth command string")?;
|
||||
|
||||
// Log additional arguments if any
|
||||
if !self.additional_reth_args.is_empty() {
|
||||
info!("Using common additional reth arguments: {:?}", self.additional_reth_args);
|
||||
}
|
||||
if !additional_args.is_empty() {
|
||||
info!("Using reference-specific additional reth arguments: {:?}", additional_args);
|
||||
}
|
||||
|
||||
let mut cmd = if self.enable_profiling {
|
||||
self.create_profiling_command(ref_type, &reth_args).await?
|
||||
} else {
|
||||
self.create_direct_command(&reth_args)
|
||||
};
|
||||
|
||||
// Set process group for better signal handling
|
||||
#[cfg(unix)]
|
||||
{
|
||||
cmd.process_group(0);
|
||||
}
|
||||
|
||||
// Set high queue size to prevent trace dropping during benchmarks
|
||||
if self.tracing_endpoint.is_some() {
|
||||
cmd.env("OTEL_BSP_MAX_QUEUE_SIZE", self.otlp_max_queue_size.to_string()); // Traces
|
||||
cmd.env("OTEL_BLRP_MAX_QUEUE_SIZE", "10000"); // Logs
|
||||
|
||||
// Set service name to differentiate baseline vs feature runs in Jaeger
|
||||
cmd.env("OTEL_SERVICE_NAME", format!("reth-{}", ref_type));
|
||||
}
|
||||
|
||||
debug!("Executing reth command: {cmd:?}");
|
||||
|
||||
let mut child = cmd
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.kill_on_drop(true) // Kill on drop so that on Ctrl-C for parent process we stop all child processes
|
||||
.spawn()
|
||||
.wrap_err("Failed to start reth node")?;
|
||||
|
||||
info!(
|
||||
"Reth node started with PID: {:?} (binary: {})",
|
||||
child.id().ok_or_eyre("Reth node is not running")?,
|
||||
binary_path_str
|
||||
);
|
||||
|
||||
// Prepare log file path
|
||||
let log_file_path = self.get_log_file_path(ref_type)?;
|
||||
info!("Reth node logs will be saved to: {:?}", log_file_path);
|
||||
|
||||
// Stream stdout and stderr with prefixes at debug level and to log file
|
||||
if let Some(stdout) = child.stdout.take() {
|
||||
let log_file = AsyncFile::create(&log_file_path)
|
||||
.await
|
||||
.wrap_err(format!("Failed to create log file: {:?}", log_file_path))?;
|
||||
tokio::spawn(async move {
|
||||
let reader = AsyncBufReader::new(stdout);
|
||||
let mut lines = reader.lines();
|
||||
let mut log_file = log_file;
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
debug!("[RETH] {}", line);
|
||||
// Write to log file (reth already includes timestamps)
|
||||
let log_line = format!("{}\n", line);
|
||||
if let Err(e) = log_file.write_all(log_line.as_bytes()).await {
|
||||
debug!("Failed to write to log file: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(stderr) = child.stderr.take() {
|
||||
let log_file = AsyncFile::options()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&log_file_path)
|
||||
.await
|
||||
.wrap_err(format!("Failed to open log file for stderr: {:?}", log_file_path))?;
|
||||
tokio::spawn(async move {
|
||||
let reader = AsyncBufReader::new(stderr);
|
||||
let mut lines = reader.lines();
|
||||
let mut log_file = log_file;
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
debug!("[RETH] {}", line);
|
||||
// Write to log file (reth already includes timestamps)
|
||||
let log_line = format!("{}\n", line);
|
||||
if let Err(e) = log_file.write_all(log_line.as_bytes()).await {
|
||||
debug!("Failed to write to log file: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Give the node a moment to start up
|
||||
sleep(Duration::from_secs(5)).await;
|
||||
|
||||
Ok((child, reth_command))
|
||||
}
|
||||
|
||||
/// Wait for the node to be ready and return its current tip.
|
||||
///
|
||||
/// Fails early if the node process exits before becoming ready.
|
||||
pub(crate) async fn wait_for_node_ready_and_get_tip(
|
||||
&self,
|
||||
child: &mut tokio::process::Child,
|
||||
) -> Result<u64> {
|
||||
info!("Waiting for node to be ready and synced...");
|
||||
|
||||
let max_wait = Duration::from_secs(120); // 2 minutes to allow for sync
|
||||
let check_interval = Duration::from_secs(2);
|
||||
let rpc_url = "http://localhost:8545";
|
||||
|
||||
// Create Alloy provider
|
||||
let url = rpc_url.parse().map_err(|e| eyre!("Invalid RPC URL '{}': {}", rpc_url, e))?;
|
||||
let provider = ProviderBuilder::new().connect_http(url);
|
||||
|
||||
let start_time = tokio::time::Instant::now();
|
||||
let mut iteration = 0;
|
||||
|
||||
timeout(max_wait, async {
|
||||
loop {
|
||||
iteration += 1;
|
||||
debug!(
|
||||
"Readiness check iteration {} (elapsed: {:?})",
|
||||
iteration,
|
||||
start_time.elapsed()
|
||||
);
|
||||
|
||||
// Check if the node process has exited.
|
||||
if let Some(status) = child.try_wait()? {
|
||||
return Err(eyre!("Node process exited unexpectedly with {status}"));
|
||||
}
|
||||
|
||||
// First check if RPC is up and node is not syncing
|
||||
match provider.syncing().await {
|
||||
Ok(sync_result) => {
|
||||
match sync_result {
|
||||
SyncStatus::Info(sync_info) => {
|
||||
debug!("Node is still syncing {sync_info:?}, waiting...");
|
||||
}
|
||||
_ => {
|
||||
debug!("HTTP RPC is up and node is not syncing, checking block number...");
|
||||
// Node is not syncing, now get the tip
|
||||
match provider.get_block_number().await {
|
||||
Ok(tip) => {
|
||||
debug!("HTTP RPC ready at block: {}, checking WebSocket...", tip);
|
||||
|
||||
// Verify WebSocket RPC is ready (public endpoint, no JWT required)
|
||||
let ws_url = format!("ws://localhost:{}", DEFAULT_WS_RPC_PORT);
|
||||
debug!("Attempting WebSocket connection to {} (public endpoint)", ws_url);
|
||||
let ws_connect = WsConnect::new(&ws_url);
|
||||
|
||||
match RpcClient::connect_pubsub(ws_connect).await
|
||||
{
|
||||
Ok(_) => {
|
||||
info!(
|
||||
"Node is ready (HTTP and WebSocket) at block: {} (took {:?}, {} iterations)",
|
||||
tip, start_time.elapsed(), iteration
|
||||
);
|
||||
return Ok(tip);
|
||||
}
|
||||
Err(e) => {
|
||||
debug!(
|
||||
"HTTP RPC ready but WebSocket not ready yet (iteration {}): {:?}",
|
||||
iteration, e
|
||||
);
|
||||
debug!("WebSocket error details: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Failed to get block number (iteration {}): {:?}", iteration, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Node RPC not ready yet or failed to check sync status (iteration {}): {:?}", iteration, e);
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Sleeping for {:?} before next check", check_interval);
|
||||
sleep(check_interval).await;
|
||||
}
|
||||
})
|
||||
.await
|
||||
.wrap_err("Timed out waiting for node to be ready and synced")?
|
||||
}
|
||||
|
||||
/// Wait for the node RPC to be ready and return its current tip, without waiting for sync.
|
||||
///
|
||||
/// This is faster than `wait_for_node_ready_and_get_tip` but may return a tip while
|
||||
/// the node is still syncing.
|
||||
pub(crate) async fn wait_for_rpc_and_get_tip(
|
||||
&self,
|
||||
child: &mut tokio::process::Child,
|
||||
) -> Result<u64> {
|
||||
info!("Waiting for node RPC to be ready (skipping sync wait)...");
|
||||
|
||||
let max_wait = Duration::from_secs(60);
|
||||
let check_interval = Duration::from_secs(2);
|
||||
let rpc_url = "http://localhost:8545";
|
||||
|
||||
let url = rpc_url.parse().map_err(|e| eyre!("Invalid RPC URL '{}': {}", rpc_url, e))?;
|
||||
let provider = ProviderBuilder::new().connect_http(url);
|
||||
|
||||
let start_time = tokio::time::Instant::now();
|
||||
let mut iteration = 0;
|
||||
|
||||
timeout(max_wait, async {
|
||||
loop {
|
||||
iteration += 1;
|
||||
debug!(
|
||||
"RPC readiness check iteration {} (elapsed: {:?})",
|
||||
iteration,
|
||||
start_time.elapsed()
|
||||
);
|
||||
|
||||
if let Some(status) = child.try_wait()? {
|
||||
return Err(eyre!("Node process exited unexpectedly with {status}"));
|
||||
}
|
||||
|
||||
match provider.get_block_number().await {
|
||||
Ok(tip) => {
|
||||
debug!("HTTP RPC ready at block: {}, checking WebSocket...", tip);
|
||||
|
||||
let ws_url = format!("ws://localhost:{}", DEFAULT_WS_RPC_PORT);
|
||||
let ws_connect = WsConnect::new(&ws_url);
|
||||
|
||||
match RpcClient::connect_pubsub(ws_connect).await {
|
||||
Ok(_) => {
|
||||
info!(
|
||||
"Node RPC is ready at block: {} (took {:?}, {} iterations)",
|
||||
tip,
|
||||
start_time.elapsed(),
|
||||
iteration
|
||||
);
|
||||
return Ok(tip);
|
||||
}
|
||||
Err(e) => {
|
||||
debug!(
|
||||
"HTTP RPC ready but WebSocket not ready yet (iteration {}): {:?}",
|
||||
iteration, e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("RPC not ready yet (iteration {}): {:?}", iteration, e);
|
||||
}
|
||||
}
|
||||
|
||||
sleep(check_interval).await;
|
||||
}
|
||||
})
|
||||
.await
|
||||
.wrap_err("Timed out waiting for node RPC to be ready")?
|
||||
}
|
||||
|
||||
/// Stop the reth node gracefully
|
||||
pub(crate) async fn stop_node(&self, child: &mut tokio::process::Child) -> Result<()> {
|
||||
let pid = child.id().ok_or_eyre("Child process ID should be available")?;
|
||||
|
||||
// Check if the process has already exited
|
||||
match child.try_wait() {
|
||||
Ok(Some(status)) => {
|
||||
info!("Reth node (PID: {}) has already exited with status: {:?}", pid, status);
|
||||
return Ok(());
|
||||
}
|
||||
Ok(None) => {
|
||||
// Process is still running, proceed to stop it
|
||||
info!("Stopping process gracefully with SIGINT (PID: {})...", pid);
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(eyre!("Failed to check process status: {}", e));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
// Send SIGINT to process group to mimic Ctrl-C behavior
|
||||
let nix_pgid = Pid::from_raw(pid as i32);
|
||||
|
||||
match killpg(nix_pgid, Signal::SIGINT) {
|
||||
Ok(()) => {}
|
||||
Err(nix::errno::Errno::ESRCH) => {
|
||||
info!("Process group {} has already exited", pid);
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(eyre!("Failed to send SIGINT to process group {}: {}", pid, e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
// On non-Unix systems, fall back to using external kill command
|
||||
let output = Command::new("taskkill")
|
||||
.args(["/PID", &pid.to_string(), "/F"])
|
||||
.output()
|
||||
.await
|
||||
.wrap_err("Failed to execute taskkill command")?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
// Check if the error is because the process doesn't exist
|
||||
if stderr.contains("not found") || stderr.contains("not exist") {
|
||||
info!("Process {} has already exited", pid);
|
||||
} else {
|
||||
return Err(eyre!("Failed to kill process {}: {}", pid, stderr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the process to exit
|
||||
match child.wait().await {
|
||||
Ok(status) => {
|
||||
info!("Reth node (PID: {}) exited with status: {:?}", pid, status);
|
||||
}
|
||||
Err(e) => {
|
||||
// If we get an error here, it might be because the process already exited
|
||||
debug!("Error waiting for process exit (may have already exited): {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unwind the node to a specific block
|
||||
pub(crate) async fn unwind_to_block(&self, block_number: u64) -> Result<()> {
|
||||
if self.use_sudo {
|
||||
info!("Unwinding node to block: {} (with sudo)", block_number);
|
||||
} else {
|
||||
info!("Unwinding node to block: {}", block_number);
|
||||
}
|
||||
|
||||
// Use the binary path from the last start_node call, or fallback to default
|
||||
let binary_path = self
|
||||
.binary_path
|
||||
.as_ref()
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| "./target/profiling/reth".to_string());
|
||||
|
||||
let mut cmd = if self.use_sudo {
|
||||
let mut sudo_cmd = Command::new("sudo");
|
||||
sudo_cmd.args([&binary_path, "stage", "unwind"]);
|
||||
sudo_cmd
|
||||
} else {
|
||||
let mut reth_cmd = Command::new(&binary_path);
|
||||
reth_cmd.args(["stage", "unwind"]);
|
||||
reth_cmd
|
||||
};
|
||||
|
||||
// Add chain argument (skip for mainnet as it's the default)
|
||||
let chain_str = self.chain.to_string();
|
||||
if chain_str != "mainnet" {
|
||||
cmd.args(["--chain", &chain_str]);
|
||||
}
|
||||
|
||||
// Add datadir if specified
|
||||
if let Some(ref datadir) = self.datadir {
|
||||
cmd.args(["--datadir", datadir]);
|
||||
}
|
||||
|
||||
cmd.args(["to-block", &block_number.to_string()]);
|
||||
|
||||
// Set environment variable to disable log styling
|
||||
cmd.env("RUST_LOG_STYLE", "never");
|
||||
|
||||
// Debug log the command
|
||||
debug!("Executing reth unwind command: {:?}", cmd);
|
||||
|
||||
let mut child = cmd
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.wrap_err("Failed to start unwind command")?;
|
||||
|
||||
// Stream stdout and stderr with prefixes in real-time
|
||||
if let Some(stdout) = child.stdout.take() {
|
||||
tokio::spawn(async move {
|
||||
let reader = AsyncBufReader::new(stdout);
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
debug!("[RETH-UNWIND] {}", line);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(stderr) = child.stderr.take() {
|
||||
tokio::spawn(async move {
|
||||
let reader = AsyncBufReader::new(stderr);
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
debug!("[RETH-UNWIND] {}", line);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Wait for the command to complete
|
||||
let status = child.wait().await.wrap_err("Failed to wait for unwind command")?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(eyre!("Unwind command failed with exit code: {:?}", status.code()));
|
||||
}
|
||||
|
||||
info!("Unwound to block: {}", block_number);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn supports_samply_flags(bin: &str) -> bool {
|
||||
let mut cmd = std::process::Command::new(bin);
|
||||
// NOTE: The flag to check must come before --help.
|
||||
// We pass --help as a shortcut to not execute any command.
|
||||
cmd.args(["--log.samply", "--help"]);
|
||||
debug!(?cmd, "Checking samply flags support");
|
||||
let Ok(output) = cmd.output() else {
|
||||
return false;
|
||||
};
|
||||
debug!(?output, "Samply flags support check");
|
||||
output.status.success()
|
||||
}
|
||||
@@ -17,7 +17,6 @@ workspace = true
|
||||
reth-cli-runner.workspace = true
|
||||
reth-cli-util.workspace = true
|
||||
reth-engine-primitives.workspace = true
|
||||
reth-ethereum-primitives.workspace = true
|
||||
reth-fs-util.workspace = true
|
||||
reth-node-api.workspace = true
|
||||
reth-node-core.workspace = true
|
||||
@@ -25,13 +24,11 @@ reth-primitives-traits.workspace = true
|
||||
reth-rpc-api.workspace = true
|
||||
|
||||
reth-tracing.workspace = true
|
||||
reth-chainspec.workspace = true
|
||||
|
||||
# alloy
|
||||
alloy-eips.workspace = true
|
||||
alloy-json-rpc.workspace = true
|
||||
alloy-consensus.workspace = true
|
||||
alloy-network.workspace = true
|
||||
|
||||
alloy-primitives = { workspace = true, features = ["rand"] }
|
||||
alloy-provider = { workspace = true, features = ["engine-api", "pubsub", "reqwest-rustls-tls"], default-features = false }
|
||||
alloy-pubsub.workspace = true
|
||||
|
||||
@@ -35,6 +35,10 @@ The `new-payload-fcu` command supports two optional waiting modes that can be us
|
||||
- `--wait-time <duration>`: Fixed sleep interval between blocks (e.g., `--wait-time 100ms` or `--wait-time 400` for 400ms)
|
||||
- `--wait-for-persistence`: Waits for blocks to be persisted using the `reth_subscribePersistedBlock` subscription
|
||||
|
||||
Both `new-payload-fcu` and `new-payload-only` support `--rpc-block-fetch-retries <RETRIES>`
|
||||
to control how many times block fetches are retried after an RPC failure. The default is `10`.
|
||||
Use `--rpc-block-fetch-retries forever` to keep retrying indefinitely.
|
||||
|
||||
When using `--wait-for-persistence`, the benchmark waits after every `(threshold + 1)` blocks, where the threshold defaults to the engine's persistence threshold (2). This can be customized with `--persistence-threshold <N>`.
|
||||
|
||||
By default, the WebSocket URL for persistence subscriptions is derived from `--engine-rpc-url` (converting to ws:// on port 8546). Use `--ws-rpc-url` to override this.
|
||||
|
||||
@@ -33,6 +33,10 @@ pub(crate) struct BenchContext {
|
||||
pub(crate) use_reth_namespace: bool,
|
||||
/// Whether to fetch and replay RLP-encoded blocks.
|
||||
pub(crate) rlp_blocks: bool,
|
||||
/// Whether to skip waiting for persistence (pass `wait_for_persistence: false`).
|
||||
pub(crate) no_wait_for_persistence: bool,
|
||||
/// Whether to skip waiting for caches (pass `wait_for_caches: false`).
|
||||
pub(crate) no_wait_for_caches: bool,
|
||||
}
|
||||
|
||||
impl BenchContext {
|
||||
@@ -60,8 +64,9 @@ impl BenchContext {
|
||||
.and_then(|t| t.as_http_error())
|
||||
.is_some_and(|e| e.status == 502)
|
||||
});
|
||||
let max_retries = bench_args.rpc_block_fetch_retries.as_max_retries();
|
||||
let client = ClientBuilder::default()
|
||||
.layer(RetryBackoffLayer::new_with_policy(10, 800, u64::MAX, retry_policy))
|
||||
.layer(RetryBackoffLayer::new_with_policy(max_retries, 800, u64::MAX, retry_policy))
|
||||
.http(rpc_url.parse()?);
|
||||
let block_provider = RootProvider::<AnyNetwork>::new(client);
|
||||
|
||||
@@ -162,6 +167,8 @@ impl BenchContext {
|
||||
let next_block = first_block.header.number + 1;
|
||||
let rlp_blocks = bench_args.rlp_blocks;
|
||||
let use_reth_namespace = bench_args.reth_new_payload || rlp_blocks;
|
||||
let no_wait_for_persistence = bench_args.no_wait_for_persistence;
|
||||
let no_wait_for_caches = bench_args.no_wait_for_caches;
|
||||
Ok(Self {
|
||||
auth_provider,
|
||||
block_provider,
|
||||
@@ -170,6 +177,8 @@ impl BenchContext {
|
||||
is_optimism,
|
||||
use_reth_namespace,
|
||||
rlp_blocks,
|
||||
no_wait_for_persistence,
|
||||
no_wait_for_caches,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,251 +0,0 @@
|
||||
//! Benchmarks empty block processing by ramping the block gas limit.
|
||||
|
||||
use crate::{
|
||||
authenticated_transport::AuthenticatedTransportConnect,
|
||||
bench::{
|
||||
helpers::{build_payload, parse_gas_limit, prepare_payload_request, rpc_block_to_header},
|
||||
output::GasRampPayloadFile,
|
||||
},
|
||||
valid_payload::{
|
||||
call_forkchoice_updated_with_reth, call_new_payload_with_reth, payload_to_new_payload,
|
||||
},
|
||||
};
|
||||
use alloy_eips::BlockNumberOrTag;
|
||||
use alloy_provider::{network::AnyNetwork, Provider, RootProvider};
|
||||
use alloy_rpc_client::ClientBuilder;
|
||||
use alloy_rpc_types_engine::{ExecutionPayload, ForkchoiceState, JwtSecret};
|
||||
|
||||
use clap::Parser;
|
||||
use reqwest::Url;
|
||||
use reth_chainspec::ChainSpec;
|
||||
use reth_cli_runner::CliContext;
|
||||
use reth_ethereum_primitives::TransactionSigned;
|
||||
use reth_primitives_traits::constants::{GAS_LIMIT_BOUND_DIVISOR, MAXIMUM_GAS_LIMIT_BLOCK};
|
||||
use reth_rpc_api::RethNewPayloadInput;
|
||||
use std::{path::PathBuf, time::Instant};
|
||||
use tracing::info;
|
||||
|
||||
/// `reth benchmark gas-limit-ramp` command.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Command {
|
||||
/// Number of blocks to generate. Mutually exclusive with --target-gas-limit.
|
||||
#[arg(long, value_name = "BLOCKS", conflicts_with = "target_gas_limit")]
|
||||
blocks: Option<u64>,
|
||||
|
||||
/// Target gas limit to ramp up to. The benchmark will generate blocks until the gas limit
|
||||
/// reaches or exceeds this value. Mutually exclusive with --blocks.
|
||||
/// Accepts short notation: K for thousand, M for million, G for billion (e.g., 2G = 2
|
||||
/// billion).
|
||||
#[arg(long, value_name = "TARGET_GAS_LIMIT", conflicts_with = "blocks", value_parser = parse_gas_limit)]
|
||||
target_gas_limit: Option<u64>,
|
||||
|
||||
/// The Engine API RPC URL.
|
||||
#[arg(long = "engine-rpc-url", value_name = "ENGINE_RPC_URL")]
|
||||
engine_rpc_url: String,
|
||||
|
||||
/// Path to the JWT secret for Engine API authentication.
|
||||
#[arg(long = "jwt-secret", value_name = "JWT_SECRET")]
|
||||
jwt_secret: PathBuf,
|
||||
|
||||
/// Output directory for benchmark results and generated payloads.
|
||||
#[arg(long, value_name = "OUTPUT")]
|
||||
output: PathBuf,
|
||||
|
||||
/// Use `reth_newPayload` endpoint instead of `engine_newPayload*`.
|
||||
///
|
||||
/// The `reth_newPayload` endpoint is a reth-specific extension that takes `ExecutionData`
|
||||
/// directly, waits for persistence and cache updates to complete before processing,
|
||||
/// and returns server-side timing breakdowns (latency, persistence wait, cache wait).
|
||||
#[arg(long, default_value = "false", verbatim_doc_comment)]
|
||||
reth_new_payload: bool,
|
||||
}
|
||||
|
||||
/// Mode for determining when to stop ramping.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum RampMode {
|
||||
/// Ramp for a fixed number of blocks.
|
||||
Blocks(u64),
|
||||
/// Ramp until reaching or exceeding target gas limit.
|
||||
TargetGasLimit(u64),
|
||||
}
|
||||
|
||||
impl Command {
|
||||
/// Execute `benchmark gas-limit-ramp` command.
|
||||
pub async fn execute(self, _ctx: CliContext) -> eyre::Result<()> {
|
||||
let mode = match (self.blocks, self.target_gas_limit) {
|
||||
(Some(blocks), None) => {
|
||||
if blocks == 0 {
|
||||
return Err(eyre::eyre!("--blocks must be greater than 0"));
|
||||
}
|
||||
RampMode::Blocks(blocks)
|
||||
}
|
||||
(None, Some(target)) => {
|
||||
if target == 0 {
|
||||
return Err(eyre::eyre!("--target-gas-limit must be greater than 0"));
|
||||
}
|
||||
RampMode::TargetGasLimit(target)
|
||||
}
|
||||
_ => {
|
||||
return Err(eyre::eyre!(
|
||||
"Exactly one of --blocks or --target-gas-limit must be specified"
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Ensure output directory exists
|
||||
if self.output.is_file() {
|
||||
return Err(eyre::eyre!("Output path must be a directory"));
|
||||
}
|
||||
if !self.output.exists() {
|
||||
std::fs::create_dir_all(&self.output)?;
|
||||
info!(target: "reth-bench", "Created output directory: {:?}", self.output);
|
||||
}
|
||||
|
||||
// Set up authenticated provider (used for both Engine API and eth_ methods)
|
||||
let jwt = std::fs::read_to_string(&self.jwt_secret)?;
|
||||
let jwt = JwtSecret::from_hex(jwt)?;
|
||||
let auth_url = Url::parse(&self.engine_rpc_url)?;
|
||||
|
||||
info!(target: "reth-bench", "Connecting to Engine RPC at {}", auth_url);
|
||||
let auth_transport = AuthenticatedTransportConnect::new(auth_url, jwt);
|
||||
let client = ClientBuilder::default().connect_with(auth_transport).await?;
|
||||
let provider = RootProvider::<AnyNetwork>::new(client);
|
||||
|
||||
// Get chain spec - required for fork detection
|
||||
let chain_id = provider.get_chain_id().await?;
|
||||
let chain_spec = ChainSpec::from_chain_id(chain_id)
|
||||
.ok_or_else(|| eyre::eyre!("Unsupported chain id: {chain_id}"))?;
|
||||
|
||||
// Fetch the current head block as parent
|
||||
let parent_block = provider
|
||||
.get_block_by_number(BlockNumberOrTag::Latest)
|
||||
.full()
|
||||
.await?
|
||||
.ok_or_else(|| eyre::eyre!("Failed to fetch latest block"))?;
|
||||
|
||||
let (mut parent_header, mut parent_hash) = rpc_block_to_header(parent_block);
|
||||
|
||||
let canonical_parent = parent_header.number;
|
||||
let start_block = canonical_parent + 1;
|
||||
|
||||
match mode {
|
||||
RampMode::Blocks(blocks) => {
|
||||
info!(
|
||||
target: "reth-bench",
|
||||
canonical_parent,
|
||||
start_block,
|
||||
end_block = start_block + blocks - 1,
|
||||
"Starting gas limit ramp benchmark (block count mode)"
|
||||
);
|
||||
}
|
||||
RampMode::TargetGasLimit(target) => {
|
||||
info!(
|
||||
target: "reth-bench",
|
||||
canonical_parent,
|
||||
start_block,
|
||||
current_gas_limit = parent_header.gas_limit,
|
||||
target_gas_limit = target,
|
||||
"Starting gas limit ramp benchmark (target gas limit mode)"
|
||||
);
|
||||
}
|
||||
}
|
||||
if self.reth_new_payload {
|
||||
info!("Using reth_newPayload and reth_forkchoiceUpdated endpoints");
|
||||
}
|
||||
|
||||
let mut blocks_processed = 0u64;
|
||||
let total_benchmark_duration = Instant::now();
|
||||
|
||||
while !should_stop(mode, blocks_processed, parent_header.gas_limit) {
|
||||
let timestamp = parent_header.timestamp.saturating_add(1);
|
||||
|
||||
let request = prepare_payload_request(&chain_spec, timestamp, parent_hash);
|
||||
let new_payload_version = request.new_payload_version;
|
||||
|
||||
let (payload, sidecar) = build_payload(&provider, request).await?;
|
||||
|
||||
let mut block =
|
||||
payload.clone().try_into_block_with_sidecar::<TransactionSigned>(&sidecar)?;
|
||||
|
||||
let max_increase = max_gas_limit_increase(parent_header.gas_limit);
|
||||
let gas_limit =
|
||||
parent_header.gas_limit.saturating_add(max_increase).min(MAXIMUM_GAS_LIMIT_BLOCK);
|
||||
|
||||
block.header.gas_limit = gas_limit;
|
||||
|
||||
let block_hash = block.header.hash_slow();
|
||||
// Regenerate the payload from the modified block, but keep the original sidecar
|
||||
// which contains the actual execution requests data (not just the hash)
|
||||
let (payload, _) = ExecutionPayload::from_block_unchecked(block_hash, &block);
|
||||
let (version, params, execution_data) = payload_to_new_payload(
|
||||
payload,
|
||||
sidecar,
|
||||
false,
|
||||
block.header.withdrawals_root,
|
||||
Some(new_payload_version),
|
||||
)?;
|
||||
|
||||
let (version, params) = if self.reth_new_payload {
|
||||
(None, serde_json::to_value((RethNewPayloadInput::ExecutionData(execution_data),))?)
|
||||
} else {
|
||||
(Some(version), params)
|
||||
};
|
||||
|
||||
// Save payload to file with version info for replay
|
||||
let payload_path =
|
||||
self.output.join(format!("payload_block_{}.json", block.header.number));
|
||||
let file = GasRampPayloadFile {
|
||||
version: version.map(|v| v as u8),
|
||||
block_hash,
|
||||
params: params.clone(),
|
||||
};
|
||||
let payload_json = serde_json::to_string_pretty(&file)?;
|
||||
std::fs::write(&payload_path, &payload_json)?;
|
||||
info!(target: "reth-bench", block_number = block.header.number, path = %payload_path.display(), "Saved payload");
|
||||
|
||||
let _ = call_new_payload_with_reth(&provider, version, params).await?;
|
||||
|
||||
let forkchoice_state = ForkchoiceState {
|
||||
head_block_hash: block_hash,
|
||||
safe_block_hash: block_hash,
|
||||
finalized_block_hash: block_hash,
|
||||
};
|
||||
call_forkchoice_updated_with_reth(&provider, version, forkchoice_state).await?;
|
||||
|
||||
parent_header = block.header;
|
||||
parent_hash = block_hash;
|
||||
blocks_processed += 1;
|
||||
|
||||
let progress = match mode {
|
||||
RampMode::Blocks(total) => format!("{blocks_processed}/{total}"),
|
||||
RampMode::TargetGasLimit(target) => {
|
||||
let pct = (parent_header.gas_limit as f64 / target as f64 * 100.0).min(100.0);
|
||||
format!("{pct:.1}%")
|
||||
}
|
||||
};
|
||||
info!(target: "reth-bench", progress, block_number = parent_header.number, gas_limit = parent_header.gas_limit, "Block processed");
|
||||
}
|
||||
|
||||
let final_gas_limit = parent_header.gas_limit;
|
||||
info!(
|
||||
target: "reth-bench",
|
||||
total_duration=?total_benchmark_duration.elapsed(),
|
||||
blocks_processed,
|
||||
final_gas_limit,
|
||||
"Benchmark complete"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const fn max_gas_limit_increase(parent_gas_limit: u64) -> u64 {
|
||||
(parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR).saturating_sub(1)
|
||||
}
|
||||
|
||||
const fn should_stop(mode: RampMode, blocks_processed: u64, current_gas_limit: u64) -> bool {
|
||||
match mode {
|
||||
RampMode::Blocks(target_blocks) => blocks_processed >= target_blocks,
|
||||
RampMode::TargetGasLimit(target) => current_gas_limit >= target,
|
||||
}
|
||||
}
|
||||
@@ -773,6 +773,7 @@ impl Command {
|
||||
suggested_fee_recipient: alloy_primitives::Address::ZERO,
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: Some(B256::ZERO),
|
||||
slot_number: None,
|
||||
},
|
||||
transactions: transactions.to_vec(),
|
||||
extra_data: None,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//! Common helpers for reth-bench commands.
|
||||
|
||||
use crate::valid_payload::call_forkchoice_updated;
|
||||
use eyre::Result;
|
||||
use std::{
|
||||
io::{BufReader, Read},
|
||||
@@ -70,180 +69,6 @@ pub(crate) fn parse_duration(s: &str) -> eyre::Result<Duration> {
|
||||
}
|
||||
}
|
||||
|
||||
use alloy_consensus::Header;
|
||||
use alloy_eips::eip4844::kzg_to_versioned_hash;
|
||||
use alloy_primitives::{Address, B256};
|
||||
use alloy_provider::{ext::EngineApi, network::AnyNetwork, RootProvider};
|
||||
use alloy_rpc_types_engine::{
|
||||
CancunPayloadFields, ExecutionPayload, ExecutionPayloadSidecar, ForkchoiceState,
|
||||
PayloadAttributes, PayloadId,
|
||||
};
|
||||
use eyre::OptionExt;
|
||||
use reth_chainspec::{ChainSpec, EthereumHardforks};
|
||||
use reth_node_api::EngineApiMessageVersion;
|
||||
use tracing::debug;
|
||||
|
||||
/// Prepared payload request data for triggering block building.
|
||||
pub(crate) struct PayloadRequest {
|
||||
/// The payload attributes for the new block.
|
||||
pub(crate) attributes: PayloadAttributes,
|
||||
/// The forkchoice state pointing to the parent block.
|
||||
pub(crate) forkchoice_state: ForkchoiceState,
|
||||
/// The engine API version for FCU calls.
|
||||
pub(crate) fcu_version: EngineApiMessageVersion,
|
||||
/// The getPayload version to use (1-5).
|
||||
pub(crate) get_payload_version: u8,
|
||||
/// The newPayload version to use.
|
||||
pub(crate) new_payload_version: EngineApiMessageVersion,
|
||||
}
|
||||
|
||||
/// Prepare payload attributes and forkchoice state for a new block.
|
||||
pub(crate) fn prepare_payload_request(
|
||||
chain_spec: &ChainSpec,
|
||||
timestamp: u64,
|
||||
parent_hash: B256,
|
||||
) -> PayloadRequest {
|
||||
let shanghai_active = chain_spec.is_shanghai_active_at_timestamp(timestamp);
|
||||
let cancun_active = chain_spec.is_cancun_active_at_timestamp(timestamp);
|
||||
let prague_active = chain_spec.is_prague_active_at_timestamp(timestamp);
|
||||
let osaka_active = chain_spec.is_osaka_active_at_timestamp(timestamp);
|
||||
|
||||
// FCU version: V3 for Cancun+Prague+Osaka, V2 for Shanghai, V1 otherwise
|
||||
let fcu_version = if cancun_active {
|
||||
EngineApiMessageVersion::V3
|
||||
} else if shanghai_active {
|
||||
EngineApiMessageVersion::V2
|
||||
} else {
|
||||
EngineApiMessageVersion::V1
|
||||
};
|
||||
|
||||
// getPayload version: 5 for Osaka, 4 for Prague, 3 for Cancun, 2 for Shanghai, 1 otherwise
|
||||
// newPayload version: 4 for Prague+Osaka (no V5), 3 for Cancun, 2 for Shanghai, 1 otherwise
|
||||
let (get_payload_version, new_payload_version) = if osaka_active {
|
||||
(5, EngineApiMessageVersion::V4) // Osaka uses getPayloadV5 but newPayloadV4
|
||||
} else if prague_active {
|
||||
(4, EngineApiMessageVersion::V4)
|
||||
} else if cancun_active {
|
||||
(3, EngineApiMessageVersion::V3)
|
||||
} else if shanghai_active {
|
||||
(2, EngineApiMessageVersion::V2)
|
||||
} else {
|
||||
(1, EngineApiMessageVersion::V1)
|
||||
};
|
||||
|
||||
PayloadRequest {
|
||||
attributes: PayloadAttributes {
|
||||
timestamp,
|
||||
prev_randao: B256::ZERO,
|
||||
suggested_fee_recipient: Address::ZERO,
|
||||
withdrawals: shanghai_active.then(Vec::new),
|
||||
parent_beacon_block_root: cancun_active.then_some(B256::ZERO),
|
||||
},
|
||||
forkchoice_state: ForkchoiceState {
|
||||
head_block_hash: parent_hash,
|
||||
safe_block_hash: parent_hash,
|
||||
finalized_block_hash: parent_hash,
|
||||
},
|
||||
fcu_version,
|
||||
get_payload_version,
|
||||
new_payload_version,
|
||||
}
|
||||
}
|
||||
|
||||
/// Trigger payload building via FCU and retrieve the built payload.
|
||||
///
|
||||
/// This sends a forkchoiceUpdated with payload attributes to start building,
|
||||
/// then calls getPayload to retrieve the result.
|
||||
pub(crate) async fn build_payload(
|
||||
provider: &RootProvider<AnyNetwork>,
|
||||
request: PayloadRequest,
|
||||
) -> eyre::Result<(ExecutionPayload, ExecutionPayloadSidecar)> {
|
||||
let fcu_result = call_forkchoice_updated(
|
||||
provider,
|
||||
request.fcu_version,
|
||||
request.forkchoice_state,
|
||||
Some(request.attributes.clone()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let payload_id =
|
||||
fcu_result.payload_id.ok_or_eyre("Payload builder did not return a payload id")?;
|
||||
|
||||
get_payload_with_sidecar(
|
||||
provider,
|
||||
request.get_payload_version,
|
||||
payload_id,
|
||||
request.attributes.parent_beacon_block_root,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Convert an RPC block to a consensus header and block hash.
|
||||
pub(crate) fn rpc_block_to_header(block: alloy_provider::network::AnyRpcBlock) -> (Header, B256) {
|
||||
let block_hash = block.header.hash;
|
||||
let header = block.header.inner.clone().into_header_with_defaults();
|
||||
(header, block_hash)
|
||||
}
|
||||
|
||||
/// Compute versioned hashes from KZG commitments.
|
||||
fn versioned_hashes_from_commitments(
|
||||
commitments: &[alloy_primitives::FixedBytes<48>],
|
||||
) -> Vec<B256> {
|
||||
commitments.iter().map(|c| kzg_to_versioned_hash(c.as_ref())).collect()
|
||||
}
|
||||
|
||||
/// Fetch an execution payload using the appropriate engine API version.
|
||||
pub(crate) async fn get_payload_with_sidecar(
|
||||
provider: &RootProvider<AnyNetwork>,
|
||||
version: u8,
|
||||
payload_id: PayloadId,
|
||||
parent_beacon_block_root: Option<B256>,
|
||||
) -> eyre::Result<(ExecutionPayload, ExecutionPayloadSidecar)> {
|
||||
debug!(target: "reth-bench", get_payload_version = ?version, ?payload_id, "Sending getPayload");
|
||||
|
||||
match version {
|
||||
1 => {
|
||||
let payload = provider.get_payload_v1(payload_id).await?;
|
||||
Ok((ExecutionPayload::V1(payload), ExecutionPayloadSidecar::none()))
|
||||
}
|
||||
2 => {
|
||||
let envelope = provider.get_payload_v2(payload_id).await?;
|
||||
let payload = match envelope.execution_payload {
|
||||
alloy_rpc_types_engine::ExecutionPayloadFieldV2::V1(p) => ExecutionPayload::V1(p),
|
||||
alloy_rpc_types_engine::ExecutionPayloadFieldV2::V2(p) => ExecutionPayload::V2(p),
|
||||
};
|
||||
Ok((payload, ExecutionPayloadSidecar::none()))
|
||||
}
|
||||
3 => {
|
||||
let envelope = provider.get_payload_v3(payload_id).await?;
|
||||
let versioned_hashes =
|
||||
versioned_hashes_from_commitments(&envelope.blobs_bundle.commitments);
|
||||
let cancun_fields = CancunPayloadFields {
|
||||
parent_beacon_block_root: parent_beacon_block_root
|
||||
.ok_or_eyre("parent_beacon_block_root required for V3")?,
|
||||
versioned_hashes,
|
||||
};
|
||||
Ok((
|
||||
ExecutionPayload::V3(envelope.execution_payload),
|
||||
ExecutionPayloadSidecar::v3(cancun_fields),
|
||||
))
|
||||
}
|
||||
4 => {
|
||||
let envelope = provider.get_payload_v4(payload_id).await?;
|
||||
Ok(envelope.into_payload_and_sidecar(
|
||||
parent_beacon_block_root.ok_or_eyre("parent_beacon_block_root required for V4")?,
|
||||
))
|
||||
}
|
||||
5 => {
|
||||
let envelope = provider.get_payload_v5(payload_id).await?;
|
||||
Ok(envelope.into_payload_and_sidecar(
|
||||
parent_beacon_block_root.ok_or_eyre("parent_beacon_block_root required for V5")?,
|
||||
))
|
||||
}
|
||||
_ => panic!("This tool does not support getPayload versions past v5"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -6,7 +6,6 @@ use reth_node_core::args::LogArgs;
|
||||
use reth_tracing::FileWorkerGuard;
|
||||
|
||||
mod context;
|
||||
mod gas_limit_ramp;
|
||||
mod generate_big_block;
|
||||
pub(crate) mod helpers;
|
||||
pub use generate_big_block::{
|
||||
@@ -16,7 +15,6 @@ pub(crate) mod metrics_scraper;
|
||||
mod new_payload_fcu;
|
||||
mod new_payload_only;
|
||||
mod output;
|
||||
mod persistence_waiter;
|
||||
mod replay_payloads;
|
||||
mod send_invalid_payload;
|
||||
mod send_payload;
|
||||
@@ -37,9 +35,6 @@ pub enum Subcommands {
|
||||
/// Benchmark which calls `newPayload`, then `forkchoiceUpdated`.
|
||||
NewPayloadFcu(new_payload_fcu::Command),
|
||||
|
||||
/// Benchmark which builds empty blocks with a ramped gas limit.
|
||||
GasLimitRamp(gas_limit_ramp::Command),
|
||||
|
||||
/// Benchmark which only calls subsequent `newPayload` calls.
|
||||
NewPayloadOnly(new_payload_only::Command),
|
||||
|
||||
@@ -99,7 +94,6 @@ impl BenchmarkCommand {
|
||||
|
||||
match self.command {
|
||||
Subcommands::NewPayloadFcu(command) => command.execute(ctx).await,
|
||||
Subcommands::GasLimitRamp(command) => command.execute(ctx).await,
|
||||
Subcommands::NewPayloadOnly(command) => command.execute(ctx).await,
|
||||
Subcommands::SendPayload(command) => command.execute(ctx).await,
|
||||
Subcommands::GenerateBigBlock(command) => command.execute(ctx).await,
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
//! Runs the `reth bench` command, calling first newPayload for each block, then calling
|
||||
//! forkchoiceUpdated.
|
||||
//!
|
||||
//! Supports configurable waiting behavior:
|
||||
//! - **`--wait-time`**: Fixed sleep interval between blocks.
|
||||
//! - **`--wait-for-persistence`**: Waits for every Nth block to be persisted using the
|
||||
//! `reth_subscribePersistedBlock` subscription, where N matches the engine's persistence
|
||||
//! threshold. This ensures the benchmark doesn't outpace persistence.
|
||||
//!
|
||||
//! Both options can be used together or independently.
|
||||
|
||||
use crate::{
|
||||
bench::{
|
||||
@@ -17,9 +9,6 @@ use crate::{
|
||||
output::{
|
||||
write_benchmark_results, CombinedResult, NewPayloadResult, TotalGasOutput, TotalGasRow,
|
||||
},
|
||||
persistence_waiter::{
|
||||
derive_ws_rpc_url, setup_persistence_subscription, PersistenceWaiter,
|
||||
},
|
||||
},
|
||||
valid_payload::{
|
||||
block_to_new_payload, call_forkchoice_updated_with_reth, call_new_payload_with_reth,
|
||||
@@ -29,6 +18,7 @@ use alloy_provider::{ext::DebugApi, Provider};
|
||||
use alloy_rpc_types_engine::ForkchoiceState;
|
||||
use clap::Parser;
|
||||
use eyre::{Context, OptionExt};
|
||||
use futures::{stream, StreamExt, TryStreamExt};
|
||||
use reth_cli_runner::CliContext;
|
||||
use reth_engine_primitives::config::DEFAULT_PERSISTENCE_THRESHOLD;
|
||||
use reth_node_core::args::BenchmarkArgs;
|
||||
@@ -49,16 +39,6 @@ pub struct Command {
|
||||
#[arg(long, value_name = "WAIT_TIME", value_parser = parse_duration, verbatim_doc_comment)]
|
||||
wait_time: Option<Duration>,
|
||||
|
||||
/// Wait for blocks to be persisted before sending the next batch.
|
||||
///
|
||||
/// When enabled, waits for every Nth block to be persisted using the
|
||||
/// `reth_subscribePersistedBlock` subscription. This ensures the benchmark
|
||||
/// doesn't outpace persistence.
|
||||
///
|
||||
/// The subscription uses the regular RPC websocket endpoint (no JWT required).
|
||||
#[arg(long, default_value = "false", verbatim_doc_comment)]
|
||||
wait_for_persistence: bool,
|
||||
|
||||
/// Engine persistence threshold used for deciding when to wait for persistence.
|
||||
///
|
||||
/// The benchmark waits after every `(threshold + 1)` blocks. By default this
|
||||
@@ -106,55 +86,17 @@ impl Command {
|
||||
if let Some(duration) = self.wait_time {
|
||||
info!(target: "reth-bench", "Using wait-time mode with {}ms delay between blocks", duration.as_millis());
|
||||
}
|
||||
if self.wait_for_persistence {
|
||||
info!(
|
||||
target: "reth-bench",
|
||||
"Persistence waiting enabled (waits after every {} blocks to match engine gap > {} behavior)",
|
||||
self.persistence_threshold + 1,
|
||||
self.persistence_threshold
|
||||
);
|
||||
}
|
||||
|
||||
// Set up waiter based on configured options
|
||||
// When both are set: wait at least wait_time, and also wait for persistence if needed
|
||||
let mut waiter = match (self.wait_time, self.wait_for_persistence) {
|
||||
(Some(duration), true) => {
|
||||
let ws_url = derive_ws_rpc_url(
|
||||
self.benchmark.ws_rpc_url.as_deref(),
|
||||
&self.benchmark.engine_rpc_url,
|
||||
)?;
|
||||
let sub = setup_persistence_subscription(ws_url, self.persistence_timeout).await?;
|
||||
Some(PersistenceWaiter::with_duration_and_subscription(
|
||||
duration,
|
||||
sub,
|
||||
self.persistence_threshold,
|
||||
self.persistence_timeout,
|
||||
))
|
||||
}
|
||||
(Some(duration), false) => Some(PersistenceWaiter::with_duration(duration)),
|
||||
(None, true) => {
|
||||
let ws_url = derive_ws_rpc_url(
|
||||
self.benchmark.ws_rpc_url.as_deref(),
|
||||
&self.benchmark.engine_rpc_url,
|
||||
)?;
|
||||
let sub = setup_persistence_subscription(ws_url, self.persistence_timeout).await?;
|
||||
Some(PersistenceWaiter::with_subscription(
|
||||
sub,
|
||||
self.persistence_threshold,
|
||||
self.persistence_timeout,
|
||||
))
|
||||
}
|
||||
(None, false) => None,
|
||||
};
|
||||
|
||||
let BenchContext {
|
||||
benchmark_mode,
|
||||
block_provider,
|
||||
auth_provider,
|
||||
mut next_block,
|
||||
next_block,
|
||||
is_optimism,
|
||||
use_reth_namespace,
|
||||
rlp_blocks,
|
||||
no_wait_for_persistence,
|
||||
no_wait_for_caches,
|
||||
} = BenchContext::new(&self.benchmark, self.rpc_url).await?;
|
||||
|
||||
let total_blocks = benchmark_mode.total_blocks();
|
||||
@@ -167,70 +109,71 @@ impl Command {
|
||||
|
||||
let buffer_size = self.rpc_block_buffer_size;
|
||||
|
||||
// Use a oneshot channel to propagate errors from the spawned task
|
||||
let (error_sender, mut error_receiver) = tokio::sync::oneshot::channel();
|
||||
let (sender, mut receiver) = tokio::sync::mpsc::channel(buffer_size);
|
||||
let mut blocks = Box::pin(
|
||||
stream::iter((next_block..)
|
||||
.take_while(|next_block| {
|
||||
benchmark_mode.contains(*next_block)
|
||||
}))
|
||||
.map(|next_block| {
|
||||
let block_provider = block_provider.clone();
|
||||
async move {
|
||||
let block_res = block_provider
|
||||
.get_block_by_number(next_block.into())
|
||||
.full()
|
||||
.await
|
||||
.wrap_err_with(|| {
|
||||
format!("Failed to fetch block by number {next_block}")
|
||||
});
|
||||
let block =
|
||||
match block_res.and_then(|opt| opt.ok_or_eyre("Block not found")) {
|
||||
Ok(block) => block,
|
||||
Err(e) => {
|
||||
tracing::error!(target: "reth-bench", "Failed to fetch block {next_block}: {e}");
|
||||
return Err(e)
|
||||
}
|
||||
};
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
while benchmark_mode.contains(next_block) {
|
||||
let block_res = block_provider
|
||||
.get_block_by_number(next_block.into())
|
||||
.full()
|
||||
.await
|
||||
.wrap_err_with(|| format!("Failed to fetch block by number {next_block}"));
|
||||
let block = match block_res.and_then(|opt| opt.ok_or_eyre("Block not found")) {
|
||||
Ok(block) => block,
|
||||
Err(e) => {
|
||||
tracing::error!(target: "reth-bench", "Failed to fetch block {next_block}: {e}");
|
||||
let _ = error_sender.send(e);
|
||||
break;
|
||||
let rlp = if rlp_blocks {
|
||||
let rlp = match block_provider
|
||||
.debug_get_raw_block(next_block.into())
|
||||
.await
|
||||
{
|
||||
Ok(rlp) => rlp,
|
||||
Err(e) => {
|
||||
tracing::error!(target: "reth-bench", "Failed to fetch raw block {next_block}: {e}");
|
||||
return Err(e.into())
|
||||
}
|
||||
};
|
||||
Some(rlp)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let head_block_hash = block.header.hash;
|
||||
let safe_block_hash = block_provider
|
||||
.get_block_by_number(block.header.number.saturating_sub(32).into());
|
||||
|
||||
let finalized_block_hash = block_provider
|
||||
.get_block_by_number(block.header.number.saturating_sub(64).into());
|
||||
|
||||
let (safe, finalized) =
|
||||
tokio::join!(safe_block_hash, finalized_block_hash);
|
||||
|
||||
let safe_block_hash = match safe {
|
||||
Ok(Some(block)) => block.header.hash,
|
||||
Ok(None) | Err(_) => head_block_hash,
|
||||
};
|
||||
|
||||
let finalized_block_hash = match finalized {
|
||||
Ok(Some(block)) => block.header.hash,
|
||||
Ok(None) | Err(_) => head_block_hash,
|
||||
};
|
||||
|
||||
Ok((block, head_block_hash, safe_block_hash, finalized_block_hash, rlp))
|
||||
}
|
||||
};
|
||||
|
||||
let rlp = if rlp_blocks {
|
||||
let rlp = match block_provider.debug_get_raw_block(next_block.into()).await {
|
||||
Ok(rlp) => rlp,
|
||||
Err(e) => {
|
||||
tracing::error!(target: "reth-bench", "Failed to fetch raw block {next_block}: {e}");
|
||||
let _ = error_sender
|
||||
.send(eyre::eyre!("Failed to fetch raw block {next_block}: {e}"));
|
||||
break;
|
||||
}
|
||||
};
|
||||
Some(rlp)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let head_block_hash = block.header.hash;
|
||||
let safe_block_hash = block_provider
|
||||
.get_block_by_number(block.header.number.saturating_sub(32).into());
|
||||
|
||||
let finalized_block_hash = block_provider
|
||||
.get_block_by_number(block.header.number.saturating_sub(64).into());
|
||||
|
||||
let (safe, finalized) = tokio::join!(safe_block_hash, finalized_block_hash,);
|
||||
|
||||
let safe_block_hash = match safe {
|
||||
Ok(Some(block)) => block.header.hash,
|
||||
Ok(None) | Err(_) => head_block_hash,
|
||||
};
|
||||
|
||||
let finalized_block_hash = match finalized {
|
||||
Ok(Some(block)) => block.header.hash,
|
||||
Ok(None) | Err(_) => head_block_hash,
|
||||
};
|
||||
|
||||
next_block += 1;
|
||||
if let Err(e) = sender
|
||||
.send((block, head_block_hash, safe_block_hash, finalized_block_hash, rlp))
|
||||
.await
|
||||
{
|
||||
tracing::error!(target: "reth-bench", "Failed to send block data: {e}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.buffered(buffer_size),
|
||||
);
|
||||
|
||||
let mut results = Vec::new();
|
||||
let mut blocks_processed = 0u64;
|
||||
@@ -239,7 +182,7 @@ impl Command {
|
||||
|
||||
while let Some((block, head, safe, finalized, rlp)) = {
|
||||
let wait_start = Instant::now();
|
||||
let result = receiver.recv().await;
|
||||
let result = blocks.try_next().await?;
|
||||
total_wait_time += wait_start.elapsed();
|
||||
result
|
||||
} {
|
||||
@@ -256,8 +199,14 @@ impl Command {
|
||||
finalized_block_hash: finalized,
|
||||
};
|
||||
|
||||
let (version, params) =
|
||||
block_to_new_payload(block, is_optimism, rlp, use_reth_namespace)?;
|
||||
let (version, params) = block_to_new_payload(
|
||||
block,
|
||||
is_optimism,
|
||||
rlp,
|
||||
use_reth_namespace,
|
||||
no_wait_for_persistence,
|
||||
no_wait_for_caches,
|
||||
)?;
|
||||
let start = Instant::now();
|
||||
let server_timings =
|
||||
call_new_payload_with_reth(&auth_provider, version, params).await?;
|
||||
@@ -315,8 +264,8 @@ impl Command {
|
||||
warn!(target: "reth-bench", %err, block_number, "Failed to scrape metrics");
|
||||
}
|
||||
|
||||
if let Some(w) = &mut waiter {
|
||||
w.on_block(block_number).await?;
|
||||
if let Some(wait_time) = self.wait_time {
|
||||
tokio::time::sleep(wait_time).await;
|
||||
}
|
||||
|
||||
let gas_row =
|
||||
@@ -324,15 +273,6 @@ impl Command {
|
||||
results.push((gas_row, combined_result));
|
||||
}
|
||||
|
||||
// Check if the spawned task encountered an error
|
||||
if let Ok(error) = error_receiver.try_recv() {
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
// Drop waiter - we don't need to wait for final blocks to persist
|
||||
// since the benchmark goal is measuring Ggas/s of newPayload/FCU, not persistence.
|
||||
drop(waiter);
|
||||
|
||||
let (gas_output_results, combined_results): (Vec<TotalGasRow>, Vec<CombinedResult>) =
|
||||
results.into_iter().unzip();
|
||||
|
||||
|
||||
@@ -52,6 +52,8 @@ impl Command {
|
||||
is_optimism,
|
||||
use_reth_namespace,
|
||||
rlp_blocks,
|
||||
no_wait_for_persistence,
|
||||
no_wait_for_caches,
|
||||
} = BenchContext::new(&self.benchmark, self.rpc_url).await?;
|
||||
|
||||
let total_blocks = benchmark_mode.total_blocks();
|
||||
@@ -122,8 +124,14 @@ impl Command {
|
||||
|
||||
debug!(target: "reth-bench", number=?block.header.number, "Sending payload to engine");
|
||||
|
||||
let (version, params) =
|
||||
block_to_new_payload(block, is_optimism, rlp, use_reth_namespace)?;
|
||||
let (version, params) = block_to_new_payload(
|
||||
block,
|
||||
is_optimism,
|
||||
rlp,
|
||||
use_reth_namespace,
|
||||
no_wait_for_persistence,
|
||||
no_wait_for_caches,
|
||||
)?;
|
||||
|
||||
let start = Instant::now();
|
||||
let server_timings =
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
//! Contains various benchmark output formats, either for logging or for
|
||||
//! serialization to / from files.
|
||||
|
||||
use alloy_primitives::B256;
|
||||
use csv::Writer;
|
||||
use eyre::OptionExt;
|
||||
use reth_primitives_traits::constants::GIGAGAS;
|
||||
use serde::{ser::SerializeStruct, Deserialize, Serialize};
|
||||
use serde::{ser::SerializeStruct, Serialize};
|
||||
use std::{fs, path::Path, time::Duration};
|
||||
use tracing::info;
|
||||
|
||||
@@ -18,20 +17,6 @@ pub(crate) const COMBINED_OUTPUT_SUFFIX: &str = "combined_latency.csv";
|
||||
/// This is the suffix for new payload output csv files.
|
||||
pub(crate) const NEW_PAYLOAD_OUTPUT_SUFFIX: &str = "new_payload_latency.csv";
|
||||
|
||||
/// Serialized format for gas ramp payloads on disk.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct GasRampPayloadFile {
|
||||
/// Engine API version (1-5).
|
||||
///
|
||||
/// `None` indicates that `reth_newPayload` should be used.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) version: Option<u8>,
|
||||
/// The block hash for FCU.
|
||||
pub(crate) block_hash: B256,
|
||||
/// The params to pass to newPayload.
|
||||
pub(crate) params: serde_json::Value,
|
||||
}
|
||||
|
||||
/// This represents the results of a single `newPayload` call in the benchmark, containing the gas
|
||||
/// used and the `newPayload` latency.
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -1,334 +0,0 @@
|
||||
//! Persistence waiting utilities for benchmarks.
|
||||
//!
|
||||
//! Provides waiting behavior to control benchmark pacing:
|
||||
//! - **Fixed duration waits**: Sleep for a fixed time between blocks
|
||||
//! - **Persistence-based waits**: Wait for blocks to be persisted using
|
||||
//! `reth_subscribePersistedBlock` subscription
|
||||
//! - **Combined mode**: Wait at least the fixed duration, and also wait for persistence if the
|
||||
//! block hasn't been persisted yet (whichever takes longer)
|
||||
|
||||
use alloy_eips::BlockNumHash;
|
||||
use alloy_network::Ethereum;
|
||||
use alloy_provider::{Provider, RootProvider};
|
||||
use alloy_pubsub::SubscriptionStream;
|
||||
use alloy_rpc_client::RpcClient;
|
||||
use alloy_transport_ws::WsConnect;
|
||||
use eyre::Context;
|
||||
use futures::StreamExt;
|
||||
use std::time::Duration;
|
||||
use tracing::{debug, info};
|
||||
|
||||
/// Default `WebSocket` RPC port for reth.
|
||||
const DEFAULT_WS_RPC_PORT: u16 = 8546;
|
||||
use url::Url;
|
||||
|
||||
/// Returns the websocket RPC URL used for the persistence subscription.
|
||||
///
|
||||
/// Preference:
|
||||
/// - If `ws_rpc_url` is provided, use it directly.
|
||||
/// - Otherwise, derive a WS RPC URL from `engine_rpc_url`.
|
||||
///
|
||||
/// The persistence subscription endpoint (`reth_subscribePersistedBlock`) is exposed on
|
||||
/// the regular RPC server (WS port, usually 8546), not on the engine API port (usually 8551).
|
||||
/// Since we may only have the engine URL by default, we convert the scheme
|
||||
/// (http→ws, https→wss) and force the port to 8546.
|
||||
pub(crate) fn derive_ws_rpc_url(
|
||||
ws_rpc_url: Option<&str>,
|
||||
engine_rpc_url: &str,
|
||||
) -> eyre::Result<Url> {
|
||||
if let Some(ws_url) = ws_rpc_url {
|
||||
let parsed: Url = ws_url
|
||||
.parse()
|
||||
.wrap_err_with(|| format!("Failed to parse WebSocket RPC URL: {ws_url}"))?;
|
||||
info!(target: "reth-bench", ws_url = %parsed, "Using provided WebSocket RPC URL");
|
||||
Ok(parsed)
|
||||
} else {
|
||||
let derived = engine_url_to_ws_url(engine_rpc_url)?;
|
||||
debug!(
|
||||
target: "reth-bench",
|
||||
engine_url = %engine_rpc_url,
|
||||
%derived,
|
||||
"Derived WebSocket RPC URL from engine RPC URL"
|
||||
);
|
||||
Ok(derived)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts an engine API URL to the default RPC websocket URL.
|
||||
///
|
||||
/// Transformations:
|
||||
/// - `http` → `ws`
|
||||
/// - `https` → `wss`
|
||||
/// - `ws` / `wss` keep their scheme
|
||||
/// - Port is always set to `8546`, reth's default RPC websocket port.
|
||||
///
|
||||
/// This is used when we only know the engine API URL (typically `:8551`) but
|
||||
/// need to connect to the node's WS RPC endpoint for persistence events.
|
||||
fn engine_url_to_ws_url(engine_url: &str) -> eyre::Result<Url> {
|
||||
let url: Url = engine_url
|
||||
.parse()
|
||||
.wrap_err_with(|| format!("Failed to parse engine RPC URL: {engine_url}"))?;
|
||||
|
||||
let mut ws_url = url.clone();
|
||||
|
||||
match ws_url.scheme() {
|
||||
"http" => ws_url
|
||||
.set_scheme("ws")
|
||||
.map_err(|_| eyre::eyre!("Failed to set WS scheme for URL: {url}"))?,
|
||||
"https" => ws_url
|
||||
.set_scheme("wss")
|
||||
.map_err(|_| eyre::eyre!("Failed to set WSS scheme for URL: {url}"))?,
|
||||
"ws" | "wss" => {}
|
||||
scheme => {
|
||||
return Err(eyre::eyre!(
|
||||
"Unsupported URL scheme '{scheme}' for URL: {url}. Expected http, https, ws, or wss."
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
ws_url
|
||||
.set_port(Some(DEFAULT_WS_RPC_PORT))
|
||||
.map_err(|_| eyre::eyre!("Failed to set port for URL: {url}"))?;
|
||||
|
||||
Ok(ws_url)
|
||||
}
|
||||
|
||||
/// Waits until the persistence subscription reports that `target` has been persisted.
|
||||
///
|
||||
/// Consumes subscription events until `last_persisted >= target`, or returns an error if:
|
||||
/// - the subscription stream ends unexpectedly, or
|
||||
/// - `timeout` elapses before `target` is observed.
|
||||
async fn wait_for_persistence(
|
||||
stream: &mut SubscriptionStream<BlockNumHash>,
|
||||
target: u64,
|
||||
last_persisted: &mut u64,
|
||||
timeout: Duration,
|
||||
) -> eyre::Result<()> {
|
||||
tokio::time::timeout(timeout, async {
|
||||
while *last_persisted < target {
|
||||
match stream.next().await {
|
||||
Some(persisted) => {
|
||||
*last_persisted = persisted.number;
|
||||
debug!(
|
||||
target: "reth-bench",
|
||||
persisted_block = ?last_persisted,
|
||||
"Received persistence notification"
|
||||
);
|
||||
}
|
||||
None => {
|
||||
return Err(eyre::eyre!("Persistence subscription closed unexpectedly"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.map_err(|_| {
|
||||
eyre::eyre!(
|
||||
"Persistence timeout: target block {} not persisted within {:?}. Last persisted: {}",
|
||||
target,
|
||||
timeout,
|
||||
last_persisted
|
||||
)
|
||||
})?
|
||||
}
|
||||
|
||||
/// Wrapper that keeps both the subscription stream and the underlying provider alive.
|
||||
/// The provider must be kept alive for the subscription to continue receiving events.
|
||||
pub(crate) struct PersistenceSubscription {
|
||||
_provider: RootProvider<Ethereum>,
|
||||
stream: SubscriptionStream<BlockNumHash>,
|
||||
}
|
||||
|
||||
impl PersistenceSubscription {
|
||||
const fn new(
|
||||
provider: RootProvider<Ethereum>,
|
||||
stream: SubscriptionStream<BlockNumHash>,
|
||||
) -> Self {
|
||||
Self { _provider: provider, stream }
|
||||
}
|
||||
|
||||
const fn stream_mut(&mut self) -> &mut SubscriptionStream<BlockNumHash> {
|
||||
&mut self.stream
|
||||
}
|
||||
}
|
||||
|
||||
/// Establishes a websocket connection and subscribes to `reth_subscribePersistedBlock`.
|
||||
///
|
||||
/// The `keepalive_interval` is set to match `persistence_timeout` so that the `WebSocket`
|
||||
/// connection is not dropped during long MDBX commits that block the server from responding
|
||||
/// to pings.
|
||||
pub(crate) async fn setup_persistence_subscription(
|
||||
ws_url: Url,
|
||||
persistence_timeout: Duration,
|
||||
) -> eyre::Result<PersistenceSubscription> {
|
||||
info!(target: "reth-bench", "Connecting to WebSocket at {} for persistence subscription", ws_url);
|
||||
|
||||
let ws_connect =
|
||||
WsConnect::new(ws_url.to_string()).with_keepalive_interval(persistence_timeout);
|
||||
let client = RpcClient::connect_pubsub(ws_connect)
|
||||
.await
|
||||
.wrap_err("Failed to connect to WebSocket RPC endpoint")?;
|
||||
let provider: RootProvider<Ethereum> = RootProvider::new(client);
|
||||
|
||||
let subscription = provider
|
||||
.subscribe_to::<BlockNumHash>("reth_subscribePersistedBlock")
|
||||
.await
|
||||
.wrap_err("Failed to subscribe to persistence notifications")?;
|
||||
|
||||
info!(target: "reth-bench", "Subscribed to persistence notifications");
|
||||
|
||||
Ok(PersistenceSubscription::new(provider, subscription.into_stream()))
|
||||
}
|
||||
|
||||
/// Encapsulates the block waiting logic.
|
||||
///
|
||||
/// Provides a simple `on_block()` interface that handles both:
|
||||
/// - Fixed duration waits (when `wait_time` is set)
|
||||
/// - Persistence-based waits (when `subscription` is set)
|
||||
///
|
||||
/// For persistence mode, waits after every `(threshold + 1)` blocks.
|
||||
pub(crate) struct PersistenceWaiter {
|
||||
wait_time: Option<Duration>,
|
||||
subscription: Option<PersistenceSubscription>,
|
||||
blocks_sent: u64,
|
||||
last_persisted: u64,
|
||||
threshold: u64,
|
||||
timeout: Duration,
|
||||
}
|
||||
|
||||
impl PersistenceWaiter {
|
||||
pub(crate) const fn with_duration(wait_time: Duration) -> Self {
|
||||
Self {
|
||||
wait_time: Some(wait_time),
|
||||
subscription: None,
|
||||
blocks_sent: 0,
|
||||
last_persisted: 0,
|
||||
threshold: 0,
|
||||
timeout: Duration::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn with_subscription(
|
||||
subscription: PersistenceSubscription,
|
||||
threshold: u64,
|
||||
timeout: Duration,
|
||||
) -> Self {
|
||||
Self {
|
||||
wait_time: None,
|
||||
subscription: Some(subscription),
|
||||
blocks_sent: 0,
|
||||
last_persisted: 0,
|
||||
threshold,
|
||||
timeout,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a waiter that combines both duration and persistence waiting.
|
||||
///
|
||||
/// Waits at least `wait_time` between blocks, and also waits for persistence
|
||||
/// if the block hasn't been persisted yet (whichever takes longer).
|
||||
pub(crate) const fn with_duration_and_subscription(
|
||||
wait_time: Duration,
|
||||
subscription: PersistenceSubscription,
|
||||
threshold: u64,
|
||||
timeout: Duration,
|
||||
) -> Self {
|
||||
Self {
|
||||
wait_time: Some(wait_time),
|
||||
subscription: Some(subscription),
|
||||
blocks_sent: 0,
|
||||
last_persisted: 0,
|
||||
threshold,
|
||||
timeout,
|
||||
}
|
||||
}
|
||||
|
||||
/// Called once per block. Waits based on the configured mode.
|
||||
///
|
||||
/// When both `wait_time` and `subscription` are set (combined mode):
|
||||
/// - Always waits at least `wait_time`
|
||||
/// - Additionally waits for persistence if we're at a persistence checkpoint
|
||||
#[allow(clippy::manual_is_multiple_of)]
|
||||
pub(crate) async fn on_block(&mut self, block_number: u64) -> eyre::Result<()> {
|
||||
// Always wait for the fixed duration if configured
|
||||
if let Some(wait_time) = self.wait_time {
|
||||
tokio::time::sleep(wait_time).await;
|
||||
}
|
||||
|
||||
// Check persistence if subscription is configured
|
||||
let Some(ref mut subscription) = self.subscription else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
self.blocks_sent += 1;
|
||||
|
||||
if self.blocks_sent % (self.threshold + 1) == 0 {
|
||||
debug!(
|
||||
target: "reth-bench",
|
||||
target_block = ?block_number,
|
||||
last_persisted = self.last_persisted,
|
||||
blocks_sent = self.blocks_sent,
|
||||
"Waiting for persistence"
|
||||
);
|
||||
|
||||
wait_for_persistence(
|
||||
subscription.stream_mut(),
|
||||
block_number,
|
||||
&mut self.last_persisted,
|
||||
self.timeout,
|
||||
)
|
||||
.await?;
|
||||
|
||||
debug!(
|
||||
target: "reth-bench",
|
||||
persisted = self.last_persisted,
|
||||
"Persistence caught up"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::time::Instant;
|
||||
|
||||
#[test]
|
||||
fn test_engine_url_to_ws_url() {
|
||||
// http -> ws, always uses port 8546
|
||||
let result = engine_url_to_ws_url("http://localhost:8551").unwrap();
|
||||
assert_eq!(result.as_str(), "ws://localhost:8546/");
|
||||
|
||||
// https -> wss
|
||||
let result = engine_url_to_ws_url("https://localhost:8551").unwrap();
|
||||
assert_eq!(result.as_str(), "wss://localhost:8546/");
|
||||
|
||||
// Custom engine port still maps to 8546
|
||||
let result = engine_url_to_ws_url("http://localhost:9551").unwrap();
|
||||
assert_eq!(result.port(), Some(8546));
|
||||
|
||||
// Already ws passthrough
|
||||
let result = engine_url_to_ws_url("ws://localhost:8546").unwrap();
|
||||
assert_eq!(result.scheme(), "ws");
|
||||
|
||||
// Invalid inputs
|
||||
assert!(engine_url_to_ws_url("ftp://localhost:8551").is_err());
|
||||
assert!(engine_url_to_ws_url("not a valid url").is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_waiter_with_duration() {
|
||||
let mut waiter = PersistenceWaiter::with_duration(Duration::from_millis(1));
|
||||
|
||||
let start = Instant::now();
|
||||
waiter.on_block(1).await.unwrap();
|
||||
waiter.on_block(2).await.unwrap();
|
||||
waiter.on_block(3).await.unwrap();
|
||||
|
||||
// Should have waited ~3ms total
|
||||
assert!(start.elapsed() >= Duration::from_millis(3));
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,4 @@
|
||||
//! Command for replaying pre-generated payloads from disk.
|
||||
//!
|
||||
//! This command reads `ExecutionPayloadEnvelopeV4` files from a directory and replays them
|
||||
//! in sequence using `newPayload` followed by `forkchoiceUpdated`.
|
||||
//!
|
||||
//! Supports configurable waiting behavior:
|
||||
//! - **`--wait-time`**: Fixed sleep interval between blocks.
|
||||
//! - **`--wait-for-persistence`**: Waits for every Nth block to be persisted using the
|
||||
//! `reth_subscribePersistedBlock` subscription, where N matches the engine's persistence
|
||||
//! threshold. This ensures the benchmark doesn't outpace persistence.
|
||||
//!
|
||||
//! Both options can be used together or independently.
|
||||
|
||||
use crate::{
|
||||
authenticated_transport::AuthenticatedTransportConnect,
|
||||
@@ -17,11 +6,7 @@ use crate::{
|
||||
helpers::parse_duration,
|
||||
metrics_scraper::MetricsScraper,
|
||||
output::{
|
||||
write_benchmark_results, CombinedResult, GasRampPayloadFile, NewPayloadResult,
|
||||
TotalGasOutput, TotalGasRow,
|
||||
},
|
||||
persistence_waiter::{
|
||||
derive_ws_rpc_url, setup_persistence_subscription, PersistenceWaiter,
|
||||
write_benchmark_results, CombinedResult, NewPayloadResult, TotalGasOutput, TotalGasRow,
|
||||
},
|
||||
},
|
||||
valid_payload::{call_forkchoice_updated_with_reth, call_new_payload_with_reth},
|
||||
@@ -36,14 +21,13 @@ use alloy_rpc_types_engine::{
|
||||
use clap::Parser;
|
||||
use eyre::Context;
|
||||
use reth_cli_runner::CliContext;
|
||||
use reth_engine_primitives::config::DEFAULT_PERSISTENCE_THRESHOLD;
|
||||
use reth_node_api::EngineApiMessageVersion;
|
||||
use reth_rpc_api::RethNewPayloadInput;
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tracing::{debug, info};
|
||||
use tracing::{debug, info, warn};
|
||||
use url::Url;
|
||||
|
||||
/// `reth bench replay-payloads` command
|
||||
@@ -73,9 +57,9 @@ pub struct Command {
|
||||
#[arg(long, value_name = "SKIP", default_value = "0")]
|
||||
skip: usize,
|
||||
|
||||
/// Optional directory containing gas ramp payloads to replay first.
|
||||
/// These are replayed before the main payloads to warm up the gas limit.
|
||||
#[arg(long, value_name = "GAS_RAMP_DIR")]
|
||||
/// Deprecated: gas ramp is no longer needed. Use `--testing.skip-gas-limit-ramp-check`
|
||||
/// and `--testing.gas-limit` on the reth node instead. This flag is accepted but ignored.
|
||||
#[arg(long, value_name = "GAS_RAMP_DIR", hide = true)]
|
||||
gas_ramp_dir: Option<PathBuf>,
|
||||
|
||||
/// Optional output directory for benchmark results (CSV files).
|
||||
@@ -89,47 +73,6 @@ pub struct Command {
|
||||
#[arg(long, value_name = "WAIT_TIME", value_parser = parse_duration, verbatim_doc_comment)]
|
||||
wait_time: Option<Duration>,
|
||||
|
||||
/// Wait for blocks to be persisted before sending the next batch.
|
||||
///
|
||||
/// When enabled, waits for every Nth block to be persisted using the
|
||||
/// `reth_subscribePersistedBlock` subscription. This ensures the benchmark
|
||||
/// doesn't outpace persistence.
|
||||
///
|
||||
/// The subscription uses the regular RPC websocket endpoint (no JWT required).
|
||||
#[arg(long, default_value = "false", verbatim_doc_comment)]
|
||||
wait_for_persistence: bool,
|
||||
|
||||
/// Engine persistence threshold used for deciding when to wait for persistence.
|
||||
///
|
||||
/// The benchmark waits after every `(threshold + 1)` blocks. By default this
|
||||
/// matches the engine's `DEFAULT_PERSISTENCE_THRESHOLD` (2), so waits occur
|
||||
/// at blocks 3, 6, 9, etc.
|
||||
#[arg(
|
||||
long = "persistence-threshold",
|
||||
value_name = "PERSISTENCE_THRESHOLD",
|
||||
default_value_t = DEFAULT_PERSISTENCE_THRESHOLD,
|
||||
verbatim_doc_comment
|
||||
)]
|
||||
persistence_threshold: u64,
|
||||
|
||||
/// Timeout for waiting on persistence at each checkpoint.
|
||||
///
|
||||
/// Must be long enough to account for the persistence thread being blocked
|
||||
/// by pruning after the previous save.
|
||||
#[arg(
|
||||
long = "persistence-timeout",
|
||||
value_name = "PERSISTENCE_TIMEOUT",
|
||||
value_parser = parse_duration,
|
||||
default_value = "120s",
|
||||
verbatim_doc_comment
|
||||
)]
|
||||
persistence_timeout: Duration,
|
||||
|
||||
/// Optional `WebSocket` RPC URL for persistence subscription.
|
||||
/// If not provided, derives from engine RPC URL by changing scheme to ws and port to 8546.
|
||||
#[arg(long, value_name = "WS_RPC_URL", verbatim_doc_comment)]
|
||||
ws_rpc_url: Option<String>,
|
||||
|
||||
/// Use `reth_newPayload` endpoint instead of `engine_newPayload*`.
|
||||
///
|
||||
/// The `reth_newPayload` endpoint is a reth-specific extension that takes `ExecutionData`
|
||||
@@ -138,6 +81,20 @@ pub struct Command {
|
||||
#[arg(long, default_value = "false", verbatim_doc_comment)]
|
||||
reth_new_payload: bool,
|
||||
|
||||
/// Skip waiting for in-flight persistence before processing.
|
||||
///
|
||||
/// Only works with `--reth-new-payload`. When set, passes `wait_for_persistence: false`
|
||||
/// to the `reth_newPayload` endpoint.
|
||||
#[arg(long, default_value = "false", verbatim_doc_comment, requires = "reth_new_payload")]
|
||||
no_wait_for_persistence: bool,
|
||||
|
||||
/// Skip waiting for execution cache and sparse trie locks before processing.
|
||||
///
|
||||
/// Only works with `--reth-new-payload`. When set, passes `wait_for_caches: false`
|
||||
/// to the `reth_newPayload` endpoint.
|
||||
#[arg(long, default_value = "false", verbatim_doc_comment, requires = "reth_new_payload")]
|
||||
no_wait_for_caches: bool,
|
||||
|
||||
/// Optional Prometheus metrics endpoint to scrape after each block.
|
||||
///
|
||||
/// When provided, reth-bench will fetch metrics from this URL after each
|
||||
@@ -157,18 +114,6 @@ struct LoadedPayload {
|
||||
block_hash: B256,
|
||||
}
|
||||
|
||||
/// A gas ramp payload loaded from disk.
|
||||
struct GasRampPayload {
|
||||
/// Block number from filename.
|
||||
block_number: u64,
|
||||
/// Engine API version for newPayload.
|
||||
///
|
||||
/// `None` indicates that `reth_newPayload` should be used.
|
||||
version: Option<EngineApiMessageVersion>,
|
||||
/// The file contents.
|
||||
file: GasRampPayloadFile,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
/// Execute the `replay-payloads` command.
|
||||
pub async fn execute(self, _ctx: CliContext) -> eyre::Result<()> {
|
||||
@@ -178,44 +123,10 @@ impl Command {
|
||||
if let Some(duration) = self.wait_time {
|
||||
info!(target: "reth-bench", "Using wait-time mode with {}ms delay between blocks", duration.as_millis());
|
||||
}
|
||||
if self.wait_for_persistence {
|
||||
info!(
|
||||
target: "reth-bench",
|
||||
"Persistence waiting enabled (waits after every {} blocks to match engine gap > {} behavior)",
|
||||
self.persistence_threshold + 1,
|
||||
self.persistence_threshold
|
||||
);
|
||||
}
|
||||
if self.reth_new_payload {
|
||||
info!("Using reth_newPayload and reth_forkchoiceUpdated endpoints");
|
||||
}
|
||||
|
||||
// Set up waiter based on configured options
|
||||
// When both are set: wait at least wait_time, and also wait for persistence if needed
|
||||
let mut waiter = match (self.wait_time, self.wait_for_persistence) {
|
||||
(Some(duration), true) => {
|
||||
let ws_url = derive_ws_rpc_url(self.ws_rpc_url.as_deref(), &self.engine_rpc_url)?;
|
||||
let sub = setup_persistence_subscription(ws_url, self.persistence_timeout).await?;
|
||||
Some(PersistenceWaiter::with_duration_and_subscription(
|
||||
duration,
|
||||
sub,
|
||||
self.persistence_threshold,
|
||||
self.persistence_timeout,
|
||||
))
|
||||
}
|
||||
(Some(duration), false) => Some(PersistenceWaiter::with_duration(duration)),
|
||||
(None, true) => {
|
||||
let ws_url = derive_ws_rpc_url(self.ws_rpc_url.as_deref(), &self.engine_rpc_url)?;
|
||||
let sub = setup_persistence_subscription(ws_url, self.persistence_timeout).await?;
|
||||
Some(PersistenceWaiter::with_subscription(
|
||||
sub,
|
||||
self.persistence_threshold,
|
||||
self.persistence_timeout,
|
||||
))
|
||||
}
|
||||
(None, false) => None,
|
||||
};
|
||||
|
||||
let mut metrics_scraper = MetricsScraper::maybe_new(self.metrics_url.clone());
|
||||
|
||||
// Set up authenticated engine provider
|
||||
@@ -245,18 +156,16 @@ impl Command {
|
||||
"Using initial parent block"
|
||||
);
|
||||
|
||||
// Load all payloads upfront to avoid I/O delays between phases
|
||||
let gas_ramp_payloads = if let Some(ref gas_ramp_dir) = self.gas_ramp_dir {
|
||||
let payloads = self.load_gas_ramp_payloads(gas_ramp_dir)?;
|
||||
if payloads.is_empty() {
|
||||
return Err(eyre::eyre!("No gas ramp payload files found in {:?}", gas_ramp_dir));
|
||||
}
|
||||
info!(target: "reth-bench", count = payloads.len(), "Loaded gas ramp payloads from disk");
|
||||
payloads
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
// Warn if deprecated --gas-ramp-dir is passed
|
||||
if self.gas_ramp_dir.is_some() {
|
||||
warn!(
|
||||
target: "reth-bench",
|
||||
"--gas-ramp-dir is deprecated and ignored. Use --testing.skip-gas-limit-ramp-check \
|
||||
and --testing.gas-limit on the reth node instead."
|
||||
);
|
||||
}
|
||||
|
||||
// Load all payloads upfront to avoid I/O delays between phases
|
||||
let payloads = self.load_payloads()?;
|
||||
if payloads.is_empty() {
|
||||
return Err(eyre::eyre!("No payload files found in {:?}", self.payload_dir));
|
||||
@@ -265,40 +174,6 @@ impl Command {
|
||||
|
||||
let mut parent_hash = initial_parent_hash;
|
||||
|
||||
// Replay gas ramp payloads first
|
||||
for (i, payload) in gas_ramp_payloads.iter().enumerate() {
|
||||
info!(
|
||||
target: "reth-bench",
|
||||
gas_ramp_payload = i + 1,
|
||||
total = gas_ramp_payloads.len(),
|
||||
block_number = payload.block_number,
|
||||
block_hash = %payload.file.block_hash,
|
||||
"Executing gas ramp payload (newPayload + FCU)"
|
||||
);
|
||||
|
||||
let _ = call_new_payload_with_reth(
|
||||
&auth_provider,
|
||||
payload.version,
|
||||
payload.file.params.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let fcu_state = ForkchoiceState {
|
||||
head_block_hash: payload.file.block_hash,
|
||||
safe_block_hash: parent_hash,
|
||||
finalized_block_hash: parent_hash,
|
||||
};
|
||||
call_forkchoice_updated_with_reth(&auth_provider, payload.version, fcu_state).await?;
|
||||
|
||||
info!(target: "reth-bench", gas_ramp_payload = i + 1, "Gas ramp payload executed successfully");
|
||||
|
||||
parent_hash = payload.file.block_hash;
|
||||
}
|
||||
|
||||
if !gas_ramp_payloads.is_empty() {
|
||||
info!(target: "reth-bench", count = gas_ramp_payloads.len(), "All gas ramp payloads replayed");
|
||||
}
|
||||
|
||||
let mut results = Vec::new();
|
||||
let total_benchmark_duration = Instant::now();
|
||||
|
||||
@@ -345,7 +220,14 @@ impl Command {
|
||||
},
|
||||
),
|
||||
};
|
||||
(None, serde_json::to_value((RethNewPayloadInput::ExecutionData(reth_data),))?)
|
||||
(
|
||||
None,
|
||||
serde_json::to_value((
|
||||
RethNewPayloadInput::ExecutionData(reth_data),
|
||||
self.no_wait_for_persistence.then_some(false),
|
||||
self.no_wait_for_caches.then_some(false),
|
||||
))?,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Some(EngineApiMessageVersion::V4),
|
||||
@@ -409,10 +291,6 @@ impl Command {
|
||||
tracing::warn!(target: "reth-bench", %err, block_number, "Failed to scrape metrics");
|
||||
}
|
||||
|
||||
if let Some(w) = &mut waiter {
|
||||
w.on_block(block_number).await?;
|
||||
}
|
||||
|
||||
let gas_row =
|
||||
TotalGasRow { block_number, transaction_count, gas_used, time: current_duration };
|
||||
results.push((gas_row, combined_result));
|
||||
@@ -420,10 +298,6 @@ impl Command {
|
||||
parent_hash = block_hash;
|
||||
}
|
||||
|
||||
// Drop waiter - we don't need to wait for final blocks to persist
|
||||
// since the benchmark goal is measuring Ggas/s of newPayload/FCU, not persistence.
|
||||
drop(waiter);
|
||||
|
||||
let (gas_output_results, combined_results): (Vec<TotalGasRow>, Vec<CombinedResult>) =
|
||||
results.into_iter().unzip();
|
||||
|
||||
@@ -510,65 +384,4 @@ impl Command {
|
||||
|
||||
Ok(payloads)
|
||||
}
|
||||
|
||||
/// Load and parse gas ramp payload files from a directory.
|
||||
fn load_gas_ramp_payloads(&self, dir: &PathBuf) -> eyre::Result<Vec<GasRampPayload>> {
|
||||
let mut payloads = Vec::new();
|
||||
|
||||
let entries: Vec<_> = std::fs::read_dir(dir)
|
||||
.wrap_err_with(|| format!("Failed to read directory {:?}", dir))?
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| {
|
||||
e.path().extension().and_then(|s| s.to_str()) == Some("json") &&
|
||||
e.file_name().to_string_lossy().starts_with("payload_block_")
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Parse filenames to get block numbers and sort
|
||||
let mut indexed_paths: Vec<(u64, PathBuf)> = entries
|
||||
.into_iter()
|
||||
.filter_map(|e| {
|
||||
let name = e.file_name();
|
||||
let name_str = name.to_string_lossy();
|
||||
// Extract block number from "payload_block_NNN.json"
|
||||
let block_str = name_str.strip_prefix("payload_block_")?.strip_suffix(".json")?;
|
||||
let block_number: u64 = block_str.parse().ok()?;
|
||||
Some((block_number, e.path()))
|
||||
})
|
||||
.collect();
|
||||
|
||||
indexed_paths.sort_by_key(|(num, _)| *num);
|
||||
|
||||
for (block_number, path) in indexed_paths {
|
||||
let content = std::fs::read_to_string(&path)
|
||||
.wrap_err_with(|| format!("Failed to read {:?}", path))?;
|
||||
let file: GasRampPayloadFile = serde_json::from_str(&content)
|
||||
.wrap_err_with(|| format!("Failed to parse {:?}", path))?;
|
||||
|
||||
let version = if let Some(version) = file.version {
|
||||
match version {
|
||||
1 => EngineApiMessageVersion::V1,
|
||||
2 => EngineApiMessageVersion::V2,
|
||||
3 => EngineApiMessageVersion::V3,
|
||||
4 => EngineApiMessageVersion::V4,
|
||||
5 => EngineApiMessageVersion::V5,
|
||||
v => return Err(eyre::eyre!("Invalid version {} in {:?}", v, path)),
|
||||
}
|
||||
.into()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
info!(
|
||||
block_number,
|
||||
block_hash = %file.block_hash,
|
||||
path = %path.display(),
|
||||
"Loaded gas ramp payload"
|
||||
);
|
||||
|
||||
payloads.push(GasRampPayload { block_number, version, file });
|
||||
}
|
||||
|
||||
Ok(payloads)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +240,7 @@ impl Command {
|
||||
ExecutionPayload::V1(p) => config.apply_to_payload_v1(p),
|
||||
ExecutionPayload::V2(p) => config.apply_to_payload_v2(p),
|
||||
ExecutionPayload::V3(p) => config.apply_to_payload_v3(p),
|
||||
ExecutionPayload::V4(p) => config.apply_to_payload_v3(&mut p.payload_inner),
|
||||
};
|
||||
|
||||
let skip_recalc = self.skip_hash_recalc || config.should_skip_hash_recalc();
|
||||
@@ -254,6 +255,9 @@ impl Command {
|
||||
ExecutionPayload::V1(p) => p.block_hash,
|
||||
ExecutionPayload::V2(p) => p.payload_inner.block_hash,
|
||||
ExecutionPayload::V3(p) => p.payload_inner.payload_inner.block_hash,
|
||||
ExecutionPayload::V4(p) => {
|
||||
p.payload_inner.payload_inner.payload_inner.block_hash
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -262,6 +266,9 @@ impl Command {
|
||||
ExecutionPayload::V1(p) => p.block_hash = new_hash,
|
||||
ExecutionPayload::V2(p) => p.payload_inner.block_hash = new_hash,
|
||||
ExecutionPayload::V3(p) => p.payload_inner.payload_inner.block_hash = new_hash,
|
||||
ExecutionPayload::V4(p) => {
|
||||
p.payload_inner.payload_inner.payload_inner.block_hash = new_hash
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -167,16 +167,25 @@ where
|
||||
/// Converts an RPC block into versioned engine API params and an [`ExecutionData`].
|
||||
///
|
||||
/// Returns `(version, versioned_params, execution_data)`.
|
||||
///
|
||||
/// When `no_wait_for_persistence` or `no_wait_for_caches` is `true` and using `reth_newPayload`,
|
||||
/// passes the corresponding `wait_for_*: false` to skip that wait.
|
||||
pub(crate) fn block_to_new_payload(
|
||||
block: AnyRpcBlock,
|
||||
is_optimism: bool,
|
||||
rlp: Option<Bytes>,
|
||||
reth_new_payload: bool,
|
||||
no_wait_for_persistence: bool,
|
||||
no_wait_for_caches: bool,
|
||||
) -> eyre::Result<(Option<EngineApiMessageVersion>, serde_json::Value)> {
|
||||
if let Some(rlp) = rlp {
|
||||
return Ok((
|
||||
None,
|
||||
serde_json::to_value((RethNewPayloadInput::<ExecutionData>::BlockRlp(rlp),))?,
|
||||
serde_json::to_value((
|
||||
RethNewPayloadInput::<ExecutionData>::BlockRlp(rlp),
|
||||
no_wait_for_persistence.then_some(false),
|
||||
no_wait_for_caches.then_some(false),
|
||||
))?,
|
||||
));
|
||||
}
|
||||
let block = block
|
||||
@@ -194,7 +203,14 @@ pub(crate) fn block_to_new_payload(
|
||||
payload_to_new_payload(payload, sidecar, is_optimism, block.withdrawals_root, None)?;
|
||||
|
||||
if reth_new_payload {
|
||||
Ok((None, serde_json::to_value((RethNewPayloadInput::ExecutionData(execution_data),))?))
|
||||
Ok((
|
||||
None,
|
||||
serde_json::to_value((
|
||||
RethNewPayloadInput::ExecutionData(execution_data),
|
||||
no_wait_for_persistence.then_some(false),
|
||||
no_wait_for_caches.then_some(false),
|
||||
))?,
|
||||
))
|
||||
} else {
|
||||
Ok((Some(version), params))
|
||||
}
|
||||
@@ -214,6 +230,20 @@ pub(crate) fn payload_to_new_payload(
|
||||
let execution_data = ExecutionData { payload: payload.clone(), sidecar: sidecar.clone() };
|
||||
|
||||
let (version, params) = match payload {
|
||||
ExecutionPayload::V4(payload) => {
|
||||
let cancun = sidecar.cancun().unwrap();
|
||||
let prague = sidecar.prague().unwrap();
|
||||
let requests = prague.requests.requests_hash();
|
||||
(
|
||||
EngineApiMessageVersion::V5,
|
||||
serde_json::to_value((
|
||||
payload,
|
||||
cancun.versioned_hashes.clone(),
|
||||
cancun.parent_beacon_block_root,
|
||||
requests,
|
||||
))?,
|
||||
)
|
||||
}
|
||||
ExecutionPayload::V3(payload) => {
|
||||
let cancun = sidecar.cancun().unwrap();
|
||||
|
||||
@@ -371,9 +401,12 @@ pub(crate) async fn call_forkchoice_updated<N, P: EngineApiValidWaitExt<N>>(
|
||||
forkchoice_state: ForkchoiceState,
|
||||
payload_attributes: Option<PayloadAttributes>,
|
||||
) -> TransportResult<ForkchoiceUpdated> {
|
||||
// FCU V3 is used for both Cancun and Prague (there is no FCU V4)
|
||||
// FCU V3 is used for Cancun, Prague, and Amsterdam (there is no FCU V4-V6)
|
||||
match message_version {
|
||||
EngineApiMessageVersion::V3 | EngineApiMessageVersion::V4 | EngineApiMessageVersion::V5 => {
|
||||
EngineApiMessageVersion::V3 |
|
||||
EngineApiMessageVersion::V4 |
|
||||
EngineApiMessageVersion::V5 |
|
||||
EngineApiMessageVersion::V6 => {
|
||||
provider.fork_choice_updated_v3_wait(forkchoice_state, payload_attributes).await
|
||||
}
|
||||
EngineApiMessageVersion::V2 => {
|
||||
|
||||
@@ -70,7 +70,7 @@ aquamarine.workspace = true
|
||||
clap = { workspace = true, features = ["derive", "env"] }
|
||||
|
||||
[dev-dependencies]
|
||||
alloy-node-bindings = "1.6.3"
|
||||
alloy-node-bindings = "1.5.2"
|
||||
alloy-provider = { workspace = true, features = ["reqwest"] }
|
||||
alloy-rpc-types-eth.workspace = true
|
||||
backon.workspace = true
|
||||
|
||||
@@ -28,7 +28,7 @@ use alloy_consensus::{
|
||||
};
|
||||
use alloy_eips::{
|
||||
eip1559::INITIAL_BASE_FEE, eip7685::EMPTY_REQUESTS_HASH, eip7840::BlobParams,
|
||||
eip7892::BlobScheduleBlobParams,
|
||||
eip7892::BlobScheduleBlobParams, eip7928::EMPTY_BLOCK_ACCESS_LIST_HASH,
|
||||
};
|
||||
use alloy_genesis::{ChainConfig, Genesis};
|
||||
use alloy_primitives::{address, b256, Address, BlockNumber, B256, U256};
|
||||
@@ -76,6 +76,18 @@ pub fn make_genesis_header(genesis: &Genesis, hardforks: &ChainHardforks) -> Hea
|
||||
.active_at_timestamp(genesis.timestamp)
|
||||
.then_some(EMPTY_REQUESTS_HASH);
|
||||
|
||||
// If Amsterdam is activated at genesis we set block access list hash to empty hash.
|
||||
let block_access_list_hash = hardforks
|
||||
.fork(EthereumHardfork::Amsterdam)
|
||||
.active_at_timestamp(genesis.timestamp)
|
||||
.then_some(EMPTY_BLOCK_ACCESS_LIST_HASH);
|
||||
|
||||
// If Amsterdam is activated at genesis we set slot number to 0.
|
||||
let slot_number = hardforks
|
||||
.fork(EthereumHardfork::Amsterdam)
|
||||
.active_at_timestamp(genesis.timestamp)
|
||||
.then_some(0);
|
||||
|
||||
Header {
|
||||
number: genesis.number.unwrap_or_default(),
|
||||
parent_hash: genesis.parent_hash.unwrap_or_default(),
|
||||
@@ -93,6 +105,8 @@ pub fn make_genesis_header(genesis: &Genesis, hardforks: &ChainHardforks) -> Hea
|
||||
blob_gas_used,
|
||||
excess_blob_gas,
|
||||
requests_hash,
|
||||
block_access_list_hash,
|
||||
slot_number,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -298,6 +312,7 @@ pub fn create_chain_config(
|
||||
cancun_time: timestamp(EthereumHardfork::Cancun),
|
||||
prague_time: timestamp(EthereumHardfork::Prague),
|
||||
osaka_time: timestamp(EthereumHardfork::Osaka),
|
||||
amsterdam_time: timestamp(EthereumHardfork::Amsterdam),
|
||||
bpo1_time: timestamp(EthereumHardfork::Bpo1),
|
||||
bpo2_time: timestamp(EthereumHardfork::Bpo2),
|
||||
bpo3_time: timestamp(EthereumHardfork::Bpo3),
|
||||
@@ -880,6 +895,7 @@ impl From<Genesis> for ChainSpec {
|
||||
(EthereumHardfork::Cancun.boxed(), genesis.config.cancun_time),
|
||||
(EthereumHardfork::Prague.boxed(), genesis.config.prague_time),
|
||||
(EthereumHardfork::Osaka.boxed(), genesis.config.osaka_time),
|
||||
(EthereumHardfork::Amsterdam.boxed(), genesis.config.amsterdam_time),
|
||||
(EthereumHardfork::Bpo1.boxed(), genesis.config.bpo1_time),
|
||||
(EthereumHardfork::Bpo2.boxed(), genesis.config.bpo2_time),
|
||||
(EthereumHardfork::Bpo3.boxed(), genesis.config.bpo3_time),
|
||||
@@ -1191,6 +1207,19 @@ impl ChainSpecBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable Amsterdam at genesis.
|
||||
pub fn amsterdam_activated(mut self) -> Self {
|
||||
self = self.osaka_activated();
|
||||
self.hardforks.insert(EthereumHardfork::Amsterdam, ForkCondition::Timestamp(0));
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable Amsterdam at the given timestamp.
|
||||
pub fn with_amsterdam_at(mut self, timestamp: u64) -> Self {
|
||||
self.hardforks.insert(EthereumHardfork::Amsterdam, ForkCondition::Timestamp(timestamp));
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the resulting [`ChainSpec`].
|
||||
///
|
||||
/// # Panics
|
||||
|
||||
@@ -179,8 +179,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, true)
|
||||
.wrap_err_with(|| {
|
||||
format!(
|
||||
"Failed to validate block {} {}",
|
||||
|
||||
@@ -87,6 +87,27 @@ pub fn validate_cancun_gas<B: Block>(block: &SealedBlock<B>) -> Result<(), Conse
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate that Amsterdam header fields are present in the block.
|
||||
///
|
||||
/// This checks that the `block_access_list_hash` and `slot_number` are set in the header,
|
||||
/// as required post-Amsterdam.
|
||||
///
|
||||
/// See [EIP-7928]: Block-level Access Lists
|
||||
/// See [EIP-7778]: Slot Number in Block Header
|
||||
///
|
||||
/// [EIP-7928]: https://eips.ethereum.org/EIPS/eip-7928
|
||||
/// [EIP-7778]: https://eips.ethereum.org/EIPS/eip-7778
|
||||
#[inline]
|
||||
pub fn validate_amsterdam_header_fields<H: BlockHeader>(header: &H) -> Result<(), ConsensusError> {
|
||||
if header.block_access_list_hash().is_none() {
|
||||
return Err(ConsensusError::BlockAccessListHashMissing);
|
||||
}
|
||||
if header.slot_number().is_none() {
|
||||
return Err(ConsensusError::SlotNumberMissing);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensures the block response data matches the header.
|
||||
///
|
||||
/// This ensures the body response items match the header's hashes:
|
||||
|
||||
@@ -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,8 @@ std = [
|
||||
"reth-primitives-traits/std",
|
||||
"alloy-primitives/std",
|
||||
"alloy-consensus/std",
|
||||
"reth-primitives-traits/std",
|
||||
"alloy-eip7928/std",
|
||||
"reth-execution-types/std",
|
||||
"thiserror/std",
|
||||
]
|
||||
test-utils = [
|
||||
"reth-primitives-traits/test-utils",
|
||||
]
|
||||
test-utils = ["reth-primitives-traits/test-utils"]
|
||||
|
||||
@@ -13,6 +13,7 @@ extern crate alloc;
|
||||
|
||||
use alloc::{boxed::Box, fmt::Debug, string::String, sync::Arc, vec::Vec};
|
||||
use alloy_consensus::Header;
|
||||
use alloy_eip7928::BlockAccessList;
|
||||
use alloy_primitives::{BlockHash, BlockNumber, Bloom, B256};
|
||||
use core::error::Error;
|
||||
|
||||
@@ -54,12 +55,17 @@ pub trait FullConsensus<N: NodePrimitives>: Consensus<N::Block> {
|
||||
/// If `receipt_root_bloom` is provided, the implementation should use the pre-computed
|
||||
/// receipt root and logs bloom instead of computing them from the receipts.
|
||||
///
|
||||
/// If `allow_bal_check` is enabled, we calculate the bal hash and match with the header bal
|
||||
/// hash. We don't do by default because for payload validation, we do the same bal check
|
||||
///
|
||||
/// Note: validating blocks does not include other validations of the Consensus
|
||||
fn validate_block_post_execution(
|
||||
&self,
|
||||
block: &RecoveredBlock<N::Block>,
|
||||
result: &BlockExecutionResult<N::Receipt>,
|
||||
receipt_root_bloom: Option<ReceiptRootBloom>,
|
||||
block_access_list: Option<BlockAccessList>,
|
||||
allow_bal_check: bool,
|
||||
) -> Result<(), ConsensusError>;
|
||||
}
|
||||
|
||||
@@ -331,6 +337,30 @@ pub enum ConsensusError {
|
||||
#[error("unexpected parent beacon block root")]
|
||||
ParentBeaconBlockRootUnexpected,
|
||||
|
||||
/// Error when the block access list hash is missing.
|
||||
#[error("missing block access list hash")]
|
||||
BlockAccessListHashMissing,
|
||||
|
||||
/// Error when an unexpected block access list hash is encountered.
|
||||
#[error("unexpected block access list hash")]
|
||||
BlockAccessListHashUnexpected,
|
||||
|
||||
/// Error when an unexpected block access list cost is encountered.
|
||||
#[error("block access list cost exceeds gas limit")]
|
||||
BlockAccessListCostMoreThanGasLimit,
|
||||
|
||||
/// Error when the block access list hash doesn't match the expected value.
|
||||
#[error("block access list hash mismatch: {0}")]
|
||||
BlockAccessListHashMismatch(GotExpectedBoxed<B256>),
|
||||
|
||||
/// Error when the slot number is missing.
|
||||
#[error("missing slot number")]
|
||||
SlotNumberMissing,
|
||||
|
||||
/// Error when an unexpected slot number is encountered.
|
||||
#[error("unexpected slot number")]
|
||||
SlotNumberUnexpected,
|
||||
|
||||
/// Error when blob gas used exceeds the maximum allowed.
|
||||
#[error("blob gas used {blob_gas_used} exceeds maximum allowance {max_blob_gas_per_block}")]
|
||||
BlobGasUsedExceedsMaxBlobGasPerBlock {
|
||||
|
||||
@@ -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,8 @@ 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>,
|
||||
_allow_bal_check: bool,
|
||||
) -> 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,8 @@ 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>,
|
||||
_allow_bal_check: bool,
|
||||
) -> Result<(), ConsensusError> {
|
||||
if self.fail_validation() {
|
||||
Err(ConsensusError::BaseFeeMissing)
|
||||
|
||||
@@ -55,6 +55,8 @@ pub fn generate_test_blocks(chain_spec: &ChainSpec, count: u64) -> Vec<SealedBlo
|
||||
excess_blob_gas: None,
|
||||
parent_beacon_block_root: None,
|
||||
requests_hash: None,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
// Set required fields based on chain spec
|
||||
|
||||
@@ -227,6 +227,7 @@ where
|
||||
suggested_fee_recipient: alloy_primitives::Address::random(),
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: Some(B256::ZERO),
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
env.active_node_state_mut()?
|
||||
@@ -299,6 +300,7 @@ where
|
||||
suggested_fee_recipient: alloy_primitives::Address::random(),
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: Some(B256::ZERO),
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
let fresh_fcu_result = EngineApiClient::<Engine>::fork_choice_updated_v3(
|
||||
|
||||
@@ -271,6 +271,7 @@ where
|
||||
suggested_fee_recipient: alloy_primitives::Address::ZERO,
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: Some(B256::ZERO),
|
||||
slot_number: None,
|
||||
};
|
||||
EthPayloadBuilderAttributes::new(B256::ZERO, attributes)
|
||||
};
|
||||
@@ -301,6 +302,7 @@ where
|
||||
suggested_fee_recipient: alloy_primitives::Address::ZERO,
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: Some(B256::ZERO),
|
||||
slot_number: None,
|
||||
};
|
||||
<<N as NodeTypes>::Payload as PayloadTypes>::PayloadBuilderAttributes::from(
|
||||
EthPayloadBuilderAttributes::new(B256::ZERO, attributes),
|
||||
|
||||
@@ -161,6 +161,7 @@ async fn test_testsuite_assert_mine_block() -> Result<()> {
|
||||
suggested_fee_recipient: Address::random(),
|
||||
withdrawals: None,
|
||||
parent_beacon_block_root: None,
|
||||
slot_number: None,
|
||||
},
|
||||
));
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
//! E2E tests for `RocksDB` provider functionality.
|
||||
|
||||
use alloy_consensus::BlockHeader;
|
||||
use alloy_primitives::B256;
|
||||
use alloy_rpc_types_eth::{Transaction, TransactionReceipt};
|
||||
use alloy_eips::eip2718::Encodable2718;
|
||||
use alloy_primitives::{Address, Bytes, TxKind, B256, U256};
|
||||
use alloy_rpc_types_eth::{Transaction, TransactionInput, TransactionReceipt, TransactionRequest};
|
||||
use eyre::Result;
|
||||
use jsonrpsee::core::client::ClientT;
|
||||
use reth_chainspec::{ChainSpec, ChainSpecBuilder, MAINNET};
|
||||
@@ -89,6 +90,7 @@ fn test_attributes_generator(timestamp: u64) -> EthPayloadBuilderAttributes {
|
||||
suggested_fee_recipient: alloy_primitives::Address::ZERO,
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: Some(B256::ZERO),
|
||||
slot_number: None,
|
||||
};
|
||||
EthPayloadBuilderAttributes::new(B256::ZERO, attributes)
|
||||
}
|
||||
@@ -573,3 +575,426 @@ async fn test_rocksdb_reorg_unwind() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Historical account queries: verifies that `eth_getBalance` and `eth_getTransactionCount`
|
||||
/// return correct values at past block numbers after the account state has changed.
|
||||
///
|
||||
/// This test exercises the database-backed historical state lookup path. After mining the
|
||||
/// blocks we care about (1-3), we mine additional blocks to advance the canonical head
|
||||
/// far enough that the engine tree's persistence + in-memory eviction cycle guarantees
|
||||
/// blocks 1-3 are no longer in the in-memory overlay. Historical queries for those blocks
|
||||
/// must then be served from `RocksDB` changesets.
|
||||
#[tokio::test]
|
||||
async fn test_rocksdb_historical_account_queries() -> Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let chain_spec = test_chain_spec();
|
||||
let chain_id = chain_spec.chain().id();
|
||||
|
||||
let (mut nodes, _) = E2ETestSetupBuilder::<EthereumNode, _>::new(
|
||||
1,
|
||||
chain_spec.clone(),
|
||||
test_attributes_generator,
|
||||
)
|
||||
.with_storage_v2()
|
||||
.with_tree_config_modifier(|config| config.with_persistence_threshold(0))
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
assert_eq!(nodes.len(), 1);
|
||||
|
||||
let wallets = wallet::Wallet::new(1).with_chain_id(chain_id).wallet_gen();
|
||||
let signer = wallets[0].clone();
|
||||
let sender: Address = signer.address();
|
||||
let client = nodes[0].rpc_client().expect("RPC client");
|
||||
|
||||
// Query the sender's balance and nonce at genesis (block 0)
|
||||
let genesis_balance: U256 = client.request("eth_getBalance", (sender, "0x0")).await?;
|
||||
let genesis_nonce: U256 = client.request("eth_getTransactionCount", (sender, "0x0")).await?;
|
||||
assert!(genesis_balance > U256::ZERO, "Sender should have genesis balance");
|
||||
assert_eq!(genesis_nonce, U256::ZERO, "Sender nonce should be 0 at genesis");
|
||||
|
||||
// Mine block 1 with a transfer (nonce 0)
|
||||
let raw_tx1 =
|
||||
TransactionTestContext::transfer_tx_bytes_with_nonce(chain_id, signer.clone(), 0).await;
|
||||
let tx_hash1 = nodes[0].rpc.inject_tx(raw_tx1).await?;
|
||||
wait_for_pending_tx(&client, tx_hash1).await;
|
||||
|
||||
let payload1 = nodes[0].advance_block().await?;
|
||||
assert_eq!(payload1.block().number(), 1);
|
||||
poll_tx_in_rocksdb(&nodes[0].inner.provider, tx_hash1).await;
|
||||
|
||||
// Record state after block 1
|
||||
let balance_at_1: U256 = client.request("eth_getBalance", (sender, "0x1")).await?;
|
||||
let nonce_at_1: U256 = client.request("eth_getTransactionCount", (sender, "0x1")).await?;
|
||||
assert!(balance_at_1 < genesis_balance, "Balance should decrease after transfer + gas");
|
||||
assert_eq!(nonce_at_1, U256::from(1), "Nonce should be 1 after first tx");
|
||||
|
||||
// Mine block 2 with another transfer (nonce 1)
|
||||
let raw_tx2 =
|
||||
TransactionTestContext::transfer_tx_bytes_with_nonce(chain_id, signer.clone(), 1).await;
|
||||
let tx_hash2 = nodes[0].rpc.inject_tx(raw_tx2).await?;
|
||||
wait_for_pending_tx(&client, tx_hash2).await;
|
||||
|
||||
let payload2 = nodes[0].advance_block().await?;
|
||||
assert_eq!(payload2.block().number(), 2);
|
||||
poll_tx_in_rocksdb(&nodes[0].inner.provider, tx_hash2).await;
|
||||
|
||||
let balance_at_2: U256 = client.request("eth_getBalance", (sender, "0x2")).await?;
|
||||
let nonce_at_2: U256 = client.request("eth_getTransactionCount", (sender, "0x2")).await?;
|
||||
assert!(balance_at_2 < balance_at_1, "Balance should decrease further after second tx");
|
||||
assert_eq!(nonce_at_2, U256::from(2), "Nonce should be 2 after second tx");
|
||||
|
||||
// Mine block 3 with a third transfer (nonce 2)
|
||||
let raw_tx3 =
|
||||
TransactionTestContext::transfer_tx_bytes_with_nonce(chain_id, signer.clone(), 2).await;
|
||||
let tx_hash3 = nodes[0].rpc.inject_tx(raw_tx3).await?;
|
||||
wait_for_pending_tx(&client, tx_hash3).await;
|
||||
|
||||
let payload3 = nodes[0].advance_block().await?;
|
||||
assert_eq!(payload3.block().number(), 3);
|
||||
poll_tx_in_rocksdb(&nodes[0].inner.provider, tx_hash3).await;
|
||||
|
||||
let balance_at_3: U256 = client.request("eth_getBalance", (sender, "0x3")).await?;
|
||||
let nonce_at_3: U256 = client.request("eth_getTransactionCount", (sender, "0x3")).await?;
|
||||
assert!(balance_at_3 < balance_at_2, "Balance should decrease further after third tx");
|
||||
assert_eq!(nonce_at_3, U256::from(3), "Nonce should be 3 after third tx");
|
||||
|
||||
// Mine additional blocks to push blocks 1-3 out of the in-memory overlay.
|
||||
// With persistence_threshold=0 and memory_block_buffer_target=0, each new block
|
||||
// triggers persistence up to `head` followed by in-memory eviction. Mining several
|
||||
// more blocks ensures the engine loop has completed at least one full
|
||||
// persist-then-evict cycle covering blocks 1-3.
|
||||
// Each block needs a transaction because the payload builder requires non-empty payloads.
|
||||
for nonce in 3..8u64 {
|
||||
let raw_tx =
|
||||
TransactionTestContext::transfer_tx_bytes_with_nonce(chain_id, signer.clone(), nonce)
|
||||
.await;
|
||||
let tx_hash = nodes[0].rpc.inject_tx(raw_tx).await?;
|
||||
wait_for_pending_tx(&client, tx_hash).await;
|
||||
nodes[0].advance_block().await?;
|
||||
}
|
||||
// Allow the engine loop to process the persistence completions
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
|
||||
// Historical queries — blocks 1-3 should now be served from the database.
|
||||
let hist_balance_0: U256 = client.request("eth_getBalance", (sender, "0x0")).await?;
|
||||
let hist_nonce_0: U256 = client.request("eth_getTransactionCount", (sender, "0x0")).await?;
|
||||
assert_eq!(
|
||||
hist_balance_0, genesis_balance,
|
||||
"Historical balance at block 0 should match genesis"
|
||||
);
|
||||
assert_eq!(hist_nonce_0, U256::ZERO, "Historical nonce at block 0 should be 0");
|
||||
|
||||
let hist_balance_1: U256 = client.request("eth_getBalance", (sender, "0x1")).await?;
|
||||
let hist_nonce_1: U256 = client.request("eth_getTransactionCount", (sender, "0x1")).await?;
|
||||
assert_eq!(hist_balance_1, balance_at_1, "Historical balance at block 1 should match");
|
||||
assert_eq!(hist_nonce_1, U256::from(1), "Historical nonce at block 1 should be 1");
|
||||
|
||||
let hist_balance_2: U256 = client.request("eth_getBalance", (sender, "0x2")).await?;
|
||||
let hist_nonce_2: U256 = client.request("eth_getTransactionCount", (sender, "0x2")).await?;
|
||||
assert_eq!(hist_balance_2, balance_at_2, "Historical balance at block 2 should match");
|
||||
assert_eq!(hist_nonce_2, U256::from(2), "Historical nonce at block 2 should be 2");
|
||||
|
||||
let hist_balance_3: U256 = client.request("eth_getBalance", (sender, "0x3")).await?;
|
||||
let hist_nonce_3: U256 = client.request("eth_getTransactionCount", (sender, "0x3")).await?;
|
||||
assert_eq!(hist_balance_3, balance_at_3, "Historical balance at block 3 should match");
|
||||
assert_eq!(hist_nonce_3, U256::from(3), "Historical nonce at block 3 should be 3");
|
||||
|
||||
// "latest" should still match head
|
||||
let latest_balance: U256 = client.request("eth_getBalance", (sender, "latest")).await?;
|
||||
let latest_nonce: U256 = client.request("eth_getTransactionCount", (sender, "latest")).await?;
|
||||
assert_eq!(
|
||||
latest_nonce,
|
||||
U256::from(8),
|
||||
"Latest nonce should be 8 (3 original + 5 extra blocks)"
|
||||
);
|
||||
assert!(latest_balance < balance_at_3, "Latest balance should be less than block 3 balance");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reproduces the race condition between `save_blocks` and `RocksDB` pruning described in
|
||||
/// <https://github.com/paradigmxyz/reth/pull/23081>.
|
||||
///
|
||||
/// Both `save_blocks` and the pruner push to `pending_rocksdb_batches` before a single
|
||||
/// `commit()`. The pruner reads committed (stale) state that doesn't include `save_blocks`'
|
||||
/// new entry, filters it, and pushes its own batch. On commit the pruner's batch overwrites
|
||||
/// `save_blocks`' batch for the same `ShardedKey(addr, u64::MAX)`. Every cycle the new
|
||||
/// block's history entry is silently lost. After enough cycles the shard is completely empty.
|
||||
///
|
||||
/// This test mines blocks with account-history pruning (`block_interval=1`,
|
||||
/// `minimum_distance=5`), waits for persistence, then reads the `AccountsHistory` shard
|
||||
/// directly from `RocksDB`. Without the fix the shard is empty — all entries were
|
||||
/// overwritten by the pruner's stale batches. With the fix, entries for the most recent
|
||||
/// blocks survive.
|
||||
#[tokio::test]
|
||||
async fn test_rocksdb_account_history_pruning() -> Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let chain_spec = test_chain_spec();
|
||||
let chain_id = chain_spec.chain().id();
|
||||
|
||||
const PRUNE_DISTANCE: u64 = 5;
|
||||
const TOTAL_BLOCKS: u64 = 20;
|
||||
|
||||
let (mut nodes, _) = E2ETestSetupBuilder::<EthereumNode, _>::new(
|
||||
1,
|
||||
chain_spec.clone(),
|
||||
test_attributes_generator,
|
||||
)
|
||||
.with_storage_v2()
|
||||
.with_tree_config_modifier(|config| config.with_persistence_threshold(0))
|
||||
.with_node_config_modifier(|mut config| {
|
||||
config.pruning.account_history_distance = Some(PRUNE_DISTANCE);
|
||||
config.pruning.minimum_distance = Some(PRUNE_DISTANCE);
|
||||
config.pruning.block_interval = Some(1);
|
||||
config
|
||||
})
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
assert_eq!(nodes.len(), 1);
|
||||
|
||||
let wallets = wallet::Wallet::new(1).with_chain_id(chain_id).wallet_gen();
|
||||
let signer = wallets[0].clone();
|
||||
let sender: Address = signer.address();
|
||||
let client = nodes[0].rpc_client().expect("RPC client");
|
||||
|
||||
// Mine blocks one at a time with a delay so each save_blocks + pruner cycle
|
||||
// completes independently. The race fires every cycle (the pruner reads stale
|
||||
// committed state that doesn't include save_blocks' pending batch), but processing
|
||||
// one block at a time makes the outcome deterministic.
|
||||
let mut last_tx_hash = B256::ZERO;
|
||||
for nonce in 0..TOTAL_BLOCKS {
|
||||
let raw_tx =
|
||||
TransactionTestContext::transfer_tx_bytes_with_nonce(chain_id, signer.clone(), nonce)
|
||||
.await;
|
||||
let tx_hash = nodes[0].rpc.inject_tx(raw_tx).await?;
|
||||
wait_for_pending_tx(&client, tx_hash).await;
|
||||
|
||||
let payload = nodes[0].advance_block().await?;
|
||||
assert_eq!(payload.block().number(), nonce + 1);
|
||||
last_tx_hash = tx_hash;
|
||||
|
||||
// Let the persistence cycle (save_blocks → pruner → commit) complete before
|
||||
// producing the next block, so each cycle processes exactly one block.
|
||||
tokio::time::sleep(Duration::from_millis(300)).await;
|
||||
}
|
||||
|
||||
// Wait for the last block to be fully persisted to RocksDB.
|
||||
poll_tx_in_rocksdb(&nodes[0].inner.provider, last_tx_hash).await;
|
||||
|
||||
// Read the AccountsHistory shard for `sender` directly from RocksDB.
|
||||
// This is the data structure corrupted by the race.
|
||||
let rocksdb = nodes[0].inner.provider.rocksdb_provider();
|
||||
let shards = rocksdb.account_history_shards(sender).unwrap();
|
||||
let all_entries: Vec<u64> = shards.iter().flat_map(|(_, list)| list.iter()).collect();
|
||||
|
||||
// The sender has a transfer in every block, so the shard should contain an entry
|
||||
// for every block in the retention window: (TOTAL_BLOCKS - PRUNE_DISTANCE, TOTAL_BLOCKS].
|
||||
//
|
||||
// Without the fix: the pruner reads stale committed state each cycle, overwrites
|
||||
// save_blocks' entry, and only the very last block survives (no subsequent pruner
|
||||
// cycle to overwrite it). The shard ends up as just [TOTAL_BLOCKS].
|
||||
//
|
||||
// With the fix: save_blocks' batch is committed before the pruner reads, so the
|
||||
// pruner sees the new entry and preserves it. All retained blocks are present.
|
||||
let expected: Vec<u64> = ((TOTAL_BLOCKS - PRUNE_DISTANCE + 1)..=TOTAL_BLOCKS).collect();
|
||||
assert_eq!(
|
||||
all_entries, expected,
|
||||
"AccountsHistory shard for sender doesn't match expected retention window. \
|
||||
Expected {expected:?}, got {all_entries:?}. \
|
||||
The pruner's stale batch overwrote save_blocks' entries \
|
||||
(save_blocks/pruner race, see PR #23081)."
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mirrors [`test_rocksdb_account_history_pruning`] for the `StoragesHistory` table.
|
||||
///
|
||||
/// The same race condition between `save_blocks` and the pruner that affects
|
||||
/// `AccountsHistory` also affects `StoragesHistory`:
|
||||
/// - `write_storage_history` reads committed `RocksDB` state and pushes a batch.
|
||||
/// - `prune_storage_history_batch` also reads stale committed state and pushes its own batch.
|
||||
/// - On a single `commit()`, the pruner's batch overwrites `save_blocks`' batch for the same
|
||||
/// `StorageShardedKey(addr, slot, u64::MAX)`.
|
||||
///
|
||||
/// This test deploys a minimal contract that writes to storage slot 0 each block,
|
||||
/// then verifies that `StoragesHistory` contains entries for the full retention
|
||||
/// window. Without the fix the shard would be truncated — new entries silently
|
||||
/// lost every cycle.
|
||||
#[tokio::test]
|
||||
async fn test_rocksdb_storage_history_pruning() -> Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let chain_spec = test_chain_spec();
|
||||
let chain_id = chain_spec.chain().id();
|
||||
|
||||
const PRUNE_DISTANCE: u64 = 5;
|
||||
const TOTAL_BLOCKS: u64 = 20;
|
||||
|
||||
let (mut nodes, _) = E2ETestSetupBuilder::<EthereumNode, _>::new(
|
||||
1,
|
||||
chain_spec.clone(),
|
||||
test_attributes_generator,
|
||||
)
|
||||
.with_storage_v2()
|
||||
.with_tree_config_modifier(|config| config.with_persistence_threshold(0))
|
||||
.with_node_config_modifier(|mut config| {
|
||||
config.pruning.storage_history_distance = Some(PRUNE_DISTANCE);
|
||||
config.pruning.minimum_distance = Some(PRUNE_DISTANCE);
|
||||
config.pruning.block_interval = Some(1);
|
||||
config
|
||||
})
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
assert_eq!(nodes.len(), 1);
|
||||
|
||||
let wallets = wallet::Wallet::new(1).with_chain_id(chain_id).wallet_gen();
|
||||
let signer = wallets[0].clone();
|
||||
let client = nodes[0].rpc_client().expect("RPC client");
|
||||
|
||||
// Deploy a minimal contract that stores CALLDATA[0..32] into slot 0:
|
||||
// PUSH0 ; [0]
|
||||
// CALLDATALOAD ; [calldata[0..32]]
|
||||
// PUSH0 ; [0, calldata[0..32]]
|
||||
// SSTORE ; sstore(0, calldata[0..32])
|
||||
// STOP
|
||||
// Bytecode: 5f355f5500
|
||||
//
|
||||
// Init code that deploys this runtime:
|
||||
// PUSH5 5f355f5500 ; push 5-byte runtime
|
||||
// PUSH0 ; offset 0 in memory
|
||||
// MSTORE ; store at mem[0..32] (right-padded in 32 bytes)
|
||||
// PUSH1 0x05 ; size = 5
|
||||
// PUSH1 0x1b ; offset = 27 (32 - 5)
|
||||
// PUSH0 ; destOffset = 0
|
||||
// CODECOPY ; copy runtime to mem[0..5]
|
||||
// PUSH1 0x05 ; size = 5
|
||||
// PUSH0 ; offset = 0
|
||||
// RETURN ; return mem[0..5]
|
||||
//
|
||||
// We can simplify: just use PUSH + MSTORE + RETURN pattern.
|
||||
// Init code (hex):
|
||||
// 645f355f5500 PUSH5 runtime_bytecode
|
||||
// 5f PUSH0 (memory offset for MSTORE, stores at 27..32)
|
||||
// 52 MSTORE
|
||||
// 6005 PUSH1 5 (size)
|
||||
// 601b PUSH1 27 (offset = 32-5)
|
||||
// f3 RETURN
|
||||
let init_code = Bytes::from_static(&[
|
||||
0x64, 0x5f, 0x35, 0x5f, 0x55, 0x00, // PUSH5 runtime
|
||||
0x5f, // PUSH0
|
||||
0x52, // MSTORE
|
||||
0x60, 0x05, // PUSH1 5
|
||||
0x60, 0x1b, // PUSH1 27
|
||||
0xf3, // RETURN
|
||||
]);
|
||||
|
||||
// Deploy in block 1 (nonce 0)
|
||||
let deploy_tx = TransactionRequest {
|
||||
nonce: Some(0),
|
||||
value: Some(U256::ZERO),
|
||||
to: Some(TxKind::Create),
|
||||
gas: Some(100_000),
|
||||
max_fee_per_gas: Some(1000e9 as u128),
|
||||
max_priority_fee_per_gas: Some(20e9 as u128),
|
||||
chain_id: Some(chain_id),
|
||||
input: TransactionInput { input: None, data: Some(init_code) },
|
||||
..Default::default()
|
||||
};
|
||||
let signed_deploy = TransactionTestContext::sign_tx(signer.clone(), deploy_tx).await;
|
||||
let deploy_bytes: Bytes = signed_deploy.encoded_2718().into();
|
||||
let deploy_hash = nodes[0].rpc.inject_tx(deploy_bytes).await?;
|
||||
wait_for_pending_tx(&client, deploy_hash).await;
|
||||
|
||||
let payload1 = nodes[0].advance_block().await?;
|
||||
assert_eq!(payload1.block().number(), 1);
|
||||
poll_tx_in_rocksdb(&nodes[0].inner.provider, deploy_hash).await;
|
||||
|
||||
// Let the persistence cycle complete before the next block (same cadence as the loop below)
|
||||
tokio::time::sleep(Duration::from_millis(300)).await;
|
||||
|
||||
// Get the deployed contract address from the receipt
|
||||
let receipt: Option<TransactionReceipt> =
|
||||
client.request("eth_getTransactionReceipt", [deploy_hash]).await?;
|
||||
let contract_address = receipt
|
||||
.expect("deploy receipt should exist")
|
||||
.contract_address
|
||||
.expect("deploy should create a contract");
|
||||
|
||||
// Sanity check: verify the runtime bytecode is what we expect
|
||||
let code: Bytes = client.request("eth_getCode", (contract_address, "latest")).await?;
|
||||
assert_eq!(
|
||||
code,
|
||||
Bytes::from_static(&[0x5f, 0x35, 0x5f, 0x55, 0x00]),
|
||||
"Deployed runtime should be PUSH0 CALLDATALOAD PUSH0 SSTORE STOP"
|
||||
);
|
||||
|
||||
// The storage slot we track: slot 0, encoded as B256
|
||||
let storage_slot = B256::ZERO;
|
||||
|
||||
// Mine TOTAL_BLOCKS - 1 more blocks (block 2..=TOTAL_BLOCKS), each calling the
|
||||
// contract to write a new value to slot 0. This creates a storage changeset entry
|
||||
// per block for (contract_address, slot 0).
|
||||
let mut last_tx_hash = deploy_hash;
|
||||
for nonce in 1..TOTAL_BLOCKS {
|
||||
// calldata = abi encode the block number so each write is unique
|
||||
let block_num = nonce + 1;
|
||||
let calldata = B256::from(U256::from(block_num));
|
||||
|
||||
let call_tx = TransactionRequest {
|
||||
nonce: Some(nonce),
|
||||
value: Some(U256::ZERO),
|
||||
to: Some(TxKind::Call(contract_address)),
|
||||
gas: Some(50_000),
|
||||
max_fee_per_gas: Some(1000e9 as u128),
|
||||
max_priority_fee_per_gas: Some(20e9 as u128),
|
||||
chain_id: Some(chain_id),
|
||||
input: TransactionInput { input: None, data: Some(Bytes::from(calldata.0)) },
|
||||
..Default::default()
|
||||
};
|
||||
let signed_call = TransactionTestContext::sign_tx(signer.clone(), call_tx).await;
|
||||
let call_bytes: Bytes = signed_call.encoded_2718().into();
|
||||
let tx_hash = nodes[0].rpc.inject_tx(call_bytes).await?;
|
||||
wait_for_pending_tx(&client, tx_hash).await;
|
||||
|
||||
let payload = nodes[0].advance_block().await?;
|
||||
assert_eq!(payload.block().number(), block_num);
|
||||
last_tx_hash = tx_hash;
|
||||
|
||||
// Let the persistence cycle complete before the next block
|
||||
tokio::time::sleep(Duration::from_millis(300)).await;
|
||||
}
|
||||
|
||||
// Wait for the last block to be fully persisted to RocksDB
|
||||
poll_tx_in_rocksdb(&nodes[0].inner.provider, last_tx_hash).await;
|
||||
|
||||
// Read StoragesHistory shard for (contract_address, slot 0) directly from RocksDB
|
||||
let rocksdb = nodes[0].inner.provider.rocksdb_provider();
|
||||
let shards = rocksdb.storage_history_shards(contract_address, storage_slot).unwrap();
|
||||
let all_entries: Vec<u64> = shards.iter().flat_map(|(_, list)| list.iter()).collect();
|
||||
|
||||
// The contract has a storage write in blocks 2..=TOTAL_BLOCKS (the deploy in block 1
|
||||
// only executes init code — no SSTORE — so block 1 has no StoragesHistory entry for
|
||||
// slot 0). With pruning distance=5, the retention window should be
|
||||
// (TOTAL_BLOCKS - PRUNE_DISTANCE, TOTAL_BLOCKS] = blocks 16..=20.
|
||||
//
|
||||
// Without the fix: the pruner overwrites save_blocks' entries each cycle,
|
||||
// leaving only the very last block (or empty).
|
||||
//
|
||||
// With the fix: all blocks in the retention window are present.
|
||||
let expected: Vec<u64> = ((TOTAL_BLOCKS - PRUNE_DISTANCE + 1)..=TOTAL_BLOCKS).collect();
|
||||
assert_eq!(
|
||||
all_entries, expected,
|
||||
"StoragesHistory shard for contract slot 0 doesn't match expected retention window. \
|
||||
Expected {expected:?}, got {all_entries:?}. \
|
||||
The pruner's stale batch overwrote save_blocks' entries \
|
||||
(save_blocks/pruner race for StorageHistory, see PR #23081)."
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ op-alloy-rpc-types-engine = { workspace = true, optional = true }
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
# op = [
|
||||
# "dep:op-alloy-rpc-types-engine",
|
||||
# "reth-payload-primitives/op",
|
||||
# "reth-primitives-traits/op",
|
||||
# ]
|
||||
op = [
|
||||
"dep:op-alloy-rpc-types-engine",
|
||||
"reth-payload-primitives/op",
|
||||
"reth-primitives-traits/op",
|
||||
]
|
||||
|
||||
@@ -57,6 +57,7 @@ where
|
||||
.chain_spec
|
||||
.is_cancun_active_at_timestamp(timestamp)
|
||||
.then(B256::random),
|
||||
slot_number: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,13 +146,22 @@ pub struct TreeConfig {
|
||||
slow_block_threshold: Option<Duration>,
|
||||
/// Whether to fully disable sparse trie cache pruning between blocks.
|
||||
disable_sparse_trie_cache_pruning: bool,
|
||||
/// Whether to use the arena-based sparse trie implementation.
|
||||
enable_arena_sparse_trie: bool,
|
||||
/// Timeout for the state root task before spawning a sequential fallback computation.
|
||||
/// If `Some`, after waiting this duration for the state root task, a sequential state root
|
||||
/// computation is spawned in parallel and whichever finishes first is used.
|
||||
/// If `None`, the timeout fallback is disabled.
|
||||
state_root_task_timeout: Option<Duration>,
|
||||
/// Whether to disable BAL (Block Access List, EIP-7928) based parallel execution.
|
||||
/// When disabled, falls back to transaction-based prewarming even when a BAL is available.
|
||||
disable_bal_parallel_execution: bool,
|
||||
/// Whether to disable BAL-driven parallel state root computation.
|
||||
/// When disabled, the BAL hashed post state is not sent to the multiproof task for
|
||||
/// early parallel state root computation.
|
||||
disable_bal_parallel_state_root: bool,
|
||||
/// Whether to disable BAL (Block Access List) batched IO during prewarming.
|
||||
/// When disabled, falls back to individual per-slot storage reads instead of
|
||||
/// batched cursor reads via `storage_range`.
|
||||
disable_bal_batch_io: bool,
|
||||
/// Maximum random jitter applied before each proof computation (trie-debug only).
|
||||
/// When set, each proof worker sleeps for a random duration up to this value
|
||||
/// before starting a proof calculation.
|
||||
@@ -187,8 +196,10 @@ impl Default for TreeConfig {
|
||||
sparse_trie_max_hot_accounts: DEFAULT_SPARSE_TRIE_MAX_HOT_ACCOUNTS,
|
||||
slow_block_threshold: None,
|
||||
disable_sparse_trie_cache_pruning: false,
|
||||
enable_arena_sparse_trie: false,
|
||||
state_root_task_timeout: Some(DEFAULT_STATE_ROOT_TASK_TIMEOUT),
|
||||
disable_bal_parallel_execution: false,
|
||||
disable_bal_parallel_state_root: false,
|
||||
disable_bal_batch_io: false,
|
||||
#[cfg(feature = "trie-debug")]
|
||||
proof_jitter: None,
|
||||
}
|
||||
@@ -249,8 +260,10 @@ impl TreeConfig {
|
||||
sparse_trie_max_hot_accounts,
|
||||
slow_block_threshold,
|
||||
disable_sparse_trie_cache_pruning: false,
|
||||
enable_arena_sparse_trie: false,
|
||||
state_root_task_timeout,
|
||||
disable_bal_parallel_execution: false,
|
||||
disable_bal_parallel_state_root: false,
|
||||
disable_bal_batch_io: false,
|
||||
#[cfg(feature = "trie-debug")]
|
||||
proof_jitter: None,
|
||||
}
|
||||
@@ -552,17 +565,6 @@ impl TreeConfig {
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns whether the arena-based sparse trie is enabled.
|
||||
pub const fn enable_arena_sparse_trie(&self) -> bool {
|
||||
self.enable_arena_sparse_trie
|
||||
}
|
||||
|
||||
/// Setter for whether to enable the arena-based sparse trie.
|
||||
pub const fn with_enable_arena_sparse_trie(mut self, value: bool) -> Self {
|
||||
self.enable_arena_sparse_trie = value;
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the state root task timeout.
|
||||
pub const fn state_root_task_timeout(&self) -> Option<Duration> {
|
||||
self.state_root_task_timeout
|
||||
@@ -574,6 +576,34 @@ impl TreeConfig {
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns whether BAL-based parallel execution is disabled.
|
||||
pub const fn disable_bal_parallel_execution(&self) -> bool {
|
||||
self.disable_bal_parallel_execution
|
||||
}
|
||||
|
||||
/// Setter for whether to disable BAL-based parallel execution.
|
||||
pub const fn without_bal_parallel_execution(
|
||||
mut self,
|
||||
disable_bal_parallel_execution: bool,
|
||||
) -> Self {
|
||||
self.disable_bal_parallel_execution = disable_bal_parallel_execution;
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns whether BAL-driven parallel state root computation is disabled.
|
||||
pub const fn disable_bal_parallel_state_root(&self) -> bool {
|
||||
self.disable_bal_parallel_state_root
|
||||
}
|
||||
|
||||
/// Setter for whether to disable BAL-driven parallel state root computation.
|
||||
pub const fn without_bal_parallel_state_root(
|
||||
mut self,
|
||||
disable_bal_parallel_state_root: bool,
|
||||
) -> Self {
|
||||
self.disable_bal_parallel_state_root = disable_bal_parallel_state_root;
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the proof jitter duration, if configured (trie-debug only).
|
||||
#[cfg(feature = "trie-debug")]
|
||||
pub const fn proof_jitter(&self) -> Option<Duration> {
|
||||
@@ -586,4 +616,15 @@ impl TreeConfig {
|
||||
self.proof_jitter = proof_jitter;
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns whether BAL batched IO is disabled.
|
||||
pub const fn disable_bal_batch_io(&self) -> bool {
|
||||
self.disable_bal_batch_io
|
||||
}
|
||||
|
||||
/// Setter for whether to disable BAL batched IO.
|
||||
pub const fn without_bal_batch_io(mut self, disable_bal_batch_io: bool) -> Self {
|
||||
self.disable_bal_batch_io = disable_bal_batch_io;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,12 +149,17 @@ pub struct NewPayloadTimings {
|
||||
/// Server-side execution latency.
|
||||
pub latency: Duration,
|
||||
/// Time spent waiting for persistence to complete.
|
||||
/// `None` when no persistence was in-flight.
|
||||
///
|
||||
/// `None` when wasn't asked to wait for persistence.
|
||||
pub persistence_wait: Option<Duration>,
|
||||
/// Time spent waiting for the execution cache lock.
|
||||
pub execution_cache_wait: Duration,
|
||||
/// Time spent waiting for the sparse trie lock.
|
||||
pub sparse_trie_wait: Duration,
|
||||
///
|
||||
/// `None` when wasn't asked to wait for execution cache.
|
||||
pub execution_cache_wait: Option<Duration>,
|
||||
/// Time spent waiting for the sparse trie cache lock.
|
||||
///
|
||||
/// `None` when wasn't asked to wait for sparse trie cache.
|
||||
pub sparse_trie_wait: Option<Duration>,
|
||||
}
|
||||
|
||||
/// A message for the beacon engine from other components of the node (engine RPC API invoked by the
|
||||
@@ -170,11 +175,17 @@ pub enum BeaconEngineMessage<Payload: PayloadTypes> {
|
||||
},
|
||||
/// Message with new payload used by `reth_newPayload` endpoint.
|
||||
///
|
||||
/// Waits for persistence, execution cache, and sparse trie locks before processing,
|
||||
/// and returns detailed timing breakdown alongside the payload status.
|
||||
/// Supports independent control over waiting for persistence and cache locks before
|
||||
/// processing, providing unbiased timing measurements when enabled.
|
||||
///
|
||||
/// Returns detailed timing breakdown alongside the payload status.
|
||||
RethNewPayload {
|
||||
/// The execution payload received by Engine API.
|
||||
payload: Payload::ExecutionData,
|
||||
/// Whether to wait for in-flight persistence to complete before processing.
|
||||
wait_for_persistence: bool,
|
||||
/// Whether to wait for execution cache and sparse trie locks before processing.
|
||||
wait_for_caches: bool,
|
||||
/// The sender for returning payload status result and timing breakdown.
|
||||
tx: oneshot::Sender<Result<(PayloadStatus, NewPayloadTimings), BeaconOnNewPayloadError>>,
|
||||
},
|
||||
@@ -259,14 +270,23 @@ where
|
||||
|
||||
/// Sends a new payload message used by `reth_newPayload` endpoint.
|
||||
///
|
||||
/// Waits for persistence, execution cache, and sparse trie locks before processing,
|
||||
/// and returns detailed timing breakdown alongside the payload status.
|
||||
/// `wait_for_persistence`: waits for in-flight persistence to complete.
|
||||
/// `wait_for_caches`: waits for execution cache and sparse trie locks.
|
||||
///
|
||||
/// Returns detailed timing breakdown alongside the payload status.
|
||||
pub async fn reth_new_payload(
|
||||
&self,
|
||||
payload: Payload::ExecutionData,
|
||||
wait_for_persistence: bool,
|
||||
wait_for_caches: bool,
|
||||
) -> Result<(PayloadStatus, NewPayloadTimings), BeaconOnNewPayloadError> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.to_engine.send(BeaconEngineMessage::RethNewPayload { payload, tx });
|
||||
let _ = self.to_engine.send(BeaconEngineMessage::RethNewPayload {
|
||||
payload,
|
||||
wait_for_persistence,
|
||||
wait_for_caches,
|
||||
tx,
|
||||
});
|
||||
rx.await.map_err(|_| BeaconOnNewPayloadError::EngineUnavailable)?
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ metrics.workspace = true
|
||||
reth-metrics = { workspace = true, features = ["common"] }
|
||||
|
||||
# misc
|
||||
itertools.workspace = true
|
||||
schnellru.workspace = true
|
||||
rayon.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
@@ -6,7 +6,7 @@ use alloy_primitives::{
|
||||
use fixed_cache::{AnyRef, CacheConfig, Stats, StatsHandler};
|
||||
use metrics::{Counter, Gauge, Histogram};
|
||||
use parking_lot::Once;
|
||||
use reth_errors::ProviderResult;
|
||||
use reth_errors::{ProviderError, ProviderResult};
|
||||
use reth_metrics::Metrics;
|
||||
use reth_primitives_traits::{Account, Bytecode};
|
||||
use reth_provider::{
|
||||
@@ -466,6 +466,55 @@ impl<S: StateProvider, const PREWARM: bool> StateProvider for CachedStateProvide
|
||||
self.state_provider.storage(account, storage_key)
|
||||
}
|
||||
}
|
||||
|
||||
fn storage_range(
|
||||
&self,
|
||||
account: Address,
|
||||
keys: &[StorageKey],
|
||||
) -> ProviderResult<Vec<(StorageKey, StorageValue)>> {
|
||||
let mut uncached_keys = Vec::new();
|
||||
let mut result = Vec::with_capacity(keys.len());
|
||||
|
||||
for &key in keys {
|
||||
if let Some(value) = self.caches.get_storage(account, key) {
|
||||
if !value.is_zero() {
|
||||
result.push((key, value));
|
||||
}
|
||||
} else {
|
||||
uncached_keys.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Batch-fetch all uncached keys from the inner provider
|
||||
if !uncached_keys.is_empty() {
|
||||
let mut fetched = self.state_provider.storage_range(account, &uncached_keys)?;
|
||||
// Sort by raw key to align with uncached_keys for the merge-join below.
|
||||
// The inner provider may return results in a different order (e.g. hashed state
|
||||
// iterates by hashed slot).
|
||||
fetched.sort_unstable_by_key(|(k, _)| *k);
|
||||
// Merge-join to find zero slots without allocating a HashSet.
|
||||
let mut fetched_iter = fetched.iter();
|
||||
let mut next = fetched_iter.next();
|
||||
for &key in &uncached_keys {
|
||||
if let Some(&(fk, fv)) = next &&
|
||||
fk == key
|
||||
{
|
||||
let _ = self.caches.get_or_try_insert_storage_with(account, key, || {
|
||||
Ok::<_, ProviderError>(fv)
|
||||
});
|
||||
result.push((key, fv));
|
||||
next = fetched_iter.next();
|
||||
continue;
|
||||
}
|
||||
// key not returned by inner provider → zero slot
|
||||
let _ = self.caches.get_or_try_insert_storage_with(account, key, || {
|
||||
Ok::<_, ProviderError>(StorageValue::ZERO)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: BytecodeReader, const PREWARM: bool> BytecodeReader for CachedStateProvider<S, PREWARM> {
|
||||
@@ -756,6 +805,11 @@ impl ExecutionCache {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a cached storage value if present, without inserting.
|
||||
pub fn get_storage(&self, address: Address, key: StorageKey) -> Option<StorageValue> {
|
||||
self.0.storage_cache.get(&(address, key))
|
||||
}
|
||||
|
||||
/// Insert storage value into cache.
|
||||
pub fn insert_storage(&self, address: Address, key: StorageKey, value: Option<StorageValue>) {
|
||||
self.0.storage_cache.insert((address, key), value.unwrap_or_default());
|
||||
|
||||
@@ -1544,59 +1544,53 @@ where
|
||||
// handle the event if any
|
||||
self.on_maybe_tree_event(maybe_event)?;
|
||||
}
|
||||
BeaconEngineMessage::RethNewPayload { payload, tx } => {
|
||||
// Before processing the new payload, we wait for persistence and
|
||||
// cache updates to complete. We do it in parallel, spawning
|
||||
// persistence and cache update wait tasks with Tokio, so that we
|
||||
// can get an unbiased breakdown on how long did every step take.
|
||||
//
|
||||
// If we first wait for persistence, and only then for cache
|
||||
// updates, we will offset the cache update waits by the duration of
|
||||
// persistence, which is incorrect.
|
||||
debug!(target: "engine::tree", "Waiting for persistence and caches in parallel before processing reth_newPayload");
|
||||
|
||||
let pending_persistence = self.persistence_state.rx.take();
|
||||
let persistence_rx = if let Some((rx, start_time, _action)) =
|
||||
pending_persistence
|
||||
{
|
||||
let (persistence_tx, persistence_rx) =
|
||||
std::sync::mpsc::channel();
|
||||
self.runtime.spawn_blocking_named("wait-persist", move || {
|
||||
let start = Instant::now();
|
||||
let result =
|
||||
rx.recv().expect("persistence state channel closed");
|
||||
let _ = persistence_tx.send((
|
||||
result,
|
||||
start_time,
|
||||
start.elapsed(),
|
||||
));
|
||||
});
|
||||
Some(persistence_rx)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let cache_wait = self.payload_validator.wait_for_caches();
|
||||
|
||||
let persistence_wait = if let Some(persistence_rx) = persistence_rx
|
||||
{
|
||||
let (result, start_time, wait_duration) = persistence_rx
|
||||
.recv()
|
||||
.expect("persistence result channel closed");
|
||||
let _ = self.on_persistence_complete(result, start_time);
|
||||
Some(wait_duration)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
BeaconEngineMessage::RethNewPayload {
|
||||
payload,
|
||||
wait_for_persistence,
|
||||
wait_for_caches,
|
||||
tx,
|
||||
} => {
|
||||
debug!(
|
||||
target: "engine::tree",
|
||||
?persistence_wait,
|
||||
execution_cache_wait = ?cache_wait.execution_cache,
|
||||
sparse_trie_wait = ?cache_wait.sparse_trie,
|
||||
"Persistence finished and caches updated for reth_newPayload"
|
||||
wait_for_persistence,
|
||||
wait_for_caches,
|
||||
"Processing reth_newPayload"
|
||||
);
|
||||
|
||||
let persistence_wait = if wait_for_persistence {
|
||||
let pending_persistence = self.persistence_state.rx.take();
|
||||
if let Some((rx, start_time, _action)) = pending_persistence {
|
||||
let (persistence_tx, persistence_rx) =
|
||||
std::sync::mpsc::channel();
|
||||
self.runtime.spawn_blocking_named(
|
||||
"wait-persist",
|
||||
move || {
|
||||
let start = Instant::now();
|
||||
let result = rx
|
||||
.recv()
|
||||
.expect("persistence state channel closed");
|
||||
let _ = persistence_tx.send((
|
||||
result,
|
||||
start_time,
|
||||
start.elapsed(),
|
||||
));
|
||||
},
|
||||
);
|
||||
let (result, start_time, wait_duration) = persistence_rx
|
||||
.recv()
|
||||
.expect("persistence result channel closed");
|
||||
let _ = self.on_persistence_complete(result, start_time);
|
||||
Some(wait_duration)
|
||||
} else {
|
||||
Some(Duration::ZERO)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let cache_wait = wait_for_caches
|
||||
.then(|| self.payload_validator.wait_for_caches());
|
||||
|
||||
let start = Instant::now();
|
||||
let gas_used = payload.gas_used();
|
||||
let num_hash = payload.num_hash();
|
||||
@@ -1615,8 +1609,9 @@ where
|
||||
let timings = NewPayloadTimings {
|
||||
latency,
|
||||
persistence_wait,
|
||||
execution_cache_wait: cache_wait.execution_cache,
|
||||
sparse_trie_wait: cache_wait.sparse_trie,
|
||||
execution_cache_wait: cache_wait
|
||||
.map(|wait| wait.execution_cache),
|
||||
sparse_trie_wait: cache_wait.map(|wait| wait.sparse_trie),
|
||||
};
|
||||
if let Err(err) =
|
||||
tx.send(output.map(|o| (o.outcome, timings)).map_err(|e| {
|
||||
|
||||
@@ -39,8 +39,7 @@ use reth_trie_parallel::{
|
||||
root::ParallelStateRootError,
|
||||
};
|
||||
use reth_trie_sparse::{
|
||||
ArenaParallelSparseTrie, ConfigurableSparseTrie, ParallelSparseTrie, ParallelismThresholds,
|
||||
RevealableSparseTrie, SparseStateTrie,
|
||||
ArenaParallelSparseTrie, ConfigurableSparseTrie, RevealableSparseTrie, SparseStateTrie,
|
||||
};
|
||||
use std::{
|
||||
ops::Not,
|
||||
@@ -62,14 +61,6 @@ pub mod sparse_trie;
|
||||
|
||||
use preserved_sparse_trie::{PreservedSparseTrie, SharedPreservedSparseTrie};
|
||||
|
||||
/// Default parallelism thresholds to use with the [`ParallelSparseTrie`].
|
||||
///
|
||||
/// These values were determined by performing benchmarks using gradually increasing values to judge
|
||||
/// the effects. Below 100 throughput would generally be equal or slightly less, while above 150 it
|
||||
/// would deteriorate to the point where PST might as well not be used.
|
||||
const PARALLEL_SPARSE_TRIE_PARALLELISM_THRESHOLDS: ParallelismThresholds =
|
||||
ParallelismThresholds { min_revealed_nodes: 100, min_updated_nodes: 100 };
|
||||
|
||||
/// Default node capacity for shrinking the sparse trie. This is used to limit the number of trie
|
||||
/// nodes in allocated sparse tries.
|
||||
///
|
||||
@@ -138,10 +129,14 @@ where
|
||||
sparse_trie_max_hot_accounts: usize,
|
||||
/// Whether sparse trie cache pruning is fully disabled.
|
||||
disable_sparse_trie_cache_pruning: bool,
|
||||
/// Whether to use the arena-based sparse trie implementation.
|
||||
enable_arena_sparse_trie: bool,
|
||||
/// Whether to disable cache metrics recording.
|
||||
disable_cache_metrics: bool,
|
||||
/// Whether to disable BAL-based parallel execution (falls back to tx-based prewarming).
|
||||
disable_bal_parallel_execution: bool,
|
||||
/// Whether to disable BAL-driven parallel state root computation.
|
||||
disable_bal_parallel_state_root: bool,
|
||||
/// Whether BAL batched IO is disabled.
|
||||
disable_bal_batch_io: bool,
|
||||
}
|
||||
|
||||
impl<N, Evm> PayloadProcessor<Evm>
|
||||
@@ -175,8 +170,10 @@ where
|
||||
sparse_trie_max_hot_slots: config.sparse_trie_max_hot_slots(),
|
||||
sparse_trie_max_hot_accounts: config.sparse_trie_max_hot_accounts(),
|
||||
disable_sparse_trie_cache_pruning: config.disable_sparse_trie_cache_pruning(),
|
||||
enable_arena_sparse_trie: config.enable_arena_sparse_trie(),
|
||||
disable_cache_metrics: config.disable_cache_metrics(),
|
||||
disable_bal_parallel_execution: config.disable_bal_parallel_execution(),
|
||||
disable_bal_parallel_state_root: config.disable_bal_parallel_state_root(),
|
||||
disable_bal_batch_io: config.disable_bal_batch_io(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -505,6 +502,7 @@ where
|
||||
executed_tx_index: Arc::clone(&executed_tx_index),
|
||||
precompile_cache_disabled: self.precompile_cache_disabled,
|
||||
precompile_cache_map: self.precompile_cache_map.clone(),
|
||||
disable_bal_batch_io: self.disable_bal_batch_io,
|
||||
};
|
||||
|
||||
let (prewarm_task, to_prewarm_task) = PrewarmCacheTask::new(
|
||||
@@ -512,14 +510,16 @@ where
|
||||
self.execution_cache.clone(),
|
||||
prewarm_ctx,
|
||||
to_multi_proof,
|
||||
self.disable_bal_parallel_state_root,
|
||||
);
|
||||
|
||||
{
|
||||
let to_prewarm_task = to_prewarm_task.clone();
|
||||
let disable_bal_parallel_execution = self.disable_bal_parallel_execution;
|
||||
self.executor.spawn_blocking_named("prewarm", move || {
|
||||
let mode = if skip_prewarm {
|
||||
PrewarmMode::Skipped
|
||||
} else if let Some(bal) = bal {
|
||||
} else if let Some(bal) = bal.filter(|_| !disable_bal_parallel_execution) {
|
||||
PrewarmMode::BlockAccessList(bal)
|
||||
} else {
|
||||
PrewarmMode::Transactions(transactions)
|
||||
@@ -567,7 +567,6 @@ where
|
||||
let max_hot_slots = self.sparse_trie_max_hot_slots;
|
||||
let max_hot_accounts = self.sparse_trie_max_hot_accounts;
|
||||
let disable_cache_pruning = self.disable_sparse_trie_cache_pruning;
|
||||
let enable_arena_sparse_trie = self.enable_arena_sparse_trie;
|
||||
let executor = self.executor.clone();
|
||||
|
||||
let parent_span = Span::current();
|
||||
@@ -594,19 +593,9 @@ where
|
||||
target: "engine::tree::payload_processor",
|
||||
"Creating new sparse trie - no preserved trie available"
|
||||
);
|
||||
let default_trie = if enable_arena_sparse_trie {
|
||||
RevealableSparseTrie::blind_from(
|
||||
ConfigurableSparseTrie::Arena(ArenaParallelSparseTrie::default()),
|
||||
)
|
||||
} else {
|
||||
RevealableSparseTrie::blind_from(
|
||||
ConfigurableSparseTrie::HashMap(
|
||||
ParallelSparseTrie::default().with_parallelism_thresholds(
|
||||
PARALLEL_SPARSE_TRIE_PARALLELISM_THRESHOLDS,
|
||||
),
|
||||
),
|
||||
)
|
||||
};
|
||||
let default_trie = RevealableSparseTrie::blind_from(
|
||||
ConfigurableSparseTrie::Arena(ArenaParallelSparseTrie::default()),
|
||||
);
|
||||
SparseStateTrie::default()
|
||||
.with_accounts_trie(default_trie.clone())
|
||||
.with_default_storage_trie(default_trie)
|
||||
|
||||
@@ -22,6 +22,7 @@ use alloy_eip7928::BlockAccessList;
|
||||
use alloy_eips::eip4895::Withdrawal;
|
||||
use alloy_primitives::{keccak256, StorageKey, B256};
|
||||
use crossbeam_channel::Sender as CrossbeamSender;
|
||||
use itertools::{EitherOrBoth, Itertools};
|
||||
use metrics::{Counter, Gauge, Histogram};
|
||||
use rayon::prelude::*;
|
||||
use reth_evm::{execute::ExecutableTxFor, ConfigureEvm, Evm, EvmFor, RecoveredTx, SpecFor};
|
||||
@@ -39,7 +40,7 @@ use std::sync::{
|
||||
mpsc::{self, channel, Receiver, Sender},
|
||||
Arc,
|
||||
};
|
||||
use tracing::{debug, debug_span, instrument, trace, warn, Span};
|
||||
use tracing::{debug, debug_span, instrument, trace, trace_span, warn, Span};
|
||||
|
||||
/// Determines the prewarming mode: transaction-based, BAL-based, or skipped.
|
||||
#[derive(Debug)]
|
||||
@@ -75,6 +76,8 @@ where
|
||||
actions_rx: Receiver<PrewarmTaskEvent<N::Receipt>>,
|
||||
/// Parent span for tracing
|
||||
parent_span: Span,
|
||||
/// Whether to disable BAL-driven parallel state root computation.
|
||||
disable_bal_parallel_state_root: bool,
|
||||
}
|
||||
|
||||
impl<N, P, Evm> PrewarmCacheTask<N, P, Evm>
|
||||
@@ -89,6 +92,7 @@ where
|
||||
execution_cache: PayloadExecutionCache,
|
||||
ctx: PrewarmContext<N, P, Evm>,
|
||||
to_multi_proof: Option<CrossbeamSender<MultiProofMessage>>,
|
||||
disable_bal_parallel_state_root: bool,
|
||||
) -> (Self, Sender<PrewarmTaskEvent<N::Receipt>>) {
|
||||
let (actions_tx, actions_rx) = channel();
|
||||
|
||||
@@ -107,6 +111,7 @@ where
|
||||
to_multi_proof,
|
||||
actions_rx,
|
||||
parent_span: Span::current(),
|
||||
disable_bal_parallel_state_root,
|
||||
},
|
||||
actions_tx,
|
||||
)
|
||||
@@ -165,7 +170,7 @@ where
|
||||
tx_count += 1;
|
||||
let parent_span = Span::current();
|
||||
s.spawn(move |_| {
|
||||
let _enter = debug_span!(
|
||||
let _enter = trace_span!(
|
||||
target: "engine::tree::payload_processor::prewarm",
|
||||
parent: parent_span,
|
||||
"prewarm_tx",
|
||||
@@ -290,24 +295,19 @@ where
|
||||
let new_cache = SavedCache::new(hash, caches, cache_metrics)
|
||||
.with_disable_cache_metrics(disable_cache_metrics);
|
||||
|
||||
// Insert state into cache while holding the lock
|
||||
// Access the BundleState through the shared ExecutionOutcome
|
||||
if new_cache.cache().insert_state(&execution_outcome.state).is_err() {
|
||||
// Clear the cache on error to prevent having a polluted cache
|
||||
*cached = None;
|
||||
debug!(target: "engine::caching", "cleared execution cache on update error");
|
||||
return;
|
||||
}
|
||||
|
||||
new_cache.update_metrics();
|
||||
|
||||
// Defer `insert_state` until after validation: FixedCache's non-blocking
|
||||
// inserts silently drop writes under concurrent reader contention.
|
||||
if valid_block_rx.recv().is_ok() {
|
||||
// Replace the shared cache with the new one; the previous cache (if any) is
|
||||
// dropped.
|
||||
if new_cache.cache().insert_state(&execution_outcome.state).is_err() {
|
||||
*cached = None;
|
||||
debug!(target: "engine::caching", "cleared execution cache on update error");
|
||||
return;
|
||||
}
|
||||
|
||||
*cached = Some(new_cache);
|
||||
} else {
|
||||
// Block was invalid; caches were already mutated by insert_state above,
|
||||
// so we must clear to prevent using polluted state
|
||||
*cached = None;
|
||||
debug!(target: "engine::caching", "cleared execution cache on invalid block");
|
||||
}
|
||||
@@ -356,7 +356,12 @@ where
|
||||
let ctx = self.ctx.clone();
|
||||
self.executor.prewarming_pool().install_fn(|| {
|
||||
bal.par_iter().for_each_init(
|
||||
|| (ctx.clone(), None::<CachedStateProvider<reth_provider::StateProviderBox>>),
|
||||
|| {
|
||||
(
|
||||
ctx.clone(),
|
||||
None::<CachedStateProvider<reth_provider::StateProviderBox, true>>,
|
||||
)
|
||||
},
|
||||
|(ctx, provider), account| {
|
||||
if ctx.should_stop() {
|
||||
return;
|
||||
@@ -381,6 +386,9 @@ where
|
||||
/// Converts the BAL to [`HashedPostState`](reth_trie::HashedPostState) and sends it to the
|
||||
/// multiproof task.
|
||||
fn send_bal_hashed_state(&self, bal: &BlockAccessList) {
|
||||
if self.disable_bal_parallel_state_root {
|
||||
return;
|
||||
}
|
||||
let Some(to_multi_proof) = &self.to_multi_proof else { return };
|
||||
|
||||
let provider = match self.ctx.provider.build() {
|
||||
@@ -515,6 +523,8 @@ where
|
||||
pub precompile_cache_disabled: bool,
|
||||
/// The precompile cache map.
|
||||
pub precompile_cache_map: PrecompileCacheMap<SpecFor<Evm>>,
|
||||
/// Whether BAL batched IO is disabled.
|
||||
pub disable_bal_batch_io: bool,
|
||||
}
|
||||
|
||||
/// Per-thread EVM state initialised by [`PrewarmContext::evm_for_ctx`] and stored in
|
||||
@@ -600,7 +610,7 @@ where
|
||||
/// thread.
|
||||
fn prefetch_bal_account(
|
||||
&self,
|
||||
provider: &mut Option<CachedStateProvider<reth_provider::StateProviderBox>>,
|
||||
provider: &mut Option<CachedStateProvider<reth_provider::StateProviderBox, true>>,
|
||||
account: &alloy_eip7928::AccountChanges,
|
||||
) {
|
||||
let state_provider = match provider {
|
||||
@@ -621,7 +631,7 @@ where
|
||||
self.saved_cache.as_ref().expect("BAL prewarm should only run with cache");
|
||||
let caches = saved_cache.cache().clone();
|
||||
let cache_metrics = saved_cache.metrics().clone();
|
||||
slot.insert(CachedStateProvider::new(built, caches, cache_metrics))
|
||||
slot.insert(CachedStateProvider::new_prewarm(built, caches, cache_metrics))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -629,11 +639,24 @@ where
|
||||
|
||||
let _ = state_provider.basic_account(&account.address);
|
||||
|
||||
for slot in &account.storage_changes {
|
||||
let _ = state_provider.storage(account.address, StorageKey::from(slot.slot));
|
||||
}
|
||||
for &slot in &account.storage_reads {
|
||||
let _ = state_provider.storage(account.address, StorageKey::from(slot));
|
||||
if self.disable_bal_batch_io {
|
||||
for slot in &account.storage_changes {
|
||||
let _ = state_provider.storage(account.address, StorageKey::from(slot.slot));
|
||||
}
|
||||
for &slot in &account.storage_reads {
|
||||
let _ = state_provider.storage(account.address, StorageKey::from(slot));
|
||||
}
|
||||
} else {
|
||||
let slots: Vec<StorageKey> = account
|
||||
.storage_changes
|
||||
.iter()
|
||||
.map(|s| StorageKey::from(s.slot))
|
||||
.merge_join_by(account.storage_reads.iter().map(|&s| StorageKey::from(s)), Ord::cmp)
|
||||
.map(|either| match either {
|
||||
EitherOrBoth::Left(k) | EitherOrBoth::Right(k) | EitherOrBoth::Both(k, _) => k,
|
||||
})
|
||||
.collect();
|
||||
let _ = state_provider.storage_range(account.address, &slots);
|
||||
}
|
||||
|
||||
self.metrics.bal_slot_iteration_duration.record(start.elapsed().as_secs_f64());
|
||||
|
||||
@@ -15,6 +15,7 @@ use alloy_eip7928::BlockAccessList;
|
||||
use alloy_eips::{eip1898::BlockWithParent, eip4895::Withdrawal, NumHash};
|
||||
use alloy_evm::Evm;
|
||||
use alloy_primitives::{map::B256Set, B256};
|
||||
use alloy_rlp::Decodable;
|
||||
#[cfg(feature = "trie-debug")]
|
||||
use reth_trie_sparse::debug_recorder::TrieDebugRecorder;
|
||||
|
||||
@@ -864,15 +865,21 @@ 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");
|
||||
|
||||
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()
|
||||
});
|
||||
|
||||
@@ -931,6 +938,7 @@ where
|
||||
handle.iter_transactions(),
|
||||
&receipt_tx,
|
||||
&executed_tx_index,
|
||||
has_bal,
|
||||
)?;
|
||||
drop(receipt_tx);
|
||||
|
||||
@@ -945,6 +953,32 @@ where
|
||||
debug_span!(target: "engine::tree", "merge_transitions")
|
||||
.in_scope(|| db.merge_transitions(BundleRetention::Reverts));
|
||||
|
||||
// Validate BAL hash if we executed with BAL tracking
|
||||
if has_bal {
|
||||
// Get the expected BAL from input and the built BAL from execution
|
||||
let expected_bal =
|
||||
input.block_access_list().transpose().map_err(BlockExecutionError::other)?;
|
||||
|
||||
let built_bal = db.take_built_alloy_bal();
|
||||
|
||||
// Compute hashes and compare
|
||||
let expected_hash = expected_bal
|
||||
.as_ref()
|
||||
.map(|bal| alloy_eips::eip7928::compute_block_access_list_hash(bal));
|
||||
|
||||
let built_hash = built_bal
|
||||
.as_ref()
|
||||
.map(|bal| alloy_eips::eip7928::compute_block_access_list_hash(bal));
|
||||
|
||||
if let (Some(expected), Some(got)) = (expected_hash, built_hash) &&
|
||||
expected != got
|
||||
{
|
||||
return Err(InsertBlockErrorKind::Consensus(
|
||||
ConsensusError::BlockAccessListHashMismatch((got, expected).into()),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let output = BlockExecutionOutput { result, state: db.take_bundle() };
|
||||
|
||||
let execution_duration = execution_start.elapsed();
|
||||
@@ -962,18 +996,21 @@ where
|
||||
/// - Executing each transaction with timing metrics
|
||||
/// - Streaming receipts to the receipt root computation task
|
||||
/// - Collecting transaction senders for later use
|
||||
/// - Bumping BAL index after each transaction when BAL tracking is enabled
|
||||
///
|
||||
/// 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>>>,
|
||||
DB: revm::Database + 'a,
|
||||
Tx: alloy_evm::block::ExecutableTx<E> + alloy_evm::RecoveredTx<InnerTx>,
|
||||
InnerTx: TxHashRef,
|
||||
Err: core::error::Error + Send + Sync + 'static,
|
||||
@@ -986,6 +1023,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();
|
||||
@@ -1030,6 +1072,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);
|
||||
|
||||
@@ -1340,9 +1386,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,
|
||||
None,
|
||||
false,
|
||||
) {
|
||||
// call post-block hook
|
||||
self.on_invalid_block(parent_block, block, output, None, ctx.state_mut());
|
||||
return Err(err.into())
|
||||
@@ -2028,9 +2078,16 @@ impl<T: PayloadTypes> BlockOrPayload<T> {
|
||||
}
|
||||
|
||||
/// Returns the block access list if available.
|
||||
pub const fn block_access_list(&self) -> Option<Result<BlockAccessList, alloy_rlp::Error>> {
|
||||
// TODO decode and return `BlockAccessList`
|
||||
None
|
||||
pub fn block_access_list(&self) -> Option<Result<BlockAccessList, alloy_rlp::Error>>
|
||||
where
|
||||
T::ExecutionData: ExecutionPayload,
|
||||
{
|
||||
match self {
|
||||
Self::Payload(payload) => payload
|
||||
.block_access_list()
|
||||
.map(|bytes| BlockAccessList::decode(&mut bytes.as_ref())),
|
||||
Self::Block(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of transactions in the payload or block.
|
||||
@@ -2065,4 +2122,15 @@ impl<T: PayloadTypes> BlockOrPayload<T> {
|
||||
Self::Block(block) => block.gas_used(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the gas limit used by the block.
|
||||
pub fn gas_limit(&self) -> u64
|
||||
where
|
||||
T::ExecutionData: ExecutionPayload,
|
||||
{
|
||||
match self {
|
||||
Self::Payload(payload) => payload.gas_limit(),
|
||||
Self::Block(block) => block.gas_limit(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +81,8 @@ pub struct CacheEntry<S> {
|
||||
}
|
||||
|
||||
impl<S> CacheEntry<S> {
|
||||
const fn regular_gas_used(&self) -> u64 {
|
||||
self.output.gas.limit() - self.output.gas.remaining()
|
||||
const fn gas_used(&self) -> u64 {
|
||||
self.output.gas_used
|
||||
}
|
||||
|
||||
fn to_precompile_result(&self) -> PrecompileResult {
|
||||
@@ -170,10 +170,10 @@ where
|
||||
|
||||
fn call(&self, input: PrecompileInput<'_>) -> PrecompileResult {
|
||||
if let Some(entry) = &self.cache.get(input.data, self.spec_id.clone()) &&
|
||||
input.gas >= entry.regular_gas_used()
|
||||
input.gas >= entry.gas_used()
|
||||
{
|
||||
self.increment_by_one_precompile_cache_hits();
|
||||
return entry.to_precompile_result();
|
||||
return entry.to_precompile_result()
|
||||
}
|
||||
|
||||
let calldata = input.data;
|
||||
@@ -228,14 +228,15 @@ mod tests {
|
||||
use super::*;
|
||||
use reth_evm::{EthEvmFactory, Evm, EvmEnv, EvmFactory};
|
||||
use reth_revm::db::EmptyDB;
|
||||
use revm::{context::TxEnv, interpreter::gas::GasTracker, precompile::PrecompileOutput};
|
||||
use revm::{context::TxEnv, precompile::PrecompileOutput};
|
||||
use revm_primitives::hardfork::SpecId;
|
||||
|
||||
#[test]
|
||||
fn test_precompile_cache_basic() {
|
||||
let dyn_precompile: DynPrecompile = (|_input: PrecompileInput<'_>| -> PrecompileResult {
|
||||
Ok(PrecompileOutput {
|
||||
gas: GasTracker::new(0, 0, 0),
|
||||
gas_used: 0,
|
||||
gas_refunded: 0,
|
||||
bytes: Bytes::default(),
|
||||
reverted: false,
|
||||
})
|
||||
@@ -246,7 +247,8 @@ mod tests {
|
||||
CachedPrecompile::new(dyn_precompile, PrecompileCache::default(), SpecId::PRAGUE, None);
|
||||
|
||||
let output = PrecompileOutput {
|
||||
gas: GasTracker::new(50, 0, 0),
|
||||
gas_used: 50,
|
||||
gas_refunded: 0,
|
||||
bytes: alloy_primitives::Bytes::copy_from_slice(b"cached_result"),
|
||||
reverted: false,
|
||||
};
|
||||
@@ -277,7 +279,8 @@ mod tests {
|
||||
assert_eq!(input.data, input_data);
|
||||
|
||||
Ok(PrecompileOutput {
|
||||
gas: GasTracker::new(5000, 0, 0),
|
||||
gas_used: 5000,
|
||||
gas_refunded: 0,
|
||||
bytes: alloy_primitives::Bytes::copy_from_slice(b"output_from_precompile_1"),
|
||||
reverted: false,
|
||||
})
|
||||
@@ -291,7 +294,8 @@ mod tests {
|
||||
assert_eq!(input.data, input_data);
|
||||
|
||||
Ok(PrecompileOutput {
|
||||
gas: GasTracker::new(7000, 0, 0),
|
||||
gas_used: 7000,
|
||||
gas_refunded: 0,
|
||||
bytes: alloy_primitives::Bytes::copy_from_slice(b"output_from_precompile_2"),
|
||||
reverted: false,
|
||||
})
|
||||
|
||||
@@ -34,6 +34,8 @@ pub(crate) fn create_header() -> Header {
|
||||
excess_blob_gas: None,
|
||||
parent_beacon_block_root: None,
|
||||
requests_hash: None,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +140,8 @@ pub(crate) fn create_test_block_with_compressed_data(number: BlockNumber) -> Blo
|
||||
excess_blob_gas: None,
|
||||
parent_beacon_block_root: None,
|
||||
requests_hash: None,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
// Create test body
|
||||
|
||||
@@ -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,
|
||||
@@ -43,12 +43,18 @@ pub struct EthBeaconConsensus<ChainSpec> {
|
||||
chain_spec: Arc<ChainSpec>,
|
||||
/// Maximum allowed extra data size in bytes
|
||||
max_extra_data_size: usize,
|
||||
/// When true, skips the gas limit change validation between parent and child blocks.
|
||||
skip_gas_limit_ramp_check: bool,
|
||||
}
|
||||
|
||||
impl<ChainSpec: EthChainSpec + EthereumHardforks> EthBeaconConsensus<ChainSpec> {
|
||||
/// Create a new instance of [`EthBeaconConsensus`]
|
||||
pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
|
||||
Self { chain_spec, max_extra_data_size: MAXIMUM_EXTRA_DATA_SIZE }
|
||||
Self {
|
||||
chain_spec,
|
||||
max_extra_data_size: MAXIMUM_EXTRA_DATA_SIZE,
|
||||
skip_gas_limit_ramp_check: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the maximum allowed extra data size.
|
||||
@@ -62,6 +68,12 @@ impl<ChainSpec: EthChainSpec + EthereumHardforks> EthBeaconConsensus<ChainSpec>
|
||||
self
|
||||
}
|
||||
|
||||
/// Disables the gas limit change validation between parent and child blocks.
|
||||
pub const fn with_skip_gas_limit_ramp_check(mut self, skip: bool) -> Self {
|
||||
self.skip_gas_limit_ramp_check = skip;
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the chain spec associated with this consensus engine.
|
||||
pub const fn chain_spec(&self) -> &Arc<ChainSpec> {
|
||||
&self.chain_spec
|
||||
@@ -78,6 +90,8 @@ where
|
||||
block: &RecoveredBlock<N::Block>,
|
||||
result: &BlockExecutionResult<N::Receipt>,
|
||||
receipt_root_bloom: Option<ReceiptRootBloom>,
|
||||
block_access_list: Option<BlockAccessList>,
|
||||
allow_bal_check: bool,
|
||||
) -> Result<(), ConsensusError> {
|
||||
validate_block_post_execution(
|
||||
block,
|
||||
@@ -85,6 +99,9 @@ where
|
||||
&result.receipts,
|
||||
&result.requests,
|
||||
receipt_root_bloom,
|
||||
&block_access_list,
|
||||
allow_bal_check,
|
||||
Some(result.gas_used),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -205,7 +222,9 @@ where
|
||||
|
||||
validate_against_parent_timestamp(header.header(), parent.header())?;
|
||||
|
||||
validate_against_parent_gas_limit(header, parent, &self.chain_spec)?;
|
||||
if !self.skip_gas_limit_ramp_check {
|
||||
validate_against_parent_gas_limit(header, parent, &self.chain_spec)?;
|
||||
}
|
||||
|
||||
validate_against_parent_eip1559_base_fee(
|
||||
header.header(),
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use alloc::vec::Vec;
|
||||
use alloy_consensus::{proofs::calculate_receipt_root, BlockHeader, TxReceipt};
|
||||
use alloy_eips::{eip7685::Requests, Encodable2718};
|
||||
use alloy_eips::{
|
||||
eip7685::Requests,
|
||||
eip7928::{compute_block_access_list_hash, BlockAccessList},
|
||||
Encodable2718,
|
||||
};
|
||||
use alloy_primitives::{Bloom, Bytes, B256};
|
||||
use reth_chainspec::EthereumHardforks;
|
||||
use reth_consensus::ConsensusError;
|
||||
@@ -15,27 +19,56 @@ use reth_primitives_traits::{
|
||||
///
|
||||
/// If `receipt_root_bloom` is provided, the pre-computed receipt root and logs bloom are used
|
||||
/// instead of computing them from the receipts.
|
||||
///
|
||||
/// If `allow_bal_check` is true, we compute the bal hash and match with the header bal hash
|
||||
///
|
||||
/// `gas_spent` is the `gas_used` value from the block execution result. When EIP-7778
|
||||
/// (Amsterdam) is active, block header `gas_used` tracks gas before refunds while receipt
|
||||
/// `cumulative_gas_used` tracks gas after refunds. In that case, the header must be validated
|
||||
/// against the execution result's `gas_used` rather than the receipt value.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn validate_block_post_execution<B, R, ChainSpec>(
|
||||
block: &RecoveredBlock<B>,
|
||||
chain_spec: &ChainSpec,
|
||||
receipts: &[R],
|
||||
requests: &Requests,
|
||||
receipt_root_bloom: Option<(B256, Bloom)>,
|
||||
block_access_list: &Option<BlockAccessList>,
|
||||
allow_bal_check: bool,
|
||||
gas_spent: Option<u64>,
|
||||
) -> Result<(), ConsensusError>
|
||||
where
|
||||
B: Block,
|
||||
R: Receipt,
|
||||
ChainSpec: EthereumHardforks,
|
||||
{
|
||||
// Check if gas used matches the value set in header.
|
||||
// EIP-7778: When Amsterdam is active, block header gas_used tracks gas before refunds,
|
||||
// but receipt cumulative_gas_used still tracks gas after refunds. Use the execution
|
||||
// result's gas_used which always matches the header semantics.
|
||||
let cumulative_gas_used =
|
||||
receipts.last().map(|receipt| receipt.cumulative_gas_used()).unwrap_or(0);
|
||||
if chain_spec.is_amsterdam_active_at_timestamp(block.header().timestamp()) {
|
||||
gas_spent.unwrap_or_default()
|
||||
} else {
|
||||
receipts.last().map(|receipt| receipt.cumulative_gas_used()).unwrap_or(0)
|
||||
};
|
||||
if block.header().gas_used() != cumulative_gas_used {
|
||||
return Err(ConsensusError::BlockGasUsed {
|
||||
gas: GotExpected { got: cumulative_gas_used, expected: block.header().gas_used() },
|
||||
gas_spent_by_tx: gas_spent_by_transactions(receipts),
|
||||
})
|
||||
}
|
||||
// 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()) && allow_bal_check {
|
||||
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(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Before Byzantium, receipts contained state root that would mean that expensive
|
||||
// operation as hashing that is required for state root got calculated in every
|
||||
|
||||
@@ -17,11 +17,12 @@ pub use payload::{payload_id, BlobSidecars, EthBuiltPayload, EthPayloadBuilderAt
|
||||
mod error;
|
||||
pub use error::*;
|
||||
|
||||
use alloy_rpc_types_engine::{ExecutionData, ExecutionPayload};
|
||||
use alloy_rpc_types_engine::{
|
||||
ExecutionData, ExecutionPayload, ExecutionPayloadEnvelopeV5, ExecutionPayloadEnvelopeV6,
|
||||
};
|
||||
pub use alloy_rpc_types_engine::{
|
||||
ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4,
|
||||
ExecutionPayloadEnvelopeV5, ExecutionPayloadEnvelopeV6, ExecutionPayloadV1,
|
||||
PayloadAttributes as EthPayloadAttributes,
|
||||
ExecutionPayloadV1, PayloadAttributes as EthPayloadAttributes,
|
||||
};
|
||||
use reth_engine_primitives::EngineTypes;
|
||||
use reth_payload_primitives::{BuiltPayload, PayloadTypes};
|
||||
|
||||
@@ -6,13 +6,15 @@ use alloy_eips::{
|
||||
eip4895::Withdrawals,
|
||||
eip7594::{BlobTransactionSidecarEip7594, BlobTransactionSidecarVariant},
|
||||
eip7685::Requests,
|
||||
eip7928::BlockAccessList,
|
||||
};
|
||||
use alloy_primitives::{Address, B256, U256};
|
||||
use alloy_rlp::Encodable;
|
||||
use alloy_rpc_types_engine::{
|
||||
BlobsBundleV1, BlobsBundleV2, ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3,
|
||||
ExecutionPayloadEnvelopeV4, ExecutionPayloadEnvelopeV5, ExecutionPayloadEnvelopeV6,
|
||||
ExecutionPayloadFieldV2, ExecutionPayloadV1, ExecutionPayloadV3, PayloadAttributes, PayloadId,
|
||||
ExecutionPayloadFieldV2, ExecutionPayloadV1, ExecutionPayloadV3, ExecutionPayloadV4,
|
||||
PayloadAttributes, PayloadId,
|
||||
};
|
||||
use core::convert::Infallible;
|
||||
use reth_ethereum_primitives::EthPrimitives;
|
||||
@@ -41,6 +43,8 @@ pub struct EthBuiltPayload<N: NodePrimitives = EthPrimitives> {
|
||||
pub(crate) sidecars: BlobSidecars,
|
||||
/// The requests of the payload
|
||||
pub(crate) requests: Option<Requests>,
|
||||
/// The block access list of the payload
|
||||
pub(crate) block_access_list: Option<BlockAccessList>,
|
||||
}
|
||||
|
||||
// === impl BuiltPayload ===
|
||||
@@ -54,8 +58,9 @@ impl<N: NodePrimitives> EthBuiltPayload<N> {
|
||||
block: Arc<SealedBlock<N::Block>>,
|
||||
fees: U256,
|
||||
requests: Option<Requests>,
|
||||
block_access_list: Option<BlockAccessList>,
|
||||
) -> Self {
|
||||
Self { id, block, fees, requests, sidecars: BlobSidecars::Empty }
|
||||
Self { id, block, fees, requests, sidecars: BlobSidecars::Empty, block_access_list }
|
||||
}
|
||||
|
||||
/// Returns the identifier of the payload.
|
||||
@@ -162,10 +167,35 @@ impl EthBuiltPayload {
|
||||
}
|
||||
|
||||
/// Try converting built payload into [`ExecutionPayloadEnvelopeV6`].
|
||||
///
|
||||
/// Note: Amsterdam fork is not yet implemented, so this conversion is not yet supported.
|
||||
pub fn try_into_v6(self) -> Result<ExecutionPayloadEnvelopeV6, BuiltPayloadConversionError> {
|
||||
unimplemented!("ExecutionPayloadEnvelopeV6 not yet supported")
|
||||
let Self { block, fees, sidecars, requests, block_access_list, .. } = self;
|
||||
|
||||
let blobs_bundle = match sidecars {
|
||||
BlobSidecars::Empty => BlobsBundleV2::empty(),
|
||||
BlobSidecars::Eip7594(sidecars) => BlobsBundleV2::from(sidecars),
|
||||
BlobSidecars::Eip4844(_) => {
|
||||
return Err(BuiltPayloadConversionError::UnexpectedEip4844Sidecars)
|
||||
}
|
||||
};
|
||||
Ok(ExecutionPayloadEnvelopeV6 {
|
||||
execution_payload: ExecutionPayloadV4::from_block_unchecked_with_bal(
|
||||
block.hash(),
|
||||
&Arc::unwrap_or_clone(block).into_block(),
|
||||
alloy_rlp::encode(block_access_list.unwrap_or_default()).into(),
|
||||
),
|
||||
block_value: fees,
|
||||
// From the engine API spec:
|
||||
//
|
||||
// > Client software **MAY** use any heuristics to decide whether to set
|
||||
// `shouldOverrideBuilder` flag or not. If client software does not implement any
|
||||
// heuristic this flag **SHOULD** be set to `false`.
|
||||
//
|
||||
// Spec:
|
||||
// <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#specification-2>
|
||||
should_override_builder: false,
|
||||
blobs_bundle,
|
||||
execution_requests: requests.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,6 +378,8 @@ pub struct EthPayloadBuilderAttributes {
|
||||
pub withdrawals: Withdrawals,
|
||||
/// Root of the parent beacon block
|
||||
pub parent_beacon_block_root: Option<B256>,
|
||||
/// Slot number (EIP-7928, Amsterdam).
|
||||
pub slot_number: Option<u64>,
|
||||
}
|
||||
|
||||
// === impl EthPayloadBuilderAttributes ===
|
||||
@@ -372,6 +404,7 @@ impl EthPayloadBuilderAttributes {
|
||||
prev_randao: attributes.prev_randao,
|
||||
withdrawals: attributes.withdrawals.unwrap_or_default().into(),
|
||||
parent_beacon_block_root: attributes.parent_beacon_block_root,
|
||||
slot_number: attributes.slot_number,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -418,6 +451,10 @@ impl PayloadBuilderAttributes for EthPayloadBuilderAttributes {
|
||||
fn withdrawals(&self) -> &Withdrawals {
|
||||
&self.withdrawals
|
||||
}
|
||||
|
||||
fn slot_number(&self) -> Option<u64> {
|
||||
self.slot_number
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the payload id for the configured payload from the [`PayloadAttributes`].
|
||||
@@ -440,6 +477,10 @@ pub fn payload_id(parent: &B256, attributes: &PayloadAttributes) -> PayloadId {
|
||||
hasher.update(parent_beacon_block);
|
||||
}
|
||||
|
||||
if let Some(slot_number) = attributes.slot_number {
|
||||
hasher.update(&slot_number.to_be_bytes()[..]);
|
||||
}
|
||||
|
||||
let out = hasher.finalize();
|
||||
|
||||
#[allow(deprecated)] // generic-array 0.14 deprecated
|
||||
@@ -477,6 +518,7 @@ mod tests {
|
||||
.unwrap(),
|
||||
withdrawals: None,
|
||||
parent_beacon_block_root: None,
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
// Verify that the generated payload ID matches the expected value
|
||||
@@ -514,6 +556,7 @@ mod tests {
|
||||
},
|
||||
]),
|
||||
parent_beacon_block_root: None,
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
// Verify that the generated payload ID matches the expected value
|
||||
@@ -546,6 +589,7 @@ mod tests {
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
// Verify that the generated payload ID matches the expected value
|
||||
|
||||
@@ -45,11 +45,11 @@ where
|
||||
execution_ctx: ctx,
|
||||
parent,
|
||||
transactions,
|
||||
output: BlockExecutionResult { receipts, requests, gas_used, blob_gas_used },
|
||||
output: BlockExecutionResult { receipts, requests, gas_used, blob_gas_used, .. },
|
||||
state_root,
|
||||
block_access_list_hash,
|
||||
..
|
||||
} = input;
|
||||
|
||||
let timestamp = evm_env.block_env.timestamp().saturating_to();
|
||||
|
||||
let transactions_root = proofs::calculate_transaction_root(&transactions);
|
||||
@@ -90,6 +90,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,6 +118,8 @@ where
|
||||
blob_gas_used: block_blob_gas_used,
|
||||
excess_blob_gas,
|
||||
requests_hash,
|
||||
block_access_list_hash: bal_hash,
|
||||
slot_number: ctx.slot_number,
|
||||
};
|
||||
|
||||
Ok(Block {
|
||||
|
||||
@@ -175,6 +175,7 @@ where
|
||||
suggested_fee_recipient: attributes.suggested_fee_recipient,
|
||||
prev_randao: attributes.prev_randao,
|
||||
gas_limit: attributes.gas_limit,
|
||||
slot_number: attributes.slot_number,
|
||||
},
|
||||
self.chain_spec().next_block_base_fee(parent, attributes.timestamp).unwrap_or_default(),
|
||||
self.chain_spec(),
|
||||
@@ -194,6 +195,7 @@ where
|
||||
ommers: &block.body().ommers,
|
||||
withdrawals: block.body().withdrawals.as_ref().map(|w| Cow::Borrowed(w.as_slice())),
|
||||
extra_data: block.header().extra_data.clone(),
|
||||
slot_number: block.header().slot_number,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -209,6 +211,7 @@ where
|
||||
ommers: &[],
|
||||
withdrawals: attributes.withdrawals.map(|w| Cow::Owned(w.into_inner())),
|
||||
extra_data: attributes.extra_data,
|
||||
slot_number: attributes.slot_number,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -273,7 +276,11 @@ where
|
||||
gas_limit: payload.payload.gas_limit(),
|
||||
basefee: payload.payload.saturated_base_fee_per_gas(),
|
||||
blob_excess_gas_and_price,
|
||||
slot_num: 0,
|
||||
slot_num: if spec >= SpecId::AMSTERDAM {
|
||||
payload.payload.as_v4().unwrap().slot_number
|
||||
} else {
|
||||
Default::default()
|
||||
},
|
||||
};
|
||||
|
||||
Ok(EvmEnv { cfg_env, block_env })
|
||||
@@ -290,6 +297,7 @@ where
|
||||
ommers: &[],
|
||||
withdrawals: payload.payload.withdrawals().map(|w| Cow::Borrowed(w.as_slice())),
|
||||
extra_data: payload.payload.as_v1().extra_data.clone(),
|
||||
slot_number: payload.payload.as_v4().map(|v4| v4.slot_number),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -14,13 +14,17 @@ impl ReceiptBuilder for RethReceiptBuilder {
|
||||
type Receipt = Receipt;
|
||||
|
||||
fn build_receipt<E: Evm>(&self, ctx: ReceiptBuilderCtx<'_, TxType, E>) -> Self::Receipt {
|
||||
let ReceiptBuilderCtx { tx_type, result, cumulative_gas_used, .. } = ctx;
|
||||
let ReceiptBuilderCtx { tx_type, result, cumulative_gas_used, gas_spent, .. } = ctx;
|
||||
// EIP-7778: when active, `cumulative_gas_used` tracks gas before refunds (for block
|
||||
// accounting), but receipts must use gas after refunds (unchanged). `gas_spent` holds the
|
||||
// after-refund cumulative gas when EIP-7778 is active.
|
||||
let receipt_gas = gas_spent.unwrap_or(cumulative_gas_used);
|
||||
Receipt {
|
||||
tx_type,
|
||||
// Success flag was added in `EIP-658: Embedding transaction status code in
|
||||
// receipts`.
|
||||
success: result.is_success(),
|
||||
cumulative_gas_used,
|
||||
cumulative_gas_used: receipt_gas,
|
||||
logs: result.into_logs(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,6 +293,7 @@ where
|
||||
EthConfigHandler::new(ctx.node.provider().clone(), ctx.node.evm_config().clone());
|
||||
|
||||
let testing_skip_invalid_transactions = ctx.config.rpc.testing_skip_invalid_transactions;
|
||||
let testing_gas_limit_override = ctx.config.rpc.testing_gas_limit;
|
||||
|
||||
self.inner
|
||||
.launch_add_ons_with(ctx, move |container| {
|
||||
@@ -314,6 +315,9 @@ where
|
||||
if testing_skip_invalid_transactions {
|
||||
testing_api = testing_api.with_skip_invalid_transactions();
|
||||
}
|
||||
if let Some(gas_limit) = testing_gas_limit_override {
|
||||
testing_api = testing_api.with_gas_limit_override(gas_limit);
|
||||
}
|
||||
container
|
||||
.modules
|
||||
.merge_if_module_configured(RethRpcModule::Testing, testing_api.into_rpc())?;
|
||||
@@ -563,7 +567,10 @@ where
|
||||
type Consensus = Arc<EthBeaconConsensus<<Node::Types as NodeTypes>::ChainSpec>>;
|
||||
|
||||
async fn build_consensus(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::Consensus> {
|
||||
Ok(Arc::new(EthBeaconConsensus::new(ctx.chain_spec())))
|
||||
Ok(Arc::new(
|
||||
EthBeaconConsensus::new(ctx.chain_spec())
|
||||
.with_skip_gas_limit_ramp_check(ctx.config().rpc.testing_skip_gas_limit_ramp_check),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -221,6 +221,7 @@ async fn test_testing_build_block_v1_osaka() -> eyre::Result<()> {
|
||||
suggested_fee_recipient: Address::ZERO,
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: Some(B256::ZERO),
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
let request = TestingBuildBlockRequestV1 {
|
||||
|
||||
@@ -25,6 +25,7 @@ pub(crate) fn eth_payload_attributes(timestamp: u64) -> EthPayloadBuilderAttribu
|
||||
suggested_fee_recipient: Address::ZERO,
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: Some(B256::ZERO),
|
||||
slot_number: None,
|
||||
};
|
||||
EthPayloadBuilderAttributes::new(B256::ZERO, attributes)
|
||||
}
|
||||
@@ -38,6 +39,7 @@ pub(crate) fn eth_payload_attributes_shanghai(timestamp: u64) -> EthPayloadBuild
|
||||
suggested_fee_recipient: Address::ZERO,
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: None,
|
||||
slot_number: None,
|
||||
};
|
||||
EthPayloadBuilderAttributes::new(B256::ZERO, attributes)
|
||||
}
|
||||
@@ -83,7 +85,9 @@ where
|
||||
tx = tx.into_create().with_input(dummy_bytecode.clone());
|
||||
} else {
|
||||
tx = tx.with_to(*call_destinations.choose(rng).unwrap()).with_input(
|
||||
(0..rng.random_range(0..10000)).map(|_| rng.random()).collect::<Vec<u8>>(),
|
||||
(0..rng.random_range(0..10000))
|
||||
.map(|_| rng.random::<u8>())
|
||||
.collect::<Vec<u8>>(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ async fn testing_rpc_build_block_works() -> eyre::Result<()> {
|
||||
suggested_fee_recipient: Address::ZERO,
|
||||
withdrawals: None,
|
||||
parent_beacon_block_root: None,
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
let request = TestingBuildBlockRequestV1 {
|
||||
|
||||
@@ -154,8 +154,16 @@ where
|
||||
|
||||
let state_provider = client.state_by_block_hash(parent_header.hash())?;
|
||||
let state = StateProviderDatabase::new(state_provider.as_ref());
|
||||
let mut db =
|
||||
State::builder().with_database(cached_reads.as_db_mut(state)).with_bundle_update().build();
|
||||
let chain_spec = client.chain_spec();
|
||||
|
||||
let is_amsterdam = chain_spec.is_amsterdam_active_at_timestamp(attributes.timestamp());
|
||||
|
||||
// Build state with BAL builder enabled when Amsterdam is active
|
||||
let mut db = State::builder()
|
||||
.with_database(cached_reads.as_db_mut(state))
|
||||
.with_bundle_update()
|
||||
.with_bal_builder_if(is_amsterdam)
|
||||
.build();
|
||||
|
||||
let mut builder = evm_config
|
||||
.builder_for_next_block(
|
||||
@@ -169,12 +177,11 @@ where
|
||||
parent_beacon_block_root: attributes.parent_beacon_block_root(),
|
||||
withdrawals: Some(attributes.withdrawals().clone()),
|
||||
extra_data: builder_config.extra_data,
|
||||
slot_number: attributes.slot_number,
|
||||
},
|
||||
)
|
||||
.map_err(PayloadBuilderError::other)?;
|
||||
|
||||
let chain_spec = client.chain_spec();
|
||||
|
||||
debug!(target: "payload_builder", id=%attributes.id, parent_header = ?parent_header.hash(), parent_number = parent_header.number, "building new payload");
|
||||
let mut cumulative_gas_used = 0;
|
||||
let block_gas_limit: u64 = builder.evm_mut().block().gas_limit();
|
||||
@@ -324,6 +331,25 @@ 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.
|
||||
Err(BlockExecutionError::Validation(
|
||||
BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
|
||||
transaction_gas_limit,
|
||||
block_available_gas,
|
||||
},
|
||||
)) => {
|
||||
trace!(target: "payload_builder", %transaction_gas_limit, %block_available_gas, ?tx, "skipping transaction exceeding block gas limit");
|
||||
best_txs.mark_invalid(
|
||||
&pool_tx,
|
||||
&InvalidPoolTransactionError::ExceedsGasLimit(
|
||||
transaction_gas_limit,
|
||||
block_available_gas,
|
||||
),
|
||||
);
|
||||
continue
|
||||
}
|
||||
// this is an error that we should treat as fatal for this attempt
|
||||
Err(err) => return Err(PayloadBuilderError::evm(err)),
|
||||
};
|
||||
@@ -360,7 +386,7 @@ where
|
||||
return Ok(BuildOutcome::Aborted { fees: total_fees, cached_reads })
|
||||
}
|
||||
|
||||
let BlockBuilderOutcome { execution_result, block, .. } =
|
||||
let BlockBuilderOutcome { execution_result, block, block_access_list, .. } =
|
||||
builder.finish(state_provider.as_ref())?;
|
||||
|
||||
let requests = chain_spec
|
||||
@@ -377,9 +403,10 @@ where
|
||||
}));
|
||||
}
|
||||
|
||||
let payload = EthBuiltPayload::new(attributes.id, sealed_block, total_fees, requests)
|
||||
// add blob sidecars from the executed txs
|
||||
.with_sidecars(blob_sidecars);
|
||||
let payload =
|
||||
EthBuiltPayload::new(attributes.id, sealed_block, total_fees, requests, block_access_list)
|
||||
// add blob sidecars from the executed txs
|
||||
.with_sidecars(blob_sidecars);
|
||||
|
||||
Ok(BuildOutcome::Better { payload, cached_reads })
|
||||
}
|
||||
|
||||
@@ -62,4 +62,4 @@ test-utils = [
|
||||
"reth-trie-common/test-utils",
|
||||
"reth-ethereum-primitives/test-utils",
|
||||
]
|
||||
# op = ["alloy-evm/op", "reth-primitives-traits/op"]
|
||||
op = ["alloy-evm/op", "reth-primitives-traits/op"]
|
||||
|
||||
@@ -72,6 +72,12 @@ where
|
||||
Self::Right(b) => b.into_state(),
|
||||
}
|
||||
}
|
||||
fn take_bal(&mut self) -> Option<alloy_eips::eip7928::BlockAccessList> {
|
||||
match self {
|
||||
Self::Left(a) => a.take_bal(),
|
||||
Self::Right(b) => b.take_bal(),
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> usize {
|
||||
match self {
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
use crate::{ConfigureEvm, Database, OnStateHook, TxEnvFor};
|
||||
use alloc::{boxed::Box, sync::Arc, vec::Vec};
|
||||
use alloy_consensus::{BlockHeader, Header};
|
||||
use alloy_eips::eip2718::WithEncoded;
|
||||
use alloy_eips::{
|
||||
eip2718::WithEncoded,
|
||||
eip7928::{compute_block_access_list_hash, BlockAccessList},
|
||||
};
|
||||
pub use alloy_evm::block::{BlockExecutor, BlockExecutorFactory};
|
||||
use alloy_evm::{
|
||||
block::{CommitChanges, ExecutableTxParts},
|
||||
@@ -24,6 +27,7 @@ use reth_trie_common::{updates::TrieUpdates, HashedPostState};
|
||||
use revm::{
|
||||
context::result::ExecutionResult,
|
||||
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
|
||||
@@ -144,6 +148,9 @@ pub trait Executor<DB: Database>: Sized {
|
||||
/// Consumes the executor and returns the [`State`] containing all state changes.
|
||||
fn into_state(self) -> State<DB>;
|
||||
|
||||
/// Take built [`BlockAccessList`] from executor
|
||||
fn take_bal(&mut self) -> Option<BlockAccessList>;
|
||||
|
||||
/// The size hint of the batch's tracked state size.
|
||||
///
|
||||
/// This is used to optimize DB commits depending on the size of the state.
|
||||
@@ -165,6 +172,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
|
||||
///
|
||||
@@ -208,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> {
|
||||
@@ -225,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,
|
||||
@@ -235,6 +246,7 @@ impl<'a, 'b, F: BlockExecutorFactory, H> BlockAssemblerInput<'a, 'b, F, H> {
|
||||
bundle_state,
|
||||
state_provider,
|
||||
state_root,
|
||||
block_access_list_hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -304,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.
|
||||
@@ -453,7 +467,11 @@ 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(
|
||||
@@ -468,7 +486,10 @@ where
|
||||
self.executor.execute_transaction_with_commit_condition((tx_env, &tx), f)?
|
||||
{
|
||||
self.transactions.push(tx);
|
||||
Ok(Some(gas_used.into()))
|
||||
// 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)
|
||||
}
|
||||
@@ -484,6 +505,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));
|
||||
|
||||
// calculate the state root
|
||||
let hashed_state = state.hashed_post_state(&db.bundle_state);
|
||||
let (state_root, trie_updates) = state
|
||||
@@ -502,11 +528,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 {
|
||||
@@ -535,7 +568,11 @@ pub struct BasicBlockExecutor<F, DB> {
|
||||
impl<F, DB: Database> BasicBlockExecutor<F, DB> {
|
||||
/// Creates a new `BasicBlockExecutor` with the given strategy.
|
||||
pub fn new(strategy_factory: F, db: DB) -> Self {
|
||||
let db = State::builder().with_database(db).with_bundle_update().build();
|
||||
let db = State::builder()
|
||||
.with_database(db)
|
||||
.with_bundle_update()
|
||||
.with_bal_builder_if(true)
|
||||
.build();
|
||||
Self { strategy_factory, db }
|
||||
}
|
||||
}
|
||||
@@ -553,11 +590,21 @@ 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)?;
|
||||
|
||||
executor.evm_mut().db_mut().bal_state.bal_builder = Some(Bal::new());
|
||||
executor.apply_pre_execution_changes()?;
|
||||
executor.evm_mut().db_mut().bump_bal_index();
|
||||
|
||||
for tx in block.transactions_recovered() {
|
||||
executor.execute_transaction(tx)?;
|
||||
executor.evm_mut().db_mut().bump_bal_index();
|
||||
}
|
||||
|
||||
let result = executor.apply_post_execution_changes()?;
|
||||
|
||||
self.db.merge_transitions(BundleRetention::Reverts);
|
||||
|
||||
@@ -588,6 +635,10 @@ where
|
||||
self.db
|
||||
}
|
||||
|
||||
fn take_bal(&mut self) -> Option<BlockAccessList> {
|
||||
self.db.take_built_alloy_bal()
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> usize {
|
||||
self.db.bundle_state.size_hint()
|
||||
}
|
||||
@@ -693,6 +744,10 @@ mod tests {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn take_bal(&mut self) -> Option<BlockAccessList> {
|
||||
None
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
@@ -121,6 +121,7 @@ pub use alloy_evm::{
|
||||
/// gas_limit: 30_000_000,
|
||||
/// withdrawals: Some(withdrawals),
|
||||
/// parent_beacon_block_root: Some(beacon_root),
|
||||
/// slot_number: Some(slot),
|
||||
/// };
|
||||
///
|
||||
/// // Build a new block on top of parent
|
||||
@@ -505,6 +506,8 @@ pub struct NextBlockEnvAttributes {
|
||||
pub withdrawals: Option<Withdrawals>,
|
||||
/// Optional extra data.
|
||||
pub extra_data: Bytes,
|
||||
/// Slot number (EIP-7928, Amsterdam).
|
||||
pub slot_number: Option<u64>,
|
||||
}
|
||||
|
||||
/// Abstraction over transaction environment.
|
||||
|
||||
@@ -195,40 +195,40 @@ mod tests {
|
||||
|
||||
// wal with 1 block and tx (old 3-field format)
|
||||
// <https://github.com/paradigmxyz/reth/issues/15012>
|
||||
#[test]
|
||||
fn decode_notification_wal() {
|
||||
let wal = include_bytes!("../../test-data/28.wal");
|
||||
let notification: reth_exex_types::serde_bincode_compat::ExExNotification<
|
||||
'_,
|
||||
reth_ethereum_primitives::EthPrimitives,
|
||||
> = rmp_serde::decode::from_slice(wal.as_slice()).unwrap();
|
||||
let notification: ExExNotification = notification.into();
|
||||
match notification {
|
||||
ExExNotification::ChainCommitted { new } => {
|
||||
assert_eq!(new.blocks().len(), 1);
|
||||
assert_eq!(new.tip().transaction_count(), 1);
|
||||
}
|
||||
_ => panic!("unexpected notification"),
|
||||
}
|
||||
}
|
||||
// #[test]
|
||||
// fn decode_notification_wal() {
|
||||
// let wal = include_bytes!("../../test-data/28.wal");
|
||||
// let notification: reth_exex_types::serde_bincode_compat::ExExNotification<
|
||||
// '_,
|
||||
// reth_ethereum_primitives::EthPrimitives,
|
||||
// > = rmp_serde::decode::from_slice(wal.as_slice()).unwrap();
|
||||
// let notification: ExExNotification = notification.into();
|
||||
// match notification {
|
||||
// ExExNotification::ChainCommitted { new } => {
|
||||
// assert_eq!(new.blocks().len(), 1);
|
||||
// assert_eq!(new.tip().transaction_count(), 1);
|
||||
// }
|
||||
// _ => panic!("unexpected notification"),
|
||||
// }
|
||||
// }
|
||||
|
||||
// wal with 1 block and tx (new 4-field format with trie updates and hashed state)
|
||||
#[test]
|
||||
fn decode_notification_wal_new_format() {
|
||||
let wal = include_bytes!("../../test-data/new_format.wal");
|
||||
let notification: reth_exex_types::serde_bincode_compat::ExExNotification<
|
||||
'_,
|
||||
reth_ethereum_primitives::EthPrimitives,
|
||||
> = rmp_serde::decode::from_slice(wal.as_slice()).unwrap();
|
||||
let notification: ExExNotification = notification.into();
|
||||
// #[test]
|
||||
// fn decode_notification_wal_new_format() {
|
||||
// let wal = include_bytes!("../../test-data/new_format.wal");
|
||||
// let notification: reth_exex_types::serde_bincode_compat::ExExNotification<
|
||||
// '_,
|
||||
// reth_ethereum_primitives::EthPrimitives,
|
||||
// > = rmp_serde::decode::from_slice(wal.as_slice()).unwrap();
|
||||
// let notification: ExExNotification = notification.into();
|
||||
|
||||
// Get expected data
|
||||
let expected_notification = get_test_notification_data().unwrap();
|
||||
assert_eq!(
|
||||
¬ification, &expected_notification,
|
||||
"Decoded notification should match expected static data"
|
||||
);
|
||||
}
|
||||
// // Get expected data
|
||||
// let expected_notification = get_test_notification_data().unwrap();
|
||||
// assert_eq!(
|
||||
// ¬ification, &expected_notification,
|
||||
// "Decoded notification should match expected static data"
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip() -> eyre::Result<()> {
|
||||
|
||||
@@ -296,6 +296,8 @@ mod tests {
|
||||
excess_blob_gas: None,
|
||||
parent_beacon_block_root: None,
|
||||
requests_hash: None,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
},
|
||||
]),
|
||||
}.encode(&mut data);
|
||||
@@ -333,6 +335,8 @@ mod tests {
|
||||
excess_blob_gas: None,
|
||||
parent_beacon_block_root: None,
|
||||
requests_hash: None,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
},
|
||||
]),
|
||||
};
|
||||
@@ -439,6 +443,8 @@ mod tests {
|
||||
excess_blob_gas: None,
|
||||
parent_beacon_block_root: None,
|
||||
requests_hash: None,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
},
|
||||
],
|
||||
withdrawals: None,
|
||||
@@ -516,6 +522,8 @@ mod tests {
|
||||
excess_blob_gas: None,
|
||||
parent_beacon_block_root: None,
|
||||
requests_hash: None,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
},
|
||||
],
|
||||
withdrawals: None,
|
||||
|
||||
@@ -152,6 +152,8 @@ mod tests {
|
||||
excess_blob_gas: None,
|
||||
parent_beacon_block_root: None,
|
||||
requests_hash: None,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
};
|
||||
assert_eq!(header.hash_slow(), expected_hash);
|
||||
}
|
||||
@@ -268,6 +270,8 @@ mod tests {
|
||||
excess_blob_gas: Some(0),
|
||||
parent_beacon_block_root: None,
|
||||
requests_hash: None,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
let header = Header::decode(&mut data.as_slice()).unwrap();
|
||||
@@ -310,6 +314,8 @@ mod tests {
|
||||
blob_gas_used: Some(0),
|
||||
excess_blob_gas: Some(0x1600000),
|
||||
requests_hash: None,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
let header = Header::decode(&mut data.as_slice()).unwrap();
|
||||
|
||||
@@ -122,10 +122,10 @@ test-utils = [
|
||||
"reth-tasks/test-utils",
|
||||
]
|
||||
trie-debug = ["reth-engine-tree/trie-debug"]
|
||||
# op = [
|
||||
# "reth-db/op",
|
||||
# "reth-db-api/op",
|
||||
# "reth-engine-local/op",
|
||||
# "reth-evm/op",
|
||||
# "reth-primitives-traits/op",
|
||||
# ]
|
||||
op = [
|
||||
"reth-db/op",
|
||||
"reth-db-api/op",
|
||||
"reth-engine-local/op",
|
||||
"reth-evm/op",
|
||||
"reth-primitives-traits/op",
|
||||
]
|
||||
|
||||
@@ -1017,13 +1017,7 @@ where
|
||||
.with_executor(node.task_executor().clone())
|
||||
.with_evm_config(node.evm_config().clone())
|
||||
.with_consensus(node.consensus().clone())
|
||||
.build_with_auth_server(
|
||||
module_config,
|
||||
engine_api,
|
||||
eth_api,
|
||||
engine_events.clone(),
|
||||
beacon_engine_handle.clone(),
|
||||
);
|
||||
.build_with_auth_server(module_config, engine_api, eth_api, engine_events.clone());
|
||||
|
||||
// in dev mode we generate 20 random dev-signer accounts
|
||||
if config.dev.dev {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! clap [Args](clap::Args) for benchmark configuration
|
||||
|
||||
use clap::Args;
|
||||
use std::path::PathBuf;
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
/// Parameters for benchmark configuration
|
||||
#[derive(Debug, Args, PartialEq, Eq, Default, Clone)]
|
||||
@@ -71,19 +71,96 @@ pub struct BenchmarkArgs {
|
||||
#[arg(long = "metrics-url", value_name = "URL", verbatim_doc_comment)]
|
||||
pub metrics_url: Option<String>,
|
||||
|
||||
/// Number of retries for fetching blocks from `--rpc-url` after a failure.
|
||||
///
|
||||
/// Use `0` to fail immediately, or `forever` to never stop retrying.
|
||||
#[arg(
|
||||
long = "rpc-block-fetch-retries",
|
||||
value_name = "RETRIES",
|
||||
default_value = "10",
|
||||
value_parser = parse_rpc_block_fetch_retries,
|
||||
verbatim_doc_comment
|
||||
)]
|
||||
pub rpc_block_fetch_retries: RpcBlockFetchRetries,
|
||||
|
||||
/// Use `reth_newPayload` endpoint instead of `engine_newPayload*`.
|
||||
///
|
||||
/// The `reth_newPayload` endpoint is a reth-specific extension that takes `ExecutionData`
|
||||
/// directly, waits for persistence and cache updates to complete before processing,
|
||||
/// and returns server-side timing breakdowns (latency, persistence wait, cache wait).
|
||||
///
|
||||
/// Cannot be used with `--wait-for-persistence` because `reth_newPayload` already
|
||||
/// waits for persistence by default.
|
||||
#[arg(long, default_value = "false", verbatim_doc_comment)]
|
||||
pub reth_new_payload: bool,
|
||||
|
||||
/// Skip waiting for in-flight persistence before processing.
|
||||
///
|
||||
/// Only works with `--reth-new-payload`. When set, passes `wait_for_persistence: false`
|
||||
/// to the `reth_newPayload` endpoint.
|
||||
#[arg(long, default_value = "false", verbatim_doc_comment, requires = "reth_new_payload")]
|
||||
pub no_wait_for_persistence: bool,
|
||||
|
||||
/// Skip waiting for execution cache and sparse trie locks before processing.
|
||||
///
|
||||
/// Only works with `--reth-new-payload`. When set, passes `wait_for_caches: false`
|
||||
/// to the `reth_newPayload` endpoint.
|
||||
#[arg(long, default_value = "false", verbatim_doc_comment, requires = "reth_new_payload")]
|
||||
pub no_wait_for_caches: bool,
|
||||
|
||||
/// Fetch and replay RLP-encoded blocks. Implies `reth_new_payload`.
|
||||
#[arg(long, default_value = "false", verbatim_doc_comment)]
|
||||
pub rlp_blocks: bool,
|
||||
}
|
||||
|
||||
/// Retry strategy for fetching blocks from the benchmark RPC provider.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RpcBlockFetchRetries {
|
||||
/// Retry up to `u32` times after the first failed attempt.
|
||||
Finite(u32),
|
||||
/// Retry forever.
|
||||
Forever,
|
||||
}
|
||||
|
||||
impl RpcBlockFetchRetries {
|
||||
/// Returns the maximum number of retries for the `RetryBackoffLayer`.
|
||||
pub const fn as_max_retries(self) -> u32 {
|
||||
match self {
|
||||
Self::Finite(n) => n,
|
||||
Self::Forever => u32::MAX,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RpcBlockFetchRetries {
|
||||
fn default() -> Self {
|
||||
Self::Finite(10)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for RpcBlockFetchRetries {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.trim();
|
||||
if s.eq_ignore_ascii_case("forever") ||
|
||||
s.eq_ignore_ascii_case("infinite") ||
|
||||
s.eq_ignore_ascii_case("inf")
|
||||
{
|
||||
return Ok(Self::Forever)
|
||||
}
|
||||
|
||||
let retries = s
|
||||
.parse::<u32>()
|
||||
.map_err(|_| format!("invalid retry value {s:?}, expected a number or 'forever'"))?;
|
||||
Ok(Self::Finite(retries))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_rpc_block_fetch_retries(value: &str) -> Result<RpcBlockFetchRetries, String> {
|
||||
value.parse()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -105,4 +182,26 @@ mod tests {
|
||||
let args = CommandParser::<BenchmarkArgs>::parse_from(["reth-bench"]).args;
|
||||
assert_eq!(args, default_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_rpc_block_fetch_retries_forever() {
|
||||
let args = CommandParser::<BenchmarkArgs>::parse_from([
|
||||
"reth-bench",
|
||||
"--rpc-block-fetch-retries",
|
||||
"forever",
|
||||
])
|
||||
.args;
|
||||
assert_eq!(args.rpc_block_fetch_retries, RpcBlockFetchRetries::Forever);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_rpc_block_fetch_retries_number() {
|
||||
let args = CommandParser::<BenchmarkArgs>::parse_from([
|
||||
"reth-bench",
|
||||
"--rpc-block-fetch-retries",
|
||||
"7",
|
||||
])
|
||||
.args;
|
||||
assert_eq!(args.rpc_block_fetch_retries, RpcBlockFetchRetries::Finite(7));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ pub struct DefaultEngineValues {
|
||||
slow_block_threshold: Option<Duration>,
|
||||
disable_sparse_trie_cache_pruning: bool,
|
||||
state_root_task_timeout: Option<String>,
|
||||
bal_parallel_execution_disabled: bool,
|
||||
bal_parallel_state_root_disabled: bool,
|
||||
}
|
||||
|
||||
impl DefaultEngineValues {
|
||||
@@ -204,6 +206,18 @@ impl DefaultEngineValues {
|
||||
self.state_root_task_timeout = v;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether to disable BAL-based parallel execution by default
|
||||
pub const fn with_bal_parallel_execution_disabled(mut self, v: bool) -> Self {
|
||||
self.bal_parallel_execution_disabled = v;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether to disable BAL-driven parallel state root by default
|
||||
pub const fn with_bal_parallel_state_root_disabled(mut self, v: bool) -> Self {
|
||||
self.bal_parallel_state_root_disabled = v;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DefaultEngineValues {
|
||||
@@ -233,6 +247,8 @@ impl Default for DefaultEngineValues {
|
||||
slow_block_threshold: None,
|
||||
disable_sparse_trie_cache_pruning: false,
|
||||
state_root_task_timeout: Some("1s".to_string()),
|
||||
bal_parallel_execution_disabled: false,
|
||||
bal_parallel_state_root_disabled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -383,11 +399,6 @@ pub struct EngineArgs {
|
||||
#[arg(long = "engine.disable-sparse-trie-cache-pruning", default_value_t = DefaultEngineValues::get_global().disable_sparse_trie_cache_pruning)]
|
||||
pub disable_sparse_trie_cache_pruning: bool,
|
||||
|
||||
/// Enable the arena-based sparse trie implementation instead of the default hash-map-based
|
||||
/// one.
|
||||
#[arg(long = "engine.enable-arena-sparse-trie", default_value_t = false)]
|
||||
pub enable_arena_sparse_trie: bool,
|
||||
|
||||
/// Configure the timeout for the state root task before spawning a sequential fallback.
|
||||
/// If the state root task takes longer than this, a sequential computation starts in
|
||||
/// parallel and whichever finishes first is used.
|
||||
@@ -403,6 +414,19 @@ pub struct EngineArgs {
|
||||
)]
|
||||
pub state_root_task_timeout: Option<Duration>,
|
||||
|
||||
/// Disable BAL (Block Access List, EIP-7928) based parallel execution. When set, falls 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,
|
||||
|
||||
/// Disable BAL-driven parallel state root computation. When set, the BAL hashed post state
|
||||
/// is not sent to the multiproof task for early parallel state root computation.
|
||||
#[arg(long = "engine.disable-bal-parallel-state-root", default_value_t = DefaultEngineValues::get_global().bal_parallel_state_root_disabled)]
|
||||
pub bal_parallel_state_root_disabled: bool,
|
||||
/// Disable BAL (Block Access List) batched IO during prewarming. When set, falls back
|
||||
/// to individual per-slot storage reads instead of batched cursor reads.
|
||||
#[arg(long = "engine.disable-bal-batch-io", default_value_t = false)]
|
||||
pub disable_bal_batch_io: bool,
|
||||
/// Add random jitter before each proof computation (trie-debug only).
|
||||
/// Each proof worker sleeps for a random duration up to this value before
|
||||
/// starting work. Useful for stress-testing timing-sensitive proof logic.
|
||||
@@ -445,6 +469,8 @@ impl Default for EngineArgs {
|
||||
slow_block_threshold,
|
||||
disable_sparse_trie_cache_pruning,
|
||||
state_root_task_timeout,
|
||||
bal_parallel_execution_disabled,
|
||||
bal_parallel_state_root_disabled,
|
||||
} = DefaultEngineValues::get_global().clone();
|
||||
Self {
|
||||
persistence_threshold,
|
||||
@@ -474,10 +500,12 @@ impl Default for EngineArgs {
|
||||
sparse_trie_max_hot_accounts,
|
||||
slow_block_threshold,
|
||||
disable_sparse_trie_cache_pruning,
|
||||
enable_arena_sparse_trie: false,
|
||||
state_root_task_timeout: state_root_task_timeout
|
||||
.as_deref()
|
||||
.map(|s| humantime::parse_duration(s).expect("valid default duration")),
|
||||
bal_parallel_execution_disabled,
|
||||
bal_parallel_state_root_disabled,
|
||||
disable_bal_batch_io: false,
|
||||
#[cfg(feature = "trie-debug")]
|
||||
proof_jitter: None,
|
||||
}
|
||||
@@ -509,8 +537,11 @@ impl EngineArgs {
|
||||
.with_sparse_trie_max_hot_accounts(self.sparse_trie_max_hot_accounts)
|
||||
.with_slow_block_threshold(self.slow_block_threshold)
|
||||
.with_disable_sparse_trie_cache_pruning(self.disable_sparse_trie_cache_pruning)
|
||||
.with_enable_arena_sparse_trie(self.enable_arena_sparse_trie)
|
||||
.with_state_root_task_timeout(self.state_root_task_timeout.filter(|d| !d.is_zero()));
|
||||
.with_state_root_task_timeout(self.state_root_task_timeout.filter(|d| !d.is_zero()))
|
||||
.without_bal_parallel_execution(self.bal_parallel_execution_disabled)
|
||||
.without_bal_parallel_state_root(self.bal_parallel_state_root_disabled)
|
||||
.without_bal_parallel_state_root(self.bal_parallel_state_root_disabled)
|
||||
.without_bal_batch_io(self.disable_bal_batch_io);
|
||||
#[cfg(feature = "trie-debug")]
|
||||
let config = config.with_proof_jitter(self.proof_jitter);
|
||||
config
|
||||
@@ -567,8 +598,10 @@ mod tests {
|
||||
sparse_trie_max_hot_accounts: 500,
|
||||
slow_block_threshold: None,
|
||||
disable_sparse_trie_cache_pruning: true,
|
||||
enable_arena_sparse_trie: true,
|
||||
state_root_task_timeout: Some(Duration::from_secs(2)),
|
||||
bal_parallel_execution_disabled: true,
|
||||
bal_parallel_state_root_disabled: true,
|
||||
disable_bal_batch_io: true,
|
||||
#[cfg(feature = "trie-debug")]
|
||||
proof_jitter: None,
|
||||
};
|
||||
@@ -607,9 +640,11 @@ mod tests {
|
||||
"--engine.sparse-trie-max-hot-accounts",
|
||||
"500",
|
||||
"--engine.disable-sparse-trie-cache-pruning",
|
||||
"--engine.enable-arena-sparse-trie",
|
||||
"--engine.state-root-task-timeout",
|
||||
"2s",
|
||||
"--engine.disable-bal-parallel-execution",
|
||||
"--engine.disable-bal-parallel-state-root",
|
||||
"--engine.disable-bal-batch-io",
|
||||
])
|
||||
.args;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use reth_tracing::{
|
||||
tracing_subscriber::filter::Directive, FileInfo, FileWorkerGuard, LayerInfo, Layers, LogFormat,
|
||||
RethTracer, Tracer,
|
||||
};
|
||||
use std::{fmt, fmt::Display};
|
||||
use std::{fmt, fmt::Display, sync::OnceLock};
|
||||
use tracing::{level_filters::LevelFilter, Level};
|
||||
|
||||
/// Constant to convert megabytes to bytes
|
||||
@@ -14,24 +14,27 @@ const MB_TO_BYTES: u64 = 1024 * 1024;
|
||||
|
||||
const PROFILER_TRACING_FILTER: &str = "debug";
|
||||
|
||||
/// Global static log defaults
|
||||
static LOG_DEFAULTS: OnceLock<DefaultLogArgs> = OnceLock::new();
|
||||
|
||||
/// The log configuration.
|
||||
#[derive(Debug, Args)]
|
||||
#[command(next_help_heading = "Logging")]
|
||||
pub struct LogArgs {
|
||||
/// The format to use for logs written to stdout.
|
||||
#[arg(long = "log.stdout.format", value_name = "FORMAT", global = true, default_value_t = LogFormat::Terminal)]
|
||||
#[arg(long = "log.stdout.format", value_name = "FORMAT", global = true, default_value_t = DefaultLogArgs::get_global().log_stdout_format)]
|
||||
pub log_stdout_format: LogFormat,
|
||||
|
||||
/// The filter to use for logs written to stdout.
|
||||
#[arg(long = "log.stdout.filter", value_name = "FILTER", global = true, default_value = "")]
|
||||
#[arg(long = "log.stdout.filter", value_name = "FILTER", global = true, default_value_t = DefaultLogArgs::get_global().log_stdout_filter.clone())]
|
||||
pub log_stdout_filter: String,
|
||||
|
||||
/// The format to use for logs written to the log file.
|
||||
#[arg(long = "log.file.format", value_name = "FORMAT", global = true, default_value_t = LogFormat::Terminal)]
|
||||
#[arg(long = "log.file.format", value_name = "FORMAT", global = true, default_value_t = DefaultLogArgs::get_global().log_file_format)]
|
||||
pub log_file_format: LogFormat,
|
||||
|
||||
/// The filter to use for logs written to the log file.
|
||||
#[arg(long = "log.file.filter", value_name = "FILTER", global = true, default_value = "debug")]
|
||||
#[arg(long = "log.file.filter", value_name = "FILTER", global = true, default_value_t = DefaultLogArgs::get_global().log_file_filter.clone())]
|
||||
pub log_file_filter: String,
|
||||
|
||||
/// The path to put log files in.
|
||||
@@ -39,11 +42,11 @@ pub struct LogArgs {
|
||||
pub log_file_directory: PlatformPath<LogsDir>,
|
||||
|
||||
/// The prefix name of the log files.
|
||||
#[arg(long = "log.file.name", value_name = "NAME", global = true, default_value = "reth.log")]
|
||||
#[arg(long = "log.file.name", value_name = "NAME", global = true, default_value_t = DefaultLogArgs::get_global().log_file_name.clone())]
|
||||
pub log_file_name: String,
|
||||
|
||||
/// The maximum size (in MB) of one log file.
|
||||
#[arg(long = "log.file.max-size", value_name = "SIZE", global = true, default_value_t = 200)]
|
||||
#[arg(long = "log.file.max-size", value_name = "SIZE", global = true, default_value_t = DefaultLogArgs::get_global().log_file_max_size)]
|
||||
pub log_file_max_size: u64,
|
||||
|
||||
/// The maximum amount of log files that will be stored. If set to 0, background file logging
|
||||
@@ -54,7 +57,7 @@ pub struct LogArgs {
|
||||
pub log_file_max_files: Option<usize>,
|
||||
|
||||
/// Write logs to journald.
|
||||
#[arg(long = "log.journald", global = true)]
|
||||
#[arg(long = "log.journald", global = true, default_value_t = DefaultLogArgs::get_global().journald)]
|
||||
pub journald: bool,
|
||||
|
||||
/// The filter to use for logs written to journald.
|
||||
@@ -62,12 +65,12 @@ pub struct LogArgs {
|
||||
long = "log.journald.filter",
|
||||
value_name = "FILTER",
|
||||
global = true,
|
||||
default_value = "error"
|
||||
default_value_t = DefaultLogArgs::get_global().journald_filter.clone()
|
||||
)]
|
||||
pub journald_filter: String,
|
||||
|
||||
/// Emit traces to samply. Only useful when profiling.
|
||||
#[arg(long = "log.samply", global = true, hide = true)]
|
||||
#[arg(long = "log.samply", global = true, hide = true, default_value_t = DefaultLogArgs::get_global().samply)]
|
||||
pub samply: bool,
|
||||
|
||||
/// The filter to use for traces emitted to samply.
|
||||
@@ -75,13 +78,13 @@ pub struct LogArgs {
|
||||
long = "log.samply.filter",
|
||||
value_name = "FILTER",
|
||||
global = true,
|
||||
default_value = PROFILER_TRACING_FILTER,
|
||||
default_value_t = DefaultLogArgs::get_global().samply_filter.clone(),
|
||||
hide = true
|
||||
)]
|
||||
pub samply_filter: String,
|
||||
|
||||
/// Emit traces to tracy. Only useful when profiling.
|
||||
#[arg(long = "log.tracy", global = true, hide = true)]
|
||||
#[arg(long = "log.tracy", global = true, hide = true, default_value_t = DefaultLogArgs::get_global().tracy)]
|
||||
pub tracy: bool,
|
||||
|
||||
/// The filter to use for traces emitted to tracy.
|
||||
@@ -89,7 +92,7 @@ pub struct LogArgs {
|
||||
long = "log.tracy.filter",
|
||||
value_name = "FILTER",
|
||||
global = true,
|
||||
default_value = PROFILER_TRACING_FILTER,
|
||||
default_value_t = DefaultLogArgs::get_global().tracy_filter.clone(),
|
||||
hide = true
|
||||
)]
|
||||
pub tracy_filter: String,
|
||||
@@ -100,7 +103,7 @@ pub struct LogArgs {
|
||||
long,
|
||||
value_name = "COLOR",
|
||||
global = true,
|
||||
default_value_t = ColorMode::Always
|
||||
default_value_t = DefaultLogArgs::get_global().color
|
||||
)]
|
||||
pub color: ColorMode,
|
||||
|
||||
@@ -210,6 +213,136 @@ impl LogArgs {
|
||||
}
|
||||
}
|
||||
|
||||
/// Default values for log configuration that can be customized.
|
||||
///
|
||||
/// Global defaults can be set via [`DefaultLogArgs::try_init`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DefaultLogArgs {
|
||||
log_stdout_format: LogFormat,
|
||||
log_stdout_filter: String,
|
||||
log_file_format: LogFormat,
|
||||
log_file_filter: String,
|
||||
log_file_name: String,
|
||||
log_file_max_size: u64,
|
||||
journald: bool,
|
||||
journald_filter: String,
|
||||
samply: bool,
|
||||
samply_filter: String,
|
||||
tracy: bool,
|
||||
tracy_filter: String,
|
||||
color: ColorMode,
|
||||
}
|
||||
|
||||
impl DefaultLogArgs {
|
||||
/// Initialize the global log defaults with this configuration.
|
||||
pub fn try_init(self) -> Result<(), Self> {
|
||||
LOG_DEFAULTS.set(self)
|
||||
}
|
||||
|
||||
/// Get a reference to the global log defaults.
|
||||
pub fn get_global() -> &'static Self {
|
||||
LOG_DEFAULTS.get_or_init(Self::default)
|
||||
}
|
||||
|
||||
/// Set the default stdout log format.
|
||||
pub const fn with_log_stdout_format(mut self, v: LogFormat) -> Self {
|
||||
self.log_stdout_format = v;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default stdout log filter.
|
||||
pub fn with_log_stdout_filter(mut self, v: String) -> Self {
|
||||
self.log_stdout_filter = v;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default file log format.
|
||||
pub const fn with_log_file_format(mut self, v: LogFormat) -> Self {
|
||||
self.log_file_format = v;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default file log filter.
|
||||
pub fn with_log_file_filter(mut self, v: String) -> Self {
|
||||
self.log_file_filter = v;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default log file name.
|
||||
pub fn with_log_file_name(mut self, v: String) -> Self {
|
||||
self.log_file_name = v;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default max log file size in MB.
|
||||
pub const fn with_log_file_max_size(mut self, v: u64) -> Self {
|
||||
self.log_file_max_size = v;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether journald logging is enabled by default.
|
||||
pub const fn with_journald(mut self, v: bool) -> Self {
|
||||
self.journald = v;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default journald filter.
|
||||
pub fn with_journald_filter(mut self, v: String) -> Self {
|
||||
self.journald_filter = v;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether samply tracing is enabled by default.
|
||||
pub const fn with_samply(mut self, v: bool) -> Self {
|
||||
self.samply = v;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default samply filter.
|
||||
pub fn with_samply_filter(mut self, v: String) -> Self {
|
||||
self.samply_filter = v;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether tracy tracing is enabled by default.
|
||||
pub const fn with_tracy(mut self, v: bool) -> Self {
|
||||
self.tracy = v;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default tracy filter.
|
||||
pub fn with_tracy_filter(mut self, v: String) -> Self {
|
||||
self.tracy_filter = v;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default color mode.
|
||||
pub const fn with_color(mut self, v: ColorMode) -> Self {
|
||||
self.color = v;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DefaultLogArgs {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
log_stdout_format: LogFormat::Terminal,
|
||||
log_stdout_filter: String::new(),
|
||||
log_file_format: LogFormat::Terminal,
|
||||
log_file_filter: "debug".to_string(),
|
||||
log_file_name: "reth.log".to_string(),
|
||||
log_file_max_size: 200,
|
||||
journald: false,
|
||||
journald_filter: "error".to_string(),
|
||||
samply: false,
|
||||
samply_filter: PROFILER_TRACING_FILTER.to_string(),
|
||||
tracy: false,
|
||||
tracy_filter: PROFILER_TRACING_FILTER.to_string(),
|
||||
color: ColorMode::Always,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The color mode for the cli.
|
||||
#[derive(Debug, Copy, Clone, ValueEnum, Eq, PartialEq)]
|
||||
pub enum ColorMode {
|
||||
|
||||
@@ -22,7 +22,7 @@ pub use database::DatabaseArgs;
|
||||
|
||||
/// LogArgs struct for configuring the logger
|
||||
mod log;
|
||||
pub use log::{ColorMode, LogArgs, Verbosity};
|
||||
pub use log::{ColorMode, DefaultLogArgs, LogArgs, Verbosity};
|
||||
|
||||
/// `TraceArgs` for tracing and spans support
|
||||
mod trace;
|
||||
@@ -62,7 +62,7 @@ pub use datadir_args::DatadirArgs;
|
||||
|
||||
/// BenchmarkArgs struct for configuring the benchmark to run
|
||||
mod benchmark_args;
|
||||
pub use benchmark_args::BenchmarkArgs;
|
||||
pub use benchmark_args::{BenchmarkArgs, RpcBlockFetchRetries};
|
||||
|
||||
/// EngineArgs for configuring the engine
|
||||
mod engine;
|
||||
@@ -78,7 +78,7 @@ pub use static_files::{StaticFilesArgs, MINIMAL_BLOCKS_PER_FILE};
|
||||
|
||||
/// `StorageArgs` for configuring storage settings.
|
||||
mod storage;
|
||||
pub use storage::StorageArgs;
|
||||
pub use storage::{DefaultStorageValues, StorageArgs};
|
||||
|
||||
mod error;
|
||||
pub mod types;
|
||||
|
||||
@@ -805,6 +805,8 @@ impl DiscoveryArgs {
|
||||
..
|
||||
} = self;
|
||||
|
||||
let has_discv5_addr_args = discv5_addr.is_some() || discv5_addr_ipv6.is_some();
|
||||
|
||||
// Use rlpx address if none given
|
||||
let discv5_addr_ipv4 = discv5_addr.or(match rlpx_tcp_socket {
|
||||
SocketAddr::V4(addr) => Some(*addr.ip()),
|
||||
@@ -820,7 +822,9 @@ impl DiscoveryArgs {
|
||||
discv5_addr_ipv4.map(|addr| SocketAddrV4::new(addr, *discv5_port)),
|
||||
discv5_addr_ipv6.map(|addr| SocketAddrV6::new(addr, *discv5_port_ipv6, 0, 0)),
|
||||
));
|
||||
if discv5_addr.is_some() || discv5_addr_ipv6.is_some() || self.disable_nat {
|
||||
|
||||
if has_discv5_addr_args || self.disable_nat {
|
||||
// disable native enr update if addresses manually set or nat disabled
|
||||
discv5_config_builder.disable_enr_update();
|
||||
}
|
||||
reth_discv5::Config::builder(rlpx_tcp_socket)
|
||||
|
||||
@@ -648,6 +648,22 @@ pub struct RpcServerArgs {
|
||||
#[arg(long = "testing.skip-invalid-transactions", default_value_t = true)]
|
||||
pub testing_skip_invalid_transactions: bool,
|
||||
|
||||
/// Skip the 1/1024 gas limit change restriction between parent and child blocks.
|
||||
///
|
||||
/// When enabled, consensus will not enforce the gas limit bound divisor check,
|
||||
/// allowing blocks to jump to an arbitrary gas limit without ramping up over
|
||||
/// thousands of empty blocks.
|
||||
#[arg(long = "testing.skip-gas-limit-ramp-check", default_value_t = false, hide = true)]
|
||||
pub testing_skip_gas_limit_ramp_check: bool,
|
||||
|
||||
/// Override the gas limit used by `testing_buildBlockV1`.
|
||||
///
|
||||
/// When set, `testing_buildBlockV1` will use this value instead of inheriting
|
||||
/// the parent block's gas limit. Accepts short notation: K for thousand, M for
|
||||
/// million, G for billion (e.g., 1G = 1 billion).
|
||||
#[arg(long = "testing.gas-limit", value_name = "GAS_LIMIT", hide = true)]
|
||||
pub testing_gas_limit: Option<u64>,
|
||||
|
||||
/// Force upcasting EIP-4844 blob sidecars to EIP-7594 format when Osaka is active.
|
||||
///
|
||||
/// When enabled, blob transactions submitted via `eth_sendRawTransaction` with EIP-4844
|
||||
@@ -886,6 +902,8 @@ impl Default for RpcServerArgs {
|
||||
gas_price_oracle,
|
||||
rpc_send_raw_transaction_sync_timeout,
|
||||
testing_skip_invalid_transactions: true,
|
||||
testing_skip_gas_limit_ramp_check: false,
|
||||
testing_gas_limit: None,
|
||||
rpc_force_blob_sidecar_upcasting: false,
|
||||
}
|
||||
}
|
||||
@@ -1063,6 +1081,8 @@ mod tests {
|
||||
},
|
||||
rpc_send_raw_transaction_sync_timeout: std::time::Duration::from_secs(30),
|
||||
testing_skip_invalid_transactions: true,
|
||||
testing_skip_gas_limit_ramp_check: false,
|
||||
testing_gas_limit: None,
|
||||
rpc_force_blob_sidecar_upcasting: false,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,26 +1,68 @@
|
||||
//! clap [Args](clap::Args) for storage configuration
|
||||
|
||||
use clap::{ArgAction, Args};
|
||||
use clap::Args;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
/// Global static storage defaults
|
||||
static STORAGE_DEFAULTS: OnceLock<DefaultStorageValues> = OnceLock::new();
|
||||
|
||||
/// Default values for storage that can be customized
|
||||
///
|
||||
/// Global defaults can be set via [`DefaultStorageValues::try_init`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DefaultStorageValues {
|
||||
v2: bool,
|
||||
}
|
||||
|
||||
impl DefaultStorageValues {
|
||||
/// Initialize the global storage defaults with this configuration
|
||||
pub fn try_init(self) -> Result<(), Self> {
|
||||
STORAGE_DEFAULTS.set(self)
|
||||
}
|
||||
|
||||
/// Get a reference to the global storage defaults
|
||||
pub fn get_global() -> &'static Self {
|
||||
STORAGE_DEFAULTS.get_or_init(Self::default)
|
||||
}
|
||||
|
||||
/// Set the default V2 storage layout flag
|
||||
pub const fn with_v2(mut self, v: bool) -> Self {
|
||||
self.v2 = v;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DefaultStorageValues {
|
||||
fn default() -> Self {
|
||||
Self { v2: true }
|
||||
}
|
||||
}
|
||||
|
||||
/// Parameters for storage configuration.
|
||||
///
|
||||
/// V2 storage is now the default for all new databases. The `--storage.v2` flag is
|
||||
/// accepted for backwards compatibility but has no effect — v2 is always used.
|
||||
/// `--storage.v2` controls whether new databases use the hot/cold V2 storage layout.
|
||||
/// Defaults to `true`.
|
||||
///
|
||||
/// Existing databases always use the settings persisted in their metadata.
|
||||
///
|
||||
/// Individual storage settings can be overridden with `--static-files.*` and `--rocksdb.*` flags.
|
||||
#[derive(Debug, Args, PartialEq, Eq, Clone, Copy, Default)]
|
||||
#[derive(Debug, Args, PartialEq, Eq, Clone, Copy)]
|
||||
#[command(next_help_heading = "Storage")]
|
||||
pub struct StorageArgs {
|
||||
/// Deprecated no-op: v2 storage is now always enabled for new databases.
|
||||
/// Enable V2 (hot/cold) storage layout for new databases.
|
||||
///
|
||||
/// Kept for backwards compatibility with existing scripts and configurations.
|
||||
/// Existing databases always use the settings persisted in their metadata.
|
||||
#[arg(long = "storage.v2", action = ArgAction::SetTrue, hide = true)]
|
||||
/// When set, new databases will be initialized with the V2 storage layout that
|
||||
/// separates hot and cold data. Existing databases always use the settings
|
||||
/// persisted in their metadata regardless of this flag.
|
||||
#[arg(long = "storage.v2", default_value_t = DefaultStorageValues::get_global().v2, action = clap::ArgAction::Set)]
|
||||
pub v2: bool,
|
||||
}
|
||||
|
||||
impl Default for StorageArgs {
|
||||
fn default() -> Self {
|
||||
let defaults = DefaultStorageValues::get_global();
|
||||
Self { v2: defaults.v2 }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -36,13 +78,18 @@ mod tests {
|
||||
#[test]
|
||||
fn test_default_storage_args() {
|
||||
let args = CommandParser::<StorageArgs>::parse_from(["reth"]).args;
|
||||
assert_eq!(args, StorageArgs::default());
|
||||
assert!(args.v2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_v2_flag_accepted() {
|
||||
// Flag is accepted for backwards compatibility but is a no-op
|
||||
let args = CommandParser::<StorageArgs>::parse_from(["reth", "--storage.v2"]).args;
|
||||
fn test_storage_v2_explicit_true() {
|
||||
let args = CommandParser::<StorageArgs>::parse_from(["reth", "--storage.v2=true"]).args;
|
||||
assert!(args.v2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_storage_v2_explicit_false() {
|
||||
let args = CommandParser::<StorageArgs>::parse_from(["reth", "--storage.v2=false"]).args;
|
||||
assert!(!args.v2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,14 +373,15 @@ impl<ChainSpec> NodeConfig<ChainSpec> {
|
||||
|
||||
/// Returns the effective storage settings for this node.
|
||||
///
|
||||
/// Always returns [`StorageSettings::v2()`] — v2 storage is the default for
|
||||
/// new nodes. Existing nodes retain whatever settings are persisted in their
|
||||
/// database metadata (checked during genesis init).
|
||||
///
|
||||
/// Determined by the `--storage.v2` flag (defaults to `true`).
|
||||
/// Existing databases retain whatever settings are persisted in their
|
||||
/// metadata (checked during genesis init).
|
||||
pub const fn storage_settings(&self) -> StorageSettings {
|
||||
StorageSettings::v2()
|
||||
if self.storage.v2 {
|
||||
StorageSettings::v2()
|
||||
} else {
|
||||
StorageSettings::v1()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the max block that the node should run to, looking it up from the network if
|
||||
|
||||
@@ -120,8 +120,14 @@ where
|
||||
Self::Right(r) => r.withdrawals(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn slot_number(&self) -> Option<u64> {
|
||||
match self {
|
||||
Self::Left(l) => l.slot_number(),
|
||||
Self::Right(r) => r.slot_number(),
|
||||
}
|
||||
}
|
||||
}
|
||||
/// this structure enables the chaining of multiple `PayloadBuilder` implementations,
|
||||
/// creating a hierarchical fallback system. It's designed to be nestable, allowing
|
||||
/// for complex builder arrangements like `Stack<Stack<A, B>, C>` with different
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
//! },
|
||||
//! ..Default::default()
|
||||
//! };
|
||||
//! let payload = EthBuiltPayload::new(self.attributes.id, Arc::new(SealedBlock::seal_slow(block)), U256::ZERO, None);
|
||||
//! let payload = EthBuiltPayload::new(self.attributes.id, Arc::new(SealedBlock::seal_slow(block)), U256::ZERO, None, None);
|
||||
//! Ok(payload)
|
||||
//! }
|
||||
//!
|
||||
|
||||
@@ -91,6 +91,7 @@ impl PayloadJob for TestPayloadJob {
|
||||
Arc::new(Block::<_>::default().seal_slow()),
|
||||
U256::ZERO,
|
||||
Some(Default::default()),
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ std = [
|
||||
"either/std",
|
||||
"alloy-consensus/std",
|
||||
]
|
||||
# op = [
|
||||
# "dep:op-alloy-rpc-types-engine",
|
||||
# "reth-primitives-traits/op",
|
||||
# ]
|
||||
op = [
|
||||
"dep:op-alloy-rpc-types-engine",
|
||||
"reth-primitives-traits/op",
|
||||
]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user