diff --git a/.github/workflows/benchmark-gpu.yml b/.github/workflows/benchmark-gpu.yml index 040b51c89..11fcbf222 100644 --- a/.github/workflows/benchmark-gpu.yml +++ b/.github/workflows/benchmark-gpu.yml @@ -6,10 +6,11 @@ name: Benchmark GPU (PR) # It builds the cli at the PR head and at main, runs N interleaved pairs on the GPU, # posts the paired-t + Wilcoxon verdict back to the PR, then ALWAYS destroys the box. # -# Triggered by a "/bench-gpu [N] [cont[TX]|mono[TX]]" comment on a PR (N = pair count, -# default 14) or via workflow_dispatch. Workload default: ethrex 100tx --continuations; -# "mono[TX]" = legacy monolithic prove. Cont proofs need rkyv pointer_width_64 on both -# sides, so PR branches older than that fix must rebase before a cont bench. +# Triggered by a "/bench-gpu [N]" comment on a PR (N = pair count, default 14) or via +# workflow_dispatch. +# +# Workload: the real block (see tooling/ethrex-block-converter/README.md), proven with +# --continuations at the calibrated epoch size below. # Orchestration runs on a GitHub-hosted runner; all GPU work happens on the rented # Vast box (provisioned by the template onstart). # @@ -23,9 +24,6 @@ on: pairs: description: "Number of A/B/B/A pairs" default: "14" - mode: - description: "Workload: cont[TX] (--continuations, TX defaults to 100) or mono[TX] (monolithic, TX defaults to 5)" - default: "cont100" issue_comment: types: [created] @@ -47,6 +45,25 @@ env: VAST_IMAGE_DISK: "64" # cli features for the ABBA build — the GPU (cuda) prover path plus jemalloc heap stats. BENCH_FEATURES: "jemalloc-stats,prover/cuda" + # Continuation epoch for the REAL-BLOCK path, from the RTX 5090 calibration on + # 2026-07-31 against main @9ccdaf2 (raw traces: + # ~/workspace/lambda_vm_bench_cache/gpu_epoch_calib_2026-07-31/, PROVENANCE.txt). + # Measured on the 32,607 MiB card, same fixture and CLI, one prove per setting: + # + # 2^21 70.52 s wall 19,193 MiB VRAM (58.9%) 25 epochs 1.65 GB proof + # 2^22 59.87 s wall 23,193 MiB VRAM (71.1%) 13 epochs 1.12 GB proof + # 2^23 OOM at 32,079 MiB (98.4%) after 9.7 s — needs ~44 GiB + # + # So 2^22 is the largest setting that fits a 32 GiB card, and it is ~15% faster than + # 2^21 (equivalently, 2^21 is ~18% slower) with 28.9% VRAM headroom left. 2^23 is out + # of reach for every card below 48 GiB, not just this one. + # + # GPU PATH ONLY, and deliberately so. It is NOT pushed into bench_abba.sh's default + # (2^20, which the CPU /bench-abba uses) nor into the CLI's + # DEFAULT_CONTINUATION_EPOCH_SIZE_LOG2 (also 20): 2^22 needs ~28 GiB of HOST memory on + # a CPU build, which would break laptops. VRAM is the binding constraint here and host + # RAM is the binding constraint there, so the two defaults are not the same question. + GPU_REAL_EPOCH_LOG2: "22" # Unique per-run label set on the instance, for easy identification in the Vast console. RUN_LABEL: "gpu-bench-${{ github.run_id }}-${{ github.run_attempt }}" # Pin the Vast CLI to an immutable commit (a PyPI version can be re-published; a commit @@ -63,8 +80,8 @@ jobs: github.event.issue.pull_request && startsWith(github.event.comment.body, '/bench-gpu') && contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)) - # Provisioning + dual cuda build (~30 min) + 2*pairs proves (~3.5 min each at - # the default cont100). Sized for the 32-pair worst case (~4.5 hr) with headroom; + # Provisioning + dual cuda build + 2*pairs real-block proves (~2 min each, + # host-CPU-dependent). Sized for the 32-pair worst case with generous headroom; # teardown still always destroys the box. timeout-minutes: 330 steps: @@ -76,47 +93,30 @@ jobs: COMMENT_BODY: ${{ github.event.comment.body }} PR_NUM: ${{ github.event.issue.number }} DISPATCH_PAIRS: ${{ github.event.inputs.pairs }} - DISPATCH_MODE: ${{ github.event.inputs.mode }} DISPATCH_REF: ${{ github.ref_name }} run: | if [ "$EVENT_NAME" = "issue_comment" ]; then # Pin the head SHA (works for fork PRs; avoids a force-push race mid-run). HEAD_SHA=$(gh pr view "$PR_NUM" --repo "$GITHUB_REPOSITORY" --json headRefOid -q .headRefOid) OUT_PR_NUM="$PR_NUM"; OUT_HEAD_SHA="$HEAD_SHA"; OUT_BRANCH="" - # Everything after "/bench-gpu" on its line, tokens in any order: - # a number = pair count; cont[TX]/mono[TX] = workload (see loop below). + # Everything after "/bench-gpu" on its line: the only token is an + # optional pair count. ARGS=$(printf '%s' "$COMMENT_BODY" | tr -d '\r' | sed -n 's|^/bench-gpu||p' | head -n1) PAIRS=14 else # workflow_dispatch: compare this branch vs main. OUT_PR_NUM=""; OUT_HEAD_SHA=""; OUT_BRANCH="$DISPATCH_REF" - ARGS="$DISPATCH_MODE" + ARGS="" PAIRS=${DISPATCH_PAIRS:-14} fi - # Defaults: continuations with the 100-transfer fixture (production mode). - CONTINUATIONS=1; TX_COUNT=100 set -f # tokens must not glob-expand against the runner's CWD for tok in $ARGS; do case "$tok" in - cont) CONTINUATIONS=1; TX_COUNT=100 ;; - mono) CONTINUATIONS=0; TX_COUNT=5 ;; - cont[0-9]*) CONTINUATIONS=1; TX_COUNT="${tok#cont}" ;; - mono[0-9]*) CONTINUATIONS=0; TX_COUNT="${tok#mono}" ;; [0-9]*) PAIRS="$tok" ;; *) echo "::warning::ignoring unrecognized token '$tok'" ;; esac done - # TX_COUNT and PAIRS are interpolated into the remote bash -lc below: enforce - # digits-only. Mono is capped at 5tx (monolithic peak heap grows with the - # trace; 20tx needs ~78 GB, over the 48 GB box floor). - if [ "$CONTINUATIONS" = "1" ]; then TX_DEFAULT=100; TX_MAX=100; else TX_DEFAULT=5; TX_MAX=5; fi - case "$TX_COUNT" in - ''|*[!0-9]*) echo "::warning::invalid tx count '$TX_COUNT'; using $TX_DEFAULT"; TX_COUNT=$TX_DEFAULT ;; - esac - if [ "$TX_COUNT" -lt 1 ] || [ "$TX_COUNT" -gt "$TX_MAX" ]; then - echo "::warning::tx count $TX_COUNT out of range [1,$TX_MAX] for this mode; using $TX_DEFAULT" - TX_COUNT=$TX_DEFAULT - fi + # PAIRS is interpolated into the remote bash -lc below: enforce digits-only. case "$PAIRS" in ''|*[!0-9]*) echo "::warning::invalid pair count '$PAIRS'; using 14"; PAIRS=14 ;; esac @@ -132,39 +132,14 @@ jobs: PAIRS=$((PAIRS + 1)) echo "::notice::rounded odd pair count up to $PAIRS so AB/BA orders balance" fi - if [ "$CONTINUATIONS" = "1" ]; then - WORKLOAD="ethrex ${TX_COUNT}tx continuations" - else - WORKLOAD="ethrex ${TX_COUNT}tx monolithic" - fi - # Outputs land before the fail-fast below so the always() result - # comment is fully labeled even when this step exits early. + WORKLOAD="ethrex real block, continuations" { echo "pr_num=$OUT_PR_NUM" echo "head_sha=$OUT_HEAD_SHA" echo "branch=$OUT_BRANCH" echo "pairs=$PAIRS" - echo "continuations=$CONTINUATIONS" - echo "tx_count=$TX_COUNT" echo "workload=$WORKLOAD" } >> "$GITHUB_OUTPUT" - # Fail fast if the PR side predates the pointer_width_64 fix: a >=40tx - # continuation proof exceeds rkyv's old 2 GiB cap and only dies after the - # full dual build (~1 hr of GPU rental). Skip the check when the fetch - # fails: gh api prints HTTP error bodies to stdout, so gate on its exit - # status AND on the payload looking like the manifest (it declares - # rkyv) — never treat an error blob as a missing feature. Purely - # textual; deletable once every open branch postdates the fix. - if [ "$CONTINUATIONS" = "1" ] && [ "$TX_COUNT" -ge 40 ]; then - REF="${OUT_HEAD_SHA:-$OUT_BRANCH}" - if SIDE=$(gh api "repos/$GITHUB_REPOSITORY/contents/prover/Cargo.toml?ref=$REF" \ - -H "Accept: application/vnd.github.raw" 2>/dev/null) \ - && printf '%s' "$SIDE" | grep -q '^rkyv' \ - && ! printf '%s' "$SIDE" | grep -q pointer_width_64; then - echo "::error::PR branch predates the rkyv pointer_width_64 fix — a ${TX_COUNT}tx continuation proof cannot serialize. Rebase onto main, or bench with cont10/mono." - exit 1 - fi - fi echo "Using $PAIRS A/B/B/A pairs on $WORKLOAD" - name: Acknowledge (react + occupancy notice) @@ -182,7 +157,12 @@ jobs: // Post the "started" notice under the SAME marker the result step uses, so the // result updates this comment in place (and re-runs reuse it rather than stacking). const marker = 'GPU Benchmark (ABBA)'; - const body = `## GPU Benchmark (ABBA) — running…\n\n⏳ Renting an RTX 5090 on Vast.ai and running ${process.env.PAIRS} interleaved pairs (PR vs main) of ${process.env.WORKLOAD} on the CUDA prover path. This takes ~2.5 hr at the default workload; the result will replace this comment.`; + // Reference: 4 pairs measured ~20 min end-to-end (rental + dual build + + // 8 proves). Per-prove wall varies with the rented host's CPU (the prover + // is partly host-CPU-bound), so scale expectations from pair count, not + // from a fixed per-run figure. + const mins = 12 + Number(process.env.PAIRS) * 2; + const body = `## GPU Benchmark (ABBA) — running…\n\n⏳ Renting an RTX 5090 on Vast.ai and running ${process.env.PAIRS} interleaved pairs (PR vs main) of ${process.env.WORKLOAD} on the CUDA prover path. Rough ETA ~${mins} min. The result will replace this comment.`; const comments = await github.paginate(github.rest.issues.listComments, { owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, per_page: 100, @@ -234,10 +214,11 @@ jobs: # because vast can't numerically compare the driver_version string server-side. MIN_DRIVER: "580" run: | - # cpu_ram filter is in GB. Floor 48 GB: continuation proves are flat-memory - # (~10 GB) and the legacy 5tx monolithic prove also fits — far below the old - # 20-transfer prove (~78 GB heap) that set the previous 96 GB floor. 48 GB - # widens the dedicated pool (~15 vs ~11 offers). + # cpu_ram filter is in GB. Floor 48 GB: the real block at epoch 2^22 peaks at + # ~36 GB host RSS on the CUDA path (measured, main vintage) — ~25% headroom. + # Continuation peak is set by the epoch size, not the block, so bigger blocks + # don't move it; raising the epoch would (see the calibration tables in + # tooling/ethrex-block-converter/README.md). # gpu_frac=1 requires a WHOLE-MACHINE offer (you rent every GPU on the host), so # Vast places no other tenant on the box: CPU cores, RAM/memory bandwidth, PCIe, # and NVMe are fully dedicated. Without it the "most expensive" sort below lands on @@ -364,6 +345,7 @@ jobs: KEY: ${{ steps.sshkey.outputs.key_path }} run: | SSH="ssh -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10 -o BatchMode=yes -i $KEY -p $PORT root@$HOST" + echo "Waiting for the template onstart script to finish (Rust + LLVM + sysroot + clone)..." # The bootstrap's final stdout line is "=== done ===". Vast captures onstart # output to /var/log/onstart.log; fall back to checking the artifacts it leaves. @@ -395,8 +377,6 @@ jobs: HEAD_SHA: ${{ steps.config.outputs.head_sha }} BRANCH: ${{ steps.config.outputs.branch }} PAIRS: ${{ steps.config.outputs.pairs }} - CONTINUATIONS: ${{ steps.config.outputs.continuations }} - TX_COUNT: ${{ steps.config.outputs.tx_count }} run: | SSH="ssh -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10 -o BatchMode=yes -i $KEY -p $PORT root@$HOST" @@ -421,7 +401,9 @@ jobs: # explicit and robust to the template default changing.) The harness still builds the # cli at REF_A (the PR) and origin/main in isolated worktrees, runs PAIRS interleaved # A/B/B/A proves, and prints the paired-t CI + Wilcoxon verdict. BENCH_FEATURES routes - # the build through the CUDA prover path; CONTINUATIONS/TX_COUNT pick the workload. + # the build through the CUDA prover path. bench_abba.sh is shared with the CPU ABBA + # flow and has its own defaults, so its knobs are pinned explicitly here: the real + # block, continuations, the calibrated epoch. # The harness runs from main, so workflow/script changes take effect post-merge. # REBUILD=1: each Vast box is fresh, GPU-specific hardware — always rebuild both # binaries (cubin is compiled for the detected arch); never trust a cached binary. @@ -437,7 +419,7 @@ jobs: git fetch --force origin main; $FETCH; \ git checkout -f origin/main; \ REBUILD=1 CUDARC_PIN=cuda-12080 SYSROOT_DIR=/opt/lambda-vm-sysroot BENCH_FEATURES='$BENCH_FEATURES' \ - CONTINUATIONS=$CONTINUATIONS TX_COUNT=$TX_COUNT \ + WORKLOAD=real CONTINUATIONS=1 EPOCH_SIZE_LOG2=$GPU_REAL_EPOCH_LOG2 \ scripts/bench_abba.sh $REF_A origin/main $PAIRS" # pipefail so a failed remote bench (e.g. a prove that dies) propagates through the diff --git a/.github/workflows/benchmark-pr.yml b/.github/workflows/benchmark-pr.yml index fd74c7b3f..f9c83ea3b 100644 --- a/.github/workflows/benchmark-pr.yml +++ b/.github/workflows/benchmark-pr.yml @@ -12,6 +12,13 @@ on: - 'executor/**' - 'bin/cli/**' - 'tooling/ethrex-fixtures/**' + # A baseline is only valid for the workload it measured, and these two + # define the real-block workload: the converter decides the fixture's + # bytes, the Makefile decides which block it is. Without them a repointed + # block would leave main's baseline stale until some prover file happened + # to change — and the comparison guard would suppress the table until then. + - 'tooling/ethrex-block-converter/**' + - 'Makefile' # Uncomment to auto-run on PRs: # pull_request: # branches: [main] @@ -32,18 +39,80 @@ concurrency: cancel-in-progress: false env: - # Headline program: the ethrex guest ELF proven against a 20-transfer block - # (distinct sender -> distinct recipient per tx). One ELF; the workload is the - # private input (rkyv ProgramInput), generated in-job and gitignored (see the - # "Generate ethrex bench fixtures" step). + # `/bench` proves ONE workload: a real Ethereum block. The synthetic N-transfer + # screen that used to run alongside it was removed deliberately — if you are about + # to add it back, these are the two reasons it went: + # + # 1. Its only unique coverage was the MONOLITHIC prove path, which is vestigial + # (reportedly slower than a 1-epoch continuation). Coverage of a path we intend + # to delete is not a reason to spend runner time. + # 2. Its crypto mix is one no real block has: 8.83 ECSM per Mcycle against a real + # block's ~1.3-1.6. Screening against it tunes the prover for a worst case that + # cannot occur. + # + # The synthetic fixtures themselves are NOT gone — /bench-growth still sweeps them to + # get a heap-vs-block-size slope, which needs a family of blocks and so cannot come + # from one real one. Only the headline screen was dropped. + # + # Cycle counts below are for the ELF THIS JOB BUILDS (see "Build ethrex guest ELF"), + # as of main @ 9ccdaf2 with clang 21. They move with guest optimisation (#861 gave the + # guest thin LTO) and ~2% with the clang major on PATH, so a count quoted against a + # different ELF reads as a regression — always pin the ELF when repeating one. ELF: executor/program_artifacts/rust/ethrex.elf - INPUT: executor/tests/ethrex_bench_20.bin - BENCH_RUNS_PR: 3 - # Cheap-tier screen: catches regressions down to ~1.5% on its own and leaves - # smaller/ambiguous deltas to the manual drift-free ABBA tiebreaker. Pushing either - # side past 5 buys little here (the cached comparison can't beat the ~1% drift wall), - # so the per-PR run count is also capped at 5 (clamp below). - BENCH_RUNS_BASELINE: 5 + # The workload: a real Ethereum block. WHICH block lives in + # the Makefile and nowhere else — nothing in this file names one, so a repoint + # moves this job without editing it. At the current default that is 50.78M cycles, + # 10,478 keccak calls and 116 ecsm calls: ~5.8x the work of the synthetic block with a + # ~18x different keccak:ecrecover mix. That is the whole point — a prover change + # can move the synthetic number and the real one in opposite directions. + # + # The path is resolved from the Makefile (`make -s print-real-block-fixture`) into + # REAL_INPUT at run time. The ~1 MB .bin is gitignored and FETCHED by URL + sha256 + # (see "Fetch ethrex real-block fixture"); while that URL is unset the whole + # section degrades to a warning rather than failing the job. + # + # Continuations are mandatory here, not a preference: a monolithic prove costs + # ~4.9 GB of peak heap per million cycles on this workload family (from the + # measured growth fit, 10,728 MB + 2,007 MB/transfer at R^2 = 0.998), so the + # current default would need ~240 GB and a heavier candidate far more. + # `--continuations` makes peak heap a function of the epoch size instead of the + # trace length. Costs move with the block; the per-candidate table is in + # tooling/ethrex-block-converter/README.md. + # + # Budget ~1.2 GB of disk for the bundle each run — hence the `rm -f` after every + # prove. A heavier block pushes it past 2 GiB, which needs rkyv `pointer_width_64`; + # a PR branch predating that fix fails at write time rather than mismeasuring. + # + # Epoch 2^22, from the CPU sweep on 2026-07-31 (124 GiB / 32-core box, real block, + # branch vintage; full table in tooling/ethrex-block-converter/README.md): + # + # 2^21 464.26 s 18.43 GiB RSS 26 epochs 1.72 GB proof + # 2^22 397.88 s 32.21 GiB RSS 13 epochs 1.15 GB proof + # 2^23 356.47 s 60.01 GiB RSS 7 epochs 0.90 GB proof + # + # This runner's floor is >=64 GiB, so 2^22's 32.2 GiB fits with ~50% headroom while + # 2^23's 60 GiB does not fit safely — memory, not speed, is what picks 2^22 here. + # Moving off 2^21 is worth ~14% wall on the calibration box. That box is NOT this + # runner (it measured 8.6 s/Mcycle against this runner's 5.3-5.6), so treat the + # ratio as transferable and the absolute seconds as not: the first /bench run at + # this epoch establishes this runner's actual time. + # + # Deliberately NOT the CLI's DEFAULT_CONTINUATION_EPOCH_SIZE_LOG2, which stays 20 so + # a laptop can still prove; and not the GPU path's 2^22, which happens to coincide + # but is chosen by VRAM rather than host RAM (see benchmark-gpu.yml). + REAL_BLOCK_EPOCH_LOG2: "22" + # Sampled like the synthetic screen, 3 runs, and reported the same way (median + + # spread). Fewer than the synthetic's 5 because each run is minutes rather than + # seconds; enough that one slow run shows up as spread instead of moving the median. + # + # THIS IS THE DIAL. The runner's per-run real-block time is still unmeasured — the + # calibration ran on a different box (see REAL_BLOCK_EPOCH_LOG2 above). If the first + # runs land above ~6 min each, /bench becomes a ~25 min occupancy of a runner every + # other bench queues behind, and this count is what to turn down (2, or 1) before + # reaching for anything else. + # `/bench N` overrides this, clamped to [1,5]. Past 5 the cached comparison can't + # beat the ~1% session-drift wall anyway — that is what /bench-abba is for. + BENCH_RUNS_REAL: 3 # Memory-scaling sweep: same ELF, different N-transfer inputs. GROWTH_PROGRAMS # are the generated (gitignored) fixture basenames in executor/tests/; GROWTH_STEPS # the matching transfer counts (x-axis; slope is MB per transfer). @@ -53,7 +122,11 @@ env: jobs: benchmark: runs-on: [self-hosted, bench] - # Skip unless: push to main, workflow_dispatch, or "/bench" comment on a PR + # Skip unless: push to main, workflow_dispatch, or "/bench" comment on a PR. + # "/bench-growth" is handled by THIS job (it is prefixed by "/bench" and + # deliberately absent from the exclusion list below); it only switches whether the + # growth sweep runs, in the "Determine run count" step. The real block needs no + # token — it runs on every invocation. if: >- github.event_name == 'push' || github.event_name == 'workflow_dispatch' || @@ -135,20 +208,57 @@ jobs: echo "run_growth=false" >> "$GITHUB_OUTPUT" fi + # The real block runs on EVERY invocation — plain `/bench`, push to main, and + # workflow_dispatch alike. It is the number that means something, so it should + # not need a second command to ask for; the synthetic screen rides along + # unchanged as the cheap, high-sensitivity half. + # + # The cost is real and lands on a shared runner: ~4.5-4.8 min per run x + # BENCH_RUNS_REAL, so every /bench and every push to main now occupies the + # bench server for roughly 15-20 min, and /bench-abba and /bench-verify queue + # behind it. That trade was made deliberately — see BENCH_RUNS_REAL above for + # the dial if it proves too expensive. + RUN_REAL=true + + # The fixture is FETCHED from a pinned URL + sha256, not built (see the + # Makefile). The guard covers the window after a repoint but before the new + # artifact is uploaded: degrade to the synthetic sections with a warning + # rather than failing, so a push to main cannot go red over an upload nobody + # in CI can perform. + if [ "$RUN_REAL" = "true" ] && [ -z "$(make -s print-real-block-fixture-url)" ]; then + echo "::warning::Real-block benchmark skipped: ETHREX_REAL_BLOCK_FIXTURE_URL is unset in the Makefile." + RUN_REAL=false + fi + echo "run_real=$RUN_REAL" >> "$GITHUB_OUTPUT" + + # Resolve the real-block fixture path from its single source of truth (the + # Makefile) and pin it in the job env. Captured HERE, before the baseline + # step's `git checkout origin/main`, so the PR and main sides agree on the + # workload even when main's Makefile names a different block. + echo "REAL_INPUT=$(make -s print-real-block-fixture)" >> "$GITHUB_ENV" + + # Every metrics writer appends; the step that used to create this file was the + # synthetic screen, which is gone. Truncate here (this step always runs) so a + # previous run's values can't survive on the persistent self-hosted runner. + : > /tmp/metrics.txt + + # `/bench N` sets the real-block sample count. Both sides use the same count + # (push-to-main publishes the baseline), so there is no PR-vs-baseline split + # any more — an asymmetric count would put the two sides' noise on different + # footings, which is what sampling is for. + RUNS=$BENCH_RUNS_REAL if [ "$EVENT_NAME" = "issue_comment" ]; then CUSTOM_N=$(echo "$COMMENT_BODY" | sed -n 's|^/bench[[:space:]]*\([0-9]\+\).*|\1|p') - RUNS=${CUSTOM_N:-$BENCH_RUNS_PR} - elif [ "$EVENT_NAME" = "push" ] || [ "$EVENT_NAME" = "workflow_dispatch" ]; then - RUNS=$BENCH_RUNS_BASELINE - else - RUNS=$BENCH_RUNS_PR + RUNS=${CUSTOM_N:-$BENCH_RUNS_REAL} fi # Clamp to 1-5. Beyond 5 the single-session cached comparison barely improves # (it can't beat the ~1% drift wall); use the ABBA tiebreaker for finer deltas. + # At ~4-5 min a run this is also the difference between a 15 min and a 25 min + # occupancy of the one bench runner. if [ "$RUNS" -lt 1 ] 2>/dev/null || [ "$RUNS" -gt 5 ] 2>/dev/null; then - echo "::warning::Run count $RUNS out of range [1,5], defaulting to $BENCH_RUNS_PR" - RUNS=$BENCH_RUNS_PR + echo "::warning::Run count $RUNS out of range [1,5], defaulting to $BENCH_RUNS_REAL" + RUNS=$BENCH_RUNS_REAL fi echo "runs=$RUNS" >> "$GITHUB_OUTPUT" @@ -169,74 +279,19 @@ jobs: echo "Using $RUNS iterations, TABLE_PARALLELISM=default" fi - - name: Benchmark PR - id: pr - env: - RUNS: ${{ steps.config.outputs.runs }} - TABLE_PARALLELISM: ${{ steps.config.outputs.table_parallelism }} + - name: Fetch ethrex real-block fixture + if: steps.config.outputs.run_real == 'true' run: | - if [ -n "$TABLE_PARALLELISM" ]; then - export TABLE_PARALLELISM - echo "TABLE_PARALLELISM=$TABLE_PARALLELISM" - fi - TIMES="" - HEAPS="" - for i in $(seq 1 $RUNS); do - echo "--- Run $i/$RUNS ---" - ./target/release/cli prove "$ELF" --private-input "$INPUT" -o /tmp/proof.bin --time \ - | tee /tmp/cli_output_$i.txt - rm -f /tmp/proof.bin - - T=$(grep -o 'Proving time: [0-9.]*' /tmp/cli_output_$i.txt | awk '{print $3}') - H=$(grep -o 'Peak heap: [0-9]*' /tmp/cli_output_$i.txt | awk '{print $3}') - - if [ -z "$T" ] || [ -z "$H" ]; then - echo "::error::Failed to parse metrics from run $i" - cat /tmp/cli_output_$i.txt - exit 1 - fi - - TIMES="$TIMES $T" - HEAPS="$HEAPS $H" - done - - # Median position (works for odd N; for even N picks lower-middle) - MEDIAN_POS=$(( (RUNS + 1) / 2 )) - TIME_MEDIAN=$(echo $TIMES | tr ' ' '\n' | sort -n | awk "NR==$MEDIAN_POS") - HEAP_MEDIAN=$(echo $HEAPS | tr ' ' '\n' | sort -n | awk "NR==$MEDIAN_POS") - - if [ -z "$HEAP_MEDIAN" ] || [ -z "$TIME_MEDIAN" ]; then - echo "::error::Failed to compute median metrics" - exit 1 - fi - - # Spread: (max - min) / median * 100 - TIME_MIN=$(echo $TIMES | tr ' ' '\n' | sort -n | head -1) - TIME_MAX=$(echo $TIMES | tr ' ' '\n' | sort -n | tail -1) - TIME_SPREAD=$(awk "BEGIN { if ($TIME_MEDIAN > 0) printf \"%.1f\", (($TIME_MAX - $TIME_MIN) / $TIME_MEDIAN) * 100; else print \"0.0\" }") - - HEAP_MIN=$(echo $HEAPS | tr ' ' '\n' | sort -n | head -1) - HEAP_MAX=$(echo $HEAPS | tr ' ' '\n' | sort -n | tail -1) - HEAP_SPREAD=$(awk "BEGIN { if ($HEAP_MEDIAN > 0) printf \"%.1f\", (($HEAP_MAX - $HEAP_MIN) / $HEAP_MEDIAN) * 100; else print \"0.0\" }") - - ALL_TIMES=$(echo $TIMES | tr ' ' '\n' | paste -sd '/' -) - ALL_HEAPS=$(echo $HEAPS | tr ' ' '\n' | paste -sd '/' -) - - echo "peak_mb=$HEAP_MEDIAN" >> "$GITHUB_OUTPUT" - echo "time_s=$TIME_MEDIAN" >> "$GITHUB_OUTPUT" - echo "time_spread=$TIME_SPREAD" >> "$GITHUB_OUTPUT" - echo "heap_spread=$HEAP_SPREAD" >> "$GITHUB_OUTPUT" - echo "all_times=$ALL_TIMES" >> "$GITHUB_OUTPUT" - echo "all_heaps=$ALL_HEAPS" >> "$GITHUB_OUTPUT" - echo "runs=$RUNS" >> "$GITHUB_OUTPUT" - - echo "peak_mb=$HEAP_MEDIAN" > /tmp/metrics.txt - echo "time_s=$TIME_MEDIAN" >> /tmp/metrics.txt - echo "time_spread=$TIME_SPREAD" >> /tmp/metrics.txt - echo "heap_spread=$HEAP_SPREAD" >> /tmp/metrics.txt - echo "all_times=$ALL_TIMES" >> /tmp/metrics.txt - echo "all_heaps=$ALL_HEAPS" >> /tmp/metrics.txt - echo "runs=$RUNS" >> /tmp/metrics.txt + # ~1 MB, gitignored, and never in the checkout: fetch it rather than failing + # on a missing file, the same way the synthetic fixtures are generated above. + # This is a URL + sha256 download, not a build — no converter, no ethrex host + # dependency tree, no ethrex-replay cache. The step is already gated on the + # URL being set (see "Determine run count"). + # + # Untracked, so like the ELF it survives `git checkout origin/main` and both + # sides prove the identical block. + make ethrex-real-block-fixture + ls -l "$REAL_INPUT" - name: Memory growth (PR) id: pr-growth @@ -312,6 +367,70 @@ jobs: echo "growth_slope_mb=$SLOPE" >> /tmp/metrics.txt echo "growth_r2=$R2" >> /tmp/metrics.txt + - name: Real block (PR) + id: pr-real + if: steps.config.outputs.run_real == 'true' + env: + RUNS: ${{ steps.config.outputs.runs }} + TABLE_PARALLELISM: ${{ steps.config.outputs.table_parallelism }} + run: | + if [ -n "$TABLE_PARALLELISM" ]; then + export TABLE_PARALLELISM + echo "TABLE_PARALLELISM=$TABLE_PARALLELISM" + fi + TIMES="" + HEAPS="" + EPOCHS="" + for i in $(seq 1 "$RUNS"); do + echo "--- Real block run $i/$RUNS (continuations, epoch 2^$REAL_BLOCK_EPOCH_LOG2) ---" + ./target/release/cli prove "$ELF" --private-input "$REAL_INPUT" \ + --continuations --epoch-size-log2 "$REAL_BLOCK_EPOCH_LOG2" \ + -o /tmp/real_proof.bin --time | tee /tmp/real_output_$i.txt + rm -f /tmp/real_proof.bin + + T=$(grep -o 'Proving time: [0-9.]*' /tmp/real_output_$i.txt | awk '{print $3}') + # Peak heap is optional here, unlike the monolithic step: the continuation + # prove path only learned to report it alongside this benchmark, so a + # baseline built from an older main prints no such line. Missing heap + # degrades one table cell; a missing time makes the run meaningless. + H=$(grep -o 'Peak heap: [0-9]*' /tmp/real_output_$i.txt | awk '{print $3}') + E=$(grep -o 'Epochs: [0-9]*' /tmp/real_output_$i.txt | awk '{print $2}') + + if [ -z "$T" ]; then + echo "::error::Failed to parse real-block proving time from run $i" + cat /tmp/real_output_$i.txt + exit 1 + fi + if [ -z "$H" ]; then + echo "::warning::No 'Peak heap' line on the real-block run (CLI predates continuation heap reporting)" + fi + + TIMES="$TIMES $T" + if [ -n "$H" ]; then HEAPS="$HEAPS $H"; fi + EPOCHS="$E" + done + + # Same summary as the synthetic screen: median for the headline, spread so a + # single slow run is visible rather than silently shifting the verdict, and + # every raw value so a reader can judge for themselves. + MEDIAN_POS=$(( (RUNS + 1) / 2 )) + TIME_MEDIAN=$(echo $TIMES | tr ' ' '\n' | sort -n | awk "NR==$MEDIAN_POS") + HEAP_MEDIAN=$(echo $HEAPS | tr ' ' '\n' | sort -n | awk "NR==$MEDIAN_POS") + TIME_MIN=$(echo $TIMES | tr ' ' '\n' | sort -n | head -1) + TIME_MAX=$(echo $TIMES | tr ' ' '\n' | sort -n | tail -1) + TIME_SPREAD=$(awk "BEGIN { if ($TIME_MEDIAN > 0) printf \"%.1f\", (($TIME_MAX - $TIME_MIN) / $TIME_MEDIAN) * 100; else print \"0.0\" }") + ALL_TIMES=$(echo $TIMES | tr ' ' '\n' | paste -sd '/' -) + + { + echo "real_time_s=$TIME_MEDIAN" + echo "real_peak_mb=$HEAP_MEDIAN" + echo "real_epochs=$EPOCHS" + echo "real_runs=$RUNS" + echo "real_time_spread=$TIME_SPREAD" + echo "real_all_times=$ALL_TIMES" + echo "real_input=$(basename "$REAL_INPUT")" + } | tee -a /tmp/metrics.txt >> "$GITHUB_OUTPUT" + - name: Upload metrics artifact uses: actions/upload-artifact@v4 with: @@ -338,17 +457,15 @@ jobs: BASELINE_FILE=$(ls -t baseline/*/metrics.txt 2>/dev/null | head -1) if [ -n "$BASELINE_FILE" ]; then echo "found=true" >> "$GITHUB_OUTPUT" - echo "peak_mb=$(grep 'peak_mb=' "$BASELINE_FILE" | cut -d= -f2)" >> "$GITHUB_OUTPUT" - echo "time_s=$(grep 'time_s=' "$BASELINE_FILE" | cut -d= -f2)" >> "$GITHUB_OUTPUT" - echo "time_spread=$(grep 'time_spread=' "$BASELINE_FILE" | cut -d= -f2)" >> "$GITHUB_OUTPUT" - echo "heap_spread=$(grep 'heap_spread=' "$BASELINE_FILE" | cut -d= -f2)" >> "$GITHUB_OUTPUT" - echo "all_times=$(grep 'all_times=' "$BASELINE_FILE" | cut -d= -f2)" >> "$GITHUB_OUTPUT" - echo "all_heaps=$(grep 'all_heaps=' "$BASELINE_FILE" | cut -d= -f2)" >> "$GITHUB_OUTPUT" - echo "runs=$(grep 'runs=' "$BASELINE_FILE" | cut -d= -f2)" >> "$GITHUB_OUTPUT" - echo "growth_heaps=$(grep 'growth_heaps=' "$BASELINE_FILE" | cut -d= -f2)" >> "$GITHUB_OUTPUT" - echo "growth_times=$(grep 'growth_times=' "$BASELINE_FILE" | cut -d= -f2)" >> "$GITHUB_OUTPUT" - echo "growth_slope_mb=$(grep 'growth_slope_mb=' "$BASELINE_FILE" | cut -d= -f2)" >> "$GITHUB_OUTPUT" - echo "growth_r2=$(grep 'growth_r2=' "$BASELINE_FILE" | cut -d= -f2)" >> "$GITHUB_OUTPUT" + # Anchored (`^key=`): `real_time_s` contains `time_s`, so an unanchored + # grep could match two lines and write a multi-line step output. + get() { grep "^$1=" "$BASELINE_FILE" | head -1 | cut -d= -f2; } + for key in growth_heaps growth_times growth_slope_mb growth_r2 \ + real_time_s real_peak_mb real_input; do + # A baseline predating the real block simply has no real_* keys; empty + # values hide the table rather than producing a bogus comparison. + echo "$key=$(get "$key")" >> "$GITHUB_OUTPUT" + done exit 0 fi fi @@ -363,6 +480,7 @@ jobs: GH_TOKEN: ${{ github.token }} RUNS: ${{ steps.config.outputs.runs }} RUN_GROWTH: ${{ steps.config.outputs.run_growth }} + RUN_REAL: ${{ steps.config.outputs.run_real }} TABLE_PARALLELISM: ${{ steps.config.outputs.table_parallelism }} run: | if [ -n "$TABLE_PARALLELISM" ]; then @@ -380,56 +498,6 @@ jobs: cargo build --release -p cli --features jemalloc-stats - # --- Primary benchmark (ethrex 20 transfers) --- - TIMES="" - HEAPS="" - for i in $(seq 1 $RUNS); do - echo "--- Baseline run $i/$RUNS ---" - ./target/release/cli prove "$ELF" --private-input "$INPUT" -o /tmp/proof.bin --time \ - | tee /tmp/baseline_output_$i.txt - rm -f /tmp/proof.bin - - T=$(grep -o 'Proving time: [0-9.]*' /tmp/baseline_output_$i.txt | awk '{print $3}') - H=$(grep -o 'Peak heap: [0-9]*' /tmp/baseline_output_$i.txt | awk '{print $3}') - - if [ -z "$T" ] || [ -z "$H" ]; then - echo "::error::Failed to parse baseline metrics from run $i" - cat /tmp/baseline_output_$i.txt - exit 1 - fi - - TIMES="$TIMES $T" - HEAPS="$HEAPS $H" - done - - MEDIAN_POS=$(( (RUNS + 1) / 2 )) - TIME_MEDIAN=$(echo $TIMES | tr ' ' '\n' | sort -n | awk "NR==$MEDIAN_POS") - HEAP_MEDIAN=$(echo $HEAPS | tr ' ' '\n' | sort -n | awk "NR==$MEDIAN_POS") - - if [ -z "$HEAP_MEDIAN" ] || [ -z "$TIME_MEDIAN" ]; then - echo "::error::Failed to compute baseline median metrics" - exit 1 - fi - - TIME_MIN=$(echo $TIMES | tr ' ' '\n' | sort -n | head -1) - TIME_MAX=$(echo $TIMES | tr ' ' '\n' | sort -n | tail -1) - TIME_SPREAD=$(awk "BEGIN { if ($TIME_MEDIAN > 0) printf \"%.1f\", (($TIME_MAX - $TIME_MIN) / $TIME_MEDIAN) * 100; else print \"0.0\" }") - - HEAP_MIN=$(echo $HEAPS | tr ' ' '\n' | sort -n | head -1) - HEAP_MAX=$(echo $HEAPS | tr ' ' '\n' | sort -n | tail -1) - HEAP_SPREAD=$(awk "BEGIN { if ($HEAP_MEDIAN > 0) printf \"%.1f\", (($HEAP_MAX - $HEAP_MIN) / $HEAP_MEDIAN) * 100; else print \"0.0\" }") - - ALL_TIMES=$(echo $TIMES | tr ' ' '\n' | paste -sd '/' -) - ALL_HEAPS=$(echo $HEAPS | tr ' ' '\n' | paste -sd '/' -) - - echo "peak_mb=$HEAP_MEDIAN" >> "$GITHUB_OUTPUT" - echo "time_s=$TIME_MEDIAN" >> "$GITHUB_OUTPUT" - echo "time_spread=$TIME_SPREAD" >> "$GITHUB_OUTPUT" - echo "heap_spread=$HEAP_SPREAD" >> "$GITHUB_OUTPUT" - echo "all_times=$ALL_TIMES" >> "$GITHUB_OUTPUT" - echo "all_heaps=$ALL_HEAPS" >> "$GITHUB_OUTPUT" - echo "runs=$RUNS" >> "$GITHUB_OUTPUT" - # --- Growth benchmarks (default parallelism, 1 sample each) --- # Only run if /bench-growth, push, or workflow_dispatch if [ "$RUN_GROWTH" != "true" ]; then @@ -495,6 +563,41 @@ jobs: echo "growth_r2=$R2" >> "$GITHUB_OUTPUT" fi # end run_growth check + # --- Real block (continuations, 1 sample) --- + # Only reached when no cached baseline exists, which is the expensive case: + # push-to-main normally publishes the real-block numbers, so a /bench comment + # pays for the PR side alone. $REAL_INPUT was resolved before this + # checkout and the fixture is untracked, so main proves the identical block. + if [ "$RUN_REAL" != "true" ]; then + echo "Skipping real-block baseline (fixture URL unset)" + else + # Sampled to the same count as the PR side. A 3-vs-1 comparison would put + # the two sides' noise on different footings, which is precisely the error + # sampling exists to avoid. + RTIMES=""; RHEAPS="" + for i in $(seq 1 "$RUNS"); do + echo "--- Baseline real block run $i/$RUNS (epoch 2^$REAL_BLOCK_EPOCH_LOG2) ---" + ./target/release/cli prove "$ELF" --private-input "$REAL_INPUT" \ + --continuations --epoch-size-log2 "$REAL_BLOCK_EPOCH_LOG2" \ + -o /tmp/real_proof.bin --time | tee /tmp/baseline_real_$i.txt + rm -f /tmp/real_proof.bin + T=$(grep -o 'Proving time: [0-9.]*' /tmp/baseline_real_$i.txt | awk '{print $3}') + # Optional, as on the PR side: a main that predates continuation heap + # reporting prints no such line, and that must not fail the comparison. + H=$(grep -o 'Peak heap: [0-9]*' /tmp/baseline_real_$i.txt | awk '{print $3}') + if [ -z "$T" ]; then + echo "::error::Failed to parse baseline real-block proving time from run $i" + cat /tmp/baseline_real_$i.txt + exit 1 + fi + RTIMES="$RTIMES $T" + if [ -n "$H" ]; then RHEAPS="$RHEAPS $H"; fi + done + RMED_POS=$(( (RUNS + 1) / 2 )) + echo "real_time_s=$(echo $RTIMES | tr ' ' '\n' | sort -n | awk "NR==$RMED_POS")" >> "$GITHUB_OUTPUT" + echo "real_peak_mb=$(echo $RHEAPS | tr ' ' '\n' | sort -n | awk "NR==$RMED_POS")" >> "$GITHUB_OUTPUT" + fi + # Restore PR checkout git checkout "$PR_SHA" @@ -506,110 +609,99 @@ jobs: env: # Baseline artifact outputs BASELINE_FOUND: ${{ steps.baseline-artifact.outputs.found }} - BA_PEAK_MB: ${{ steps.baseline-artifact.outputs.peak_mb }} - BA_TIME_S: ${{ steps.baseline-artifact.outputs.time_s }} - BA_TIME_SPREAD: ${{ steps.baseline-artifact.outputs.time_spread }} - BA_HEAP_SPREAD: ${{ steps.baseline-artifact.outputs.heap_spread }} - BA_ALL_TIMES: ${{ steps.baseline-artifact.outputs.all_times }} - BA_ALL_HEAPS: ${{ steps.baseline-artifact.outputs.all_heaps }} - BA_RUNS: ${{ steps.baseline-artifact.outputs.runs }} BA_GROWTH_HEAPS: ${{ steps.baseline-artifact.outputs.growth_heaps }} BA_GROWTH_TIMES: ${{ steps.baseline-artifact.outputs.growth_times }} BA_GROWTH_SLOPE: ${{ steps.baseline-artifact.outputs.growth_slope_mb }} BA_GROWTH_R2: ${{ steps.baseline-artifact.outputs.growth_r2 }} + BA_REAL_TIME: ${{ steps.baseline-artifact.outputs.real_time_s }} + BA_REAL_PEAK: ${{ steps.baseline-artifact.outputs.real_peak_mb }} + BA_REAL_INPUT: ${{ steps.baseline-artifact.outputs.real_input }} # Baseline run outputs - BR_PEAK_MB: ${{ steps.baseline-run.outputs.peak_mb }} - BR_TIME_S: ${{ steps.baseline-run.outputs.time_s }} - BR_TIME_SPREAD: ${{ steps.baseline-run.outputs.time_spread }} - BR_HEAP_SPREAD: ${{ steps.baseline-run.outputs.heap_spread }} - BR_ALL_TIMES: ${{ steps.baseline-run.outputs.all_times }} - BR_ALL_HEAPS: ${{ steps.baseline-run.outputs.all_heaps }} - BR_RUNS: ${{ steps.baseline-run.outputs.runs }} BR_GROWTH_HEAPS: ${{ steps.baseline-run.outputs.growth_heaps }} BR_GROWTH_TIMES: ${{ steps.baseline-run.outputs.growth_times }} BR_GROWTH_SLOPE: ${{ steps.baseline-run.outputs.growth_slope_mb }} BR_GROWTH_R2: ${{ steps.baseline-run.outputs.growth_r2 }} - # PR outputs - CURRENT_PEAK: ${{ steps.pr.outputs.peak_mb }} - CURRENT_TIME: ${{ steps.pr.outputs.time_s }} - PR_TIME_SPREAD: ${{ steps.pr.outputs.time_spread }} - PR_HEAP_SPREAD: ${{ steps.pr.outputs.heap_spread }} - PR_ALL_TIMES: ${{ steps.pr.outputs.all_times }} - PR_ALL_HEAPS: ${{ steps.pr.outputs.all_heaps }} - PR_RUNS: ${{ steps.pr.outputs.runs }} + BR_REAL_TIME: ${{ steps.baseline-run.outputs.real_time_s }} + BR_REAL_PEAK: ${{ steps.baseline-run.outputs.real_peak_mb }} # PR growth outputs PR_GROWTH_HEAPS: ${{ steps.pr-growth.outputs.growth_heaps }} PR_GROWTH_TIMES: ${{ steps.pr-growth.outputs.growth_times }} PR_GROWTH_SLOPE: ${{ steps.pr-growth.outputs.growth_slope_mb }} PR_GROWTH_R2: ${{ steps.pr-growth.outputs.growth_r2 }} + # PR real-block outputs + PR_REAL_TIME: ${{ steps.pr-real.outputs.real_time_s }} + PR_REAL_PEAK: ${{ steps.pr-real.outputs.real_peak_mb }} + PR_REAL_EPOCHS: ${{ steps.pr-real.outputs.real_epochs }} + PR_REAL_INPUT: ${{ steps.pr-real.outputs.real_input }} + PR_REAL_RUNS: ${{ steps.pr-real.outputs.real_runs }} + PR_REAL_TIME_SPREAD: ${{ steps.pr-real.outputs.real_time_spread }} + PR_REAL_ALL_TIMES: ${{ steps.pr-real.outputs.real_all_times }} run: | # Pick baseline source if [ "$BASELINE_FOUND" = "true" ]; then - BASELINE_PEAK="$BA_PEAK_MB" - BASELINE_TIME="$BA_TIME_S" BASELINE_SRC="cached" - BASELINE_TIME_SPREAD="$BA_TIME_SPREAD" - BASELINE_HEAP_SPREAD="$BA_HEAP_SPREAD" - BASELINE_ALL_TIMES="$BA_ALL_TIMES" - BASELINE_ALL_HEAPS="$BA_ALL_HEAPS" - BASELINE_RUNS="$BA_RUNS" BASELINE_GROWTH_HEAPS="$BA_GROWTH_HEAPS" BASELINE_GROWTH_TIMES="$BA_GROWTH_TIMES" BASELINE_GROWTH_SLOPE="$BA_GROWTH_SLOPE" BASELINE_GROWTH_R2="$BA_GROWTH_R2" + BASELINE_REAL_TIME="$BA_REAL_TIME" + BASELINE_REAL_PEAK="$BA_REAL_PEAK" + BASELINE_REAL_INPUT="$BA_REAL_INPUT" else - BASELINE_PEAK="$BR_PEAK_MB" - BASELINE_TIME="$BR_TIME_S" BASELINE_SRC="built from main" - BASELINE_TIME_SPREAD="$BR_TIME_SPREAD" - BASELINE_HEAP_SPREAD="$BR_HEAP_SPREAD" - BASELINE_ALL_TIMES="$BR_ALL_TIMES" - BASELINE_ALL_HEAPS="$BR_ALL_HEAPS" - BASELINE_RUNS="$BR_RUNS" BASELINE_GROWTH_HEAPS="$BR_GROWTH_HEAPS" BASELINE_GROWTH_TIMES="$BR_GROWTH_TIMES" BASELINE_GROWTH_SLOPE="$BR_GROWTH_SLOPE" BASELINE_GROWTH_R2="$BR_GROWTH_R2" + BASELINE_REAL_TIME="$BR_REAL_TIME" + BASELINE_REAL_PEAK="$BR_REAL_PEAK" + # Freshly proven on this runner from $REAL_INPUT, so by construction the + # same block the PR side used; the cached path carries its own label. + BASELINE_REAL_INPUT="$PR_REAL_INPUT" fi - if [ -z "$BASELINE_PEAK" ] || [ "$BASELINE_PEAK" -eq 0 ] 2>/dev/null || - [ -z "$BASELINE_TIME" ]; then - echo "::error::Invalid baseline values: peak=$BASELINE_PEAK time=$BASELINE_TIME" - exit 1 - fi + echo "baseline_src=$BASELINE_SRC" >> "$GITHUB_OUTPUT" - PEAK_DIFF=$((CURRENT_PEAK - BASELINE_PEAK)) - PEAK_PCT=$(awk "BEGIN { printf \"%.1f\", ($PEAK_DIFF * 100) / $BASELINE_PEAK }") - TIME_DIFF=$(awk "BEGIN { printf \"%.3f\", $CURRENT_TIME - $BASELINE_TIME }") - TIME_PCT=$(awk "BEGIN { printf \"%.1f\", (($CURRENT_TIME - $BASELINE_TIME) * 100) / $BASELINE_TIME }") + # A missing real-block baseline is NOT an error: it is what a PR sees before + # main has published one, and the comment renders the PR side alone. Only a + # missing PR-side number means the run failed to measure anything, and the + # real-block step already exits non-zero in that case. + if [ -z "$BASELINE_REAL_TIME" ]; then + echo "::notice::No real-block baseline available; reporting the PR side only." + fi - echo "baseline_peak=$BASELINE_PEAK" >> "$GITHUB_OUTPUT" - echo "baseline_time=$BASELINE_TIME" >> "$GITHUB_OUTPUT" - echo "baseline_src=$BASELINE_SRC" >> "$GITHUB_OUTPUT" - echo "peak_diff=$PEAK_DIFF" >> "$GITHUB_OUTPUT" - echo "peak_pct=$PEAK_PCT" >> "$GITHUB_OUTPUT" - echo "time_diff=$TIME_DIFF" >> "$GITHUB_OUTPUT" - echo "time_pct=$TIME_PCT" >> "$GITHUB_OUTPUT" - echo "pr_time_spread=$PR_TIME_SPREAD" >> "$GITHUB_OUTPUT" - echo "pr_heap_spread=$PR_HEAP_SPREAD" >> "$GITHUB_OUTPUT" - echo "pr_all_times=$PR_ALL_TIMES" >> "$GITHUB_OUTPUT" - echo "pr_all_heaps=$PR_ALL_HEAPS" >> "$GITHUB_OUTPUT" - echo "pr_runs=$PR_RUNS" >> "$GITHUB_OUTPUT" - echo "baseline_time_spread=$BASELINE_TIME_SPREAD" >> "$GITHUB_OUTPUT" - echo "baseline_heap_spread=$BASELINE_HEAP_SPREAD" >> "$GITHUB_OUTPUT" - echo "baseline_all_times=$BASELINE_ALL_TIMES" >> "$GITHUB_OUTPUT" - echo "baseline_all_heaps=$BASELINE_ALL_HEAPS" >> "$GITHUB_OUTPUT" - echo "baseline_runs=$BASELINE_RUNS" >> "$GITHUB_OUTPUT" - - # Growth comparison - echo "pr_growth_heaps=$PR_GROWTH_HEAPS" >> "$GITHUB_OUTPUT" - echo "pr_growth_times=$PR_GROWTH_TIMES" >> "$GITHUB_OUTPUT" - echo "pr_growth_slope=$PR_GROWTH_SLOPE" >> "$GITHUB_OUTPUT" - echo "pr_growth_r2=$PR_GROWTH_R2" >> "$GITHUB_OUTPUT" - echo "baseline_growth_heaps=$BASELINE_GROWTH_HEAPS" >> "$GITHUB_OUTPUT" - echo "baseline_growth_times=$BASELINE_GROWTH_TIMES" >> "$GITHUB_OUTPUT" - echo "baseline_growth_slope=$BASELINE_GROWTH_SLOPE" >> "$GITHUB_OUTPUT" - echo "baseline_growth_r2=$BASELINE_GROWTH_R2" >> "$GITHUB_OUTPUT" + # Real-block comparison. Rendered only when BOTH sides have a number AND + # they are the same block: a baseline captured before the Makefile was + # repointed measures a different workload, and showing that as a delta + # would invent a regression out of a fixture swap. + echo "pr_real_time=$PR_REAL_TIME" >> "$GITHUB_OUTPUT" + echo "pr_real_peak=$PR_REAL_PEAK" >> "$GITHUB_OUTPUT" + echo "pr_real_epochs=$PR_REAL_EPOCHS" >> "$GITHUB_OUTPUT" + echo "pr_real_input=$PR_REAL_INPUT" >> "$GITHUB_OUTPUT" + echo "pr_real_runs=$PR_REAL_RUNS" >> "$GITHUB_OUTPUT" + echo "pr_real_time_spread=$PR_REAL_TIME_SPREAD" >> "$GITHUB_OUTPUT" + echo "pr_real_all_times=$PR_REAL_ALL_TIMES" >> "$GITHUB_OUTPUT" + echo "baseline_real_time=$BASELINE_REAL_TIME" >> "$GITHUB_OUTPUT" + echo "baseline_real_peak=$BASELINE_REAL_PEAK" >> "$GITHUB_OUTPUT" + echo "baseline_real_input=$BASELINE_REAL_INPUT" >> "$GITHUB_OUTPUT" + + if [ -n "$PR_REAL_TIME" ] && [ -n "$BASELINE_REAL_TIME" ]; then + if [ -n "$BASELINE_REAL_INPUT" ] && [ "$BASELINE_REAL_INPUT" != "$PR_REAL_INPUT" ]; then + echo "::warning::Baseline real block ($BASELINE_REAL_INPUT) differs from the PR's ($PR_REAL_INPUT); not comparing." + echo "real_mismatch=$BASELINE_REAL_INPUT" >> "$GITHUB_OUTPUT" + else + REAL_TIME_DIFF=$(awk "BEGIN { printf \"%.3f\", $PR_REAL_TIME - $BASELINE_REAL_TIME }") + REAL_TIME_PCT=$(awk "BEGIN { printf \"%.1f\", (($PR_REAL_TIME - $BASELINE_REAL_TIME) * 100) / $BASELINE_REAL_TIME }") + echo "real_time_diff=$REAL_TIME_DIFF" >> "$GITHUB_OUTPUT" + echo "real_time_pct=$REAL_TIME_PCT" >> "$GITHUB_OUTPUT" + if [ -n "$PR_REAL_PEAK" ] && [ -n "$BASELINE_REAL_PEAK" ]; then + REAL_PEAK_DIFF=$((PR_REAL_PEAK - BASELINE_REAL_PEAK)) + REAL_PEAK_PCT=$(awk "BEGIN { printf \"%.1f\", ($REAL_PEAK_DIFF * 100) / $BASELINE_REAL_PEAK }") + echo "real_peak_diff=$REAL_PEAK_DIFF" >> "$GITHUB_OUTPUT" + echo "real_peak_pct=$REAL_PEAK_PCT" >> "$GITHUB_OUTPUT" + fi + fi + fi # Growth slope comparison if [ -n "$BASELINE_GROWTH_SLOPE" ] && [ -n "$PR_GROWTH_SLOPE" ]; then @@ -623,24 +715,7 @@ jobs: if: github.event_name != 'push' && github.event_name != 'workflow_dispatch' uses: actions/github-script@v7 env: - PR_PEAK: ${{ steps.pr.outputs.peak_mb }} - PR_TIME: ${{ steps.pr.outputs.time_s }} - BASELINE_PEAK: ${{ steps.compare.outputs.baseline_peak }} - BASELINE_TIME: ${{ steps.compare.outputs.baseline_time }} BASELINE_SRC: ${{ steps.compare.outputs.baseline_src }} - PEAK_PCT: ${{ steps.compare.outputs.peak_pct }} - TIME_PCT: ${{ steps.compare.outputs.time_pct }} - PEAK_DIFF: ${{ steps.compare.outputs.peak_diff }} - TIME_DIFF: ${{ steps.compare.outputs.time_diff }} - PR_RUNS: ${{ steps.compare.outputs.pr_runs }} - PR_TIME_SPREAD: ${{ steps.compare.outputs.pr_time_spread }} - PR_HEAP_SPREAD: ${{ steps.compare.outputs.pr_heap_spread }} - PR_ALL_TIMES: ${{ steps.compare.outputs.pr_all_times }} - PR_ALL_HEAPS: ${{ steps.compare.outputs.pr_all_heaps }} - BASE_TIME_SPREAD: ${{ steps.compare.outputs.baseline_time_spread }} - BASE_HEAP_SPREAD: ${{ steps.compare.outputs.baseline_heap_spread }} - BASE_ALL_TIMES: ${{ steps.compare.outputs.baseline_all_times }} - BASE_ALL_HEAPS: ${{ steps.compare.outputs.baseline_all_heaps }} PR_GROWTH_HEAPS: ${{ steps.compare.outputs.pr_growth_heaps }} PR_GROWTH_TIMES: ${{ steps.compare.outputs.pr_growth_times }} PR_GROWTH_SLOPE: ${{ steps.compare.outputs.pr_growth_slope }} @@ -651,28 +726,30 @@ jobs: BASE_GROWTH_R2: ${{ steps.compare.outputs.baseline_growth_r2 }} GROWTH_SLOPE_DIFF: ${{ steps.compare.outputs.growth_slope_diff }} GROWTH_SLOPE_PCT: ${{ steps.compare.outputs.growth_slope_pct }} + PR_REAL_TIME: ${{ steps.compare.outputs.pr_real_time }} + PR_REAL_PEAK: ${{ steps.compare.outputs.pr_real_peak }} + PR_REAL_EPOCHS: ${{ steps.compare.outputs.pr_real_epochs }} + PR_REAL_INPUT: ${{ steps.compare.outputs.pr_real_input }} + BASE_REAL_TIME: ${{ steps.compare.outputs.baseline_real_time }} + BASE_REAL_PEAK: ${{ steps.compare.outputs.baseline_real_peak }} + REAL_TIME_DIFF: ${{ steps.compare.outputs.real_time_diff }} + REAL_TIME_PCT: ${{ steps.compare.outputs.real_time_pct }} + REAL_PEAK_DIFF: ${{ steps.compare.outputs.real_peak_diff }} + REAL_PEAK_PCT: ${{ steps.compare.outputs.real_peak_pct }} + REAL_MISMATCH: ${{ steps.compare.outputs.real_mismatch }} + REAL_EPOCH_LOG2: ${{ env.REAL_BLOCK_EPOCH_LOG2 }} + REAL_RUNS: ${{ steps.compare.outputs.pr_real_runs }} + REAL_TIME_SPREAD: ${{ steps.compare.outputs.pr_real_time_spread }} + REAL_ALL_TIMES: ${{ steps.compare.outputs.pr_real_all_times }} COMMIT_SHA: ${{ steps.pr-ref.outputs.sha || github.sha }} TABLE_PARALLELISM: ${{ steps.config.outputs.table_parallelism }} with: + # Renderable offline: `node scripts/render_bench_comment.js` extracts this block + # and prints the markdown for a set of scenarios, so wording and formatting can + # be checked without occupying the bench server for 15-20 min. Add a scenario + # there when you add a branch here. script: | - const peak = process.env.PR_PEAK; - const time = process.env.PR_TIME; - const basePeak = process.env.BASELINE_PEAK; - const baseTime = process.env.BASELINE_TIME; const baseSrc = process.env.BASELINE_SRC; - const peakPct = process.env.PEAK_PCT; - const timePct = process.env.TIME_PCT; - const peakDiff = process.env.PEAK_DIFF; - const timeDiff = process.env.TIME_DIFF; - const runs = process.env.PR_RUNS || '1'; - const prTimeSpread = process.env.PR_TIME_SPREAD; - const prHeapSpread = process.env.PR_HEAP_SPREAD; - const prAllTimes = process.env.PR_ALL_TIMES; - const prAllHeaps = process.env.PR_ALL_HEAPS; - const baseTimeSpread = process.env.BASE_TIME_SPREAD; - const baseHeapSpread = process.env.BASE_HEAP_SPREAD; - const baseAllTimes = process.env.BASE_ALL_TIMES; - const baseAllHeaps = process.env.BASE_ALL_HEAPS; // Growth data const prGrowthHeaps = process.env.PR_GROWTH_HEAPS; @@ -688,76 +765,86 @@ jobs: const fmt = (v) => parseFloat(v) >= 0 ? `+${v}` : v; const icon = (pct) => parseFloat(pct) > 5 ? '🔴' : parseFloat(pct) < -5 ? '🟢' : '⚪'; - const SPREAD_THRESHOLD = 5.0; - - // --- Section 1: Primary benchmark --- - const nLabel = parseInt(runs) > 1 ? ` (median of ${runs})` : ''; - const tableParallelism = process.env.TABLE_PARALLELISM; - const tpLabel = tableParallelism ? tableParallelism : 'auto (cores / 3)'; - let body = `## Benchmark — ethrex 20 transfers${nLabel}\n\n`; - body += `Table parallelism: ${tpLabel}\n\n`; - body += `| Metric | main | PR | Δ |\n`; - body += `|--------|------|----|---|\n`; - body += `| **Peak heap** | ${basePeak} MB | ${peak} MB | ${fmt(peakDiff)} MB (${fmt(peakPct)}%) ${icon(peakPct)} |\n`; - body += `| **Prove time** | ${baseTime}s | ${time}s | ${fmt(timeDiff)}s (${fmt(timePct)}%) ${icon(timePct)} |\n\n`; - - const regression = parseFloat(peakPct) > 5 || parseFloat(timePct) > 5; - const improvement = parseFloat(peakPct) < -5 || parseFloat(timePct) < -5; - if (regression) { - body += `> ⚠️ **Regression detected** — heap or time increased by more than 5%.\n`; - } else if (improvement) { - body += `> 🎉 **Improvement detected** — heap or time decreased by more than 5%.\n`; - } else { - body += `> ✅ No significant change.\n`; - } - - // Tier-1 -> Tier-2 escalation: a small time speedup the cheap 3-5 run CI - // can't confirm (it catches >=~1.5% on its own). Point at the drift-free - // ABBA tiebreaker, which the user runs on demand via `/bench-abba`. - const tp = parseFloat(timePct); - if (tp < 0 && tp > -1.5) { - body += `>\n`; - body += `> 🔬 **Looks like a small speedup (${fmt(timePct)}%) — below what ${runs} runs can confirm.** `; - body += `Comment \`/bench-abba\` to run the drift-free ABBA tiebreaker (paired-t CI + exact Wilcoxon). `; - body += `Note: it occupies the bench server for ~30–40 min.\n`; - body += `> Optional pair count: \`/bench-abba 32\` (20 resolves ~1%, 32 for ~0.6%).\n`; - } - - // Spread warnings - const prWarnings = []; - const baseWarnings = []; - - if (prTimeSpread && parseFloat(prTimeSpread) > SPREAD_THRESHOLD) { - const vals = prAllTimes ? prAllTimes.split('/').map(t => `${t}s`).join(' / ') : ''; - prWarnings.push(`Prove time spread: ${prTimeSpread}% (${vals})`); - } - if (prHeapSpread && parseFloat(prHeapSpread) > SPREAD_THRESHOLD) { - const vals = prAllHeaps ? prAllHeaps.split('/').map(h => `${h} MB`).join(' / ') : ''; - prWarnings.push(`Heap spread: ${prHeapSpread}% (${vals})`); - } - if (baseTimeSpread && parseFloat(baseTimeSpread) > SPREAD_THRESHOLD) { - const vals = baseAllTimes ? baseAllTimes.split('/').map(t => `${t}s`).join(' / ') : ''; - baseWarnings.push(`Baseline time spread: ${baseTimeSpread}% (${vals}) — comparison may be less reliable`); - } - if (baseHeapSpread && parseFloat(baseHeapSpread) > SPREAD_THRESHOLD) { - const vals = baseAllHeaps ? baseAllHeaps.split('/').map(h => `${h} MB`).join(' / ') : ''; - baseWarnings.push(`Baseline heap spread: ${baseHeapSpread}% (${vals}) — comparison may be less reliable`); - } - const allWarnings = [...prWarnings, ...baseWarnings]; - if (allWarnings.length > 0) { - body += `\n`; - for (const w of allWarnings) { - body += `> ⚠️ ${w}\n`; + // Real-block data + const realTime = process.env.PR_REAL_TIME; + const realPeak = process.env.PR_REAL_PEAK; + const realEpochs = process.env.PR_REAL_EPOCHS; + const realInput = process.env.PR_REAL_INPUT; + const baseRealTime = process.env.BASE_REAL_TIME; + const baseRealPeak = process.env.BASE_REAL_PEAK; + const realTimeDiff = process.env.REAL_TIME_DIFF; + const realTimePct = process.env.REAL_TIME_PCT; + const realPeakDiff = process.env.REAL_PEAK_DIFF; + const realPeakPct = process.env.REAL_PEAK_PCT; + const realMismatch = process.env.REAL_MISMATCH; + const realEpochLog2 = process.env.REAL_EPOCH_LOG2; + const realRuns = process.env.REAL_RUNS || '1'; + const realTimeSpread = process.env.REAL_TIME_SPREAD; + const realAllTimes = process.env.REAL_ALL_TIMES; + + // Stable marker: the "find and update the existing comment" lookup at the + // bottom keys off it, so section headings can change without orphaning + // every comment already posted. Legacy title matches stay as a fallback. + let body = `\n`; + + // --- Section 1: Real block (headline; present whenever the fixture is fetchable) --- + if (realTime) { + body += `## Benchmark — real block${realInput ? ` (\`${realInput}\`)` : ''}${parseInt(realRuns) > 1 ? ` (median of ${realRuns})` : ''}\n\n`; + // Run count lives in the heading ("median of N"), as on the synthetic + // section — repeating it here just made the two disagree in tone. + body += `continuations · epoch 2^${realEpochLog2}`; + if (realEpochs) body += ` · ${realEpochs} epochs`; + body += `\n\n`; + + if (realMismatch) { + body += `> ⚠️ Baseline measured a different block (\`${realMismatch}\`) — showing the PR side only.\n\n`; } - if (prWarnings.length > 0) { - body += `> Consider re-running \`/bench\`\n`; + + const haveRealCmp = !!(baseRealTime && realTimePct && !realMismatch); + if (haveRealCmp) { + body += `| Metric | main | PR | Δ |\n`; + body += `|--------|------|----|---|\n`; + if (realPeak && baseRealPeak && realPeakPct) { + body += `| **Peak heap** | ${baseRealPeak} MB | ${realPeak} MB | ${fmt(realPeakDiff)} MB (${fmt(realPeakPct)}%) ${icon(realPeakPct)} |\n`; + } + body += `| **Prove time** | ${baseRealTime}s | ${realTime}s | ${fmt(realTimeDiff)}s (${fmt(realTimePct)}%) ${icon(realTimePct)} |\n\n`; + + // Wider bands than the synthetic screen's 5%: 3 runs of a minutes-long + // prove resolve less than 5 runs of a 25s one, so the middle is reported + // as unresolved rather than as "fine". + const rp = parseFloat(realTimePct); + if (rp > 10) { + body += `> ⚠️ **Regression on the real block** — prove time up ${Math.abs(rp).toFixed(1)}%.\n`; + } else if (rp < -10) { + body += `> 🎉 **Improvement on the real block** — prove time down ${Math.abs(rp).toFixed(1)}%.\n`; + } else if (Math.abs(rp) >= 3) { + body += `> ❓ **${fmt(realTimePct)}% — beyond what ${realRuns} runs resolve.** Use \`/bench-abba\` for a paired test.\n`; + } else { + body += `> ✅ No significant change.\n`; + } + if (realTimeSpread && parseFloat(realTimeSpread) > 5.0) { + const vals = realAllTimes ? realAllTimes.split('/').map(t => `${t}s`).join(' / ') : ''; + body += `>\n> ⚠️ Real-block prove-time spread: ${realTimeSpread}% (${vals}) — the median above is less trustworthy than usual.\n`; + } else if (realTimeSpread && parseInt(realRuns) > 1) { + body += `>\n> Prove-time spread ${realTimeSpread}%${realAllTimes ? ` (${realAllTimes.split('/').map(t => `${t}s`).join(' / ')})` : ''}\n`; + } + } else { + body += `| Metric | PR |\n`; + body += `|--------|----|\n`; + if (realPeak) body += `| **Peak heap** | ${realPeak} MB |\n`; + body += `| **Prove time** | ${realTime}s |\n\n`; + if (!realMismatch) { + body += `> ℹ️ No real-block baseline yet — main publishes one on its next push.\n`; + } } - } else if (parseInt(runs) > 1) { - body += `\n> ✅ Low variance (time: ${prTimeSpread || '0.0'}%, heap: ${prHeapSpread || '0.0'}%)\n`; + body += `\n`; } - // --- Section 2: Memory growth --- + // --- Section 3: Memory growth --- + // Synthetic on purpose: this plots heap against BLOCK SIZE, which needs a + // family of blocks that differ only in transaction count. A real block is + // one point and cannot produce a slope. if (prGrowthHeaps) { const prHeaps = prGrowthHeaps.split('/'); const baseHeaps = baseGrowthHeaps ? baseGrowthHeaps.split('/') : null; @@ -816,6 +903,14 @@ jobs: } // --- Footer --- + // The real block runs on every invocation, so its absence means the fixture + // could not be fetched — say that plainly rather than offering a command to + // re-request it, which no longer exists. + if (!realTime) { + body += `\n> 🧱 **No prover measurement — the real-block fixture was not available.** `; + body += `\`/bench\` proves only the real block, so nothing was measured this run. `; + body += `Check that \`ETHREX_REAL_BLOCK_FIXTURE_URL\` is set in the Makefile and that the artifact is reachable; the run log carries the warning.\n`; + } const sha = process.env.COMMIT_SHA.substring(0, 8); body += `\nCommit: ${sha} · Baseline: ${baseSrc} · Runner: self-hosted bench\n`; @@ -825,9 +920,11 @@ jobs: issue_number: context.issue.number, }); - // Find existing comment (check both old and new markers for transition) + // Find existing comment. The HTML marker is the durable key; the title + // matches below are the transition path for comments posted before it. const existing = comments.find(c => c.user.type === 'Bot' && ( + c.body.includes('') || c.body.includes('Benchmark — ethrex') || c.body.includes('Benchmark — fib_iterative_8M') || c.body.includes('Benchmark — fib_iterative_2M') || diff --git a/.github/workflows/ethrex-block-converter.yml b/.github/workflows/ethrex-block-converter.yml new file mode 100644 index 000000000..53c640e9b --- /dev/null +++ b/.github/workflows/ethrex-block-converter.yml @@ -0,0 +1,113 @@ +name: ethrex block-converter tests + +# Validation for the real-block benchmark fixture and the converter that produces +# it. Deliberately NOT part of the required PR gate (pr_main.yaml): the fixture is a +# benchmark input, not a correctness input — no product code reads it — so it needs +# to be right when it changes, not on every PR. Running it there cost every PR a +# network download plus a cold build of ~335 packages (blst, c-kzg and secp256k1-sys +# C builds, malachite, ark-ff/asm). +# +# It fires on the things that can actually invalidate it: the converter, the ethrex +# host-reference tests, and the Makefile (which holds the block pin, the fixture URL +# and its sha256). +on: + workflow_dispatch: + pull_request: + branches: ["**"] + paths: + - 'tooling/ethrex-block-converter/**' + - 'tooling/ethrex-tests/**' + - 'Makefile' + - '.github/workflows/ethrex-block-converter.yml' + push: + branches: ["main"] + paths: + - 'tooling/ethrex-block-converter/**' + - 'tooling/ethrex-tests/**' + - 'Makefile' + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + converter: + name: Block converter tests + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Setup Rust Environment + uses: ./.github/actions/setup-rust + + - name: Cache cargo build artifacts + uses: Swatinem/rust-cache@v2 + with: + # Own key, not pr_main.yaml's "lambda-vm-test": a shared key across jobs + # that build different crate sets causes recompilation on both sides. + shared-key: "lambda-vm-ethrex-block-converter" + cache-all-crates: "true" + # Detached workspace (own Cargo.lock, own target dir), so the default + # `. -> target` would miss it and rebuild the ethrex tree every run. + workspaces: | + tooling/ethrex-block-converter -> target + + # Downloads the pinned ethrex-replay cache (the converter's test input) and + # runs the crate's tests: host-side parity through the guest's own + # `LambdaVmEcsmCrypto`, the unmappable-network rejection, and the + # reproducibility digest. Note these do NOT screen KZG — this crate's graph + # links c-kzg via ethrex-config, so point evaluation (0x0a) resolves here and + # to nothing in the guest. The block-usability job below is what covers that. + - name: Run converter tests + run: make test-ethrex-real-block-converter + + block-usable: + name: Real-block usability screen + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Setup Rust Environment + uses: ./.github/actions/setup-rust + + - name: Cache cargo build artifacts + uses: Swatinem/rust-cache@v2 + with: + shared-key: "lambda-vm-real-block-usable" + cache-all-crates: "true" + workspaces: | + tooling/ethrex-tests -> target + + # Fetch-and-verify, not build: no converter, no ethrex-replay cache, no rev pin. + # The guard covers the window after a repoint but before the new artifact is + # uploaded: the screen below is its only consumer, and failing the job on an + # unset URL would block PRs on an upload nobody in the PR can perform. + - name: Fetch real-block fixture + id: fixture + run: | + if [ -z "$(make -s print-real-block-fixture-url)" ]; then + echo "::warning::ETHREX_REAL_BLOCK_FIXTURE_URL is unset — skipping the real-block usability screen. Set it in the Makefile once the .bin is hosted." + echo "present=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + make ethrex-real-block-fixture + echo "present=true" >> "$GITHUB_OUTPUT" + + # `test_ethrex_real_block_native` is the screen that makes a block USABLE rather + # than merely realistic: this crate links no KZG backend (pinned by + # `no_kzg_backend_linked`, which runs in pr_main.yaml), so a block calling point + # evaluation (0x0a) diverges from consensus and fails here instead of silently + # passing and then failing in the guest. + # + # `test_ethrex_real_block_vm` stays excluded: it drives the block through the + # guest ELF, needs the RV64 toolchain, and its runtime is unmeasured. + - name: Screen the block against the guest's precompile surface + if: steps.fixture.outputs.present == 'true' + run: | + cd tooling/ethrex-tests && \ + cargo test --release test_ethrex_real_block_native -- --include-ignored diff --git a/.github/workflows/pr_main.yaml b/.github/workflows/pr_main.yaml index a4554fda2..5d560a63a 100644 --- a/.github/workflows/pr_main.yaml +++ b/.github/workflows/pr_main.yaml @@ -56,6 +56,18 @@ jobs: with: shared-key: "lambda-vm-test" cache-all-crates: "true" + # ethrex-tests is a detached workspace (own Cargo.lock, own target dir), so + # the default `. -> target` misses it and its release tree would rebuild + # from scratch every run. `cache-all-crates` only covers the registry, not + # compiled artifacts. + # + # ethrex-block-converter is deliberately NOT here: this job no longer builds it. + # That workspace is the expensive one (~335 packages, including the blst, + # c-kzg and secp256k1-sys C builds, malachite and ark-ff/asm) and it now + # builds only in ethrex-block-converter.yml, which carries its own cache entry. + workspaces: | + . -> target + tooling/ethrex-tests -> target - name: Cache compiled ASM ELF artifacts id: cache-asm-elfs @@ -107,9 +119,24 @@ jobs: # them from that directory to use its isolated Cargo.lock. The guest ELF # and committed fixtures are already present from the steps above. # --include-ignored also runs the heavier synthetic-block test. + # + # `--skip test_ethrex_real_block` is a substring match, so it drops BOTH + # real-block tests (the `_vm` and `_native` ones) and with them this job's need + # for the real-block fixture. That fixture is a benchmark input: it has to be + # right once, not on every PR, and fetching plus screening it here put a network + # download and a ~335-package build in the required gate for an artifact with no + # bearing on correctness. Both moved to ethrex-block-converter.yml, which runs when + # the converter or the block pin actually changes. `no_kzg_backend_linked` still + # runs here — it is a pure unit test, and it is the property the real-block + # screen over there depends on. + # + # This is `make test-ethrex-offline` spelled out, matching the other test steps + # in this job: they all bypass make so its dependency check cannot decide the + # cache-restored ELFs are stale and trigger a rebuild. - name: Run ethrex host-reference tests (detached workspace) run: | - cd tooling/ethrex-tests && cargo test --release -- --include-ignored + cd tooling/ethrex-tests && \ + cargo test --release -- --include-ignored --skip test_ethrex_real_block test-cli: name: CLI tests diff --git a/Makefile b/Makefile index 4abed4a90..a3471bdb4 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,15 @@ .PHONY: deps deps-linux deps-macos compile-programs-asm compile-programs-rust compile-bench \ compile-programs compile-recursion-elfs clean-asm clean-rust clean-bench clean-shared \ clean-recursion-elfs clean test test-asm \ -test-rust test-ethrex test-executor test-syscalls test-flamegraph flamegraph-prover test-profile-recursion test-profile-recursion-single test-profile-recursion-multi \ +test-rust test-ethrex test-ethrex-offline test-executor test-syscalls test-flamegraph flamegraph-prover test-profile-recursion test-profile-recursion-single test-profile-recursion-multi \ test-profile-recursion-block recursion-profile-block-input \ test-fast test-prover test-prover-all test-prover-debug test-disk-spill test-math-cuda test-cuda-integration test-cuda-fallback \ test-prover-cuda test-prover-comprehensive-cuda \ bench-math-cuda bench-prover bench-prover-cuda build check clippy fmt lint regen-ethrex-fixtures \ -update-ethrex-fixture-checksums check-ethrex-fixture-checksums +update-ethrex-fixture-checksums check-ethrex-fixture-checksums ethrex-real-block-fixture \ +ethrex-real-block-cache ethrex-real-block-converter-cache print-real-block-fixture \ +print-real-block-fixture-url \ +test-ethrex-real-block-converter regen-real-block-fixture UNAME := $(shell uname) @@ -271,10 +274,187 @@ test-asm: compile-programs-asm test-rust: compile-programs-rust cargo test -p executor --test rust +# ===== Real block: the benchmark workload ===== +# +# A genuine Ethereum block, as opposed to the synthetic N-plain-transfer blocks +# from tooling/ethrex-fixtures. Two artifacts, both gitignored and both FETCHED +# rather than built: +# +# the fixture the rkyv ProgramInput the benchmarks prove (~1 MB) +# the cache the ethrex-replay JSON it was converted from (~2 MB), read only +# by the converter's tests and by `regen-real-block-fixture` +# +# Fetching a verified binary is the same contract as prepare-sysroot above, and it +# keeps the converter, the ~335-package ethrex host dependency tree and an +# ethrex-replay `rev` pin off the path of everyone who just wants to run a +# benchmark. It also decouples the block from what upstream happens to host: +# ethrex-replay publishes a cache for Hoodi and nothing else, so any mainnet block +# is unreachable by the convert-locally route (its cache takes ~4 minutes and ~700 +# calls against an archive RPC to produce) and trivial by this one. +# +# The converter still exists and is still tested — see "Real-block converter" +# below. It is a regeneration tool for ethrex rev bumps, not a build step. +# +# ---- Repointing to a different block ---- +# These FIVE lines and nothing else. Every path below derives from them, the +# benchmark scripts and CI resolve the fixture through +# `make -s print-real-block-fixture`, and no workflow, script or env var anywhere +# names a block. Outside this file the repoint touches only REAL_BLOCK_FIXTURE in +# tooling/ethrex-tests, which points the usability screen at the block actually +# being proven. The converter's own pins do NOT move — see below. +# tooling/ethrex-block-converter/README.md carries the procedure and each candidate's +# measured cost. +ETHREX_REAL_BLOCK_NETWORK := mainnet +ETHREX_REAL_BLOCK := 25368371 +ETHREX_REAL_BLOCK_FIXTURE_URL := https://github.com/yetanotherco/lambda_vm/releases/download/bench-fixtures-v1/ethrex_mainnet_25368371.bin +ETHREX_REAL_BLOCK_FIXTURE_SHA256 := 61eba49b6b254f4a05def5a47b08a21ae3eee56f0d37bcd7b3a24b0cc1e4a300 +# The block's source cache, hosted in the same release. Only `regen-real-block-fixture` +# reads it — the converter's TESTS use a different, upstream-pinned cache (below). +ETHREX_REAL_BLOCK_CACHE_URL := https://github.com/yetanotherco/lambda_vm/releases/download/bench-fixtures-v1/cache_mainnet_25368371.json +ETHREX_REAL_BLOCK_CACHE_SHA256 := 7aa88a5f7c5755b7575870f95e6c5c26186947f5e9e0d52199148c74e2a2736b + +ETHREX_REAL_BLOCK_ID := $(ETHREX_REAL_BLOCK_NETWORK)_$(ETHREX_REAL_BLOCK) +ETHREX_REAL_BLOCK_FIXTURE := executor/tests/ethrex_$(ETHREX_REAL_BLOCK_ID).bin +ETHREX_REAL_BLOCK_CACHE := tooling/ethrex-block-converter/caches/cache_$(ETHREX_REAL_BLOCK_ID).json + +# $(call ensure_verified,url,sha256,dest,label,url-var-name) +# +# Guard-then-fetch, the same shape as prepare-sysroot above: the digest of whatever +# is already on disk is checked on EVERY invocation, so this catches the three ways +# a wrong artifact gets there — a stale copy from before a re-upload under the same +# block number, a corrupted file, and a hand-placed one — not just an absent file. +# That is why these are phony targets rather than file rules: a file rule does not +# run when its target exists, which is exactly the case that needs checking. +# (It replaces the ethrex-replay rev stamp, which guarded the same class of +# staleness for the one input that used to be pinned by rev.) +# +# On a miss it downloads to a temp file and verifies BEFORE moving into place, so an +# interrupted or corrupted transfer cannot leave a file that later reads as valid and +# silently changes what every benchmark measures. The "neither sha256sum nor shasum" +# case is a hard error, as in prepare-sysroot — a skipped check would defeat the +# point of fetching a binary at all. +define ensure_verified + @set -e; \ + dest="$(3)"; want="$(2)"; \ + if command -v sha256sum >/dev/null 2>&1; then shacmd="sha256sum"; \ + elif command -v shasum >/dev/null 2>&1; then shacmd="shasum -a 256"; \ + else echo "$(4): missing sha256sum or shasum for checksum verification" >&2; exit 1; fi; \ + sha_of() { $$shacmd "$$1" | awk '{print $$1}'; }; \ + if [ -f "$$dest" ] && [ "$$(sha_of "$$dest")" = "$$want" ]; then \ + exit 0; \ + fi; \ + if [ -f "$$dest" ]; then \ + echo "$(4) $$dest does not match $$want - refetching."; \ + fi; \ + if [ -z "$(1)" ]; then \ + echo "$(4): $(5) is unset." >&2; \ + echo " The $(ETHREX_REAL_BLOCK_ID) $(4) is fetched, not built. Set $(5) in the" >&2; \ + echo " Makefile to wherever the artifact is hosted; see" >&2; \ + echo " tooling/ethrex-block-converter/README.md for how to produce and host one." >&2; \ + exit 1; \ + fi; \ + mkdir -p $(dir $(3)); \ + tmp="$$dest.tmp"; \ + cleanup() { rm -f "$$tmp"; }; \ + trap 'cleanup' EXIT; \ + trap 'cleanup; exit 130' INT; \ + trap 'cleanup; exit 143' TERM; \ + echo "Downloading $(4) $(ETHREX_REAL_BLOCK_ID)..."; \ + curl -fL --proto '=https' --retry 3 --retry-delay 2 --retry-all-errors "$(1)" -o "$$tmp"; \ + echo "Verifying $(4) checksum..."; \ + if [ "$$(sha_of "$$tmp")" != "$$want" ]; then \ + echo "$(4): checksum mismatch for $(1)" >&2; \ + exit 1; \ + fi; \ + mv "$$tmp" "$$dest"; \ + trap - EXIT +endef + +ethrex-real-block-fixture: + $(call ensure_verified,$(ETHREX_REAL_BLOCK_FIXTURE_URL),$(ETHREX_REAL_BLOCK_FIXTURE_SHA256),$(ETHREX_REAL_BLOCK_FIXTURE),fixture,ETHREX_REAL_BLOCK_FIXTURE_URL) + +ethrex-real-block-cache: + $(call ensure_verified,$(ETHREX_REAL_BLOCK_CACHE_URL),$(ETHREX_REAL_BLOCK_CACHE_SHA256),$(ETHREX_REAL_BLOCK_CACHE),cache,ETHREX_REAL_BLOCK_CACHE_URL) + +# Single source of truth for the benchmark tooling. scripts/bench_verify.sh, +# scripts/perf_diff.sh and .github/workflows/benchmark-pr.yml read the fixture +# path from here instead of hardcoding it, so repointing the block above moves +# every benchmark at once. `-s` on the caller's side keeps the output clean. +print-real-block-fixture: + @echo $(ETHREX_REAL_BLOCK_FIXTURE) + +# Lets CI ask "is the fixture hosted yet?" without parsing the Makefile. Prints +# nothing while the URL is unset, which is the condition callers branch on. +print-real-block-fixture-url: + @echo $(ETHREX_REAL_BLOCK_FIXTURE_URL) + +# ===== Real-block converter (regeneration tool, off the build path) ===== +# +# Only needed when the guest's ethrex `rev` moves and the fixture has to be +# rebuilt, or when validating a candidate block. Nothing in the benchmark or test +# path builds this crate. +# +# Its TEST input is pinned to Hoodi 1265656, independently of whichever block the +# benchmarks currently prove, and stays there across a repoint. What these tests +# exercise is the CONVERSION — cache JSON in, correctly-laid-out rkyv out — which +# any real block demonstrates equally well. Hoodi's is the one cache ethrex-replay +# publishes, so pinning there costs us no hosting, cannot drift, and leaves the +# benchmark block free to change without touching this crate. +# +# Pinned by immutable `rev`, as the guest pins ethrex itself: a branch ref would let +# the converter's reproducibility digest drift under a fixed input. +ETHREX_REPLAY_REV := 2693e0182a8734117151d8ea2891eda5afc60383 +ETHREX_CONVERTER_TEST_BLOCK := hoodi_1265656 +ETHREX_CONVERTER_CACHE := tooling/ethrex-block-converter/caches/cache_$(ETHREX_CONVERTER_TEST_BLOCK).json +# The cache filename is keyed on the block only, and its download rule has no other +# prerequisite, so make would treat an already-present cache as up to date across an +# `ETHREX_REPLAY_REV` bump and silently keep reading the old input. Depending on a +# rev-stamped marker makes a re-pin discard the stale cache; without it the mismatch +# only surfaces downstream as a `conversion_is_reproducible` digest failure, which +# reads as "regenerate the fixture" and points at the wrong thing. +ETHREX_REPLAY_REV_STAMP := tooling/ethrex-block-converter/caches/.replay-rev-$(ETHREX_REPLAY_REV) + +$(ETHREX_REPLAY_REV_STAMP): + mkdir -p $(dir $@) + rm -f $(ETHREX_CONVERTER_CACHE) $(dir $@).replay-rev-* + touch $@ + +$(ETHREX_CONVERTER_CACHE): $(ETHREX_REPLAY_REV_STAMP) + mkdir -p $(dir $@) + curl -fsSL --retry 3 --retry-delay 2 --retry-all-errors -o $@.tmp \ + https://raw.githubusercontent.com/lambdaclass/ethrex-replay/$(ETHREX_REPLAY_REV)/caches/cache_$(ETHREX_CONVERTER_TEST_BLOCK).json + mv $@.tmp $@ + +ethrex-real-block-converter-cache: $(ETHREX_CONVERTER_CACHE) + +# Converter correctness: host-side parity through the guest's own Crypto impl, the +# network-rejection guard, and the reproducibility digest. Runs on changes to the +# converter (see .github/workflows/ethrex-block-converter.yml), not on every PR. +test-ethrex-real-block-converter: $(ETHREX_CONVERTER_CACHE) + cd tooling/ethrex-block-converter && cargo test --release + +# Manual regeneration of the BENCHMARK fixture (not the converter's test block): +# fetches that block's own cache and re-converts it, overwriting the fixture in +# place so you can hash the result and upload it. That upload, plus SHA256/URL at +# the top, is how the fixture is actually replaced. +regen-real-block-fixture: ethrex-real-block-cache + cd tooling/ethrex-block-converter && \ + cargo run --release -- ../../$(ETHREX_REAL_BLOCK_CACHE) ../../$(ETHREX_REAL_BLOCK_FIXTURE) + # ethrex host-reference tests live in the detached `tooling/ethrex-tests` # workspace (ethrex pins rkyv's `unaligned` feature; isolated Cargo.lock). -test-ethrex: compile-programs-rust - cd tooling/ethrex-tests && cargo test --release -- --include-ignored +# Needs the real-block fixture, so it needs the fixture URL to be set; CI runs the +# `-offline` variant below in the PR gate and this one only in ethrex-block-converter.yml. +test-ethrex: compile-programs-rust ethrex-real-block-fixture + cd tooling/ethrex-tests && cargo test --release -- --include-ignored --skip test_ethrex_real_block_vm + +# Offline variant: no network, and what the PR gate runs. `--skip +# test_ethrex_real_block` is a substring match, so it drops both real-block tests — +# the `_vm` one and the `_native` one, which reads the fetched fixture and would +# otherwise fail on a clean checkout. The committed synthetic fixtures and +# `no_kzg_backend_linked` still run. +test-ethrex-offline: compile-programs-rust + cd tooling/ethrex-tests && cargo test --release -- --include-ignored --skip test_ethrex_real_block test-flamegraph: cargo test -p executor --test flamegraph diff --git a/bench_vs/run_ethrex.sh b/bench_vs/run_ethrex.sh index 4438e1c26..b323d5866 100755 --- a/bench_vs/run_ethrex.sh +++ b/bench_vs/run_ethrex.sh @@ -33,10 +33,12 @@ NC='\033[0m' BLOCKS=( "ethrex empty block|ethrex_empty_block.bin" "ethrex 1 tx|ethrex_simple_tx.bin" - # ethrex_10_transfers.bin (~42M cycles) executes fine but is too heavy to - # prove on a typical machine (OOMs ~36 GB) — software ecrecover dominates - # (~4M cycles/transfer). Kept as a fixture; add here once ecrecover is a - # precompile or for big-memory/nightly proving. + # ethrex_10_transfers.bin is 6.8M cycles (measured), NOT the ~42M this comment + # used to claim: that figure — and the "OOMs ~36 GB, software ecrecover + # dominates at ~4M cycles/transfer" reasoning built on it — predates ecrecover + # becoming an ECSM accelerator, which cut it ~6x. It is no longer too heavy to + # prove. Left out only because the two blocks above already cover the cheap + # end; add it if a mid-size point is wanted. ) # --- Parse args ------------------------------------------------------------- diff --git a/bin/cli/src/main.rs b/bin/cli/src/main.rs index 1de36220b..a04e920db 100644 --- a/bin/cli/src/main.rs +++ b/bin/cli/src/main.rs @@ -790,6 +790,12 @@ fn cmd_prove_continuation( eprintln!( "Generating continuation proof (blowup={blowup}, epoch_size_log2={epoch_size_log2}, epoch_size={epoch_size})...", ); + // Same tracker as the monolithic path: the peak here is the flat per-epoch + // working set rather than a whole-trace high-water mark, and it is the metric + // that decides which epoch size a machine can run, so the benchmarks need it + // reported identically on both paths. + #[cfg(feature = "jemalloc-stats")] + let tracker = heap_tracker::HeapTracker::start(); let start = Instant::now(); let bundle = match prover::continuation::prove_continuation( &elf_data, @@ -834,6 +840,11 @@ fn cmd_prove_continuation( if time { println!("Proving time: {:.3}s", prove_elapsed.as_secs_f64()); } + #[cfg(feature = "jemalloc-stats")] + { + let peak_bytes = tracker.stop(); + println!("Peak heap: {} MB", peak_bytes / (1024 * 1024)); + } ExitCode::SUCCESS } diff --git a/executor/.gitignore b/executor/.gitignore index 17f7497d7..1229bde5c 100644 --- a/executor/.gitignore +++ b/executor/.gitignore @@ -1,6 +1,13 @@ /target /program_artifacts/rust -/tests/ethrex_hoodi.bin +# Real-block fixtures (~1 MB): generated by `make ethrex-real-block-fixture` +# from an ethrex-replay cache, never committed. See tooling/ethrex-block-converter. +# One pattern per network the converter accepts (mainnet/hoodi/sepolia — it +# rejects anything else), so repointing ETHREX_REAL_BLOCK_NETWORK in the Makefile +# cannot silently make a ~1 MB fixture committable. +/tests/ethrex_mainnet_*.bin +/tests/ethrex_hoodi*.bin +/tests/ethrex_sepolia_*.bin /tests/ethrex_bench_*.bin # _4 is committed (~17 KB): used by `make recursion-profile-block-input` and # scripts/bench_recursion_scaling.sh. Other sizes stay ignored — scaling.sh diff --git a/executor/tests/README.md b/executor/tests/README.md index 39fd13813..80c16d4eb 100644 --- a/executor/tests/README.md +++ b/executor/tests/README.md @@ -52,3 +52,13 @@ ethrex_10_transfers.bin sha256: 38901ee4d40b99cf0aa7f642a92f0fc8db76d974bf43033a1673839020c3c28e contents: stateless ethrex block with ten plain ETH transfer transactions ``` + +## Real-block fixtures + +The blocks above are synthetic (N plain ETH transfers over a small genesis). +For a representative workload — real contract execution, real trie depth, real +bytecode — `make ethrex-real-block-fixture` generates +`ethrex_hoodi_1265656.bin` from an ethrex-replay cache. It is ~1 MB, so it is +gitignored and generated on demand rather than committed, and its checksum is +pinned in `tooling/ethrex-block-converter/README.md` rather than above (the +checksum script only covers committed fixtures). diff --git a/scripts/bench_abba.sh b/scripts/bench_abba.sh index fd0e7ad3e..061c6e407 100755 --- a/scripts/bench_abba.sh +++ b/scripts/bench_abba.sh @@ -10,8 +10,9 @@ # than an unpaired two-sample test. # # WHAT IT DOES: -# 1. Builds the ethrex guest ELF + 5-transfer fixture once (identical for both -# sides — a prover-only change doesn't touch the guest). +# 1. Builds the ethrex guest ELF and obtains the workload fixture once (identical +# for both sides — a prover-only change doesn't touch the guest). Which fixture +# is WORKLOAD's business; see below. # 2. Builds the `cli` prover at REF_A and REF_B (skips the build and reuses the # cached binaries if they already exist; set REBUILD=1 to force). # 3. Runs N_PAIRS interleaved pairs in A B B A ... order (alternating which side @@ -35,6 +36,16 @@ # EPOCH_SIZE_LOG2= continuation epoch size (default 20; min 18). # TX_COUNT= ethrex transfer fixture to prove (default 5; use 20 for a # large continuation trace where GPU-residency wins are visible). +# Ignored when WORKLOAD=real. +# WORKLOAD=synthetic|real (default synthetic) which block to prove. `real` +# fetches the real-block fixture (block identity lives in the Makefile) and +# forces --continuations; TX_COUNT and CONTINUATIONS do not apply to it. +# +# On WORKLOAD: the synthetic default is N plain transfers — ecrecover-heavy over a +# near-empty state — while a real block is keccak- and trie-bound, so a prover change +# can move the two in opposite directions. `real` is what benchmark-gpu.yml runs by +# default; `synthetic` stays the default HERE so CPU /bench-abba results remain +# comparable with everything recorded before. # # Sizing (ethrex pair-noise sd ~1.2%, 80% power): ~12 pairs for a 1% effect, # ~18 for 0.8%, ~32 for 0.6%. Default 20 -> solid on 0.8-1%, ~60% power at 0.6% @@ -65,6 +76,18 @@ CONTINUATIONS="${CONTINUATIONS:-0}" EPOCH_SIZE_LOG2="${EPOCH_SIZE_LOG2:-20}" # ethrex transfer-count fixture to prove (executor/tests/ethrex_${TX_COUNT}_transfers.bin). TX_COUNT="${TX_COUNT:-5}" +WORKLOAD="${WORKLOAD:-synthetic}" +case "$WORKLOAD" in + synthetic|real) ;; + *) echo "ERROR: WORKLOAD must be 'synthetic' or 'real' (got '$WORKLOAD')." >&2; exit 2 ;; +esac +# A real block sits far past the monolithic memory ceiling (~4.9 GB of peak heap per +# million cycles puts it in the hundreds of GB), so continuations are forced rather +# than offered: CONTINUATIONS=0 here could only produce an OOM. +if [ "$WORKLOAD" = "real" ] && [ "$CONTINUATIONS" != "1" ]; then + echo " NOTE: WORKLOAD=real always proves with continuations (monolithic would OOM)." + CONTINUATIONS=1 +fi if [ "$CONTINUATIONS" = "1" ]; then CONT_ARGS="--continuations --epoch-size-log2 $EPOCH_SIZE_LOG2" else @@ -72,7 +95,8 @@ else fi ELF_REL="executor/program_artifacts/rust/ethrex.elf" -INPUT_REL="executor/tests/ethrex_${TX_COUNT}_transfers.bin" +# Resolved after the cd to the repo root (WORKLOAD=real reads it from the Makefile). +INPUT_REL="" WORK="/tmp/abba_run" WT="/tmp/abba_wt" PROOF="/tmp/abba_proof.bin" @@ -94,6 +118,16 @@ if [ $((N_PAIRS % 2)) -ne 0 ]; then fi echo " pairs=$N_PAIRS (=$((N_PAIRS * 2)) prove runs)" +# The real block's identity lives in the Makefile and nowhere else, so repointing it +# never needs an edit here. +if [ "$WORKLOAD" = "real" ]; then + INPUT_REL="$(make -s print-real-block-fixture)" + echo " workload=real $INPUT_REL (continuations, epoch 2^$EPOCH_SIZE_LOG2)" +else + INPUT_REL="executor/tests/ethrex_${TX_COUNT}_transfers.bin" + echo " workload=synthetic ${TX_COUNT}tx $INPUT_REL" +fi + mkdir -p "$WORK" # --- 1. Guest ELF + fixture (identical for both sides; build once if missing) --- @@ -103,9 +137,17 @@ if [ ! -f "$ELF_REL" ]; then make "$ELF_REL" fi if [ ! -f "$INPUT_REL" ]; then - echo "==> Generating ethrex ${TX_COUNT}-transfer fixture (missing)" - ( cd tooling/ethrex-fixtures && cargo build --release ) - tooling/ethrex-fixtures/target/release/ethrex-fixtures "$TX_COUNT" "$INPUT_REL" distinct + if [ "$WORKLOAD" = "real" ]; then + # ~1 MB, gitignored, never in a fresh checkout — and a rented GPU box is always a + # fresh checkout. Fetched by URL + sha256, not built: no converter, no ethrex host + # dependency tree, so this costs seconds on the box. + echo "==> Fetching ethrex real-block fixture (missing)" + make ethrex-real-block-fixture + else + echo "==> Generating ethrex ${TX_COUNT}-transfer fixture (missing)" + ( cd tooling/ethrex-fixtures && cargo build --release ) + tooling/ethrex-fixtures/target/release/ethrex-fixtures "$TX_COUNT" "$INPUT_REL" distinct + fi fi ELF="$(cd "$(dirname "$ELF_REL")" && pwd)/$(basename "$ELF_REL")" INPUT="$(cd "$(dirname "$INPUT_REL")" && pwd)/$(basename "$INPUT_REL")" diff --git a/scripts/bench_verify.sh b/scripts/bench_verify.sh index 33c1c1464..068b4c864 100755 --- a/scripts/bench_verify.sh +++ b/scripts/bench_verify.sh @@ -27,7 +27,15 @@ # below 6: the exact Wilcoxon's smallest attainable two-sided p is 2/2^n, so at # n=4 it is 0.125 and the arm can only ever report BORDERLINE, however large and # clean the effect. -# CONT_EPOCH_LOG2= continuation epoch size (default 20, min 18). 20 matches +# WORKLOAD=synthetic|real (default synthetic) which BLOCK both arms prove. +# `real` fetches the real-block fixture (identity lives in the Makefile) and runs +# the continuation arm ONLY — a real block is hundreds of GB monolithically, so +# that arm is skipped rather than left to OOM. See "Workload" below. +# CONT_EPOCH_LOG2= continuation epoch size (default 20, min 18). For +# WORKLOAD=real prefer the calibrated tier for the box you are on — 2^22 on the +# bench runner or a 64 GiB machine, 2^23 on a 128 GiB one (see +# tooling/ethrex-block-converter/README.md, "Choosing the epoch size"); the default +# below is chosen for the SYNTHETIC arm. 20 matches # scripts/bench_abba.sh, so this arm proves the same bundle shape /bench already # proves on the same server — and 20 txs at 2^20 is strictly cheaper than the # 100 txs at 2^20 that /bench runs by default, so it can't be the thing that OOMs @@ -36,6 +44,17 @@ # bench_recursion_cycles.sh's BLOCK_EPOCH_LOG2=21: that arm needs FEW epochs so # the bundle fits the guest's 512 MiB private-input cap, a constraint that # doesn't apply to host-side verification. +# +# Workload. What this script measures is VERIFY cost, and verify cost is structural in +# the proof — table mix, trace lengths, query count — so the block decides what is being +# measured, not the loop around it. The synthetic default is 20 plain transfers: +# ecrecover-heavy over a near-empty state. A real block inverts that mix (keccak- and +# trie-bound), so a verifier change can move the two differently. +# +# `synthetic` stays the default because it is what `/bench-verify` runs and what every +# number recorded so far used; `real` is the representative one, and costs a ~4.5-4.8 +# min continuation prove per side (cached in $WORK afterwards) before any verify run. +# Both sides always prove the same block, so a comparison is never mixed. set -euo pipefail @@ -50,9 +69,22 @@ N_PAIRS="${3:-20}" BENCH_FEATURES="${BENCH_FEATURES:-jemalloc-stats}" CONT_PAIRS="${CONT_PAIRS:-8}" CONT_EPOCH_LOG2="${CONT_EPOCH_LOG2:-20}" +WORKLOAD="${WORKLOAD:-synthetic}" +case "$WORKLOAD" in + synthetic|real) ;; + *) echo "ERROR: WORKLOAD must be 'synthetic' or 'real' (got '$WORKLOAD')." >&2; exit 2 ;; +esac +# WORKLOAD=real skips the monolithic arm, so CONT_PAIRS=0 on top of it would build both +# binaries, prove nothing and report nothing. Fail before the ~30-min build rather than +# after it. +if [ "$WORKLOAD" = "real" ] && [ "${CONT_PAIRS}" -eq 0 ] 2>/dev/null; then + echo "ERROR: WORKLOAD=real runs the continuation arm only, so CONT_PAIRS=0 would measure nothing." >&2 + exit 2 +fi ELF_REL="executor/program_artifacts/rust/ethrex.elf" -INPUT_REL="executor/tests/ethrex_bench_20.bin" +# Resolved after the cd to the repo root (WORKLOAD=real reads it from the Makefile). +INPUT_REL="" WORK="/tmp/verify_run" WT="/tmp/verify_wt" PROOF_B="$WORK/proof_b.bin" # baseline's proof (cached in $WORK, keyed like the binaries) @@ -113,7 +145,26 @@ fi if [ $((CONT_PAIRS % 2)) -ne 0 ]; then echo " WARNING: CONT_PAIRS=$CONT_PAIRS is odd; use an even count so AB/BA orders balance." fi -echo " pairs=$N_PAIRS (=$((N_PAIRS * 2)) verify runs)" +# The real block's identity lives in the Makefile and nowhere else, so repointing it +# never needs an edit here. A real block cannot be proven monolithically (~4.9 GB of peak +# heap per million cycles puts it in the hundreds of GB), so that arm is skipped outright +# rather than left to OOM halfway through a rented or shared machine's run. +RUN_MONO=1 +if [ "$WORKLOAD" = "real" ]; then + INPUT_REL="$(make -s print-real-block-fixture)" + RUN_MONO=0 + BLOCK_LABEL="ethrex real block $(basename "$INPUT_REL")" +else + INPUT_REL="executor/tests/ethrex_bench_20.bin" + BLOCK_LABEL="ethrex 20-tx block" +fi + +echo " workload=$WORKLOAD $INPUT_REL" +if [ "$RUN_MONO" = "1" ]; then + echo " pairs=$N_PAIRS (=$((N_PAIRS * 2)) verify runs)" +else + echo " monolithic arm SKIPPED (a real block exceeds the monolithic memory ceiling)" +fi echo " continuation pairs=$CONT_PAIRS epoch=2^$CONT_EPOCH_LOG2" mkdir -p "$WORK" @@ -129,9 +180,16 @@ if [ ! -f "$ELF_REL" ]; then make "$ELF_REL" fi if [ ! -f "$INPUT_REL" ]; then - echo "==> Generating ethrex 20-transfer fixture (missing)" - ( cd tooling/ethrex-fixtures && cargo build --release ) - tooling/ethrex-fixtures/target/release/ethrex-fixtures 20 "$INPUT_REL" distinct + if [ "$WORKLOAD" = "real" ]; then + # ~1 MB, gitignored, never in a fresh checkout. Fetched by URL + sha256, not built: + # no converter and no ethrex host dependency tree on this path. + echo "==> Fetching ethrex real-block fixture (missing)" + make ethrex-real-block-fixture + else + echo "==> Generating ethrex 20-transfer fixture (missing)" + ( cd tooling/ethrex-fixtures && cargo build --release ) + tooling/ethrex-fixtures/target/release/ethrex-fixtures 20 "$INPUT_REL" distinct + fi fi ELF="$(cd "$(dirname "$ELF_REL")" && pwd)/$(basename "$ELF_REL")" INPUT="$(cd "$(dirname "$INPUT_REL")" && pwd)/$(basename "$INPUT_REL")" @@ -245,12 +303,15 @@ prove_cached() { # $1=binary $2=proof-path $3=sha $4...=extra prove args if [ -n "$ep" ]; then printf '%s\n' "$ep" > "$out.epochs"; fi echo "$marker" > "$out.sha" } -prove_cached "$WORK/cli_B" "$PROOF_B" "$SHA_B" || exit 1 -prove_cached "$WORK/cli_A" "$PROOF_A" "$SHA_A" || exit 1 +SIZE_B=0; SIZE_A=0 +if [ "$RUN_MONO" = "1" ]; then + prove_cached "$WORK/cli_B" "$PROOF_B" "$SHA_B" || exit 1 + prove_cached "$WORK/cli_A" "$PROOF_A" "$SHA_A" || exit 1 -# Proof sizes (bytes) for the Proof size row. -SIZE_B="$(wc -c < "$PROOF_B" | tr -d '[:space:]')" -SIZE_A="$(wc -c < "$PROOF_A" | tr -d '[:space:]')" + # Proof sizes (bytes) for the Proof size row. + SIZE_B="$(wc -c < "$PROOF_B" | tr -d '[:space:]')" + SIZE_A="$(wc -c < "$PROOF_A" | tr -d '[:space:]')" +fi # Decide whether both sides can VERIFY one shared proof (baseline's) — tightest # timing precision — or must verify their own. In auto mode, distinguish a real @@ -454,10 +515,15 @@ else: PY } -decide_mode "$PROOF_B" "$PROOF_A" -run_abba "$N_PAIRS" "$WORK/pairs.csv" || exit 1 -MONO_MODE="$MODE" -MONO_NOTE="$PER_SIDE_NOTE" +MONO_SKIP="" +if [ "$RUN_MONO" = "1" ]; then + decide_mode "$PROOF_B" "$PROOF_A" + run_abba "$N_PAIRS" "$WORK/pairs.csv" || exit 1 + MONO_MODE="$MODE" + MONO_NOTE="$PER_SIDE_NOTE" +else + MONO_SKIP="not run: a real block exceeds the monolithic memory ceiling, so only the continuation arm below applies" +fi # Render the monolithic report NOW, not at the end with the continuation one. Both are # still emitted together at the end (the extractor needs them contiguous after the # anchor), but computing this one here means it also survives the run dying during the @@ -465,9 +531,13 @@ MONO_NOTE="$PER_SIDE_NOTE" # step timeout or an OOM kill takes the whole process down, and CI would then post # "Run failed" and throw away a monolithic verdict it had already measured. bench-verify.yml # falls back to this file in that case. -MONO_TITLE="ethrex 20-tx block · monolithic · blowup=2, 219 queries" -MONO_REPORT="$(print_stats "$WORK/pairs.csv" "$MONO_TITLE" \ - "$SIZE_A" "$SIZE_B" "$MONO_MODE" "$MONO_NOTE")" +MONO_TITLE="$BLOCK_LABEL · monolithic · blowup=2, 219 queries" +if [ -n "$MONO_SKIP" ]; then + MONO_REPORT="$(printf '#### %s\n\n_(Monolithic arm %s.)_\n' "$MONO_TITLE" "$MONO_SKIP")" +else + MONO_REPORT="$(print_stats "$WORK/pairs.csv" "$MONO_TITLE" \ + "$SIZE_A" "$SIZE_B" "$MONO_MODE" "$MONO_NOTE")" +fi printf '%s\n' "$MONO_REPORT" > "$WORK/result_mono.txt" # --- 3b. Same measurement over a CONTINUATION bundle of the same block --------- @@ -525,7 +595,7 @@ if [ -z "$CONT_SKIP" ]; then fi fi fi -CONT_TITLE="ethrex 20-tx block · continuations, epoch 2^$CONT_EPOCH_LOG2$CONT_EPOCHS · blowup=2, 219 queries" +CONT_TITLE="$BLOCK_LABEL · continuations, epoch 2^$CONT_EPOCH_LOG2$CONT_EPOCHS · blowup=2, 219 queries" printf '%s\n' "$MONO_REPORT" if [ -n "$CONT_SKIP" ]; then echo diff --git a/scripts/perf_diff.sh b/scripts/perf_diff.sh index ddadf53fd..a6c0380be 100755 --- a/scripts/perf_diff.sh +++ b/scripts/perf_diff.sh @@ -13,6 +13,23 @@ # # USAGE (on the bench server): # scripts/perf_diff.sh REF_A [REF_B=origin/main] +# Env: WORKLOAD=synthetic|real (default synthetic) picks the block to profile; +# EPOCH_SIZE_LOG2= (default 22) sizes the epoch, WORKLOAD=real only. +# 22 is the calibrated bench-runner tier, matching /bench; use 23 on a +# 128 GiB box (tooling/ethrex-block-converter/README.md, "Choosing the epoch size"). +# +# Pick the workload that matches the run you are localizing, because the symbol +# mix follows the block: the synthetic default is 20 plain transfers (8.73M cycles, +# 411 keccak calls, 80 ecsm calls) and a real block inverts that (50.78M cycles, +# 10,478 keccak, 116 ecsm), so a hot symbol in one need not be hot in the other. +# Both counts are from the same guest ELF (merge fdb92f67, main @ 9ccdaf2, clang 21); +# they move with guest optimisation (#861's thin LTO) and ~2% with the clang major, so +# pin the ELF when quoting one. +# +# WORKLOAD=real also switches to a continuation prove (monolithic would need ~240 GB +# at that trace length), which is ~4-5 min per recording — five recordings, so budget +# ~25 min, plus ~1.2 GB of disk per bundle and ~32 GiB of RAM at the default epoch. +# # Produces: # - two perf-diff tables (recorded twice per side, interleaved B A B A — # symbols whose delta repeats across both tables are real, one-off @@ -29,9 +46,17 @@ if [ $# -lt 1 ]; then fi REF_A="$1" REF_B="${2:-origin/main}" +WORKLOAD="${WORKLOAD:-synthetic}" +# 2^22: the calibrated tier for the bench server this script targets, same as +# /bench's real-block arm. Memory picks it, not speed — 32.2 GiB fits a >=64 GiB box with ~50% +# headroom where 2^23's 60 GiB does not. +EPOCH_SIZE_LOG2="${EPOCH_SIZE_LOG2:-22}" +case "$WORKLOAD" in + synthetic|real) ;; + *) echo "ERROR: WORKLOAD must be 'synthetic' or 'real' (got '$WORKLOAD')." >&2; exit 2 ;; +esac ELF_REL="executor/program_artifacts/rust/ethrex.elf" -INPUT_REL="executor/tests/ethrex_bench_20.bin" WORK="/tmp/perf_diff" WT="/tmp/perf_diff_wt" PROOF="/tmp/perf_diff_proof.bin" @@ -39,9 +64,30 @@ PROOF="/tmp/perf_diff_proof.bin" ROOT="$(git rev-parse --show-toplevel)" cd "$ROOT" +# The real block's name lives in the Makefile and nowhere else, so repointing the +# benchmark to a different block never needs an edit here. +if [ "$WORKLOAD" = "real" ]; then + INPUT_REL="$(make -s print-real-block-fixture)" + CONT_ARGS="--continuations --epoch-size-log2 $EPOCH_SIZE_LOG2" +else + INPUT_REL="executor/tests/ethrex_bench_20.bin" + CONT_ARGS="" +fi + command -v perf >/dev/null 2>&1 || { echo "ERROR: perf not installed (linux-tools)." >&2; exit 1; } [ -f "$ELF_REL" ] || { echo "ERROR: missing $ELF_REL — run bench_abba.sh once (it builds the guest)." >&2; exit 1; } -[ -f "$INPUT_REL" ] || { echo "ERROR: missing $INPUT_REL — run bench_abba.sh once (it builds the fixture)." >&2; exit 1; } +if [ ! -f "$INPUT_REL" ]; then + if [ "$WORKLOAD" = "real" ]; then + # ~1 MB, gitignored, never in a fresh checkout; fetch rather than abort. This is + # a URL + sha256 download, not a build. + echo "==> Fetching ethrex real-block fixture (missing)" + make ethrex-real-block-fixture + else + echo "ERROR: missing $INPUT_REL — run bench_abba.sh once (it builds the fixture)." >&2 + exit 1 + fi +fi +echo "==> Workload: $WORKLOAD ($INPUT_REL${CONT_ARGS:+, continuations epoch 2^$EPOCH_SIZE_LOG2})" echo "==> Refs" git fetch origin --quiet || echo "WARNING: 'git fetch origin' failed -- resolving against possibly-stale local refs." >&2 @@ -86,14 +132,16 @@ fi # --- Record: warmup, then B A B A (interleaved so drift hits both sides) --- record() { # $1=binary $2=out.data + # shellcheck disable=SC2086 # CONT_ARGS is a deliberate multi-word flag list (empty when synthetic) perf record -F 599 -o "$WORK/$2" -- \ - "$WORK/$1" prove "$ELF_REL" --private-input "$INPUT_REL" -o "$PROOF" --time \ + "$WORK/$1" prove "$ELF_REL" --private-input "$INPUT_REL" $CONT_ARGS -o "$PROOF" --time \ >"$WORK/$2.log" 2>&1 rm -f "$PROOF" grep -o 'Proving time: [0-9.]*' "$WORK/$2.log" || true } echo "==> Warmup (B, not recorded)" -"$WORK/cli_B" prove "$ELF_REL" --private-input "$INPUT_REL" -o "$PROOF" --time >/dev/null 2>&1 +# shellcheck disable=SC2086 +"$WORK/cli_B" prove "$ELF_REL" --private-input "$INPUT_REL" $CONT_ARGS -o "$PROOF" --time >/dev/null 2>&1 rm -f "$PROOF" echo "==> Recording B (main), run 1"; record cli_B B1.data echo "==> Recording A (PR), run 1"; record cli_A A1.data diff --git a/scripts/render_bench_comment.js b/scripts/render_bench_comment.js new file mode 100644 index 000000000..eee39f231 --- /dev/null +++ b/scripts/render_bench_comment.js @@ -0,0 +1,99 @@ +#!/usr/bin/env node +// +// render_bench_comment.js — renders benchmark-pr.yml's PR-comment script offline. +// +// WHY: that comment is ~200 lines of JS embedded in YAML, and the only other way to +// see its output is to push and run a real benchmark on the shared bench server +// (~15-20 min of occupancy). This extracts the `script:` block from the workflow, +// runs it against synthetic env values, and prints the markdown — so a wording or +// formatting change is checked in a second. +// +// It renders; it does not assert. Read the output. The scenarios below are the ones +// that have actually broken before: sign handling on the verdict lines, the +// no-baseline and block-mismatch paths, and the fixture-unavailable footer. +// +// USAGE: node scripts/render_bench_comment.js +// +// Add a scenario by adding a key to `scenarios`; each is the env the comment step +// would see. Keep one per branch of the renderer. +const fs = require('fs'); +const path = require('path'); +const wfPath = path.join(__dirname, '..', '.github', 'workflows', 'benchmark-pr.yml'); +const wf = fs.readFileSync(wfPath, 'utf8'); +const lines = wf.split('\n'); +const start = lines.map(l => l.trim() === 'script: |').lastIndexOf(true); +if (start < 0) throw new Error('script block not found'); +const indent = lines[start].match(/^\s*/)[0].length + 2; +const body = []; +for (let i = start + 1; i < lines.length; i++) { + const l = lines[i]; + if (l.trim() === '') { body.push(''); continue; } + if (l.match(/^\s*/)[0].length < indent) break; + body.push(l.slice(indent)); +} +const src = body.join('\n'); + +const captured = []; +const github = { rest: { issues: { + listComments: async () => ({ data: [] }), + createComment: async (a) => captured.push(a.body), + updateComment: async (a) => captured.push(a.body), +}}}; +const context = { repo: { owner: 'o', repo: 'r' }, issue: { number: 1 } }; + +const REAL = { + COMMIT_SHA: 'c4e42b2900', REAL_EPOCH_LOG2: '22', BASELINE_SRC: 'cached', + PR_REAL_TIME: '281.400', PR_REAL_PEAK: '32980', PR_REAL_EPOCHS: '13', + PR_REAL_INPUT: 'ethrex_mainnet_25368371.bin', + REAL_RUNS: '3', REAL_TIME_SPREAD: '1.9', REAL_ALL_TIMES: '279.1/281.4/284.4', +}; +const scenarios = { + 'A: normal /bench — real block vs cached baseline': { + ...REAL, BASE_REAL_TIME: '288.900', BASE_REAL_PEAK: '32975', + REAL_TIME_DIFF: '-7.500', REAL_TIME_PCT: '-2.6', REAL_PEAK_DIFF: '5', REAL_PEAK_PCT: '0.0', + }, + 'B: clear improvement': { + ...REAL, BASE_REAL_TIME: '340.000', BASE_REAL_PEAK: '32975', + REAL_TIME_DIFF: '-58.600', REAL_TIME_PCT: '-17.2', REAL_PEAK_DIFF: '5', REAL_PEAK_PCT: '0.0', + }, + 'C: clear regression': { + ...REAL, BASE_REAL_TIME: '240.000', BASE_REAL_PEAK: '32975', + REAL_TIME_DIFF: '+41.400', REAL_TIME_PCT: '17.3', REAL_PEAK_DIFF: '5', REAL_PEAK_PCT: '0.0', + }, + 'D: unresolved middle band (3-10%)': { + ...REAL, BASE_REAL_TIME: '300.000', BASE_REAL_PEAK: '32975', + REAL_TIME_DIFF: '-18.600', REAL_TIME_PCT: '-6.2', REAL_PEAK_DIFF: '5', REAL_PEAK_PCT: '0.0', + }, + 'E: bad spread': { + ...REAL, REAL_TIME_SPREAD: '11.4', REAL_ALL_TIMES: '265.0/281.4/297.1', + BASE_REAL_TIME: '288.900', BASE_REAL_PEAK: '32975', + REAL_TIME_DIFF: '-7.500', REAL_TIME_PCT: '-2.6', REAL_PEAK_DIFF: '5', REAL_PEAK_PCT: '0.0', + }, + 'F: no baseline yet': { ...REAL, BASELINE_SRC: 'built from main' }, + 'G: baseline measured a different block': { + ...REAL, BASE_REAL_TIME: '288.900', BASE_REAL_PEAK: '32975', + REAL_MISMATCH: 'ethrex_hoodi_1265656.bin', + }, + 'H: fixture unavailable': { COMMIT_SHA: 'c4e42b2900', REAL_EPOCH_LOG2: '22', BASELINE_SRC: 'cached' }, + 'I: with growth sweep (push-style)': { + ...REAL, BASE_REAL_TIME: '288.900', BASE_REAL_PEAK: '32975', + REAL_TIME_DIFF: '-7.500', REAL_TIME_PCT: '-2.6', REAL_PEAK_DIFF: '5', REAL_PEAK_PCT: '0.0', + PR_GROWTH_HEAPS: '18756/26966/34748/43896/50431', + PR_GROWTH_TIMES: '10.685/13.434/18.854/21.094/24.737', + PR_GROWTH_SLOPE: '2007', PR_GROWTH_R2: '0.9981', + BASE_GROWTH_HEAPS: '18700/26900/34700/43800/50217', + BASE_GROWTH_SLOPE: '2000', BASE_GROWTH_R2: '0.9980', + GROWTH_SLOPE_DIFF: '7', GROWTH_SLOPE_PCT: '0.4', + }, +}; + +(async () => { + for (const [name, env] of Object.entries(scenarios)) { + captured.length = 0; + process.env = { ...env }; + const fn = new Function('github', 'context', 'require', `return (async () => { ${src} })()`); + await fn(github, context, require); + console.log(`\n${'='.repeat(72)}\n### ${name}\n${'='.repeat(72)}`); + console.log(captured[0]); + } +})(); diff --git a/tooling/ethrex-block-converter/.gitignore b/tooling/ethrex-block-converter/.gitignore new file mode 100644 index 000000000..6d50c2d01 --- /dev/null +++ b/tooling/ethrex-block-converter/.gitignore @@ -0,0 +1,4 @@ +/target +# Downloaded ethrex-replay cache JSONs (~1.5 MB each), fetched on demand by +# `make ethrex-real-block-fixture`. +/caches/ diff --git a/tooling/ethrex-block-converter/Cargo.lock b/tooling/ethrex-block-converter/Cargo.lock new file mode 100644 index 000000000..a8268a857 --- /dev/null +++ b/tooling/ethrex-block-converter/Cargo.lock @@ -0,0 +1,3344 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "educe", + "fnv", + "hashbrown 0.15.5", + "itertools 0.13.0", + "num-bigint", + "num-integer", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "arrayvec", + "digest", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash", + "ark-ff", + "ark-serialize", + "ark-std", + "educe", + "fnv", + "hashbrown 0.15.5", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "arrayvec", + "digest", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.7", +] + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitcoin-consensus-encoding" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "207311705279250ba465076a1bac4b1ac982855fff73fc5f67e22158ac58cdc9" +dependencies = [ + "bitcoin-internals", + "hex-conservative 1.2.0", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d573f4cf32996a8dce612e4348cece65a241f1882ed594047c9ba348e8869fa5" + +[[package]] +name = "bitcoin-io" +version = "0.1.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb5de036369d1ac59d3c1819ebc4d850f89466f5401c571a285b6ed564a4cb78" +dependencies = [ + "bitcoin-consensus-encoding", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" +dependencies = [ + "bitcoin-io", + "hex-conservative 0.2.2", +] + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "bitvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "bls12_381" +version = "0.8.0" +source = "git+https://github.com/lambdaclass/bls12_381?branch=expose-affine-constructors#78cad0378b17fc3157b83f514be192bf46edf9a1" +dependencies = [ + "digest", + "ff", + "group", + "pairing", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "blst" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20659f9bbee16cbbd2f7393e40ab6309f5a98f76a2eb57a995ec508b72387fe" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + +[[package]] +name = "bytecheck" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0caa33a2c0edca0419d15ac723dff03f1956f7978329b1e3b5fdaaaed9d3ca8b" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "rancor", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89385e82b5d1821d2219e0b095efa2cc1f246cbf99080f3be46a1a85c0d392d9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +dependencies = [ + "serde", +] + +[[package]] +name = "c-kzg" +version = "2.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d04308254695569fdb9bfe3bacc1c91837a670d0806605eb82d63748fbd3a6" +dependencies = [ + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] + +[[package]] +name = "cc" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "concat-kdf" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d72c1252426a83be2092dd5884a5f6e3b8e7180f6891b6263d2c21b92ec8816" +dependencies = [ + "digest", +] + +[[package]] +name = "const-default" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const_format" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" +dependencies = [ + "const_format_proc_macros", + "konst", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "ctrlc" +version = "3.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" +dependencies = [ + "dispatch2", + "nix", + "windows-sys", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn 2.0.119", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "embedded-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f2de9133f68db0d4627ad69db767726c99ff8585272716708227008d3f1bddd" +dependencies = [ + "const-default", + "critical-section", + "linked_list_allocator", + "rlsf", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "enum-ordinalize" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07f808d588c10e464ea6f7d3eaed500049eff30aaac103460f61828c2d65b3eb" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e528e2d34ba8a67a1a650b86beae8ef69fc5fdb638016f386b973226590432" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "ethbloom" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c321610643004cf908ec0f5f2aa0d8f1f8e14b540562a2887a1111ff1ecbf7b" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab15ed80916029f878e0267c3a9f92b67df55e79af370bf66199059ae2b4ee3" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + +[[package]] +name = "ethrex-block-converter" +version = "0.1.0" +dependencies = [ + "ethrex-common", + "ethrex-config", + "ethrex-guest-program", + "k256", + "lambda-vm-ethrex-crypto", + "rkyv", + "serde", + "serde_json", + "sha2", +] + +[[package]] +name = "ethrex-blockchain" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "bytes", + "crossbeam", + "ethrex-common", + "ethrex-crypto", + "ethrex-metrics", + "ethrex-rlp", + "ethrex-storage", + "ethrex-trie", + "ethrex-vm", + "rayon", + "rustc-hash", + "thiserror 2.0.19", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "ethrex-common" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "bytes", + "crc32fast", + "ethereum-types", + "ethrex-crypto", + "ethrex-rlp", + "ethrex-trie", + "hex", + "hex-literal", + "hex-simd", + "indexmap 2.14.0", + "lazy_static", + "libc", + "lru", + "once_cell", + "rkyv", + "rustc-hash", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.19", + "tracing", +] + +[[package]] +name = "ethrex-config" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "ethrex-common", + "ethrex-p2p", + "hex", + "serde", + "serde_json", +] + +[[package]] +name = "ethrex-crypto" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "ark-bn254", + "ark-ec", + "ark-ff", + "bls12_381", + "c-kzg", + "ethereum-types", + "ff", + "hex-literal", + "k256", + "malachite", + "num-bigint", + "p256", + "ripemd", + "sha2", + "thiserror 2.0.19", + "tiny-keccak", +] + +[[package]] +name = "ethrex-guest-program" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "bytes", + "ethereum-types", + "ethrex-common", + "ethrex-crypto", + "ethrex-l2-common", + "ethrex-rlp", + "ethrex-vm", + "hex", + "k256", + "lambda-vm-syscalls", + "rkyv", + "serde", + "serde_with", + "thiserror 2.0.19", +] + +[[package]] +name = "ethrex-l2-common" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "bytes", + "ethereum-types", + "ethrex-common", + "ethrex-crypto", + "k256", + "lambdaworks-crypto", + "rkyv", + "secp256k1", + "serde", + "serde_with", + "thiserror 2.0.19", + "tracing", +] + +[[package]] +name = "ethrex-levm" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "bytes", + "derive_more", + "ethrex-common", + "ethrex-crypto", + "ethrex-rlp", + "malachite", + "rustc-hash", + "serde", + "strum", + "thiserror 2.0.19", +] + +[[package]] +name = "ethrex-metrics" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "ethrex-common", + "serde", + "serde_json", + "thiserror 2.0.19", + "tracing-subscriber", +] + +[[package]] +name = "ethrex-p2p" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "aes", + "aes-gcm", + "bytes", + "concat-kdf", + "crossbeam", + "ctr", + "ethereum-types", + "ethrex-blockchain", + "ethrex-common", + "ethrex-crypto", + "ethrex-rlp", + "ethrex-storage", + "ethrex-trie", + "futures", + "hex", + "hkdf", + "hmac", + "indexmap 2.14.0", + "lazy_static", + "lru", + "prometheus", + "rand 0.8.7", + "rayon", + "rustc-hash", + "secp256k1", + "serde", + "sha2", + "snap", + "spawned-concurrency", + "spawned-rt", + "thiserror 2.0.19", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + +[[package]] +name = "ethrex-rlp" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "bytes", + "ethereum-types", + "thiserror 2.0.19", +] + +[[package]] +name = "ethrex-storage" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "anyhow", + "bytes", + "ethrex-common", + "ethrex-crypto", + "ethrex-rlp", + "ethrex-trie", + "fastbloom", + "lru", + "rayon", + "rustc-hash", + "serde", + "serde_json", + "thiserror 2.0.19", + "tokio", + "tracing", +] + +[[package]] +name = "ethrex-trie" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "anyhow", + "bytes", + "crossbeam", + "ethereum-types", + "ethrex-crypto", + "ethrex-rlp", + "lazy_static", + "rayon", + "rkyv", + "rustc-hash", + "serde", + "thiserror 2.0.19", +] + +[[package]] +name = "ethrex-vm" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "bytes", + "derive_more", + "dyn-clone", + "ethrex-common", + "ethrex-crypto", + "ethrex-levm", + "ethrex-rlp", + "rustc-hash", + "serde", + "thiserror 2.0.19", + "tracing", +] + +[[package]] +name = "fastbloom" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" +dependencies = [ + "getrandom 0.3.4", + "libm", + "rand 0.9.5", + "siphasher", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "bitvec", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.7", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + +[[package]] +name = "futures-executor" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" + +[[package]] +name = "futures-macro" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "futures-sink" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" + +[[package]] +name = "futures-task" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex-conservative" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35431185f361ccf3ffc58254628af5f1f5d5f28531da2e02e5d6c82bbc282a10" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hex-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f7685beb53fc20efc2605f32f5d51e9ba18b8ef237961d1760169d2290d3bee" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "impl-codec" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d40b9d5e17727407e55028eafc22b2dc68781786e6d7eb8a21103f5058e3a14" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54ed8ad1f3877f7e775b8cbf30ed1bd3209a95401817f19a0eb4402d13f8cf90" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a143eada6a1ec4aefa5049037a26a6d597bfd64f8c026d07b77133e02b7dd0b" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "konst" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" +dependencies = [ + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + +[[package]] +name = "lambda-vm-ethrex-crypto" +version = "0.1.0" +dependencies = [ + "ethrex-crypto", + "k256", + "lambda-vm-syscalls", +] + +[[package]] +name = "lambda-vm-syscalls" +version = "0.1.0" +dependencies = [ + "embedded-alloc", + "getrandom 0.2.17", + "getrandom 0.3.4", + "lazy_static", + "rand 0.9.5", + "riscv", + "thiserror 1.0.69", +] + +[[package]] +name = "lambdaworks-crypto" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58b1a1c1102a5a7fbbda117b79fb3a01e033459c738a3c1642269603484fd1c1" +dependencies = [ + "lambdaworks-math", + "rand 0.8.7", + "rand_chacha 0.3.1", + "serde", + "sha2", + "sha3", +] + +[[package]] +name = "lambdaworks-math" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "018a95aa873eb49896a858dee0d925c33f3978d073c64b08dd4f2c9b35a017c6" +dependencies = [ + "getrandom 0.2.17", + "num-bigint", + "num-traits", + "rand 0.8.7", + "serde", + "serde_json", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "linked_list_allocator" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b23ac50abb8261cb38c6e2a7192d3302e0836dac1628f6a93b82b4fad185897" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "lru" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "malachite" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec410515e231332b14cd986a475d1c3323bcfa4c7efc038bfa1d5b410b1c57e4" +dependencies = [ + "malachite-base", + "malachite-nz", + "malachite-q", +] + +[[package]] +name = "malachite-base" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c738d3789301e957a8f7519318fcbb1b92bb95863b28f6938ae5a05be6259f34" +dependencies = [ + "hashbrown 0.15.5", + "itertools 0.14.0", + "libm", + "ryu", +] + +[[package]] +name = "malachite-nz" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1707c9a1fa36ce21749b35972bfad17bbf34cf5a7c96897c0491da321e387d3b" +dependencies = [ + "itertools 0.14.0", + "libm", + "malachite-base", + "wide", +] + +[[package]] +name = "malachite-q" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d764801aa4e96bbb69b389dcd03b50075345131cd63ca2e380bca71cc37a3675" +dependencies = [ + "itertools 0.14.0", + "malachite-base", + "malachite-nz", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "munge" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e17401f259eba956ca16491461b6e8f72913a0a114e39736ce404410f915a0c" +dependencies = [ + "munge_macro", +] + +[[package]] +name = "munge_macro" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", +] + +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "primitive-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prometheus" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ca5326d8d0b950a9acd87e6a3f94745394f62e4dae1b1ee22b2bc0c394af43a" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "memchr", + "parking_lot", + "protobuf", + "thiserror 2.0.19", +] + +[[package]] +name = "protobuf" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65a1d4ddae7d8b5de68153b48f6aa3bba8cb002b243dbdbc55a5afbc98f99f4" +dependencies = [ + "once_cell", + "protobuf-support", + "thiserror 1.0.69", +] + +[[package]] +name = "protobuf-support" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6" +dependencies = [ + "thiserror 1.0.69", +] + +[[package]] +name = "ptr_meta" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9a0cf95a1196af61d4f1cbdab967179516d9a4a4312af1f31948f8f6224a79" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rancor" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daff8b7b3ccf5f7ba270b3e7a0a4d4c701c5797e38dec27c7e2c3dbb830fed1c" +dependencies = [ + "ptr_meta", +] + +[[package]] +name = "rand" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "regex-automata" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rend" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "663ba70707f96e871406fe10d68128412e619b06d1d47cb91c3a4c6501176240" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest", +] + +[[package]] +name = "riscv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05cfa3f7b30c84536a9025150d44d26b8e1cc20ddf436448d74cd9591eefb25" +dependencies = [ + "critical-section", + "embedded-hal", + "paste", + "riscv-macros", + "riscv-pac", +] + +[[package]] +name = "riscv-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d323d13972c1b104aa036bc692cd08b822c8bbf23d79a27c526095856499799" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "riscv-pac" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436" + +[[package]] +name = "rkyv" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73389e0c99e664f919275ab5b5b0471391fe9a8de61e1dff9b1eaf56a90f16e3" +dependencies = [ + "bytecheck", + "bytes", + "hashbrown 0.17.1", + "indexmap 2.14.0", + "munge", + "ptr_meta", + "rancor", + "rend", + "rkyv_derive", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d2ed0b54125315fb36bd021e82d314d1c126548f871634b483f46b31d13cac6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "rlp" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa24e92bb2a83198bb76d661a71df9f7076b8c420b8696e4d3d97d50d94479e3" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rlsf" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07393724337be2ee43a9d86164df4505746874a3fa65913374bc6d6a92314362" +dependencies = [ + "cfg-if", + "const-default", + "libc", + "rustversion", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes", + "rand 0.8.7", + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_with" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +dependencies = [ + "base64", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.2", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "snap" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "199905e6153d6405f9728fe44daace35f8f837bbf830bb6e85fbd5828709a886" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "spawned-concurrency" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc21166874e8cd7584ea795c223303160461f0bb1b571bc23e92ca2abb7c5149" +dependencies = [ + "futures", + "pin-project-lite", + "spawned-macros", + "spawned-rt", + "thiserror 2.0.19", + "tracing", +] + +[[package]] +name = "spawned-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d64742b41741dfebd5b5ba4dbc4cbc5cc91f4a2cf8107191007d64295682973" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "spawned-rt" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e270e6606a118708120671f2d171316762fa832cab73699c714c23aaafe6eb" +dependencies = [ + "ctrlc", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl 2.0.19", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "time" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tokio-stream" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "futures-util", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "uuid" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zerocopy" +version = "0.8.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/tooling/ethrex-block-converter/Cargo.toml b/tooling/ethrex-block-converter/Cargo.toml new file mode 100644 index 000000000..b1796fa60 --- /dev/null +++ b/tooling/ethrex-block-converter/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "ethrex-block-converter" +version = "0.1.0" +edition = "2024" + +# Detached workspace: keeps the heavy ethrex host deps out of the main build. +[workspace] + +[dependencies] +# Pinned to the SAME ethrex rev as the guest (open LambdaVM-backend PR branch) +# so the generated ProgramInput rkyv layout matches what the guest deserializes. +ethrex-common = { git = "https://github.com/lambdaclass/ethrex.git", rev = "156cb8d6a3974f411d71622eecd1b249ee37ff1c", package = "ethrex-common", default-features = false } +ethrex-config = { git = "https://github.com/lambdaclass/ethrex.git", rev = "156cb8d6a3974f411d71622eecd1b249ee37ff1c", package = "ethrex-config", default-features = false } +# Matches the guest's own declaration (executor/programs/rust/ethrex/Cargo.toml). +# +# CAVEAT: this line alone does NOT reproduce the guest's precompile surface. The +# `ethrex-config` dep above pulls `ethrex-p2p`, whose `default = ["c-kzg"]` +# propagates to `ethrex-crypto/c-kzg` — and `default-features = false` cannot +# switch it off, because ethrex's own workspace declares `ethrex-p2p` with its +# defaults on. So this graph links a working c-kzg (and malachite modexp, and +# `ark-ff/asm` BN254) that the guest does not have; verify with +# `cargo tree -e features -i ethrex-crypto`. Closing that gap means dropping +# `ethrex-config` and sourcing `ChainConfig` another way — see the README. +ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "156cb8d6a3974f411d71622eecd1b249ee37ff1c", package = "ethrex-guest-program", default-features = false, features = ["lambdavm"] } + +# Exact pin: the fixture writer and the guest/executor readers must agree on the +# rkyv layout. Keep this in sync with the guest +# (executor/programs/rust/ethrex/Cargo.toml) and the other two detached ethrex +# tool workspaces (tooling/ethrex-fixtures, tooling/ethrex-tests). The `executor` +# crate itself declares no rkyv — the guest is what deserializes ProgramInput. +rkyv = { version = "=0.8.16", features = ["std", "unaligned"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +# Not used by this crate's code directly — declared to pin k256's feature set. +# `lambda-vm-ethrex-crypto` (a dev-dependency) is otherwise the only thing asking +# for `expose-field`, and under resolver 3 dev-dependency features are not unified +# into builds that exclude dev-dependencies: `cargo run` (the +# `make ethrex-real-block-fixture` step) and the `cargo test` that follows it in +# CI would resolve two different k256 feature sets, recompiling k256 and every +# ethrex crate above it in the same job. Declaring the features here puts them in +# both resolutions. Keep in sync with crypto/ethrex-crypto's k256 line; verify: +# cargo tree -e features,no-dev -i k256 # must list expose-field +# cargo tree -e features -i k256 # must match the above +k256 = { version = "=0.13.4", default-features = false, features = ["arithmetic", "expose-field"] } + +[dev-dependencies] +# The `Crypto` impl the guest injects. Off riscv64 its keccak and ECSM paths both +# fall back to software, so this exercises the guest's *fallback* crypto semantics +# — not the accelerators themselves. (Its k256 feature needs are mirrored in +# [dependencies] above — see the comment there.) +lambda-vm-ethrex-crypto = { path = "../../crypto/ethrex-crypto" } +# No feature delta against the rest of the graph (sha2/std is already on), so this +# one can stay a dev-dependency without forcing the rebuild described above. +sha2 = "0.10" + +# `ethrex-guest-program`'s `lambdavm` feature pins `lambda-vm-syscalls` to an +# older commit; override it with our working-tree copy, as the guest does. +[patch."https://github.com/yetanotherco/lambda_vm.git"] +lambda-vm-syscalls = { path = "../../syscalls" } diff --git a/tooling/ethrex-block-converter/README.md b/tooling/ethrex-block-converter/README.md new file mode 100644 index 000000000..78dc4fd2e --- /dev/null +++ b/tooling/ethrex-block-converter/README.md @@ -0,0 +1,504 @@ +# ethrex-block-converter + +Converts a **real Ethereum block** into a serialized `ProgramInput` `.bin` for +the lambda-vm ethrex guest, reading an [`ethrex-replay`][replay] cache JSON. + +This complements `tooling/ethrex-fixtures`, which builds *synthetic* blocks of N +plain ETH transfers. Those are cheap and deterministic but not representative: +they execute no contract code, touch a genesis state trie only a couple of +levels deep, and carry no bytecode in the witness. This tool produces the +opposite — a block that actually looks like Ethereum. + +Figures below are for the **current default** real block, mainnet 25368371; a +repoint replaces them (see [Adopting a different block](#adopting-a-different-block)). +All are measured. + +| | `ethrex_bench_20.bin` (synthetic) | `ethrex_mainnet_25368371.bin` (real, default) | +|---|---|---| +| gas used | 420,000 | **2,428,684** | +| transactions | 20 (all plain transfers) | 29 (real mix) | +| serialized size | 32,766 B | 1,110,156 B | +| cycles | 8,734,622 | **50,781,557** | +| keccak / ecsm calls | 411 / 80 | **10,478** / 116 | +| keccaks per ecrecover | 5.1 | **90** | + +Note the real block uses ~5.8x the gas and costs ~5.8x the cycles here, and that it +inverts the crypto mix: the synthetic block is ecrecover-bound, the real one keccak- +and trie-bound. That inversion is the point — a prover change can move the two +numbers in opposite directions. (The gas and cycle ratios agreeing is a coincidence +of this pair, not a rule: cycles/gas is 20.9 here and ranges ~21–38 across the +candidate blocks.) + +**Pin the ELF whenever you quote a cycle count.** Counts above were measured on this +branch at merge `fdb92f67` (main @ `9ccdaf2`), guest built with **clang 21.1.8**. +Two things move them: +- **Guest optimisation.** #861 gave the guest thin LTO; this block read 74,819,518 + on a mid-July pre-LTO ELF, so anything quoting ~74.8M or ~65.6M is **superseded**. +- **clang major version**, by ~2%. The guest embeds C (secp256k1-sys) and the + Makefile pins target flags but not the compiler, so `cc` picks up whatever `clang` + is on PATH. The RTX 5090 box (clang-18) measured **50,713,534** for this block on + main @ `9ccdaf2` — 0.13% below the number above, same commit, different compiler. + +Why this block specifically: it was the **only** block in a 90-day Dune sweep that +matched the shape constraints in the 1.6–2.6M gas band — exactly 2 heavy +transactions, no single whale transaction dominating, and a sane plain-transfer +share. Its composition is 11.94 tx/Mgas, 44.3% of gas in heavy transactions, 22.5% +in the top transaction, p50 transaction gas 41,297. A block that is merely *large* +is easy to find; one that is structurally typical is not. + +The synthetic column is `ethrex_bench_20.bin` as the benchmark scripts actually +generate it — `ethrex-fixtures 20 … distinct`, i.e. 20 distinct genesis-funded +senders to 20 distinct recipients (`scripts/bench_verify.sh`, +`scripts/bench_recursion_scaling.sh`). The same block in `same` mode (one sender, +one recipient) serializes to 16,811 B. + +Block 25368371 is **verified to run on the guest's precompile surface** (see +[Validation](#validation)); it needs no accelerator we don't have. Any +replacement block must clear the same check — that is what makes it usable, not +just realistic. + +## Getting the fixture + +The fixture is **fetched, not built**: + +```bash +make ethrex-real-block-fixture +``` + +That downloads the finished `.bin` from `ETHREX_REAL_BLOCK_FIXTURE_URL` and +verifies `ETHREX_REAL_BLOCK_FIXTURE_SHA256` before moving it into place — +the same contract as `prepare-sysroot`. The file is gitignored (~1 MB; see +`executor/.gitignore`), and a corrupt or interrupted download is discarded rather +than left looking valid. + +The digest of whatever is already on disk is re-checked on every invocation, not +only when the file is missing — so a stale copy left over from a re-upload under the +same block number, a corrupted file, or a hand-placed one is all caught and +re-fetched. That check is the reason these are phony targets rather than file rules. + +Artifacts live in the **[`bench-fixtures-v1`][release]** release on +`yetanotherco/lambda_vm`, fetched unauthenticated: + +| asset | sha256 | read by | +|---|---|---| +| `ethrex_mainnet_25368371.bin` | `61eba49b…` | every benchmark (**current default**) | +| `cache_mainnet_25368371.json` | `7aa88a5f…` | `regen-real-block-fixture` | +| `ethrex_mainnet_25453112.bin` | `0298663d…` | alternate candidate | +| `cache_mainnet_25453112.json` | `20ffbbc1…` | alternate candidate | + +Each block has two assets: the fixture and the **cache** it was converted from +(`make ethrex-real-block-cache`, ~2 MB, same verify-then-move contract). Only +`regen-real-block-fixture` reads the cache. Note it is *not* the cache the +converter's own tests use — see [Validation](#validation). + +This crate is not on the fixture's path at all. Fetching a verified binary takes the +converter, the ~335-package ethrex host dependency tree and an ethrex-replay `rev` +pin off the critical path of everyone who just wants to run a benchmark. It also +decouples the benchmark block from what upstream hosts: ethrex-replay publishes a +cache for Hoodi and nothing else, so any mainnet block is unreachable by the +convert-locally route — producing its cache takes ~4 minutes and ~700 calls against +an archive RPC — and trivial by this one. + +[release]: https://github.com/yetanotherco/lambda_vm/releases/tag/bench-fixtures-v1 + +## Regenerating the fixture (ethrex rev bumps) + +Needed roughly twice a year, when the guest's ethrex `rev` moves and the rkyv +layout changes with it: + +```bash +make regen-real-block-fixture # fetches that block's cache, rebuilds the .bin +sha256sum "$(make -s print-real-block-fixture)" +``` + +Then upload the result and update `ETHREX_REAL_BLOCK_FIXTURE_SHA256` and its URL. + +Directly, against any cache file: + +```bash +cd tooling/ethrex-block-converter +cargo run --release -- +``` + +Output is deterministic for a given cache file: + +```text +wrote ../../executor/tests/ethrex_mainnet_25368371.bin (1110156 bytes): 1 block(s) \ + from mainnet starting at #25368371, 29 transaction(s), 2428684 gas +``` + +Verified: regenerating from the hosted cache reproduces `61eba49b…` byte for byte, +which is what proves the hosted `.bin` and the hosted cache describe the same block. + +The converter's `conversion_is_reproducible` test enforces the same property, but +against its own pinned block rather than this one — see [Validation](#validation). + +## What benchmarks with it + +Costs below are for the **current default block** (mainnet 25368371) and move with +it — see [Measured cost of candidate blocks](#measured-cost-of-candidate-blocks). + +| Where | How to run it | Cost (current default) | +|---|---|---| +| `benchmark-pr.yml` | **`/bench`** on a PR — the only workload it proves; also push to main and `workflow_dispatch` | 3 runs, ~4–5 min each (first run at epoch 2^22 confirms) | +| `scripts/bench_verify.sh` | `WORKLOAD=real scripts/bench_verify.sh ` | ~4.5–4.8 min per side, then cached | +| `scripts/perf_diff.sh` | `WORKLOAD=real scripts/perf_diff.sh ` | 5 recordings, so ~25 min | +| `benchmark-gpu.yml` | `/bench-gpu` on a PR — **the default there** | 59.87 s/prove on an RTX 5090 (see below) | +| `scripts/bench_abba.sh` | `WORKLOAD=real scripts/bench_abba.sh ` | ~4 h at 20 pairs — **manual only** | + +None of them hardcode the fixture path or a block number — they read the path from +`make -s print-real-block-fixture` and run `make ethrex-real-block-fixture` when +the `.bin` is absent. + +**`/bench-abba` is deliberately NOT wired to the real block.** The script supports +it (`WORKLOAD=real`, which is what the GPU workflow drives), but the CPU ABBA +workflow still defaults to the synthetic fixture: 20 pairs × 2 proves × ~4.5–4.8 min +is **~3 hours** on the one shared bench runner, which every other bench queues behind. +Run it by hand when a paired test on a real workload is worth that; the option is +here rather than a footgun on a comment trigger. + +**Both bench flows prove this block.** `/bench` runs it sampled on the shared +runner, against the cached baseline main publishes; `/bench-gpu [N]` runs it as +N A/B/B/A pairs on a rented box, comparing PR vs main on the same machine — +absolute GPU times are host-CPU-dependent, so only same-box deltas are +meaningful. + +**GPU baseline (measured), and why the GPU epoch is 2^22.** On an RTX 5090 (32,607 MiB) +against main @ `9ccdaf2`, same fixture and CLI, one prove per setting: + +| epoch | wall | VRAM | epochs | proof | +|---|---|---|---|---| +| 2^21 | 70.52 s | 19,193 MiB (58.9%) | 25 | 1.65 GB | +| **2^22** | **59.87 s** | 23,193 MiB (71.1%) | 13 | 1.12 GB | +| 2^23 | OOM after 9.7 s | 32,079 MiB (98.4%) — needs ~44 GiB | — | — | + +**VRAM is the binding constraint**, so 2^22 is simply the largest setting that fits a +32 GiB card — and it is ~15% faster than 2^21 (equivalently, 2^21 is ~18% slower) with +28.9% headroom to spare. 2^23 is out of reach for every card below 48 GiB, not just +this one. `benchmark-gpu.yml` defaults the real-block path to 2^22 for this reason; +raw traces are in `~/workspace/lambda_vm_bench_cache/gpu_epoch_calib_2026-07-31/` +(`PROVENANCE.txt`). + +That default is **GPU-path only**. `bench_abba.sh` still defaults to 2^20 for the CPU +`/bench-abba`, and the CLI's own `DEFAULT_CONTINUATION_EPOCH_SIZE_LOG2` is still 20: +2^22 needs ~28 GiB of *host* memory on a CPU build, which would break laptops. VRAM +binds on one path and host RAM on the other, so they are not the same question and the +GPU number must not be copied across. **The CPU-server epoch recommendation is pending +calibration.** + +The CPU bench runner is roughly **4.5–4.8x** the GPU wall time for the same block. + +Do not derive one from the other in general: the CPU rate (5.31–5.62 s/Mcycle) does not +transfer to the GPU, and the RTX 5090 sweep found the prover CPU-bound at the serial +producer above epoch 2^21, so GPU time lands closer to CPU time than a naive +device-throughput estimate suggests. + +**Continuations are mandatory, not a tuning choice.** Peak heap on a monolithic +prove grows ~4.9 GB per million cycles on this workload family (measured on the +bench server: `10,728 MB + 2,007 MB/transfer`, R² = 0.998 across 4→20 transfers), +so this block would need **~240 GB** monolithically — and a heavier candidate far +more. `--continuations` makes peak heap a function of the epoch size instead of the +trace length, so the same block fits in **~32 GiB** at epoch 2^22, the setting both the +CPU bench runner and the GPU path now use — picked by host RAM on one and by VRAM on +the other. See [Choosing the epoch size](#choosing-the-epoch-size) for the full curve +and the other tiers. The +bundle on disk is ~1.9 GB; note that a heavier block pushes it past 2 GiB, which +needs rkyv `pointer_width_64` to serialize. + +**`/bench` proves this block and nothing else** — 3 sampled runs against the cached +3-run baseline main publishes on every push. `/bench N` changes the sample count +(clamped to 5). + +The synthetic N-transfer screen that used to run alongside it was **removed**. Two +reasons, recorded here because "add a cheap screen back" is an easy suggestion to +make twice: + +1. Its only unique coverage was the **monolithic** prove path, which is vestigial — + reportedly slower than a single-epoch continuation. Spending runner time to cover + a path we intend to delete is not a trade worth making. +2. Its crypto mix is one **no real block has**: 8.83 ECSM per Mcycle, against a real + block's ~1.3–1.6. Screening against it tunes the prover for a worst case that + cannot occur. + +The synthetic fixtures themselves are not gone — `/bench-growth` still sweeps them for +a heap-vs-block-size slope, which needs a family of blocks and so cannot come from one +real one. `/bench-abba` and `/bench-verify` are untouched. + +**The cost is a shared resource.** One runner carries every `/bench`, `/bench-abba` +and `/bench-verify` in the repo, and a `/bench` now occupies it for **~15–20 min**, on +every comment and every push to main. `BENCH_RUNS_REAL` in `benchmark-pr.yml` is the +dial. Its per-run time on that runner is still unmeasured — the epoch calibration ran +on a different box — so the first run is what tells you whether 3 was the right +number; above ~6 min a run, turn it down. + +Cycle counts here (8.73M synthetic, 50.78M real) are from merge `fdb92f67` (main @ +`9ccdaf2`, clang 21). They move with guest optimisation and ~2% with the clang major, +so pin the ELF whenever you quote one, or it will look like a regression the next time +someone measures. + +## Where validation runs + +The checks themselves are described under [Validation](#validation) below; this is +where each one executes. + +`.github/workflows/ethrex-block-converter.yml` runs them on changes to this crate, +`tooling/ethrex-tests`, or the `Makefile` — **not** on every PR. The fixture is a +benchmark input, read by no product code; it has to be right when it changes, not on +every commit, and running it per-PR put a network fetch and a cold build of ~335 +packages in the required gate. + +`no_kzg_backend_linked` is the exception and stays in the required gate +(`pr_main.yaml`): it is a pure unit test costing microseconds, and it is the property +the usability screen depends on, so it should fail on the PR that breaks it rather +than on some later, unrelated one. + +## Prerequisites (for regeneration only) +Rust (stable) and network access on first run (cargo fetches the pinned ethrex +crates; `make` downloads the cache). **No RV64 target or sysroot needed** — this +is a host tool. + +## Getting a cache for a different block (regeneration only) + +The cache format is `ethrex-replay`'s, so use that tool to produce one — it +handles the RPC fetching, multiple client backends, and the `eth_getProof` +fallback, none of which is worth reimplementing here: + +```bash +# In a checkout of https://github.com/lambdaclass/ethrex-replay +ethrex-replay cache --rpc-url +``` + +`debug_executionWitness` requires a **reth or ethrex** node; public providers +(Alchemy, Infura) do not serve it. `ethrex-replay` also supports `eth_getProof` +for geth/nethermind. + +Only **mainnet, Hoodi and Sepolia** caches are accepted. ethrex-replay writes +`network: "LocalDevnet"` for any other chain, and that resolves to a test chain +(chain_id 9, every fork active from timestamp 0) — so converting it would replay +the block under invented rules while still passing every check here, since the +witness is only ever validated against whichever config we chose. The converter +refuses instead; `unmappable_network_is_rejected` pins that. + +## Adopting a different block + +The benchmark block and this crate's test block are **independent** — the fetch is +what decouples them — so a repoint touches two files and neither is this crate's +source. + +**1. The Makefile — the only place a block number appears.** Five lines: + +```make +ETHREX_REAL_BLOCK_NETWORK := +ETHREX_REAL_BLOCK := +ETHREX_REAL_BLOCK_FIXTURE_URL := +ETHREX_REAL_BLOCK_FIXTURE_SHA256 := +ETHREX_REAL_BLOCK_CACHE_URL / _SHA256 := +``` + +**2. `REAL_BLOCK_FIXTURE` in `tooling/ethrex-tests`**, which points the usability +screen at the block actually being benchmarked. That is the whole of it. + +**Nothing in this crate moves.** Its test constants stay pinned to Hoodi 1265656 +across every repoint — what they exercise is the conversion, not the workload, and +Hoodi's is the one cache ethrex-replay publishes, so pinning there costs no hosting +and cannot drift. + +Everything else derives from the Makefile — the fixture name, and through +`make -s print-real-block-fixture` the benchmark scripts and `benchmark-pr.yml`. No +workflow, script or env var names a block. Nothing in `executor/.gitignore` needs +touching either: it already ignores every accepted network's fixture name, so a +repointed ~1 MB fixture cannot become committable by accident. + +### Measured cost of candidate blocks + +Cost is a property of the block, so it changes with the repoint. All figures are +measured, never derived from gas — **cycles per gas is not constant** (20.9 for the +current default), so ranking candidates by gas mispredicts cost. + +**Current default — main-vintage (merge `fdb92f67`, main @ `9ccdaf2`):** + +| block | gas | cycles | GPU prove (RTX 5090) | CPU prove | proof | fixture | +|---|---|---|---|---|---|---| +| **mainnet 25368371** | 2.43M | **50,781,557** (clang 21)
50,713,534 (clang 18) | **59.87 s** @ epoch 2^22 | ~4.5–4.8x the GPU wall | ~1.9 GB | 1,110,156 B, `61eba49b…` | + +Epoch 2^22 is the GPU recommendation: VRAM binds, 2^22 leaves 28.9% headroom on a +32 GiB card and 2^23 does not fit one at all. See +[Choosing the epoch size](#choosing-the-epoch-size) for the CPU tiers. + +**Alternates — PRE-LTO vintage, superseded, re-measure before quoting.** These were +taken on a mid-July guest ELF, before #861 gave the guest thin LTO; the same build +change took the current default from 74,819,518 to ~50.7M, so expect these to fall by +a comparable factor. Kept because they are the selection evidence, not because the +numbers are current: + +| block | gas | cycles (pre-LTO) | fixture | +|---|---|---|---| +| mainnet 25453112 | 4.24M | 125,932,956 | 2,019,747 B, `0298663d…` | +| hoodi 1265656 | 4.40M | 168,319,360 | 1,021,207 B, `1f7d4c4c…` | + +All three clear the usability screen. Add a row rather than editing the wiring, and +say which ELF a number came from. + +Two things these numbers show that a gas-based estimate would have got wrong, and both +survive the vintage change because they are same-ELF comparisons. **Gas does not order +cost:** 25453112 carries *more* gas than hoodi 1265656 yet costs ~25% fewer cycles. +And **fixture size does not track cost** either: the current default is the cheapest +block and the middle-sized fixture. + +The default is the cheapest of the three, which matters because the CPU workload sits +on a single shared bench runner. It was also the only block in a 90-day Dune sweep +matching the shape constraints (2 heavy transactions, no whale, sane transfer share) +in the 1.6–2.6M gas band — so it is cheap *and* structurally typical, not cheap +because it is degenerate. + +### Choosing the epoch size + +`--epoch-size-log2` trades memory for speed, and **the right value is a property of the +machine, not of the block**. Three tiers, all measured: + +| where | epoch | why | +|---|---|---| +| GPU, 32 GiB card | **2^22** | VRAM-bound — 2^23 does not fit | +| CPU bench runner (≥64 GiB) | **2^22** | host-RAM-bound — 2^23's 60 GiB does not fit safely | +| CPU server, 128 GiB class | **2^23** | the knee; 2^24 fits but is not worth it | +| laptops (CLI default) | **2^20** | unchanged, so a plain `cli prove` still works | + +CPU sweep, 2026-07-31, on a 124 GiB / 32-core box, real block, **branch vintage** +(53,757,588 cycles on that box's clang-21 pre-LTO ELF): + +| epoch | epochs | wall | peak RSS | proof | +|---|---|---|---|---| +| 2^20 | 52 | 616.90 s | 14.56 GiB | 2.83 GB | +| 2^21 | 26 | 464.26 s | 18.43 GiB | 1.72 GB | +| 2^22 | 13 | 397.88 s | 32.21 GiB | 1.15 GB | +| **2^23** | 7 | **356.47 s** | **60.01 GiB** | 0.90 GB | +| 2^24 | 4 | ~334 s | ~97–105 GiB *(provisional)* | — | + +**There is a real knee.** Speed gained per doubling shrinks — 24.7%, 14.3%, 10.4%, +~6% — while memory roughly doubles at each step near the top. 2^23 uses 48% of a +124 GiB box (~52% headroom); 2^24 buys only ~6% more speed for ~15% headroom, so it +**fits but is not recommended on a shared box**, where one co-tenant turns a tight fit +into an OOM. The 2^24 RSS figure is provisional pending the calibration agent's formal +report. + +A main-vintage anchor also ran on the same box: 2^23 = 344.12 s / 58.87 GiB, i.e. ~3% +faster than branch vintage at the same memory — as expected, since #861 cut cycles and +peak RSS is set by the epoch size rather than the trace length. + +**Do not read absolute seconds off this table for another machine.** The calibration box +runs ~8.6 s/Mcycle against the bench runner's 5.31–5.62, so the *ratios* between epochs +transfer and the wall times do not. `/bench`'s real-block arm now runs at 2^22 (it was +2^21); the first run after that change establishes the runner's real number. + +### Verifying a repoint + +Run **both**, in this order: + +```bash +make ethrex-real-block-fixture # fetch + verify the new .bin +make test-ethrex # the block is USABLE on the guest +``` + +`make test-ethrex` is the one that matters here, and the converter's tests cannot +replace it — they run against a different block. A new block is only +usable if it needs no accelerator the guest lacks, and this crate cannot tell you +that — its graph links a working c-kzg, so a block calling point evaluation (0x0a) +passes here and fails in the guest. `test_ethrex_real_block_native` in +`tooling/ethrex-tests` is the screen. See [Validation](#validation). + +Benchmark comparability does not survive the swap, and that is intentional rather +than a wrinkle to work around: `benchmark-pr.yml` records which block it measured +and refuses to diff a PR against a baseline that measured a different one, so the +first run after a repoint reports one-sided numbers until main republishes. + +## Why the JSON and not ethrex-replay's own `.bin` + +`ethrex-replay` can already emit a rkyv `ProgramInput`, but it tracks ethrex +`main`, where the type has diverged from the rev our guest pins +(`156cb8d6…`): `main` has an extra `fee_configs` field, moved the type from +`l1::` to `input::`, and uses rkyv 0.8.10 against our exact `=0.8.16`. Its +binary would not deserialize in our guest. + +The cache JSON carries only `blocks` + `witness` + `network` as plain serde, so +it survives that drift. This tool re-reads it with **our** pinned ethrex types +and re-serializes with **our** rkyv, which is what keeps the output layout +correct by construction. When the guest's ethrex `rev` is bumped, bump it here +too (and in `tooling/ethrex-fixtures` and the guest) and regenerate. + +A previous real-block fixture (`ethrex_hoodi.bin`) was lost exactly to this kind +of drift — it predated the `Crypto` trait and stopped deserializing. Reading the +version-tolerant JSON instead of a pinned binary is the mitigation. + +## Validation + +Six checks, ordered so the argument builds: the host-side parity test first, then +what it does *not* cover, then the checks in `tooling/ethrex-tests` that close the +gap. Five run on the host and need no RV64 toolchain, and each executes in +milliseconds. + +What costs time is a cold build of the ethrex host dependency tree — ~335 +packages, including the `blst`, `c-kzg` and `secp256k1-sys` C builds, `malachite` +and `ark-ff/asm` — not the tests. That build is why these checks live in their own +path-filtered workflow rather than the PR gate (see [Where validation +runs](#where-validation-runs)); `ethrex-block-converter.yml` caches this workspace's +`target/` under its own key, so only cold runs pay it. + +These run against this crate's own pinned block (Hoodi 1265656), **not** the +benchmark block — they test the conversion, which any real block exercises equally, +and Hoodi's is the one cache ethrex-replay publishes. Only +`test_ethrex_real_block_native` follows the benchmark block. + +**`cargo test` here — `real_block_executes_under_guest_crypto`.** Executes the +block through `LambdaVmEcsmCrypto`, the `Crypto` impl the guest injects, so it is +exercised via the guest's own trait dispatch. Stateless re-execution ends in a +post-state-root check, so any divergence from consensus fails here. + +**It does not screen KZG.** Declaring `ethrex-guest-program` with +`default-features = false, features = ["lambdavm"]` is necessary but not +sufficient: the `ethrex-config` dependency (used only for +`Network::get_genesis()`) pulls `ethrex-p2p`, whose `default = ["c-kzg"]` +propagates down to `ethrex-crypto/c-kzg` — and `default-features = false` cannot +switch it off, because ethrex's own workspace declares `ethrex-p2p` with defaults +on. Verify with `cargo tree -e features -i ethrex-crypto`. So point evaluation +(0x0a) resolves to a working c-kzg here and to nothing in the guest. + +Scope of the gap: in `ethrex-crypto`, KZG is the **only** precompile whose +*availability* is feature-gated. The other two gates swap between working +implementations — `secp256k1` picks libsecp256k1 over k256, `std` picks malachite +over num-bigint for modexp — so they change which code runs, not whether a block +can execute. Dropping `ethrex-config` would therefore be a CI-time improvement +(it also sheds `ethrex-p2p`, `ethrex-blockchain`, `ethrex-storage` and the c-kzg +and secp256k1 C builds), not a correctness fix. + +**`cargo test` here — `unmappable_network_is_rejected`.** Refuses a cache whose +`network` cannot be mapped to real chain rules rather than converting it under +substituted ones — see [above](#getting-a-cache-for-a-different-block) for why +that matters. + +**`cargo test` here — `conversion_is_reproducible`.** Pins the block's stats and +the fixture's **sha256**. A length assert would not do: `ChainConfig` is +fixed-size, so a substituted chain config yields a byte-length-identical fixture, +and rkyv's `big_endian` feature would byte-swap in place — neither changes the +byte count. This is what catches an ethrex rev bump that moves the rkyv layout +instead of silently producing a fixture the guest can't read. + +**`tooling/ethrex-tests` — `no_kzg_backend_linked`.** Asserts that crate links no +KZG backend. That was incidental to its dependency graph, and it is the property +the next check relies on, so it is pinned here rather than assumed. + +**`tooling/ethrex-tests` — `test_ethrex_real_block_native`.** The one check that +follows the BENCHMARK block. Checks the serialized `.bin` itself deserializes and +executes. Since that crate links no KZG +backend, this is also **what screens point evaluation (0x0a)**: a block reaching +it diverges from consensus and fails here. + +**`tooling/ethrex-tests` — `test_ethrex_real_block_vm`** (`#[ignore]`, excluded +from PR CI). The block through the guest ELF, comparing the VM's committed +output against the native reference. Needs the RV64 toolchain and its runtime is +unmeasured; run it on a build server, not a laptop: + +```bash +cd tooling/ethrex-tests && cargo test --release test_ethrex_real_block_vm -- --ignored +``` + +[replay]: https://github.com/lambdaclass/ethrex-replay diff --git a/tooling/ethrex-block-converter/src/main.rs b/tooling/ethrex-block-converter/src/main.rs new file mode 100644 index 000000000..f96b03f24 --- /dev/null +++ b/tooling/ethrex-block-converter/src/main.rs @@ -0,0 +1,223 @@ +//! Convert a real Ethereum block into the rkyv-serialized `ProgramInput` the +//! lambda-vm ethrex guest consumes, from an `ethrex-replay` cache JSON. +//! +//! Usage: +//! cargo run --release -- + +use ethrex_common::types::Block; +use ethrex_common::types::block_execution_witness::RpcExecutionWitness; +use ethrex_config::networks::Network; +use ethrex_guest_program::l1::ProgramInput; +use serde::Deserialize; + +/// The subset of `ethrex-replay`'s on-disk cache that a `ProgramInput` needs. +/// +/// Deliberately deserialized with *our* pinned ethrex types rather than by +/// depending on `ethrex-replay`: it tracks ethrex `main`, where `ProgramInput` +/// has diverged (extra `fee_configs` field, different module path, rkyv 0.8.10 +/// vs our `=0.8.16`), so its own `.bin` output would not deserialize in our +/// guest. This JSON is the version-tolerant interface between the two. +/// +/// Extra fields in the file (L2 blob data, custom `chain_config`) are ignored. +#[derive(Deserialize)] +struct Cache { + blocks: Vec, + witness: RpcExecutionWitness, + network: Network, +} + +/// Summary of the converted block, for the CLI's one-line report. +struct BlockSummary { + network: String, + first_block_number: u64, + blocks: usize, + transactions: usize, + gas_used: u64, +} + +fn program_input_from_cache( + cache_path: &str, +) -> Result<(ProgramInput, BlockSummary), Box> { + let cache: Cache = + serde_json::from_reader(std::io::BufReader::new(std::fs::File::open(cache_path)?))?; + + let Some(first_block) = cache.blocks.first() else { + return Err("cache contains no blocks".into()); + }; + let summary = BlockSummary { + network: cache.network.to_string(), + first_block_number: first_block.header.number, + blocks: cache.blocks.len(), + transactions: cache.blocks.iter().map(|b| b.body.transactions.len()).sum(), + gas_used: cache.blocks.iter().map(|b| b.header.gas_used).sum(), + }; + + // The chain rules come from `network`, and ethrex-replay maps every chain it + // doesn't recognise (anything but mainnet / Hoodi / Sepolia) onto + // `LocalDevnet` — which resolves to a test chain: chain_id 9, every fork + // active from timestamp 0. Converting under that would execute the block + // against invented rules while still satisfying every check downstream (the + // witness is replayed against whatever config we picked, and host and guest + // read the same one), so refuse rather than guess. + if !matches!(cache.network, Network::PublicNetwork(_)) { + return Err(format!( + "unsupported network `{}`: its chain rules would be guessed, not read \ + (ethrex-replay writes LocalDevnet for any chain it does not recognise)", + cache.network + ) + .into()); + } + + // `into_execution_witness` rebuilds the trie structures from the flat node + // list and needs the parent header, which the cache carries inside `witness`. + let chain_config = cache.network.get_genesis()?.config; + let witness = cache + .witness + .into_execution_witness(chain_config, summary.first_block_number)?; + + Ok((ProgramInput::new(cache.blocks, witness), summary)) +} + +fn usage_and_exit(program: &str) -> ! { + eprintln!("usage: {program} "); + std::process::exit(2); +} + +fn main() -> Result<(), Box> { + let mut args = std::env::args(); + let program = args + .next() + .unwrap_or_else(|| "ethrex-block-converter".into()); + let (Some(cache_path), Some(out_path)) = (args.next(), args.next()) else { + usage_and_exit(&program); + }; + if args.next().is_some() { + usage_and_exit(&program); + } + + let (program_input, summary) = program_input_from_cache(&cache_path)?; + let bytes = rkyv::to_bytes::(&program_input)?; + std::fs::write(&out_path, &bytes)?; + + println!( + "wrote {out_path} ({} bytes): {} block(s) from {} starting at #{}, \ + {} transaction(s), {} gas", + bytes.len(), + summary.blocks, + summary.network, + summary.first_block_number, + summary.transactions, + summary.gas_used, + ); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use ethrex_config::networks::HOODI_CHAIN_ID; + + const CACHE: &str = "caches/cache_hoodi_1265656.json"; + + /// This crate is pinned to Hoodi 1265656 on purpose, independently of whichever + /// block the benchmarks currently prove: it is the one cache ethrex-replay hosts + /// upstream, so keeping the converter's test input there costs us no hosting and + /// cannot drift. What is under test here is the CONVERSION, not the benchmark + /// workload — see tooling/ethrex-block-converter/README.md. + /// + /// `caches/` is gitignored and fetched on demand, so every test here fails on + /// a clean checkout until the cache is downloaded. Say so instead of surfacing + /// a bare `No such file or directory` from `unwrap()`. + const CACHE_MISSING: &str = "caches/cache_hoodi_1265656.json is missing — run \ + `make ethrex-real-block-converter-cache` from the repo root first"; + + /// Executes the converted block with `LambdaVmEcsmCrypto`, the `Crypto` impl + /// the guest injects, so the block is exercised through the same trait + /// dispatch the guest uses. Stateless re-execution ends in a post-state-root + /// check, so any divergence from consensus fails here — on the host, with no + /// RV64 toolchain and no proving run. + /// + /// It does NOT screen KZG: this crate's graph links `c-kzg` (via + /// `ethrex-config` → `ethrex-p2p`, see Cargo.toml), so point evaluation + /// (0x0a) resolves to a working implementation here while the guest has none. + /// A block calling 0x0a would pass this test. + /// + /// `test_ethrex_real_block_native` in `tooling/ethrex-tests` is what covers + /// 0x0a, and `no_kzg_backend_linked` there keeps it covered. That split is + /// sufficient rather than a workaround: KZG is the only precompile in + /// `ethrex-crypto` whose *availability* is feature-gated — the other gates + /// swap between two working implementations. + #[test] + fn real_block_executes_under_guest_crypto() { + use ethrex_guest_program::l1::execution_program; + use lambda_vm_ethrex_crypto::LambdaVmEcsmCrypto; + use std::sync::Arc; + + let (program_input, _) = program_input_from_cache(CACHE).expect(CACHE_MISSING); + execution_program(program_input, Arc::new(LambdaVmEcsmCrypto)).unwrap(); + } + + /// A cache whose `network` we can't map to real chain rules must be refused, + /// not converted under substituted ones. Only the `network` field is changed + /// here, and the result is byte-length-identical to the real fixture — which + /// is exactly why the other tests cannot catch this on their own. + #[test] + fn unmappable_network_is_rejected() { + let mut cache: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(CACHE).expect(CACHE_MISSING)).unwrap(); + cache["network"] = serde_json::json!("LocalDevnet"); + + let path = std::env::temp_dir().join(format!( + "ethrex_real_block_localdevnet_{}.json", + std::process::id() + )); + std::fs::write(&path, serde_json::to_vec(&cache).unwrap()).unwrap(); + let result = program_input_from_cache(path.to_str().unwrap()); + std::fs::remove_file(&path).ok(); + let Err(err) = result else { + panic!("LocalDevnet resolves to chain_id 9 with all forks at 0; must not convert"); + }; + + assert!( + err.to_string().contains("unsupported network"), + "wrong rejection reason: {err}" + ); + } + + /// The serialized form is what the guest actually reads, so pin it: a + /// change here means the rkyv layout moved and every consumer of the + /// fixture (and its README checksum) needs regenerating. + #[test] + fn conversion_is_reproducible() { + use sha2::Digest; + + let (program_input, summary) = program_input_from_cache(CACHE).expect(CACHE_MISSING); + let bytes = rkyv::to_bytes::(&program_input).unwrap(); + + assert_eq!(summary.first_block_number, 1_265_656); + assert_eq!(summary.transactions, 11); + assert_eq!(summary.gas_used, 4_402_947); + + // Asserted separately from the digest below, not covered by it. The digest + // is exactly what a legitimate ethrex rev bump forces someone to rewrite + // (the layout moves, this goes red, a fresh digest gets pasted in) — and at + // that moment it stops covering the substituted-chain-config case it was + // chosen for. This assert survives that churn. + assert_eq!( + program_input.execution_witness.chain_config.chain_id, HOODI_CHAIN_ID, + "chain config is not Hoodi's — the block would replay under other rules", + ); + + // Digest, not length: a layout change can preserve the byte count exactly + // (`ChainConfig` is fixed-size, and rkyv's `big_endian` feature would only + // byte-swap in place), so `len()` cannot pin the archived form. + let digest: String = sha2::Sha256::digest(&bytes) + .iter() + .map(|b| format!("{b:02x}")) + .collect(); + assert_eq!( + digest, "1f7d4c4cdf9bd52472d9ebafdb4038f57a88c3c92d65c96fd86d7e323db87142", + "fixture bytes changed — regenerate it and update the README checksum", + ); + } +} diff --git a/tooling/ethrex-tests/Cargo.lock b/tooling/ethrex-tests/Cargo.lock index 26f991c7a..250e2411f 100644 --- a/tooling/ethrex-tests/Cargo.lock +++ b/tooling/ethrex-tests/Cargo.lock @@ -610,6 +610,7 @@ version = "0.1.0" dependencies = [ "k256", "num-bigint", + "num-integer", "num-traits", ] diff --git a/tooling/ethrex-tests/tests/ethrex.rs b/tooling/ethrex-tests/tests/ethrex.rs index c87ccceba..eaa811bf4 100644 --- a/tooling/ethrex-tests/tests/ethrex.rs +++ b/tooling/ethrex-tests/tests/ethrex.rs @@ -40,8 +40,8 @@ const ELF_PATH: &str = "../../executor/program_artifacts/rust/ethrex.elf"; const FIXTURES_DIR: &str = "../../executor/tests"; /// Larger-block smoke test: a synthetic ethrex block with 10 ETH transfers. -/// (Replaces the old `ethrex_hoodi.bin` real-block fixture, which was in the -/// pre-Crypto-trait ethrex format and no longer deserializes.) +/// (The old `ethrex_hoodi.bin` real-block fixture predated the `Crypto` trait +/// and no longer deserializes; the current real-block fixture is separate.) #[ignore = "heavier synthetic block (10 txs); run in the dedicated --ignored CI step"] #[test] fn test_ethrex() { @@ -86,3 +86,85 @@ fn test_ethrex_empty_block() { let output = execution_program(input, Arc::new(NativeCrypto)).unwrap(); run_program_and_check_public_output(ELF_PATH, output.encode(), inputs); } + +const REAL_BLOCK_FIXTURE: &str = "ethrex_mainnet_25368371.bin"; + +/// Host-only acceptance gate for the real-block fixture produced by +/// `tooling/ethrex-block-converter` (`make ethrex-real-block-fixture`): the block is +/// re-executed statelessly against its own witness, so a successful run means +/// the recovered tries, codes and headers reproduce the header's post-state +/// root. Needs no guest ELF, which is what keeps it runnable where the RV64 +/// toolchain isn't available. +/// +/// Checks the *serialized artifact* specifically — that the committed rkyv +/// bytes deserialize and execute — which is why it reads the `.bin` rather +/// than converting the cache itself. +/// +/// It is also, in practice, the check that the block needs no KZG: this crate's +/// dependency graph links no KZG backend, so a block calling point evaluation +/// (0x0a) diverges from consensus here and fails. That property is incidental to +/// the dep graph rather than declared, so `no_kzg_backend_linked` below pins it. +/// (`tooling/ethrex-block-converter`'s parity test does NOT cover 0x0a — it links +/// `c-kzg` transitively via `ethrex-config`.) +#[test] +fn test_ethrex_real_block_native() { + use ethrex_guest_program::crypto::NativeCrypto; + use ethrex_guest_program::l1::{ProgramInput, execution_program}; + use rkyv::rancor::Error; + use std::sync::Arc; + let inputs = std::fs::read(format!("{FIXTURES_DIR}/{REAL_BLOCK_FIXTURE}")).unwrap(); + let input = rkyv::from_bytes::(&inputs).unwrap(); + execution_program(input, Arc::new(NativeCrypto)).unwrap(); +} + +/// Pins the property the test above leans on: this crate's dependency graph must +/// link no KZG backend, so a block calling point evaluation (0x0a) diverges from +/// consensus and fails rather than passing on a surface the guest doesn't have. +/// If a future dependency pulls `c-kzg` or `kzg-rs` in, this goes red instead of +/// the screen silently disappearing. +/// Detection is by error *text*, deliberately: zero input fails under a linked +/// backend too (invalid G1 encoding), and both paths surface as +/// `CryptoError::Other`, so the variant can't tell them apart. The string comes +/// from `ethrex-crypto`'s `KzgError::Unimplemented`; if upstream rewords it this +/// test goes red, which is the safe direction. +/// +/// Worth knowing why this can regress: `ethrex-crypto`'s own default feature set +/// is `["std", "kzg-rs", "secp256k1"]`, so any future dependency pulling it in +/// with defaults on restores a backend and silently removes the screen. +#[test] +fn no_kzg_backend_linked() { + use ethrex_guest_program::crypto::{Crypto, NativeCrypto}; + let result = NativeCrypto.verify_kzg_proof(&[0u8; 32], &[0u8; 32], &[0u8; 48], &[0u8; 48]); + let message = match result { + Ok(()) => "verify_kzg_proof accepted zero input".to_string(), + Err(err) => format!("{err:?}"), + }; + assert!( + message.contains("One of features c-kzg, openvm-kzg or kzg-rs should be active"), + "a KZG backend is linked into ethrex-tests, so test_ethrex_real_block_native no \ + longer screens precompile 0x0a: {message}" + ); +} + +/// The same real block through the guest ELF, checking the VM's committed +/// output matches the native reference. Split from the native gate above +/// because this one needs the ethrex ELF and is far heavier than the synthetic +/// fixtures (a ~1 MB witness, real contract execution). +/// +/// Deliberately excluded from the PR CI step, which otherwise runs everything +/// via `--include-ignored`: the cycle cost of a real block in the VM has not +/// been measured yet, so it is opt-in until we know what it does to job time. +/// Run it explicitly with: +/// cd tooling/ethrex-tests && cargo test --release test_ethrex_real_block_vm -- --ignored +#[ignore = "real block through the VM; unmeasured runtime, run explicitly on a build server"] +#[test] +fn test_ethrex_real_block_vm() { + use ethrex_guest_program::crypto::NativeCrypto; + use ethrex_guest_program::l1::{ProgramInput, execution_program}; + use rkyv::rancor::Error; + use std::sync::Arc; + let inputs = std::fs::read(format!("{FIXTURES_DIR}/{REAL_BLOCK_FIXTURE}")).unwrap(); + let input = rkyv::from_bytes::(&inputs).unwrap(); + let output = execution_program(input, Arc::new(NativeCrypto)).unwrap(); + run_program_and_check_public_output(ELF_PATH, output.encode(), inputs); +}