mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-04-30 03:01:58 -04:00
Compare commits
58 Commits
fix-filter
...
klkvr/opti
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe2297639c | ||
|
|
4e49b5dea2 | ||
|
|
bb963e634e | ||
|
|
c225132b81 | ||
|
|
dcc5d9ec30 | ||
|
|
6cd56b645b | ||
|
|
794dbff26e | ||
|
|
fcfbed0bbc | ||
|
|
70bcd475fe | ||
|
|
cd6e895a97 | ||
|
|
6552a3a9ab | ||
|
|
6a91089542 | ||
|
|
a9a1e504b4 | ||
|
|
e280f25885 | ||
|
|
37c4f908fa | ||
|
|
a157be3f3b | ||
|
|
e0eb306b2b | ||
|
|
7f4f3f1eb9 | ||
|
|
8970f82aaf | ||
|
|
8529da976f | ||
|
|
8fa539225b | ||
|
|
93d546a36d | ||
|
|
5c83eb0b06 | ||
|
|
cd32e3cc05 | ||
|
|
26470cadfc | ||
|
|
506ab806e4 | ||
|
|
c2e846093e | ||
|
|
5df22b12d8 | ||
|
|
ff9700bb3b | ||
|
|
85d35fa6c0 | ||
|
|
47544d9a7e | ||
|
|
ef33961aff | ||
|
|
0e01a694a7 | ||
|
|
ee19320ee8 | ||
|
|
9251997c1f | ||
|
|
302993b45a | ||
|
|
8d97ab63c6 | ||
|
|
251f83ab0b | ||
|
|
e6e0dde903 | ||
|
|
b1b51261af | ||
|
|
2ae5ef475e | ||
|
|
8861e2724f | ||
|
|
734ec4ffe6 | ||
|
|
cbcdf8dac0 | ||
|
|
826e387c87 | ||
|
|
1c40188993 | ||
|
|
49a2df0d7a | ||
|
|
a1d1b6def6 | ||
|
|
56bbb3ce2c | ||
|
|
5b1010322c | ||
|
|
a195b777eb | ||
|
|
5045e6ef8b | ||
|
|
b49cadb346 | ||
|
|
aeb2c6e731 | ||
|
|
477fed7a11 | ||
|
|
59993b974a | ||
|
|
9ecef47aff | ||
|
|
0ba685386d |
5
.changelog/dull-koalas-play.md
Normal file
5
.changelog/dull-koalas-play.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
reth-trie-sparse: patch
|
||||
---
|
||||
|
||||
Added recording of `SetRoot` operation in `ParallelSparseTrie::set_root` when the `trie-debug` feature is enabled.
|
||||
6
.changelog/evil-fish-smile.md
Normal file
6
.changelog/evil-fish-smile.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
reth-rpc-convert: minor
|
||||
reth-storage-rpc-provider: minor
|
||||
---
|
||||
|
||||
Replaced the separate `TryFromBlockResponse`, `TryFromReceiptResponse`, and `TryFromTransactionResponse` traits with a unified `RpcResponseConverter` trait and default `EthRpcConverter` implementation. Removed the `op-alloy-network` dependency and refactored `RpcBlockchainProvider` to store a dynamic converter instance instead of relying on per-type trait bounds.
|
||||
5
.changelog/odd-donkeys-chirp.md
Normal file
5
.changelog/odd-donkeys-chirp.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
reth-engine-tree: patch
|
||||
---
|
||||
|
||||
Fixed `compare_trie_updates` to return `bool` indicating whether differences were found, and updated the caller to properly use the return value instead of treating all successful comparisons as having no differences.
|
||||
5
.changelog/proud-crabs-bark.md
Normal file
5
.changelog/proud-crabs-bark.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
reth-db-api: patch
|
||||
---
|
||||
|
||||
Changed `StoredNibblesSubKey` encoding to use a stack-allocated `[u8; 65]` array instead of a heap-allocated `Vec<u8>`, avoiding unnecessary heap allocation.
|
||||
5
.changelog/rich-lakes-bark.md
Normal file
5
.changelog/rich-lakes-bark.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
reth-provider: patch
|
||||
---
|
||||
|
||||
Fixed sender pruning during block reorg to skip when sender_recovery is fully pruned, preventing a fatal crash when no sender data exists in static files.
|
||||
5
.changelog/warm-pandas-cook.md
Normal file
5
.changelog/warm-pandas-cook.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
reth-transaction-pool: patch
|
||||
---
|
||||
|
||||
Fixed a bug where transactions from the same sender were added to the pending subpool out of nonce order. Ensured `process_updates` runs before `add_new_transaction` so that lower-nonce promotions are enqueued before the newly inserted higher-nonce transaction, preserving correct ordering for live `BestTransactions` iterators.
|
||||
49
.github/scripts/bench-reth-build.sh
vendored
49
.github/scripts/bench-reth-build.sh
vendored
@@ -2,55 +2,56 @@
|
||||
#
|
||||
# Builds (or fetches from cache) reth binaries for benchmarking.
|
||||
#
|
||||
# Usage: bench-reth-build.sh <main|branch> <commit> [branch-sha]
|
||||
# Usage: bench-reth-build.sh <baseline|feature> <source-dir> <commit> [branch-sha]
|
||||
#
|
||||
# main — build/fetch the baseline binary at <commit> (merge-base)
|
||||
# branch — build/fetch the candidate binary + reth-bench at <commit>
|
||||
# optional branch-sha is the PR head commit for cache key
|
||||
# baseline — build/fetch the baseline binary at <commit> (merge-base)
|
||||
# source-dir must be checked out at <commit>
|
||||
# feature — build/fetch the candidate binary + reth-bench at <commit>
|
||||
# source-dir must be checked out at <commit>
|
||||
# optional branch-sha is the PR head commit for cache key
|
||||
#
|
||||
# Outputs:
|
||||
# main: target/profiling-baseline/reth
|
||||
# branch: target/profiling/reth, reth-bench installed to cargo bin
|
||||
# baseline: <source-dir>/target/profiling/reth
|
||||
# feature: <source-dir>/target/profiling/reth, reth-bench installed to cargo bin
|
||||
#
|
||||
# Required: mc (MinIO client) configured at /home/ubuntu/.mc
|
||||
set -euo pipefail
|
||||
|
||||
MC="mc --config-dir /home/ubuntu/.mc"
|
||||
MODE="$1"
|
||||
COMMIT="$2"
|
||||
SOURCE_DIR="$2"
|
||||
COMMIT="$3"
|
||||
|
||||
case "$MODE" in
|
||||
main)
|
||||
baseline|main)
|
||||
BUCKET="minio/reth-binaries/${COMMIT}"
|
||||
mkdir -p target/profiling-baseline
|
||||
mkdir -p "${SOURCE_DIR}/target/profiling"
|
||||
|
||||
if $MC stat "${BUCKET}/reth" &>/dev/null; then
|
||||
echo "Cache hit for main (${COMMIT}), downloading binary..."
|
||||
$MC cp "${BUCKET}/reth" target/profiling-baseline/reth
|
||||
chmod +x target/profiling-baseline/reth
|
||||
echo "Cache hit for baseline (${COMMIT}), downloading binary..."
|
||||
$MC cp "${BUCKET}/reth" "${SOURCE_DIR}/target/profiling/reth"
|
||||
chmod +x "${SOURCE_DIR}/target/profiling/reth"
|
||||
else
|
||||
echo "Cache miss for main (${COMMIT}), building from source..."
|
||||
CURRENT_REF=$(git rev-parse HEAD)
|
||||
git checkout "${COMMIT}"
|
||||
echo "Cache miss for baseline (${COMMIT}), building from source..."
|
||||
cd "${SOURCE_DIR}"
|
||||
cargo build --profile profiling --bin reth
|
||||
cp target/profiling/reth target/profiling-baseline/reth
|
||||
$MC cp target/profiling-baseline/reth "${BUCKET}/reth"
|
||||
git checkout "${CURRENT_REF}"
|
||||
$MC cp target/profiling/reth "${BUCKET}/reth"
|
||||
fi
|
||||
;;
|
||||
|
||||
branch)
|
||||
BRANCH_SHA="${3:-$COMMIT}"
|
||||
feature|branch)
|
||||
BRANCH_SHA="${4:-$COMMIT}"
|
||||
BUCKET="minio/reth-binaries/${BRANCH_SHA}"
|
||||
|
||||
if $MC stat "${BUCKET}/reth" &>/dev/null && $MC stat "${BUCKET}/reth-bench" &>/dev/null; then
|
||||
echo "Cache hit for ${BRANCH_SHA}, downloading binaries..."
|
||||
mkdir -p target/profiling
|
||||
$MC cp "${BUCKET}/reth" target/profiling/reth
|
||||
mkdir -p "${SOURCE_DIR}/target/profiling"
|
||||
$MC cp "${BUCKET}/reth" "${SOURCE_DIR}/target/profiling/reth"
|
||||
$MC cp "${BUCKET}/reth-bench" /home/ubuntu/.cargo/bin/reth-bench
|
||||
chmod +x target/profiling/reth /home/ubuntu/.cargo/bin/reth-bench
|
||||
chmod +x "${SOURCE_DIR}/target/profiling/reth" /home/ubuntu/.cargo/bin/reth-bench
|
||||
else
|
||||
echo "Cache miss for ${BRANCH_SHA}, building from source..."
|
||||
cd "${SOURCE_DIR}"
|
||||
rustup show active-toolchain || rustup default stable
|
||||
make profiling
|
||||
make install-reth-bench
|
||||
@@ -60,7 +61,7 @@ case "$MODE" in
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Usage: $0 <main|branch> <commit> [branch-sha]"
|
||||
echo "Usage: $0 <baseline|feature> <source-dir> <commit> [branch-sha]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
60
.github/scripts/bench-reth-charts.py
vendored
60
.github/scripts/bench-reth-charts.py
vendored
@@ -53,7 +53,7 @@ def parse_combined_csv(path: str) -> list[dict]:
|
||||
|
||||
def plot_latency_and_throughput(
|
||||
feature: list[dict], baseline: list[dict] | None, out: Path,
|
||||
baseline_name: str = "main", branch_name: str = "branch",
|
||||
baseline_name: str = "baseline", feature_name: str = "feature",
|
||||
):
|
||||
num_plots = 3 if baseline else 2
|
||||
fig, axes = plt.subplots(num_plots, 1, figsize=(12, 4 * num_plots), sharex=True)
|
||||
@@ -76,14 +76,14 @@ def plot_latency_and_throughput(
|
||||
ax1.plot(base_x, base_lat, linewidth=0.8, label=baseline_name, alpha=0.7)
|
||||
ax2.plot(base_x, base_ggas, linewidth=0.8, label=baseline_name, alpha=0.7)
|
||||
|
||||
ax1.plot(feat_x, feat_lat, linewidth=0.8, label=branch_name)
|
||||
ax1.plot(feat_x, feat_lat, linewidth=0.8, label=feature_name)
|
||||
ax1.set_ylabel("Latency (ms)")
|
||||
ax1.set_title("newPayload Latency per Block")
|
||||
ax1.grid(True, alpha=0.3)
|
||||
if baseline:
|
||||
ax1.legend()
|
||||
|
||||
ax2.plot(feat_x, feat_ggas, linewidth=0.8, label=branch_name)
|
||||
ax2.plot(feat_x, feat_ggas, linewidth=0.8, label=feature_name)
|
||||
ax2.set_ylabel("Ggas/s")
|
||||
ax2.set_title("Execution Throughput per Block")
|
||||
ax2.grid(True, alpha=0.3)
|
||||
@@ -105,7 +105,7 @@ def plot_latency_and_throughput(
|
||||
ax3.bar(blocks, diffs, width=1.0, color=colors, alpha=0.7, edgecolor="none")
|
||||
ax3.axhline(0, color="black", linewidth=0.5)
|
||||
ax3.set_ylabel("Δ Latency (%)")
|
||||
ax3.set_title("Per-Block newPayload Latency Change (branch vs main)")
|
||||
ax3.set_title("Per-Block newPayload Latency Change (feature vs baseline)")
|
||||
ax3.grid(True, alpha=0.3, axis="y")
|
||||
|
||||
axes[-1].set_xlabel("Block Number")
|
||||
@@ -116,7 +116,7 @@ def plot_latency_and_throughput(
|
||||
|
||||
def plot_wait_breakdown(
|
||||
feature: list[dict], baseline: list[dict] | None, out: Path,
|
||||
baseline_name: str = "main", branch_name: str = "branch",
|
||||
baseline_name: str = "baseline", feature_name: str = "feature",
|
||||
):
|
||||
series = [
|
||||
("Persistence Wait", "persistence_wait_us"),
|
||||
@@ -135,7 +135,7 @@ def plot_wait_breakdown(
|
||||
fx = [r["block_number"] for r in feature if r[key] is not None]
|
||||
fy = [r[key] / 1_000 for r in feature if r[key] is not None]
|
||||
if fx:
|
||||
ax.plot(fx, fy, linewidth=0.8, label=branch_name)
|
||||
ax.plot(fx, fy, linewidth=0.8, label=feature_name)
|
||||
|
||||
ax.set_ylabel("ms")
|
||||
ax.set_title(label)
|
||||
@@ -163,7 +163,7 @@ def _add_regression(ax, x, y, color, label):
|
||||
|
||||
def plot_gas_vs_latency(
|
||||
feature: list[dict], baseline: list[dict] | None, out: Path,
|
||||
baseline_name: str = "main", branch_name: str = "branch",
|
||||
baseline_name: str = "baseline", feature_name: str = "feature",
|
||||
):
|
||||
fig, ax = plt.subplots(figsize=(8, 6))
|
||||
|
||||
@@ -176,7 +176,7 @@ def plot_gas_vs_latency(
|
||||
fgas = [r["gas_used"] / 1_000_000 for r in feature]
|
||||
flat = [r["new_payload_latency_us"] / 1_000 for r in feature]
|
||||
ax.scatter(fgas, flat, s=8, alpha=0.6)
|
||||
_add_regression(ax, fgas, flat, "tab:orange", branch_name)
|
||||
_add_regression(ax, fgas, flat, "tab:orange", feature_name)
|
||||
|
||||
ax.set_xlabel("Gas Used (Mgas)")
|
||||
ax.set_ylabel("newPayload Latency (ms)")
|
||||
@@ -188,30 +188,56 @@ def plot_gas_vs_latency(
|
||||
plt.close(fig)
|
||||
|
||||
|
||||
def merge_csvs(paths: list[str]) -> list[dict]:
|
||||
"""Parse and merge multiple CSVs, averaging values for duplicate blocks."""
|
||||
by_block: dict[int, list[dict]] = {}
|
||||
for path in paths:
|
||||
for row in parse_combined_csv(path):
|
||||
by_block.setdefault(row["block_number"], []).append(row)
|
||||
|
||||
merged = []
|
||||
for bn in sorted(by_block):
|
||||
rows = by_block[bn]
|
||||
if len(rows) == 1:
|
||||
merged.append(rows[0])
|
||||
else:
|
||||
avg = {"block_number": bn}
|
||||
for key in ("gas_used", "new_payload_latency_us"):
|
||||
avg[key] = int(sum(r[key] for r in rows) / len(rows))
|
||||
for key in ("persistence_wait_us", "execution_cache_wait_us", "sparse_trie_wait_us"):
|
||||
vals = [r[key] for r in rows if r[key] is not None]
|
||||
avg[key] = int(sum(vals) / len(vals)) if vals else None
|
||||
merged.append(avg)
|
||||
return merged
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Generate benchmark charts")
|
||||
parser.add_argument("combined_csv", help="Path to combined_latency.csv (feature)")
|
||||
parser.add_argument(
|
||||
"--feature", nargs="+", required=True,
|
||||
help="Path(s) to feature combined_latency.csv",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-dir", required=True, help="Output directory for PNG charts"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--baseline", help="Path to baseline (main) combined_latency.csv"
|
||||
"--baseline", nargs="+", help="Path(s) to baseline combined_latency.csv"
|
||||
)
|
||||
parser.add_argument("--baseline-name", default="main", help="Label for baseline")
|
||||
parser.add_argument("--branch-name", default="branch", help="Label for branch")
|
||||
parser.add_argument("--baseline-name", default="baseline", help="Label for baseline")
|
||||
parser.add_argument("--feature-name", "--branch-name", default="feature", help="Label for feature")
|
||||
args = parser.parse_args()
|
||||
|
||||
feature = parse_combined_csv(args.combined_csv)
|
||||
feature = merge_csvs(args.feature)
|
||||
if not feature:
|
||||
print("No results found in combined CSV", file=sys.stderr)
|
||||
print("No results found in feature CSV(s)", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
baseline = None
|
||||
if args.baseline:
|
||||
baseline = parse_combined_csv(args.baseline)
|
||||
baseline = merge_csvs(args.baseline)
|
||||
if not baseline:
|
||||
print(
|
||||
"Warning: no results in baseline CSV, skipping comparison",
|
||||
"Warning: no results in baseline CSV(s), skipping comparison",
|
||||
file=sys.stderr,
|
||||
)
|
||||
baseline = None
|
||||
@@ -220,7 +246,7 @@ def main():
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
bname = args.baseline_name
|
||||
fname = args.branch_name
|
||||
fname = args.feature_name
|
||||
plot_latency_and_throughput(feature, baseline, out_dir / "latency_throughput.png", bname, fname)
|
||||
plot_wait_breakdown(feature, baseline, out_dir / "wait_breakdown.png", bname, fname)
|
||||
plot_gas_vs_latency(feature, baseline, out_dir / "gas_vs_latency.png", bname, fname)
|
||||
|
||||
17
.github/scripts/bench-reth-run.sh
vendored
17
.github/scripts/bench-reth-run.sh
vendored
@@ -12,7 +12,8 @@ LABEL="$1"
|
||||
BINARY="$2"
|
||||
OUTPUT_DIR="$3"
|
||||
DATADIR="$SCHELK_MOUNT/datadir"
|
||||
LOG="/tmp/reth-bench-node-${LABEL}.log"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
LOG="${OUTPUT_DIR}/node.log"
|
||||
|
||||
cleanup() {
|
||||
kill "$TAIL_PID" 2>/dev/null || true
|
||||
@@ -23,8 +24,12 @@ cleanup() {
|
||||
sleep 1
|
||||
done
|
||||
sudo kill -9 "$RETH_PID" 2>/dev/null || true
|
||||
sleep 1
|
||||
fi
|
||||
if mountpoint -q "$SCHELK_MOUNT"; then
|
||||
sudo umount -l "$SCHELK_MOUNT" || true
|
||||
sudo schelk recover -y || true
|
||||
fi
|
||||
mountpoint -q "$SCHELK_MOUNT" && sudo schelk recover -y || true
|
||||
}
|
||||
TAIL_PID=
|
||||
trap cleanup EXIT
|
||||
@@ -74,8 +79,12 @@ for i in $(seq 1 60); do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# 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)"
|
||||
|
||||
# Warmup
|
||||
sudo nice -n -20 "$RETH_BENCH" new-payload-fcu \
|
||||
$BENCH_NICE "$RETH_BENCH" new-payload-fcu \
|
||||
--rpc-url "$BENCH_RPC_URL" \
|
||||
--engine-rpc-url http://127.0.0.1:8551 \
|
||||
--jwt-secret "$DATADIR/jwt.hex" \
|
||||
@@ -83,7 +92,7 @@ sudo nice -n -20 "$RETH_BENCH" new-payload-fcu \
|
||||
--reth-new-payload 2>&1 | sed -u "s/^/[bench] /"
|
||||
|
||||
# Benchmark
|
||||
sudo nice -n -20 "$RETH_BENCH" new-payload-fcu \
|
||||
$BENCH_NICE "$RETH_BENCH" new-payload-fcu \
|
||||
--rpc-url "$BENCH_RPC_URL" \
|
||||
--engine-rpc-url http://127.0.0.1:8551 \
|
||||
--jwt-secret "$DATADIR/jwt.hex" \
|
||||
|
||||
125
.github/scripts/bench-reth-snapshot.sh
vendored
Executable file
125
.github/scripts/bench-reth-snapshot.sh
vendored
Executable file
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Downloads the latest nightly snapshot into the schelk volume with
|
||||
# progress reporting to the GitHub PR comment.
|
||||
#
|
||||
# Skips the download if the local ETag marker matches the remote one.
|
||||
#
|
||||
# Usage: bench-reth-snapshot.sh [--check]
|
||||
# --check Only check if a download is needed; exits 0 if up-to-date, 1 if not.
|
||||
#
|
||||
# Required env:
|
||||
# SCHELK_MOUNT – schelk mount point (e.g. /reth-bench)
|
||||
# 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)
|
||||
# BENCH_JOB_URL – link to the Actions job
|
||||
# BENCH_ACTOR – user who triggered the benchmark
|
||||
# BENCH_CONFIG – config summary line
|
||||
set -euo pipefail
|
||||
|
||||
BUCKET="minio/reth-snapshots/reth-1-minimal-nightly-previous.tar.zst"
|
||||
DATADIR="$SCHELK_MOUNT/datadir"
|
||||
ETAG_FILE="$HOME/.reth-bench-snapshot-etag"
|
||||
|
||||
# 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
|
||||
|
||||
LOCAL_ETAG=""
|
||||
[ -f "$ETAG_FILE" ] && LOCAL_ETAG=$(cat "$ETAG_FILE")
|
||||
|
||||
if [ "$REMOTE_ETAG" = "$LOCAL_ETAG" ]; then
|
||||
echo "Snapshot is up-to-date (ETag: ${REMOTE_ETAG})"
|
||||
if [ "${1:-}" = "--check" ]; then
|
||||
exit 0
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Snapshot needs update (local: ${LOCAL_ETAG:-<none>}, remote: ${REMOTE_ETAG})"
|
||||
if [ "${1:-}" = "--check" ]; then
|
||||
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"
|
||||
exit 1
|
||||
fi
|
||||
echo "Snapshot size: $TOTAL_BYTES bytes ($(numfmt --to=iec "$TOTAL_BYTES"))"
|
||||
|
||||
# Prepare mount
|
||||
mountpoint -q "$SCHELK_MOUNT" && sudo schelk recover -y || true
|
||||
sudo schelk mount -y
|
||||
sudo rm -rf "$DATADIR"
|
||||
sudo mkdir -p "$DATADIR"
|
||||
|
||||
update_comment() {
|
||||
local pct="$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")"
|
||||
curl -sf -X PATCH \
|
||||
-H "Authorization: token ${GITHUB_TOKEN}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/${BENCH_REPO}/issues/comments/${BENCH_COMMENT_ID}" \
|
||||
-d "$(jq -nc --arg body "$body" '{body: $body}')" \
|
||||
> /dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
# Track compressed bytes flowing through the pipe
|
||||
DL_BYTES_FILE=$(mktemp)
|
||||
echo 0 > "$DL_BYTES_FILE"
|
||||
|
||||
# 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 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"
|
||||
echo "Snapshot download complete"
|
||||
|
||||
# Sync the new snapshot as the schelk baseline
|
||||
sync
|
||||
sudo schelk recover -y
|
||||
|
||||
# Save ETag marker
|
||||
echo "$REMOTE_ETAG" > "$ETAG_FILE"
|
||||
echo "Snapshot synced to schelk (ETag: ${REMOTE_ETAG})"
|
||||
207
.github/scripts/bench-reth-summary.py
vendored
207
.github/scripts/bench-reth-summary.py
vendored
@@ -8,12 +8,12 @@ Usage:
|
||||
--baseline-csv <baseline_combined.csv> \
|
||||
[--repo <owner/repo>] \
|
||||
[--baseline-ref <sha>] \
|
||||
[--branch-name <name>] \
|
||||
[--branch-sha <sha>]
|
||||
[--feature-name <name>] \
|
||||
[--feature-sha <sha>]
|
||||
|
||||
Generates a paired statistical comparison between baseline (main) and branch.
|
||||
Generates a paired statistical comparison between baseline and feature.
|
||||
Matches blocks by number and computes per-block diffs to cancel out gas
|
||||
variance. Fails if baseline or branch CSV is missing or empty.
|
||||
variance. Fails if baseline or feature CSV is missing or empty.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -28,6 +28,14 @@ T_CRITICAL = 1.96 # two-tailed 95% confidence
|
||||
BOOTSTRAP_ITERATIONS = 10_000
|
||||
|
||||
|
||||
def _opt_int(row: dict, key: str) -> int | None:
|
||||
"""Return int value for a CSV field, or None if missing/empty."""
|
||||
v = row.get(key)
|
||||
if v is None or v == "":
|
||||
return None
|
||||
return int(v)
|
||||
|
||||
|
||||
def parse_combined_csv(path: str) -> list[dict]:
|
||||
"""Parse combined_latency.csv into a list of per-block dicts."""
|
||||
rows = []
|
||||
@@ -43,11 +51,9 @@ def parse_combined_csv(path: str) -> list[dict]:
|
||||
"new_payload_latency_us": int(row["new_payload_latency"]),
|
||||
"fcu_latency_us": int(row["fcu_latency"]),
|
||||
"total_latency_us": int(row["total_latency"]),
|
||||
"persistence_wait_us": int(row["persistence_wait"])
|
||||
if row.get("persistence_wait")
|
||||
else None,
|
||||
"execution_cache_wait_us": int(row.get("execution_cache_wait", 0)),
|
||||
"sparse_trie_wait_us": int(row.get("sparse_trie_wait", 0)),
|
||||
"persistence_wait_us": _opt_int(row, "persistence_wait"),
|
||||
"execution_cache_wait_us": _opt_int(row, "execution_cache_wait"),
|
||||
"sparse_trie_wait_us": _opt_int(row, "sparse_trie_wait"),
|
||||
}
|
||||
)
|
||||
return rows
|
||||
@@ -112,26 +118,45 @@ def compute_stats(combined: list[dict]) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def compute_wait_stats(combined: list[dict], field: str) -> dict:
|
||||
"""Compute mean/p50/p95 for a wait time field (in ms)."""
|
||||
values_ms = []
|
||||
for r in combined:
|
||||
v = r.get(field)
|
||||
if v is not None:
|
||||
values_ms.append(v / 1_000)
|
||||
if not values_ms:
|
||||
return {}
|
||||
n = len(values_ms)
|
||||
mean_val = sum(values_ms) / n
|
||||
sorted_vals = sorted(values_ms)
|
||||
return {
|
||||
"mean_ms": mean_val,
|
||||
"p50_ms": percentile(sorted_vals, 50),
|
||||
"p95_ms": percentile(sorted_vals, 95),
|
||||
}
|
||||
|
||||
|
||||
def _paired_data(
|
||||
baseline: list[dict], branch: list[dict]
|
||||
baseline: list[dict], feature: list[dict]
|
||||
) -> tuple[list[tuple[float, float]], list[float], list[float]]:
|
||||
"""Match blocks and return paired latencies and per-block diffs.
|
||||
|
||||
Returns:
|
||||
pairs: list of (baseline_ms, branch_ms) tuples
|
||||
lat_diffs_ms: list of branch − baseline latency diffs in ms
|
||||
mgas_diffs: list of branch − baseline Mgas/s diffs
|
||||
pairs: list of (baseline_ms, feature_ms) tuples
|
||||
lat_diffs_ms: list of feature − baseline latency diffs in ms
|
||||
mgas_diffs: list of feature − baseline Mgas/s diffs
|
||||
"""
|
||||
baseline_by_block = {r["block_number"]: r for r in baseline}
|
||||
branch_by_block = {r["block_number"]: r for r in branch}
|
||||
common_blocks = sorted(set(baseline_by_block) & set(branch_by_block))
|
||||
feature_by_block = {r["block_number"]: r for r in feature}
|
||||
common_blocks = sorted(set(baseline_by_block) & set(feature_by_block))
|
||||
|
||||
pairs = []
|
||||
lat_diffs_ms = []
|
||||
mgas_diffs = []
|
||||
for bn in common_blocks:
|
||||
b = baseline_by_block[bn]
|
||||
f = branch_by_block[bn]
|
||||
f = feature_by_block[bn]
|
||||
b_ms = b["new_payload_latency_us"] / 1_000
|
||||
f_ms = f["new_payload_latency_us"] / 1_000
|
||||
pairs.append((b_ms, f_ms))
|
||||
@@ -148,21 +173,23 @@ def _paired_data(
|
||||
|
||||
def compute_paired_stats(
|
||||
baseline_runs: list[list[dict]],
|
||||
branch_runs: list[list[dict]],
|
||||
feature_runs: list[list[dict]],
|
||||
) -> dict:
|
||||
"""Compute paired statistics between baseline and branch runs.
|
||||
"""Compute paired statistics between baseline and feature runs.
|
||||
|
||||
Each pair (baseline_runs[i], branch_runs[i]) produces per-block diffs.
|
||||
Each pair (baseline_runs[i], feature_runs[i]) produces per-block diffs.
|
||||
All diffs are pooled for the final CI.
|
||||
"""
|
||||
all_pairs = []
|
||||
all_lat_diffs = []
|
||||
all_mgas_diffs = []
|
||||
for baseline, branch in zip(baseline_runs, branch_runs):
|
||||
pairs, lat_diffs, mgas_diffs = _paired_data(baseline, branch)
|
||||
blocks_per_pair = []
|
||||
for baseline, feature in zip(baseline_runs, feature_runs):
|
||||
pairs, lat_diffs, mgas_diffs = _paired_data(baseline, feature)
|
||||
all_pairs.extend(pairs)
|
||||
all_lat_diffs.extend(lat_diffs)
|
||||
all_mgas_diffs.extend(mgas_diffs)
|
||||
blocks_per_pair.append(len(pairs))
|
||||
|
||||
if not all_lat_diffs:
|
||||
return {}
|
||||
@@ -175,10 +202,10 @@ def compute_paired_stats(
|
||||
|
||||
# Bootstrap CI on difference-of-percentiles (resample paired blocks)
|
||||
base_lats = sorted([p[0] for p in all_pairs])
|
||||
branch_lats = sorted([p[1] for p in all_pairs])
|
||||
p50_diff = percentile(branch_lats, 50) - percentile(base_lats, 50)
|
||||
p90_diff = percentile(branch_lats, 90) - percentile(base_lats, 90)
|
||||
p99_diff = percentile(branch_lats, 99) - percentile(base_lats, 99)
|
||||
feature_lats = sorted([p[1] for p in all_pairs])
|
||||
p50_diff = percentile(feature_lats, 50) - percentile(base_lats, 50)
|
||||
p90_diff = percentile(feature_lats, 90) - percentile(base_lats, 90)
|
||||
p99_diff = percentile(feature_lats, 99) - percentile(base_lats, 99)
|
||||
|
||||
rng = random.Random(42)
|
||||
p50_boot, p90_boot, p99_boot = [], [], []
|
||||
@@ -212,6 +239,7 @@ def compute_paired_stats(
|
||||
"p99_ci_ms": (p99_boot[hi] - p99_boot[lo]) / 2,
|
||||
"mean_mgas_diff": mean_mgas_diff,
|
||||
"mgas_ci": mgas_ci,
|
||||
"blocks": max(blocks_per_pair),
|
||||
}
|
||||
|
||||
|
||||
@@ -268,11 +296,12 @@ def generate_comparison_table(
|
||||
paired: dict,
|
||||
repo: str,
|
||||
baseline_ref: str,
|
||||
branch_name: str,
|
||||
branch_sha: str,
|
||||
baseline_name: str,
|
||||
feature_name: str,
|
||||
feature_sha: str,
|
||||
) -> str:
|
||||
"""Generate a markdown comparison table between baseline (main) and branch."""
|
||||
n = paired["n"]
|
||||
"""Generate a markdown comparison table between baseline and feature."""
|
||||
n = paired["blocks"]
|
||||
|
||||
def pct(base: float, feat: float) -> float:
|
||||
return (feat - base) / base * 100.0 if base > 0 else 0.0
|
||||
@@ -294,11 +323,11 @@ def generate_comparison_table(
|
||||
mgas_ci_pct = paired["mgas_ci"] / run1["mean_mgas_s"] * 100.0 if run1["mean_mgas_s"] > 0 else 0.0
|
||||
|
||||
base_url = f"https://github.com/{repo}/commit"
|
||||
baseline_label = f"[`main`]({base_url}/{baseline_ref})"
|
||||
branch_label = f"[`{branch_name}`]({base_url}/{branch_sha})"
|
||||
baseline_label = f"[`{baseline_name}`]({base_url}/{baseline_ref})"
|
||||
feature_label = f"[`{feature_name}`]({base_url}/{feature_sha})"
|
||||
|
||||
lines = [
|
||||
f"| Metric | {baseline_label} | {branch_label} | Change |",
|
||||
f"| Metric | {baseline_label} | {feature_label} | Change |",
|
||||
"|--------|------|--------|--------|",
|
||||
f"| Mean | {fmt_ms(run1['mean_ms'])} | {fmt_ms(run2['mean_ms'])} | {change_str(mean_pct, lat_ci_pct, lower_is_better=True)} |",
|
||||
f"| StdDev | {fmt_ms(run1['stddev_ms'])} | {fmt_ms(run2['stddev_ms'])} | |",
|
||||
@@ -312,17 +341,51 @@ def generate_comparison_table(
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_wait_time_table(
|
||||
title: str,
|
||||
baseline_stats: dict,
|
||||
feature_stats: dict,
|
||||
baseline_label: str,
|
||||
feature_label: str,
|
||||
) -> str:
|
||||
"""Generate a markdown table for a wait time metric."""
|
||||
if not baseline_stats or not feature_stats:
|
||||
return ""
|
||||
lines = [
|
||||
f"### {title}",
|
||||
"",
|
||||
f"| Metric | {baseline_label} | {feature_label} |",
|
||||
"|--------|------|--------|",
|
||||
f"| Mean | {fmt_ms(baseline_stats['mean_ms'])} | {fmt_ms(feature_stats['mean_ms'])} |",
|
||||
f"| P50 | {fmt_ms(baseline_stats['p50_ms'])} | {fmt_ms(feature_stats['p50_ms'])} |",
|
||||
f"| P95 | {fmt_ms(baseline_stats['p95_ms'])} | {fmt_ms(feature_stats['p95_ms'])} |",
|
||||
]
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_markdown(
|
||||
summary: dict, comparison_table: str,
|
||||
behind_main: int = 0, repo: str = "", baseline_ref: str = "",
|
||||
wait_time_tables: list[str] | None = None,
|
||||
behind_baseline: int = 0, repo: str = "", baseline_ref: str = "", baseline_name: str = "",
|
||||
) -> str:
|
||||
"""Generate a markdown comment body."""
|
||||
lines = ["## Benchmark Results", "", comparison_table]
|
||||
if behind_main > 0:
|
||||
s = "s" if behind_main > 1 else ""
|
||||
diff_link = f"https://github.com/{repo}/compare/{baseline_ref[:12]}...main"
|
||||
lines = ["## Benchmark Results", ""]
|
||||
if behind_baseline > 0:
|
||||
s = "s" if behind_baseline > 1 else ""
|
||||
diff_link = f"https://github.com/{repo}/compare/{baseline_ref[:12]}...{baseline_name}"
|
||||
lines.append(f"> ⚠️ Feature is [**{behind_baseline} commit{s} behind `{baseline_name}`**]({diff_link}). Consider rebasing for accurate results.")
|
||||
lines.append("")
|
||||
lines.append(f"> ⚠️ Branch is [**{behind_main} commit{s} behind `main`**]({diff_link}). Consider rebasing for accurate results.")
|
||||
lines.append(comparison_table)
|
||||
if wait_time_tables:
|
||||
lines.append("")
|
||||
lines.append("<details>")
|
||||
lines.append("<summary>Wait Time Breakdown</summary>")
|
||||
lines.append("")
|
||||
for table in wait_time_tables:
|
||||
if table:
|
||||
lines.append(table)
|
||||
lines.append("")
|
||||
lines.append("</details>")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
@@ -333,8 +396,8 @@ def main():
|
||||
help="Baseline combined_latency.csv files (A1, A2)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--branch-csv", nargs="+", required=True,
|
||||
help="Branch combined_latency.csv files (B1, B2)",
|
||||
"--feature-csv", "--branch-csv", nargs="+", required=True,
|
||||
help="Feature combined_latency.csv files (B1, B2)",
|
||||
)
|
||||
parser.add_argument("--gas-csv", required=True, help="Path to total_gas.csv")
|
||||
parser.add_argument(
|
||||
@@ -345,65 +408,91 @@ def main():
|
||||
"--repo", default="paradigmxyz/reth", help="GitHub repo (owner/name)"
|
||||
)
|
||||
parser.add_argument("--baseline-ref", default=None, help="Baseline commit SHA")
|
||||
parser.add_argument("--branch-name", default=None, help="Branch name")
|
||||
parser.add_argument("--branch-sha", default=None, help="Branch commit SHA")
|
||||
parser.add_argument("--behind-main", type=int, default=0, help="Commits behind main")
|
||||
parser.add_argument("--baseline-name", default=None, help="Baseline display name")
|
||||
parser.add_argument("--feature-name", "--branch-name", default=None, help="Feature branch name")
|
||||
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")
|
||||
args = parser.parse_args()
|
||||
|
||||
if len(args.baseline_csv) != len(args.branch_csv):
|
||||
print("Must provide equal number of baseline and branch CSVs", file=sys.stderr)
|
||||
if len(args.baseline_csv) != len(args.feature_csv):
|
||||
print("Must provide equal number of baseline and feature CSVs", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
baseline_runs = []
|
||||
branch_runs = []
|
||||
feature_runs = []
|
||||
for path in args.baseline_csv:
|
||||
data = parse_combined_csv(path)
|
||||
if not data:
|
||||
print(f"No results in {path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
baseline_runs.append(data)
|
||||
for path in args.branch_csv:
|
||||
for path in args.feature_csv:
|
||||
data = parse_combined_csv(path)
|
||||
if not data:
|
||||
print(f"No results in {path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
branch_runs.append(data)
|
||||
feature_runs.append(data)
|
||||
|
||||
gas = parse_gas_csv(args.gas_csv)
|
||||
|
||||
all_baseline = [r for run in baseline_runs for r in run]
|
||||
all_branch = [r for run in branch_runs for r in run]
|
||||
all_feature = [r for run in feature_runs for r in run]
|
||||
|
||||
summary = compute_summary(all_branch, gas)
|
||||
summary = compute_summary(all_feature, gas)
|
||||
with open(args.output_summary, "w") as f:
|
||||
json.dump(summary, f, indent=2)
|
||||
print(f"Summary written to {args.output_summary}")
|
||||
|
||||
baseline_stats = compute_stats(all_baseline)
|
||||
branch_stats = compute_stats(all_branch)
|
||||
paired_stats = compute_paired_stats(baseline_runs, branch_runs)
|
||||
feature_stats = compute_stats(all_feature)
|
||||
paired_stats = compute_paired_stats(baseline_runs, feature_runs)
|
||||
|
||||
if not paired_stats:
|
||||
print("No common blocks between baseline and branch runs", file=sys.stderr)
|
||||
print("No common blocks between baseline and feature runs", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
baseline_ref = args.baseline_ref or "main"
|
||||
baseline_name = args.baseline_name or "baseline"
|
||||
feature_name = args.feature_name or "feature"
|
||||
feature_sha = args.feature_ref or "unknown"
|
||||
|
||||
comparison_table = generate_comparison_table(
|
||||
baseline_stats,
|
||||
branch_stats,
|
||||
feature_stats,
|
||||
paired_stats,
|
||||
repo=args.repo,
|
||||
baseline_ref=args.baseline_ref or "main",
|
||||
branch_name=args.branch_name or "branch",
|
||||
branch_sha=args.branch_sha or "unknown",
|
||||
baseline_ref=baseline_ref,
|
||||
baseline_name=baseline_name,
|
||||
feature_name=feature_name,
|
||||
feature_sha=feature_sha,
|
||||
)
|
||||
print(f"Generated comparison ({paired_stats['n']} paired blocks, "
|
||||
f"mean diff {paired_stats['mean_diff_ms']:+.3f}ms ± {paired_stats['ci_ms']:.3f}ms)")
|
||||
|
||||
base_url = f"https://github.com/{args.repo}/commit"
|
||||
baseline_label = f"[`{baseline_name}`]({base_url}/{baseline_ref})"
|
||||
feature_label = f"[`{feature_name}`]({base_url}/{feature_sha})"
|
||||
|
||||
wait_fields = [
|
||||
("persistence_wait_us", "Persistence Wait"),
|
||||
("sparse_trie_wait_us", "Trie Cache Update Wait"),
|
||||
("execution_cache_wait_us", "Execution Cache Update Wait"),
|
||||
]
|
||||
wait_time_tables = []
|
||||
for field, title in wait_fields:
|
||||
b_stats = compute_wait_stats(all_baseline, field)
|
||||
f_stats = compute_wait_stats(all_feature, field)
|
||||
table = generate_wait_time_table(title, b_stats, f_stats, baseline_label, feature_label)
|
||||
if table:
|
||||
wait_time_tables.append(table)
|
||||
|
||||
markdown = generate_markdown(
|
||||
summary, comparison_table,
|
||||
behind_main=args.behind_main,
|
||||
wait_time_tables=wait_time_tables,
|
||||
behind_baseline=args.behind_baseline,
|
||||
repo=args.repo,
|
||||
baseline_ref=args.baseline_ref or "",
|
||||
baseline_ref=baseline_ref,
|
||||
baseline_name=baseline_name,
|
||||
)
|
||||
|
||||
with open(args.output_markdown, "w") as f:
|
||||
|
||||
27
.github/scripts/bench-update-status.js
vendored
Normal file
27
.github/scripts/bench-update-status.js
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
// Updates the reth-bench PR comment with current status.
|
||||
//
|
||||
// Reads from environment:
|
||||
// BENCH_COMMENT_ID – GitHub comment ID to update
|
||||
// BENCH_JOB_URL – URL to the Actions job page
|
||||
// BENCH_CONFIG – Config line (blocks, warmup, refs)
|
||||
// BENCH_ACTOR – User who triggered the benchmark
|
||||
//
|
||||
// Usage from actions/github-script:
|
||||
// const s = require('./.github/scripts/bench-update-status.js');
|
||||
// await s({github, context, status: 'Building baseline binary...'});
|
||||
|
||||
function buildBody(status) {
|
||||
return `cc @${process.env.BENCH_ACTOR}\n\n🚀 Benchmark started! [View job](${process.env.BENCH_JOB_URL})\n\n⏳ **Status:** ${status}\n\n${process.env.BENCH_CONFIG}`;
|
||||
}
|
||||
|
||||
async function updateStatus({ github, context, status }) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: parseInt(process.env.BENCH_COMMENT_ID),
|
||||
body: buildBody(status),
|
||||
});
|
||||
}
|
||||
|
||||
updateStatus.buildBody = buildBody;
|
||||
module.exports = updateStatus;
|
||||
28
.github/scripts/hive/expected_failures.yaml
vendored
28
.github/scripts/hive/expected_failures.yaml
vendored
@@ -59,10 +59,6 @@ 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
|
||||
@@ -71,32 +67,8 @@ 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/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
|
||||
|
||||
4
.github/scripts/hive/ignored_tests.yaml
vendored
4
.github/scripts/hive/ignored_tests.yaml
vendored
@@ -16,12 +16,16 @@ engine-withdrawals:
|
||||
- Withdrawals Fork on Block 1 - 8 Block Re-Org NewPayload (Paris) (reth)
|
||||
- Withdrawals Fork on Block 8 - 10 Block Re-Org NewPayload (Paris) (reth)
|
||||
- Withdrawals Fork on Canonical Block 8 / Side Block 7 - 10 Block Re-Org (Paris) (reth)
|
||||
- Sync after 2 blocks - Withdrawals on Block 2 - Multiple Withdrawal Accounts (Paris) (reth)
|
||||
- Sync after 2 blocks - Withdrawals on Block 2 - Multiple Withdrawal Accounts - No Transactions (Paris) (reth)
|
||||
- Sync after 128 blocks - Withdrawals on Block 2 - Multiple Withdrawal Accounts (Paris) (reth)
|
||||
engine-cancun:
|
||||
- Transaction Re-Org, New Payload on Revert Back (Cancun) (reth)
|
||||
- Transaction Re-Org, Re-Org to Different Block (Cancun) (reth)
|
||||
- Transaction Re-Org, Re-Org Out (Cancun) (reth)
|
||||
- Invalid Missing Ancestor ReOrg, StateRoot, EmptyTxs=False, Invalid P9 (Cancun) (reth)
|
||||
- Invalid Missing Ancestor Syncing ReOrg, Timestamp, EmptyTxs=False, CanonicalReOrg=False, Invalid P8 (Cancun) (reth)
|
||||
- Invalid Missing Ancestor Syncing ReOrg, Timestamp, EmptyTxs=False, CanonicalReOrg=True, Invalid P8 (Cancun) (reth)
|
||||
- Multiple New Payloads Extending Canonical Chain, Wait for Canonical Payload (Cancun) (reth)
|
||||
engine-api:
|
||||
- Transaction Re-Org, Re-Org Out (Paris) (reth)
|
||||
|
||||
8
.github/scripts/hive/run_simulator.sh
vendored
8
.github/scripts/hive/run_simulator.sh
vendored
@@ -6,8 +6,14 @@ cd hivetests/
|
||||
sim="${1}"
|
||||
limit="${2}"
|
||||
|
||||
# Use lower parallelism for eels tests to avoid OOM-killing the runner
|
||||
parallelism=16
|
||||
if [[ "${sim}" == *"eels"* ]]; then
|
||||
parallelism=4
|
||||
fi
|
||||
|
||||
run_hive() {
|
||||
hive --sim "${sim}" --sim.limit "${limit}" --sim.parallelism 16 --client reth 2>&1 | tee /tmp/log || true
|
||||
hive --sim "${sim}" --sim.limit "${limit}" --sim.parallelism "${parallelism}" --client reth 2>&1 | tee /tmp/log || true
|
||||
}
|
||||
|
||||
check_log() {
|
||||
|
||||
674
.github/workflows/bench.yml
vendored
674
.github/workflows/bench.yml
vendored
@@ -3,7 +3,7 @@
|
||||
# The reth-bench job replays real blocks via the Engine API against a reth node
|
||||
# backed by a local snapshot managed with schelk.
|
||||
#
|
||||
# It runs the main (baseline) binary and the branch (candidate) binary on the
|
||||
# It runs the baseline binary and the feature (candidate) binary on the
|
||||
# same block range (snapshot recovered between runs) to compare performance.
|
||||
|
||||
on:
|
||||
@@ -18,7 +18,22 @@ on:
|
||||
blocks:
|
||||
description: "Number of blocks to benchmark"
|
||||
required: false
|
||||
default: "50"
|
||||
default: "500"
|
||||
type: string
|
||||
warmup:
|
||||
description: "Number of warmup blocks"
|
||||
required: false
|
||||
default: "100"
|
||||
type: string
|
||||
baseline:
|
||||
description: "Baseline git ref (default: merge-base)"
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
feature:
|
||||
description: "Feature git ref (default: branch head)"
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
|
||||
env:
|
||||
@@ -33,14 +48,13 @@ permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: bench-${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
codspeed:
|
||||
if: github.event_name != 'issue_comment'
|
||||
if: github.event_name == 'push'
|
||||
runs-on: depot-ubuntu-latest
|
||||
concurrency:
|
||||
group: bench-codspeed-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
strategy:
|
||||
matrix:
|
||||
partition: [1, 2]
|
||||
@@ -75,17 +89,26 @@ jobs:
|
||||
mode: instrumentation
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
||||
reth-bench:
|
||||
if: github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, 'derek bench')
|
||||
name: reth-bench
|
||||
runs-on: [self-hosted, Linux, X64]
|
||||
timeout-minutes: 120
|
||||
env:
|
||||
BENCH_RPC_URL: https://ethereum.reth.rs/rpc
|
||||
SCHELK_MOUNT: /reth-bench
|
||||
reth-bench-ack:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, 'derek bench')) ||
|
||||
github.event_name == 'workflow_dispatch'
|
||||
name: reth-bench-ack
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
pr: ${{ steps.args.outputs.pr }}
|
||||
actor: ${{ steps.args.outputs.actor }}
|
||||
blocks: ${{ steps.args.outputs.blocks }}
|
||||
warmup: ${{ steps.args.outputs.warmup }}
|
||||
baseline: ${{ steps.args.outputs.baseline }}
|
||||
feature: ${{ steps.args.outputs.feature }}
|
||||
baseline-name: ${{ steps.args.outputs.baseline-name }}
|
||||
feature-name: ${{ steps.args.outputs.feature-name }}
|
||||
comment-id: ${{ steps.ack.outputs.comment-id }}
|
||||
steps:
|
||||
- name: Check org membership
|
||||
uses: actions/github-script@v7
|
||||
if: github.event_name == 'issue_comment'
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
@@ -104,77 +127,307 @@ jobs:
|
||||
|
||||
- name: Parse arguments
|
||||
id: args
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const body = context.payload.comment.body.trim();
|
||||
const known = new Set(['blocks', 'warmup']);
|
||||
const defaults = { blocks: '500', warmup: '100' };
|
||||
const unknown = [];
|
||||
const invalid = [];
|
||||
const args = body.replace(/^derek bench\s*/, '');
|
||||
for (const part of args.split(/\s+/).filter(Boolean)) {
|
||||
const eq = part.indexOf('=');
|
||||
if (eq === -1) {
|
||||
unknown.push(part);
|
||||
continue;
|
||||
}
|
||||
const key = part.slice(0, eq);
|
||||
const value = part.slice(eq + 1);
|
||||
if (!known.has(key)) {
|
||||
unknown.push(key);
|
||||
} else if (!/^\d+$/.test(value)) {
|
||||
invalid.push(`\`${key}=${value}\` (must be a positive integer)`);
|
||||
} else {
|
||||
defaults[key] = value;
|
||||
}
|
||||
}
|
||||
const errors = [];
|
||||
if (unknown.length) errors.push(`Unknown argument(s): \`${unknown.join('`, `')}\``);
|
||||
if (invalid.length) errors.push(`Invalid value(s): ${invalid.join(', ')}`);
|
||||
if (errors.length) {
|
||||
const msg = `❌ **Invalid bench command**\n\n${errors.join('\n')}\n\n**Usage:** \`derek bench [blocks=N] [warmup=N]\``;
|
||||
await github.rest.issues.createComment({
|
||||
let pr, actor, blocks, warmup, baseline, feature;
|
||||
|
||||
if (context.eventName === 'workflow_dispatch') {
|
||||
actor = '${{ github.actor }}';
|
||||
blocks = '${{ github.event.inputs.blocks }}' || '500';
|
||||
warmup = '${{ github.event.inputs.warmup }}' || '100';
|
||||
baseline = '${{ github.event.inputs.baseline }}';
|
||||
feature = '${{ github.event.inputs.feature }}';
|
||||
|
||||
// Find PR for the selected branch
|
||||
const branch = '${{ github.ref_name }}';
|
||||
const { data: prs } = await github.rest.pulls.list({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: msg,
|
||||
head: `${context.repo.owner}:${branch}`,
|
||||
state: 'open',
|
||||
per_page: 1,
|
||||
});
|
||||
core.setFailed(msg);
|
||||
return;
|
||||
pr = prs.length ? String(prs[0].number) : '';
|
||||
if (!pr) {
|
||||
core.info(`No open PR found for branch '${branch}', results will be in job summary`);
|
||||
}
|
||||
} else {
|
||||
pr = String(context.issue.number);
|
||||
actor = context.payload.comment.user.login;
|
||||
|
||||
const body = context.payload.comment.body.trim();
|
||||
const intArgs = new Set(['blocks', 'warmup']);
|
||||
const refArgs = new Set(['baseline', 'feature']);
|
||||
const defaults = { blocks: '500', warmup: '100', baseline: '', feature: '' };
|
||||
const unknown = [];
|
||||
const invalid = [];
|
||||
const args = body.replace(/^derek bench\s*/, '');
|
||||
for (const part of args.split(/\s+/).filter(Boolean)) {
|
||||
const eq = part.indexOf('=');
|
||||
if (eq === -1) {
|
||||
unknown.push(part);
|
||||
continue;
|
||||
}
|
||||
const key = part.slice(0, eq);
|
||||
const value = part.slice(eq + 1);
|
||||
if (intArgs.has(key)) {
|
||||
if (!/^\d+$/.test(value)) {
|
||||
invalid.push(`\`${key}=${value}\` (must be a positive integer)`);
|
||||
} else {
|
||||
defaults[key] = value;
|
||||
}
|
||||
} else if (refArgs.has(key)) {
|
||||
if (!value) {
|
||||
invalid.push(`\`${key}=\` (must be a git ref)`);
|
||||
} else {
|
||||
defaults[key] = value;
|
||||
}
|
||||
} else {
|
||||
unknown.push(key);
|
||||
}
|
||||
}
|
||||
const errors = [];
|
||||
if (unknown.length) errors.push(`Unknown argument(s): \`${unknown.join('`, `')}\``);
|
||||
if (invalid.length) errors.push(`Invalid value(s): ${invalid.join(', ')}`);
|
||||
if (errors.length) {
|
||||
const msg = `❌ **Invalid bench command**\n\n${errors.join('\n')}\n\n**Usage:** \`derek bench [blocks=N] [warmup=N] [baseline=REF] [feature=REF]\``;
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: msg,
|
||||
});
|
||||
core.setFailed(msg);
|
||||
return;
|
||||
}
|
||||
blocks = defaults.blocks;
|
||||
warmup = defaults.warmup;
|
||||
baseline = defaults.baseline;
|
||||
feature = defaults.feature;
|
||||
}
|
||||
core.setOutput('blocks', defaults.blocks);
|
||||
core.setOutput('warmup', defaults.warmup);
|
||||
core.exportVariable('BENCH_BLOCKS', defaults.blocks);
|
||||
core.exportVariable('BENCH_WARMUP_BLOCKS', defaults.warmup);
|
||||
|
||||
// Resolve display names for baseline/feature
|
||||
let baselineName = baseline || 'main';
|
||||
let featureName = feature;
|
||||
if (!featureName) {
|
||||
if (pr) {
|
||||
const { data: prData } = await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: parseInt(pr),
|
||||
});
|
||||
featureName = prData.head.ref;
|
||||
} else {
|
||||
featureName = '${{ github.ref_name }}';
|
||||
}
|
||||
}
|
||||
|
||||
core.setOutput('pr', pr || '');
|
||||
core.setOutput('actor', actor);
|
||||
core.setOutput('blocks', blocks);
|
||||
core.setOutput('warmup', warmup);
|
||||
core.setOutput('baseline', baseline);
|
||||
core.setOutput('feature', feature);
|
||||
core.setOutput('baseline-name', baselineName);
|
||||
core.setOutput('feature-name', featureName);
|
||||
|
||||
- name: Acknowledge request
|
||||
id: ack
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
await github.rest.reactions.createForIssueComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: context.payload.comment.id,
|
||||
content: 'eyes',
|
||||
});
|
||||
if (context.eventName === 'issue_comment') {
|
||||
await github.rest.reactions.createForIssueComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: context.payload.comment.id,
|
||||
content: 'eyes',
|
||||
});
|
||||
}
|
||||
|
||||
const pr = '${{ steps.args.outputs.pr }}';
|
||||
if (!pr) return;
|
||||
|
||||
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
||||
|
||||
// Count queued/waiting bench runs ahead of this one
|
||||
let queueMsg = '';
|
||||
let ahead = 0;
|
||||
try {
|
||||
const statuses = ['queued', 'in_progress', 'waiting', 'requested', 'pending'];
|
||||
const allRuns = [];
|
||||
for (const status of statuses) {
|
||||
const { data: { workflow_runs: r } } = await github.rest.actions.listWorkflowRuns({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
workflow_id: 'bench.yml',
|
||||
status,
|
||||
per_page: 100,
|
||||
});
|
||||
allRuns.push(...r);
|
||||
}
|
||||
// Only count runs that trigger reth-bench (not push-triggered codspeed runs)
|
||||
const benchRuns = allRuns.filter(r => r.event === 'issue_comment' || r.event === 'workflow_dispatch');
|
||||
const thisRun = benchRuns.find(r => r.id === context.runId);
|
||||
const thisCreatedAt = thisRun ? new Date(thisRun.created_at) : new Date();
|
||||
ahead = benchRuns.filter(r => r.id !== context.runId && new Date(r.created_at) <= thisCreatedAt).length;
|
||||
if (ahead > 0) {
|
||||
queueMsg = `\n🔢 **Queue position:** \`#${ahead + 1}\` (${ahead} job(s) ahead)`;
|
||||
}
|
||||
} catch (e) {
|
||||
// Non-fatal — queue info is best-effort
|
||||
core.info(`Could not fetch queue info: ${e.message}`);
|
||||
}
|
||||
|
||||
const actor = '${{ steps.args.outputs.actor }}';
|
||||
const blocks = '${{ steps.args.outputs.blocks }}';
|
||||
const warmup = '${{ steps.args.outputs.warmup }}';
|
||||
const baseline = '${{ steps.args.outputs.baseline-name }}';
|
||||
const feature = '${{ steps.args.outputs.feature-name }}';
|
||||
const config = `**Config:** ${blocks} blocks, ${warmup} warmup blocks, baseline: \`${baseline}\`, feature: \`${feature}\``;
|
||||
|
||||
const { data: comment } = await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: `🚀 Benchmark started! [View run](${runUrl})\n\n⏳ **Status:** Building binaries...\n\n**Config:** ${blocks} blocks, ${warmup} warmup blocks`,
|
||||
issue_number: parseInt(pr),
|
||||
body: `cc @${actor}\n\n🚀 Benchmark queued! [View run](${runUrl})\n\n⏳ **Status:** Waiting for runner...${queueMsg}\n\n${config}`,
|
||||
});
|
||||
core.setOutput('comment-id', comment.id);
|
||||
core.setOutput('comment-id', String(comment.id));
|
||||
core.setOutput('queue-position', String(ahead || 0));
|
||||
|
||||
- name: Poll queue position
|
||||
if: steps.ack.outputs.comment-id && steps.ack.outputs.queue-position != '0'
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const pr = '${{ steps.args.outputs.pr }}';
|
||||
const commentId = parseInt('${{ steps.ack.outputs.comment-id }}');
|
||||
const actor = '${{ steps.args.outputs.actor }}';
|
||||
const blocks = '${{ steps.args.outputs.blocks }}';
|
||||
const warmup = '${{ steps.args.outputs.warmup }}';
|
||||
const baseline = '${{ steps.args.outputs.baseline-name }}';
|
||||
const feature = '${{ steps.args.outputs.feature-name }}';
|
||||
const config = `**Config:** ${blocks} blocks, ${warmup} warmup blocks, baseline: \`${baseline}\`, feature: \`${feature}\``;
|
||||
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
||||
|
||||
async function getQueuePosition() {
|
||||
const statuses = ['queued', 'in_progress', 'waiting', 'requested', 'pending'];
|
||||
const allRuns = [];
|
||||
for (const status of statuses) {
|
||||
const { data: { workflow_runs: r } } = await github.rest.actions.listWorkflowRuns({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
workflow_id: 'bench.yml',
|
||||
status,
|
||||
per_page: 100,
|
||||
});
|
||||
allRuns.push(...r);
|
||||
}
|
||||
const benchRuns = allRuns.filter(r => r.event === 'issue_comment' || r.event === 'workflow_dispatch');
|
||||
const thisRun = benchRuns.find(r => r.id === context.runId);
|
||||
const thisCreatedAt = thisRun ? new Date(thisRun.created_at) : new Date();
|
||||
return benchRuns.filter(r => r.id !== context.runId && new Date(r.created_at) <= thisCreatedAt).length;
|
||||
}
|
||||
|
||||
let lastPosition = parseInt('${{ steps.ack.outputs.queue-position }}');
|
||||
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
|
||||
while (true) {
|
||||
await sleep(10_000);
|
||||
try {
|
||||
const ahead = await getQueuePosition();
|
||||
if (ahead !== lastPosition) {
|
||||
lastPosition = ahead;
|
||||
const queueMsg = ahead > 0
|
||||
? `\n🔢 **Queue position:** \`#${ahead + 1}\` (${ahead} job(s) ahead)`
|
||||
: '';
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: commentId,
|
||||
body: `cc @${actor}\n\n🚀 Benchmark queued! [View run](${runUrl})\n\n⏳ **Status:** Waiting for runner...${queueMsg}\n\n${config}`,
|
||||
});
|
||||
}
|
||||
if (ahead === 0) break;
|
||||
} catch (e) {
|
||||
core.info(`Queue poll error: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
reth-bench:
|
||||
needs: reth-bench-ack
|
||||
name: reth-bench
|
||||
runs-on: [self-hosted, Linux, X64]
|
||||
timeout-minutes: 120
|
||||
concurrency:
|
||||
group: reth-bench-queue
|
||||
cancel-in-progress: false
|
||||
env:
|
||||
BENCH_RPC_URL: https://ethereum.reth.rs/rpc
|
||||
SCHELK_MOUNT: /reth-bench
|
||||
BENCH_WORK_DIR: ${{ github.workspace }}/bench-work
|
||||
BENCH_PR: ${{ needs.reth-bench-ack.outputs.pr }}
|
||||
BENCH_ACTOR: ${{ needs.reth-bench-ack.outputs.actor }}
|
||||
BENCH_BLOCKS: ${{ needs.reth-bench-ack.outputs.blocks }}
|
||||
BENCH_WARMUP_BLOCKS: ${{ needs.reth-bench-ack.outputs.warmup }}
|
||||
BENCH_COMMENT_ID: ${{ needs.reth-bench-ack.outputs.comment-id }}
|
||||
steps:
|
||||
- name: Resolve checkout ref
|
||||
id: checkout-ref
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
if (!process.env.BENCH_PR) {
|
||||
core.setOutput('ref', '${{ github.ref }}');
|
||||
return;
|
||||
}
|
||||
const { data: pr } = await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: parseInt(process.env.BENCH_PR),
|
||||
});
|
||||
// For closed/merged PRs, the merge ref doesn't exist — use head SHA
|
||||
if (pr.state !== 'open') {
|
||||
core.info(`PR #${process.env.BENCH_PR} is ${pr.state}, using head SHA ${pr.head.sha}`);
|
||||
core.setOutput('ref', pr.head.sha);
|
||||
} else {
|
||||
core.setOutput('ref', `refs/pull/${process.env.BENCH_PR}/merge`);
|
||||
}
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
ref: ${{ format('refs/pull/{0}/merge', github.event.issue.number) }}
|
||||
ref: ${{ steps.checkout-ref.outputs.ref }}
|
||||
|
||||
- name: Resolve job URL and update status
|
||||
if: env.BENCH_COMMENT_ID
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const { data: jobs } = await github.rest.actions.listJobsForWorkflowRun({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: context.runId,
|
||||
});
|
||||
const job = jobs.jobs.find(j => j.name === 'reth-bench');
|
||||
const jobUrl = job ? job.html_url : `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
||||
core.exportVariable('BENCH_JOB_URL', jobUrl);
|
||||
|
||||
const blocks = process.env.BENCH_BLOCKS;
|
||||
const warmup = process.env.BENCH_WARMUP_BLOCKS;
|
||||
const baseline = '${{ needs.reth-bench-ack.outputs.baseline-name }}';
|
||||
const feature = '${{ needs.reth-bench-ack.outputs.feature-name }}';
|
||||
core.exportVariable('BENCH_CONFIG', `**Config:** ${blocks} blocks, ${warmup} warmup blocks, baseline: \`${baseline}\`, feature: \`${feature}\``);
|
||||
|
||||
const { buildBody } = require('./.github/scripts/bench-update-status.js');
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: parseInt(process.env.BENCH_COMMENT_ID),
|
||||
body: buildBody('Building binaries...'),
|
||||
});
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
@@ -187,7 +440,7 @@ jobs:
|
||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||
missing=()
|
||||
for cmd in mc schelk cpupower taskset stdbuf python3 curl make uv; do
|
||||
for cmd in mc schelk cpupower taskset stdbuf python3 curl make uv pzstd jq; do
|
||||
command -v "$cmd" &>/dev/null || missing+=("$cmd")
|
||||
done
|
||||
if [ ${#missing[@]} -gt 0 ]; then
|
||||
@@ -197,14 +450,110 @@ jobs:
|
||||
echo "All dependencies found"
|
||||
|
||||
# Build binaries
|
||||
- name: Fetch or build main binaries
|
||||
- name: Resolve PR head branch
|
||||
id: pr-info
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
if (process.env.BENCH_PR) {
|
||||
const { data: pr } = await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: parseInt(process.env.BENCH_PR),
|
||||
});
|
||||
core.setOutput('head-ref', pr.head.ref);
|
||||
core.setOutput('head-sha', pr.head.sha);
|
||||
} else {
|
||||
core.setOutput('head-ref', '${{ github.ref_name }}');
|
||||
core.setOutput('head-sha', '${{ github.sha }}');
|
||||
}
|
||||
|
||||
- name: Resolve refs
|
||||
id: refs
|
||||
run: |
|
||||
MERGE_BASE=$(git merge-base HEAD origin/main 2>/dev/null || echo "${{ github.sha }}")
|
||||
.github/scripts/bench-reth-build.sh main "$MERGE_BASE"
|
||||
- name: Fetch or build branch binaries
|
||||
BASELINE_ARG="${{ needs.reth-bench-ack.outputs.baseline }}"
|
||||
FEATURE_ARG="${{ needs.reth-bench-ack.outputs.feature }}"
|
||||
|
||||
if [ -n "$BASELINE_ARG" ]; then
|
||||
git fetch origin "$BASELINE_ARG" --quiet 2>/dev/null || true
|
||||
BASELINE_REF=$(git rev-parse "$BASELINE_ARG" 2>/dev/null || git rev-parse "origin/$BASELINE_ARG" 2>/dev/null)
|
||||
BASELINE_NAME="$BASELINE_ARG"
|
||||
else
|
||||
BASELINE_REF=$(git merge-base HEAD origin/main 2>/dev/null || echo "${{ github.sha }}")
|
||||
BASELINE_NAME="main"
|
||||
fi
|
||||
|
||||
if [ -n "$FEATURE_ARG" ]; then
|
||||
git fetch origin "$FEATURE_ARG" --quiet 2>/dev/null || true
|
||||
FEATURE_REF=$(git rev-parse "$FEATURE_ARG" 2>/dev/null || git rev-parse "origin/$FEATURE_ARG" 2>/dev/null)
|
||||
FEATURE_NAME="$FEATURE_ARG"
|
||||
else
|
||||
FEATURE_REF="${{ steps.pr-info.outputs.head-sha }}"
|
||||
FEATURE_NAME="${{ steps.pr-info.outputs.head-ref }}"
|
||||
fi
|
||||
|
||||
echo "baseline-ref=$BASELINE_REF" >> "$GITHUB_OUTPUT"
|
||||
echo "baseline-name=$BASELINE_NAME" >> "$GITHUB_OUTPUT"
|
||||
echo "feature-ref=$FEATURE_REF" >> "$GITHUB_OUTPUT"
|
||||
echo "feature-name=$FEATURE_NAME" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Check if snapshot needs update
|
||||
id: snapshot-check
|
||||
run: |
|
||||
BRANCH_SHA="${{ github.sha }}"
|
||||
.github/scripts/bench-reth-build.sh branch "$BRANCH_SHA"
|
||||
if .github/scripts/bench-reth-snapshot.sh --check; then
|
||||
echo "needed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "needed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Update status (snapshot needed)
|
||||
if: env.BENCH_COMMENT_ID && steps.snapshot-check.outputs.needed == 'true'
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const s = require('./.github/scripts/bench-update-status.js');
|
||||
await s({github, context, status: 'Building binaries & downloading snapshot...'});
|
||||
|
||||
- name: Prepare source dirs
|
||||
run: |
|
||||
BASELINE_REF="${{ steps.refs.outputs.baseline-ref }}"
|
||||
if [ -d ../reth-baseline ]; then
|
||||
git -C ../reth-baseline fetch origin "$BASELINE_REF"
|
||||
else
|
||||
git clone . ../reth-baseline
|
||||
fi
|
||||
git -C ../reth-baseline checkout "$BASELINE_REF"
|
||||
ln -sfn "$(pwd)" ../reth-feature
|
||||
|
||||
- name: Build binaries and download snapshot in parallel
|
||||
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)"
|
||||
|
||||
.github/scripts/bench-reth-build.sh baseline "${BASELINE_DIR}" "${{ steps.refs.outputs.baseline-ref }}" &
|
||||
PID_BASELINE=$!
|
||||
.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)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# System tuning for reproducible benchmarks
|
||||
- name: System setup
|
||||
@@ -248,57 +597,70 @@ jobs:
|
||||
# Clean up any leftover state
|
||||
- name: Pre-flight cleanup
|
||||
run: |
|
||||
pkill -9 reth || true
|
||||
mountpoint -q "$SCHELK_MOUNT" && sudo schelk recover -y || true
|
||||
sudo pkill -9 reth || true
|
||||
sleep 1
|
||||
if mountpoint -q "$SCHELK_MOUNT"; then
|
||||
sudo umount -l "$SCHELK_MOUNT" || true
|
||||
sudo schelk recover -y || true
|
||||
fi
|
||||
rm -rf "$BENCH_WORK_DIR"
|
||||
mkdir -p "$BENCH_WORK_DIR"
|
||||
|
||||
- name: Update status (running benchmarks)
|
||||
if: steps.ack.outputs.comment-id
|
||||
uses: actions/github-script@v7
|
||||
if: success() && env.BENCH_COMMENT_ID
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: ${{ steps.ack.outputs.comment-id || 0 }},
|
||||
body: `🚀 Benchmark started! [View run](${runUrl})\n\n⏳ **Status:** Running benchmarks (2 runs)...`,
|
||||
});
|
||||
const s = require('./.github/scripts/bench-update-status.js');
|
||||
await s({github, context, status: 'Running benchmarks...'});
|
||||
|
||||
- name: "Run benchmark: baseline"
|
||||
run: taskset -c 0 .github/scripts/bench-reth-run.sh baseline target/profiling-baseline/reth /tmp/bench-results-baseline
|
||||
# Interleaved run order (B-F-F-B) to reduce systematic bias from
|
||||
# thermal drift and cache warming.
|
||||
- name: "Run benchmark: baseline (1/2)"
|
||||
id: run-baseline-1
|
||||
run: taskset -c 0 .github/scripts/bench-reth-run.sh baseline ../reth-baseline/target/profiling/reth "$BENCH_WORK_DIR/baseline-1"
|
||||
|
||||
- name: "Run benchmark: branch"
|
||||
run: taskset -c 0 .github/scripts/bench-reth-run.sh branch target/profiling/reth /tmp/bench-results-branch
|
||||
- name: "Run benchmark: feature (1/2)"
|
||||
id: run-feature-1
|
||||
run: taskset -c 0 .github/scripts/bench-reth-run.sh feature ../reth-feature/target/profiling/reth "$BENCH_WORK_DIR/feature-1"
|
||||
|
||||
- name: "Run benchmark: feature (2/2)"
|
||||
id: run-feature-2
|
||||
run: taskset -c 0 .github/scripts/bench-reth-run.sh feature ../reth-feature/target/profiling/reth "$BENCH_WORK_DIR/feature-2"
|
||||
|
||||
- name: "Run benchmark: baseline (2/2)"
|
||||
id: run-baseline-2
|
||||
run: taskset -c 0 .github/scripts/bench-reth-run.sh baseline ../reth-baseline/target/profiling/reth "$BENCH_WORK_DIR/baseline-2"
|
||||
|
||||
# Results & charts
|
||||
- name: Parse results
|
||||
id: results
|
||||
if: success()
|
||||
env:
|
||||
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||
BRANCH_SHA: ${{ github.sha }}
|
||||
BASELINE_REF: ${{ steps.refs.outputs.baseline-ref }}
|
||||
BASELINE_NAME: ${{ steps.refs.outputs.baseline-name }}
|
||||
FEATURE_NAME: ${{ steps.refs.outputs.feature-name }}
|
||||
FEATURE_REF: ${{ steps.refs.outputs.feature-ref }}
|
||||
run: |
|
||||
git fetch origin main --quiet
|
||||
# Use the actual PR head commit, not HEAD (which is the merge commit
|
||||
# refs/pull/N/merge and always has origin/main as a parent).
|
||||
MERGE_BASE=$(git merge-base "${BRANCH_SHA}" origin/main 2>/dev/null || echo "${{ github.sha }}")
|
||||
MAIN_HEAD=$(git rev-parse origin/main 2>/dev/null || echo "")
|
||||
BEHIND_MAIN=0
|
||||
if [ -n "$MAIN_HEAD" ] && [ "$MERGE_BASE" != "$MAIN_HEAD" ]; then
|
||||
BEHIND_MAIN=$(git rev-list --count "${MERGE_BASE}..${MAIN_HEAD}" 2>/dev/null || echo "0")
|
||||
git fetch origin "${BASELINE_NAME}" --quiet 2>/dev/null || true
|
||||
BASELINE_HEAD=$(git rev-parse "origin/${BASELINE_NAME}" 2>/dev/null || echo "")
|
||||
BEHIND_BASELINE=0
|
||||
if [ -n "$BASELINE_HEAD" ] && [ "$BASELINE_REF" != "$BASELINE_HEAD" ]; then
|
||||
BEHIND_BASELINE=$(git rev-list --count "${BASELINE_REF}..${BASELINE_HEAD}" 2>/dev/null || echo "0")
|
||||
fi
|
||||
|
||||
SUMMARY_ARGS="--output-summary /tmp/bench-summary.json"
|
||||
SUMMARY_ARGS="$SUMMARY_ARGS --output-markdown /tmp/bench-comment.md"
|
||||
SUMMARY_ARGS="--output-summary $BENCH_WORK_DIR/summary.json"
|
||||
SUMMARY_ARGS="$SUMMARY_ARGS --output-markdown $BENCH_WORK_DIR/comment.md"
|
||||
SUMMARY_ARGS="$SUMMARY_ARGS --repo ${{ github.repository }}"
|
||||
SUMMARY_ARGS="$SUMMARY_ARGS --baseline-ref ${MERGE_BASE}"
|
||||
SUMMARY_ARGS="$SUMMARY_ARGS --branch-name ${BRANCH_NAME}"
|
||||
SUMMARY_ARGS="$SUMMARY_ARGS --branch-sha ${BRANCH_SHA}"
|
||||
SUMMARY_ARGS="$SUMMARY_ARGS --baseline-csv /tmp/bench-results-baseline/combined_latency.csv"
|
||||
SUMMARY_ARGS="$SUMMARY_ARGS --branch-csv /tmp/bench-results-branch/combined_latency.csv"
|
||||
SUMMARY_ARGS="$SUMMARY_ARGS --gas-csv /tmp/bench-results-branch/total_gas.csv"
|
||||
if [ "$BEHIND_MAIN" -gt 0 ]; then
|
||||
SUMMARY_ARGS="$SUMMARY_ARGS --behind-main $BEHIND_MAIN"
|
||||
SUMMARY_ARGS="$SUMMARY_ARGS --baseline-ref ${BASELINE_REF}"
|
||||
SUMMARY_ARGS="$SUMMARY_ARGS --baseline-name ${BASELINE_NAME}"
|
||||
SUMMARY_ARGS="$SUMMARY_ARGS --feature-name ${FEATURE_NAME}"
|
||||
SUMMARY_ARGS="$SUMMARY_ARGS --feature-ref ${FEATURE_REF}"
|
||||
SUMMARY_ARGS="$SUMMARY_ARGS --baseline-csv $BENCH_WORK_DIR/baseline-1/combined_latency.csv $BENCH_WORK_DIR/baseline-2/combined_latency.csv"
|
||||
SUMMARY_ARGS="$SUMMARY_ARGS --feature-csv $BENCH_WORK_DIR/feature-1/combined_latency.csv $BENCH_WORK_DIR/feature-2/combined_latency.csv"
|
||||
SUMMARY_ARGS="$SUMMARY_ARGS --gas-csv $BENCH_WORK_DIR/feature-1/total_gas.csv"
|
||||
if [ "$BEHIND_BASELINE" -gt 0 ]; then
|
||||
SUMMARY_ARGS="$SUMMARY_ARGS --behind-baseline $BEHIND_BASELINE"
|
||||
fi
|
||||
# shellcheck disable=SC2086
|
||||
python3 .github/scripts/bench-reth-summary.py $SUMMARY_ARGS
|
||||
@@ -306,30 +668,29 @@ jobs:
|
||||
- name: Generate charts
|
||||
if: success()
|
||||
env:
|
||||
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||
BASELINE_NAME: ${{ steps.refs.outputs.baseline-name }}
|
||||
FEATURE_NAME: ${{ steps.refs.outputs.feature-name }}
|
||||
run: |
|
||||
CHART_ARGS="/tmp/bench-results-branch/combined_latency.csv --output-dir /tmp/bench-charts"
|
||||
CHART_ARGS="$CHART_ARGS --baseline /tmp/bench-results-baseline/combined_latency.csv"
|
||||
CHART_ARGS="$CHART_ARGS --branch-name ${BRANCH_NAME}"
|
||||
CHART_ARGS="--output-dir $BENCH_WORK_DIR/charts"
|
||||
CHART_ARGS="$CHART_ARGS --feature $BENCH_WORK_DIR/feature-1/combined_latency.csv $BENCH_WORK_DIR/feature-2/combined_latency.csv"
|
||||
CHART_ARGS="$CHART_ARGS --baseline $BENCH_WORK_DIR/baseline-1/combined_latency.csv $BENCH_WORK_DIR/baseline-2/combined_latency.csv"
|
||||
CHART_ARGS="$CHART_ARGS --baseline-name ${BASELINE_NAME}"
|
||||
CHART_ARGS="$CHART_ARGS --feature-name ${FEATURE_NAME}"
|
||||
# shellcheck disable=SC2086
|
||||
uv run --with matplotlib python3 .github/scripts/bench-reth-charts.py $CHART_ARGS
|
||||
|
||||
- name: Upload results
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: bench-reth-results
|
||||
path: |
|
||||
/tmp/bench-results-baseline/
|
||||
/tmp/bench-results-branch/
|
||||
/tmp/bench-summary.json
|
||||
/tmp/bench-charts/
|
||||
path: ${{ env.BENCH_WORK_DIR }}
|
||||
|
||||
- name: Push charts
|
||||
id: push-charts
|
||||
if: success()
|
||||
if: success() && env.BENCH_PR
|
||||
run: |
|
||||
PR_NUMBER=${{ github.event.issue.number }}
|
||||
PR_NUMBER=${{ env.BENCH_PR }}
|
||||
RUN_ID=${{ github.run_id }}
|
||||
CHART_DIR="pr/${PR_NUMBER}/${RUN_ID}"
|
||||
|
||||
@@ -341,7 +702,7 @@ jobs:
|
||||
fi
|
||||
|
||||
mkdir -p "${CHART_DIR}"
|
||||
cp /tmp/bench-charts/*.png "${CHART_DIR}/"
|
||||
cp "$BENCH_WORK_DIR"/charts/*.png "${CHART_DIR}/"
|
||||
git add "${CHART_DIR}"
|
||||
git -c user.name="github-actions" -c user.email="github-actions@github.com" \
|
||||
commit -m "bench charts for PR #${PR_NUMBER} run ${RUN_ID}"
|
||||
@@ -350,42 +711,41 @@ jobs:
|
||||
|
||||
- name: Compare & comment
|
||||
if: success()
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
let comment = '';
|
||||
try {
|
||||
comment = fs.readFileSync('/tmp/bench-comment.md', 'utf8');
|
||||
comment = fs.readFileSync(process.env.BENCH_WORK_DIR + '/comment.md', 'utf8');
|
||||
} catch (e) {
|
||||
comment = '⚠️ Engine benchmark completed but failed to generate comparison.';
|
||||
}
|
||||
|
||||
const sha = '${{ steps.push-charts.outputs.sha }}';
|
||||
const prNumber = context.issue.number;
|
||||
const prNumber = process.env.BENCH_PR;
|
||||
const runId = '${{ github.run_id }}';
|
||||
const baseUrl = `https://raw.githubusercontent.com/${context.repo.owner}/${context.repo.repo}/${sha}/pr/${prNumber}/${runId}`;
|
||||
|
||||
const charts = [
|
||||
{ file: 'latency_throughput.png', label: 'Latency, Throughput & Diff' },
|
||||
{ file: 'wait_breakdown.png', label: 'Wait Time Breakdown' },
|
||||
{ file: 'gas_vs_latency.png', label: 'Gas vs Latency' },
|
||||
];
|
||||
|
||||
let chartMarkdown = '\n\n### Charts\n\n';
|
||||
for (const chart of charts) {
|
||||
chartMarkdown += `<details><summary>${chart.label}</summary>\n\n`;
|
||||
chartMarkdown += `\n\n`;
|
||||
chartMarkdown += `</details>\n\n`;
|
||||
if (sha && prNumber) {
|
||||
const baseUrl = `https://raw.githubusercontent.com/${context.repo.owner}/${context.repo.repo}/${sha}/pr/${prNumber}/${runId}`;
|
||||
const charts = [
|
||||
{ file: 'latency_throughput.png', label: 'Latency, Throughput & Diff' },
|
||||
{ file: 'wait_breakdown.png', label: 'Wait Time Breakdown' },
|
||||
{ file: 'gas_vs_latency.png', label: 'Gas vs Latency' },
|
||||
];
|
||||
let chartMarkdown = '\n\n### Charts\n\n';
|
||||
for (const chart of charts) {
|
||||
chartMarkdown += `<details><summary>${chart.label}</summary>\n\n`;
|
||||
chartMarkdown += `\n\n`;
|
||||
chartMarkdown += `</details>\n\n`;
|
||||
}
|
||||
comment += chartMarkdown;
|
||||
}
|
||||
|
||||
comment += chartMarkdown;
|
||||
|
||||
const requestedBy = '${{ github.event.comment.user.login }}';
|
||||
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
||||
const body = `cc @${requestedBy}\n\n✅ Benchmark complete! [View run](${runUrl})\n\n${comment}`;
|
||||
const ackCommentId = '${{ steps.ack.outputs.comment-id }}';
|
||||
const jobUrl = process.env.BENCH_JOB_URL || `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
||||
const body = `cc @${process.env.BENCH_ACTOR}\n\n✅ Benchmark complete! [View job](${jobUrl})\n\n${comment}`;
|
||||
const ackCommentId = process.env.BENCH_COMMENT_ID;
|
||||
|
||||
if (ackCommentId) {
|
||||
await github.rest.issues.updateComment({
|
||||
@@ -395,22 +755,38 @@ jobs:
|
||||
body,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body,
|
||||
});
|
||||
// No PR — write results to job summary
|
||||
await core.summary.addRaw(body).write();
|
||||
}
|
||||
|
||||
- name: Update status (failed)
|
||||
if: failure() && env.BENCH_COMMENT_ID
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const steps_status = [
|
||||
['building binaries${{ steps.snapshot-check.outputs.needed == 'true' && ' & downloading snapshot' || '' }}', '${{ steps.build.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 }}'],
|
||||
['running baseline benchmark (2/2)', '${{ steps.run-baseline-2.outcome }}'],
|
||||
];
|
||||
const failed = steps_status.find(([, o]) => o === 'failure');
|
||||
const failedStep = failed ? failed[0] : 'unknown step';
|
||||
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner, repo: context.repo.repo,
|
||||
comment_id: parseInt(process.env.BENCH_COMMENT_ID),
|
||||
body: `cc @${process.env.BENCH_ACTOR}\n\n❌ Benchmark failed while ${failedStep}. [View logs](${process.env.BENCH_JOB_URL})`,
|
||||
});
|
||||
|
||||
- name: Upload node log
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: reth-node-log
|
||||
path: |
|
||||
/tmp/reth-bench-node-baseline.log
|
||||
/tmp/reth-bench-node-branch.log
|
||||
${{ env.BENCH_WORK_DIR }}/*/node.log
|
||||
|
||||
- name: Restore system settings
|
||||
if: always()
|
||||
|
||||
3
.github/workflows/hive.yml
vendored
3
.github/workflows/hive.yml
vendored
@@ -188,7 +188,8 @@ jobs:
|
||||
- build-reth-edge
|
||||
- prepare-hive
|
||||
name: ${{ matrix.storage }} / ${{ matrix.scenario.sim }}${{ matrix.scenario.limit && format(' - {0}', matrix.scenario.limit) }}
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-4' || 'ubuntu-latest' }}
|
||||
# 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:
|
||||
issues: write
|
||||
steps:
|
||||
|
||||
157
Cargo.lock
generated
157
Cargo.lock
generated
@@ -121,9 +121,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-consensus"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e4ff99651d46cef43767b5e8262ea228cd05287409ccb0c947cc25e70a952f9"
|
||||
checksum = "b0c0dc44157867da82c469c13186015b86abef209bf0e41625e4b68bac61d728"
|
||||
dependencies = [
|
||||
"alloy-eips",
|
||||
"alloy-primitives",
|
||||
@@ -149,9 +149,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-consensus-any"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a0701b0eda8051a2398591113e7862f807ccdd3315d0b441f06c2a0865a379b"
|
||||
checksum = "ba4cdb42df3871cd6b346d6a938ec2ba69a9a0f49d1f82714bc5c48349268434"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -164,9 +164,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-contract"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3c83c7a3c4e1151e8cac383d0a67ddf358f37e5ea51c95a1283d897c9de0a5a"
|
||||
checksum = "ca63b7125a981415898ffe2a2a696c83696c9c6bdb1671c8a912946bbd8e49e7"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-dyn-abi",
|
||||
@@ -262,9 +262,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-eips"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "def1626eea28d48c6cc0a6f16f34d4af0001906e4f889df6c660b39c86fd044d"
|
||||
checksum = "b9f7ef09f21bd1e9cb8a686f168cb4a206646804567f0889eadb8dcc4c9288c8"
|
||||
dependencies = [
|
||||
"alloy-eip2124",
|
||||
"alloy-eip2930",
|
||||
@@ -311,9 +311,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-genesis"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55d9d1aba3f914f0e8db9e4616ae37f3d811426d95bdccf44e47d0605ab202f6"
|
||||
checksum = "7c9cf3b99f46615fbf7dc1add0c96553abb7bf88fc9ec70dfbe7ad0b47ba7fe8"
|
||||
dependencies = [
|
||||
"alloy-eips",
|
||||
"alloy-primitives",
|
||||
@@ -365,9 +365,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-json-rpc"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e57586581f2008933241d16c3e3f633168b3a5d2738c5c42ea5246ec5e0ef17a"
|
||||
checksum = "ff42cd777eea61f370c0b10f2648a1c81e0b783066cd7269228aa993afd487f7"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-sol-types",
|
||||
@@ -380,9 +380,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-network"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b36c2a0ed74e48851f78415ca5b465211bd678891ba11e88fee09eac534bab1"
|
||||
checksum = "8cbca04f9b410fdc51aaaf88433cbac761213905a65fe832058bcf6690585762"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-consensus-any",
|
||||
@@ -406,9 +406,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-network-primitives"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "636c8051da58802e757b76c3b65af610b95799f72423dc955737dec73de234fd"
|
||||
checksum = "42d6d15e069a8b11f56bef2eccbad2a873c6dd4d4c81d04dda29710f5ea52f04"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -419,9 +419,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-node-bindings"
|
||||
version = "1.6.3"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ce6930ce52e43b0768dc99ceeff5cb9e673e8c9f87d926914cd028b2e3f7233"
|
||||
checksum = "e5a65d1ef42da862d7a528e95c170fa3066a81f23fbb9e778c213cf5e4063a0f"
|
||||
dependencies = [
|
||||
"alloy-genesis",
|
||||
"alloy-hardforks 0.2.13",
|
||||
@@ -484,9 +484,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-provider"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3dd56e2eafe8b1803e325867ac2c8a4c73c9fb5f341ffd8347f9344458c5922"
|
||||
checksum = "d181c8cc7cf4805d7e589bf4074d56d55064fa1a979f005a45a62b047616d870"
|
||||
dependencies = [
|
||||
"alloy-chains",
|
||||
"alloy-consensus",
|
||||
@@ -529,9 +529,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-pubsub"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6eebf54983d4fccea08053c218ee5c288adf2e660095a243d0532a8070b43955"
|
||||
checksum = "e8bd82953194dec221aa4cbbbb0b1e2df46066fe9d0333ac25b43a311e122d13"
|
||||
dependencies = [
|
||||
"alloy-json-rpc",
|
||||
"alloy-primitives",
|
||||
@@ -573,9 +573,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-client"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91577235d341a1bdbee30a463655d08504408a4d51e9f72edbfc5a622829f402"
|
||||
checksum = "f2792758a93ae32a32e9047c843d536e1448044f78422d71bf7d7c05149e103f"
|
||||
dependencies = [
|
||||
"alloy-json-rpc",
|
||||
"alloy-primitives",
|
||||
@@ -599,9 +599,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79cff039bf01a17d76c0aace3a3a773d5f895eb4c68baaae729ec9da9e86c99c"
|
||||
checksum = "7bdcbf9dfd5eea8bfeb078b1d906da8cd3a39c4d4dbe7a628025648e323611f6"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-types-engine",
|
||||
@@ -612,9 +612,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-admin"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "564afceae126df73b95f78c81eb46e2ef689a45ace0fcdaf5c9a178693a5ccca"
|
||||
checksum = "42325c117af3a9e49013f881c1474168db57978e02085fc9853a1c89e0562740"
|
||||
dependencies = [
|
||||
"alloy-genesis",
|
||||
"alloy-primitives",
|
||||
@@ -624,9 +624,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-anvil"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d22250cf438b6a3926de67683c08163bfa1fd1efa47ee9512cbcd631b6b0243c"
|
||||
checksum = "e0a3100b76987c1b1dc81f3abe592b7edc29e92b1242067a69d65e0030b35cf9"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-types-eth",
|
||||
@@ -636,9 +636,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-any"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73234a141ecce14e2989748c04fcac23deee67a445e2c4c167cfb42d4dacd1b6"
|
||||
checksum = "dd720b63f82b457610f2eaaf1f32edf44efffe03ae25d537632e7d23e7929e1a"
|
||||
dependencies = [
|
||||
"alloy-consensus-any",
|
||||
"alloy-rpc-types-eth",
|
||||
@@ -647,9 +647,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-beacon"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "625af0c3ebd3c31322edb1fb6b8e3e518acc39e164ed07e422eaff05310ff2fa"
|
||||
checksum = "4a22e13215866f5dfd5d3278f4c41f1fad9410dc68ce39022f58593c873c26f8"
|
||||
dependencies = [
|
||||
"alloy-eips",
|
||||
"alloy-primitives",
|
||||
@@ -667,9 +667,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-debug"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "779f70ab16a77e305571881b07a9bc6b0068ae6f962497baf5762825c5b839fb"
|
||||
checksum = "e1b21e1ad18ff1b31ff1030e046462ab8168cf8894e6778cd805c8bdfe2bd649"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"derive_more",
|
||||
@@ -679,9 +679,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-engine"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10620d600cc46538f613c561ac9a923843c6c74c61f054828dcdb8dd18c72ec4"
|
||||
checksum = "e4ac61f03f1edabccde1c687b5b25fff28f183afee64eaa2e767def3929e4457"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -700,9 +700,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-eth"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "010e101dbebe0c678248907a2545b574a87d078d82c2f6f5d0e8e7c9a6149a10"
|
||||
checksum = "9b2dc411f13092f237d2bf6918caf80977fc2f51485f9b90cb2a2f956912c8c9"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-consensus-any",
|
||||
@@ -722,9 +722,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-mev"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "375e4bf001135fe4f344db6197fafed8c2b61e99fa14d3597f44cd413f79e45b"
|
||||
checksum = "fe85bf3be739126aa593dca9fb3ab13ca93fa7873e6f2247be64d7f2cb15f34a"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -737,9 +737,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-trace"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be096f74d85e1f927580b398bf7bc5b4aa62326f149680ec0867e3c040c9aced"
|
||||
checksum = "1ad79f1e27e161943b5a4f99fe5534ef0849876214be411e0032c12f38e94daa"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-types-eth",
|
||||
@@ -751,9 +751,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-txpool"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14ab75189fbc29c5dd6f0bc1529bccef7b00773b458763f4d9d81a77ae4a1a2d"
|
||||
checksum = "d459f902a2313737bc66d18ed094c25d2aeb268b74d98c26bbbda2aa44182ab0"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-types-eth",
|
||||
@@ -763,9 +763,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-serde"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e6d631f8b975229361d8af7b2c749af31c73b3cf1352f90e144ddb06227105e"
|
||||
checksum = "e2ce1e0dbf7720eee747700e300c99aac01b1a95bb93f493a01e78ee28bb1a37"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"arbitrary",
|
||||
@@ -775,9 +775,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-signer"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97f40010b5e8f79b70bf163b38cd15f529b18ca88c4427c0e43441ee54e4ed82"
|
||||
checksum = "2425c6f314522c78e8198979c8cbf6769362be4da381d4152ea8eefce383535d"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"async-trait",
|
||||
@@ -790,9 +790,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-signer-local"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c4ec1cc27473819399a3f0da83bc1cef0ceaac8c1c93997696e46dc74377a58"
|
||||
checksum = "c3ecb71ee53d8d9c3fa7bac17542c8116ebc7a9726c91b1bf333ec3d04f5a789"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-network",
|
||||
@@ -879,9 +879,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-transport"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a03bb3f02b9a7ab23dacd1822fa7f69aa5c8eefcdcf57fad085e0b8d76fb4334"
|
||||
checksum = "fa186e560d523d196580c48bf00f1bf62e63041f28ecf276acc22f8b27bb9f53"
|
||||
dependencies = [
|
||||
"alloy-json-rpc",
|
||||
"auto_impl",
|
||||
@@ -902,9 +902,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-transport-http"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ce599598ef8ebe067f3627509358d9faaa1ef94f77f834a7783cd44209ef55c"
|
||||
checksum = "aa501ad58dd20acddbfebc65b52e60f05ebf97c52fa40d1b35e91f5e2da0ad0e"
|
||||
dependencies = [
|
||||
"alloy-json-rpc",
|
||||
"alloy-transport",
|
||||
@@ -918,9 +918,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-transport-ipc"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49963a2561ebd439549915ea61efb70a7b13b97500ec16ca507721c9d9957d07"
|
||||
checksum = "c2ef85688e5ac2da72afc804e0a1f153a1f309f05a864b1998bbbed7804dbaab"
|
||||
dependencies = [
|
||||
"alloy-json-rpc",
|
||||
"alloy-pubsub",
|
||||
@@ -938,9 +938,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-transport-ws"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ed38ea573c6658e0c2745af9d1f1773b1ed83aa59fbd9c286358ad469c3233a"
|
||||
checksum = "b9f00445db69d63298e2b00a0ea1d859f00e6424a3144ffc5eba9c31da995e16"
|
||||
dependencies = [
|
||||
"alloy-pubsub",
|
||||
"alloy-transport",
|
||||
@@ -976,9 +976,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-tx-macros"
|
||||
version = "1.6.3"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "397406cf04b11ca2a48e6f81804c70af3f40a36abf648e11dc7416043eb0834d"
|
||||
checksum = "6fa0c53e8c1e1ef4d01066b01c737fb62fc9397ab52c6e7bb5669f97d281b9bc"
|
||||
dependencies = [
|
||||
"darling 0.21.3",
|
||||
"proc-macro2",
|
||||
@@ -2211,9 +2211,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.58"
|
||||
version = "4.5.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806"
|
||||
checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -2221,9 +2221,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.58"
|
||||
version = "4.5.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2"
|
||||
checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -3599,6 +3599,7 @@ dependencies = [
|
||||
"reth-primitives",
|
||||
"reth-primitives-traits",
|
||||
"reth-provider",
|
||||
"reth-tasks",
|
||||
"reth-tracing",
|
||||
"secp256k1 0.30.0",
|
||||
"serde",
|
||||
@@ -7269,9 +7270,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rapidhash"
|
||||
version = "4.3.0"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84816e4c99c467e92cf984ee6328caa976dfecd33a673544489d79ca2caaefe5"
|
||||
checksum = "111325c42c4bafae99e777cd77b40dea9a2b30c69e9d8c74b6eccd7fba4337de"
|
||||
dependencies = [
|
||||
"rand 0.9.2",
|
||||
"rustversion",
|
||||
@@ -8707,19 +8708,13 @@ dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rlp",
|
||||
"alloy-rpc-types-eth",
|
||||
"alloy-serde",
|
||||
"arbitrary",
|
||||
"bincode 1.3.3",
|
||||
"derive_more",
|
||||
"modular-bitfield",
|
||||
"proptest",
|
||||
"proptest-arbitrary-interop",
|
||||
"rand 0.8.5",
|
||||
"rand 0.9.2",
|
||||
"reth-codecs",
|
||||
"reth-primitives-traits",
|
||||
"reth-zstd-compressors",
|
||||
"secp256k1 0.30.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
@@ -9321,6 +9316,7 @@ dependencies = [
|
||||
"reth-stages-types",
|
||||
"reth-storage-api",
|
||||
"reth-storage-errors",
|
||||
"reth-tasks",
|
||||
"reth-tracing",
|
||||
"reth-tracing-otlp",
|
||||
"reth-transaction-pool",
|
||||
@@ -9938,9 +9934,7 @@ dependencies = [
|
||||
"dyn-clone",
|
||||
"jsonrpsee-types",
|
||||
"op-alloy-consensus",
|
||||
"op-alloy-network",
|
||||
"op-alloy-rpc-types",
|
||||
"reth-ethereum-primitives",
|
||||
"reth-evm",
|
||||
"reth-primitives-traits",
|
||||
"serde_json",
|
||||
@@ -10332,12 +10326,12 @@ dependencies = [
|
||||
"reth-chainspec",
|
||||
"reth-db-api",
|
||||
"reth-errors",
|
||||
"reth-ethereum-primitives",
|
||||
"reth-execution-types",
|
||||
"reth-node-types",
|
||||
"reth-primitives",
|
||||
"reth-provider",
|
||||
"reth-prune-types",
|
||||
"reth-rpc-convert",
|
||||
"reth-stages-types",
|
||||
"reth-storage-api",
|
||||
"reth-trie",
|
||||
@@ -10351,6 +10345,7 @@ name = "reth-tasks"
|
||||
version = "1.11.0"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"dashmap",
|
||||
"futures-util",
|
||||
"metrics",
|
||||
"parking_lot",
|
||||
@@ -12320,9 +12315,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.8+spec-1.1.0"
|
||||
version = "1.0.9+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc"
|
||||
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
@@ -12770,9 +12765,9 @@ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.23"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
|
||||
56
Cargo.toml
56
Cargo.toml
@@ -459,33 +459,33 @@ alloy-trie = { version = "0.9.4", default-features = false }
|
||||
|
||||
alloy-hardforks = "0.4.5"
|
||||
|
||||
alloy-consensus = { version = "1.6.3", default-features = false }
|
||||
alloy-contract = { version = "1.6.3", default-features = false }
|
||||
alloy-eips = { version = "1.6.3", default-features = false }
|
||||
alloy-genesis = { version = "1.6.3", default-features = false }
|
||||
alloy-json-rpc = { version = "1.6.3", default-features = false }
|
||||
alloy-network = { version = "1.6.3", default-features = false }
|
||||
alloy-network-primitives = { version = "1.6.3", default-features = false }
|
||||
alloy-provider = { version = "1.6.3", features = ["reqwest", "debug-api"], default-features = false }
|
||||
alloy-pubsub = { version = "1.6.3", default-features = false }
|
||||
alloy-rpc-client = { version = "1.6.3", default-features = false }
|
||||
alloy-rpc-types = { version = "1.6.3", features = ["eth"], default-features = false }
|
||||
alloy-rpc-types-admin = { version = "1.6.3", default-features = false }
|
||||
alloy-rpc-types-anvil = { version = "1.6.3", default-features = false }
|
||||
alloy-rpc-types-beacon = { version = "1.6.3", default-features = false }
|
||||
alloy-rpc-types-debug = { version = "1.6.3", default-features = false }
|
||||
alloy-rpc-types-engine = { version = "1.6.3", default-features = false }
|
||||
alloy-rpc-types-eth = { version = "1.6.3", default-features = false }
|
||||
alloy-rpc-types-mev = { version = "1.6.3", default-features = false }
|
||||
alloy-rpc-types-trace = { version = "1.6.3", default-features = false }
|
||||
alloy-rpc-types-txpool = { version = "1.6.3", default-features = false }
|
||||
alloy-serde = { version = "1.6.3", default-features = false }
|
||||
alloy-signer = { version = "1.6.3", default-features = false }
|
||||
alloy-signer-local = { version = "1.6.3", default-features = false }
|
||||
alloy-transport = { version = "1.6.3" }
|
||||
alloy-transport-http = { version = "1.6.3", features = ["reqwest-rustls-tls"], default-features = false }
|
||||
alloy-transport-ipc = { version = "1.6.3", default-features = false }
|
||||
alloy-transport-ws = { version = "1.6.3", default-features = false }
|
||||
alloy-consensus = { version = "1.7.3", default-features = false }
|
||||
alloy-contract = { version = "1.7.3", default-features = false }
|
||||
alloy-eips = { version = "1.7.3", default-features = false }
|
||||
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-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-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 }
|
||||
alloy-rpc-types-debug = { version = "1.7.3", default-features = false }
|
||||
alloy-rpc-types-engine = { version = "1.7.3", default-features = false }
|
||||
alloy-rpc-types-eth = { version = "1.7.3", default-features = false }
|
||||
alloy-rpc-types-mev = { version = "1.7.3", default-features = false }
|
||||
alloy-rpc-types-trace = { version = "1.7.3", default-features = false }
|
||||
alloy-rpc-types-txpool = { version = "1.7.3", default-features = false }
|
||||
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-ipc = { version = "1.7.3", default-features = false }
|
||||
alloy-transport-ws = { version = "1.7.3", default-features = false }
|
||||
|
||||
# op
|
||||
alloy-op-evm = { version = "0.27.2", default-features = false }
|
||||
@@ -651,7 +651,7 @@ ethereum_ssz_derive = "0.10.1"
|
||||
jemalloc_pprof = { version = "0.8", default-features = false }
|
||||
tikv-jemalloc-ctl = "0.6"
|
||||
tikv-jemallocator = "0.6"
|
||||
tracy-client = "0.18.0"
|
||||
tracy-client = { version = "0.18.0", features = ["demangle"] }
|
||||
snmalloc-rs = { version = "0.3.7", features = ["build_cc"] }
|
||||
|
||||
aes = "0.8.1"
|
||||
|
||||
156
bin/reth-bench/src/bench/metrics_scraper.rs
Normal file
156
bin/reth-bench/src/bench/metrics_scraper.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
//! Prometheus metrics scraper for reth-bench.
|
||||
//!
|
||||
//! Scrapes a node's Prometheus metrics endpoint after each block to record
|
||||
//! execution and state root durations with block-level granularity.
|
||||
|
||||
use csv::Writer;
|
||||
use eyre::Context;
|
||||
use reqwest::Client;
|
||||
use serde::Serialize;
|
||||
use std::{path::Path, time::Duration};
|
||||
use tracing::info;
|
||||
|
||||
/// Suffix for the metrics CSV output file.
|
||||
pub(crate) const METRICS_OUTPUT_SUFFIX: &str = "metrics.csv";
|
||||
|
||||
/// A single row of scraped prometheus metrics for one block.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub(crate) struct MetricsRow {
|
||||
/// The block number.
|
||||
pub(crate) block_number: u64,
|
||||
/// EVM execution duration in seconds (from `sync_execution_execution_duration` gauge).
|
||||
pub(crate) execution_duration_secs: Option<f64>,
|
||||
/// State root computation duration in seconds (from
|
||||
/// `sync_block_validation_state_root_duration` gauge).
|
||||
pub(crate) state_root_duration_secs: Option<f64>,
|
||||
}
|
||||
|
||||
/// Scrapes a Prometheus metrics endpoint after each block to collect
|
||||
/// execution and state root durations.
|
||||
pub(crate) struct MetricsScraper {
|
||||
/// The full URL of the Prometheus metrics endpoint.
|
||||
url: String,
|
||||
/// Reusable HTTP client.
|
||||
client: Client,
|
||||
/// Collected metrics rows, one per block.
|
||||
rows: Vec<MetricsRow>,
|
||||
}
|
||||
|
||||
impl MetricsScraper {
|
||||
/// Creates a new scraper if a URL is provided.
|
||||
pub(crate) fn maybe_new(url: Option<String>) -> Option<Self> {
|
||||
url.map(|url| {
|
||||
info!(target: "reth-bench", %url, "Prometheus metrics scraping enabled");
|
||||
let client = Client::builder()
|
||||
.timeout(Duration::from_secs(5))
|
||||
.build()
|
||||
.expect("failed to build reqwest client");
|
||||
Self { url, client, rows: Vec::new() }
|
||||
})
|
||||
}
|
||||
|
||||
/// Scrapes the metrics endpoint and records values for the given block.
|
||||
pub(crate) async fn scrape_after_block(&mut self, block_number: u64) -> eyre::Result<()> {
|
||||
let text = self
|
||||
.client
|
||||
.get(&self.url)
|
||||
.send()
|
||||
.await
|
||||
.wrap_err("failed to fetch metrics endpoint")?
|
||||
.error_for_status()
|
||||
.wrap_err("metrics endpoint returned error status")?
|
||||
.text()
|
||||
.await
|
||||
.wrap_err("failed to read metrics response body")?;
|
||||
|
||||
let execution = parse_gauge(&text, "sync_execution_execution_duration");
|
||||
let state_root = parse_gauge(&text, "sync_block_validation_state_root_duration");
|
||||
|
||||
self.rows.push(MetricsRow {
|
||||
block_number,
|
||||
execution_duration_secs: execution,
|
||||
state_root_duration_secs: state_root,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes collected metrics to a CSV file in the output directory.
|
||||
pub(crate) fn write_csv(&self, output_dir: &Path) -> eyre::Result<()> {
|
||||
let path = output_dir.join(METRICS_OUTPUT_SUFFIX);
|
||||
info!(target: "reth-bench", "Writing scraped metrics to file: {:?}", path);
|
||||
let mut writer = Writer::from_path(&path)?;
|
||||
for row in &self.rows {
|
||||
writer.serialize(row)?;
|
||||
}
|
||||
writer.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a Prometheus gauge value from exposition-format text.
|
||||
///
|
||||
/// Searches for lines starting with `name` followed by either a space or `{`
|
||||
/// (for labeled metrics), then parses the numeric value. Returns the last
|
||||
/// matching sample to handle metrics emitted with multiple label sets.
|
||||
fn parse_gauge(text: &str, name: &str) -> Option<f64> {
|
||||
let mut result = None;
|
||||
for line in text.lines() {
|
||||
let line = line.trim();
|
||||
if line.is_empty() || line.starts_with('#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !line.starts_with(name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure we match the full metric name, not a prefix of another metric.
|
||||
let rest = &line[name.len()..];
|
||||
if !rest.starts_with(' ') && !rest.starts_with('{') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Format: `metric_name{labels} value [timestamp]` or `metric_name value [timestamp]`
|
||||
// Value is always the second whitespace-separated token.
|
||||
let mut parts = line.split_whitespace();
|
||||
if let Some(value_str) = parts.nth(1) &&
|
||||
let Ok(v) = value_str.parse::<f64>()
|
||||
{
|
||||
result = Some(v);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_gauge_simple() {
|
||||
let text = r#"# HELP sync_execution_execution_duration Duration of execution
|
||||
# TYPE sync_execution_execution_duration gauge
|
||||
sync_execution_execution_duration 0.123456
|
||||
"#;
|
||||
assert_eq!(parse_gauge(text, "sync_execution_execution_duration"), Some(0.123456));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_gauge_missing() {
|
||||
let text = "some_other_metric 1.0\n";
|
||||
assert_eq!(parse_gauge(text, "sync_execution_execution_duration"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_gauge_with_labels() {
|
||||
let text = "sync_block_validation_state_root_duration{instance=\"node1\"} 0.5\n";
|
||||
assert_eq!(parse_gauge(text, "sync_block_validation_state_root_duration"), Some(0.5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_gauge_prefix_no_false_match() {
|
||||
let text =
|
||||
"sync_execution_execution_duration_total 99.0\nsync_execution_execution_duration 0.5\n";
|
||||
assert_eq!(parse_gauge(text, "sync_execution_execution_duration"), Some(0.5));
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ pub(crate) mod helpers;
|
||||
pub use generate_big_block::{
|
||||
RawTransaction, RpcTransactionSource, TransactionCollector, TransactionSource,
|
||||
};
|
||||
pub(crate) mod metrics_scraper;
|
||||
mod new_payload_fcu;
|
||||
mod new_payload_only;
|
||||
mod output;
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::{
|
||||
bench::{
|
||||
context::BenchContext,
|
||||
helpers::parse_duration,
|
||||
metrics_scraper::MetricsScraper,
|
||||
output::{
|
||||
write_benchmark_results, CombinedResult, NewPayloadResult, TotalGasOutput, TotalGasRow,
|
||||
},
|
||||
@@ -30,7 +31,7 @@ use reth_cli_runner::CliContext;
|
||||
use reth_engine_primitives::config::DEFAULT_PERSISTENCE_THRESHOLD;
|
||||
use reth_node_core::args::BenchmarkArgs;
|
||||
use std::time::{Duration, Instant};
|
||||
use tracing::{debug, info};
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
/// `reth benchmark new-payload-fcu` command
|
||||
#[derive(Debug, Parser)]
|
||||
@@ -155,6 +156,8 @@ impl Command {
|
||||
|
||||
let total_blocks = benchmark_mode.total_blocks();
|
||||
|
||||
let mut metrics_scraper = MetricsScraper::maybe_new(self.benchmark.metrics_url.clone());
|
||||
|
||||
if use_reth_namespace {
|
||||
info!("Using reth_newPayload endpoint");
|
||||
}
|
||||
@@ -288,6 +291,12 @@ impl Command {
|
||||
};
|
||||
info!(target: "reth-bench", progress, %combined_result);
|
||||
|
||||
if let Some(scraper) = metrics_scraper.as_mut() &&
|
||||
let Err(err) = scraper.scrape_after_block(block_number).await
|
||||
{
|
||||
warn!(target: "reth-bench", %err, block_number, "Failed to scrape metrics");
|
||||
}
|
||||
|
||||
if let Some(w) = &mut waiter {
|
||||
w.on_block(block_number).await?;
|
||||
}
|
||||
@@ -313,6 +322,10 @@ impl Command {
|
||||
write_benchmark_results(path, &gas_output_results, &combined_results)?;
|
||||
}
|
||||
|
||||
if let (Some(path), Some(scraper)) = (&self.benchmark.output, &metrics_scraper) {
|
||||
scraper.write_csv(path)?;
|
||||
}
|
||||
|
||||
let gas_output =
|
||||
TotalGasOutput::with_combined_results(gas_output_results, &combined_results)?;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use crate::{
|
||||
bench::{
|
||||
context::BenchContext,
|
||||
metrics_scraper::MetricsScraper,
|
||||
output::{
|
||||
NewPayloadResult, TotalGasOutput, TotalGasRow, GAS_OUTPUT_SUFFIX,
|
||||
NEW_PAYLOAD_OUTPUT_SUFFIX,
|
||||
@@ -54,6 +55,8 @@ impl Command {
|
||||
|
||||
let total_blocks = benchmark_mode.total_blocks();
|
||||
|
||||
let mut metrics_scraper = MetricsScraper::maybe_new(self.benchmark.metrics_url.clone());
|
||||
|
||||
if use_reth_namespace {
|
||||
info!("Using reth_newPayload endpoint");
|
||||
}
|
||||
@@ -142,6 +145,12 @@ impl Command {
|
||||
let row =
|
||||
TotalGasRow { block_number, transaction_count, gas_used, time: current_duration };
|
||||
results.push((row, new_payload_result));
|
||||
|
||||
if let Some(scraper) = metrics_scraper.as_mut() &&
|
||||
let Err(err) = scraper.scrape_after_block(block_number).await
|
||||
{
|
||||
tracing::warn!(target: "reth-bench", %err, block_number, "Failed to scrape metrics");
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the spawned task encountered an error
|
||||
@@ -172,6 +181,10 @@ impl Command {
|
||||
}
|
||||
writer.flush()?;
|
||||
|
||||
if let Some(scraper) = &metrics_scraper {
|
||||
scraper.write_csv(&path)?;
|
||||
}
|
||||
|
||||
info!(target: "reth-bench", "Finished writing benchmark output files to {:?}.", path);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ use crate::{
|
||||
authenticated_transport::AuthenticatedTransportConnect,
|
||||
bench::{
|
||||
helpers::parse_duration,
|
||||
metrics_scraper::MetricsScraper,
|
||||
output::{
|
||||
write_benchmark_results, CombinedResult, GasRampPayloadFile, NewPayloadResult,
|
||||
TotalGasOutput, TotalGasRow,
|
||||
@@ -135,6 +136,14 @@ pub struct Command {
|
||||
/// and returns server-side timing breakdowns (latency, persistence wait, cache wait).
|
||||
#[arg(long, default_value = "false", verbatim_doc_comment)]
|
||||
reth_new_payload: bool,
|
||||
|
||||
/// Optional Prometheus metrics endpoint to scrape after each block.
|
||||
///
|
||||
/// When provided, reth-bench will fetch metrics from this URL after each
|
||||
/// payload, recording per-block execution and state root durations.
|
||||
/// Results are written to `metrics.csv` in the output directory.
|
||||
#[arg(long = "metrics-url", value_name = "URL", verbatim_doc_comment)]
|
||||
metrics_url: Option<String>,
|
||||
}
|
||||
|
||||
/// A loaded payload ready for execution.
|
||||
@@ -204,6 +213,8 @@ impl Command {
|
||||
(None, false) => None,
|
||||
};
|
||||
|
||||
let mut metrics_scraper = MetricsScraper::maybe_new(self.metrics_url.clone());
|
||||
|
||||
// Set up authenticated engine provider
|
||||
let jwt =
|
||||
std::fs::read_to_string(&self.jwt_secret).wrap_err("Failed to read JWT secret file")?;
|
||||
@@ -395,6 +406,12 @@ impl Command {
|
||||
let progress = format!("{}/{}", i + 1, payloads.len());
|
||||
info!(target: "reth-bench", progress, %combined_result);
|
||||
|
||||
if let Some(scraper) = metrics_scraper.as_mut() &&
|
||||
let Err(err) = scraper.scrape_after_block(block_number).await
|
||||
{
|
||||
tracing::warn!(target: "reth-bench", %err, block_number, "Failed to scrape metrics");
|
||||
}
|
||||
|
||||
if let Some(w) = &mut waiter {
|
||||
w.on_block(block_number).await?;
|
||||
}
|
||||
@@ -418,6 +435,10 @@ impl Command {
|
||||
write_benchmark_results(path, &gas_output_results, &combined_results)?;
|
||||
}
|
||||
|
||||
if let (Some(path), Some(scraper)) = (&self.output, &metrics_scraper) {
|
||||
scraper.write_csv(path)?;
|
||||
}
|
||||
|
||||
let gas_output =
|
||||
TotalGasOutput::with_combined_results(gas_output_results, &combined_results)?;
|
||||
info!(
|
||||
|
||||
@@ -43,7 +43,7 @@ reth-node-metrics.workspace = true
|
||||
reth-ethereum-primitives = { workspace = true, optional = true }
|
||||
reth-provider.workspace = true
|
||||
reth-prune.workspace = true
|
||||
reth-prune-types = { workspace = true, optional = true }
|
||||
reth-prune-types.workspace = true
|
||||
reth-revm.workspace = true
|
||||
reth-stages.workspace = true
|
||||
reth-stages-types = { workspace = true, optional = true }
|
||||
@@ -125,7 +125,7 @@ arbitrary = [
|
||||
"reth-stages-types/test-utils",
|
||||
"reth-trie-common/test-utils",
|
||||
"reth-codecs/arbitrary",
|
||||
"reth-prune-types?/arbitrary",
|
||||
"reth-prune-types/arbitrary",
|
||||
"reth-stages-types?/arbitrary",
|
||||
"reth-trie-common/arbitrary",
|
||||
"alloy-consensus/arbitrary",
|
||||
|
||||
@@ -89,13 +89,15 @@ impl<C: ChainSpecParser> EnvironmentArgs<C> {
|
||||
/// Initializes environment according to [`AccessRights`] and returns an instance of
|
||||
/// [`Environment`].
|
||||
///
|
||||
/// Internally builds a [`reth_tasks::Runtime`] attached to the current tokio handle for
|
||||
/// parallel storage I/O.
|
||||
pub fn init<N: CliNodeTypes>(&self, access: AccessRights) -> eyre::Result<Environment<N>>
|
||||
/// The provided `runtime` is used for parallel storage I/O.
|
||||
pub fn init<N: CliNodeTypes>(
|
||||
&self,
|
||||
access: AccessRights,
|
||||
runtime: reth_tasks::Runtime,
|
||||
) -> eyre::Result<Environment<N>>
|
||||
where
|
||||
C: ChainSpecParser<ChainSpec = N::ChainSpec>,
|
||||
{
|
||||
let runtime = reth_tasks::Runtime::with_existing_handle(tokio::runtime::Handle::current())?;
|
||||
let data_dir = self.datadir.clone().resolve_datadir(self.chain.chain());
|
||||
let db_path = data_dir.db();
|
||||
let sf_path = data_dir.static_files();
|
||||
|
||||
@@ -16,6 +16,7 @@ mod copy;
|
||||
mod diff;
|
||||
mod get;
|
||||
mod list;
|
||||
mod prune_checkpoints;
|
||||
mod repair_trie;
|
||||
mod settings;
|
||||
mod state;
|
||||
@@ -67,6 +68,8 @@ pub enum Subcommands {
|
||||
Path,
|
||||
/// Manage storage settings
|
||||
Settings(settings::Command),
|
||||
/// View or set prune checkpoints
|
||||
PruneCheckpoints(prune_checkpoints::Command),
|
||||
/// Gets storage size information for an account
|
||||
AccountStorage(account_storage::Command),
|
||||
/// Gets account state and storage at a specific block
|
||||
@@ -83,7 +86,8 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C>
|
||||
/// provided command.
|
||||
macro_rules! db_exec {
|
||||
($env:expr, $tool:ident, $N:ident, $access_rights:expr, $command:block) => {
|
||||
let Environment { provider_factory, .. } = $env.init::<$N>($access_rights)?;
|
||||
let Environment { provider_factory, .. } =
|
||||
$env.init::<$N>($access_rights, ctx.task_executor.clone())?;
|
||||
|
||||
let $tool = DbTool::new(provider_factory)?;
|
||||
$command;
|
||||
@@ -204,6 +208,11 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C>
|
||||
command.execute(&tool)?;
|
||||
});
|
||||
}
|
||||
Subcommands::PruneCheckpoints(command) => {
|
||||
db_exec!(self.env, tool, N, command.access_rights(), {
|
||||
command.execute(&tool)?;
|
||||
});
|
||||
}
|
||||
Subcommands::AccountStorage(command) => {
|
||||
db_exec!(self.env, tool, N, AccessRights::RO, {
|
||||
command.execute(&tool)?;
|
||||
|
||||
221
crates/cli/commands/src/db/prune_checkpoints.rs
Normal file
221
crates/cli/commands/src/db/prune_checkpoints.rs
Normal file
@@ -0,0 +1,221 @@
|
||||
//! `reth db prune-checkpoints` command for viewing and setting prune checkpoint values.
|
||||
|
||||
use clap::{Args, Parser, Subcommand, ValueEnum};
|
||||
use reth_db_common::DbTool;
|
||||
use reth_provider::{providers::ProviderNodeTypes, DBProvider, DatabaseProviderFactory};
|
||||
use reth_prune_types::{PruneCheckpoint, PruneMode, PruneSegment};
|
||||
use reth_storage_api::{PruneCheckpointReader, PruneCheckpointWriter};
|
||||
|
||||
use crate::common::AccessRights;
|
||||
|
||||
/// `reth db prune-checkpoints` subcommand
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Command {
|
||||
#[command(subcommand)]
|
||||
command: Subcommands,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
/// Returns database access rights required for the command.
|
||||
pub fn access_rights(&self) -> AccessRights {
|
||||
match &self.command {
|
||||
Subcommands::Get { .. } => AccessRights::RO,
|
||||
Subcommands::Set(_) => AccessRights::RW,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum Subcommands {
|
||||
/// Get prune checkpoint(s) from database.
|
||||
///
|
||||
/// Shows the current prune progress for each segment, including the highest
|
||||
/// pruned block/tx number and the active prune mode.
|
||||
Get {
|
||||
/// Specific segment to query. If omitted, shows all segments.
|
||||
#[arg(long, value_enum)]
|
||||
segment: Option<SegmentArg>,
|
||||
},
|
||||
/// Set a prune checkpoint for a segment.
|
||||
///
|
||||
/// WARNING: Manually setting checkpoints can cause data inconsistencies.
|
||||
/// Only use this if you know what you're doing (e.g., recovering from a
|
||||
/// corrupted checkpoint or forcing a re-prune from a specific block).
|
||||
Set(SetArgs),
|
||||
}
|
||||
|
||||
/// Arguments for the `set` subcommand
|
||||
#[derive(Debug, Args)]
|
||||
pub struct SetArgs {
|
||||
/// The prune segment to update
|
||||
#[arg(long, value_enum)]
|
||||
segment: SegmentArg,
|
||||
|
||||
/// Highest pruned block number
|
||||
#[arg(long)]
|
||||
block_number: Option<u64>,
|
||||
|
||||
/// Highest pruned transaction number
|
||||
#[arg(long)]
|
||||
tx_number: Option<u64>,
|
||||
|
||||
/// Prune mode to write: full, distance, or before
|
||||
#[arg(long, value_enum)]
|
||||
mode: PruneModeArg,
|
||||
|
||||
/// Value for distance or before mode (required unless mode is full)
|
||||
#[arg(long, required_if_eq_any([("mode", "distance"), ("mode", "before")]))]
|
||||
mode_value: Option<u64>,
|
||||
}
|
||||
|
||||
/// CLI-friendly prune segment names (excludes deprecated variants)
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
#[clap(rename_all = "kebab-case")]
|
||||
pub enum SegmentArg {
|
||||
SenderRecovery,
|
||||
TransactionLookup,
|
||||
Receipts,
|
||||
ContractLogs,
|
||||
AccountHistory,
|
||||
StorageHistory,
|
||||
Bodies,
|
||||
}
|
||||
|
||||
impl From<SegmentArg> for PruneSegment {
|
||||
fn from(arg: SegmentArg) -> Self {
|
||||
match arg {
|
||||
SegmentArg::SenderRecovery => Self::SenderRecovery,
|
||||
SegmentArg::TransactionLookup => Self::TransactionLookup,
|
||||
SegmentArg::Receipts => Self::Receipts,
|
||||
SegmentArg::ContractLogs => Self::ContractLogs,
|
||||
SegmentArg::AccountHistory => Self::AccountHistory,
|
||||
SegmentArg::StorageHistory => Self::StorageHistory,
|
||||
SegmentArg::Bodies => Self::Bodies,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// CLI-friendly prune mode
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
#[clap(rename_all = "kebab-case")]
|
||||
pub enum PruneModeArg {
|
||||
/// Prune all blocks
|
||||
Full,
|
||||
/// Keep the last N blocks (requires --mode-value)
|
||||
Distance,
|
||||
/// Prune blocks before a specific block number (requires --mode-value)
|
||||
Before,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
/// Execute the command
|
||||
pub fn execute<N: ProviderNodeTypes>(self, tool: &DbTool<N>) -> eyre::Result<()> {
|
||||
match self.command {
|
||||
Subcommands::Get { segment } => Self::get(tool, segment),
|
||||
Subcommands::Set(args) => Self::set(tool, args),
|
||||
}
|
||||
}
|
||||
|
||||
fn get<N: ProviderNodeTypes>(
|
||||
tool: &DbTool<N>,
|
||||
segment: Option<SegmentArg>,
|
||||
) -> eyre::Result<()> {
|
||||
let provider = tool.provider_factory.provider()?;
|
||||
|
||||
match segment {
|
||||
Some(seg) => {
|
||||
let segment: PruneSegment = seg.into();
|
||||
match provider.get_prune_checkpoint(segment)? {
|
||||
Some(checkpoint) => print_checkpoint(segment, &checkpoint),
|
||||
None => println!("No checkpoint found for {segment}"),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let mut checkpoints = provider.get_prune_checkpoints()?;
|
||||
checkpoints.sort_by_key(|(seg, _)| *seg);
|
||||
if checkpoints.is_empty() {
|
||||
println!("No prune checkpoints found.");
|
||||
} else {
|
||||
println!(
|
||||
"{:<25} {:>15} {:>15} {:>20}",
|
||||
"Segment", "Block Number", "Tx Number", "Prune Mode"
|
||||
);
|
||||
println!("{}", "-".repeat(80));
|
||||
for (segment, checkpoint) in &checkpoints {
|
||||
println!(
|
||||
"{:<25} {:>15} {:>15} {:>20}",
|
||||
segment.to_string(),
|
||||
fmt_opt(checkpoint.block_number),
|
||||
fmt_opt(checkpoint.tx_number),
|
||||
fmt_mode(&checkpoint.prune_mode),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set<N: ProviderNodeTypes>(tool: &DbTool<N>, args: SetArgs) -> eyre::Result<()> {
|
||||
eyre::ensure!(
|
||||
args.block_number.is_some() || args.tx_number.is_some(),
|
||||
"at least one of --block-number or --tx-number must be provided"
|
||||
);
|
||||
|
||||
let prune_mode = match args.mode {
|
||||
PruneModeArg::Full => PruneMode::Full,
|
||||
PruneModeArg::Distance => PruneMode::Distance(
|
||||
args.mode_value
|
||||
.ok_or_else(|| eyre::eyre!("--mode-value is required for distance mode"))?,
|
||||
),
|
||||
PruneModeArg::Before => PruneMode::Before(
|
||||
args.mode_value
|
||||
.ok_or_else(|| eyre::eyre!("--mode-value is required for before mode"))?,
|
||||
),
|
||||
};
|
||||
|
||||
let segment: PruneSegment = args.segment.into();
|
||||
let checkpoint = PruneCheckpoint {
|
||||
block_number: args.block_number,
|
||||
tx_number: args.tx_number,
|
||||
prune_mode,
|
||||
};
|
||||
|
||||
let provider_rw = tool.provider_factory.database_provider_rw()?;
|
||||
|
||||
// Show previous value if any
|
||||
if let Some(prev) = provider_rw.get_prune_checkpoint(segment)? {
|
||||
println!("Previous checkpoint for {segment}:");
|
||||
print_checkpoint(segment, &prev);
|
||||
println!();
|
||||
}
|
||||
|
||||
provider_rw.save_prune_checkpoint(segment, checkpoint)?;
|
||||
provider_rw.commit()?;
|
||||
|
||||
println!("Updated checkpoint for {segment}:");
|
||||
print_checkpoint(segment, &checkpoint);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn print_checkpoint(segment: PruneSegment, checkpoint: &PruneCheckpoint) {
|
||||
println!(" Segment: {segment}");
|
||||
println!(" Block Number: {}", fmt_opt(checkpoint.block_number));
|
||||
println!(" Tx Number: {}", fmt_opt(checkpoint.tx_number));
|
||||
println!(" Prune Mode: {}", fmt_mode(&checkpoint.prune_mode));
|
||||
}
|
||||
|
||||
fn fmt_opt(val: Option<u64>) -> String {
|
||||
val.map_or("-".to_string(), |n| n.to_string())
|
||||
}
|
||||
|
||||
fn fmt_mode(mode: &PruneMode) -> String {
|
||||
match mode {
|
||||
PruneMode::Full => "Full".to_string(),
|
||||
PruneMode::Distance(d) => format!("Distance({d})"),
|
||||
PruneMode::Before(b) => format!("Before({b})"),
|
||||
}
|
||||
}
|
||||
@@ -44,11 +44,11 @@ pub struct ExportArgs {
|
||||
|
||||
impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> ExportEraCommand<C> {
|
||||
/// Execute `export-era` command
|
||||
pub async fn execute<N>(self) -> eyre::Result<()>
|
||||
pub async fn execute<N>(self, runtime: reth_tasks::Runtime) -> eyre::Result<()>
|
||||
where
|
||||
N: CliNodeTypes<ChainSpec = C::ChainSpec>,
|
||||
{
|
||||
let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RO)?;
|
||||
let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RO, runtime)?;
|
||||
|
||||
// Either specified path or default to `<data-dir>/<chain>/era1-export/`
|
||||
let data_dir = match &self.export.path {
|
||||
|
||||
@@ -47,6 +47,7 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> ImportComm
|
||||
pub async fn execute<N, Comp>(
|
||||
self,
|
||||
components: impl FnOnce(Arc<N::ChainSpec>) -> Comp,
|
||||
runtime: reth_tasks::Runtime,
|
||||
) -> eyre::Result<()>
|
||||
where
|
||||
N: CliNodeTypes<ChainSpec = C::ChainSpec>,
|
||||
@@ -54,7 +55,8 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> ImportComm
|
||||
{
|
||||
info!(target: "reth::cli", "reth {} starting", version_metadata().short_version);
|
||||
|
||||
let Environment { provider_factory, config, .. } = self.env.init::<N>(AccessRights::RW)?;
|
||||
let Environment { provider_factory, config, .. } =
|
||||
self.env.init::<N>(AccessRights::RW, runtime.clone())?;
|
||||
|
||||
let components = components(provider_factory.chain_spec());
|
||||
|
||||
@@ -85,6 +87,7 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> ImportComm
|
||||
&config,
|
||||
executor.clone(),
|
||||
consensus.clone(),
|
||||
runtime.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ pub async fn import_blocks_from_file<N>(
|
||||
config: &Config,
|
||||
executor: impl ConfigureEvm<Primitives = N::Primitives> + 'static,
|
||||
consensus: Arc<impl FullConsensus<N::Primitives> + 'static>,
|
||||
runtime: reth_tasks::Runtime,
|
||||
) -> eyre::Result<ImportResult>
|
||||
where
|
||||
N: ProviderNodeTypes,
|
||||
@@ -139,7 +140,7 @@ where
|
||||
total_decoded_blocks += file_client.headers_len();
|
||||
total_decoded_txns += file_client.total_transactions();
|
||||
|
||||
let (mut pipeline, events, _runtime) = build_import_pipeline_impl(
|
||||
let (mut pipeline, events) = build_import_pipeline_impl(
|
||||
config,
|
||||
provider_factory.clone(),
|
||||
&consensus,
|
||||
@@ -147,6 +148,7 @@ where
|
||||
static_file_producer.clone(),
|
||||
import_config.no_state,
|
||||
executor.clone(),
|
||||
runtime.clone(),
|
||||
)?;
|
||||
|
||||
// override the tip
|
||||
@@ -257,6 +259,7 @@ where
|
||||
///
|
||||
/// If configured to execute, all stages will run. Otherwise, only stages that don't require state
|
||||
/// will run.
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub fn build_import_pipeline_impl<N, C, E>(
|
||||
config: &Config,
|
||||
provider_factory: ProviderFactory<N>,
|
||||
@@ -265,11 +268,8 @@ pub fn build_import_pipeline_impl<N, C, E>(
|
||||
static_file_producer: StaticFileProducer<ProviderFactory<N>>,
|
||||
disable_exec: bool,
|
||||
evm_config: E,
|
||||
) -> eyre::Result<(
|
||||
Pipeline<N>,
|
||||
impl futures::Stream<Item = NodeEvent<N::Primitives>> + use<N, C, E>,
|
||||
reth_tasks::Runtime,
|
||||
)>
|
||||
runtime: reth_tasks::Runtime,
|
||||
) -> eyre::Result<(Pipeline<N>, impl futures::Stream<Item = NodeEvent<N::Primitives>> + use<N, C, E>)>
|
||||
where
|
||||
N: ProviderNodeTypes,
|
||||
C: FullConsensus<N::Primitives> + 'static,
|
||||
@@ -285,9 +285,6 @@ where
|
||||
.sealed_header(last_block_number)?
|
||||
.ok_or_else(|| ProviderError::HeaderNotFound(last_block_number.into()))?;
|
||||
|
||||
let runtime = reth_tasks::Runtime::with_existing_handle(tokio::runtime::Handle::current())
|
||||
.expect("failed to create runtime");
|
||||
|
||||
let mut header_downloader = ReverseHeadersDownloaderBuilder::new(config.stages.headers)
|
||||
.build(file_client.clone(), consensus.clone())
|
||||
.into_task_with(&runtime);
|
||||
@@ -333,5 +330,5 @@ where
|
||||
|
||||
let events = pipeline.events().map(Into::into);
|
||||
|
||||
Ok((pipeline, events, runtime))
|
||||
Ok((pipeline, events))
|
||||
}
|
||||
|
||||
@@ -64,13 +64,14 @@ impl TryFromChain for ChainKind {
|
||||
|
||||
impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> ImportEraCommand<C> {
|
||||
/// Execute `import-era` command
|
||||
pub async fn execute<N>(self) -> eyre::Result<()>
|
||||
pub async fn execute<N>(self, runtime: reth_tasks::Runtime) -> eyre::Result<()>
|
||||
where
|
||||
N: CliNodeTypes<ChainSpec = C::ChainSpec>,
|
||||
{
|
||||
info!(target: "reth::cli", "reth {} starting", version_metadata().short_version);
|
||||
|
||||
let Environment { provider_factory, config, .. } = self.env.init::<N>(AccessRights::RW)?;
|
||||
let Environment { provider_factory, config, .. } =
|
||||
self.env.init::<N>(AccessRights::RW, runtime)?;
|
||||
|
||||
let mut hash_collector = Collector::new(config.stages.etl.file_size, config.stages.etl.dir);
|
||||
|
||||
|
||||
@@ -18,10 +18,13 @@ pub struct InitCommand<C: ChainSpecParser> {
|
||||
|
||||
impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> InitCommand<C> {
|
||||
/// Execute the `init` command
|
||||
pub async fn execute<N: CliNodeTypes<ChainSpec = C::ChainSpec>>(self) -> eyre::Result<()> {
|
||||
pub async fn execute<N: CliNodeTypes<ChainSpec = C::ChainSpec>>(
|
||||
self,
|
||||
runtime: reth_tasks::Runtime,
|
||||
) -> eyre::Result<()> {
|
||||
info!(target: "reth::cli", "reth init starting");
|
||||
|
||||
let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
|
||||
let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RW, runtime)?;
|
||||
|
||||
let genesis_block_number = provider_factory.chain_spec().genesis_header().number();
|
||||
let hash = provider_factory
|
||||
|
||||
@@ -65,7 +65,7 @@ pub struct InitStateCommand<C: ChainSpecParser> {
|
||||
|
||||
impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> InitStateCommand<C> {
|
||||
/// Execute the `init` command
|
||||
pub async fn execute<N>(self) -> eyre::Result<()>
|
||||
pub async fn execute<N>(self, runtime: reth_tasks::Runtime) -> eyre::Result<()>
|
||||
where
|
||||
N: CliNodeTypes<
|
||||
ChainSpec = C::ChainSpec,
|
||||
@@ -74,7 +74,8 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> InitStateC
|
||||
{
|
||||
info!(target: "reth::cli", "Reth init-state starting");
|
||||
|
||||
let Environment { config, provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
|
||||
let Environment { config, provider_factory, .. } =
|
||||
self.env.init::<N>(AccessRights::RW, runtime)?;
|
||||
|
||||
let static_file_provider = provider_factory.static_file_provider();
|
||||
let provider_rw = provider_factory.database_provider_rw()?;
|
||||
|
||||
@@ -16,6 +16,7 @@ use reth_node_core::{
|
||||
args::{DatadirArgs, NetworkArgs},
|
||||
utils::get_single_header,
|
||||
};
|
||||
use reth_tasks::Runtime;
|
||||
|
||||
pub mod bootnode;
|
||||
pub mod enode;
|
||||
@@ -194,17 +195,18 @@ impl<C: ChainSpecParser> DownloadArgs<C> {
|
||||
let rlpx_socket = (self.network.addr, self.network.port).into();
|
||||
let boot_nodes = self.chain.bootnodes().unwrap_or_default();
|
||||
|
||||
let net = NetworkConfigBuilder::<N::NetworkPrimitives>::new(p2p_secret_key)
|
||||
.peer_config(config.peers_config_with_basic_nodes_from_file(None))
|
||||
.external_ip_resolver(self.network.nat.clone())
|
||||
.network_id(self.network.network_id)
|
||||
.boot_nodes(boot_nodes.clone())
|
||||
.apply(|builder| {
|
||||
self.network.discovery.apply_to_builder(builder, rlpx_socket, boot_nodes)
|
||||
})
|
||||
.build_with_noop_provider(self.chain.clone())
|
||||
.manager()
|
||||
.await?;
|
||||
let net =
|
||||
NetworkConfigBuilder::<N::NetworkPrimitives>::new(p2p_secret_key, Runtime::test())
|
||||
.peer_config(config.peers_config_with_basic_nodes_from_file(None))
|
||||
.external_ip_resolver(self.network.nat.clone())
|
||||
.network_id(self.network.network_id)
|
||||
.boot_nodes(boot_nodes.clone())
|
||||
.apply(|builder| {
|
||||
self.network.discovery.apply_to_builder(builder, rlpx_socket, boot_nodes)
|
||||
})
|
||||
.build_with_noop_provider(self.chain.clone())
|
||||
.manager()
|
||||
.await?;
|
||||
let handle = net.handle().clone();
|
||||
tokio::task::spawn(net);
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> PruneComma
|
||||
self,
|
||||
ctx: CliContext,
|
||||
) -> eyre::Result<()> {
|
||||
let env = self.env.init::<N>(AccessRights::RW)?;
|
||||
let env = self.env.init::<N>(AccessRights::RW, ctx.task_executor.clone())?;
|
||||
let provider_factory = env.provider_factory;
|
||||
let config = env.config.prune;
|
||||
let data_dir = env.data_dir;
|
||||
|
||||
@@ -60,11 +60,15 @@ impl<C: ChainSpecParser> Command<C> {
|
||||
|
||||
impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>> Command<C> {
|
||||
/// Execute `re-execute` command
|
||||
pub async fn execute<N>(self, components: impl CliComponentsBuilder<N>) -> eyre::Result<()>
|
||||
pub async fn execute<N>(
|
||||
self,
|
||||
components: impl CliComponentsBuilder<N>,
|
||||
runtime: reth_tasks::Runtime,
|
||||
) -> eyre::Result<()>
|
||||
where
|
||||
N: CliNodeTypes<ChainSpec = C::ChainSpec>,
|
||||
{
|
||||
let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RO)?;
|
||||
let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RO, runtime)?;
|
||||
|
||||
let components = components(provider_factory.chain_spec());
|
||||
|
||||
|
||||
@@ -37,11 +37,11 @@ pub struct Command<C: ChainSpecParser> {
|
||||
|
||||
impl<C: ChainSpecParser> Command<C> {
|
||||
/// Execute `db` command
|
||||
pub async fn execute<N: CliNodeTypes>(self) -> eyre::Result<()>
|
||||
pub async fn execute<N: CliNodeTypes>(self, runtime: reth_tasks::Runtime) -> eyre::Result<()>
|
||||
where
|
||||
C: ChainSpecParser<ChainSpec = N::ChainSpec>,
|
||||
{
|
||||
let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
|
||||
let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RW, runtime)?;
|
||||
|
||||
let tool = DbTool::new(provider_factory)?;
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ use reth_stages::{stages::ExecutionStage, Stage, StageCheckpoint, UnwindInput};
|
||||
use std::sync::Arc;
|
||||
use tracing::info;
|
||||
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub(crate) async fn dump_execution_stage<N, E, C>(
|
||||
db_tool: &DbTool<N>,
|
||||
from: u64,
|
||||
@@ -24,6 +25,7 @@ pub(crate) async fn dump_execution_stage<N, E, C>(
|
||||
should_run: bool,
|
||||
evm_config: E,
|
||||
consensus: C,
|
||||
runtime: reth_tasks::Runtime,
|
||||
) -> eyre::Result<()>
|
||||
where
|
||||
N: ProviderNodeTypes<DB = DatabaseEnv>,
|
||||
@@ -37,7 +39,6 @@ where
|
||||
unwind_and_copy(db_tool, from, tip_block_number, &output_db, evm_config.clone())?;
|
||||
|
||||
if should_run {
|
||||
let runtime = reth_tasks::Runtime::with_existing_handle(tokio::runtime::Handle::current())?;
|
||||
dry_run(
|
||||
ProviderFactory::<N>::new(
|
||||
output_db,
|
||||
|
||||
@@ -18,6 +18,7 @@ pub(crate) async fn dump_hashing_account_stage<N: ProviderNodeTypes<DB = Databas
|
||||
to: BlockNumber,
|
||||
output_datadir: ChainPath<DataDirPath>,
|
||||
should_run: bool,
|
||||
runtime: reth_tasks::Runtime,
|
||||
) -> Result<()> {
|
||||
let (output_db, tip_block_number) = setup(from, to, &output_datadir.db(), db_tool)?;
|
||||
|
||||
@@ -33,7 +34,6 @@ pub(crate) async fn dump_hashing_account_stage<N: ProviderNodeTypes<DB = Databas
|
||||
unwind_and_copy(db_tool, from, tip_block_number, &output_db)?;
|
||||
|
||||
if should_run {
|
||||
let runtime = reth_tasks::Runtime::with_existing_handle(tokio::runtime::Handle::current())?;
|
||||
dry_run(
|
||||
ProviderFactory::<N>::new(
|
||||
output_db,
|
||||
|
||||
@@ -17,13 +17,13 @@ pub(crate) async fn dump_hashing_storage_stage<N: ProviderNodeTypes<DB = Databas
|
||||
to: u64,
|
||||
output_datadir: ChainPath<DataDirPath>,
|
||||
should_run: bool,
|
||||
runtime: reth_tasks::Runtime,
|
||||
) -> Result<()> {
|
||||
let (output_db, tip_block_number) = setup(from, to, &output_datadir.db(), db_tool)?;
|
||||
|
||||
unwind_and_copy(db_tool, from, tip_block_number, &output_db)?;
|
||||
|
||||
if should_run {
|
||||
let runtime = reth_tasks::Runtime::with_existing_handle(tokio::runtime::Handle::current())?;
|
||||
dry_run(
|
||||
ProviderFactory::<N>::new(
|
||||
output_db,
|
||||
|
||||
@@ -24,6 +24,7 @@ use reth_stages::{
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub(crate) async fn dump_merkle_stage<N>(
|
||||
db_tool: &DbTool<N>,
|
||||
from: BlockNumber,
|
||||
@@ -32,6 +33,7 @@ pub(crate) async fn dump_merkle_stage<N>(
|
||||
should_run: bool,
|
||||
evm_config: impl ConfigureEvm<Primitives = N::Primitives>,
|
||||
consensus: impl FullConsensus<N::Primitives> + 'static,
|
||||
runtime: reth_tasks::Runtime,
|
||||
) -> Result<()>
|
||||
where
|
||||
N: ProviderNodeTypes<DB = DatabaseEnv>,
|
||||
@@ -57,7 +59,6 @@ where
|
||||
unwind_and_copy(db_tool, (from, to), tip_block_number, &output_db, evm_config, consensus)?;
|
||||
|
||||
if should_run {
|
||||
let runtime = reth_tasks::Runtime::with_existing_handle(tokio::runtime::Handle::current())?;
|
||||
dry_run(
|
||||
ProviderFactory::<N>::new(
|
||||
output_db,
|
||||
|
||||
@@ -72,30 +72,36 @@ pub struct StageCommand {
|
||||
}
|
||||
|
||||
macro_rules! handle_stage {
|
||||
($stage_fn:ident, $tool:expr, $command:expr) => {{
|
||||
($stage_fn:ident, $tool:expr, $command:expr, $runtime:expr) => {{
|
||||
let StageCommand { output_datadir, from, to, dry_run, .. } = $command;
|
||||
let output_datadir =
|
||||
output_datadir.with_chain($tool.chain().chain(), DatadirArgs::default());
|
||||
$stage_fn($tool, *from, *to, output_datadir, *dry_run).await?
|
||||
$stage_fn($tool, *from, *to, output_datadir, *dry_run, $runtime).await?
|
||||
}};
|
||||
|
||||
($stage_fn:ident, $tool:expr, $command:expr, $executor:expr, $consensus:expr) => {{
|
||||
($stage_fn:ident, $tool:expr, $command:expr, $executor:expr, $consensus:expr, $runtime:expr) => {{
|
||||
let StageCommand { output_datadir, from, to, dry_run, .. } = $command;
|
||||
let output_datadir =
|
||||
output_datadir.with_chain($tool.chain().chain(), DatadirArgs::default());
|
||||
$stage_fn($tool, *from, *to, output_datadir, *dry_run, $executor, $consensus).await?
|
||||
$stage_fn($tool, *from, *to, output_datadir, *dry_run, $executor, $consensus, $runtime)
|
||||
.await?
|
||||
}};
|
||||
}
|
||||
|
||||
impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C> {
|
||||
/// Execute `dump-stage` command
|
||||
pub async fn execute<N, Comp, F>(self, components: F) -> eyre::Result<()>
|
||||
pub async fn execute<N, Comp, F>(
|
||||
self,
|
||||
components: F,
|
||||
runtime: reth_tasks::Runtime,
|
||||
) -> eyre::Result<()>
|
||||
where
|
||||
N: CliNodeTypes<ChainSpec = C::ChainSpec>,
|
||||
Comp: CliNodeComponents<N>,
|
||||
F: FnOnce(Arc<C::ChainSpec>) -> Comp,
|
||||
{
|
||||
let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RO)?;
|
||||
let Environment { provider_factory, .. } =
|
||||
self.env.init::<N>(AccessRights::RO, runtime.clone())?;
|
||||
let tool = DbTool::new(provider_factory)?;
|
||||
let components = components(tool.chain());
|
||||
let evm_config = components.evm_config().clone();
|
||||
@@ -103,12 +109,23 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C>
|
||||
|
||||
match &self.command {
|
||||
Stages::Execution(cmd) => {
|
||||
handle_stage!(dump_execution_stage, &tool, cmd, evm_config, consensus)
|
||||
handle_stage!(
|
||||
dump_execution_stage,
|
||||
&tool,
|
||||
cmd,
|
||||
evm_config,
|
||||
consensus,
|
||||
runtime.clone()
|
||||
)
|
||||
}
|
||||
Stages::StorageHashing(cmd) => {
|
||||
handle_stage!(dump_hashing_storage_stage, &tool, cmd, runtime.clone())
|
||||
}
|
||||
Stages::AccountHashing(cmd) => {
|
||||
handle_stage!(dump_hashing_account_stage, &tool, cmd, runtime.clone())
|
||||
}
|
||||
Stages::StorageHashing(cmd) => handle_stage!(dump_hashing_storage_stage, &tool, cmd),
|
||||
Stages::AccountHashing(cmd) => handle_stage!(dump_hashing_account_stage, &tool, cmd),
|
||||
Stages::Merkle(cmd) => {
|
||||
handle_stage!(dump_merkle_stage, &tool, cmd, evm_config, consensus)
|
||||
handle_stage!(dump_merkle_stage, &tool, cmd, evm_config, consensus, runtime.clone())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,11 +49,12 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>
|
||||
N: CliNodeTypes<ChainSpec = C::ChainSpec>,
|
||||
Comp: CliNodeComponents<N>,
|
||||
{
|
||||
let executor = ctx.task_executor.clone();
|
||||
match self.command {
|
||||
Subcommands::Run(command) => command.execute::<N, _, _>(ctx, components).await,
|
||||
Subcommands::Drop(command) => command.execute::<N>().await,
|
||||
Subcommands::Dump(command) => command.execute::<N, _, _>(components).await,
|
||||
Subcommands::Unwind(command) => command.execute::<N, _, _>(components).await,
|
||||
Subcommands::Drop(command) => command.execute::<N>(executor).await,
|
||||
Subcommands::Dump(command) => command.execute::<N, _, _>(components, executor).await,
|
||||
Subcommands::Unwind(command) => command.execute::<N, _, _>(components, executor).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,8 +119,9 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>
|
||||
// Does not do anything on windows.
|
||||
let _ = fdlimit::raise_fd_limit();
|
||||
|
||||
let runtime = ctx.task_executor.clone();
|
||||
let Environment { provider_factory, config, data_dir } =
|
||||
self.env.init::<N>(AccessRights::RW)?;
|
||||
self.env.init::<N>(AccessRights::RW, ctx.task_executor.clone())?;
|
||||
|
||||
let mut provider_rw = provider_factory.database_provider_rw()?;
|
||||
let components = components(provider_factory.chain_spec());
|
||||
@@ -171,6 +172,7 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>
|
||||
provider_factory.chain_spec(),
|
||||
p2p_secret_key,
|
||||
default_peers_path,
|
||||
runtime.clone(),
|
||||
)
|
||||
.build(provider_factory.clone())
|
||||
.start_network()
|
||||
@@ -226,6 +228,7 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>
|
||||
provider_factory.chain_spec(),
|
||||
p2p_secret_key,
|
||||
default_peers_path,
|
||||
runtime.clone(),
|
||||
)
|
||||
.build(provider_factory.clone())
|
||||
.start_network()
|
||||
|
||||
@@ -46,12 +46,14 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C>
|
||||
pub async fn execute<N: CliNodeTypes<ChainSpec = C::ChainSpec>, F, Comp>(
|
||||
self,
|
||||
components: F,
|
||||
runtime: reth_tasks::Runtime,
|
||||
) -> eyre::Result<()>
|
||||
where
|
||||
Comp: CliNodeComponents<N>,
|
||||
F: FnOnce(Arc<C::ChainSpec>) -> Comp,
|
||||
{
|
||||
let Environment { provider_factory, config, .. } = self.env.init::<N>(AccessRights::RW)?;
|
||||
let Environment { provider_factory, config, .. } =
|
||||
self.env.init::<N>(AccessRights::RW, runtime)?;
|
||||
|
||||
let target = self.command.unwind_target(provider_factory.clone())?;
|
||||
|
||||
|
||||
@@ -47,6 +47,11 @@ impl CliRunner {
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns a clone of the underlying [`Runtime`](reth_tasks::Runtime).
|
||||
pub fn runtime(&self) -> reth_tasks::Runtime {
|
||||
self.runtime.clone()
|
||||
}
|
||||
|
||||
/// Executes an async block on the runtime and blocks until completion.
|
||||
pub fn block_on<F, T>(&self, fut: F) -> T
|
||||
where
|
||||
|
||||
@@ -112,7 +112,7 @@ where
|
||||
Vec<NodeHelperType<N, BlockchainProvider<NodeTypesWithDBAdapter<N, TmpDB>>>>,
|
||||
Wallet,
|
||||
)> {
|
||||
let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current())?;
|
||||
let runtime = Runtime::test();
|
||||
|
||||
let network_config = NetworkArgs {
|
||||
discovery: DiscoveryArgs { disable_discovery: true, ..DiscoveryArgs::default() },
|
||||
|
||||
@@ -15,7 +15,6 @@ use reth_provider::{
|
||||
};
|
||||
use reth_rpc_server_types::RpcModuleSelection;
|
||||
use reth_stages_types::StageId;
|
||||
use reth_tasks::Runtime;
|
||||
use std::{path::Path, sync::Arc};
|
||||
use tempfile::TempDir;
|
||||
use tracing::{debug, info, span, Level};
|
||||
@@ -66,7 +65,7 @@ pub async fn setup_engine_with_chain_import(
|
||||
+ Copy
|
||||
+ 'static,
|
||||
) -> eyre::Result<ChainImportResult> {
|
||||
let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current())?;
|
||||
let runtime = reth_tasks::Runtime::test();
|
||||
|
||||
let network_config = NetworkArgs {
|
||||
discovery: DiscoveryArgs { disable_discovery: true, ..DiscoveryArgs::default() },
|
||||
@@ -149,6 +148,7 @@ pub async fn setup_engine_with_chain_import(
|
||||
&config,
|
||||
evm_config,
|
||||
consensus,
|
||||
runtime.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -343,6 +343,7 @@ mod tests {
|
||||
let evm_config = reth_node_ethereum::EthEvmConfig::new(chain_spec.clone());
|
||||
// Use NoopConsensus to skip gas limit validation for test imports
|
||||
let consensus = reth_consensus::noop::NoopConsensus::arc();
|
||||
let runtime = reth_tasks::Runtime::test();
|
||||
|
||||
let result = import_blocks_from_file(
|
||||
&rlp_path,
|
||||
@@ -351,6 +352,7 @@ mod tests {
|
||||
&config,
|
||||
evm_config,
|
||||
consensus,
|
||||
runtime,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -509,6 +511,7 @@ mod tests {
|
||||
let evm_config = reth_node_ethereum::EthEvmConfig::new(chain_spec.clone());
|
||||
// Use NoopConsensus to skip gas limit validation for test imports
|
||||
let consensus = reth_consensus::noop::NoopConsensus::arc();
|
||||
let runtime = reth_tasks::Runtime::test();
|
||||
|
||||
let result = import_blocks_from_file(
|
||||
&rlp_path,
|
||||
@@ -517,6 +520,7 @@ mod tests {
|
||||
&config,
|
||||
evm_config,
|
||||
consensus,
|
||||
runtime,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -352,14 +352,23 @@ where
|
||||
/// produce fewer state changes and most workers would be idle overhead.
|
||||
const SMALL_BLOCK_PROOF_WORKER_TX_THRESHOLD: usize = 30;
|
||||
|
||||
/// Transaction count threshold below which sequential signature recovery is used.
|
||||
/// Transaction count threshold below which sequential conversion is used.
|
||||
///
|
||||
/// For blocks with fewer than this many transactions, the rayon parallel iterator overhead
|
||||
/// (work-stealing setup, channel-based reorder) exceeds the cost of sequential ECDSA
|
||||
/// recovery. Inspired by Nethermind's `RecoverSignature` which uses sequential `foreach`
|
||||
/// for small blocks.
|
||||
/// (work-stealing setup, channel-based reorder) exceeds the cost of sequential conversion.
|
||||
/// Inspired by Nethermind's `RecoverSignature` which uses sequential `foreach` for small
|
||||
/// blocks.
|
||||
const SMALL_BLOCK_TX_THRESHOLD: usize = 30;
|
||||
|
||||
/// Number of leading transactions to convert sequentially before entering the rayon
|
||||
/// parallel path.
|
||||
///
|
||||
/// Rayon's work-stealing does not guarantee that index 0 is processed first, so the
|
||||
/// ordered consumer can block for up to ~1ms waiting for the first slot. By converting
|
||||
/// a small head sequentially and sending it immediately, execution can start without
|
||||
/// waiting for rayon scheduling.
|
||||
const PARALLEL_PREFETCH_COUNT: usize = 4;
|
||||
|
||||
/// Returns the multiproof chunk size adapted to the block's gas usage.
|
||||
///
|
||||
/// For blocks with ≤20M gas used, a smaller chunk size (30) yields better throughput.
|
||||
@@ -382,7 +391,7 @@ where
|
||||
///
|
||||
/// For blocks with fewer than [`Self::SMALL_BLOCK_TX_THRESHOLD`] transactions, uses
|
||||
/// sequential iteration to avoid rayon overhead. For larger blocks, uses rayon parallel
|
||||
/// iteration with [`ForEachOrdered`] to recover signatures in parallel while streaming
|
||||
/// iteration with [`ForEachOrdered`] to convert transactions in parallel while streaming
|
||||
/// results to execution in the original transaction order.
|
||||
#[expect(clippy::type_complexity)]
|
||||
#[instrument(level = "debug", target = "engine::tree::payload_processor", skip_all)]
|
||||
@@ -401,7 +410,7 @@ where
|
||||
// Empty block — nothing to do.
|
||||
} else if transaction_count < Self::SMALL_BLOCK_TX_THRESHOLD {
|
||||
// Sequential path for small blocks — avoids rayon work-stealing setup and
|
||||
// channel-based reorder overhead when it costs more than the ECDSA recovery itself.
|
||||
// channel-based reorder overhead when it costs more than sequential conversion.
|
||||
debug!(
|
||||
target: "engine::tree::payload_processor",
|
||||
transaction_count,
|
||||
@@ -409,36 +418,38 @@ where
|
||||
);
|
||||
self.executor.spawn_blocking(move || {
|
||||
let _enter =
|
||||
debug_span!(target: "engine::tree::payload_processor", "tx iterator").entered();
|
||||
debug_span!(target: "engine::tree::payload_processor", "tx_iterator").entered();
|
||||
let (transactions, convert) = transactions.into_parts();
|
||||
for (idx, tx) in transactions.into_iter().enumerate() {
|
||||
let tx = convert.convert(tx);
|
||||
let tx = tx.map(|tx| {
|
||||
let (tx_env, tx) = tx.into_parts();
|
||||
WithTxEnv { tx_env, tx: Arc::new(tx) }
|
||||
});
|
||||
if let Ok(tx) = &tx {
|
||||
let _ = prewarm_tx.send((idx, tx.clone()));
|
||||
}
|
||||
let _ = execute_tx.send(tx);
|
||||
}
|
||||
convert_serial(transactions.into_iter(), &convert, &prewarm_tx, &execute_tx);
|
||||
});
|
||||
} else {
|
||||
// Parallel path — recover signatures in parallel on rayon, stream results
|
||||
// to execution in order via `for_each_ordered`.
|
||||
//
|
||||
// To avoid a ~1ms stall waiting for rayon to schedule index 0, the first
|
||||
// few transactions are recovered sequentially and sent immediately before
|
||||
// entering the parallel iterator for the remainder.
|
||||
let prefetch = Self::PARALLEL_PREFETCH_COUNT.min(transaction_count);
|
||||
self.executor.spawn_blocking(move || {
|
||||
let _enter =
|
||||
debug_span!(target: "engine::tree::payload_processor", "tx iterator").entered();
|
||||
debug_span!(target: "engine::tree::payload_processor", "tx_iterator").entered();
|
||||
let (transactions, convert) = transactions.into_parts();
|
||||
transactions
|
||||
.into_par_iter()
|
||||
let mut all: Vec<_> = transactions.into_iter().collect();
|
||||
let rest = all.split_off(prefetch.min(all.len()));
|
||||
|
||||
// Convert the first few transactions sequentially so execution can
|
||||
// start immediately without waiting for rayon work-stealing.
|
||||
convert_serial(all.into_iter(), &convert, &prewarm_tx, &execute_tx);
|
||||
|
||||
// Convert the remaining transactions in parallel.
|
||||
rest.into_par_iter()
|
||||
.enumerate()
|
||||
.map(|(idx, tx)| {
|
||||
.map(|(i, tx)| {
|
||||
let idx = i + prefetch;
|
||||
let tx = convert.convert(tx);
|
||||
tx.map(|tx| {
|
||||
let (tx_env, tx) = tx.into_parts();
|
||||
let tx = WithTxEnv { tx_env, tx: Arc::new(tx) };
|
||||
// Send to prewarming out of order with the original index.
|
||||
let _ = prewarm_tx.send((idx, tx.clone()));
|
||||
tx
|
||||
})
|
||||
@@ -496,7 +507,7 @@ where
|
||||
|
||||
{
|
||||
let to_prewarm_task = to_prewarm_task.clone();
|
||||
self.executor.spawn_blocking(move || {
|
||||
self.executor.spawn_blocking_named("prewarm", move || {
|
||||
let mode = if skip_prewarm {
|
||||
PrewarmMode::Skipped
|
||||
} else if let Some(bal) = bal {
|
||||
@@ -550,7 +561,7 @@ where
|
||||
let executor = self.executor.clone();
|
||||
|
||||
let parent_span = Span::current();
|
||||
self.executor.spawn_blocking(move || {
|
||||
self.executor.spawn_blocking_named("sparse-trie", move || {
|
||||
let _enter = debug_span!(target: "engine::tree::payload_processor", parent: parent_span, "sparse_trie_task")
|
||||
.entered();
|
||||
|
||||
@@ -708,6 +719,30 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts transactions sequentially and sends them to the prewarm and execute channels.
|
||||
fn convert_serial<RawTx, Tx, TxEnv, InnerTx, Recovered, Err, C>(
|
||||
iter: impl Iterator<Item = RawTx>,
|
||||
convert: &C,
|
||||
prewarm_tx: &mpsc::SyncSender<(usize, WithTxEnv<TxEnv, Recovered>)>,
|
||||
execute_tx: &mpsc::SyncSender<Result<WithTxEnv<TxEnv, Recovered>, Err>>,
|
||||
) where
|
||||
Tx: ExecutableTxParts<TxEnv, InnerTx, Recovered = Recovered>,
|
||||
TxEnv: Clone,
|
||||
C: ConvertTx<RawTx, Tx = Tx, Error = Err>,
|
||||
{
|
||||
for (idx, raw_tx) in iter.enumerate() {
|
||||
let tx = convert.convert(raw_tx);
|
||||
let tx = tx.map(|tx| {
|
||||
let (tx_env, tx) = tx.into_parts();
|
||||
WithTxEnv { tx_env, tx: Arc::new(tx) }
|
||||
});
|
||||
if let Ok(tx) = &tx {
|
||||
let _ = prewarm_tx.send((idx, tx.clone()));
|
||||
}
|
||||
let _ = execute_tx.send(tx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle to all the spawned tasks.
|
||||
///
|
||||
/// Generic over `R` (receipt type) to allow sharing `Arc<ExecutionOutcome<R>>` with the
|
||||
|
||||
@@ -139,7 +139,7 @@ where
|
||||
let ctx = self.ctx.clone();
|
||||
let span = Span::current();
|
||||
|
||||
self.executor.spawn_blocking(move || {
|
||||
self.executor.spawn_blocking_named("prewarm-spawn", move || {
|
||||
let _enter = debug_span!(target: "engine::tree::payload_processor::prewarm", parent: span, "spawn_all").entered();
|
||||
|
||||
let pool_threads = executor.prewarming_pool().current_num_threads();
|
||||
@@ -624,7 +624,7 @@ where
|
||||
let to_multi_proof = to_multi_proof.clone();
|
||||
let done_tx = done_tx.clone();
|
||||
let rx = tx_receiver.clone();
|
||||
let span = debug_span!(target: "engine::tree::payload_processor::prewarm", parent: &span, "prewarm worker", idx);
|
||||
let span = debug_span!(target: "engine::tree::payload_processor::prewarm", parent: &span, "prewarm_worker", idx);
|
||||
task_executor.prewarming_pool().spawn(move || {
|
||||
let _enter = span.entered();
|
||||
ctx.transact_batch(rx, to_multi_proof, done_tx);
|
||||
|
||||
@@ -31,7 +31,7 @@ use reth_trie_sparse::{
|
||||
SparseTrie,
|
||||
};
|
||||
use revm_primitives::{hash_map::Entry, B256Map};
|
||||
use tracing::{debug, debug_span, error, instrument};
|
||||
use tracing::{debug, debug_span, error, instrument, trace_span};
|
||||
|
||||
/// Maximum number of pending/prewarm updates that we accumulate in memory before actually applying.
|
||||
const MAX_PENDING_UPDATES: usize = 100;
|
||||
@@ -118,7 +118,7 @@ where
|
||||
let (hashed_state_tx, hashed_state_rx) = crossbeam_channel::unbounded();
|
||||
|
||||
let parent_span = tracing::Span::current();
|
||||
executor.spawn_blocking(move || {
|
||||
executor.spawn_blocking_named("trie-hashing", move || {
|
||||
let _span = debug_span!(parent: parent_span, "run_hashing_task").entered();
|
||||
Self::run_hashing_task(updates, hashed_state_tx)
|
||||
});
|
||||
@@ -158,7 +158,7 @@ where
|
||||
SparseTrieTaskMessage::PrefetchProofs(targets)
|
||||
}
|
||||
MultiProofMessage::StateUpdate(_, state) => {
|
||||
let _span = debug_span!(target: "engine::tree::payload_processor::sparse_trie", "hashing state update", n = state.len()).entered();
|
||||
let _span = debug_span!(target: "engine::tree::payload_processor::sparse_trie", "hashing_state_update", n = state.len()).entered();
|
||||
let hashed = evm_state_to_hashed_post_state(state);
|
||||
SparseTrieTaskMessage::HashedState(hashed)
|
||||
}
|
||||
@@ -478,7 +478,7 @@ where
|
||||
})
|
||||
.par_bridge_buffered()
|
||||
.map(|(address, updates, mut fetched, mut trie)| {
|
||||
let _enter = debug_span!(target: "engine::tree::payload_processor::sparse_trie", parent: &span, "storage trie leaf updates", a=%address).entered();
|
||||
let _enter = trace_span!(target: "engine::tree::payload_processor::sparse_trie", parent: &span, "storage_trie_leaf_updates", a=%address).entered();
|
||||
let mut targets = Vec::new();
|
||||
|
||||
trie.update_leaves(updates, |path, min_len| match fetched.entry(path) {
|
||||
@@ -577,7 +577,7 @@ where
|
||||
})
|
||||
.par_bridge_buffered()
|
||||
.for_each(|(address, trie)| {
|
||||
let _enter = debug_span!(target: "engine::tree::payload_processor::sparse_trie", parent: &span, "storage root", ?address).entered();
|
||||
let _enter = debug_span!(target: "engine::tree::payload_processor::sparse_trie", parent: &span, "storage_root", ?address).entered();
|
||||
trie.root().expect("updates are drained, trie should be revealed by now");
|
||||
});
|
||||
drop(span);
|
||||
@@ -594,18 +594,7 @@ where
|
||||
return true;
|
||||
} else if let Some(account) = account.take() {
|
||||
let storage_root = self.trie.storage_root(addr).expect("updates are drained, storage trie should be revealed by now");
|
||||
let encoded = if account.is_none_or(|account| account.is_empty()) &&
|
||||
storage_root == EMPTY_ROOT_HASH
|
||||
{
|
||||
Vec::new()
|
||||
} else {
|
||||
account_rlp_buf.clear();
|
||||
account
|
||||
.unwrap_or_default()
|
||||
.into_trie_account(storage_root)
|
||||
.encode(account_rlp_buf);
|
||||
account_rlp_buf.clone()
|
||||
};
|
||||
let encoded = encode_account_leaf_value(account, storage_root, account_rlp_buf);
|
||||
self.account_updates.insert(*addr, LeafUpdate::Changed(encoded));
|
||||
num_promoted += 1;
|
||||
return false;
|
||||
@@ -613,13 +602,13 @@ where
|
||||
}
|
||||
|
||||
// Get the current account state either from the trie or from latest account update.
|
||||
let trie_account = if let Some(LeafUpdate::Changed(encoded)) = self.account_updates.get(addr) {
|
||||
Some(encoded).filter(|encoded| !encoded.is_empty())
|
||||
} else if !self.account_updates.contains_key(addr) {
|
||||
self.trie.get_account_value(addr)
|
||||
} else {
|
||||
let trie_account = match self.account_updates.get(addr) {
|
||||
Some(LeafUpdate::Changed(encoded)) => {
|
||||
Some(encoded).filter(|encoded| !encoded.is_empty())
|
||||
}
|
||||
// Needs to be revealed first
|
||||
return true;
|
||||
Some(LeafUpdate::Touched) => return true,
|
||||
None => self.trie.get_account_value(addr),
|
||||
};
|
||||
|
||||
let trie_account = trie_account.map(|value| TrieAccount::decode(&mut &value[..]).expect("invalid account RLP"));
|
||||
@@ -636,13 +625,7 @@ where
|
||||
(trie_account.map(Into::into), self.trie.storage_root(addr).expect("account had storage updates that were applied to its trie, storage root must be revealed by now"))
|
||||
};
|
||||
|
||||
let encoded = if account.is_none_or(|account| account.is_empty()) && storage_root == EMPTY_ROOT_HASH {
|
||||
Vec::new()
|
||||
} else {
|
||||
account_rlp_buf.clear();
|
||||
account.unwrap_or_default().into_trie_account(storage_root).encode(account_rlp_buf);
|
||||
account_rlp_buf.clone()
|
||||
};
|
||||
let encoded = encode_account_leaf_value(account, storage_root, account_rlp_buf);
|
||||
self.account_updates.insert(*addr, LeafUpdate::Changed(encoded));
|
||||
num_promoted += 1;
|
||||
|
||||
@@ -699,6 +682,21 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// RLP-encodes the account as a [`TrieAccount`] leaf value, or returns empty for deletions.
|
||||
fn encode_account_leaf_value(
|
||||
account: Option<Account>,
|
||||
storage_root: B256,
|
||||
account_rlp_buf: &mut Vec<u8>,
|
||||
) -> Vec<u8> {
|
||||
if account.is_none_or(|account| account.is_empty()) && storage_root == EMPTY_ROOT_HASH {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
account_rlp_buf.clear();
|
||||
account.unwrap_or_default().into_trie_account(storage_root).encode(account_rlp_buf);
|
||||
account_rlp_buf.clone()
|
||||
}
|
||||
|
||||
/// Message type for the sparse trie task.
|
||||
enum SparseTrieTaskMessage {
|
||||
/// A hashed state update ready to be processed.
|
||||
@@ -726,7 +724,7 @@ pub struct StateRootComputeOutcome {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloy_primitives::{keccak256, Address, U256};
|
||||
use alloy_primitives::{keccak256, Address, B256, U256};
|
||||
use reth_trie_sparse::ParallelSparseTrie;
|
||||
|
||||
#[test]
|
||||
@@ -777,4 +775,33 @@ mod tests {
|
||||
assert!(hashed_state_rx.recv().is_err());
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_account_leaf_value_empty_account_and_empty_root_is_empty() {
|
||||
let mut account_rlp_buf = vec![0xAB];
|
||||
let encoded = encode_account_leaf_value(None, EMPTY_ROOT_HASH, &mut account_rlp_buf);
|
||||
|
||||
assert!(encoded.is_empty());
|
||||
// Early return should not touch the caller's buffer.
|
||||
assert_eq!(account_rlp_buf, vec![0xAB]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_account_leaf_value_non_empty_account_is_rlp() {
|
||||
let storage_root = B256::from([0x99; 32]);
|
||||
let account = Some(Account {
|
||||
nonce: 7,
|
||||
balance: U256::from(42),
|
||||
bytecode_hash: Some(B256::from([0xAA; 32])),
|
||||
});
|
||||
let mut account_rlp_buf = vec![0x00, 0x01];
|
||||
|
||||
let encoded = encode_account_leaf_value(account, storage_root, &mut account_rlp_buf);
|
||||
let decoded = TrieAccount::decode(&mut &encoded[..]).expect("valid account RLP");
|
||||
|
||||
assert_eq!(decoded.nonce, 7);
|
||||
assert_eq!(decoded.balance, U256::from(42));
|
||||
assert_eq!(decoded.storage_root, storage_root);
|
||||
assert_eq!(account_rlp_buf, encoded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@ use std::{
|
||||
};
|
||||
use tracing::{debug, debug_span, error, info, instrument, trace, warn};
|
||||
|
||||
/// Handle to a [`HashedPostState`] computed on a background thread.
|
||||
type LazyHashedPostState = reth_tasks::LazyHandle<HashedPostState>;
|
||||
|
||||
/// Context providing access to tree state during validation.
|
||||
///
|
||||
/// This context is provided to the [`EngineValidator`] and includes the state of the tree's
|
||||
@@ -338,12 +341,16 @@ where
|
||||
BlockOrPayload::Payload(_) => {
|
||||
let payload_clone = input.clone();
|
||||
let validator = self.validator.clone();
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
self.payload_processor.executor().spawn_blocking(move || {
|
||||
let BlockOrPayload::Payload(payload) = payload_clone else { unreachable!() };
|
||||
let _ = tx.send(validator.convert_payload_to_block(payload));
|
||||
});
|
||||
Either::Left(rx)
|
||||
let handle = self.payload_processor.executor().spawn_blocking_named(
|
||||
"payload-convert",
|
||||
move || {
|
||||
let BlockOrPayload::Payload(payload) = payload_clone else {
|
||||
unreachable!()
|
||||
};
|
||||
validator.convert_payload_to_block(payload)
|
||||
},
|
||||
);
|
||||
Either::Left(handle)
|
||||
}
|
||||
BlockOrPayload::Block(_) => Either::Right(()),
|
||||
};
|
||||
@@ -353,9 +360,7 @@ where
|
||||
let convert_to_block =
|
||||
move |input: BlockOrPayload<T>| -> Result<SealedBlock<N::Block>, NewPayloadError> {
|
||||
match convert_to_block {
|
||||
Either::Left(rx) => rx.blocking_recv().map_err(|_| {
|
||||
NewPayloadError::Other("block conversion task panicked".into())
|
||||
})?,
|
||||
Either::Left(handle) => handle.try_into_inner().expect("sole handle"),
|
||||
Either::Right(()) => {
|
||||
let BlockOrPayload::Block(block) = input else { unreachable!() };
|
||||
Ok(block)
|
||||
@@ -395,7 +400,7 @@ where
|
||||
|
||||
trace!(target: "engine::tree::payload_validator", "Fetching block state provider");
|
||||
let _enter =
|
||||
debug_span!(target: "engine::tree::payload_validator", "state provider").entered();
|
||||
debug_span!(target: "engine::tree::payload_validator", "state_provider").entered();
|
||||
let Some(provider_builder) =
|
||||
ensure_ok!(self.state_provider_builder(parent_hash, ctx.state()))
|
||||
else {
|
||||
@@ -420,7 +425,7 @@ where
|
||||
.into())
|
||||
};
|
||||
|
||||
let evm_env = debug_span!(target: "engine::tree::payload_validator", "evm env")
|
||||
let evm_env = debug_span!(target: "engine::tree::payload_validator", "evm_env")
|
||||
.in_scope(|| self.evm_env_for(&input))
|
||||
.map_err(NewPayloadError::other)?;
|
||||
|
||||
@@ -507,6 +512,21 @@ where
|
||||
// needed. This frees up resources while state root computation continues.
|
||||
let valid_block_tx = handle.terminate_caching(Some(output.clone()));
|
||||
|
||||
// Spawn hashed post state computation in background so it runs concurrently with
|
||||
// block conversion and receipt root computation. This is a pure CPU-bound task
|
||||
// (keccak256 hashing of all changed addresses and storage slots).
|
||||
let hashed_state_output = output.clone();
|
||||
let hashed_state_provider = self.provider.clone();
|
||||
let hashed_state: LazyHashedPostState =
|
||||
self.payload_processor.executor().spawn_blocking_named("hash-post-state", move || {
|
||||
let _span = debug_span!(
|
||||
target: "engine::tree::payload_validator",
|
||||
"hashed_post_state",
|
||||
)
|
||||
.entered();
|
||||
hashed_state_provider.hashed_post_state(&hashed_state_output.state)
|
||||
});
|
||||
|
||||
let block = convert_to_block(input)?.with_senders(senders);
|
||||
|
||||
// Wait for the receipt root computation to complete.
|
||||
@@ -526,7 +546,8 @@ where
|
||||
&parent_block,
|
||||
&output,
|
||||
&mut ctx,
|
||||
receipt_root_bloom
|
||||
receipt_root_bloom,
|
||||
hashed_state,
|
||||
),
|
||||
block
|
||||
);
|
||||
@@ -762,20 +783,27 @@ where
|
||||
{
|
||||
debug!(target: "engine::tree::payload_validator", "Executing block");
|
||||
|
||||
let mut db = State::builder()
|
||||
.with_database(StateProviderDatabase::new(state_provider))
|
||||
.with_bundle_update()
|
||||
.without_state_clear()
|
||||
.build();
|
||||
let mut db = debug_span!(target: "engine::tree", "build_state_db").in_scope(|| {
|
||||
State::builder()
|
||||
.with_database(StateProviderDatabase::new(state_provider))
|
||||
.with_bundle_update()
|
||||
.without_state_clear()
|
||||
.build()
|
||||
});
|
||||
|
||||
let spec_id = *env.evm_env.spec_id();
|
||||
let evm = self.evm_config.evm_with_env(&mut db, env.evm_env);
|
||||
let ctx =
|
||||
self.execution_ctx_for(input).map_err(|e| InsertBlockErrorKind::Other(Box::new(e)))?;
|
||||
let mut executor = self.evm_config.create_executor(evm, ctx);
|
||||
let (spec_id, mut executor) = {
|
||||
let _span = debug_span!(target: "engine::tree", "create_evm").entered();
|
||||
let spec_id = *env.evm_env.spec_id();
|
||||
let evm = self.evm_config.evm_with_env(&mut db, env.evm_env);
|
||||
let ctx = self
|
||||
.execution_ctx_for(input)
|
||||
.map_err(|e| InsertBlockErrorKind::Other(Box::new(e)))?;
|
||||
let executor = self.evm_config.create_executor(evm, ctx);
|
||||
(spec_id, executor)
|
||||
};
|
||||
|
||||
if !self.config.precompile_cache_disabled() {
|
||||
// Only cache pure precompiles to avoid issues with stateful precompiles
|
||||
let _span = debug_span!(target: "engine::tree", "setup_precompile_cache").entered();
|
||||
executor.evm_mut().precompiles_mut().map_pure_precompiles(|address, precompile| {
|
||||
let metrics = self
|
||||
.precompile_cache_metrics
|
||||
@@ -797,7 +825,9 @@ where
|
||||
let (receipt_tx, receipt_rx) = crossbeam_channel::unbounded();
|
||||
let (result_tx, result_rx) = tokio::sync::oneshot::channel();
|
||||
let task_handle = ReceiptRootTaskHandle::new(receipt_rx, result_tx);
|
||||
self.payload_processor.executor().spawn_blocking(move || task_handle.run(receipts_len));
|
||||
self.payload_processor
|
||||
.executor()
|
||||
.spawn_blocking_named("receipt-root", move || task_handle.run(receipts_len));
|
||||
|
||||
let transaction_count = input.transaction_count();
|
||||
let executor = executor.with_state_hook(Some(Box::new(handle.state_hook())));
|
||||
@@ -815,13 +845,13 @@ where
|
||||
|
||||
// Finish execution and get the result
|
||||
let post_exec_start = Instant::now();
|
||||
let (_evm, result) = debug_span!(target: "engine::tree", "finish")
|
||||
let (_evm, result) = debug_span!(target: "engine::tree", "BlockExecutor::finish")
|
||||
.in_scope(|| executor.finish())
|
||||
.map(|(evm, result)| (evm.into_db(), result))?;
|
||||
self.metrics.record_post_execution(post_exec_start.elapsed());
|
||||
|
||||
// Merge transitions into bundle state
|
||||
debug_span!(target: "engine::tree", "merge transitions")
|
||||
debug_span!(target: "engine::tree", "merge_transitions")
|
||||
.in_scope(|| db.merge_transitions(BundleRetention::Reverts));
|
||||
|
||||
let output = BlockExecutionOutput { result, state: db.take_bundle() };
|
||||
@@ -860,7 +890,7 @@ where
|
||||
|
||||
// Apply pre-execution changes (e.g., beacon root update)
|
||||
let pre_exec_start = Instant::now();
|
||||
debug_span!(target: "engine::tree", "pre execution")
|
||||
debug_span!(target: "engine::tree", "pre_execution")
|
||||
.in_scope(|| executor.apply_pre_execution_changes())?;
|
||||
self.metrics.record_pre_execution(pre_exec_start.elapsed());
|
||||
|
||||
@@ -924,8 +954,9 @@ where
|
||||
fn compute_state_root_parallel(
|
||||
&self,
|
||||
overlay_factory: OverlayStateProviderFactory<P>,
|
||||
hashed_state: &HashedPostState,
|
||||
hashed_state: &LazyHashedPostState,
|
||||
) -> Result<(B256, TrieUpdates), ParallelStateRootError> {
|
||||
let hashed_state = hashed_state.get();
|
||||
// The `hashed_state` argument will be taken into account as part of the overlay, but we
|
||||
// need to use the prefix sets which were generated from it to indicate to the
|
||||
// ParallelStateRoot which parts of the trie need to be recomputed.
|
||||
@@ -943,8 +974,9 @@ where
|
||||
/// trie updates for this block.
|
||||
fn compute_state_root_serial(
|
||||
overlay_factory: OverlayStateProviderFactory<P>,
|
||||
hashed_state: &HashedPostState,
|
||||
hashed_state: &LazyHashedPostState,
|
||||
) -> ProviderResult<(B256, TrieUpdates)> {
|
||||
let hashed_state = hashed_state.get();
|
||||
// The `hashed_state` argument will be taken into account as part of the overlay, but we
|
||||
// need to use the prefix sets which were generated from it to indicate to the
|
||||
// StateRoot which parts of the trie need to be recomputed.
|
||||
@@ -982,7 +1014,7 @@ where
|
||||
&self,
|
||||
handle: &mut PayloadHandle<Tx, Err, R>,
|
||||
overlay_factory: OverlayStateProviderFactory<P>,
|
||||
hashed_state: &HashedPostState,
|
||||
hashed_state: &LazyHashedPostState,
|
||||
) -> ProviderResult<Result<StateRootComputeOutcome, ParallelStateRootError>> {
|
||||
let Some(timeout) = self.config.state_root_task_timeout() else {
|
||||
return Ok(handle.state_root());
|
||||
@@ -1008,7 +1040,7 @@ where
|
||||
|
||||
let seq_overlay = overlay_factory;
|
||||
let seq_hashed_state = hashed_state.clone();
|
||||
self.payload_processor.executor().spawn_blocking(move || {
|
||||
self.payload_processor.executor().spawn_blocking_named("serial-root", move || {
|
||||
let result = Self::compute_state_root_serial(seq_overlay, &seq_hashed_state);
|
||||
let _ = seq_tx.send(result);
|
||||
});
|
||||
@@ -1074,7 +1106,7 @@ where
|
||||
fn compare_trie_updates_with_serial(
|
||||
&self,
|
||||
overlay_factory: OverlayStateProviderFactory<P>,
|
||||
hashed_state: &HashedPostState,
|
||||
hashed_state: &LazyHashedPostState,
|
||||
task_trie_updates: TrieUpdates,
|
||||
) -> bool {
|
||||
debug!(target: "engine::tree::payload_validator", "Comparing trie updates with serial computation");
|
||||
@@ -1090,17 +1122,20 @@ where
|
||||
// Get a database provider to use as trie cursor factory
|
||||
match overlay_factory.database_provider_ro() {
|
||||
Ok(provider) => {
|
||||
if let Err(err) = super::trie_updates::compare_trie_updates(
|
||||
match super::trie_updates::compare_trie_updates(
|
||||
&provider,
|
||||
task_trie_updates,
|
||||
serial_trie_updates,
|
||||
) {
|
||||
warn!(
|
||||
target: "engine::tree::payload_validator",
|
||||
%err,
|
||||
"Error comparing trie updates"
|
||||
);
|
||||
return true;
|
||||
Ok(has_diff) => return has_diff,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
target: "engine::tree::payload_validator",
|
||||
%err,
|
||||
"Error comparing trie updates"
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -1170,6 +1205,8 @@ where
|
||||
///
|
||||
/// If `receipt_root_bloom` is provided, it will be used instead of computing the receipt root
|
||||
/// and logs bloom from the receipts.
|
||||
///
|
||||
/// The `hashed_state` handle wraps the background hashed post state computation.
|
||||
#[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
|
||||
fn validate_post_execution<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
|
||||
&self,
|
||||
@@ -1178,7 +1215,8 @@ where
|
||||
output: &BlockExecutionOutput<N::Receipt>,
|
||||
ctx: &mut TreeCtx<'_, N>,
|
||||
receipt_root_bloom: Option<ReceiptRootBloom>,
|
||||
) -> Result<HashedPostState, InsertBlockErrorKind>
|
||||
hashed_state: LazyHashedPostState,
|
||||
) -> Result<LazyHashedPostState, InsertBlockErrorKind>
|
||||
where
|
||||
V: PayloadValidator<T, Block = N::Block>,
|
||||
{
|
||||
@@ -1213,14 +1251,10 @@ where
|
||||
}
|
||||
drop(_enter);
|
||||
|
||||
let _enter =
|
||||
debug_span!(target: "engine::tree::payload_validator", "hashed_post_state").entered();
|
||||
let hashed_state = self.provider.hashed_post_state(&output.state);
|
||||
drop(_enter);
|
||||
|
||||
let _enter = debug_span!(target: "engine::tree::payload_validator", "validate_block_post_execution_with_hashed_state").entered();
|
||||
if let Err(err) =
|
||||
self.validator.validate_block_post_execution_with_hashed_state(&hashed_state, block)
|
||||
if let Err(err) = self
|
||||
.validator
|
||||
.validate_block_post_execution_with_hashed_state(hashed_state.get(), block)
|
||||
{
|
||||
// call post-block hook
|
||||
self.on_invalid_block(parent_block, block, output, None, ctx.state_mut());
|
||||
@@ -1444,7 +1478,7 @@ where
|
||||
block: RecoveredBlock<N::Block>,
|
||||
execution_outcome: Arc<BlockExecutionOutput<N::Receipt>>,
|
||||
ctx: &TreeCtx<'_, N>,
|
||||
hashed_state: HashedPostState,
|
||||
hashed_state: LazyHashedPostState,
|
||||
trie_output: TrieUpdates,
|
||||
overlay_factory: OverlayStateProviderFactory<P>,
|
||||
) -> ExecutedBlock<N> {
|
||||
@@ -1461,12 +1495,14 @@ where
|
||||
overlay_blocks.iter().rev().map(|b| b.trie_data_handle()).collect();
|
||||
|
||||
// Create deferred handle with fallback inputs in case the background task hasn't completed.
|
||||
let deferred_trie_data = DeferredTrieData::pending(
|
||||
Arc::new(hashed_state),
|
||||
Arc::new(trie_output),
|
||||
anchor_hash,
|
||||
ancestors,
|
||||
);
|
||||
// Resolve the lazy handle into Arc<HashedPostState>. By this point the hashed state has
|
||||
// already been computed and used for state root verification, so .get() returns instantly.
|
||||
let hashed_state = match hashed_state.try_into_inner() {
|
||||
Ok(state) => Arc::new(state),
|
||||
Err(handle) => Arc::new(handle.get().clone()),
|
||||
};
|
||||
let deferred_trie_data =
|
||||
DeferredTrieData::pending(hashed_state, Arc::new(trie_output), anchor_hash, ancestors);
|
||||
let deferred_handle_task = deferred_trie_data.clone();
|
||||
let block_validation_metrics = self.metrics.block_validation.clone();
|
||||
|
||||
@@ -1552,7 +1588,9 @@ where
|
||||
};
|
||||
|
||||
// Spawn task that computes trie data asynchronously.
|
||||
self.payload_processor.executor().spawn_blocking(compute_trie_input_task);
|
||||
self.payload_processor
|
||||
.executor()
|
||||
.spawn_blocking_named("trie-input", compute_trie_input_task);
|
||||
|
||||
ExecutedBlock::with_deferred_trie_data(
|
||||
Arc::new(block),
|
||||
|
||||
@@ -101,11 +101,13 @@ impl StorageTrieUpdatesDiff {
|
||||
|
||||
/// Compares the trie updates from state root task, regular state root calculation and database,
|
||||
/// and logs the differences if there's any.
|
||||
///
|
||||
/// Returns `true` if there are differences.
|
||||
pub(crate) fn compare_trie_updates(
|
||||
trie_cursor_factory: impl TrieCursorFactory,
|
||||
task: TrieUpdates,
|
||||
regular: TrieUpdates,
|
||||
) -> Result<(), DatabaseError> {
|
||||
) -> Result<bool, DatabaseError> {
|
||||
let mut task = adjust_trie_updates(task);
|
||||
let mut regular = adjust_trie_updates(regular);
|
||||
|
||||
@@ -179,9 +181,10 @@ pub(crate) fn compare_trie_updates(
|
||||
}
|
||||
|
||||
// log differences
|
||||
let has_differences = diff.has_differences();
|
||||
diff.log_differences();
|
||||
|
||||
Ok(())
|
||||
Ok(has_differences)
|
||||
}
|
||||
|
||||
fn compare_storage_trie_updates<C: TrieCursor>(
|
||||
|
||||
@@ -106,6 +106,11 @@ where
|
||||
self.cli.logs.log_file_directory.join(chain_spec.chain().to_string());
|
||||
}
|
||||
|
||||
// Apply node-specific log defaults before initializing tracing
|
||||
if matches!(self.cli.command, Commands::Node(_)) {
|
||||
self.cli.logs.apply_node_defaults();
|
||||
}
|
||||
|
||||
self.init_tracing(&runner)?;
|
||||
|
||||
// Install the prometheus recorder to be sure to record all metrics
|
||||
@@ -144,6 +149,8 @@ where
|
||||
N: CliNodeTypes<Primitives: NodePrimitives<BlockHeader: HeaderMut>, ChainSpec: Hardforks>,
|
||||
SubCmd: ExtendedCommand + Subcommand + fmt::Debug,
|
||||
{
|
||||
let rt = runner.runtime();
|
||||
|
||||
match cli.command {
|
||||
Commands::Node(command) => {
|
||||
// Validate RPC modules using the configured validator
|
||||
@@ -169,13 +176,13 @@ where
|
||||
command.execute(ctx, FnLauncher::new::<C, Ext>(launcher))
|
||||
})
|
||||
}
|
||||
Commands::Init(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
|
||||
Commands::InitState(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
|
||||
Commands::Init(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>(rt)),
|
||||
Commands::InitState(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>(rt)),
|
||||
Commands::Import(command) => {
|
||||
runner.run_blocking_until_ctrl_c(command.execute::<N, _>(components))
|
||||
runner.run_blocking_until_ctrl_c(command.execute::<N, _>(components, rt))
|
||||
}
|
||||
Commands::ImportEra(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
|
||||
Commands::ExportEra(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
|
||||
Commands::ImportEra(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>(rt)),
|
||||
Commands::ExportEra(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>(rt)),
|
||||
Commands::DumpGenesis(command) => runner.run_blocking_until_ctrl_c(command.execute()),
|
||||
Commands::Db(command) => {
|
||||
runner.run_blocking_command_until_exit(|ctx| command.execute::<N>(ctx))
|
||||
@@ -189,7 +196,9 @@ where
|
||||
Commands::Prune(command) => runner.run_command_until_exit(|ctx| command.execute::<N>(ctx)),
|
||||
#[cfg(feature = "dev")]
|
||||
Commands::TestVectors(command) => runner.run_until_ctrl_c(command.execute()),
|
||||
Commands::ReExecute(command) => runner.run_until_ctrl_c(command.execute::<N>(components)),
|
||||
Commands::ReExecute(command) => {
|
||||
runner.run_until_ctrl_c(command.execute::<N>(components, rt))
|
||||
}
|
||||
Commands::Ext(command) => command.execute(runner),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,6 +209,12 @@ impl<
|
||||
self.logs.log_file_directory =
|
||||
self.logs.log_file_directory.join(chain_spec.chain().to_string());
|
||||
}
|
||||
|
||||
// Apply node-specific log defaults before initializing tracing
|
||||
if matches!(self.command, Commands::Node(_)) {
|
||||
self.logs.apply_node_defaults();
|
||||
}
|
||||
|
||||
let _guard = self.init_tracing(&runner, Layers::new())?;
|
||||
|
||||
// Install the prometheus recorder to be sure to record all metrics
|
||||
@@ -445,6 +451,42 @@ mod tests {
|
||||
assert!(log_dir.as_ref().ends_with(end), "{log_dir:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn log_file_max_files_defaults() {
|
||||
use reth_node_core::args::LogArgs;
|
||||
|
||||
// Node command without explicit --log.file.max-files should get Some(5) after
|
||||
// apply_node_defaults
|
||||
let mut cli = Cli::try_parse_args_from(["reth", "node"]).unwrap();
|
||||
assert!(cli.logs.log_file_max_files.is_none());
|
||||
cli.logs.apply_node_defaults();
|
||||
assert_eq!(cli.logs.log_file_max_files, Some(LogArgs::DEFAULT_MAX_LOG_FILES_NODE));
|
||||
|
||||
// Non-node command without explicit --log.file.max-files should be None and
|
||||
// effective_log_file_max_files returns 0
|
||||
let cli = Cli::try_parse_args_from(["reth", "config"]).unwrap();
|
||||
assert!(cli.logs.log_file_max_files.is_none());
|
||||
assert_eq!(cli.logs.effective_log_file_max_files(), 0);
|
||||
|
||||
// Explicitly set value should be preserved for node command
|
||||
let mut cli =
|
||||
Cli::try_parse_args_from(["reth", "node", "--log.file.max-files", "10"]).unwrap();
|
||||
assert_eq!(cli.logs.log_file_max_files, Some(10));
|
||||
cli.logs.apply_node_defaults();
|
||||
assert_eq!(cli.logs.log_file_max_files, Some(10));
|
||||
|
||||
// Explicitly set value should be preserved for non-node command
|
||||
let cli =
|
||||
Cli::try_parse_args_from(["reth", "config", "--log.file.max-files", "3"]).unwrap();
|
||||
assert_eq!(cli.logs.log_file_max_files, Some(3));
|
||||
assert_eq!(cli.logs.effective_log_file_max_files(), 3);
|
||||
|
||||
// Setting to 0 explicitly should work
|
||||
let cli = Cli::try_parse_args_from(["reth", "node", "--log.file.max-files", "0"]).unwrap();
|
||||
assert_eq!(cli.logs.log_file_max_files, Some(0));
|
||||
assert_eq!(cli.logs.effective_log_file_max_files(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_env_filter_directives() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
|
||||
@@ -55,7 +55,7 @@ where
|
||||
EthereumBuilderConfig::new()
|
||||
.with_gas_limit(gas_limit)
|
||||
.with_max_blobs_per_block(conf.max_blobs_per_block())
|
||||
.with_extra_data(conf.extra_data_bytes()),
|
||||
.with_extra_data(conf.extra_data()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ use std::{
|
||||
#[tokio::test]
|
||||
async fn can_handle_blobs() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current()).unwrap();
|
||||
let runtime = Runtime::test();
|
||||
|
||||
let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json")).unwrap();
|
||||
let chain_spec = Arc::new(
|
||||
@@ -91,7 +91,7 @@ async fn can_handle_blobs() -> eyre::Result<()> {
|
||||
#[tokio::test]
|
||||
async fn can_send_legacy_sidecar_post_activation() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current()).unwrap();
|
||||
let runtime = Runtime::test();
|
||||
|
||||
let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json")).unwrap();
|
||||
let chain_spec = Arc::new(
|
||||
@@ -144,7 +144,7 @@ async fn can_send_legacy_sidecar_post_activation() -> eyre::Result<()> {
|
||||
#[tokio::test]
|
||||
async fn blob_conversion_at_osaka() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current()).unwrap();
|
||||
let runtime = Runtime::test();
|
||||
|
||||
let current_timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
|
||||
// Osaka activates in 2 slots
|
||||
|
||||
@@ -15,7 +15,7 @@ use std::sync::Arc;
|
||||
#[tokio::test]
|
||||
async fn can_run_dev_node() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current()).unwrap();
|
||||
let runtime = Runtime::test();
|
||||
|
||||
let node_config = NodeConfig::test()
|
||||
.with_chain(custom_chain())
|
||||
@@ -36,7 +36,7 @@ async fn can_run_dev_node() -> eyre::Result<()> {
|
||||
#[tokio::test]
|
||||
async fn can_run_dev_node_custom_attributes() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current()).unwrap();
|
||||
let runtime = Runtime::test();
|
||||
|
||||
let node_config = NodeConfig::test()
|
||||
.with_chain(custom_chain())
|
||||
|
||||
@@ -57,7 +57,7 @@ async fn can_run_eth_node() -> eyre::Result<()> {
|
||||
#[cfg(unix)]
|
||||
async fn can_run_eth_node_with_auth_engine_api_over_ipc() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current()).unwrap();
|
||||
let runtime = Runtime::test();
|
||||
|
||||
// Chain spec with test allocs
|
||||
let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json")).unwrap();
|
||||
@@ -104,7 +104,7 @@ async fn can_run_eth_node_with_auth_engine_api_over_ipc() -> eyre::Result<()> {
|
||||
#[cfg(unix)]
|
||||
async fn test_failed_run_eth_node_with_no_auth_engine_api_over_ipc_opts() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current()).unwrap();
|
||||
let runtime = Runtime::test();
|
||||
|
||||
// Chain spec with test allocs
|
||||
let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json")).unwrap();
|
||||
@@ -188,7 +188,7 @@ async fn test_engine_graceful_shutdown() -> eyre::Result<()> {
|
||||
#[tokio::test]
|
||||
async fn test_testing_build_block_v1_osaka() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current()).unwrap();
|
||||
let runtime = Runtime::test();
|
||||
|
||||
let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json")).unwrap();
|
||||
let chain_spec = Arc::new(
|
||||
|
||||
@@ -24,7 +24,7 @@ use std::{sync::Arc, time::Duration};
|
||||
#[tokio::test]
|
||||
async fn maintain_txpool_stale_eviction() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current()).unwrap();
|
||||
let runtime = Runtime::test();
|
||||
|
||||
let txpool = Pool::new(
|
||||
OkValidator::default(),
|
||||
@@ -97,7 +97,7 @@ async fn maintain_txpool_stale_eviction() -> eyre::Result<()> {
|
||||
#[tokio::test]
|
||||
async fn maintain_txpool_reorg() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current()).unwrap();
|
||||
let runtime = Runtime::test();
|
||||
|
||||
let txpool = Pool::new(
|
||||
OkValidator::default(),
|
||||
@@ -229,7 +229,7 @@ async fn maintain_txpool_reorg() -> eyre::Result<()> {
|
||||
#[tokio::test]
|
||||
async fn maintain_txpool_commit() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current()).unwrap();
|
||||
let runtime = Runtime::test();
|
||||
|
||||
let txpool = Pool::new(
|
||||
OkValidator::default(),
|
||||
|
||||
@@ -29,7 +29,7 @@ async fn debug_trace_call_matches_geth_prestate_snapshot() -> Result<()> {
|
||||
let mut genesis: Genesis = MAINNET.genesis().clone();
|
||||
genesis.coinbase = address!("0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5");
|
||||
|
||||
let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current()).unwrap();
|
||||
let runtime = Runtime::test();
|
||||
|
||||
let expected_frame = expected_snapshot_frame()?;
|
||||
let prestate_mode = match &expected_frame {
|
||||
|
||||
@@ -344,7 +344,7 @@ async fn test_eth_config() -> eyre::Result<()> {
|
||||
async fn test_admin_external_ip() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current()).unwrap();
|
||||
let runtime = Runtime::test();
|
||||
|
||||
// Chain spec with test allocs
|
||||
let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json")).unwrap();
|
||||
|
||||
@@ -46,7 +46,7 @@ fn test_basic_setup() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_eth_launcher() {
|
||||
let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current()).unwrap();
|
||||
let runtime = Runtime::test();
|
||||
let config = NodeConfig::test();
|
||||
let db = create_test_rw_db();
|
||||
let _builder =
|
||||
@@ -81,7 +81,7 @@ fn test_eth_launcher_with_tokio_runtime() {
|
||||
let custom_rt = tokio::runtime::Runtime::new().expect("Failed to create tokio runtime");
|
||||
|
||||
main_rt.block_on(async {
|
||||
let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current()).unwrap();
|
||||
let runtime = Runtime::test();
|
||||
let config = NodeConfig::test();
|
||||
let db = create_test_rw_db();
|
||||
let _builder =
|
||||
|
||||
@@ -20,7 +20,7 @@ use tokio::sync::oneshot;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn testing_rpc_build_block_works() -> eyre::Result<()> {
|
||||
let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current()).unwrap();
|
||||
let runtime = Runtime::test();
|
||||
let mut rpc_args = reth_node_core::args::RpcServerArgs::default().with_http();
|
||||
rpc_args.http_api = Some(RpcModuleSelection::from_iter([RethRpcModule::Testing]));
|
||||
let tempdir = tempdir().expect("temp datadir");
|
||||
|
||||
@@ -15,35 +15,27 @@ workspace = true
|
||||
# reth
|
||||
reth-codecs = { workspace = true, optional = true }
|
||||
reth-primitives-traits.workspace = true
|
||||
reth-zstd-compressors = { workspace = true, optional = true }
|
||||
|
||||
# ethereum
|
||||
alloy-eips = { workspace = true, features = ["k256"] }
|
||||
alloy-primitives.workspace = true
|
||||
alloy-consensus = { workspace = true, features = ["serde"] }
|
||||
alloy-serde = { workspace = true, optional = true }
|
||||
alloy-rpc-types-eth = { workspace = true, optional = true }
|
||||
alloy-rlp.workspace = true
|
||||
|
||||
# misc
|
||||
arbitrary = { workspace = true, optional = true, features = ["derive"] }
|
||||
modular-bitfield = { workspace = true, optional = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
serde_with = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
derive_more.workspace = true
|
||||
arbitrary.workspace = true
|
||||
bincode.workspace = true
|
||||
proptest.workspace = true
|
||||
proptest-arbitrary-interop.workspace = true
|
||||
rand_08.workspace = true
|
||||
rand.workspace = true
|
||||
alloy-rlp.workspace = true
|
||||
reth-codecs = { workspace = true, features = ["test-utils"] }
|
||||
reth-zstd-compressors.workspace = true
|
||||
secp256k1 = { workspace = true, features = ["rand"] }
|
||||
alloy-consensus = { workspace = true, features = ["serde", "arbitrary"] }
|
||||
serde_json.workspace = true
|
||||
serde_with.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
@@ -54,27 +46,21 @@ test-utils = [
|
||||
std = [
|
||||
"alloy-consensus/std",
|
||||
"alloy-primitives/std",
|
||||
"alloy-rlp/std",
|
||||
"reth-primitives-traits/std",
|
||||
"reth-zstd-compressors?/std",
|
||||
"serde?/std",
|
||||
"alloy-eips/std",
|
||||
"derive_more/std",
|
||||
"serde_with?/std",
|
||||
"secp256k1/std",
|
||||
"alloy-rpc-types-eth?/std",
|
||||
"alloy-serde?/std",
|
||||
"alloy-rlp/std",
|
||||
"reth-zstd-compressors/std",
|
||||
"serde_json/std",
|
||||
"serde_with/std",
|
||||
]
|
||||
reth-codec = [
|
||||
"std",
|
||||
"dep:reth-codecs",
|
||||
"dep:modular-bitfield",
|
||||
"dep:reth-zstd-compressors",
|
||||
]
|
||||
arbitrary = [
|
||||
"std",
|
||||
"dep:arbitrary",
|
||||
"alloy-consensus/arbitrary",
|
||||
"alloy-consensus/k256",
|
||||
"alloy-primitives/arbitrary",
|
||||
@@ -82,10 +68,8 @@ arbitrary = [
|
||||
"reth-primitives-traits/arbitrary",
|
||||
"alloy-eips/arbitrary",
|
||||
"alloy-rpc-types-eth?/arbitrary",
|
||||
"alloy-serde?/arbitrary",
|
||||
]
|
||||
serde-bincode-compat = [
|
||||
"dep:serde_with",
|
||||
"alloy-consensus/serde-bincode-compat",
|
||||
"alloy-eips/serde-bincode-compat",
|
||||
"reth-primitives-traits/serde-bincode-compat",
|
||||
@@ -93,15 +77,12 @@ serde-bincode-compat = [
|
||||
]
|
||||
serde = [
|
||||
"dep:serde",
|
||||
"dep:alloy-serde",
|
||||
"alloy-consensus/serde",
|
||||
"alloy-eips/serde",
|
||||
"alloy-primitives/serde",
|
||||
"reth-codecs?/serde",
|
||||
"reth-primitives-traits/serde",
|
||||
"rand/serde",
|
||||
"rand_08/serde",
|
||||
"secp256k1/serde",
|
||||
"alloy-rpc-types-eth?/serde",
|
||||
"rand/serde",
|
||||
]
|
||||
rpc = ["dep:alloy-rpc-types-eth"]
|
||||
|
||||
@@ -9,15 +9,13 @@
|
||||
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
extern crate alloc;
|
||||
// Feature-only dep: activated by `reth-codec` feature for downstream consumers.
|
||||
#[cfg(feature = "reth-codec")]
|
||||
use reth_codecs as _;
|
||||
|
||||
mod receipt;
|
||||
pub use receipt::*;
|
||||
|
||||
/// Kept for consistency tests
|
||||
#[cfg(test)]
|
||||
mod transaction;
|
||||
|
||||
pub use alloy_consensus::{transaction::PooledTransaction, TxType};
|
||||
use alloy_consensus::{TxEip4844, TxEip4844WithSidecar};
|
||||
use alloy_eips::eip7594::BlobTransactionSidecarVariant;
|
||||
@@ -35,8 +33,7 @@ pub type PooledTransactionVariant =
|
||||
/// Bincode-compatible serde implementations.
|
||||
#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
|
||||
pub mod serde_bincode_compat {
|
||||
pub use super::receipt::serde_bincode_compat::*;
|
||||
pub use alloy_consensus::serde_bincode_compat::transaction::*;
|
||||
pub use alloy_consensus::serde_bincode_compat::{transaction::*, EthereumReceipt as Receipt};
|
||||
}
|
||||
|
||||
/// Type alias for the ethereum block
|
||||
|
||||
@@ -1,42 +1,8 @@
|
||||
use core::fmt::Debug;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use alloy_consensus::{
|
||||
Eip2718DecodableReceipt, Eip2718EncodableReceipt, Eip658Value, ReceiptEnvelope,
|
||||
ReceiptWithBloom, RlpDecodableReceipt, RlpEncodableReceipt, TxReceipt, TxType, Typed2718,
|
||||
};
|
||||
use alloy_eips::eip2718::{Eip2718Error, Eip2718Result, Encodable2718, IsTyped2718};
|
||||
use alloy_primitives::{Bloom, Log, B256};
|
||||
use alloy_rlp::{BufMut, Decodable, Encodable, Header, RlpDecodable, RlpEncodable};
|
||||
use reth_primitives_traits::{proofs::ordered_trie_root_with_encoder, InMemorySize};
|
||||
|
||||
/// Helper trait alias with requirements for transaction type generic to be used within [`Receipt`].
|
||||
pub trait TxTy:
|
||||
Debug
|
||||
+ Copy
|
||||
+ Eq
|
||||
+ Send
|
||||
+ Sync
|
||||
+ InMemorySize
|
||||
+ Typed2718
|
||||
+ TryFrom<u8, Error = Eip2718Error>
|
||||
+ Decodable
|
||||
+ 'static
|
||||
{
|
||||
}
|
||||
impl<T> TxTy for T where
|
||||
T: Debug
|
||||
+ Copy
|
||||
+ Eq
|
||||
+ Send
|
||||
+ Sync
|
||||
+ InMemorySize
|
||||
+ Typed2718
|
||||
+ TryFrom<u8, Error = Eip2718Error>
|
||||
+ Decodable
|
||||
+ 'static
|
||||
{
|
||||
}
|
||||
use alloy_consensus::TxType;
|
||||
pub use alloy_consensus::{EthereumReceipt, TxTy};
|
||||
use alloy_eips::eip2718::Encodable2718;
|
||||
use alloy_primitives::B256;
|
||||
use reth_primitives_traits::proofs::ordered_trie_root_with_encoder;
|
||||
|
||||
/// Raw ethereum receipt.
|
||||
pub type Receipt<T = TxType> = EthereumReceipt<T>;
|
||||
@@ -45,528 +11,25 @@ pub type Receipt<T = TxType> = EthereumReceipt<T>;
|
||||
/// Receipt representation for RPC.
|
||||
pub type RpcReceipt<T = TxType> = EthereumReceipt<T, alloy_rpc_types_eth::Log>;
|
||||
|
||||
/// Typed ethereum transaction receipt.
|
||||
/// Receipt containing result of transaction execution.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default, RlpEncodable, RlpDecodable)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
#[cfg_attr(feature = "reth-codec", reth_codecs::add_arbitrary_tests(compact, rlp))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
|
||||
pub struct EthereumReceipt<T = TxType, L = Log> {
|
||||
/// Receipt type.
|
||||
#[cfg_attr(feature = "serde", serde(rename = "type"))]
|
||||
pub tx_type: T,
|
||||
/// If transaction is executed successfully.
|
||||
///
|
||||
/// This is the `statusCode`
|
||||
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity", rename = "status"))]
|
||||
pub success: bool,
|
||||
/// Gas used
|
||||
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
|
||||
pub cumulative_gas_used: u64,
|
||||
/// Log send from contracts.
|
||||
pub logs: Vec<L>,
|
||||
/// Calculates the receipt root for a header for the reference type of [`Receipt`].
|
||||
///
|
||||
/// NOTE: Prefer `proofs::calculate_receipt_root` if you have log blooms memoized.
|
||||
pub fn calculate_receipt_root_no_memo<T: TxTy>(receipts: &[Receipt<T>]) -> B256 {
|
||||
ordered_trie_root_with_encoder(receipts, |r, buf| {
|
||||
alloy_consensus::TxReceipt::with_bloom_ref(r).encode_2718(buf)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "rpc")]
|
||||
impl<T> Receipt<T> {
|
||||
/// Converts the logs of the receipt to RPC logs.
|
||||
pub fn into_rpc(
|
||||
self,
|
||||
next_log_index: usize,
|
||||
meta: alloy_consensus::transaction::TransactionMeta,
|
||||
) -> RpcReceipt<T> {
|
||||
let Self { tx_type, success, cumulative_gas_used, logs } = self;
|
||||
let logs = alloy_rpc_types_eth::Log::collect_for_receipt(next_log_index, meta, logs);
|
||||
RpcReceipt { tx_type, success, cumulative_gas_used, logs }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TxTy> Receipt<T> {
|
||||
/// Returns length of RLP-encoded receipt fields with the given [`Bloom`] without an RLP header.
|
||||
pub fn rlp_encoded_fields_length(&self, bloom: &Bloom) -> usize {
|
||||
self.success.length() +
|
||||
self.cumulative_gas_used.length() +
|
||||
bloom.length() +
|
||||
self.logs.length()
|
||||
}
|
||||
|
||||
/// RLP-encodes receipt fields with the given [`Bloom`] without an RLP header.
|
||||
pub fn rlp_encode_fields(&self, bloom: &Bloom, out: &mut dyn BufMut) {
|
||||
self.success.encode(out);
|
||||
self.cumulative_gas_used.encode(out);
|
||||
bloom.encode(out);
|
||||
self.logs.encode(out);
|
||||
}
|
||||
|
||||
/// Returns RLP header for inner encoding.
|
||||
pub fn rlp_header_inner(&self, bloom: &Bloom) -> Header {
|
||||
Header { list: true, payload_length: self.rlp_encoded_fields_length(bloom) }
|
||||
}
|
||||
|
||||
/// RLP-decodes the receipt from the provided buffer. This does not expect a type byte or
|
||||
/// network header.
|
||||
pub fn rlp_decode_inner(
|
||||
buf: &mut &[u8],
|
||||
tx_type: T,
|
||||
) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
|
||||
let header = Header::decode(buf)?;
|
||||
if !header.list {
|
||||
return Err(alloy_rlp::Error::UnexpectedString);
|
||||
}
|
||||
|
||||
let remaining = buf.len();
|
||||
|
||||
let success = Decodable::decode(buf)?;
|
||||
let cumulative_gas_used = Decodable::decode(buf)?;
|
||||
let logs_bloom = Decodable::decode(buf)?;
|
||||
let logs = Decodable::decode(buf)?;
|
||||
|
||||
if buf.len() + header.payload_length != remaining {
|
||||
return Err(alloy_rlp::Error::UnexpectedLength);
|
||||
}
|
||||
|
||||
Ok(ReceiptWithBloom {
|
||||
receipt: Self { cumulative_gas_used, tx_type, success, logs },
|
||||
logs_bloom,
|
||||
})
|
||||
}
|
||||
|
||||
/// Calculates the receipt root for a header for the reference type of [Receipt].
|
||||
///
|
||||
/// NOTE: Prefer `proofs::calculate_receipt_root` if you have log blooms memoized.
|
||||
pub fn calculate_receipt_root_no_memo(receipts: &[Self]) -> B256 {
|
||||
ordered_trie_root_with_encoder(receipts, |r, buf| r.with_bloom_ref().encode_2718(buf))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TxTy> Eip2718EncodableReceipt for Receipt<T> {
|
||||
fn eip2718_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
|
||||
!self.tx_type.is_legacy() as usize + self.rlp_header_inner(bloom).length_with_payload()
|
||||
}
|
||||
|
||||
fn eip2718_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
|
||||
if !self.tx_type.is_legacy() {
|
||||
out.put_u8(self.tx_type.ty());
|
||||
}
|
||||
self.rlp_header_inner(bloom).encode(out);
|
||||
self.rlp_encode_fields(bloom, out);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TxTy> Eip2718DecodableReceipt for Receipt<T> {
|
||||
fn typed_decode_with_bloom(ty: u8, buf: &mut &[u8]) -> Eip2718Result<ReceiptWithBloom<Self>> {
|
||||
Ok(Self::rlp_decode_inner(buf, T::try_from(ty)?)?)
|
||||
}
|
||||
|
||||
fn fallback_decode_with_bloom(buf: &mut &[u8]) -> Eip2718Result<ReceiptWithBloom<Self>> {
|
||||
Ok(Self::rlp_decode_inner(buf, T::try_from(0)?)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TxTy> RlpEncodableReceipt for Receipt<T> {
|
||||
fn rlp_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
|
||||
let mut len = self.eip2718_encoded_length_with_bloom(bloom);
|
||||
if !self.tx_type.is_legacy() {
|
||||
len += Header {
|
||||
list: false,
|
||||
payload_length: self.eip2718_encoded_length_with_bloom(bloom),
|
||||
}
|
||||
.length();
|
||||
}
|
||||
|
||||
len
|
||||
}
|
||||
|
||||
fn rlp_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
|
||||
if !self.tx_type.is_legacy() {
|
||||
Header { list: false, payload_length: self.eip2718_encoded_length_with_bloom(bloom) }
|
||||
.encode(out);
|
||||
}
|
||||
self.eip2718_encode_with_bloom(bloom, out);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TxTy> RlpDecodableReceipt for Receipt<T> {
|
||||
fn rlp_decode_with_bloom(buf: &mut &[u8]) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
|
||||
let header_buf = &mut &**buf;
|
||||
let header = Header::decode(header_buf)?;
|
||||
|
||||
// Legacy receipt, reuse initial buffer without advancing
|
||||
if header.list {
|
||||
return Self::rlp_decode_inner(buf, T::try_from(0)?)
|
||||
}
|
||||
|
||||
// Otherwise, advance the buffer and try decoding type flag followed by receipt
|
||||
*buf = *header_buf;
|
||||
|
||||
let remaining = buf.len();
|
||||
let tx_type = T::decode(buf)?;
|
||||
let this = Self::rlp_decode_inner(buf, tx_type)?;
|
||||
|
||||
if buf.len() + header.payload_length != remaining {
|
||||
return Err(alloy_rlp::Error::UnexpectedLength);
|
||||
}
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, L> TxReceipt for EthereumReceipt<T, L>
|
||||
where
|
||||
T: TxTy,
|
||||
L: Send + Sync + Clone + Debug + Eq + AsRef<Log>,
|
||||
{
|
||||
type Log = L;
|
||||
|
||||
fn status_or_post_state(&self) -> Eip658Value {
|
||||
self.success.into()
|
||||
}
|
||||
|
||||
fn status(&self) -> bool {
|
||||
self.success
|
||||
}
|
||||
|
||||
fn bloom(&self) -> Bloom {
|
||||
alloy_primitives::logs_bloom(self.logs.iter().map(|l| l.as_ref()))
|
||||
}
|
||||
|
||||
fn cumulative_gas_used(&self) -> u64 {
|
||||
self.cumulative_gas_used
|
||||
}
|
||||
|
||||
fn logs(&self) -> &[L] {
|
||||
&self.logs
|
||||
}
|
||||
|
||||
fn into_logs(self) -> Vec<L> {
|
||||
self.logs
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TxTy> Typed2718 for Receipt<T> {
|
||||
fn ty(&self) -> u8 {
|
||||
self.tx_type.ty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TxTy> IsTyped2718 for Receipt<T> {
|
||||
fn is_type(type_id: u8) -> bool {
|
||||
<TxType as IsTyped2718>::is_type(type_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TxTy> InMemorySize for Receipt<T> {
|
||||
fn size(&self) -> usize {
|
||||
size_of::<Self>() + self.logs.iter().map(|log| log.size()).sum::<usize>()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<ReceiptEnvelope<T>> for Receipt<TxType>
|
||||
where
|
||||
T: Into<Log>,
|
||||
{
|
||||
fn from(value: ReceiptEnvelope<T>) -> Self {
|
||||
let value = value.into_primitives_receipt();
|
||||
Self {
|
||||
tx_type: value.tx_type(),
|
||||
success: value.is_success(),
|
||||
cumulative_gas_used: value.cumulative_gas_used(),
|
||||
logs: value.into_logs(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, L> From<EthereumReceipt<T, L>> for alloy_consensus::Receipt<L> {
|
||||
fn from(value: EthereumReceipt<T, L>) -> Self {
|
||||
Self {
|
||||
status: value.success.into(),
|
||||
cumulative_gas_used: value.cumulative_gas_used,
|
||||
logs: value.logs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<L> From<EthereumReceipt<TxType, L>> for ReceiptEnvelope<L>
|
||||
where
|
||||
L: Send + Sync + Clone + Debug + Eq + AsRef<Log>,
|
||||
{
|
||||
fn from(value: EthereumReceipt<TxType, L>) -> Self {
|
||||
let tx_type = value.tx_type;
|
||||
let receipt = value.into_with_bloom().map_receipt(Into::into);
|
||||
match tx_type {
|
||||
TxType::Legacy => Self::Legacy(receipt),
|
||||
TxType::Eip2930 => Self::Eip2930(receipt),
|
||||
TxType::Eip1559 => Self::Eip1559(receipt),
|
||||
TxType::Eip4844 => Self::Eip4844(receipt),
|
||||
TxType::Eip7702 => Self::Eip7702(receipt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
|
||||
pub(super) mod serde_bincode_compat {
|
||||
use alloc::{borrow::Cow, vec::Vec};
|
||||
use alloy_consensus::TxType;
|
||||
use alloy_eips::eip2718::Eip2718Error;
|
||||
use alloy_primitives::{Log, U8};
|
||||
use core::fmt::Debug;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde_with::{DeserializeAs, SerializeAs};
|
||||
|
||||
/// Bincode-compatible [`super::Receipt`] serde implementation.
|
||||
///
|
||||
/// Intended to use with the [`serde_with::serde_as`] macro in the following way:
|
||||
/// ```rust
|
||||
/// use alloy_consensus::TxType;
|
||||
/// use reth_ethereum_primitives::{serde_bincode_compat, Receipt};
|
||||
/// use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
/// use serde_with::serde_as;
|
||||
///
|
||||
/// #[serde_as]
|
||||
/// #[derive(Serialize, Deserialize)]
|
||||
/// struct Data {
|
||||
/// #[serde_as(as = "serde_bincode_compat::Receipt<'_>")]
|
||||
/// receipt: Receipt<TxType>,
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(bound(deserialize = "T: TryFrom<u8, Error = Eip2718Error>"))]
|
||||
pub struct Receipt<'a, T = TxType> {
|
||||
/// Receipt type.
|
||||
#[serde(deserialize_with = "deserde_txtype")]
|
||||
pub tx_type: T,
|
||||
/// If transaction is executed successfully.
|
||||
///
|
||||
/// This is the `statusCode`
|
||||
pub success: bool,
|
||||
/// Gas used
|
||||
pub cumulative_gas_used: u64,
|
||||
/// Log send from contracts.
|
||||
pub logs: Cow<'a, Vec<Log>>,
|
||||
}
|
||||
|
||||
/// Ensures that txtype is deserialized symmetrically as U8
|
||||
fn deserde_txtype<'de, D, T>(deserializer: D) -> Result<T, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: TryFrom<u8, Error = Eip2718Error>,
|
||||
{
|
||||
U8::deserialize(deserializer)?.to::<u8>().try_into().map_err(serde::de::Error::custom)
|
||||
}
|
||||
|
||||
impl<'a, T: Copy> From<&'a super::Receipt<T>> for Receipt<'a, T> {
|
||||
fn from(value: &'a super::Receipt<T>) -> Self {
|
||||
Self {
|
||||
tx_type: value.tx_type,
|
||||
success: value.success,
|
||||
cumulative_gas_used: value.cumulative_gas_used,
|
||||
logs: Cow::Borrowed(&value.logs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> From<Receipt<'a, T>> for super::Receipt<T> {
|
||||
fn from(value: Receipt<'a, T>) -> Self {
|
||||
Self {
|
||||
tx_type: value.tx_type,
|
||||
success: value.success,
|
||||
cumulative_gas_used: value.cumulative_gas_used,
|
||||
logs: value.logs.into_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + Serialize> SerializeAs<super::Receipt<T>> for Receipt<'_, T> {
|
||||
fn serialize_as<S>(source: &super::Receipt<T>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
Receipt::<'_>::from(source).serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T: TryFrom<u8, Error = Eip2718Error>> DeserializeAs<'de, super::Receipt<T>>
|
||||
for Receipt<'de, T>
|
||||
{
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<super::Receipt<T>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Receipt::<'_, T>::deserialize(deserializer).map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> reth_primitives_traits::serde_bincode_compat::SerdeBincodeCompat for super::Receipt<T>
|
||||
where
|
||||
T: Copy + Serialize + TryFrom<u8, Error = Eip2718Error> + Debug + 'static,
|
||||
{
|
||||
type BincodeRepr<'a> = Receipt<'a, T>;
|
||||
|
||||
fn as_repr(&self) -> Self::BincodeRepr<'_> {
|
||||
self.into()
|
||||
}
|
||||
|
||||
fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
|
||||
repr.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{receipt::serde_bincode_compat, Receipt};
|
||||
use alloy_consensus::TxType;
|
||||
use arbitrary::Arbitrary;
|
||||
use rand::Rng;
|
||||
use serde_with::serde_as;
|
||||
|
||||
#[test]
|
||||
fn test_receipt_bincode_roundtrip() {
|
||||
#[serde_as]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
struct Data {
|
||||
#[serde_as(as = "serde_bincode_compat::Receipt<'_, TxType>")]
|
||||
receipt: Receipt<TxType>,
|
||||
}
|
||||
|
||||
let mut bytes = [0u8; 1024];
|
||||
rand::rng().fill(bytes.as_mut_slice());
|
||||
let data = Data {
|
||||
receipt: Receipt::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(),
|
||||
};
|
||||
let encoded = bincode::serialize(&data).unwrap();
|
||||
let decoded: Data = bincode::deserialize(&encoded).unwrap();
|
||||
assert_eq!(decoded, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "reth-codec")]
|
||||
mod compact {
|
||||
use super::*;
|
||||
use reth_codecs::{
|
||||
Compact,
|
||||
__private::{modular_bitfield::prelude::*, Buf},
|
||||
};
|
||||
|
||||
impl Receipt {
|
||||
#[doc = "Used bytes by [`ReceiptFlags`]"]
|
||||
pub const fn bitflag_encoded_bytes() -> usize {
|
||||
1u8 as usize
|
||||
}
|
||||
#[doc = "Unused bits for new fields by [`ReceiptFlags`]"]
|
||||
pub const fn bitflag_unused_bits() -> usize {
|
||||
0u8 as usize
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case, unused_parens)]
|
||||
mod flags {
|
||||
use super::*;
|
||||
|
||||
#[doc = "Fieldset that facilitates compacting the parent type. Used bytes: 1 | Unused bits: 0"]
|
||||
#[bitfield]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct ReceiptFlags {
|
||||
pub tx_type_len: B2,
|
||||
pub success_len: B1,
|
||||
pub cumulative_gas_used_len: B4,
|
||||
pub __zstd: B1,
|
||||
}
|
||||
|
||||
impl ReceiptFlags {
|
||||
#[doc = r" Deserializes this fieldset and returns it, alongside the original slice in an advanced position."]
|
||||
pub fn from(mut buf: &[u8]) -> (Self, &[u8]) {
|
||||
(Self::from_bytes([buf.get_u8()]), buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use flags::ReceiptFlags;
|
||||
|
||||
impl<T: Compact> Compact for Receipt<T> {
|
||||
fn to_compact<B>(&self, buf: &mut B) -> usize
|
||||
where
|
||||
B: reth_codecs::__private::bytes::BufMut + AsMut<[u8]>,
|
||||
{
|
||||
let mut flags = ReceiptFlags::default();
|
||||
let mut total_length = 0;
|
||||
let mut buffer = reth_codecs::__private::bytes::BytesMut::new();
|
||||
|
||||
let tx_type_len = self.tx_type.to_compact(&mut buffer);
|
||||
flags.set_tx_type_len(tx_type_len as u8);
|
||||
let success_len = self.success.to_compact(&mut buffer);
|
||||
flags.set_success_len(success_len as u8);
|
||||
let cumulative_gas_used_len = self.cumulative_gas_used.to_compact(&mut buffer);
|
||||
flags.set_cumulative_gas_used_len(cumulative_gas_used_len as u8);
|
||||
self.logs.to_compact(&mut buffer);
|
||||
|
||||
let zstd = buffer.len() > 7;
|
||||
if zstd {
|
||||
flags.set___zstd(1);
|
||||
}
|
||||
|
||||
let flags = flags.into_bytes();
|
||||
total_length += flags.len() + buffer.len();
|
||||
buf.put_slice(&flags);
|
||||
if zstd {
|
||||
reth_zstd_compressors::with_receipt_compressor(|compressor| {
|
||||
let compressed = compressor.compress(&buffer).expect("Failed to compress.");
|
||||
buf.put(compressed.as_slice());
|
||||
});
|
||||
} else {
|
||||
buf.put(buffer);
|
||||
}
|
||||
total_length
|
||||
}
|
||||
|
||||
fn from_compact(buf: &[u8], _len: usize) -> (Self, &[u8]) {
|
||||
let (flags, mut buf) = ReceiptFlags::from(buf);
|
||||
if flags.__zstd() != 0 {
|
||||
reth_zstd_compressors::with_receipt_decompressor(|decompressor| {
|
||||
let decompressed = decompressor.decompress(buf);
|
||||
let original_buf = buf;
|
||||
let mut buf: &[u8] = decompressed;
|
||||
let (tx_type, new_buf) = T::from_compact(buf, flags.tx_type_len() as usize);
|
||||
buf = new_buf;
|
||||
let (success, new_buf) = bool::from_compact(buf, flags.success_len() as usize);
|
||||
buf = new_buf;
|
||||
let (cumulative_gas_used, new_buf) =
|
||||
u64::from_compact(buf, flags.cumulative_gas_used_len() as usize);
|
||||
buf = new_buf;
|
||||
let (logs, _) = Vec::from_compact(buf, buf.len());
|
||||
(Self { tx_type, success, cumulative_gas_used, logs }, original_buf)
|
||||
})
|
||||
} else {
|
||||
let (tx_type, new_buf) = T::from_compact(buf, flags.tx_type_len() as usize);
|
||||
buf = new_buf;
|
||||
let (success, new_buf) = bool::from_compact(buf, flags.success_len() as usize);
|
||||
buf = new_buf;
|
||||
let (cumulative_gas_used, new_buf) =
|
||||
u64::from_compact(buf, flags.cumulative_gas_used_len() as usize);
|
||||
buf = new_buf;
|
||||
let (logs, new_buf) = Vec::from_compact(buf, buf.len());
|
||||
buf = new_buf;
|
||||
let obj = Self { tx_type, success, cumulative_gas_used, logs };
|
||||
(obj, buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "reth-codec")]
|
||||
pub use compact::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::TransactionSigned;
|
||||
use alloy_consensus::{ReceiptWithBloom, TxReceipt, TxType};
|
||||
use alloy_eips::eip2718::Encodable2718;
|
||||
use alloy_primitives::{
|
||||
address, b256, bloom, bytes, hex_literal::hex, Address, Bytes, Log, LogData,
|
||||
address, b256, bloom, bytes, hex_literal::hex, Address, Bloom, Bytes, Log, LogData,
|
||||
};
|
||||
use alloy_rlp::Decodable;
|
||||
use alloy_rlp::{Decodable, Encodable};
|
||||
use reth_codecs::Compact;
|
||||
use reth_primitives_traits::proofs::{
|
||||
calculate_receipt_root, calculate_transaction_root, calculate_withdrawals_root,
|
||||
@@ -784,6 +247,8 @@ mod tests {
|
||||
#[test]
|
||||
#[cfg(feature = "rpc")]
|
||||
fn test_receipt_serde() {
|
||||
use alloy_consensus::ReceiptEnvelope;
|
||||
|
||||
let input = r#"{"status":"0x1","cumulativeGasUsed":"0x175cc0e","logs":[{"address":"0xa18b9ca2a78660d44ab38ae72e72b18792ffe413","topics":["0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925","0x000000000000000000000000e7e7d8006cbff47bc6ac2dabf592c98e97502708","0x0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d"],"data":"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","blockHash":"0xbf9e6a368a399f996a0f0b27cab4191c028c3c99f5f76ea08a5b70b961475fcb","blockNumber":"0x164b59f","blockTimestamp":"0x68c9a713","transactionHash":"0x533aa9e57865675bb94f41aa2895c0ac81eee69686c77af16149c301e19805f1","transactionIndex":"0x14d","logIndex":"0x238","removed":false}],"logsBloom":"0x00000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000400000040000000000000004000000000000000000000000000000000000000000000020000000000000000000000000080000000000000000000000000200000020000000000000000000000000000000000000000000000000000000000000020000010000000000000000000000000000000000000000000000000000000000000","type":"0x2","transactionHash":"0x533aa9e57865675bb94f41aa2895c0ac81eee69686c77af16149c301e19805f1","transactionIndex":"0x14d","blockHash":"0xbf9e6a368a399f996a0f0b27cab4191c028c3c99f5f76ea08a5b70b961475fcb","blockNumber":"0x164b59f","gasUsed":"0xb607","effectiveGasPrice":"0x4a3ee768","from":"0xe7e7d8006cbff47bc6ac2dabf592c98e97502708","to":"0xa18b9ca2a78660d44ab38ae72e72b18792ffe413","contractAddress":null}"#;
|
||||
let receipt: RpcReceipt = serde_json::from_str(input).unwrap();
|
||||
let envelope: ReceiptEnvelope<alloy_rpc_types_eth::Log> =
|
||||
|
||||
@@ -1,717 +0,0 @@
|
||||
//! This file contains the legacy reth `TransactionSigned` type that has been replaced with
|
||||
//! alloy's TxEnvelope To test for consistency this is kept
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use alloy_consensus::{
|
||||
transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx, SignerRecoverable, TxHashRef},
|
||||
EthereumTxEnvelope, SignableTransaction, Signed, TxEip1559, TxEip2930, TxEip4844, TxEip7702,
|
||||
TxLegacy, TxType, Typed2718,
|
||||
};
|
||||
use alloy_eips::{
|
||||
eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718, IsTyped2718},
|
||||
eip2930::AccessList,
|
||||
eip7702::SignedAuthorization,
|
||||
};
|
||||
use alloy_primitives::{
|
||||
bytes::BufMut, keccak256, Address, Bytes, ChainId, Signature, TxHash, TxKind, B256, U256,
|
||||
};
|
||||
use alloy_rlp::{Decodable, Encodable};
|
||||
use core::hash::{Hash, Hasher};
|
||||
use reth_primitives_traits::{
|
||||
crypto::secp256k1::{recover_signer, recover_signer_unchecked},
|
||||
sync::OnceLock,
|
||||
transaction::signed::RecoveryError,
|
||||
InMemorySize, SignedTransaction,
|
||||
};
|
||||
|
||||
macro_rules! delegate {
|
||||
($self:expr => $tx:ident.$method:ident($($arg:expr),*)) => {
|
||||
match $self {
|
||||
Transaction::Legacy($tx) => $tx.$method($($arg),*),
|
||||
Transaction::Eip2930($tx) => $tx.$method($($arg),*),
|
||||
Transaction::Eip1559($tx) => $tx.$method($($arg),*),
|
||||
Transaction::Eip4844($tx) => $tx.$method($($arg),*),
|
||||
Transaction::Eip7702($tx) => $tx.$method($($arg),*),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// A raw transaction.
|
||||
///
|
||||
/// Transaction types were introduced in [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718).
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
|
||||
#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))]
|
||||
pub enum Transaction {
|
||||
/// Legacy transaction (type `0x0`).
|
||||
///
|
||||
/// Traditional Ethereum transactions, containing parameters `nonce`, `gasPrice`, `gasLimit`,
|
||||
/// `to`, `value`, `data`, `v`, `r`, and `s`.
|
||||
///
|
||||
/// These transactions do not utilize access lists nor do they incorporate EIP-1559 fee market
|
||||
/// changes.
|
||||
Legacy(TxLegacy),
|
||||
/// Transaction with an [`AccessList`] ([EIP-2930](https://eips.ethereum.org/EIPS/eip-2930)), type `0x1`.
|
||||
///
|
||||
/// The `accessList` specifies an array of addresses and storage keys that the transaction
|
||||
/// plans to access, enabling gas savings on cross-contract calls by pre-declaring the accessed
|
||||
/// contract and storage slots.
|
||||
Eip2930(TxEip2930),
|
||||
/// A transaction with a priority fee ([EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)), type `0x2`.
|
||||
///
|
||||
/// Unlike traditional transactions, EIP-1559 transactions use an in-protocol, dynamically
|
||||
/// changing base fee per gas, adjusted at each block to manage network congestion.
|
||||
///
|
||||
/// - `maxPriorityFeePerGas`, specifying the maximum fee above the base fee the sender is
|
||||
/// willing to pay
|
||||
/// - `maxFeePerGas`, setting the maximum total fee the sender is willing to pay.
|
||||
///
|
||||
/// The base fee is burned, while the priority fee is paid to the miner who includes the
|
||||
/// transaction, incentivizing miners to include transactions with higher priority fees per
|
||||
/// gas.
|
||||
Eip1559(TxEip1559),
|
||||
/// Shard Blob Transactions ([EIP-4844](https://eips.ethereum.org/EIPS/eip-4844)), type `0x3`.
|
||||
///
|
||||
/// Shard Blob Transactions introduce a new transaction type called a blob-carrying transaction
|
||||
/// to reduce gas costs. These transactions are similar to regular Ethereum transactions but
|
||||
/// include additional data called a blob.
|
||||
///
|
||||
/// Blobs are larger (~125 kB) and cheaper than the current calldata, providing an immutable
|
||||
/// and read-only memory for storing transaction data.
|
||||
///
|
||||
/// EIP-4844, also known as proto-danksharding, implements the framework and logic of
|
||||
/// danksharding, introducing new transaction formats and verification rules.
|
||||
Eip4844(TxEip4844),
|
||||
/// EOA Set Code Transactions ([EIP-7702](https://eips.ethereum.org/EIPS/eip-7702)), type `0x4`.
|
||||
///
|
||||
/// EOA Set Code Transactions give the ability to set contract code for an EOA in perpetuity
|
||||
/// until re-assigned by the same EOA. This allows for adding smart contract functionality to
|
||||
/// the EOA.
|
||||
Eip7702(TxEip7702),
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
/// Returns [`TxType`] of the transaction.
|
||||
pub const fn tx_type(&self) -> TxType {
|
||||
match self {
|
||||
Self::Legacy(_) => TxType::Legacy,
|
||||
Self::Eip2930(_) => TxType::Eip2930,
|
||||
Self::Eip1559(_) => TxType::Eip1559,
|
||||
Self::Eip4844(_) => TxType::Eip4844,
|
||||
Self::Eip7702(_) => TxType::Eip7702,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
const fn input_mut(&mut self) -> &mut Bytes {
|
||||
match self {
|
||||
Self::Legacy(tx) => &mut tx.input,
|
||||
Self::Eip2930(tx) => &mut tx.input,
|
||||
Self::Eip1559(tx) => &mut tx.input,
|
||||
Self::Eip4844(tx) => &mut tx.input,
|
||||
Self::Eip7702(tx) => &mut tx.input,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Typed2718 for Transaction {
|
||||
fn ty(&self) -> u8 {
|
||||
delegate!(self => tx.ty())
|
||||
}
|
||||
}
|
||||
|
||||
impl alloy_consensus::Transaction for Transaction {
|
||||
fn chain_id(&self) -> Option<ChainId> {
|
||||
delegate!(self => tx.chain_id())
|
||||
}
|
||||
|
||||
fn nonce(&self) -> u64 {
|
||||
delegate!(self => tx.nonce())
|
||||
}
|
||||
|
||||
fn gas_limit(&self) -> u64 {
|
||||
delegate!(self => tx.gas_limit())
|
||||
}
|
||||
|
||||
fn gas_price(&self) -> Option<u128> {
|
||||
delegate!(self => tx.gas_price())
|
||||
}
|
||||
|
||||
fn max_fee_per_gas(&self) -> u128 {
|
||||
delegate!(self => tx.max_fee_per_gas())
|
||||
}
|
||||
|
||||
fn max_priority_fee_per_gas(&self) -> Option<u128> {
|
||||
delegate!(self => tx.max_priority_fee_per_gas())
|
||||
}
|
||||
|
||||
fn max_fee_per_blob_gas(&self) -> Option<u128> {
|
||||
delegate!(self => tx.max_fee_per_blob_gas())
|
||||
}
|
||||
|
||||
fn priority_fee_or_price(&self) -> u128 {
|
||||
delegate!(self => tx.priority_fee_or_price())
|
||||
}
|
||||
|
||||
fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
|
||||
delegate!(self => tx.effective_gas_price(base_fee))
|
||||
}
|
||||
|
||||
fn is_dynamic_fee(&self) -> bool {
|
||||
delegate!(self => tx.is_dynamic_fee())
|
||||
}
|
||||
|
||||
fn kind(&self) -> alloy_primitives::TxKind {
|
||||
delegate!(self => tx.kind())
|
||||
}
|
||||
|
||||
fn is_create(&self) -> bool {
|
||||
delegate!(self => tx.is_create())
|
||||
}
|
||||
|
||||
fn value(&self) -> alloy_primitives::U256 {
|
||||
delegate!(self => tx.value())
|
||||
}
|
||||
|
||||
fn input(&self) -> &alloy_primitives::Bytes {
|
||||
delegate!(self => tx.input())
|
||||
}
|
||||
|
||||
fn access_list(&self) -> Option<&alloy_eips::eip2930::AccessList> {
|
||||
delegate!(self => tx.access_list())
|
||||
}
|
||||
|
||||
fn blob_versioned_hashes(&self) -> Option<&[B256]> {
|
||||
delegate!(self => tx.blob_versioned_hashes())
|
||||
}
|
||||
|
||||
fn authorization_list(&self) -> Option<&[alloy_eips::eip7702::SignedAuthorization]> {
|
||||
delegate!(self => tx.authorization_list())
|
||||
}
|
||||
}
|
||||
|
||||
impl SignableTransaction<Signature> for Transaction {
|
||||
fn set_chain_id(&mut self, chain_id: alloy_primitives::ChainId) {
|
||||
delegate!(self => tx.set_chain_id(chain_id))
|
||||
}
|
||||
|
||||
fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
|
||||
delegate!(self => tx.encode_for_signing(out))
|
||||
}
|
||||
|
||||
fn payload_len_for_signature(&self) -> usize {
|
||||
delegate!(self => tx.payload_len_for_signature())
|
||||
}
|
||||
|
||||
fn into_signed(self, signature: Signature) -> Signed<Self> {
|
||||
let tx_hash = delegate!(&self => tx.tx_hash(&signature));
|
||||
Signed::new_unchecked(self, signature, tx_hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl InMemorySize for Transaction {
|
||||
fn size(&self) -> usize {
|
||||
delegate!(self => tx.size())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "reth-codec"))]
|
||||
impl reth_codecs::Compact for Transaction {
|
||||
// Serializes the TxType to the buffer if necessary, returning 2 bits of the type as an
|
||||
// identifier instead of the length.
|
||||
fn to_compact<B>(&self, buf: &mut B) -> usize
|
||||
where
|
||||
B: alloy_rlp::bytes::BufMut + AsMut<[u8]>,
|
||||
{
|
||||
let identifier = self.tx_type().to_compact(buf);
|
||||
delegate!(self => tx.to_compact(buf));
|
||||
identifier
|
||||
}
|
||||
|
||||
// For backwards compatibility purposes, only 2 bits of the type are encoded in the identifier
|
||||
// parameter. In the case of a [`COMPACT_EXTENDED_IDENTIFIER_FLAG`], the full transaction type
|
||||
// is read from the buffer as a single byte.
|
||||
//
|
||||
// # Panics
|
||||
//
|
||||
// A panic will be triggered if an identifier larger than 3 is passed from the database. For
|
||||
// optimism an identifier with value [`DEPOSIT_TX_TYPE_ID`] is allowed.
|
||||
fn from_compact(buf: &[u8], identifier: usize) -> (Self, &[u8]) {
|
||||
let (tx_type, buf) = TxType::from_compact(buf, identifier);
|
||||
|
||||
match tx_type {
|
||||
TxType::Legacy => {
|
||||
let (tx, buf) = TxLegacy::from_compact(buf, buf.len());
|
||||
(Self::Legacy(tx), buf)
|
||||
}
|
||||
TxType::Eip2930 => {
|
||||
let (tx, buf) = TxEip2930::from_compact(buf, buf.len());
|
||||
(Self::Eip2930(tx), buf)
|
||||
}
|
||||
TxType::Eip1559 => {
|
||||
let (tx, buf) = TxEip1559::from_compact(buf, buf.len());
|
||||
(Self::Eip1559(tx), buf)
|
||||
}
|
||||
TxType::Eip4844 => {
|
||||
let (tx, buf) = TxEip4844::from_compact(buf, buf.len());
|
||||
(Self::Eip4844(tx), buf)
|
||||
}
|
||||
TxType::Eip7702 => {
|
||||
let (tx, buf) = TxEip7702::from_compact(buf, buf.len());
|
||||
(Self::Eip7702(tx), buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RlpEcdsaEncodableTx for Transaction {
|
||||
fn rlp_encoded_fields_length(&self) -> usize {
|
||||
delegate!(self => tx.rlp_encoded_fields_length())
|
||||
}
|
||||
|
||||
fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
|
||||
delegate!(self => tx.rlp_encode_fields(out))
|
||||
}
|
||||
|
||||
fn eip2718_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) {
|
||||
delegate!(self => tx.eip2718_encode_with_type(signature, tx.ty(), out))
|
||||
}
|
||||
|
||||
fn eip2718_encode(&self, signature: &Signature, out: &mut dyn BufMut) {
|
||||
delegate!(self => tx.eip2718_encode(signature, out))
|
||||
}
|
||||
|
||||
fn network_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) {
|
||||
delegate!(self => tx.network_encode_with_type(signature, tx.ty(), out))
|
||||
}
|
||||
|
||||
fn network_encode(&self, signature: &Signature, out: &mut dyn BufMut) {
|
||||
delegate!(self => tx.network_encode(signature, out))
|
||||
}
|
||||
|
||||
fn tx_hash_with_type(&self, signature: &Signature, _ty: u8) -> TxHash {
|
||||
delegate!(self => tx.tx_hash_with_type(signature, tx.ty()))
|
||||
}
|
||||
|
||||
fn tx_hash(&self, signature: &Signature) -> TxHash {
|
||||
delegate!(self => tx.tx_hash(signature))
|
||||
}
|
||||
}
|
||||
|
||||
/// Signed Ethereum transaction.
|
||||
#[derive(Debug, Clone, Eq, derive_more::AsRef, derive_more::Deref)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(rlp))]
|
||||
#[cfg_attr(feature = "test-utils", derive(derive_more::DerefMut))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
|
||||
pub struct TransactionSigned {
|
||||
/// Transaction hash
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
hash: OnceLock<TxHash>,
|
||||
/// The transaction signature values
|
||||
signature: Signature,
|
||||
/// Raw transaction info
|
||||
#[deref]
|
||||
#[as_ref]
|
||||
#[cfg_attr(feature = "test-utils", deref_mut)]
|
||||
transaction: Transaction,
|
||||
}
|
||||
|
||||
impl TransactionSigned {
|
||||
fn recalculate_hash(&self) -> B256 {
|
||||
keccak256(self.encoded_2718())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for TransactionSigned {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.signature.hash(state);
|
||||
self.transaction.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for TransactionSigned {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.signature == other.signature &&
|
||||
self.transaction == other.transaction &&
|
||||
self.tx_hash() == other.tx_hash()
|
||||
}
|
||||
}
|
||||
|
||||
impl TransactionSigned {
|
||||
/// Creates a new signed transaction from the given transaction, signature and hash.
|
||||
pub fn new(transaction: Transaction, signature: Signature, hash: B256) -> Self {
|
||||
Self { hash: hash.into(), signature, transaction }
|
||||
}
|
||||
|
||||
/// Returns the transaction hash.
|
||||
#[inline]
|
||||
pub fn hash(&self) -> &B256 {
|
||||
self.hash.get_or_init(|| self.recalculate_hash())
|
||||
}
|
||||
|
||||
/// Splits the transaction into parts.
|
||||
pub fn into_parts(self) -> (Transaction, Signature, B256) {
|
||||
let hash = *self.hash.get_or_init(|| self.recalculate_hash());
|
||||
(self.transaction, self.signature, hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl Typed2718 for TransactionSigned {
|
||||
fn ty(&self) -> u8 {
|
||||
self.transaction.ty()
|
||||
}
|
||||
}
|
||||
|
||||
impl alloy_consensus::Transaction for TransactionSigned {
|
||||
fn chain_id(&self) -> Option<ChainId> {
|
||||
self.transaction.chain_id()
|
||||
}
|
||||
|
||||
fn nonce(&self) -> u64 {
|
||||
self.transaction.nonce()
|
||||
}
|
||||
|
||||
fn gas_limit(&self) -> u64 {
|
||||
self.transaction.gas_limit()
|
||||
}
|
||||
|
||||
fn gas_price(&self) -> Option<u128> {
|
||||
self.transaction.gas_price()
|
||||
}
|
||||
|
||||
fn max_fee_per_gas(&self) -> u128 {
|
||||
self.transaction.max_fee_per_gas()
|
||||
}
|
||||
|
||||
fn max_priority_fee_per_gas(&self) -> Option<u128> {
|
||||
self.transaction.max_priority_fee_per_gas()
|
||||
}
|
||||
|
||||
fn max_fee_per_blob_gas(&self) -> Option<u128> {
|
||||
self.transaction.max_fee_per_blob_gas()
|
||||
}
|
||||
|
||||
fn priority_fee_or_price(&self) -> u128 {
|
||||
self.transaction.priority_fee_or_price()
|
||||
}
|
||||
|
||||
fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
|
||||
self.transaction.effective_gas_price(base_fee)
|
||||
}
|
||||
|
||||
fn is_dynamic_fee(&self) -> bool {
|
||||
self.transaction.is_dynamic_fee()
|
||||
}
|
||||
|
||||
fn kind(&self) -> TxKind {
|
||||
self.transaction.kind()
|
||||
}
|
||||
|
||||
fn is_create(&self) -> bool {
|
||||
self.transaction.is_create()
|
||||
}
|
||||
|
||||
fn value(&self) -> U256 {
|
||||
self.transaction.value()
|
||||
}
|
||||
|
||||
fn input(&self) -> &Bytes {
|
||||
self.transaction.input()
|
||||
}
|
||||
|
||||
fn access_list(&self) -> Option<&AccessList> {
|
||||
self.transaction.access_list()
|
||||
}
|
||||
|
||||
fn blob_versioned_hashes(&self) -> Option<&[B256]> {
|
||||
self.transaction.blob_versioned_hashes()
|
||||
}
|
||||
|
||||
fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
|
||||
self.transaction.authorization_list()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Signed<Transaction>> for TransactionSigned {
|
||||
fn from(value: Signed<Transaction>) -> Self {
|
||||
let (tx, sig, hash) = value.into_parts();
|
||||
Self::new(tx, sig, hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TransactionSigned> for EthereumTxEnvelope<TxEip4844> {
|
||||
fn from(value: TransactionSigned) -> Self {
|
||||
let (tx, signature, hash) = value.into_parts();
|
||||
match tx {
|
||||
Transaction::Legacy(tx) => Signed::new_unchecked(tx, signature, hash).into(),
|
||||
Transaction::Eip2930(tx) => Signed::new_unchecked(tx, signature, hash).into(),
|
||||
Transaction::Eip1559(tx) => Signed::new_unchecked(tx, signature, hash).into(),
|
||||
Transaction::Eip4844(tx) => Signed::new_unchecked(tx, signature, hash).into(),
|
||||
Transaction::Eip7702(tx) => Signed::new_unchecked(tx, signature, hash).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "arbitrary"))]
|
||||
impl<'a> arbitrary::Arbitrary<'a> for TransactionSigned {
|
||||
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
|
||||
#[expect(unused_mut)]
|
||||
let mut transaction = Transaction::arbitrary(u)?;
|
||||
|
||||
let secp = secp256k1::Secp256k1::new();
|
||||
let key_pair = secp256k1::Keypair::new(&secp, &mut rand_08::thread_rng());
|
||||
let signature = reth_primitives_traits::crypto::secp256k1::sign_message(
|
||||
B256::from_slice(&key_pair.secret_bytes()[..]),
|
||||
transaction.signature_hash(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Ok(Self { transaction, signature, hash: Default::default() })
|
||||
}
|
||||
}
|
||||
|
||||
impl InMemorySize for TransactionSigned {
|
||||
fn size(&self) -> usize {
|
||||
let Self { hash: _, signature, transaction } = self;
|
||||
self.tx_hash().size() + signature.size() + transaction.size()
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable2718 for TransactionSigned {
|
||||
fn type_flag(&self) -> Option<u8> {
|
||||
(!self.transaction.is_legacy()).then(|| self.ty())
|
||||
}
|
||||
|
||||
fn encode_2718_len(&self) -> usize {
|
||||
delegate!(&self.transaction => tx.eip2718_encoded_length(&self.signature))
|
||||
}
|
||||
|
||||
fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
|
||||
delegate!(&self.transaction => tx.eip2718_encode(&self.signature, out))
|
||||
}
|
||||
|
||||
fn trie_hash(&self) -> B256 {
|
||||
*self.tx_hash()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable2718 for TransactionSigned {
|
||||
fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
|
||||
match ty.try_into().map_err(|_| Eip2718Error::UnexpectedType(ty))? {
|
||||
TxType::Legacy => Err(Eip2718Error::UnexpectedType(0)),
|
||||
TxType::Eip2930 => {
|
||||
let (tx, signature) = TxEip2930::rlp_decode_with_signature(buf)?;
|
||||
Ok(Self {
|
||||
transaction: Transaction::Eip2930(tx),
|
||||
signature,
|
||||
hash: Default::default(),
|
||||
})
|
||||
}
|
||||
TxType::Eip1559 => {
|
||||
let (tx, signature) = TxEip1559::rlp_decode_with_signature(buf)?;
|
||||
Ok(Self {
|
||||
transaction: Transaction::Eip1559(tx),
|
||||
signature,
|
||||
hash: Default::default(),
|
||||
})
|
||||
}
|
||||
TxType::Eip4844 => {
|
||||
let (tx, signature) = TxEip4844::rlp_decode_with_signature(buf)?;
|
||||
Ok(Self {
|
||||
transaction: Transaction::Eip4844(tx),
|
||||
signature,
|
||||
hash: Default::default(),
|
||||
})
|
||||
}
|
||||
TxType::Eip7702 => {
|
||||
let (tx, signature) = TxEip7702::rlp_decode_with_signature(buf)?;
|
||||
Ok(Self {
|
||||
transaction: Transaction::Eip7702(tx),
|
||||
signature,
|
||||
hash: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
|
||||
let (tx, signature) = TxLegacy::rlp_decode_with_signature(buf)?;
|
||||
Ok(Self { transaction: Transaction::Legacy(tx), signature, hash: Default::default() })
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for TransactionSigned {
|
||||
fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
|
||||
self.network_encode(out);
|
||||
}
|
||||
|
||||
fn length(&self) -> usize {
|
||||
self.network_len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for TransactionSigned {
|
||||
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
|
||||
Self::network_decode(buf).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "reth-codec"))]
|
||||
impl reth_codecs::Compact for TransactionSigned {
|
||||
fn to_compact<B>(&self, buf: &mut B) -> usize
|
||||
where
|
||||
B: alloy_rlp::bytes::BufMut + AsMut<[u8]>,
|
||||
{
|
||||
use alloy_consensus::Transaction;
|
||||
|
||||
let start = buf.as_mut().len();
|
||||
|
||||
// Placeholder for bitflags.
|
||||
// The first byte uses 4 bits as flags: IsCompressed[1bit], TxType[2bits], Signature[1bit]
|
||||
buf.put_u8(0);
|
||||
|
||||
let sig_bit = self.signature.to_compact(buf) as u8;
|
||||
let zstd_bit = self.transaction.input().len() >= 32;
|
||||
|
||||
let tx_bits = if zstd_bit {
|
||||
let mut tmp = Vec::with_capacity(256);
|
||||
reth_zstd_compressors::with_tx_compressor(|compressor| {
|
||||
let tx_bits = self.transaction.to_compact(&mut tmp);
|
||||
buf.put_slice(&compressor.compress(&tmp).expect("Failed to compress"));
|
||||
tx_bits as u8
|
||||
})
|
||||
} else {
|
||||
self.transaction.to_compact(buf) as u8
|
||||
};
|
||||
|
||||
// Replace bitflags with the actual values
|
||||
buf.as_mut()[start] = sig_bit | (tx_bits << 1) | ((zstd_bit as u8) << 3);
|
||||
|
||||
buf.as_mut().len() - start
|
||||
}
|
||||
|
||||
fn from_compact(mut buf: &[u8], _len: usize) -> (Self, &[u8]) {
|
||||
use alloy_rlp::bytes::Buf;
|
||||
|
||||
// The first byte uses 4 bits as flags: IsCompressed[1], TxType[2], Signature[1]
|
||||
let bitflags = buf.get_u8() as usize;
|
||||
|
||||
let sig_bit = bitflags & 1;
|
||||
let (signature, buf) = Signature::from_compact(buf, sig_bit);
|
||||
|
||||
let zstd_bit = bitflags >> 3;
|
||||
let (transaction, buf) = if zstd_bit != 0 {
|
||||
reth_zstd_compressors::with_tx_decompressor(|decompressor| {
|
||||
// TODO: enforce that zstd is only present at a "top" level type
|
||||
let transaction_type = (bitflags & 0b110) >> 1;
|
||||
let (transaction, _) =
|
||||
Transaction::from_compact(decompressor.decompress(buf), transaction_type);
|
||||
(transaction, buf)
|
||||
})
|
||||
} else {
|
||||
let transaction_type = bitflags >> 1;
|
||||
Transaction::from_compact(buf, transaction_type)
|
||||
};
|
||||
|
||||
(Self { signature, transaction, hash: Default::default() }, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl SignerRecoverable for TransactionSigned {
|
||||
fn recover_signer(&self) -> Result<Address, RecoveryError> {
|
||||
let signature_hash = self.signature_hash();
|
||||
recover_signer(&self.signature, signature_hash)
|
||||
}
|
||||
|
||||
fn recover_signer_unchecked(&self) -> Result<Address, RecoveryError> {
|
||||
let signature_hash = self.signature_hash();
|
||||
recover_signer_unchecked(&self.signature, signature_hash)
|
||||
}
|
||||
|
||||
fn recover_unchecked_with_buf(&self, buf: &mut Vec<u8>) -> Result<Address, RecoveryError> {
|
||||
self.encode_for_signing(buf);
|
||||
let signature_hash = keccak256(buf);
|
||||
recover_signer_unchecked(&self.signature, signature_hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl TxHashRef for TransactionSigned {
|
||||
fn tx_hash(&self) -> &TxHash {
|
||||
self.hash.get_or_init(|| self.recalculate_hash())
|
||||
}
|
||||
}
|
||||
|
||||
impl IsTyped2718 for TransactionSigned {
|
||||
fn is_type(type_id: u8) -> bool {
|
||||
<alloy_consensus::TxEnvelope as IsTyped2718>::is_type(type_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl SignedTransaction for TransactionSigned {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloy_consensus::EthereumTxEnvelope;
|
||||
use proptest::proptest;
|
||||
use proptest_arbitrary_interop::arb;
|
||||
use reth_codecs::Compact;
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn test_roundtrip_compact_encode_envelope(reth_tx in arb::<TransactionSigned>()) {
|
||||
let mut expected_buf = Vec::<u8>::new();
|
||||
let expected_len = reth_tx.to_compact(&mut expected_buf);
|
||||
|
||||
let mut actual_but = Vec::<u8>::new();
|
||||
let alloy_tx = EthereumTxEnvelope::<TxEip4844>::from(reth_tx);
|
||||
let actual_len = alloy_tx.to_compact(&mut actual_but);
|
||||
|
||||
assert_eq!(actual_but, expected_buf);
|
||||
assert_eq!(actual_len, expected_len);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip_compact_decode_envelope(reth_tx in arb::<TransactionSigned>()) {
|
||||
let mut buf = Vec::<u8>::new();
|
||||
let len = reth_tx.to_compact(&mut buf);
|
||||
|
||||
let (actual_tx, _) = EthereumTxEnvelope::<TxEip4844>::from_compact(&buf, len);
|
||||
let expected_tx = EthereumTxEnvelope::<TxEip4844>::from(reth_tx);
|
||||
|
||||
assert_eq!(actual_tx, expected_tx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip_compact_encode_envelope_zstd(mut reth_tx in arb::<TransactionSigned>()) {
|
||||
// zstd only kicks in if the input is large enough
|
||||
*reth_tx.transaction.input_mut() = vec![0;33].into();
|
||||
|
||||
let mut expected_buf = Vec::<u8>::new();
|
||||
let expected_len = reth_tx.to_compact(&mut expected_buf);
|
||||
|
||||
let mut actual_but = Vec::<u8>::new();
|
||||
let alloy_tx = EthereumTxEnvelope::<TxEip4844>::from(reth_tx);
|
||||
let actual_len = alloy_tx.to_compact(&mut actual_but);
|
||||
|
||||
assert_eq!(actual_but, expected_buf);
|
||||
assert_eq!(actual_len, expected_len);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip_compact_decode_envelope_zstd(mut reth_tx in arb::<TransactionSigned>()) {
|
||||
// zstd only kicks in if the input is large enough
|
||||
*reth_tx.transaction.input_mut() = vec![0;33].into();
|
||||
|
||||
let mut buf = Vec::<u8>::new();
|
||||
let len = reth_tx.to_compact(&mut buf);
|
||||
|
||||
let (actual_tx, _) = EthereumTxEnvelope::<TxEip4844>::from_compact(&buf, len);
|
||||
let expected_tx = EthereumTxEnvelope::<TxEip4844>::from(reth_tx);
|
||||
|
||||
assert_eq!(actual_tx, expected_tx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -411,7 +411,7 @@ impl ExecutionOutcome {
|
||||
pub fn ethereum_receipts_root(&self, block_number: BlockNumber) -> Option<B256> {
|
||||
self.generic_receipts_root_slow(
|
||||
block_number,
|
||||
reth_ethereum_primitives::Receipt::calculate_receipt_root_no_memo,
|
||||
reth_ethereum_primitives::calculate_receipt_root_no_memo,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,15 +258,15 @@ pub async fn test_exex_context_with_chain_spec(
|
||||
let genesis_hash = init_genesis(&provider_factory)?;
|
||||
let provider = BlockchainProvider::new(provider_factory.clone())?;
|
||||
|
||||
let runtime = Runtime::test();
|
||||
let network_manager = NetworkManager::new(
|
||||
NetworkConfigBuilder::new(rng_secret_key())
|
||||
NetworkConfigBuilder::new(rng_secret_key(), runtime.clone())
|
||||
.with_unused_discovery_port()
|
||||
.with_unused_listener_port()
|
||||
.build(provider_factory.clone()),
|
||||
)
|
||||
.await?;
|
||||
let network = network_manager.handle().clone();
|
||||
let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current()).unwrap();
|
||||
let task_executor = runtime.clone();
|
||||
runtime.spawn_task(network_manager);
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ impl<N: NetworkPrimitives> ProtocolMessage<N> {
|
||||
}
|
||||
EthMessageID::NodeData => {
|
||||
if version >= EthVersion::Eth67 {
|
||||
return Err(MessageError::Invalid(version, EthMessageID::GetNodeData))
|
||||
return Err(MessageError::Invalid(version, EthMessageID::NodeData))
|
||||
}
|
||||
EthMessage::NodeData(RequestPair::decode(buf)?)
|
||||
}
|
||||
|
||||
@@ -102,26 +102,18 @@ pub struct NetworkConfig<C, N: NetworkPrimitives = EthNetworkPrimitives> {
|
||||
// === impl NetworkConfig ===
|
||||
|
||||
impl<N: NetworkPrimitives> NetworkConfig<(), N> {
|
||||
/// Convenience method for creating the corresponding builder type
|
||||
pub fn builder(secret_key: SecretKey) -> NetworkConfigBuilder<N> {
|
||||
NetworkConfigBuilder::new(secret_key)
|
||||
/// Convenience method for creating the corresponding builder type.
|
||||
pub fn builder(secret_key: SecretKey, executor: Runtime) -> NetworkConfigBuilder<N> {
|
||||
NetworkConfigBuilder::new(secret_key, executor)
|
||||
}
|
||||
|
||||
/// Convenience method for creating the corresponding builder type with a random secret key.
|
||||
pub fn builder_with_rng_secret_key() -> NetworkConfigBuilder<N> {
|
||||
NetworkConfigBuilder::with_rng_secret_key()
|
||||
pub fn builder_with_rng_secret_key(executor: Runtime) -> NetworkConfigBuilder<N> {
|
||||
NetworkConfigBuilder::with_rng_secret_key(executor)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, N: NetworkPrimitives> NetworkConfig<C, N> {
|
||||
/// Create a new instance with all mandatory fields set, rest is field with defaults.
|
||||
pub fn new(client: C, secret_key: SecretKey) -> Self
|
||||
where
|
||||
C: ChainSpecProvider<ChainSpec: Hardforks>,
|
||||
{
|
||||
NetworkConfig::builder(secret_key).build(client)
|
||||
}
|
||||
|
||||
/// Apply a function to the config.
|
||||
pub fn apply<F>(self, f: F) -> Self
|
||||
where
|
||||
@@ -206,7 +198,7 @@ pub struct NetworkConfigBuilder<N: NetworkPrimitives = EthNetworkPrimitives> {
|
||||
/// The default mode of the network.
|
||||
network_mode: NetworkMode,
|
||||
/// The executor to use for spawning tasks.
|
||||
executor: Option<Runtime>,
|
||||
executor: Runtime,
|
||||
/// Sets the hello message for the p2p handshake in `RLPx`
|
||||
hello_message: Option<HelloMessageWithProtocols>,
|
||||
/// The executor to use for spawning tasks.
|
||||
@@ -232,8 +224,8 @@ pub struct NetworkConfigBuilder<N: NetworkPrimitives = EthNetworkPrimitives> {
|
||||
|
||||
impl NetworkConfigBuilder<EthNetworkPrimitives> {
|
||||
/// Creates the `NetworkConfigBuilder` with [`EthNetworkPrimitives`] types.
|
||||
pub fn eth(secret_key: SecretKey) -> Self {
|
||||
Self::new(secret_key)
|
||||
pub fn eth(secret_key: SecretKey, executor: Runtime) -> Self {
|
||||
Self::new(secret_key, executor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,12 +234,12 @@ impl NetworkConfigBuilder<EthNetworkPrimitives> {
|
||||
#[expect(missing_docs)]
|
||||
impl<N: NetworkPrimitives> NetworkConfigBuilder<N> {
|
||||
/// Create a new builder instance with a random secret key.
|
||||
pub fn with_rng_secret_key() -> Self {
|
||||
Self::new(rng_secret_key())
|
||||
pub fn with_rng_secret_key(executor: Runtime) -> Self {
|
||||
Self::new(rng_secret_key(), executor)
|
||||
}
|
||||
|
||||
/// Create a new builder instance with the given secret key.
|
||||
pub fn new(secret_key: SecretKey) -> Self {
|
||||
pub fn new(secret_key: SecretKey, executor: Runtime) -> Self {
|
||||
Self {
|
||||
secret_key,
|
||||
dns_discovery_config: Some(Default::default()),
|
||||
@@ -259,7 +251,7 @@ impl<N: NetworkPrimitives> NetworkConfigBuilder<N> {
|
||||
peers_config: None,
|
||||
sessions_config: None,
|
||||
network_mode: Default::default(),
|
||||
executor: None,
|
||||
executor,
|
||||
hello_message: None,
|
||||
extra_protocols: Default::default(),
|
||||
head: None,
|
||||
@@ -340,10 +332,8 @@ impl<N: NetworkPrimitives> NetworkConfigBuilder<N> {
|
||||
}
|
||||
|
||||
/// Sets the executor to use for spawning tasks.
|
||||
///
|
||||
/// If `None`, then [`tokio::spawn`] is used for spawning tasks.
|
||||
pub fn with_task_executor(mut self, executor: Runtime) -> Self {
|
||||
self.executor = Some(executor);
|
||||
self.executor = executor;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -691,11 +681,7 @@ impl<N: NetworkPrimitives> NetworkConfigBuilder<N> {
|
||||
chain_id,
|
||||
block_import: block_import.unwrap_or_else(|| Box::<ProofOfStakeBlockImport>::default()),
|
||||
network_mode,
|
||||
executor: executor.unwrap_or_else(|| match tokio::runtime::Handle::try_current() {
|
||||
Ok(handle) => Runtime::with_existing_handle(handle)
|
||||
.expect("failed to create runtime with existing handle"),
|
||||
Err(_) => Runtime::test(),
|
||||
}),
|
||||
executor,
|
||||
status,
|
||||
hello_message,
|
||||
extra_protocols,
|
||||
@@ -749,7 +735,7 @@ mod tests {
|
||||
|
||||
fn builder() -> NetworkConfigBuilder {
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
NetworkConfigBuilder::new(secret_key)
|
||||
NetworkConfigBuilder::new(secret_key, Runtime::test())
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
//! };
|
||||
//! use reth_network_peers::mainnet_nodes;
|
||||
//! use reth_storage_api::noop::NoopProvider;
|
||||
//! use reth_tasks::Runtime;
|
||||
//!
|
||||
//! // This block provider implementation is used for testing purposes.
|
||||
//! let client = NoopProvider::default();
|
||||
@@ -58,7 +59,7 @@
|
||||
//! // The key that's used for encrypting sessions and to identify our node.
|
||||
//! let local_key = rng_secret_key();
|
||||
//!
|
||||
//! let config = NetworkConfig::<_, EthNetworkPrimitives>::builder(local_key)
|
||||
//! let config = NetworkConfig::<_, EthNetworkPrimitives>::builder(local_key, Runtime::test())
|
||||
//! .boot_nodes(mainnet_nodes())
|
||||
//! .build(client);
|
||||
//!
|
||||
@@ -80,6 +81,7 @@
|
||||
//! };
|
||||
//! use reth_network_peers::mainnet_nodes;
|
||||
//! use reth_storage_api::noop::NoopProvider;
|
||||
//! use reth_tasks::Runtime;
|
||||
//! use reth_transaction_pool::TransactionPool;
|
||||
//! async fn launch<Pool: TransactionPool>(pool: Pool) {
|
||||
//! // This block provider implementation is used for testing purposes.
|
||||
@@ -88,7 +90,7 @@
|
||||
//! // The key that's used for encrypting sessions and to identify our node.
|
||||
//! let local_key = rng_secret_key();
|
||||
//!
|
||||
//! let config = NetworkConfig::<_, EthNetworkPrimitives>::builder(local_key)
|
||||
//! let config = NetworkConfig::<_, EthNetworkPrimitives>::builder(local_key, Runtime::test())
|
||||
//! .boot_nodes(mainnet_nodes())
|
||||
//! .build(client.clone());
|
||||
//! let transactions_manager_config = config.transactions_manager_config.clone();
|
||||
|
||||
@@ -157,8 +157,9 @@ impl NetworkManager {
|
||||
/// # async fn f() {
|
||||
/// use reth_chainspec::MAINNET;
|
||||
/// use reth_network::{NetworkConfig, NetworkManager};
|
||||
/// let config =
|
||||
/// NetworkConfig::builder_with_rng_secret_key().build_with_noop_provider(MAINNET.clone());
|
||||
/// use reth_tasks::Runtime;
|
||||
/// let config = NetworkConfig::builder_with_rng_secret_key(Runtime::test())
|
||||
/// .build_with_noop_provider(MAINNET.clone());
|
||||
/// let manager = NetworkManager::eth(config).await;
|
||||
/// # }
|
||||
/// ```
|
||||
@@ -378,6 +379,7 @@ impl<N: NetworkPrimitives> NetworkManager<N> {
|
||||
/// };
|
||||
/// use reth_network_peers::mainnet_nodes;
|
||||
/// use reth_storage_api::noop::NoopProvider;
|
||||
/// use reth_tasks::Runtime;
|
||||
/// use reth_transaction_pool::TransactionPool;
|
||||
/// async fn launch<Pool: TransactionPool>(pool: Pool) {
|
||||
/// // This block provider implementation is used for testing purposes.
|
||||
@@ -386,7 +388,7 @@ impl<N: NetworkPrimitives> NetworkManager<N> {
|
||||
/// // The key that's used for encrypting sessions and to identify our node.
|
||||
/// let local_key = rng_secret_key();
|
||||
///
|
||||
/// let config = NetworkConfig::<_, EthNetworkPrimitives>::builder(local_key)
|
||||
/// let config = NetworkConfig::<_, EthNetworkPrimitives>::builder(local_key, Runtime::test())
|
||||
/// .boot_nodes(mainnet_nodes())
|
||||
/// .build(client.clone());
|
||||
/// let transactions_manager_config = config.transactions_manager_config.clone();
|
||||
|
||||
@@ -713,7 +713,7 @@ where
|
||||
}
|
||||
|
||||
fn network_config_builder(secret_key: SecretKey) -> NetworkConfigBuilder {
|
||||
NetworkConfigBuilder::new(secret_key)
|
||||
NetworkConfigBuilder::new(secret_key, Runtime::test())
|
||||
.listener_addr(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)))
|
||||
.discovery_addr(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)))
|
||||
.disable_dns_discovery()
|
||||
|
||||
@@ -20,6 +20,7 @@ use reth_eth_wire_types::EthNetworkPrimitives;
|
||||
use reth_network_api::{PeerKind, PeerRequest, PeerRequestSender};
|
||||
use reth_network_peers::PeerId;
|
||||
use reth_storage_api::noop::NoopProvider;
|
||||
use reth_tasks::Runtime;
|
||||
use reth_transaction_pool::test_utils::{testing_pool, TestPool};
|
||||
use secp256k1::SecretKey;
|
||||
use std::sync::Arc;
|
||||
@@ -32,7 +33,7 @@ pub async fn new_tx_manager(
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let client = NoopProvider::default();
|
||||
|
||||
let config = NetworkConfigBuilder::new(secret_key)
|
||||
let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
|
||||
// let OS choose port
|
||||
.listener_port(0)
|
||||
.disable_discovery()
|
||||
|
||||
@@ -381,7 +381,7 @@ impl<N: NetworkPrimitives> TransactionFetcher<N> {
|
||||
let Some(TxFetchMetadata { retries, fallback_peers, .. }) =
|
||||
self.hashes_fetch_inflight_and_pending_fetch.get(&hash)
|
||||
else {
|
||||
return
|
||||
continue
|
||||
};
|
||||
|
||||
if let Some(peer_id) = fallback_peer {
|
||||
|
||||
@@ -2167,6 +2167,7 @@ mod tests {
|
||||
sync::{NetworkSyncUpdater, SyncState},
|
||||
};
|
||||
use reth_storage_api::noop::NoopProvider;
|
||||
use reth_tasks::Runtime;
|
||||
use reth_transaction_pool::test_utils::{
|
||||
testing_pool, MockTransaction, MockTransactionFactory, TestPool,
|
||||
};
|
||||
@@ -2196,7 +2197,7 @@ mod tests {
|
||||
|
||||
let client = NoopProvider::default();
|
||||
let pool = testing_pool();
|
||||
let config = NetworkConfigBuilder::eth(secret_key)
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.disable_discovery()
|
||||
.listener_port(0)
|
||||
.build(client);
|
||||
@@ -2266,7 +2267,7 @@ mod tests {
|
||||
|
||||
let client = NoopProvider::default();
|
||||
let pool = testing_pool();
|
||||
let config = NetworkConfigBuilder::new(secret_key)
|
||||
let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
|
||||
.disable_discovery()
|
||||
.listener_port(0)
|
||||
.build(client);
|
||||
@@ -2332,7 +2333,7 @@ mod tests {
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let client = NoopProvider::default();
|
||||
|
||||
let config = NetworkConfigBuilder::new(secret_key)
|
||||
let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
|
||||
// let OS choose port
|
||||
.listener_port(0)
|
||||
.disable_discovery()
|
||||
@@ -2440,7 +2441,7 @@ mod tests {
|
||||
|
||||
let client = NoopProvider::default();
|
||||
let pool = testing_pool();
|
||||
let config = NetworkConfigBuilder::new(secret_key)
|
||||
let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
|
||||
.disable_discovery()
|
||||
.listener_port(0)
|
||||
.build(client);
|
||||
@@ -2518,7 +2519,7 @@ mod tests {
|
||||
|
||||
let client = NoopProvider::default();
|
||||
let pool = testing_pool();
|
||||
let config = NetworkConfigBuilder::new(secret_key)
|
||||
let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
|
||||
.disable_discovery()
|
||||
.listener_port(0)
|
||||
.build(client);
|
||||
@@ -2936,7 +2937,7 @@ mod tests {
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let client = NoopProvider::default();
|
||||
|
||||
let network_config = NetworkConfigBuilder::new(secret_key)
|
||||
let network_config = NetworkConfigBuilder::new(secret_key, Runtime::test())
|
||||
.listener_port(0)
|
||||
.disable_discovery()
|
||||
.build(client.clone());
|
||||
|
||||
@@ -21,6 +21,7 @@ use reth_network_p2p::{
|
||||
use reth_network_peers::{mainnet_nodes, NodeRecord, TrustedPeer};
|
||||
use reth_provider::test_utils::MockEthProvider;
|
||||
use reth_storage_api::noop::NoopProvider;
|
||||
use reth_tasks::Runtime;
|
||||
use reth_tracing::init_test_tracing;
|
||||
use reth_transaction_pool::test_utils::testing_pool;
|
||||
use secp256k1::SecretKey;
|
||||
@@ -207,8 +208,9 @@ async fn test_connect_with_boot_nodes() {
|
||||
let mut discv4 = Discv4Config::builder();
|
||||
discv4.add_boot_nodes(mainnet_nodes());
|
||||
|
||||
let config =
|
||||
NetworkConfigBuilder::eth(secret_key).discovery(discv4).build(NoopProvider::default());
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.discovery(discv4)
|
||||
.build(NoopProvider::default());
|
||||
let network = NetworkManager::new(config).await.unwrap();
|
||||
|
||||
let handle = network.handle().clone();
|
||||
@@ -229,7 +231,9 @@ async fn test_connect_with_builder() {
|
||||
discv4.add_boot_nodes(mainnet_nodes());
|
||||
|
||||
let client = NoopProvider::default();
|
||||
let config = NetworkConfigBuilder::eth(secret_key).discovery(discv4).build(client.clone());
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.discovery(discv4)
|
||||
.build(client.clone());
|
||||
let (handle, network, _, requests) = NetworkManager::new(config)
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -265,7 +269,9 @@ async fn test_connect_to_trusted_peer() {
|
||||
let discv4 = Discv4Config::builder();
|
||||
|
||||
let client = NoopProvider::default();
|
||||
let config = NetworkConfigBuilder::eth(secret_key).discovery(discv4).build(client.clone());
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.discovery(discv4)
|
||||
.build(client.clone());
|
||||
let transactions_manager_config = config.transactions_manager_config.clone();
|
||||
let (handle, network, transactions, requests) = NetworkManager::new(config)
|
||||
.await
|
||||
@@ -381,7 +387,7 @@ async fn test_trusted_peer_only() {
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let peers_config = PeersConfig::test().with_trusted_nodes_only(true);
|
||||
|
||||
let config = NetworkConfigBuilder::eth(secret_key)
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.listener_port(0)
|
||||
.disable_discovery()
|
||||
.peer_config(peers_config)
|
||||
@@ -444,7 +450,7 @@ async fn test_network_state_change() {
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let peers_config = PeersConfig::test();
|
||||
|
||||
let config = NetworkConfigBuilder::eth(secret_key)
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.listener_port(0)
|
||||
.disable_discovery()
|
||||
.peer_config(peers_config)
|
||||
@@ -485,7 +491,7 @@ async fn test_exceed_outgoing_connections() {
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let peers_config = PeersConfig::test().with_max_outbound(1);
|
||||
|
||||
let config = NetworkConfigBuilder::eth(secret_key)
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.listener_port(0)
|
||||
.disable_discovery()
|
||||
.peer_config(peers_config)
|
||||
@@ -526,7 +532,7 @@ async fn test_disconnect_incoming_when_exceeded_incoming_connections() {
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let peers_config = PeersConfig::test().with_max_inbound(0);
|
||||
|
||||
let config = NetworkConfigBuilder::eth(secret_key)
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.listener_port(0)
|
||||
.disable_discovery()
|
||||
.peer_config(peers_config)
|
||||
@@ -643,7 +649,7 @@ async fn new_random_peer(
|
||||
let peers_config =
|
||||
PeersConfig::test().with_max_inbound(max_in_bound).with_trusted_nodes(trusted_nodes);
|
||||
|
||||
let config = NetworkConfigBuilder::new(secret_key)
|
||||
let config = NetworkConfigBuilder::new(secret_key, Runtime::test())
|
||||
.listener_port(0)
|
||||
.disable_discovery()
|
||||
.peer_config(peers_config)
|
||||
@@ -715,7 +721,7 @@ async fn test_connect_peer_in_different_network_should_fail() {
|
||||
// If the remote disconnect first, then we would not get a fatal protocol error. So set
|
||||
// max_backoff_count to 0 to speed up the removal of the peer.
|
||||
let peers_config = PeersConfig::default().with_max_backoff_count(0);
|
||||
let config = NetworkConfigBuilder::eth(secret_key)
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.listener_port(0)
|
||||
.disable_discovery()
|
||||
.peer_config(peers_config)
|
||||
|
||||
@@ -19,6 +19,7 @@ use reth_network::{
|
||||
};
|
||||
use reth_network_api::{Direction, NetworkInfo, PeerId, Peers};
|
||||
use reth_provider::{noop::NoopProvider, test_utils::MockEthProvider};
|
||||
use reth_tasks::Runtime;
|
||||
use secp256k1::SecretKey;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
@@ -285,7 +286,7 @@ async fn test_connect_to_non_multiplex_peer() {
|
||||
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
|
||||
let config = NetworkConfigBuilder::eth(secret_key)
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.listener_port(0)
|
||||
.disable_discovery()
|
||||
.build(NoopProvider::default());
|
||||
|
||||
@@ -11,6 +11,7 @@ use reth_network::{
|
||||
};
|
||||
use reth_network_api::{NetworkInfo, PeersInfo};
|
||||
use reth_storage_api::noop::NoopProvider;
|
||||
use reth_tasks::Runtime;
|
||||
use secp256k1::SecretKey;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
@@ -29,7 +30,7 @@ fn is_addr_in_use_kind(err: &NetworkError, kind: ServiceKind) -> bool {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_is_default_syncing() {
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let config = NetworkConfigBuilder::eth(secret_key)
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.disable_discovery()
|
||||
.listener_port(0)
|
||||
.build(NoopProvider::default());
|
||||
@@ -40,13 +41,13 @@ async fn test_is_default_syncing() {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_listener_addr_in_use() {
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let config = NetworkConfigBuilder::eth(secret_key)
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.disable_discovery()
|
||||
.listener_port(0)
|
||||
.build(NoopProvider::default());
|
||||
let network = NetworkManager::new(config).await.unwrap();
|
||||
let listener_port = network.local_addr().port();
|
||||
let config = NetworkConfigBuilder::eth(secret_key)
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.listener_port(listener_port)
|
||||
.disable_discovery()
|
||||
.build(NoopProvider::default());
|
||||
@@ -74,7 +75,7 @@ async fn test_discovery_addr_in_use() {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_discv5_and_discv4_same_socket_fails() {
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let config = NetworkConfigBuilder::eth(secret_key)
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.listener_port(DEFAULT_DISCOVERY_PORT)
|
||||
.discovery_v5(
|
||||
reth_discv5::Config::builder((DEFAULT_DISCOVERY_ADDR, DEFAULT_DISCOVERY_PORT).into())
|
||||
@@ -105,7 +106,7 @@ async fn test_discv5_and_rlpx_same_socket_ok_without_discv4() {
|
||||
.port();
|
||||
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let config = NetworkConfigBuilder::eth(secret_key)
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.listener_port(test_port)
|
||||
.disable_discv4_discovery()
|
||||
.discovery_v5(
|
||||
@@ -126,7 +127,7 @@ async fn test_discv5_and_rlpx_same_socket_ok_without_discv4() {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_tcp_port_node_record_no_discovery() {
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let config = NetworkConfigBuilder::eth(secret_key)
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.listener_port(0)
|
||||
.disable_discovery()
|
||||
.build_with_noop_provider(MAINNET.clone());
|
||||
@@ -144,7 +145,7 @@ async fn test_tcp_port_node_record_no_discovery() {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_tcp_port_node_record_discovery() {
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let config = NetworkConfigBuilder::eth(secret_key)
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.listener_port(0)
|
||||
.discovery_port(0)
|
||||
.disable_dns_discovery()
|
||||
@@ -163,7 +164,7 @@ async fn test_tcp_port_node_record_discovery() {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_node_record_address_with_nat() {
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let config = NetworkConfigBuilder::eth(secret_key)
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.add_nat(Some(NatResolver::ExternalIp("10.1.1.1".parse().unwrap())))
|
||||
.disable_discv4_discovery()
|
||||
.disable_dns_discovery()
|
||||
@@ -179,7 +180,7 @@ async fn test_node_record_address_with_nat() {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_node_record_address_with_nat_disable_discovery() {
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let config = NetworkConfigBuilder::eth(secret_key)
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.add_nat(Some(NatResolver::ExternalIp("10.1.1.1".parse().unwrap())))
|
||||
.disable_discovery()
|
||||
.disable_nat()
|
||||
|
||||
@@ -985,8 +985,8 @@ impl<Node: FullNodeTypes<Types: NodeTypes<ChainSpec: Hardforks>>> BuilderContext
|
||||
self.config().chain.clone(),
|
||||
secret_key,
|
||||
default_peers_path,
|
||||
self.executor.clone(),
|
||||
)
|
||||
.with_task_executor(self.executor.clone())
|
||||
.set_head(self.head);
|
||||
|
||||
Ok(builder)
|
||||
|
||||
@@ -343,10 +343,7 @@ mod test {
|
||||
payload_builder_handle: PayloadBuilderHandle::<EthEngineTypes>::noop(),
|
||||
};
|
||||
|
||||
let task_executor = {
|
||||
let runtime = tokio::runtime::Runtime::new().unwrap();
|
||||
Runtime::with_existing_handle(runtime.handle().clone()).unwrap()
|
||||
};
|
||||
let task_executor = Runtime::test();
|
||||
|
||||
let node = NodeAdapter { components, task_executor, provider: NoopProvider::default() };
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ reth-rpc-eth-types.workspace = true
|
||||
reth-rpc-server-types.workspace = true
|
||||
reth-rpc-convert.workspace = true
|
||||
reth-transaction-pool.workspace = true
|
||||
reth-tasks.workspace = true
|
||||
reth-tracing.workspace = true
|
||||
reth-config = { workspace = true, features = ["serde"] }
|
||||
reth-discv4.workspace = true
|
||||
|
||||
@@ -60,6 +60,17 @@ pub struct BenchmarkArgs {
|
||||
#[arg(long, short, value_name = "BENCHMARK_OUTPUT", verbatim_doc_comment)]
|
||||
pub output: Option<PathBuf>,
|
||||
|
||||
/// Optional Prometheus metrics endpoint to scrape after each block.
|
||||
///
|
||||
/// When provided, reth-bench will fetch metrics from this URL after each
|
||||
/// `newPayload` / `forkchoiceUpdated` call, recording per-block execution
|
||||
/// and state root durations. Results are written to `metrics.csv` in the
|
||||
/// output directory.
|
||||
///
|
||||
/// Example: `http://127.0.0.1:9001/metrics`
|
||||
#[arg(long = "metrics-url", value_name = "URL", verbatim_doc_comment)]
|
||||
pub metrics_url: Option<String>,
|
||||
|
||||
/// Use `reth_newPayload` endpoint instead of `engine_newPayload*`.
|
||||
///
|
||||
/// The `reth_newPayload` endpoint is a reth-specific extension that takes `ExecutionData`
|
||||
|
||||
@@ -48,8 +48,10 @@ pub struct LogArgs {
|
||||
|
||||
/// The maximum amount of log files that will be stored. If set to 0, background file logging
|
||||
/// is disabled.
|
||||
#[arg(long = "log.file.max-files", value_name = "COUNT", global = true, default_value_t = 5)]
|
||||
pub log_file_max_files: usize,
|
||||
///
|
||||
/// Default: 5 for `node` command, 0 for non-node utility subcommands.
|
||||
#[arg(long = "log.file.max-files", value_name = "COUNT", global = true)]
|
||||
pub log_file_max_files: Option<usize>,
|
||||
|
||||
/// Write logs to journald.
|
||||
#[arg(long = "log.journald", global = true)]
|
||||
@@ -108,6 +110,28 @@ pub struct LogArgs {
|
||||
}
|
||||
|
||||
impl LogArgs {
|
||||
/// The default number of log files for the `node` subcommand.
|
||||
pub const DEFAULT_MAX_LOG_FILES_NODE: usize = 5;
|
||||
|
||||
/// Returns the effective maximum number of log files.
|
||||
///
|
||||
/// If `log_file_max_files` was explicitly set, returns that value.
|
||||
/// Otherwise returns 0 (file logging disabled).
|
||||
///
|
||||
/// Note: Callers should apply the node-specific default (5) before calling
|
||||
/// `init_tracing` if the command is the `node` subcommand.
|
||||
pub fn effective_log_file_max_files(&self) -> usize {
|
||||
self.log_file_max_files.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Applies the default `log_file_max_files` value for the `node` subcommand
|
||||
/// if not explicitly set by the user.
|
||||
pub const fn apply_node_defaults(&mut self) {
|
||||
if self.log_file_max_files.is_none() {
|
||||
self.log_file_max_files = Some(Self::DEFAULT_MAX_LOG_FILES_NODE);
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`LayerInfo`] instance.
|
||||
fn layer_info(&self, format: LogFormat, filter: String, use_color: bool) -> LayerInfo {
|
||||
LayerInfo::new(
|
||||
@@ -124,7 +148,7 @@ impl LogArgs {
|
||||
self.log_file_directory.clone().into(),
|
||||
self.log_file_name.clone(),
|
||||
self.log_file_max_size * MB_TO_BYTES,
|
||||
self.log_file_max_files,
|
||||
self.effective_log_file_max_files(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -154,7 +178,7 @@ impl LogArgs {
|
||||
tracer = tracer.with_journald(self.journald_filter.clone());
|
||||
}
|
||||
|
||||
if self.log_file_max_files > 0 {
|
||||
if self.effective_log_file_max_files() > 0 {
|
||||
let info = self.file_info();
|
||||
let file = self.layer_info(self.log_file_format, self.log_file_filter.clone(), false);
|
||||
tracer = tracer.with_file(file, info);
|
||||
|
||||
@@ -39,6 +39,7 @@ use reth_network::{
|
||||
HelloMessageWithProtocols, NetworkConfigBuilder, NetworkPrimitives,
|
||||
};
|
||||
use reth_network_peers::{mainnet_nodes, TrustedPeer};
|
||||
use reth_tasks::Runtime;
|
||||
use secp256k1::SecretKey;
|
||||
use std::str::FromStr;
|
||||
use tracing::error;
|
||||
@@ -326,6 +327,7 @@ impl NetworkArgs {
|
||||
chain_spec: impl EthChainSpec,
|
||||
secret_key: SecretKey,
|
||||
default_peers_file: PathBuf,
|
||||
executor: Runtime,
|
||||
) -> NetworkConfigBuilder<N> {
|
||||
let addr = self.resolved_addr();
|
||||
let chain_bootnodes = self
|
||||
@@ -345,7 +347,7 @@ impl NetworkArgs {
|
||||
.with_enforce_enr_fork_id(self.enforce_enr_fork_id);
|
||||
|
||||
// Configure basic network stack
|
||||
NetworkConfigBuilder::<N>::new(secret_key)
|
||||
NetworkConfigBuilder::<N>::new(secret_key, executor)
|
||||
.external_ip_resolver(self.nat.clone())
|
||||
.sessions_config(
|
||||
config.sessions.clone().with_upscaled_event_buffer(peers_config.max_peers()),
|
||||
@@ -1097,6 +1099,7 @@ mod tests {
|
||||
MAINNET.clone(),
|
||||
secret_key,
|
||||
peers_file.clone(),
|
||||
Runtime::test(),
|
||||
);
|
||||
|
||||
let net_cfg = builder.build_with_noop_provider(MAINNET.clone());
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::{cli::config::PayloadBuilderConfig, version::default_extra_data};
|
||||
use alloy_consensus::constants::MAXIMUM_EXTRA_DATA_SIZE;
|
||||
use alloy_primitives::Bytes;
|
||||
use clap::{
|
||||
builder::{RangedU64ValueParser, TypedValueParser},
|
||||
Arg, Args, Command,
|
||||
@@ -8,7 +9,7 @@ use reth_cli_util::{
|
||||
parse_duration_from_secs, parse_duration_from_secs_or_ms,
|
||||
parsers::format_duration_as_secs_or_ms,
|
||||
};
|
||||
use std::{borrow::Cow, ffi::OsStr, sync::OnceLock, time::Duration};
|
||||
use std::{ffi::OsStr, sync::OnceLock, time::Duration};
|
||||
|
||||
/// Global static payload builder defaults
|
||||
static PAYLOAD_BUILDER_DEFAULTS: OnceLock<DefaultPayloadBuilderValues> = OnceLock::new();
|
||||
@@ -80,12 +81,15 @@ impl Default for DefaultPayloadBuilderValues {
|
||||
#[command(next_help_heading = "Builder")]
|
||||
pub struct PayloadBuilderArgs {
|
||||
/// Block extra data set by the payload builder.
|
||||
///
|
||||
/// If the value is a `0x`-prefixed hex string, it is decoded into raw bytes. Otherwise, the
|
||||
/// raw UTF-8 bytes of the string are used.
|
||||
#[arg(
|
||||
long = "builder.extradata",
|
||||
value_parser = ExtraDataValueParser::default(),
|
||||
default_value_t = DefaultPayloadBuilderValues::get_global().extra_data.clone()
|
||||
default_value = DefaultPayloadBuilderValues::get_global().extra_data.as_str()
|
||||
)]
|
||||
pub extra_data: String,
|
||||
pub extra_data: Bytes,
|
||||
|
||||
/// Target gas limit for built blocks.
|
||||
#[arg(long = "builder.gaslimit", alias = "miner.gaslimit", value_name = "GAS_LIMIT")]
|
||||
@@ -130,7 +134,7 @@ impl Default for PayloadBuilderArgs {
|
||||
fn default() -> Self {
|
||||
let defaults = DefaultPayloadBuilderValues::get_global();
|
||||
Self {
|
||||
extra_data: defaults.extra_data.clone(),
|
||||
extra_data: Bytes::from(defaults.extra_data.as_bytes().to_vec()),
|
||||
interval: parse_duration_from_secs_or_ms(defaults.interval.as_str()).unwrap(),
|
||||
gas_limit: None,
|
||||
deadline: Duration::from_secs(defaults.deadline.parse().unwrap()),
|
||||
@@ -141,8 +145,8 @@ impl Default for PayloadBuilderArgs {
|
||||
}
|
||||
|
||||
impl PayloadBuilderConfig for PayloadBuilderArgs {
|
||||
fn extra_data(&self) -> Cow<'_, str> {
|
||||
self.extra_data.as_str().into()
|
||||
fn extra_data(&self) -> Bytes {
|
||||
self.extra_data.clone()
|
||||
}
|
||||
|
||||
fn interval(&self) -> Duration {
|
||||
@@ -171,7 +175,7 @@ impl PayloadBuilderConfig for PayloadBuilderArgs {
|
||||
struct ExtraDataValueParser;
|
||||
|
||||
impl TypedValueParser for ExtraDataValueParser {
|
||||
type Value = String;
|
||||
type Value = Bytes;
|
||||
|
||||
fn parse_ref(
|
||||
&self,
|
||||
@@ -181,7 +185,19 @@ impl TypedValueParser for ExtraDataValueParser {
|
||||
) -> Result<Self::Value, clap::Error> {
|
||||
let val =
|
||||
value.to_str().ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
|
||||
if val.len() > MAXIMUM_EXTRA_DATA_SIZE {
|
||||
|
||||
let bytes = if let Some(hex) = val.strip_prefix("0x") {
|
||||
alloy_primitives::hex::decode(hex).map_err(|e| {
|
||||
clap::Error::raw(
|
||||
clap::error::ErrorKind::InvalidValue,
|
||||
format!("Invalid hex in extradata: {e}"),
|
||||
)
|
||||
})?
|
||||
} else {
|
||||
val.as_bytes().to_vec()
|
||||
};
|
||||
|
||||
if bytes.len() > MAXIMUM_EXTRA_DATA_SIZE {
|
||||
return Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::InvalidValue,
|
||||
format!(
|
||||
@@ -189,7 +205,8 @@ impl TypedValueParser for ExtraDataValueParser {
|
||||
),
|
||||
))
|
||||
}
|
||||
Ok(val.to_string())
|
||||
|
||||
Ok(bytes.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +249,7 @@ mod tests {
|
||||
extra_data.as_str(),
|
||||
])
|
||||
.args;
|
||||
assert_eq!(args.extra_data, extra_data);
|
||||
assert_eq!(args.extra_data.as_ref(), extra_data.as_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -246,6 +263,49 @@ mod tests {
|
||||
assert!(args.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_hex_extra_data() {
|
||||
let hex = format!("0x{}", "ab".repeat(MAXIMUM_EXTRA_DATA_SIZE));
|
||||
let args = CommandParser::<PayloadBuilderArgs>::parse_from([
|
||||
"reth",
|
||||
"--builder.extradata",
|
||||
hex.as_str(),
|
||||
])
|
||||
.args;
|
||||
assert_eq!(args.extra_data.as_ref(), vec![0xab; MAXIMUM_EXTRA_DATA_SIZE].as_slice());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oversized_hex_extra_data() {
|
||||
let hex = format!("0x{}", "ab".repeat(MAXIMUM_EXTRA_DATA_SIZE + 1));
|
||||
assert!(CommandParser::<PayloadBuilderArgs>::try_parse_from([
|
||||
"reth",
|
||||
"--builder.extradata",
|
||||
hex.as_str(),
|
||||
])
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_hex_extra_data() {
|
||||
assert!(CommandParser::<PayloadBuilderArgs>::try_parse_from([
|
||||
"reth",
|
||||
"--builder.extradata",
|
||||
"0xZZZZ",
|
||||
])
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_odd_length_hex_extra_data() {
|
||||
assert!(CommandParser::<PayloadBuilderArgs>::try_parse_from([
|
||||
"reth",
|
||||
"--builder.extradata",
|
||||
"0xabc",
|
||||
])
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn payload_builder_args_default_sanity_check() {
|
||||
let default_args = PayloadBuilderArgs::default();
|
||||
|
||||
@@ -5,7 +5,7 @@ use alloy_primitives::Bytes;
|
||||
use reth_chainspec::{Chain, ChainKind, NamedChain};
|
||||
use reth_network::{protocol::IntoRlpxSubProtocol, NetworkPrimitives};
|
||||
use reth_transaction_pool::PoolConfig;
|
||||
use std::{borrow::Cow, time::Duration};
|
||||
use std::time::Duration;
|
||||
|
||||
/// 60M gas limit
|
||||
const ETHEREUM_BLOCK_GAS_LIMIT_60M: u64 = 60_000_000;
|
||||
@@ -15,13 +15,8 @@ const ETHEREUM_BLOCK_GAS_LIMIT_60M: u64 = 60_000_000;
|
||||
/// This provides all basic payload builder settings and is implemented by the
|
||||
/// [`PayloadBuilderArgs`](crate::args::PayloadBuilderArgs) type.
|
||||
pub trait PayloadBuilderConfig {
|
||||
/// Block extra data set by the payload builder.
|
||||
fn extra_data(&self) -> Cow<'_, str>;
|
||||
|
||||
/// Returns the extra data as bytes.
|
||||
fn extra_data_bytes(&self) -> Bytes {
|
||||
self.extra_data().as_bytes().to_vec().into()
|
||||
}
|
||||
fn extra_data(&self) -> Bytes;
|
||||
|
||||
/// The interval at which the job should build a new payload after the last.
|
||||
fn interval(&self) -> Duration;
|
||||
|
||||
@@ -206,6 +206,10 @@ impl NodeState {
|
||||
|
||||
self.current_stage = Some(current_stage);
|
||||
}
|
||||
PipelineEvent::Unwound { stage_id, result } => {
|
||||
info!(stage = %stage_id, checkpoint = %result.checkpoint.block_number, "Unwound stage");
|
||||
self.current_stage = None;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,7 +445,7 @@ mod tests {
|
||||
build_profile: "test",
|
||||
};
|
||||
|
||||
let runtime = Runtime::with_existing_handle(tokio::runtime::Handle::current()).unwrap();
|
||||
let runtime = Runtime::test();
|
||||
|
||||
let hooks = Hooks::builder().build();
|
||||
|
||||
|
||||
@@ -346,6 +346,18 @@ mod block_bincode {
|
||||
}
|
||||
}
|
||||
|
||||
impl super::SerdeBincodeCompat for alloy_consensus::EthereumReceipt {
|
||||
type BincodeRepr<'a> = alloy_consensus::serde_bincode_compat::EthereumReceipt<'a>;
|
||||
|
||||
fn as_repr(&self) -> Self::BincodeRepr<'_> {
|
||||
self.into()
|
||||
}
|
||||
|
||||
fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
|
||||
repr.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "op")]
|
||||
impl super::SerdeBincodeCompat for op_alloy_consensus::OpReceipt {
|
||||
type BincodeRepr<'a> = op_alloy_consensus::serde_bincode_compat::OpReceipt<'a>;
|
||||
|
||||
@@ -78,6 +78,12 @@ impl InMemorySize for alloy_consensus::Receipt {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: InMemorySize> InMemorySize for alloy_consensus::EthereumReceipt<T> {
|
||||
fn size(&self) -> usize {
|
||||
core::mem::size_of::<Self>() + self.logs.iter().map(|log| log.size()).sum::<usize>()
|
||||
}
|
||||
}
|
||||
|
||||
impl InMemorySize for LogData {
|
||||
fn size(&self) -> usize {
|
||||
self.data.len() + core::mem::size_of_val(self.topics())
|
||||
|
||||
@@ -8,6 +8,15 @@ use reth_db_api::{
|
||||
use std::{fmt::Debug, ops::RangeBounds};
|
||||
use tracing::debug;
|
||||
|
||||
/// Result of a single prune step in [`DbTxPruneExt::prune_table_with_range_step`].
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct PruneStepResult {
|
||||
/// `true` if the walker is finished, `false` if it may have more data to prune.
|
||||
done: bool,
|
||||
/// `true` if the current entry was deleted, `false` if it was skipped.
|
||||
deleted: bool,
|
||||
}
|
||||
|
||||
pub(crate) trait DbTxPruneExt: DbTxMut + DbTx {
|
||||
/// Clear the entire table in a single operation.
|
||||
///
|
||||
@@ -91,17 +100,20 @@ pub(crate) trait DbTxPruneExt: DbTxMut + DbTx {
|
||||
break false
|
||||
}
|
||||
|
||||
let done = self.prune_table_with_range_step(
|
||||
let result = self.prune_table_with_range_step(
|
||||
&mut walker,
|
||||
limiter,
|
||||
&mut skip_filter,
|
||||
&mut delete_callback,
|
||||
)?;
|
||||
|
||||
if done {
|
||||
if result.deleted {
|
||||
deleted_entries += 1;
|
||||
}
|
||||
|
||||
if result.done {
|
||||
break true
|
||||
}
|
||||
deleted_entries += 1;
|
||||
};
|
||||
|
||||
Ok((deleted_entries, done))
|
||||
@@ -109,8 +121,6 @@ pub(crate) trait DbTxPruneExt: DbTxMut + DbTx {
|
||||
|
||||
/// Steps once with the given walker and prunes the entry in the table.
|
||||
///
|
||||
/// Returns `true` if the walker is finished, `false` if it may have more data to prune.
|
||||
///
|
||||
/// CAUTION: Pruner limits are not checked. This allows for a clean exit of a prune run that's
|
||||
/// pruning different tables concurrently, by letting them step to the same height before
|
||||
/// timing out.
|
||||
@@ -120,18 +130,21 @@ pub(crate) trait DbTxPruneExt: DbTxMut + DbTx {
|
||||
limiter: &mut PruneLimiter,
|
||||
skip_filter: &mut impl FnMut(&TableRow<T>) -> bool,
|
||||
delete_callback: &mut impl FnMut(TableRow<T>),
|
||||
) -> Result<bool, DatabaseError> {
|
||||
let Some(res) = walker.next() else { return Ok(true) };
|
||||
) -> Result<PruneStepResult, DatabaseError> {
|
||||
let Some(res) = walker.next() else {
|
||||
return Ok(PruneStepResult { done: true, deleted: false })
|
||||
};
|
||||
|
||||
let row = res?;
|
||||
|
||||
if !skip_filter(&row) {
|
||||
if skip_filter(&row) {
|
||||
Ok(PruneStepResult { done: false, deleted: false })
|
||||
} else {
|
||||
walker.delete_current()?;
|
||||
limiter.increment_deleted_entries_count();
|
||||
delete_callback(row);
|
||||
Ok(PruneStepResult { done: false, deleted: true })
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Prune a DUPSORT table for the specified key range.
|
||||
|
||||
@@ -24,7 +24,7 @@ where
|
||||
pub fn bootstrap(config: EthConfig, executor: Runtime, eth_api: EthApi) -> Self {
|
||||
let filter = EthFilter::new(eth_api.clone(), config.filter_config(), executor.clone());
|
||||
|
||||
let pubsub = EthPubSub::with_spawner(eth_api.clone(), executor);
|
||||
let pubsub = EthPubSub::new(eth_api.clone(), executor);
|
||||
|
||||
Self { api: eth_api, filter, pubsub }
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ workspace = true
|
||||
# reth
|
||||
reth-primitives-traits.workspace = true
|
||||
reth-evm.workspace = true
|
||||
reth-ethereum-primitives.workspace = true
|
||||
|
||||
# ethereum
|
||||
alloy-primitives.workspace = true
|
||||
@@ -29,7 +28,6 @@ alloy-evm = { workspace = true, features = ["rpc"] }
|
||||
# optimism
|
||||
op-alloy-consensus = { workspace = true, optional = true }
|
||||
op-alloy-rpc-types = { workspace = true, optional = true }
|
||||
op-alloy-network = { workspace = true, optional = true }
|
||||
|
||||
# io
|
||||
jsonrpsee-types.workspace = true
|
||||
@@ -48,7 +46,6 @@ default = []
|
||||
op = [
|
||||
"dep:op-alloy-consensus",
|
||||
"dep:op-alloy-rpc-types",
|
||||
"dep:op-alloy-network",
|
||||
"reth-evm/op",
|
||||
"reth-primitives-traits/op",
|
||||
"alloy-evm/op",
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
//! Conversion traits for block responses to primitive block types.
|
||||
|
||||
use alloy_network::Network;
|
||||
use std::convert::Infallible;
|
||||
|
||||
/// Trait for converting network block responses to primitive block types.
|
||||
pub trait TryFromBlockResponse<N: Network> {
|
||||
/// The error type returned if the conversion fails.
|
||||
type Error: core::error::Error + Send + Sync + Unpin;
|
||||
|
||||
/// Converts a network block response to a primitive block type.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns `Ok(Self)` on successful conversion, or `Err(Self::Error)` if the conversion fails.
|
||||
fn from_block_response(block_response: N::BlockResponse) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl<N: Network, T> TryFromBlockResponse<N> for alloy_consensus::Block<T>
|
||||
where
|
||||
N::BlockResponse: Into<Self>,
|
||||
{
|
||||
type Error = Infallible;
|
||||
|
||||
fn from_block_response(block_response: N::BlockResponse) -> Result<Self, Self::Error> {
|
||||
Ok(block_response.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloy_consensus::{Block, TxEnvelope};
|
||||
use alloy_network::Ethereum;
|
||||
use alloy_rpc_types_eth::BlockTransactions;
|
||||
|
||||
#[test]
|
||||
fn test_try_from_block_response() {
|
||||
let rpc_block: alloy_rpc_types_eth::Block =
|
||||
alloy_rpc_types_eth::Block::new(Default::default(), BlockTransactions::Full(vec![]));
|
||||
let result =
|
||||
<Block<TxEnvelope> as TryFromBlockResponse<Ethereum>>::from_block_response(rpc_block);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
@@ -10,17 +10,12 @@
|
||||
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
pub mod block;
|
||||
pub mod receipt;
|
||||
mod rpc;
|
||||
pub mod transaction;
|
||||
|
||||
pub use block::TryFromBlockResponse;
|
||||
pub use receipt::TryFromReceiptResponse;
|
||||
pub use rpc::*;
|
||||
pub use transaction::{
|
||||
RpcConvert, RpcConverter, TransactionConversionError, TryFromTransactionResponse, TryIntoSimTx,
|
||||
TxInfoMapper,
|
||||
RpcConvert, RpcConverter, TransactionConversionError, TryIntoSimTx, TxInfoMapper,
|
||||
};
|
||||
|
||||
pub use alloy_evm::rpc::{CallFees, CallFeesError, EthTxEnvError, TryIntoTxEnv};
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
//! Conversion traits for receipt responses to primitive receipt types.
|
||||
|
||||
use alloy_network::Network;
|
||||
use std::convert::Infallible;
|
||||
|
||||
/// Trait for converting network receipt responses to primitive receipt types.
|
||||
pub trait TryFromReceiptResponse<N: Network> {
|
||||
/// The error type returned if the conversion fails.
|
||||
type Error: core::error::Error + Send + Sync + Unpin;
|
||||
|
||||
/// Converts a network receipt response to a primitive receipt type.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns `Ok(Self)` on successful conversion, or `Err(Self::Error)` if the conversion fails.
|
||||
fn from_receipt_response(receipt_response: N::ReceiptResponse) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl TryFromReceiptResponse<alloy_network::Ethereum> for reth_ethereum_primitives::Receipt {
|
||||
type Error = Infallible;
|
||||
|
||||
fn from_receipt_response(
|
||||
receipt_response: alloy_rpc_types_eth::TransactionReceipt,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(receipt_response.into_inner().into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "op")]
|
||||
impl TryFromReceiptResponse<op_alloy_network::Optimism> for op_alloy_consensus::OpReceipt {
|
||||
type Error = Infallible;
|
||||
|
||||
fn from_receipt_response(
|
||||
receipt_response: op_alloy_rpc_types::OpTransactionReceipt,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(receipt_response.inner.inner.into_components().0.map_logs(Into::into))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloy_consensus::ReceiptEnvelope;
|
||||
use alloy_network::Ethereum;
|
||||
use reth_ethereum_primitives::Receipt;
|
||||
|
||||
#[test]
|
||||
fn test_try_from_receipt_response() {
|
||||
let rpc_receipt = alloy_rpc_types_eth::TransactionReceipt {
|
||||
inner: ReceiptEnvelope::Eip1559(Default::default()),
|
||||
transaction_hash: Default::default(),
|
||||
transaction_index: None,
|
||||
block_hash: None,
|
||||
block_number: None,
|
||||
gas_used: 0,
|
||||
effective_gas_price: 0,
|
||||
blob_gas_used: None,
|
||||
blob_gas_price: None,
|
||||
from: Default::default(),
|
||||
to: None,
|
||||
contract_address: None,
|
||||
};
|
||||
let result =
|
||||
<Receipt as TryFromReceiptResponse<Ethereum>>::from_receipt_response(rpc_receipt);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[cfg(feature = "op")]
|
||||
#[test]
|
||||
fn test_try_from_receipt_response_optimism() {
|
||||
use alloy_consensus::ReceiptWithBloom;
|
||||
use op_alloy_consensus::OpReceipt;
|
||||
use op_alloy_network::Optimism;
|
||||
use op_alloy_rpc_types::OpTransactionReceipt;
|
||||
|
||||
let op_receipt = OpTransactionReceipt {
|
||||
inner: alloy_rpc_types_eth::TransactionReceipt {
|
||||
inner: ReceiptWithBloom {
|
||||
receipt: OpReceipt::Eip1559(Default::default()),
|
||||
logs_bloom: Default::default(),
|
||||
},
|
||||
transaction_hash: Default::default(),
|
||||
transaction_index: None,
|
||||
block_hash: None,
|
||||
block_number: None,
|
||||
gas_used: 0,
|
||||
effective_gas_price: 0,
|
||||
blob_gas_used: None,
|
||||
blob_gas_price: None,
|
||||
from: Default::default(),
|
||||
to: None,
|
||||
contract_address: None,
|
||||
},
|
||||
l1_block_info: Default::default(),
|
||||
};
|
||||
let result =
|
||||
<OpReceipt as TryFromReceiptResponse<Optimism>>::from_receipt_response(op_receipt);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user