diff --git a/.github/workflows/docker-reproducible.yml b/.github/workflows/docker-reproducible.yml index f3479e9468d..a4e67758b82 100644 --- a/.github/workflows/docker-reproducible.yml +++ b/.github/workflows/docker-reproducible.yml @@ -50,13 +50,13 @@ jobs: - arch: amd64 rust_target: x86_64-unknown-linux-gnu rust_image: >- - rust:1.88-bullseye@sha256:8e3c421122bf4cd3b2a866af41a4dd52d87ad9e315fd2cb5100e87a7187a9816 + rust:1.91-bullseye@sha256:ed6afcf912afc6aeddf0d1ff0dc6894c9b1c8f865964ef3f533e3ea77a64ffea platform: linux/amd64 runner: ubuntu-22.04 - arch: arm64 rust_target: aarch64-unknown-linux-gnu rust_image: >- - rust:1.88-bullseye@sha256:8b22455a7ce2adb1355067638284ee99d21cc516fab63a96c4514beaf370aa94 + rust:1.91-bullseye@sha256:2f06f086e3ceb2940b6f400f576aeec1abf6b6a7cbeb55a163ec2f9c0bbb1ed6 platform: linux/arm64 runner: ubuntu-22.04-arm runs-on: ${{ matrix.runner }} diff --git a/.github/workflows/kurtosis-eip8025.yml b/.github/workflows/kurtosis-eip8025.yml new file mode 100644 index 00000000000..c0a8e5509a7 --- /dev/null +++ b/.github/workflows/kurtosis-eip8025.yml @@ -0,0 +1,139 @@ +# Test that the EIP-8025 Kurtosis testnet starts and the proof engine integrates +# correctly with real zkboost-server backends. +name: kurtosis eip8025 + +on: + push: + branches: + - unstable + pull_request: + merge_group: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + run-eip8025-testnet: + runs-on: ${{ github.repository == 'sigp/lighthouse' && 'warp-ubuntu-latest-x64-8x' || 'ubuntu-latest' }} + steps: + - uses: actions/checkout@v5 + + - name: Install Kurtosis + run: | + echo "deb [trusted=yes] https://apt.fury.io/kurtosis-tech/ /" | sudo tee /etc/apt/sources.list.d/kurtosis.list + sudo apt update + sudo apt install -y kurtosis-cli + kurtosis analytics disable + + - name: Install yq + run: | + sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + sudo chmod +x /usr/local/bin/yq + + - name: Start EIP-8025 zkboost testnet + run: ./start_eip8025_zkboost_testnet.sh + working-directory: scripts/local_testnet + + - name: Wait for chain liveness + run: | + BEACON_URL=$(kurtosis port print eip8025-zkboost cl-1-lighthouse-reth http) + echo "Polling $BEACON_URL for head slot > 10..." + for i in $(seq 1 20); do + SLOT=$(curl -sf "$BEACON_URL/eth/v1/beacon/headers/head" \ + | jq -r '.data.header.message.slot' 2>/dev/null || echo 0) + echo " attempt $i: head slot = $SLOT" + if [ "$SLOT" -gt 10 ]; then + echo "Chain is live at slot $SLOT" + break + fi + if [ "$i" -eq 20 ]; then + echo "Timed out waiting for head slot > 10" + exit 1 + fi + sleep 30 + done + + - name: Inspect beacon node state + run: | + set -euo pipefail + ENCLAVE=eip8025-zkboost + SERVICES=(cl-1-lighthouse-reth cl-2-lighthouse-reth cl-3-lighthouse-reth cl-4-lighthouse-reth) + FAILED=0 + + for SVC in "${SERVICES[@]}"; do + URL=$(kurtosis port print "$ENCLAVE" "$SVC" http) + echo "=== $SVC ($URL) ===" + + # Syncing status — must not be syncing + SYNCING=$(curl -sf "$URL/eth/v1/node/syncing" | jq -r '.data.is_syncing') + echo " is_syncing: $SYNCING" + if [ "$SYNCING" != "false" ]; then + echo " FAIL: $SVC is still syncing" + FAILED=1 + fi + + # Peer count — must have at least one connected peer + PEERS=$(curl -sf "$URL/eth/v1/node/peer_count" | jq -r '.data.connected') + echo " connected peers: $PEERS" + if [ "${PEERS:-0}" -lt 1 ]; then + echo " FAIL: $SVC has no connected peers" + FAILED=1 + fi + + # Head slot — must be non-zero + SLOT=$(curl -sf "$URL/eth/v1/beacon/headers/head" | jq -r '.data.header.message.slot') + echo " head slot: $SLOT" + if [ "${SLOT:-0}" -lt 1 ]; then + echo " FAIL: $SVC head slot is 0" + FAILED=1 + fi + + # Finality checkpoints — informational, log the finalized epoch + FINALIZED=$(curl -sf "$URL/eth/v1/beacon/states/head/finality_checkpoints" \ + | jq -r '.data.finalized.epoch') + echo " finalized epoch: $FINALIZED" + done + + exit "$FAILED" + + - name: Check zkboost sidecars are running and generating proofs + run: | + ENCLAVE=eip8025-zkboost + + # Both zkboost services must be in RUNNING state + kurtosis enclave inspect "$ENCLAVE" | grep -E "zkboost-[12].*RUNNING" \ + || { echo "FAIL: one or more zkboost services not in RUNNING state"; exit 1; } + + # Each zkboost sidecar must have generated at least one proof. + # Check via the Prometheus metrics endpoint (zkboost_prove_total) rather than + # log scraping — kurtosis service logs may not be available in all CI environments. + for SVC in zkboost-1 zkboost-2; do + URL=$(kurtosis port print "$ENCLAVE" "$SVC" http) + COUNT=$(curl -sf "$URL/metrics" \ + | awk '/^zkboost_prove_total\{/ {sum += $2} END {print int(sum)}') + echo "$SVC: $COUNT proofs generated" + if [ "${COUNT:-0}" -lt 1 ]; then + echo "FAIL: $SVC has not generated any proofs" + exit 1 + fi + done + + - name: Stop testnet and collect logs + if: always() + run: | + mkdir -p scripts/local_testnet/logs + ENCLAVE=eip8025-zkboost + for SVC in cl-1-lighthouse-reth cl-2-lighthouse-reth cl-3-lighthouse-reth cl-4-lighthouse-reth \ + zkboost-1 zkboost-2; do + kurtosis service logs "$ENCLAVE" "$SVC" > "scripts/local_testnet/logs/${SVC}.log" 2>&1 || true + done + kurtosis enclave rm -f "$ENCLAVE" || true + + - name: Upload logs artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: logs-kurtosis-eip8025 + path: scripts/local_testnet/logs + retention-days: 3 diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 7344a9367b7..afd4e0bd670 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -26,6 +26,10 @@ env: CARGO_INCREMENTAL: 0 # Enable portable to prevent issues with caching `blst` for the wrong CPU type TEST_FEATURES: portable + # Use Clang for C/C++ compilation. Required because leveldb-sys uses + # -Wthread-safety which is a Clang-only flag unsupported by GCC. + CC: clang + CXX: clang++ jobs: check-labels: runs-on: ubuntu-latest @@ -102,6 +106,23 @@ jobs: if: env.SELF_HOSTED_RUNNERS == 'true' continue-on-error: true run: sccache --show-stats + proof-engine-tests: + name: proof-engine-tests + needs: [check-labels] + if: needs.check-labels.outputs.skip_ci != 'true' + runs-on: ${{ github.repository == 'sigp/lighthouse' && 'warp-ubuntu-latest-x64-4x' || 'ubuntu-latest' }} + steps: + - uses: actions/checkout@v5 + - name: Get latest version of stable Rust + uses: moonrepo/setup-rust@v1 + with: + channel: stable + cache-target: release + bins: cargo-nextest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Run proof engine tests sequentially + run: make test-proof-engine beacon-chain-tests: name: beacon-chain-tests needs: [check-labels] @@ -437,6 +458,7 @@ jobs: 'check-labels', 'target-branch-check', 'release-tests-ubuntu', + 'proof-engine-tests', 'beacon-chain-tests', 'op-pool-tests', 'network-tests', diff --git a/.github/workflows/zkboost-tests.yml b/.github/workflows/zkboost-tests.yml new file mode 100644 index 00000000000..be2d170284e --- /dev/null +++ b/.github/workflows/zkboost-tests.yml @@ -0,0 +1,70 @@ +name: zkboost-tests + +on: + push: + branches: + - stable + - staging + - trying + - 'pr/*' + pull_request: + merge_group: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + RUSTFLAGS: "-D warnings -C debuginfo=0" + CARGO_INCREMENTAL: 0 + TEST_FEATURES: portable + # Use Clang for C/C++ compilation. Required because leveldb-sys uses + # -Wthread-safety which is a Clang-only flag unsupported by GCC. + CC: clang + CXX: clang++ + +jobs: + check-labels: + runs-on: ubuntu-latest + name: Check for 'skip-ci' label + outputs: + skip_ci: ${{ steps.set-output.outputs.SKIP_CI }} + steps: + - name: check for skip-ci label + id: set-output + env: + LABELS: ${{ toJson(github.event.pull_request.labels) }} + run: | + SKIP_CI="false" + if [ -z "${LABELS}" ] || [ "${LABELS}" = "null" ]; then + LABELS="none"; + else + LABELS=$(echo ${LABELS} | jq -r '.[].name') + fi + for label in ${LABELS}; do + if [ "$label" = "skip-ci" ]; then + SKIP_CI="true" + break + fi + done + echo "skip_ci=$SKIP_CI" >> $GITHUB_OUTPUT + + zkboost-tests: + name: zkboost-tests + needs: [check-labels] + if: needs.check-labels.outputs.skip_ci != 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Install dependencies + run: sudo apt update && sudo apt install -y git gcc g++ make cmake pkg-config llvm-dev libclang-dev clang + - name: Get latest version of stable Rust + uses: moonrepo/setup-rust@v1 + with: + channel: stable + cache-target: release + bins: cargo-nextest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Run proof_engine_zkboost integration tests + run: make test-zkboost diff --git a/.gitignore b/.gitignore index efd7916b050..e4aa942de2a 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ genesis.ssz # VSCode /.vscode + +md-docs/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 65447c4390a..ec50547fb9d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,8 @@ { - "rust-analyzer.cargo.cfgs": [ - "!debug_assertions" - ] +"rust-analyzer.checkOnSave.enable": true, +"rust-analyzer.check.command": "clippy", +"rust-analyzer.check.allTargets": false, + +"rust-analyzer.check.extraEnv": { "CARGO_BUILD_JOBS": "4" }, +"rust-analyzer.cargo.features": ["test-utils"] } diff --git a/Cargo.lock b/Cargo.lock index 27f775b2d0d..ab10977861d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "account_manager" -version = "8.0.1" +version = "8.1.2" dependencies = [ "account_utils", "bls", @@ -73,7 +73,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -111,6 +111,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -119,9 +128,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.2.20" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bc32535569185cbcb6ad5fa64d989a47bccb9a08e27284b1f2a3ccf16e6d010" +checksum = "9247f0a399ef71aeb68f497b2b8fb348014f742b50d3b83b1e00dfe1b7d64b3d" dependencies = [ "alloy-primitives", "num_enum", @@ -130,9 +139,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.1.3" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e318e25fb719e747a7e8db1654170fc185024f3ed5b10f86c08d448a912f6e2" +checksum = "b0c0dc44157867da82c469c13186015b86abef209bf0e41625e4b68bac61d728" dependencies = [ "alloy-eips", "alloy-primitives", @@ -143,7 +152,7 @@ dependencies = [ "auto_impl", "borsh", "c-kzg", - "derive_more 2.0.1", + "derive_more 2.1.1", "either", "k256", "once_cell", @@ -152,14 +161,14 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "alloy-consensus-any" -version = "1.1.3" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "364380a845193a317bcb7a5398fc86cdb66c47ebe010771dde05f6869bf9e64a" +checksum = "ba4cdb42df3871cd6b346d6a938ec2ba69a9a0f49d1f82714bc5c48349268434" dependencies = [ "alloy-consensus", "alloy-eips", @@ -171,16 +180,16 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "1.4.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdff496dd4e98a81f4861e66f7eaf5f2488971848bb42d9c892f871730245c8" +checksum = "cc2db5c583aaef0255aa63a4fe827f826090142528bba48d1bf4119b62780cad" dependencies = [ "alloy-json-abi", "alloy-primitives", "alloy-sol-type-parser", "alloy-sol-types", "itoa", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -193,7 +202,7 @@ dependencies = [ "alloy-rlp", "crc", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -218,37 +227,50 @@ dependencies = [ "alloy-rlp", "borsh", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-eip7928" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8222b1d88f9a6d03be84b0f5e76bb60cd83991b43ad8ab6477f0e4a7809b98d" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "serde", ] [[package]] name = "alloy-eips" -version = "1.1.3" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c4d7c5839d9f3a467900c625416b24328450c65702eb3d8caff8813e4d1d33" +checksum = "b9f7ef09f21bd1e9cb8a686f168cb4a206646804567f0889eadb8dcc4c9288c8" dependencies = [ "alloy-eip2124", "alloy-eip2930", "alloy-eip7702", + "alloy-eip7928", "alloy-primitives", "alloy-rlp", "alloy-serde", "auto_impl", "borsh", "c-kzg", - "derive_more 2.0.1", + "derive_more 2.1.1", "either", "serde", "serde_with", "sha2", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "alloy-json-abi" -version = "1.4.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5513d5e6bd1cba6bdcf5373470f559f320c05c8c59493b6e98912fbe6733943f" +checksum = "e9dbe713da0c737d9e5e387b0ba790eb98b14dd207fe53eef50e19a5a8ec3dac" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -258,24 +280,24 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.1.3" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f72cf87cda808e593381fb9f005ffa4d2475552b7a6c5ac33d087bf77d82abd0" +checksum = "ff42cd777eea61f370c0b10f2648a1c81e0b783066cd7269228aa993afd487f7" dependencies = [ "alloy-primitives", "alloy-sol-types", - "http 1.3.1", + "http 1.4.0", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] [[package]] name = "alloy-network" -version = "1.1.3" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12aeb37b6f2e61b93b1c3d34d01ee720207c76fe447e2a2c217e433ac75b17f5" +checksum = "8cbca04f9b410fdc51aaaf88433cbac761213905a65fe832058bcf6690585762" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -290,18 +312,18 @@ dependencies = [ "alloy-sol-types", "async-trait", "auto_impl", - "derive_more 2.0.1", + "derive_more 2.1.1", "futures-utils-wasm", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "alloy-network-primitives" -version = "1.1.3" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abd29ace62872083e30929cd9b282d82723196d196db589f3ceda67edcc05552" +checksum = "42d6d15e069a8b11f56bef2eccbad2a873c6dd4d4c81d04dda29710f5ea52f04" dependencies = [ "alloy-consensus", "alloy-eips", @@ -312,20 +334,20 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.4.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355bf68a433e0fd7f7d33d5a9fc2583fde70bf5c530f63b80845f8da5505cf28" +checksum = "de3b431b4e72cd8bd0ec7a50b4be18e73dab74de0dba180eef171055e5d5926e" dependencies = [ "alloy-rlp", "arbitrary", "bytes", "cfg-if", "const-hex", - "derive_more 2.0.1", + "derive_more 2.1.1", "foldhash 0.2.0", - "getrandom 0.3.4", - "hashbrown 0.16.0", - "indexmap 2.12.0", + "getrandom 0.4.2", + "hashbrown 0.16.1", + "indexmap 2.13.0", "itoa", "k256", "keccak-asm", @@ -333,18 +355,18 @@ dependencies = [ "proptest", "proptest-derive", "rand 0.9.2", + "rapidhash", "ruint", "rustc-hash 2.1.1", "serde", "sha3", - "tiny-keccak", ] [[package]] name = "alloy-provider" -version = "1.1.3" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b710636d7126e08003b8217e24c09f0cca0b46d62f650a841736891b1ed1fc1" +checksum = "d181c8cc7cf4805d7e589bf4074d56d55064fa1a979f005a45a62b047616d870" dependencies = [ "alloy-chains", "alloy-consensus", @@ -366,13 +388,13 @@ dependencies = [ "either", "futures", "futures-utils-wasm", - "lru 0.13.0", + "lru 0.16.3", "parking_lot", "pin-project", "reqwest", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "url", @@ -381,9 +403,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f70d83b765fdc080dbcd4f4db70d8d23fe4761f2f02ebfa9146b833900634b4" +checksum = "e93e50f64a77ad9c5470bf2ad0ca02f228da70c792a8f06634801e202579f35e" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -392,20 +414,20 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" +checksum = "ce8849c74c9ca0f5a03da1c865e3eb6f768df816e67dd3721a398a8a7e398011" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] name = "alloy-rpc-client" -version = "1.1.3" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0882e72d2c1c0c79dcf4ab60a67472d3f009a949f774d4c17d0bdb669cfde05" +checksum = "f2792758a93ae32a32e9047c843d536e1448044f78422d71bf7d7c05149e103f" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -418,7 +440,7 @@ dependencies = [ "serde_json", "tokio", "tokio-stream", - "tower 0.5.2", + "tower 0.5.3", "tracing", "url", "wasmtimer", @@ -426,9 +448,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.1.3" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a63fb40ed24e4c92505f488f9dd256e2afaed17faa1b7a221086ebba74f4122" +checksum = "dd720b63f82b457610f2eaaf1f32edf44efffe03ae25d537632e7d23e7929e1a" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -437,9 +459,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.1.3" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eae0c7c40da20684548cbc8577b6b7447f7bf4ddbac363df95e3da220e41e72" +checksum = "9b2dc411f13092f237d2bf6918caf80977fc2f51485f9b90cb2a2f956912c8c9" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -453,14 +475,14 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "alloy-serde" -version = "1.1.3" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0df1987ed0ff2d0159d76b52e7ddfc4e4fbddacc54d2fbee765e0d14d7c01b5" +checksum = "e2ce1e0dbf7720eee747700e300c99aac01b1a95bb93f493a01e78ee28bb1a37" dependencies = [ "alloy-primitives", "serde", @@ -469,9 +491,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.1.3" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff69deedee7232d7ce5330259025b868c5e6a52fa8dffda2c861fb3a5889b24" +checksum = "2425c6f314522c78e8198979c8cbf6769362be4da381d4152ea8eefce383535d" dependencies = [ "alloy-primitives", "async-trait", @@ -479,14 +501,14 @@ dependencies = [ "either", "elliptic-curve", "k256", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "alloy-signer-local" -version = "1.1.3" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72cfe0be3ec5a8c1a46b2e5a7047ed41121d360d97f4405bb7c1c784880c86cb" +checksum = "c3ecb71ee53d8d9c3fa7bac17542c8116ebc7a9726c91b1bf333ec3d04f5a789" dependencies = [ "alloy-consensus", "alloy-network", @@ -495,46 +517,46 @@ dependencies = [ "async-trait", "k256", "rand 0.8.5", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "alloy-sol-macro" -version = "1.4.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3ce480400051b5217f19d6e9a82d9010cdde20f1ae9c00d53591e4a1afbb312" +checksum = "ab81bab693da9bb79f7a95b64b394718259fdd7e41dceeced4cad57cb71c4f6a" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] name = "alloy-sol-macro-expander" -version = "1.4.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d792e205ed3b72f795a8044c52877d2e6b6e9b1d13f431478121d8d4eaa9028" +checksum = "489f1620bb7e2483fb5819ed01ab6edc1d2f93939dce35a5695085a1afd1d699" dependencies = [ "alloy-sol-macro-input", "const-hex", "heck", - "indexmap 2.12.0", + "indexmap 2.13.0", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.110", + "sha3", + "syn 2.0.117", "syn-solidity", - "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "1.4.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd1247a8f90b465ef3f1207627547ec16940c35597875cdc09c49d58b19693c" +checksum = "56cef806ad22d4392c5fc83cf8f2089f988eb99c7067b4e0c6f1971fc1cca318" dependencies = [ "const-hex", "dunce", @@ -542,25 +564,25 @@ dependencies = [ "macro-string", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "1.4.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "954d1b2533b9b2c7959652df3076954ecb1122a28cc740aa84e7b0a49f6ac0a9" +checksum = "a6df77fea9d6a2a75c0ef8d2acbdfd92286cc599983d3175ccdc170d3433d249" dependencies = [ "serde", - "winnow", + "winnow 0.7.15", ] [[package]] name = "alloy-sol-types" -version = "1.4.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70319350969a3af119da6fb3e9bddb1bce66c9ea933600cb297c8b1850ad2a3c" +checksum = "64612d29379782a5dde6f4b6570d9c756d734d760c0c94c254d361e678a6591f" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -570,22 +592,22 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.1.3" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be98b07210d24acf5b793c99b759e9a696e4a2e67593aec0487ae3b3e1a2478c" +checksum = "fa186e560d523d196580c48bf00f1bf62e63041f28ecf276acc22f8b27bb9f53" dependencies = [ "alloy-json-rpc", "auto_impl", "base64 0.22.1", - "derive_more 2.0.1", + "derive_more 2.1.1", "futures", "futures-utils-wasm", "parking_lot", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", - "tower 0.5.2", + "tower 0.5.3", "tracing", "url", "wasmtimer", @@ -593,45 +615,46 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.1.3" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4198a1ee82e562cab85e7f3d5921aab725d9bd154b6ad5017f82df1695877c97" +checksum = "aa501ad58dd20acddbfebc65b52e60f05ebf97c52fa40d1b35e91f5e2da0ad0e" dependencies = [ "alloy-json-rpc", "alloy-transport", + "itertools 0.14.0", "reqwest", "serde_json", - "tower 0.5.2", + "tower 0.5.3", "tracing", "url", ] [[package]] name = "alloy-trie" -version = "0.9.1" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3412d52bb97c6c6cc27ccc28d4e6e8cf605469101193b50b0bd5813b1f990b5" +checksum = "3f14b5d9b2c2173980202c6ff470d96e7c5e202c65a9f67884ad565226df7fbb" dependencies = [ "alloy-primitives", "alloy-rlp", - "arrayvec", - "derive_more 2.0.1", + "derive_more 2.1.1", "nybbles", "serde", "smallvec", + "thiserror 2.0.18", "tracing", ] [[package]] name = "alloy-tx-macros" -version = "1.1.3" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "333544408503f42d7d3792bfc0f7218b643d968a03d2c0ed383ae558fb4a76d0" +checksum = "6fa0c53e8c1e1ef4d01066b01c737fb62fc9397ab52c6e7bb5669f97d281b9bc" dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -651,9 +674,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -666,15 +689,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -685,7 +708,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -696,14 +719,14 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arbitrary" @@ -716,9 +739,12 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.7.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" +dependencies = [ + "rustversion", +] [[package]] name = "archery" @@ -814,7 +840,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -852,7 +878,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -935,9 +961,6 @@ name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" -dependencies = [ - "serde", -] [[package]] name = "asn1-rs" @@ -951,7 +974,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", ] @@ -963,7 +986,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", "synstructure", ] @@ -975,14 +998,14 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] name = "asn1_der" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247" +checksum = "4858a9d740c5007a9069007c3b4e91152d0506f13c1b31dd49051fd537656156" [[package]] name = "assert-json-diff" @@ -1030,7 +1053,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 1.1.2", + "rustix 1.1.4", "slab", "windows-sys 0.61.2", ] @@ -1054,7 +1077,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -1065,7 +1088,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -1094,7 +1117,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" dependencies = [ "base64 0.22.1", - "http 1.3.1", + "http 1.4.0", "log", "url", ] @@ -1107,7 +1130,7 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -1126,7 +1149,7 @@ dependencies = [ "axum-core", "bytes", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", "itoa", @@ -1138,7 +1161,7 @@ dependencies = [ "rustversion", "serde", "sync_wrapper", - "tower 0.5.2", + "tower 0.5.3", "tower-layer", "tower-service", ] @@ -1152,7 +1175,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", "mime", @@ -1205,9 +1228,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "beacon_chain" @@ -1231,12 +1254,12 @@ dependencies = [ "genesis", "hex", "int_to_bytes", - "itertools 0.10.5", + "itertools 0.14.0", "kzg", "lighthouse_tracing", "lighthouse_version", "logging", - "lru 0.12.5", + "lru 0.16.3", "maplit", "merkle_proof", "metrics", @@ -1276,7 +1299,7 @@ dependencies = [ [[package]] name = "beacon_node" -version = "8.0.1" +version = "8.1.2" dependencies = [ "account_utils", "beacon_chain", @@ -1315,7 +1338,7 @@ dependencies = [ "clap", "eth2", "futures", - "itertools 0.10.5", + "itertools 0.14.0", "sensitive_url", "serde", "slot_clock", @@ -1334,7 +1357,7 @@ version = "0.1.0" dependencies = [ "fnv", "futures", - "itertools 0.10.5", + "itertools 0.14.0", "lighthouse_network", "logging", "metrics", @@ -1365,7 +1388,7 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cexpr", "clang-sys", "itertools 0.12.1", @@ -1378,7 +1401,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.110", + "syn 2.0.117", "which", ] @@ -1399,15 +1422,15 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitcoin-io" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" [[package]] name = "bitcoin_hashes" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" dependencies = [ "bitcoin-io", "hex-conservative", @@ -1421,9 +1444,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "bitvec" @@ -1455,6 +1478,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "block2" version = "0.6.2" @@ -1513,7 +1545,7 @@ dependencies = [ [[package]] name = "boot_node" -version = "8.0.1" +version = "8.1.2" dependencies = [ "beacon_node", "bytes", @@ -1535,25 +1567,26 @@ dependencies = [ [[package]] name = "borsh" -version = "1.5.7" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" dependencies = [ "borsh-derive", + "bytes", "cfg_aliases", ] [[package]] name = "borsh-derive" -version = "1.5.7" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" +checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -1590,9 +1623,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byte-slice-cast" @@ -1608,18 +1641,18 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] [[package]] name = "c-kzg" -version = "2.1.5" +version = "2.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e00bf4b112b07b505472dbefd19e37e53307e2bfed5a79e0cc161d58ccd0e687" +checksum = "6648ed1e4ea8e8a1a4a2c78e1cda29a3fd500bc622899c340d8525ea9a76b24a" dependencies = [ "blst", "cc", @@ -1632,9 +1665,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" dependencies = [ "serde_core", ] @@ -1659,7 +1692,7 @@ dependencies = [ "semver 1.0.27", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1668,11 +1701,20 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" -version = "1.2.46" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "jobserver", @@ -1709,7 +1751,18 @@ checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.0", ] [[package]] @@ -1719,7 +1772,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", - "chacha20", + "chacha20 0.9.1", "cipher", "poly1305", "zeroize", @@ -1727,9 +1780,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", @@ -1790,9 +1843,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.53" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -1800,34 +1853,34 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", "terminal_size", ] [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] name = "clap_lex" -version = "0.7.6" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "clap_utils" @@ -1890,33 +1943,45 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.54" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" dependencies = [ "cc", ] +[[package]] +name = "cms" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b77c319abfd5219629c45c34c89ba945ed3c5e49fcde9d16b6c3885f118a730" +dependencies = [ + "const-oid", + "der", + "spki", + "x509-cert", +] + [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "colored" -version = "3.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] name = "compare_fields" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05162add7c8618791829528194a271dca93f69194d35b19db1ca7fbfb8275278" +checksum = "f6f45d0b4d61b582303179fb7a1a142bc9d647b7583db3b0d5f25a21d286fab9" dependencies = [ "compare_fields_derive", "itertools 0.14.0", @@ -1924,12 +1989,12 @@ dependencies = [ [[package]] name = "compare_fields_derive" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ee468b2e568b668e2a686112935e7bbe9a81bf4fa6b9f6fc3410ea45fb7ce" +checksum = "92ff1dbbda10d495b2c92749c002b2025e0be98f42d1741ecc9ff820d2f04dce" dependencies = [ "quote", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -1982,12 +2047,12 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.17.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" +checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "proptest", "serde_core", ] @@ -2026,9 +2091,9 @@ dependencies = [ [[package]] name = "context_deserialize" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5f9ea0a0ae2de4943f5ca71590b6dbd0b952475f0a0cafb30a470cec78c8b9" +checksum = "4c523eea4af094b5970c321f4604abc42c5549d3cbae332e98325403fbbdbf70" dependencies = [ "context_deserialize_derive", "serde", @@ -2036,12 +2101,12 @@ dependencies = [ [[package]] name = "context_deserialize_derive" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c57b2db1e4e3ed804dcc49894a144b68fe6c754b8f545eb1dda7ad3c7dbe7e6" +checksum = "3b7bf98c48ffa511b14bb3c76202c24a8742cea1efa9570391c5d41373419a09" dependencies = [ "quote", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -2050,6 +2115,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -2094,11 +2168,20 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" dependencies = [ "crc-catalog", ] @@ -2120,25 +2203,24 @@ dependencies = [ [[package]] name = "criterion" -version = "0.5.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" dependencies = [ + "alloca", "anes", "cast", "ciborium", "clap", "criterion-plot", - "is-terminal", - "itertools 0.10.5", + "itertools 0.13.0", "num-traits", - "once_cell", "oorandom", + "page_size", "plotters", "rayon", "regex", "serde", - "serde_derive", "serde_json", "tinytemplate", "walkdir", @@ -2146,12 +2228,12 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.5.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" dependencies = [ "cast", - "itertools 0.10.5", + "itertools 0.13.0", ] [[package]] @@ -2234,12 +2316,12 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.5.1" +version = "3.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73736a89c4aff73035ba2ed2e565061954da00d4970fc9ac25dcc85a2a20d790" +checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" dependencies = [ "dispatch2", - "nix 0.30.1", + "nix 0.31.2", "windows-sys 0.61.2", ] @@ -2250,7 +2332,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", @@ -2267,17 +2349,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", -] - -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core 0.13.4", - "darling_macro 0.13.4", + "syn 2.0.117", ] [[package]] @@ -2301,17 +2373,13 @@ dependencies = [ ] [[package]] -name = "darling_core" -version = "0.13.4" +name = "darling" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn 1.0.109", + "darling_core 0.23.0", + "darling_macro 0.23.0", ] [[package]] @@ -2324,8 +2392,8 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim 0.11.1", - "syn 2.0.110", + "strsim", + "syn 2.0.117", ] [[package]] @@ -2339,19 +2407,21 @@ dependencies = [ "proc-macro2", "quote", "serde", - "strsim 0.11.1", - "syn 2.0.110", + "strsim", + "syn 2.0.117", ] [[package]] -name = "darling_macro" -version = "0.13.4" +name = "darling_core" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "darling_core 0.13.4", + "ident_case", + "proc-macro2", "quote", - "syn 1.0.109", + "strsim", + "syn 2.0.117", ] [[package]] @@ -2362,7 +2432,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -2373,7 +2443,18 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.110", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.117", ] [[package]] @@ -2412,9 +2493,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "data-encoding-macro" @@ -2433,7 +2514,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -2494,6 +2575,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", + "der_derive", + "flagset", + "pem-rfc7468", "zeroize", ] @@ -2511,11 +2595,22 @@ dependencies = [ "rusticata-macros", ] +[[package]] +name = "der_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", "serde_core", @@ -2540,7 +2635,38 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.117", ] [[package]] @@ -2549,34 +2675,45 @@ version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ - "convert_case", + "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] name = "derive_more" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ + "convert_case 0.10.0", "proc-macro2", "quote", - "syn 2.0.110", + "rustc_version 0.4.1", + "syn 2.0.117", "unicode-xid", ] +[[package]] +name = "des" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +dependencies = [ + "cipher", +] + [[package]] name = "digest" version = "0.9.0" @@ -2662,11 +2799,11 @@ dependencies = [ [[package]] name = "dispatch2" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "block2", "libc", "objc2", @@ -2680,7 +2817,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -2710,9 +2847,9 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "dtoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" [[package]] name = "dunce" @@ -2775,7 +2912,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -2998,7 +3135,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -3018,7 +3155,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -3143,7 +3280,7 @@ dependencies = [ "sha2", "tempfile", "unicode-normalization", - "uuid 0.8.2", + "uuid", "zeroize", ] @@ -3183,7 +3320,7 @@ dependencies = [ "serde_repr", "tempfile", "tiny-bip39", - "uuid 0.8.2", + "uuid", ] [[package]] @@ -3201,7 +3338,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa93f58bb1eb3d1e556e4f408ef1dac130bad01ac37db4e7ade45de40d1c86a" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", "ring", "sha2", ] @@ -3221,15 +3358,15 @@ dependencies = [ [[package]] name = "ethereum_ssz" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8cd8c4f47dfb947dbfe3cdf2945ae1da808dbedc592668658e827a12659ba1" +checksum = "2128a84f7a3850d54ee343334e3392cca61f9f6aa9441eec481b9394b43c238b" dependencies = [ "alloy-primitives", "arbitrary", "context_deserialize", "ethereum_serde_utils", - "itertools 0.13.0", + "itertools 0.14.0", "serde", "serde_derive", "smallvec", @@ -3238,14 +3375,14 @@ dependencies = [ [[package]] name = "ethereum_ssz_derive" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78d247bc40823c365a62e572441a8f8b12df03f171713f06bc76180fcd56ab71" +checksum = "cd596f91cff004fc8d02be44c21c0f9b93140a04b66027ae052f5f8e05b48eba" dependencies = [ - "darling 0.20.11", + "darling 0.23.0", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -3323,15 +3460,20 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", + "anyhow", "arc-swap", + "async-stream", + "async-trait", "bls", "builder_client", "bytes", "eth2", "ethereum_serde_utils", "ethereum_ssz", + "ethereum_ssz_derive", "fixed_bytes", "fork_choice", + "futures", "hash-db", "hash256-std-hasher", "hex", @@ -3340,12 +3482,13 @@ dependencies = [ "kzg", "lighthouse_version", "logging", - "lru 0.12.5", + "lru 0.16.3", "metrics", "parking_lot", "pretty_reqwest_error", "rand 0.9.2", "reqwest", + "reqwest-eventsource", "sensitive_url", "serde", "serde_json", @@ -3353,6 +3496,7 @@ dependencies = [ "slot_clock", "ssz_types", "state_processing", + "store", "strum", "superstruct", "task_executor", @@ -3371,9 +3515,9 @@ dependencies = [ [[package]] name = "fallible-iterator" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fallible-streaming-iterator" @@ -3462,9 +3606,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixed-hash" @@ -3486,16 +3630,22 @@ dependencies = [ "safe_arith", ] +[[package]] +name = "flagset" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" + [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", - "libz-rs-sys", "libz-sys", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -3516,21 +3666,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "fork_choice" version = "0.1.0" @@ -3583,9 +3718,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -3608,9 +3743,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -3618,27 +3753,26 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", "futures-util", - "num_cpus", ] [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -3652,13 +3786,13 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -3668,21 +3802,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" dependencies = [ "futures-io", - "rustls 0.23.35", + "rustls 0.23.37", "rustls-pki-types", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-timer" @@ -3692,9 +3826,9 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -3704,7 +3838,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -3743,9 +3876,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -3763,11 +3896,37 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "getset" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "ghash" version = "0.5.1" @@ -3821,7 +3980,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.12.0", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -3830,17 +3989,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.3.1", - "indexmap 2.12.0", + "http 1.4.0", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -3902,12 +4061,15 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ + "allocator-api2", + "equivalent", "foldhash 0.2.0", "serde", + "serde_core", ] [[package]] @@ -3937,6 +4099,15 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "hashlink" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" +dependencies = [ + "hashbrown 0.16.1", +] + [[package]] name = "hdrhistogram" version = "7.5.4" @@ -4004,9 +4175,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-conservative" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" dependencies = [ "arrayvec", ] @@ -4036,7 +4207,7 @@ dependencies = [ "rand 0.9.2", "ring", "socket2 0.5.10", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tokio", "tracing", @@ -4059,7 +4230,7 @@ dependencies = [ "rand 0.9.2", "resolv-conf", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", ] @@ -4104,12 +4275,11 @@ dependencies = [ [[package]] name = "http" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -4131,7 +4301,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.3.1", + "http 1.4.0", ] [[package]] @@ -4142,7 +4312,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "pin-project-lite", ] @@ -4172,7 +4342,7 @@ dependencies = [ "lighthouse_tracing", "lighthouse_version", "logging", - "lru 0.12.5", + "lru 0.16.3", "metrics", "network", "network_utils", @@ -4274,8 +4444,8 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.12", - "http 1.3.1", + "h2 0.4.13", + "http 1.4.0", "http-body 1.0.1", "httparse", "httpdate", @@ -4293,10 +4463,10 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http 1.3.1", + "http 1.4.0", "hyper 1.8.1", "hyper-util", - "rustls 0.23.35", + "rustls 0.23.37", "rustls-pki-types", "tokio", "tokio-rustls 0.26.4", @@ -4317,41 +4487,24 @@ dependencies = [ "tower-service", ] -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper 1.8.1", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - [[package]] name = "hyper-util" -version = "0.1.18" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", - "futures-core", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "hyper 1.8.1", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.1", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -4359,9 +4512,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -4369,7 +4522,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core", ] [[package]] @@ -4429,9 +4582,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -4443,9 +4596,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" @@ -4462,6 +4615,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -4491,19 +4650,19 @@ dependencies = [ [[package]] name = "if-addrs" -version = "0.10.2" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cabb0019d51a643781ff15c9c8a3e5dedc365c47211270f4e8f82812fedd8f0a" +checksum = "c0a05c691e1fae256cf7013d99dad472dc52d5543322761f83ec8d47eab40d2b" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] name = "if-watch" -version = "3.2.1" +version = "3.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf9d64cfcf380606e64f9a0bcf493616b65331199f984151a6fa11a7b3cde38" +checksum = "71c02a5161c313f0cbdbadc511611893584a10a7b6153cb554bdf83ddce99ec2" dependencies = [ "async-io", "core-foundation 0.9.4", @@ -4532,7 +4691,7 @@ dependencies = [ "attohttpc", "bytes", "futures", - "http 1.3.1", + "http 1.4.0", "http-body-util", "hyper 1.8.1", "hyper-util", @@ -4560,7 +4719,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -4576,13 +4735,13 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "arbitrary", "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "serde", "serde_core", ] @@ -4598,7 +4757,9 @@ dependencies = [ "filesystem", "lockfile", "metrics", + "p12-keystore", "parking_lot", + "pem", "rand 0.9.2", "reqwest", "serde", @@ -4619,6 +4780,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ + "block-padding", "generic-array", ] @@ -4654,31 +4816,20 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", ] -[[package]] -name = "is-terminal" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -4723,9 +4874,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jobserver" @@ -4739,9 +4890,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.82" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -4779,18 +4930,18 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] name = "keccak-asm" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +checksum = "b646a74e746cd25045aa0fd42f4f7f78aa6d119380182c7e63a5593c4ab8df6f" dependencies = [ "digest 0.10.7", "sha3-asm", @@ -4844,7 +4995,7 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "lcli" -version = "8.0.1" +version = "8.1.2" dependencies = [ "account_utils", "beacon_chain", @@ -4880,6 +5031,12 @@ dependencies = [ "validator_dir", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "leveldb" version = "0.8.6" @@ -4905,9 +5062,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.177" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libloading" @@ -4921,9 +5078,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libmdbx" @@ -4950,7 +5107,7 @@ dependencies = [ "either", "futures", "futures-timer", - "getrandom 0.2.16", + "getrandom 0.2.17", "libp2p-allow-block-list", "libp2p-connection-limits", "libp2p-core", @@ -4969,7 +5126,7 @@ dependencies = [ "multiaddr", "pin-project", "rw-stream-sink", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -4996,9 +5153,9 @@ dependencies = [ [[package]] name = "libp2p-core" -version = "0.43.1" +version = "0.43.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d28e2d2def7c344170f5c6450c0dbe3dfef655610dbfde2f6ac28a527abbe36" +checksum = "249128cd37a2199aff30a7675dffa51caf073b51aa612d2f544b19932b9aebca" dependencies = [ "either", "fnv", @@ -5013,7 +5170,7 @@ dependencies = [ "quick-protobuf", "rand 0.8.5", "rw-stream-sink", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "unsigned-varint 0.8.0", "web-time", @@ -5049,7 +5206,7 @@ dependencies = [ "fnv", "futures", "futures-timer", - "getrandom 0.2.16", + "getrandom 0.2.17", "hashlink 0.10.0", "hex_fmt", "libp2p-core", @@ -5082,15 +5239,15 @@ dependencies = [ "quick-protobuf", "quick-protobuf-codec", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] [[package]] name = "libp2p-identity" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3104e13b51e4711ff5738caa1fb54467c8604c2e94d607e27745bcf709068774" +checksum = "f0c7892c221730ba55f7196e98b0b8ba5e04b4155651736036628e9f73ed6fc3" dependencies = [ "asn1_der", "bs58 0.5.1", @@ -5101,7 +5258,7 @@ dependencies = [ "quick-protobuf", "rand 0.8.5", "sha2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "zeroize", ] @@ -5177,7 +5334,7 @@ dependencies = [ "rand 0.8.5", "snow", "static_assertions", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "x25519-dalek", "zeroize", @@ -5214,27 +5371,27 @@ dependencies = [ "quinn", "rand 0.8.5", "ring", - "rustls 0.23.35", + "rustls 0.23.37", "socket2 0.5.10", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", ] [[package]] name = "libp2p-swarm" -version = "0.47.0" +version = "0.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aa762e5215919a34e31c35d4b18bf2e18566ecab7f8a3d39535f4a3068f8b62" +checksum = "ce88c6c4bf746c8482480345ea3edfd08301f49e026889d1cbccfa1808a9ed9e" dependencies = [ "either", "fnv", "futures", "futures-timer", + "hashlink 0.10.0", "libp2p-core", "libp2p-identity", "libp2p-swarm-derive", - "lru 0.12.5", "multistream-select", "rand 0.8.5", "smallvec", @@ -5251,21 +5408,21 @@ checksum = "dd297cf53f0cb3dee4d2620bb319ae47ef27c702684309f682bdb7e55a18ae9c" dependencies = [ "heck", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] name = "libp2p-tcp" -version = "0.44.0" +version = "0.44.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65b4e030c52c46c8d01559b2b8ca9b7c4185f10576016853129ca1fe5cd1a644" +checksum = "fb6585b9309699f58704ec9ab0bb102eca7a3777170fa91a8678d73ca9cafa93" dependencies = [ "futures", "futures-timer", "if-watch", "libc", "libp2p-core", - "socket2 0.5.10", + "socket2 0.6.3", "tokio", "tracing", ] @@ -5282,10 +5439,10 @@ dependencies = [ "libp2p-identity", "rcgen", "ring", - "rustls 0.23.35", - "rustls-webpki 0.103.8", - "thiserror 2.0.17", - "x509-parser", + "rustls 0.23.37", + "rustls-webpki 0.103.10", + "thiserror 2.0.18", + "x509-parser 0.17.0", "yasna", ] @@ -5313,47 +5470,37 @@ dependencies = [ "either", "futures", "libp2p-core", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "yamux 0.12.1", - "yamux 0.13.8", + "yamux 0.13.10", ] [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ - "bitflags 2.10.0", "libc", ] [[package]] name = "libsqlite3-sys" -version = "0.25.2" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa" +checksum = "95b4103cffefa72eb8428cb6b47d6627161e51c2739fc5e3b734584157bc642a" dependencies = [ "cc", "pkg-config", "vcpkg", ] -[[package]] -name = "libz-rs-sys" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15413ef615ad868d4d65dce091cb233b229419c7c0c4bcaa746c0901c49ff39c" -dependencies = [ - "zlib-rs", -] - [[package]] name = "libz-sys" -version = "1.1.23" +version = "1.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" +checksum = "d52f4c29e2a68ac30c9087e1b772dc9f44a2b66ed44edf2266cf2be9b03dafc1" dependencies = [ "cc", "pkg-config", @@ -5362,7 +5509,7 @@ dependencies = [ [[package]] name = "lighthouse" -version = "8.0.1" +version = "8.1.2" dependencies = [ "account_manager", "account_utils", @@ -5432,14 +5579,14 @@ dependencies = [ "fnv", "futures", "hex", - "itertools 0.10.5", + "itertools 0.14.0", "libp2p", "libp2p-gossipsub", "libp2p-mplex", "lighthouse_version", "local-ip-address", "logging", - "lru 0.12.5", + "lru 0.16.3", "lru_cache", "metrics", "network_utils", @@ -5499,7 +5646,7 @@ dependencies = [ [[package]] name = "lighthouse_version" -version = "8.0.1" +version = "8.1.2" dependencies = [ "regex", ] @@ -5512,9 +5659,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" @@ -5545,14 +5692,13 @@ dependencies = [ [[package]] name = "local-ip-address" -version = "0.6.5" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "656b3b27f8893f7bbf9485148ff9a65f019e3f33bd5cdc87c83cab16b3fd9ec8" +checksum = "79ef8c257c92ade496781a32a581d43e3d512cf8ce714ecf04ea80f93ed0ff4a" dependencies = [ "libc", "neli", - "thiserror 2.0.17", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5574,9 +5720,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "logging" @@ -5619,11 +5765,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.13.0" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" dependencies = [ - "hashbrown 0.15.5", + "hashbrown 0.16.1", ] [[package]] @@ -5657,7 +5803,7 @@ checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -5679,13 +5825,13 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "match-lookup" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1265724d8cb29dbbc2b0f06fffb8bf1a8c0cf73a78eede9ba73a4a66c52a981e" +checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -5728,9 +5874,9 @@ checksum = "33746aadcb41349ec291e7f2f0a3aa6834d1d7c58066fb4b01f68efc4c4b7631" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memoffset" @@ -5754,25 +5900,25 @@ dependencies = [ [[package]] name = "metastruct" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d74f54f231f9a18d77393ecc5cc7ab96709b2a61ee326c2b2b291009b0cc5a07" +checksum = "969a1be9bd80794bdf93b23ab552c2ec6f3e83b33164824553fd996cdad513b8" dependencies = [ "metastruct_macro", ] [[package]] name = "metastruct_macro" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7225f3a4dfbec47a0c6a730a874185fda840d365d7bbd6ba199dd81796d5" +checksum = "de9164f767d73a507c19205868c84da411dc7795f4bdabf497d3dd93cfef9930" dependencies = [ - "darling 0.13.4", - "itertools 0.10.5", + "darling 0.23.0", + "itertools 0.14.0", "proc-macro2", "quote", "smallvec", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -5840,9 +5986,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "wasi", @@ -5878,7 +6024,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -5890,25 +6036,26 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] name = "mockito" -version = "1.7.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7760e0e418d9b7e5777c0374009ca4c93861b9066f18cb334a20ce50ab63aa48" +checksum = "90820618712cab19cfc46b274c6c22546a82affcb3c3bdf0f29e3db8e1bb92c0" dependencies = [ "assert-json-diff", "bytes", "colored", - "futures-util", - "http 1.3.1", + "futures-core", + "http 1.4.0", "http-body 1.0.1", "http-body-util", "hyper 1.8.1", "hyper-util", "log", + "pin-project-lite", "rand 0.9.2", "regex", "serde_json", @@ -5919,9 +6066,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.11" +version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" +checksum = "85f8024e1c8e71c778968af91d43700ce1d11b219d127d79fb2934153b82b42b" dependencies = [ "crossbeam-channel", "crossbeam-epoch", @@ -5929,10 +6076,9 @@ dependencies = [ "equivalent", "parking_lot", "portable-atomic", - "rustc_version 0.4.1", "smallvec", "tagptr", - "uuid 1.18.1", + "uuid", ] [[package]] @@ -6015,107 +6161,78 @@ dependencies = [ "unsigned-varint 0.7.2", ] -[[package]] -name = "native-tls" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework 2.11.1", - "security-framework-sys", - "tempfile", -] - [[package]] name = "neli" -version = "0.6.5" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93062a0dce6da2517ea35f301dfc88184ce18d3601ec786a727a87bf535deca9" +checksum = "22f9786d56d972959e1408b6a93be6af13b9c1392036c5c1fafa08a1b0c6ee87" dependencies = [ + "bitflags 2.11.0", "byteorder", + "derive_builder", + "getset", "libc", "log", "neli-proc-macros", + "parking_lot", ] [[package]] name = "neli-proc-macros" -version = "0.1.4" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8034b7fbb6f9455b2a96c19e6edf8dc9fc34c70449938d8ee3b4df363f61fe" +checksum = "05d8d08c6e98f20a62417478ebf7be8e1425ec9acecc6f63e22da633f6b71609" dependencies = [ "either", "proc-macro2", "quote", "serde", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] name = "netlink-packet-core" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" +checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4" dependencies = [ - "anyhow", - "byteorder", - "netlink-packet-utils", + "paste", ] [[package]] name = "netlink-packet-route" -version = "0.17.1" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" +checksum = "4ce3636fa715e988114552619582b530481fd5ef176a1e5c1bf024077c2c9445" dependencies = [ - "anyhow", - "bitflags 1.3.2", - "byteorder", + "bitflags 2.11.0", "libc", + "log", "netlink-packet-core", - "netlink-packet-utils", -] - -[[package]] -name = "netlink-packet-utils" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" -dependencies = [ - "anyhow", - "byteorder", - "paste", - "thiserror 1.0.69", ] [[package]] name = "netlink-proto" -version = "0.11.5" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72452e012c2f8d612410d89eea01e2d9b56205274abb35d53f60200b2ec41d60" +checksum = "b65d130ee111430e47eed7896ea43ca693c387f097dd97376bffafbf25812128" dependencies = [ "bytes", "futures", "log", "netlink-packet-core", "netlink-sys", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "netlink-sys" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16c903aa70590cb93691bf97a767c8d1d6122d2cc9070433deb3bbf36ce8bd23" +checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae" dependencies = [ "bytes", - "futures", + "futures-util", "libc", "log", "tokio", @@ -6144,7 +6261,7 @@ dependencies = [ "genesis", "hex", "igd-next", - "itertools 0.10.5", + "itertools 0.14.0", "k256", "kzg", "libp2p-gossipsub", @@ -6182,7 +6299,6 @@ dependencies = [ "discv5", "hex", "libp2p-identity", - "lru_cache", "metrics", "multiaddr", "parking_lot", @@ -6203,22 +6319,23 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.4" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.11.0", "cfg-if", + "cfg_aliases", "libc", ] [[package]] name = "nix" -version = "0.30.1" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "cfg_aliases", "libc", @@ -6230,15 +6347,31 @@ version = "0.2.0" dependencies = [ "beacon_node", "beacon_node_fallback", + "bls", + "bytes", "environment", "eth2", + "ethereum_ssz", + "ethereum_ssz_derive", "execution_layer", + "futures", + "hex", + "parking_lot", + "reqwest", "sensitive_url", + "serde", + "serde_json", + "ssz_types", + "task_executor", "tempfile", "tokio", + "tokio-stream", + "tracing", + "tree_hash", "types", "validator_client", "validator_dir", + "validator_store", ] [[package]] @@ -6259,9 +6392,9 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" dependencies = [ "winapi", ] @@ -6304,9 +6437,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-integer" @@ -6350,9 +6483,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ "num_enum_derive", "rustversion", @@ -6360,20 +6493,20 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] name = "nybbles" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4b5ecbd0beec843101bffe848217f770e8b8da81d8355b7d6e226f2199b3dc" +checksum = "0d49ff0c0d00d4a502b39df9af3a525e1efeb14b9dabb5bb83335284c1309210" dependencies = [ "alloy-rlp", "cfg-if", @@ -6385,9 +6518,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ "objc2-encode", ] @@ -6409,9 +6542,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" dependencies = [ "critical-section", "portable-atomic", @@ -6442,59 +6575,11 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" -[[package]] -name = "openssl" -version = "0.10.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" -dependencies = [ - "bitflags 2.10.0", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.110", -] - [[package]] name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-src" -version = "300.5.4+3.5.4" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" -dependencies = [ - "cc", -] - -[[package]] -name = "openssl-sys" -version = "0.9.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" -dependencies = [ - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", -] +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "opentelemetry" @@ -6506,40 +6591,24 @@ dependencies = [ "futures-sink", "js-sys", "pin-project-lite", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] -[[package]] -name = "opentelemetry-http" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f6639e842a97dbea8886e3439710ae463120091e2e064518ba8e716e6ac36d" -dependencies = [ - "async-trait", - "bytes", - "http 1.3.1", - "opentelemetry", - "reqwest", -] - [[package]] name = "opentelemetry-otlp" version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbee664a43e07615731afc539ca60c6d9f1a9425e25ca09c57bc36c87c55852b" dependencies = [ - "http 1.3.1", + "http 1.4.0", "opentelemetry", - "opentelemetry-http", "opentelemetry-proto", "opentelemetry_sdk", "prost", - "reqwest", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tonic 0.13.1", - "tracing", ] [[package]] @@ -6567,7 +6636,7 @@ dependencies = [ "percent-encoding", "rand 0.9.2", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -6581,7 +6650,7 @@ dependencies = [ "ethereum_ssz", "ethereum_ssz_derive", "fixed_bytes", - "itertools 0.10.5", + "itertools 0.14.0", "maplit", "metrics", "parking_lot", @@ -6596,6 +6665,39 @@ dependencies = [ "types", ] +[[package]] +name = "p12-keystore" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb9bf5222606eb712d3bb30e01bc9420545b00859970897e70c682353a034f2" +dependencies = [ + "base64 0.22.1", + "cbc", + "cms", + "der", + "des", + "hex", + "hmac", + "pkcs12", + "pkcs5", + "rand 0.10.0", + "rc2", + "sha1", + "sha2", + "thiserror 2.0.18", + "x509-parser 0.18.1", +] + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "pairing" version = "0.23.0" @@ -6630,7 +6732,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -6688,6 +6790,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -6696,9 +6807,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.3" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" dependencies = [ "memchr", "ucd-trie", @@ -6706,29 +6817,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -6736,6 +6847,36 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs12" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "695b3df3d3cc1015f12d70235e35b6b79befc5fa7a9b95b951eab1dd07c9efc2" +dependencies = [ + "cms", + "const-oid", + "der", + "digest 0.10.7", + "spki", + "x509-cert", + "zeroize", +] + +[[package]] +name = "pkcs5" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" +dependencies = [ + "aes", + "cbc", + "der", + "pbkdf2", + "scrypt", + "sha2", + "spki", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -6796,7 +6937,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.1.2", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -6806,7 +6947,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -6818,16 +6959,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "potential_utf" @@ -6855,9 +6996,9 @@ dependencies = [ [[package]] name = "predicates" -version = "3.1.3" +version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" dependencies = [ "anstyle", "predicates-core", @@ -6865,15 +7006,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" +checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" [[package]] name = "predicates-tree" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" dependencies = [ "predicates-core", "termtree", @@ -6894,7 +7035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -6910,9 +7051,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ "toml_edit", ] @@ -6936,14 +7077,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -6954,9 +7095,9 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25485360a54d6861439d60facef26de713b1e126bf015ec8f98239467a2b82f7" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "procfs-core", - "rustix 1.1.2", + "rustix 1.1.4", ] [[package]] @@ -6965,7 +7106,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "hex", ] @@ -7003,18 +7144,33 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", +] + +[[package]] +name = "proof_engine_test" +version = "8.1.2" +dependencies = [ + "anyhow", + "execution_layer", + "futures", + "network", + "simulator", + "task_executor", + "tokio", + "tracing", + "types", ] [[package]] name = "proptest" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" +checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.10.0", + "bitflags 2.11.0", "num-traits", "rand 0.9.2", "rand_chacha 0.9.0", @@ -7027,13 +7183,13 @@ dependencies = [ [[package]] name = "proptest-derive" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "095a99f75c69734802359b682be8daaf8980296731f6470434ea2c652af1dd30" +checksum = "fb6dc647500e84a25a85b100e76c85b8ace114c209432dc174f20aac11d4ed6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -7056,7 +7212,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -7131,8 +7287,7 @@ dependencies = [ [[package]] name = "quinn" version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +source = "git+https://github.com/sigp/quinn?rev=59af87979c8411864c1cb68613222f54ed2930a7#59af87979c8411864c1cb68613222f54ed2930a7" dependencies = [ "bytes", "cfg_aliases", @@ -7141,9 +7296,9 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash 2.1.1", - "rustls 0.23.35", - "socket2 0.6.1", - "thiserror 2.0.17", + "rustls 0.23.37", + "socket2 0.5.10", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -7152,8 +7307,7 @@ dependencies = [ [[package]] name = "quinn-proto" version = "0.11.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +source = "git+https://github.com/sigp/quinn?rev=59af87979c8411864c1cb68613222f54ed2930a7#59af87979c8411864c1cb68613222f54ed2930a7" dependencies = [ "bytes", "getrandom 0.3.4", @@ -7161,10 +7315,10 @@ dependencies = [ "rand 0.9.2", "ring", "rustc-hash 2.1.1", - "rustls 0.23.35", + "rustls 0.23.37", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -7173,22 +7327,21 @@ dependencies = [ [[package]] name = "quinn-udp" version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +source = "git+https://github.com/sigp/quinn?rev=59af87979c8411864c1cb68613222f54ed2930a7#59af87979c8411864c1cb68613222f54ed2930a7" dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.1", + "socket2 0.5.10", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.52.0", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -7199,6 +7352,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "r2d2" version = "0.8.10" @@ -7212,12 +7371,13 @@ dependencies = [ [[package]] name = "r2d2_sqlite" -version = "0.21.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f5d0337e99cd5cacd91ffc326c6cc9d8078def459df560c4f9bf9ba4a51034" +checksum = "a2ebd03c29250cdf191da93a35118b4567c2ef0eacab54f65e058d6f4c9965f6" dependencies = [ "r2d2", "rusqlite", + "uuid", ] [[package]] @@ -7245,10 +7405,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", "serde", ] +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20 0.10.0", + "getrandom 0.4.2", + "rand_core 0.10.0", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -7266,7 +7437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -7275,19 +7446,25 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", "serde", ] +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + [[package]] name = "rand_xorshift" version = "0.3.0" @@ -7303,7 +7480,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core 0.9.3", + "rand_core 0.9.5", +] + +[[package]] +name = "rapidhash" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" +dependencies = [ + "rustversion", ] [[package]] @@ -7326,6 +7512,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rc2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62c64daa8e9438b84aaae55010a93f396f8e60e3911590fcba770d04643fc1dd" +dependencies = [ + "cipher", +] + [[package]] name = "rcgen" version = "0.13.2" @@ -7354,7 +7549,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -7363,7 +7558,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", "thiserror 1.0.69", ] @@ -7385,14 +7580,14 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -7402,9 +7597,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -7413,45 +7608,42 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.12.24" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", "futures-core", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", "hyper 1.8.1", "hyper-rustls", - "hyper-tls", "hyper-util", "js-sys", "log", - "native-tls", "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.35", + "rustls 0.23.37", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-native-tls", "tokio-rustls 0.26.4", "tokio-util", - "tower 0.5.2", + "tower 0.5.3", "tower-http", "tower-service", "url", @@ -7502,7 +7694,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -7537,20 +7729,30 @@ dependencies = [ "archery", ] +[[package]] +name = "rsqlite-vfs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d" +dependencies = [ + "hashbrown 0.16.1", + "thiserror 2.0.18", +] + [[package]] name = "rtnetlink" -version = "0.13.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a552eb82d19f38c3beed3f786bd23aa434ceb9ac43ab44419ca6d67a7e186c0" +checksum = "4b960d5d873a75b5be9761b1e73b146f52dddcd27bac75263f40fba686d4d7b5" dependencies = [ - "futures", + "futures-channel", + "futures-util", "log", "netlink-packet-core", "netlink-packet-route", - "netlink-packet-utils", "netlink-proto", "netlink-sys", - "nix 0.26.4", + "nix 0.30.1", "thiserror 1.0.69", "tokio", ] @@ -7592,16 +7794,17 @@ checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rusqlite" -version = "0.28.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" +checksum = "f1c93dd1c9683b438c392c492109cb702b8090b2bfc8fed6f6e4eb4523f17af3" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.11.0", "fallible-iterator", "fallible-streaming-iterator", - "hashlink 0.8.4", + "hashlink 0.11.0", "libsqlite3-sys", "smallvec", + "sqlite-wasm-rs", ] [[package]] @@ -7672,23 +7875,23 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys 0.11.0", + "linux-raw-sys 0.12.1", "windows-sys 0.52.0", ] @@ -7708,29 +7911,29 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.8", + "rustls-webpki 0.103.10", "subtle", "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.5.1", + "security-framework", ] [[package]] @@ -7744,9 +7947,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "web-time", "zeroize", @@ -7765,9 +7968,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "ring", "rustls-pki-types", @@ -7805,9 +8008,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "safe_arith" @@ -7835,9 +8038,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] @@ -7865,9 +8068,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ "dyn-clone", "ref-cast", @@ -7936,24 +8139,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework" -version = "3.5.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -7962,9 +8152,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -8045,20 +8235,20 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -8069,7 +8259,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -8086,17 +8276,17 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.16.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.12.0", + "indexmap 2.13.0", "schemars 0.9.0", - "schemars 1.1.0", + "schemars 1.2.1", "serde_core", "serde_json", "serde_with_macros", @@ -8105,14 +8295,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.16.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" dependencies = [ - "darling 0.21.3", + "darling 0.23.0", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -8121,7 +8311,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.13.0", "itoa", "ryu", "serde", @@ -8145,7 +8335,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -8156,7 +8346,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -8172,9 +8362,9 @@ dependencies = [ [[package]] name = "sha3-asm" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +checksum = "b31139435f327c93c6038ed350ae4588e2c70a13d50599509fee6349967ba35a" dependencies = [ "cc", "cfg-if", @@ -8197,10 +8387,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -8234,9 +8425,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "similar" @@ -8246,13 +8437,13 @@ checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "simple_asn1" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", ] @@ -8260,29 +8451,37 @@ dependencies = [ name = "simulator" version = "0.2.0" dependencies = [ + "anyhow", + "beacon_chain", "clap", "environment", + "eth2", "execution_layer", "futures", "kzg", + "lighthouse_network", "logging", + "network_utils", "node_test_rig", "parking_lot", "rayon", "sensitive_url", "serde_json", + "task_executor", + "tempfile", "tokio", "tracing", "tracing-subscriber", "typenum", "types", + "validator_http_api", ] [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "slasher" @@ -8300,7 +8499,7 @@ dependencies = [ "libmdbx", "lmdb-rkv", "lmdb-rkv-sys", - "lru 0.12.5", + "lru 0.16.3", "maplit", "metrics", "parking_lot", @@ -8411,12 +8610,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -8435,6 +8634,18 @@ dependencies = [ "der", ] +[[package]] +name = "sqlite-wasm-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4206ed3a67690b9c29b77d728f6acc3ce78f16bf846d83c94f76400320181b" +dependencies = [ + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", +] + [[package]] name = "ssz_types" version = "0.14.0" @@ -8474,7 +8685,7 @@ dependencies = [ "fixed_bytes", "int_to_bytes", "integer-sqrt", - "itertools 0.10.5", + "itertools 0.14.0", "merkle_proof", "metrics", "milhouse", @@ -8522,10 +8733,10 @@ dependencies = [ "ethereum_ssz", "ethereum_ssz_derive", "fixed_bytes", - "itertools 0.10.5", + "itertools 0.14.0", "leveldb", "logging", - "lru 0.12.5", + "lru 0.16.3", "metrics", "milhouse", "parking_lot", @@ -8547,12 +8758,6 @@ dependencies = [ "zstd", ] -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -8577,7 +8782,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -8588,16 +8793,16 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "superstruct" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b986e4a629907f20a2c2a639a75bc22a8b5d99b444e0d83c395f4cb309022bf" +checksum = "bae4a9ccd7882533c1f210e400763ec6ee64c390fc12248c238276281863719e" dependencies = [ - "darling 0.20.11", - "itertools 0.13.0", + "darling 0.23.0", + "itertools 0.14.0", "proc-macro2", "quote", "smallvec", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -8623,9 +8828,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.110" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -8634,14 +8839,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.4.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff790eb176cc81bb8936aed0f7b9f14fc4670069a2d371b3e3b0ecce908b2cb3" +checksum = "53f425ae0b12e2f5ae65542e00898d500d4d318b4baf09f40fd0d410454e9947" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -8661,7 +8866,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -8681,11 +8886,11 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -8747,14 +8952,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.23.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", - "rustix 1.1.2", + "rustix 1.1.4", "windows-sys 0.52.0", ] @@ -8764,7 +8969,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ - "rustix 1.1.2", + "rustix 1.1.4", "windows-sys 0.60.2", ] @@ -8779,7 +8984,7 @@ name = "test_random_derive" version = "0.2.0" dependencies = [ "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -8793,11 +8998,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -8808,18 +9013,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -8873,30 +9078,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -8961,9 +9166,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -8976,9 +9181,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -8986,7 +9191,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.1", + "socket2 0.6.3", "tokio-macros", "tracing", "windows-sys 0.61.2", @@ -8994,23 +9199,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", + "syn 2.0.117", ] [[package]] @@ -9030,15 +9225,15 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.35", + "rustls 0.23.37", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -9048,9 +9243,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -9063,32 +9258,32 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "1.0.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.7" +version = "0.25.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.13.0", "toml_datetime", "toml_parser", - "winnow", + "winnow 1.0.0", ] [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" dependencies = [ - "winnow", + "winnow 1.0.0", ] [[package]] @@ -9102,8 +9297,8 @@ dependencies = [ "axum", "base64 0.22.1", "bytes", - "h2 0.4.12", - "http 1.3.1", + "h2 0.4.13", + "http 1.4.0", "http-body 1.0.1", "http-body-util", "hyper 1.8.1", @@ -9130,7 +9325,7 @@ dependencies = [ "async-trait", "base64 0.22.1", "bytes", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", "hyper 1.8.1", @@ -9143,7 +9338,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.4", "tokio-stream", - "tower 0.5.2", + "tower 0.5.3", "tower-layer", "tower-service", "tracing", @@ -9171,13 +9366,13 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap 2.12.0", + "indexmap 2.13.0", "pin-project-lite", "slab", "sync_wrapper", @@ -9190,18 +9385,18 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "bytes", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "iri-string", "pin-project-lite", - "tower 0.5.2", + "tower 0.5.3", "tower-layer", "tower-service", ] @@ -9220,9 +9415,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -9232,32 +9427,32 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" dependencies = [ "crossbeam-channel", - "thiserror 1.0.69", + "thiserror 2.0.18", "time", "tracing-subscriber", ] [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -9304,9 +9499,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", @@ -9325,9 +9520,9 @@ dependencies = [ [[package]] name = "tree_hash" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2db21caa355767db4fd6129876e5ae278a8699f4a6959b1e3e7aff610b532d52" +checksum = "f7fd51aa83d2eb83b04570808430808b5d24fdbf479a4d5ac5dee4a2e2dd2be4" dependencies = [ "alloy-primitives", "ethereum_hashing", @@ -9338,14 +9533,14 @@ dependencies = [ [[package]] name = "tree_hash_derive" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711cc655fcbb48384a87dc2bf641b991a15c5ad9afc3caa0b1ab1df3b436f70f" +checksum = "8840ad4d852e325d3afa7fde8a50b2412f89dce47d7eb291c0cc7f87cd040f38" dependencies = [ - "darling 0.21.3", + "darling 0.23.0", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -9401,7 +9596,7 @@ dependencies = [ "fixed_bytes", "hex", "int_to_bytes", - "itertools 0.10.5", + "itertools 0.14.0", "kzg", "maplit", "merkle_proof", @@ -9477,15 +9672,15 @@ checksum = "ccb97dac3243214f8d8507998906ca3e2e0b900bf9bf4870477f125b82e68f6e" [[package]] name = "unicase" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" @@ -9496,6 +9691,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -9543,14 +9744,15 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -9567,28 +9769,20 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -dependencies = [ - "getrandom 0.2.16", - "serde", -] - -[[package]] -name = "uuid" -version = "1.18.1" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.2", "js-sys", + "rand 0.10.0", + "serde_core", "wasm-bindgen", ] [[package]] name = "validator_client" -version = "8.0.1" +version = "8.1.2" dependencies = [ "account_utils", "beacon_node_fallback", @@ -9599,6 +9793,7 @@ dependencies = [ "doppelganger_service", "environment", "eth2", + "execution_layer", "fdlimit", "graffiti_file", "hyper 1.8.1", @@ -9659,7 +9854,7 @@ dependencies = [ "graffiti_file", "health_metrics", "initialized_validators", - "itertools 0.10.5", + "itertools 0.14.0", "lighthouse_validator_store", "lighthouse_version", "logging", @@ -9755,12 +9950,15 @@ dependencies = [ "bls", "either", "eth2", + "execution_layer", "futures", "graffiti_file", "logging", "parking_lot", "safe_arith", + "serde_json", "slot_clock", + "ssz_types", "task_executor", "tokio", "tracing", @@ -9898,18 +10096,27 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.105" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -9920,11 +10127,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.55" +version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -9933,9 +10141,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.105" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -9943,26 +10151,48 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.105" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.105" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "wasm-encoder", + "wasmparser", +] + [[package]] name = "wasm-streams" version = "0.4.2" @@ -9976,6 +10206,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "semver 1.0.27", +] + [[package]] name = "wasmtimer" version = "0.4.3" @@ -9992,9 +10234,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.82" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -10045,9 +10287,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] @@ -10109,12 +10351,14 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.53.0" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efc5cf48f83140dcaab716eeaea345f9e93d0018fb81162753a3f76c3397b538" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ - "windows-core 0.53.0", - "windows-targets 0.52.6", + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", ] [[package]] @@ -10130,13 +10374,12 @@ dependencies = [ ] [[package]] -name = "windows-core" -version = "0.53.0" +name = "windows-collections" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dcc5b895a6377f1ab9fa55acedab1fd5ac0db66ad1e6c7f47e28a22e446a5dd" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-result 0.1.2", - "windows-targets 0.52.6", + "windows-core", ] [[package]] @@ -10148,10 +10391,21 @@ dependencies = [ "windows-implement", "windows-interface", "windows-link", - "windows-result 0.4.1", + "windows-result", "windows-strings", ] +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.60.2" @@ -10160,7 +10414,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -10171,7 +10425,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -10181,12 +10435,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-result" -version = "0.1.2" +name = "windows-numerics" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-targets 0.52.6", + "windows-core", + "windows-link", ] [[package]] @@ -10300,6 +10555,15 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -10440,9 +10704,18 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" dependencies = [ "memchr", ] @@ -10459,9 +10732,91 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.13.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap 2.13.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver 1.0.27", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "workspace_members" @@ -10498,6 +10853,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "x509-cert" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" +dependencies = [ + "const-oid", + "der", + "spki", +] + [[package]] name = "x509-parser" version = "0.17.0" @@ -10511,7 +10877,24 @@ dependencies = [ "nom", "oid-registry", "rusticata-macros", - "thiserror 2.0.17", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 2.0.18", "time", ] @@ -10572,9 +10955,9 @@ dependencies = [ [[package]] name = "yamux" -version = "0.13.8" +version = "0.13.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deab71f2e20691b4728b349c6cee8fc7223880fa67b6b4f92225ec32225447e5" +checksum = "1991f6690292030e31b0144d73f5e8368936c58e45e7068254f7138b23b00672" dependencies = [ "futures", "log", @@ -10614,28 +10997,28 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -10655,7 +11038,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", "synstructure", ] @@ -10671,13 +11054,13 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -10710,7 +11093,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -10722,16 +11105,22 @@ dependencies = [ "arbitrary", "crc32fast", "flate2", - "indexmap 2.12.0", + "indexmap 2.13.0", "memchr", "zopfli", ] [[package]] name = "zlib-rs" -version = "0.5.4" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + +[[package]] +name = "zmij" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f936044d677be1a1168fae1d03b583a285a5dd9d8cbf7b24c23aa1fc775235" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zopfli" diff --git a/Cargo.toml b/Cargo.toml index 441490ee1b9..bb4290304c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,7 @@ members = [ "testing/ef_tests", "testing/execution_engine_integration", "testing/node_test_rig", + "testing/proof_engine", "testing/simulator", "testing/state_transition_vectors", "testing/validator_test_rig", @@ -91,7 +92,7 @@ resolver = "2" [workspace.package] edition = "2024" -version = "8.0.1" +version = "8.1.2" [workspace.dependencies] account_utils = { path = "common/account_utils" } @@ -100,13 +101,14 @@ alloy-dyn-abi = { version = "1", default-features = false } alloy-json-abi = { version = "1", default-features = false } alloy-network = { version = "1", default-features = false } alloy-primitives = { version = "1", default-features = false, features = ["rlp", "getrandom"] } -alloy-provider = { version = "1", default-features = false, features = ["reqwest"] } +alloy-provider = { version = "1", default-features = false, features = ["reqwest", "reqwest-rustls-tls"] } alloy-rlp = { version = "0.3", default-features = false } alloy-rpc-types-eth = { version = "1", default-features = false, features = ["serde"] } alloy-signer-local = { version = "1", default-features = false } anyhow = "1" arbitrary = { version = "1", features = ["derive"] } async-channel = "1.9.0" +async-stream = "0.3" axum = "0.7.7" beacon_chain = { path = "beacon_node/beacon_chain" } beacon_node = { path = "beacon_node" } @@ -126,7 +128,7 @@ clap_utils = { path = "common/clap_utils" } compare_fields = "0.1" console-subscriber = "0.4" context_deserialize = "0.2" -criterion = "0.5" +criterion = "0.8" delay_map = "0.4" deposit_contract = { path = "common/deposit_contract" } directory = { path = "common/directory" } @@ -166,7 +168,7 @@ http_api = { path = "beacon_node/http_api" } hyper = "1" initialized_validators = { path = "validator_client/initialized_validators" } int_to_bytes = { path = "consensus/int_to_bytes" } -itertools = "0.10" +itertools = "0.14" kzg = { path = "crypto/kzg" } libsecp256k1 = "0.7" lighthouse_network = { path = "beacon_node/lighthouse_network" } @@ -177,12 +179,13 @@ lockfile = { path = "common/lockfile" } log = "0.4" logging = { path = "common/logging" } logroller = "0.1.8" -lru = "0.12" +lru = "0.16" lru_cache = { path = "common/lru_cache" } malloc_utils = { path = "common/malloc_utils" } maplit = "1" merkle_proof = { path = "consensus/merkle_proof" } metrics = { path = "common/metrics" } + milhouse = { version = "0.9", default-features = false, features = ["context_deserialize"] } mockall = "0.13" mockall_double = "0.3" @@ -194,7 +197,7 @@ node_test_rig = { path = "testing/node_test_rig" } num_cpus = "1" once_cell = "1.17.1" opentelemetry = "0.30.0" -opentelemetry-otlp = { version = "0.30.0", features = ["grpc-tonic", "tls-roots"] } +opentelemetry-otlp = { version = "0.30.0", default-features = false, features = ["grpc-tonic", "tls-roots", "trace"] } opentelemetry_sdk = "0.30.0" operation_pool = { path = "beacon_node/operation_pool" } parking_lot = "0.12" @@ -213,11 +216,11 @@ reqwest = { version = "0.12", default-features = false, features = [ "json", "stream", "rustls-tls", - "native-tls-vendored", ] } +reqwest-eventsource = "0.6" ring = "0.17" rpds = "0.11" -rusqlite = { version = "0.28", features = ["bundled"] } +rusqlite = { version = "0.38", features = ["bundled"] } rust_eth_kzg = "0.9" safe_arith = "0.1" sensitive_url = { version = "0.1", features = ["serde"] } @@ -230,7 +233,7 @@ signing_method = { path = "validator_client/signing_method" } slasher = { path = "slasher", default-features = false } slashing_protection = { path = "validator_client/slashing_protection" } slot_clock = { path = "common/slot_clock" } -smallvec = { version = "1.11.2", features = ["arbitrary"] } +smallvec = { version = "1", features = ["arbitrary"] } snap = "1" ssz_types = { version = "0.14.0", features = ["context_deserialize", "runtime_types"] } state_processing = { path = "consensus/state_processing" } @@ -262,7 +265,7 @@ tree_hash_derive = "0.12.0" typenum = "1" types = { path = "consensus/types" } url = "2" -uuid = { version = "0.8", features = ["serde", "v4"] } +uuid = { version = "1", features = ["serde", "v4"] } validator_client = { path = "validator_client" } validator_dir = { path = "common/validator_dir" } validator_http_api = { path = "validator_client/http_api" } @@ -277,6 +280,7 @@ workspace_members = { path = "common/workspace_members" } xdelta3 = { git = "https://github.com/sigp/xdelta3-rs", rev = "4db64086bb02e9febb584ba93b9d16bb2ae3825a" } zeroize = { version = "1", features = ["zeroize_derive", "serde"] } zip = { version = "6.0", default-features = false, features = ["deflate"] } + zstd = "0.13" [profile.maxperf] @@ -291,3 +295,4 @@ debug = true [patch.crates-io] quick-protobuf = { git = "https://github.com/sigp/quick-protobuf.git", rev = "681f413312404ab6e51f0b46f39b0075c6f4ebfd" } +quinn = { git = "https://github.com/sigp/quinn", rev = "59af87979c8411864c1cb68613222f54ed2930a7" } diff --git a/Dockerfile b/Dockerfile index 8cc20ab000f..2b3923301c2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,7 @@ -FROM rust:1.88.0-bullseye AS builder -RUN apt-get update && apt-get -y upgrade && apt-get install -y cmake libclang-dev +FROM rust:1.91.0-bullseye AS builder +RUN apt-get update && apt-get -y upgrade && apt-get install -y cmake libclang-dev clang +ENV CC=clang +ENV CXX=clang++ ARG FEATURES ARG PROFILE=release ARG CARGO_USE_GIT_CLI=true diff --git a/Dockerfile.reproducible b/Dockerfile.reproducible index 903515373f8..c4526c73170 100644 --- a/Dockerfile.reproducible +++ b/Dockerfile.reproducible @@ -1,5 +1,5 @@ -# Define the Rust image as an argument with a default to x86_64 Rust 1.88 image based on Debian Bullseye -ARG RUST_IMAGE="rust:1.88-bullseye@sha256:8e3c421122bf4cd3b2a866af41a4dd52d87ad9e315fd2cb5100e87a7187a9816" +# Define the Rust image as an argument with a default to x86_64 Rust 1.91 image based on Debian Bullseye +ARG RUST_IMAGE="rust:1.91-bullseye@sha256:ed6afcf912afc6aeddf0d1ff0dc6894c9b1c8f865964ef3f533e3ea77a64ffea" FROM ${RUST_IMAGE} AS builder # Install specific version of the build dependencies diff --git a/Makefile b/Makefile index 9d08c3ebe18..5567955f774 100644 --- a/Makefile +++ b/Makefile @@ -177,7 +177,13 @@ build-release-tarballs: test-release: cargo nextest run --workspace --release --features "$(TEST_FEATURES)" \ --exclude ef_tests --exclude beacon_chain --exclude slasher --exclude network \ - --exclude http_api + --exclude http_api --exclude proof_engine_test + +# Runs the proof engine integration tests sequentially so they are not starved of +# CPU by the parallel test-release job. Each test spawns multiple beacon nodes and +# is sensitive to slot timing, so dedicated sequential execution is required. +test-proof-engine: + cargo nextest run -p proof_engine_test --release --test-threads 1 # Runs the full workspace tests in **debug**, without downloading any additional test @@ -186,6 +192,10 @@ test-debug: cargo nextest run --workspace --features "$(TEST_FEATURES)" \ --exclude ef_tests --exclude beacon_chain --exclude network --exclude http_api +# Runs the proof_engine_zkboost integration tests against a real (mock-backend) zkBoost server. +test-zkboost: + cargo nextest run --manifest-path testing/proof_engine_zkboost/Cargo.toml --release + # Runs cargo-fmt (linter). cargo-fmt: cargo fmt --all -- --check @@ -324,7 +334,7 @@ install-audit: cargo install --force cargo-audit audit-CI: - cargo audit + cargo audit --ignore RUSTSEC-2026-0049 # Runs cargo deny (check for banned crates, duplicate versions, and source restrictions) deny: install-deny deny-CI diff --git a/account_manager/Cargo.toml b/account_manager/Cargo.toml index 8dd50cbc6ee..05e6f125546 100644 --- a/account_manager/Cargo.toml +++ b/account_manager/Cargo.toml @@ -1,10 +1,7 @@ [package] name = "account_manager" version = { workspace = true } -authors = [ - "Paul Hauner ", - "Luke Anderson ", -] +authors = ["Paul Hauner ", "Luke Anderson "] edition = { workspace = true } [dependencies] diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index 5352814dd5d..796d62deca3 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -1,10 +1,7 @@ [package] name = "beacon_node" version = { workspace = true } -authors = [ - "Paul Hauner ", - "Age Manning ", "Age Manning ; @@ -428,6 +435,10 @@ pub struct BeaconChain { /// Maintains a record of which validators we've seen BLS to execution changes for. pub observed_bls_to_execution_changes: Mutex>, + /// Deduplication cache for execution proofs. + pub observed_execution_proofs: RwLock, + /// Persistent tracker of validators that signed invalid execution proofs. + pub invalid_proof_tracker: RwLock, /// Interfaces with the execution client. pub execution_layer: Option>, /// Stores information about the canonical head and finalized/justified checkpoints of the @@ -448,6 +459,12 @@ pub struct BeaconChain { /// A handler for events generated by the beacon chain. This is only initialized when the /// HTTP server is enabled. pub event_handler: Option>, + /// Internal event bus for test and simulation subscribers. + /// + /// Lazily initialised on the first call to [`Self::subscribe_internal_events`]; stays `None` + /// in production runs where no subscriber has ever registered, making all emission sites + /// (`if let Some(tx) = self.internal_event_tx.get()`) no-ops. + pub internal_event_tx: OnceLock>, /// Caches the attester shuffling for a given epoch and shuffling key root. pub shuffling_cache: RwLock, /// Caches the beacon block proposer shuffling for a given epoch and shuffling key root. @@ -630,6 +647,32 @@ impl BeaconChain { )?)) } + /// Load persisted ProofEngine state from disk, returning `None` if not found or corrupt. + pub fn load_proof_engine_state(store: BeaconStore) -> Option { + match store + .hot_db + .get_bytes(DBColumn::ProofEngine, PROOF_ENGINE_DB_KEY.as_slice()) + { + Ok(Some(bytes)) => { + match PersistedProofEngineState::from_bytes(&bytes, store.get_config()) { + Ok(persisted) => { + tracing::info!("Loaded ProofEngine state from disk"); + Some(persisted) + } + Err(e) => { + tracing::warn!(error = ?e, "Failed to decode ProofEngine state from disk, starting fresh"); + None + } + } + } + Ok(None) => None, + Err(e) => { + tracing::warn!(error = ?e, "Failed to read ProofEngine state from disk, starting fresh"); + None + } + } + } + /// Persists `self.op_pool` to disk. /// /// ## Notes @@ -648,6 +691,13 @@ impl BeaconChain { } /// Persists the custody information to disk. + pub fn persist_invalid_proof_tracker(&self) -> Result<(), Error> { + self.invalid_proof_tracker + .read() + .persist_to_store(&self.store) + .map_err(Error::DBError) + } + pub fn persist_custody_context(&self) -> Result<(), Error> { if !self.spec.is_peer_das_scheduled() { return Ok(()); @@ -4019,7 +4069,7 @@ impl BeaconChain { .block_processed(block_root); self.import_block_update_metrics_and_events( - block, + signed_block, block_root, block_time_imported, payload_verification_status, @@ -4307,12 +4357,14 @@ impl BeaconChain { fn import_block_update_metrics_and_events( &self, - block: BeaconBlockRef, + signed_block: Arc>, block_root: Hash256, block_time_imported: Duration, payload_verification_status: PayloadVerificationStatus, current_slot: Slot, ) { + let block = signed_block.message(); + // Only present some metrics for blocks from the previous epoch or later. // // This helps avoid noise in the metrics during sync. @@ -4344,14 +4396,15 @@ impl BeaconChain { ); } - if let Some(event_handler) = self.event_handler.as_ref() - && event_handler.has_block_subscribers() - { - event_handler.register(EventKind::Block(SseBlock { - slot: block.slot(), - block: block_root, - execution_optimistic: payload_verification_status.is_optimistic(), - })); + if let Some(event_handler) = self.event_handler.as_ref() { + // Emit Block event if there are block subscribers + if event_handler.has_block_subscribers() { + event_handler.register(EventKind::Block(SseBlock { + slot: block.slot(), + block: block_root, + execution_optimistic: payload_verification_status.is_optimistic(), + })); + } } // Do not trigger light_client server update producer for old blocks, to extra work @@ -7421,6 +7474,232 @@ impl BeaconChain { .custody_context() .custody_columns_for_epoch(epoch_opt, &self.spec) } + + /// Return all proof-engine buffer entries that are still missing sufficient proofs, + /// with `MissingProofInfo.root` replaced by the corresponding beacon block root. + /// + /// Entries whose `request_root → block_root` mapping is not yet in the store LRU cache + /// are filtered out (the block may not have been imported yet). + pub fn missing_execution_proofs(&self) -> Vec { + let Some(el) = self.execution_layer.as_ref() else { + return vec![]; + }; + let Some(pe) = el.proof_engine() else { + return vec![]; + }; + pe.missing_proofs() + .into_iter() + .filter_map(|mut info| { + let (block_root, slot) = self.store.get_block_root_by_request_root(&info.root)?; + info.root = block_root; + info.slot = slot; + Some(info) + }) + .collect() + } + + /// Get execution proofs associated with the given beacon block root (EIP-8025). + /// + /// Translates beacon block root → new_payload request root → proofs via the proof engine + /// state. Returns an empty vec if the node has no proof engine or if there are no proofs for + /// the requested block. + pub fn get_execution_proofs_by_block_root( + &self, + block_root: Hash256, + ) -> Vec { + let Some(proof_engine) = self + .execution_layer + .as_ref() + .and_then(|el| el.proof_engine()) + else { + return vec![]; + }; + let Some(request_root) = self.store.get_request_root_by_block_root(&block_root) else { + return vec![]; + }; + proof_engine.get_proofs_by_root(&request_root) + } + + /// Subscribe to the internal event bus. + /// + /// The broadcast channel is created lazily on the first call to this method. In production + /// runs where no subscriber registers, the channel is never created and + /// [`Self::internal_event_sender`] returns `None`, making all emission sites no-ops. + pub fn subscribe_internal_events( + &self, + ) -> tokio::sync::broadcast::Receiver { + self.internal_event_tx + .get_or_init(|| { + tokio::sync::broadcast::channel( + crate::internal_events::INTERNAL_EVENT_CHANNEL_CAPACITY, + ) + .0 + }) + .subscribe() + } + + /// Returns the internal event sender if any subscriber has registered, otherwise `None`. + /// + /// Use this to gate event construction: only build the event value when a subscriber exists. + pub fn internal_event_sender( + &self, + ) -> Option<&tokio::sync::broadcast::Sender> { + self.internal_event_tx.get() + } + + /// Send an event on the internal event bus. + /// + /// Does nothing if no subscriber has registered. Prefer calling this after checking + /// [`Self::internal_event_sender`]`.is_some()` so event construction is also skipped. + pub fn emit_internal_event(&self, event: InternalBeaconNodeEvent) { + if let Some(tx) = self.internal_event_tx.get() { + let _ = tx.send(event); + } + } + + /// Verify a signed execution proof (EIP-8025). + /// + /// This method: + /// 1. Verifies the BLS signature over the proof message using the supplied `validator_pubkey` + /// 2. Verifies the proof via the ProofEngine + /// 3. If the proof is valid, updates fork choice to mark the corresponding block as valid. + /// + /// # Returns + /// + /// `Ok((ProofStatus, Option<(Hash256, Slot)>))` on success, or an `ExecutionProofError` + /// if BLS or engine verification cannot be completed. + pub async fn verify_execution_proof( + self: &Arc, + signed_proof: Arc, + validator_pubkey: PublicKeyBytes, + ) -> Result<(ProofStatus, Option<(Hash256, Slot)>), Error> { + // Clone for moving into the BLS spawn closure — Arc clone is O(1). + let chain = self.clone(); + let signed_proof_for_bls = signed_proof.clone(); + + // BLS verification is cpu-bound; run it on a blocking thread. + self.spawn_blocking_handle( + move || { + let head = chain.canonical_head.cached_head(); + let fork_name = chain.spec.fork_name_at_slot::(head.head_slot()); + + verify_signed_execution_proof_signature::( + &signed_proof_for_bls, + &validator_pubkey, + fork_name, + chain.genesis_validators_root, + &chain.spec, + ) + }, + "verify_execution_proof_bls", + ) + .await??; + + // Record IGNORE-3 dedup only after confirming the signature is valid. + self.observed_execution_proofs + .write() + .observe_verification_attempt( + signed_proof.request_root(), + signed_proof.message.proof_type, + validator_pubkey, + ); + + // Step 2: ProofEngine verification + // The proof engine must be configured if we are receiving execution proofs, so if it's not available then that's an error. + let proof_engine = self + .execution_layer + .as_ref() + .ok_or(ExecutionProofError::NoExecutionLayer)? + .proof_engine() + .ok_or(ExecutionProofError::NoExecutionLayer)?; + + let verification_result = proof_engine.verify_execution_proof(&signed_proof).await?; + + // Step 3: Update the fork choice if the proof engine returns valid. + // The proof engine returns valid if the proof is valid and the criteria for the associated block root to be considered valid are met. + // The proof engine returns ACCEPTED if the proof is valid but block validity criteria are not met. + if verification_result.is_valid() || verification_result.is_accepted() { + let request_root = signed_proof.request_root(); + + let (block_root, slot) = self + .store + .get_block_root_by_request_root(&request_root) + .ok_or_else(|| ExecutionProofError::UnknownRequestRoot(request_root))?; + + // Record the proof as valid for IGNORE-2 dedup regardless of Valid vs Accepted — + // both statuses mean the proof content is correct. + self.observed_execution_proofs.write().observe_valid_proof( + request_root, + signed_proof.message.proof_type, + slot, + ); + + // Only update fork choice for fully valid proofs. Accepted means the proof + // verified but the criteria for marking the block valid are not yet met. + if verification_result.is_valid() { + debug!( + ?request_root, + ?block_root, + validator_index = signed_proof.validator_index, + proof_type = signed_proof.message.proof_type, + "Processing verified execution proof" + ); + + // Fork choice write lock must be taken on a blocking thread to avoid + // stalling the async runtime. + let chain = self.clone(); + let fc_result: Result<(), ForkChoiceError> = self + .spawn_blocking_handle( + move || { + chain + .canonical_head + .fork_choice_write_lock() + .on_valid_execution_payload(block_root) + }, + "verify_execution_proof_fork_choice_update", + ) + .await?; + + match fc_result { + Ok(()) => { + info!( + ?block_root, + ?request_root, + "Updated fork choice for verified proof" + ); + } + // There is a chance that a race condition occurs where the block has not been + // imported into fork choice yet. This is a benign condition that can be ignored + // caused by proof verification time < block execution time. + Err(ForkChoiceError::FailedToProcessValidExecutionPayload(ref msg)) + if msg.contains("NodeUnknown") => + { + warn!( + ?block_root, + ?request_root, + "Proof valid but block not yet in fork choice, skipping fc update" + ); + } + Err(e) => return Err(Error::ForkChoiceError(e)), + } + } + + return Ok((verification_result, Some((block_root, slot)))); + } + + // Ban the validator if the proof engine explicitly rejected the proof. + if verification_result == ProofStatus::Invalid { + self.invalid_proof_tracker + .write() + .record_invalid_proof(InvalidProofRecord { + validator_pubkey, + request_root: signed_proof.request_root(), + proof_type: signed_proof.message.proof_type, + }); + } + + Ok((verification_result, None)) + } } impl Drop for BeaconChain { @@ -7428,7 +7707,9 @@ impl Drop for BeaconChain { let drop = || -> Result<(), Error> { self.persist_fork_choice()?; self.persist_op_pool()?; - self.persist_custody_context() + self.persist_custody_context()?; + self.persist_proof_engine()?; + self.persist_invalid_proof_tracker() }; if let Err(e) = drop() { diff --git a/beacon_node/beacon_chain/src/bellatrix_readiness.rs b/beacon_node/beacon_chain/src/bellatrix_readiness.rs index 412870354b9..6e702ce2856 100644 --- a/beacon_node/beacon_chain/src/bellatrix_readiness.rs +++ b/beacon_node/beacon_chain/src/bellatrix_readiness.rs @@ -207,17 +207,19 @@ impl BeaconChain { // Use getBlockByNumber(0) to check that the block hash matches. // At present, Geth does not respond to engine_getPayloadBodiesByRange before genesis. - let execution_block = execution_layer - .get_block_by_number(BlockByNumberQuery::Tag("0x0")) - .await - .map_err(|e| Error::ExecutionLayerGetBlockByNumberFailed(Box::new(e)))? - .ok_or(Error::BlockHashMissingFromExecutionLayer(exec_block_hash))?; - - if execution_block.block_hash != exec_block_hash { - return Ok(GenesisExecutionPayloadStatus::BlockHashMismatch { - got: execution_block.block_hash, - expected: exec_block_hash, - }); + if execution_layer.engine().is_some() { + let execution_block = execution_layer + .get_block_by_number(BlockByNumberQuery::Tag("0x0")) + .await + .map_err(|e| Error::ExecutionLayerGetBlockByNumberFailed(Box::new(e)))? + .ok_or(Error::BlockHashMissingFromExecutionLayer(exec_block_hash))?; + + if execution_block.block_hash != exec_block_hash { + return Ok(GenesisExecutionPayloadStatus::BlockHashMismatch { + got: execution_block.block_hash, + expected: exec_block_hash, + }); + } } Ok(GenesisExecutionPayloadStatus::Correct(exec_block_hash)) diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index dc38fc1c292..99dd7094f2a 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -9,6 +9,7 @@ use crate::data_availability_checker::DataAvailabilityChecker; use crate::fork_choice_signal::ForkChoiceSignalTx; use crate::fork_revert::{reset_fork_choice_to_finalization, revert_to_fork_boundary}; use crate::graffiti_calculator::{GraffitiCalculator, GraffitiOrigin}; +use crate::invalid_proof_tracker::InvalidProofTracker; use crate::kzg_utils::build_data_column_sidecars; use crate::light_client_server_cache::LightClientServerCache; use crate::migrate::{BackgroundMigrator, MigratorConfig}; @@ -22,7 +23,7 @@ use crate::{ BeaconChain, BeaconChainTypes, BeaconForkChoiceStore, BeaconSnapshot, ServerSentEventHandler, }; use bls::Signature; -use execution_layer::ExecutionLayer; +use execution_layer::{ExecutionLayer, ForkchoiceState}; use fixed_bytes::FixedBytesExtended; use fork_choice::{ForkChoice, ResetPayloadStatuses}; use futures::channel::mpsc::Sender; @@ -41,7 +42,7 @@ use std::sync::Arc; use std::time::Duration; use store::{Error as StoreError, HotColdDB, ItemStore, KeyValueStoreOp}; use task_executor::{ShutdownReason, TaskExecutor}; -use tracing::{debug, error, info}; +use tracing::{debug, error, info, warn}; use types::data::CustodyIndex; use types::{ BeaconBlock, BeaconState, BlobSidecarList, ChainSpec, ColumnIndex, DataColumnSidecarList, @@ -916,6 +917,15 @@ where let genesis_validators_root = head_snapshot.beacon_state.genesis_validators_root(); let genesis_time = head_snapshot.beacon_state.genesis_time(); + let genesis_execution_block_hash = (head_snapshot.beacon_state.slot() == 0) + .then(|| { + head_snapshot + .beacon_state + .latest_execution_payload_header() + .ok() + .map(|header| header.block_hash()) + }) + .flatten(); let canonical_head = CanonicalHead::new(fork_choice, Arc::new(head_snapshot)); let shuffling_cache_size = self.chain_config.shuffling_cache_size; let complete_blob_backfill = self.chain_config.complete_blob_backfill; @@ -975,6 +985,34 @@ where }; debug!(?custody_context, "Loaded persisted custody context"); + // Restore ProofEngine state from disk if available, or seed from genesis on fresh start. + if let Some(proof_engine) = self + .execution_layer + .as_ref() + .and_then(|el| el.proof_engine()) + && let Some(store) = self.store + { + match crate::BeaconChain::>::load_proof_engine_state( + store.clone(), + ) { + Some(persisted) => proof_engine.restore_from_persisted(persisted), + None if genesis_execution_block_hash.is_some() => { + proof_engine + .forkchoice_updated(ForkchoiceState::new_genesis( + genesis_execution_block_hash.expect("is Some"), + )) + .map_err(|err| { + format!("failed to seed proof engine with genesis hash: {err:?}") + })?; + } + _ => { + warn!( + "No persisted ProofEngine state and head is not at genesis. ProofEngine may be out of sync until next fork choice update." + ); + } + } + } + let beacon_chain = BeaconChain { spec: self.spec.clone(), config: self.chain_config, @@ -1012,6 +1050,10 @@ where observed_proposer_slashings: <_>::default(), observed_attester_slashings: <_>::default(), observed_bls_to_execution_changes: <_>::default(), + observed_execution_proofs: <_>::default(), + invalid_proof_tracker: parking_lot::RwLock::new(InvalidProofTracker::load_from_store( + &store, + )), execution_layer: self.execution_layer.clone(), genesis_validators_root, genesis_time, @@ -1021,6 +1063,7 @@ where fork_choice_signal_tx, fork_choice_signal_rx, event_handler: self.event_handler, + internal_event_tx: std::sync::OnceLock::new(), shuffling_cache: RwLock::new(ShufflingCache::new( shuffling_cache_size, head_shuffling_ids, diff --git a/beacon_node/beacon_chain/src/canonical_head.rs b/beacon_node/beacon_chain/src/canonical_head.rs index 76c08c5e39c..6704cf7c8d0 100644 --- a/beacon_node/beacon_chain/src/canonical_head.rs +++ b/beacon_node/beacon_chain/src/canonical_head.rs @@ -42,6 +42,7 @@ use crate::{ validator_monitor::get_slot_delay_ms, }; use eth2::types::{EventKind, SseChainReorg, SseFinalizedCheckpoint, SseHead, SseLateHead}; +use execution_layer::eip8025::PROOF_ENGINE_DB_KEY; use fork_choice::{ ExecutionStatus, ForkChoiceStore, ForkChoiceView, ForkchoiceUpdateParameters, ProtoBlock, ResetPayloadStatuses, @@ -867,6 +868,7 @@ impl BeaconChain { if is_epoch_transition || reorg_distance.is_some() { self.persist_fork_choice()?; + self.persist_proof_engine()?; self.op_pool.prune_attestations(self.epoch()?); } @@ -958,6 +960,13 @@ impl BeaconChain { .start_slot(T::EthSpec::slots_per_epoch()), ); + self.observed_execution_proofs.write().prune( + new_view + .finalized_checkpoint + .epoch + .start_slot(T::EthSpec::slots_per_epoch()), + ); + if let Some(event_handler) = self.event_handler.as_ref() && event_handler.has_finalized_subscribers() { @@ -1047,6 +1056,23 @@ impl BeaconChain { .map_err(Into::into) } + /// Persist the proof engine to disk, writing immediately. + pub fn persist_proof_engine(&self) -> Result<(), Error> { + let Some(proof_engine) = self + .execution_layer + .as_ref() + .and_then(|el| el.proof_engine()) + else { + return Ok(()); + }; + + let op = proof_engine + .to_persisted() + .as_kv_store_op(PROOF_ENGINE_DB_KEY, self.store.get_config())?; + self.store.hot_db.do_atomically(vec![op])?; + Ok(()) + } + /// Return a database operation for writing fork choice to disk. pub fn persist_fork_choice_in_batch_standalone( fork_choice: &BeaconForkChoice, diff --git a/beacon_node/beacon_chain/src/custody_context.rs b/beacon_node/beacon_chain/src/custody_context.rs index c512ce616a1..d706dd99707 100644 --- a/beacon_node/beacon_chain/src/custody_context.rs +++ b/beacon_node/beacon_chain/src/custody_context.rs @@ -377,13 +377,10 @@ impl CustodyContext { current_slot: Slot, spec: &ChainSpec, ) -> Option { - let Some((effective_epoch, new_validator_custody)) = self + let (effective_epoch, new_validator_custody) = self .validator_registrations .write() - .register_validators::(validators_and_balance, current_slot, spec) - else { - return None; - }; + .register_validators::(validators_and_balance, current_slot, spec)?; let current_cgc = self.validator_custody_count.load(Ordering::Relaxed); diff --git a/beacon_node/beacon_chain/src/eip8025/mod.rs b/beacon_node/beacon_chain/src/eip8025/mod.rs new file mode 100644 index 00000000000..2b74f3e1048 --- /dev/null +++ b/beacon_node/beacon_chain/src/eip8025/mod.rs @@ -0,0 +1,13 @@ +//! EIP-8025: Optional Execution Proofs +//! +//! This module provides beacon chain integration for EIP-8025 optional execution proofs. +//! It includes: +//! - Proof verification logic using validator signatures +//! - TODO: integrate into proof engine + +pub mod proof_verification; + +pub use proof_verification::{ + ExecutionProofError, compute_execution_proof_domain, compute_signing_root, + verify_signed_execution_proof_signature, +}; diff --git a/beacon_node/beacon_chain/src/eip8025/proof_verification.rs b/beacon_node/beacon_chain/src/eip8025/proof_verification.rs new file mode 100644 index 00000000000..32c57e085e7 --- /dev/null +++ b/beacon_node/beacon_chain/src/eip8025/proof_verification.rs @@ -0,0 +1,456 @@ +//! EIP-8025 Proof Verification +//! +//! This module implements the proof verification logic for EIP-8025 optional execution proofs. +//! It provides: +//! - BLS signature verification for validator signatures +//! - Validator index validation against the BeaconState +//! - TODO: integration into proof engine for end-to-end verification + +use crate::BeaconChainError; +use execution_layer::eip8025::ProofEngineError; +use std::fmt; +use tree_hash::TreeHash; +use types::{ChainSpec, Domain, EthSpec, ForkName, Hash256, SignedExecutionProof, SigningData}; + +/// Errors that can occur during execution proof verification. +#[derive(Debug)] +pub enum ExecutionProofError { + /// The BLS signature is invalid. + InvalidSignature, + /// The proof data is empty. + EmptyProofData, + /// The validator index is out of range. + InvalidValidatorIndex, + /// Failed to decompress the validator's public key. + InvalidValidatorPubkey, + /// Failed to decompress the signature. + InvalidSignatureFormat, + /// Failed to retrieve beacon state. + StateError(String), + /// No execution layer configured. + NoExecutionLayer, + /// The request root referenced by the proof is not known. + UnknownRequestRoot(Hash256), + /// The was an error in the proof engine during verification. + ProofEngineError(ProofEngineError), +} + +impl fmt::Display for ExecutionProofError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ExecutionProofError::InvalidSignature => { + write!(f, "Invalid BLS signature") + } + ExecutionProofError::EmptyProofData => { + write!(f, "Proof data is empty") + } + ExecutionProofError::InvalidValidatorIndex => { + write!(f, "Validator index out of range") + } + ExecutionProofError::InvalidValidatorPubkey => { + write!(f, "Invalid validator public key format") + } + ExecutionProofError::InvalidSignatureFormat => { + write!(f, "Invalid signature format") + } + ExecutionProofError::StateError(msg) => { + write!(f, "Beacon state error: {}", msg) + } + ExecutionProofError::NoExecutionLayer => { + write!(f, "No execution layer configured") + } + ExecutionProofError::UnknownRequestRoot(root) => { + write!( + f, + "Unknown request root {:?}. Block may not be imported yet or was already finalized.", + root + ) + } + ExecutionProofError::ProofEngineError(engine_error) => { + write!(f, "Proof engine error: {:?}", engine_error) + } + } + } +} + +impl std::error::Error for ExecutionProofError {} + +/// Compute the signing root for an execution proof message. +/// +/// This function is public for use by the validator client when signing proofs. +pub fn compute_signing_root(message: &types::ExecutionProof, domain: Hash256) -> Hash256 { + SigningData { + object_root: message.tree_hash_root(), + domain, + } + .tree_hash_root() +} + +/// Compute the domain for execution proof signing. +/// +/// This function is public for use by the validator client when signing proofs. +pub fn compute_execution_proof_domain( + fork_name: ForkName, + genesis_validators_root: Hash256, + spec: &ChainSpec, +) -> Hash256 { + let fork_version = spec.fork_version_for_name(fork_name); + spec.compute_domain( + Domain::ExecutionProof, + fork_version, + genesis_validators_root, + ) +} + +// TODO: migrate into an impl on BeaconChain +/// Verify a validator's BLS signature over an execution proof. +/// +/// This function: +/// 1. Checks that the fork supports EIP-8025 +/// 2. Checks that proof data is not empty (max proof size should be enforced by ssz deserialization) +/// 3. Verifies the BLS signature over the proof message using the validator's pubkey +/// +/// # Arguments +/// +/// * `signed_proof` - The signed execution proof to verify +/// * `validator_pubkey` - The public key of the validator at the specified index +/// * `fork_name` - The current fork name +/// * `genesis_validators_root` - The genesis validators root for domain computation +/// * `spec` - The chain specification +/// +/// # Returns +/// +/// `Ok(())` if the proof is valid, otherwise an `ExecutionProofError`. +pub fn verify_signed_execution_proof_signature( + signed_proof: &SignedExecutionProof, + validator_pubkey: &bls::PublicKeyBytes, + fork_name: ForkName, + genesis_validators_root: Hash256, + spec: &ChainSpec, +) -> Result<(), BeaconChainError> { + // Check proof data is not empty + if signed_proof.message.proof_data.is_empty() { + Err(ExecutionProofError::EmptyProofData)?; + } + + // Decompress the validator's public key + let pubkey = validator_pubkey + .decompress() + .map_err(|_| ExecutionProofError::InvalidValidatorPubkey)?; + + // Decompress the signature using bls::SignatureBytes::decompress() + let signature = signed_proof + .signature + .decompress() + .map_err(|_| ExecutionProofError::InvalidSignatureFormat)?; + + // Get the domain for execution proof signing + let domain = compute_execution_proof_domain(fork_name, genesis_validators_root, spec); + + // Compute the signing root + let signing_root = compute_signing_root(&signed_proof.message, domain); + + // Verify the signature + if !signature.verify(&pubkey, signing_root) { + Err(ExecutionProofError::InvalidSignature)?; + } + + Ok(()) +} + +impl From for BeaconChainError { + fn from(engine_error: ProofEngineError) -> Self { + BeaconChainError::ExecutionProofError(ExecutionProofError::ProofEngineError(engine_error)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::BeaconChainError; + use bls::{Keypair, SignatureBytes}; + use ssz_types::VariableList; + use types::{ExecutionProof, MainnetEthSpec, PublicInput}; + + fn get_fulu_spec() -> ChainSpec { + ForkName::Fulu.make_genesis_spec(MainnetEthSpec::default_spec()) + } + + fn create_test_proof(proof_data: Vec) -> ExecutionProof { + ExecutionProof { + proof_data: VariableList::new(proof_data).unwrap(), + proof_type: 1, + public_input: PublicInput { + new_payload_request_root: Hash256::repeat_byte(0xab), + }, + } + } + + fn sign_proof( + proof: &ExecutionProof, + keypair: &Keypair, + fork_name: ForkName, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> SignedExecutionProof { + let domain = compute_execution_proof_domain(fork_name, genesis_validators_root, spec); + let signing_root = compute_signing_root(proof, domain); + let signature = keypair.sk.sign(signing_root); + + // Convert signature to bls::SignatureBytes + let sig_bytes = signature.serialize(); + let signature_vec: SignatureBytes = SignatureBytes::deserialize(&sig_bytes).unwrap(); + + SignedExecutionProof { + message: proof.clone(), + validator_index: 0, + signature: signature_vec, + } + } + + #[test] + fn test_verify_valid_signature() { + let keypair = Keypair::random(); + let spec = get_fulu_spec(); + let genesis_validators_root = Hash256::repeat_byte(0xcd); + let proof = create_test_proof(vec![1, 2, 3, 4]); + + let signed = sign_proof( + &proof, + &keypair, + ForkName::Fulu, + genesis_validators_root, + &spec, + ); + + let result = verify_signed_execution_proof_signature::( + &signed, + &keypair.pk.compress(), + ForkName::Fulu, + genesis_validators_root, + &spec, + ); + + assert!(result.is_ok()); + } + + #[test] + fn test_verify_invalid_signature() { + let keypair = Keypair::random(); + let wrong_keypair = Keypair::random(); + let spec = get_fulu_spec(); + let genesis_validators_root = Hash256::repeat_byte(0xcd); + let proof = create_test_proof(vec![1, 2, 3, 4]); + + // Sign with one keypair, verify with another + let signed = sign_proof( + &proof, + &keypair, + ForkName::Fulu, + genesis_validators_root, + &spec, + ); + + let result = verify_signed_execution_proof_signature::( + &signed, + &wrong_keypair.pk.compress(), + ForkName::Fulu, + genesis_validators_root, + &spec, + ); + + assert!(matches!( + result, + Err(BeaconChainError::ExecutionProofError( + ExecutionProofError::InvalidSignature + )) + )); + } + + #[test] + fn test_verify_empty_proof_data() { + let keypair = Keypair::random(); + let spec = get_fulu_spec(); + let genesis_validators_root = Hash256::repeat_byte(0xcd); + let proof = create_test_proof(vec![]); // Empty proof data + + let signed = sign_proof( + &proof, + &keypair, + ForkName::Fulu, + genesis_validators_root, + &spec, + ); + + let result = verify_signed_execution_proof_signature::( + &signed, + &keypair.pk.compress(), + ForkName::Fulu, + genesis_validators_root, + &spec, + ); + + assert!(matches!( + result, + Err(BeaconChainError::ExecutionProofError( + ExecutionProofError::EmptyProofData + )) + )); + } + + #[test] + fn test_verify_invalid_pubkey_format() { + let keypair = Keypair::random(); + let spec = get_fulu_spec(); + let genesis_validators_root = Hash256::repeat_byte(0xcd); + let proof = create_test_proof(vec![1, 2, 3, 4]); + + let signed = sign_proof( + &proof, + &keypair, + ForkName::Fulu, + genesis_validators_root, + &spec, + ); + + // Create invalid pubkey bytes (all zeros is not a valid point on the curve) + let invalid_pubkey = bls::PublicKeyBytes::empty(); + + let result = verify_signed_execution_proof_signature::( + &signed, + &invalid_pubkey, + ForkName::Fulu, + genesis_validators_root, + &spec, + ); + + assert!(matches!( + result, + Err(BeaconChainError::ExecutionProofError( + ExecutionProofError::InvalidValidatorPubkey + )) + )); + } + + #[test] + fn test_verify_invalid_signature_format() { + let keypair = Keypair::random(); + let spec = get_fulu_spec(); + let genesis_validators_root = Hash256::repeat_byte(0xcd); + let proof = create_test_proof(vec![1, 2, 3, 4]); + + // Create a signed proof with invalid signature bytes. + // BLS signatures are G2 points. Bytes 0xff repeated are not a valid + // compressed G2 point representation because they fail deserialization. + let invalid_signature = SignatureBytes::deserialize(&[0xff; 96]).unwrap(); + let signed = SignedExecutionProof { + message: proof, + validator_index: 0, + signature: invalid_signature, + }; + + let result = verify_signed_execution_proof_signature::( + &signed, + &keypair.pk.compress(), + ForkName::Fulu, + genesis_validators_root, + &spec, + ); + + assert!(matches!( + result, + Err(BeaconChainError::ExecutionProofError( + ExecutionProofError::InvalidSignatureFormat + )) + )); + } + + #[test] + fn test_compute_signing_root_deterministic() { + let proof = create_test_proof(vec![1, 2, 3, 4]); + let domain = Hash256::repeat_byte(0xaa); + + let root1 = compute_signing_root(&proof, domain); + let root2 = compute_signing_root(&proof, domain); + + assert_eq!(root1, root2); + } + + #[test] + fn test_compute_signing_root_different_inputs() { + let proof1 = create_test_proof(vec![1, 2, 3, 4]); + let proof2 = create_test_proof(vec![5, 6, 7, 8]); + let domain = Hash256::repeat_byte(0xaa); + + let root1 = compute_signing_root(&proof1, domain); + let root2 = compute_signing_root(&proof2, domain); + + assert_ne!(root1, root2); + } + + #[test] + fn test_compute_signing_root_different_domains() { + let proof = create_test_proof(vec![1, 2, 3, 4]); + let domain1 = Hash256::repeat_byte(0xaa); + let domain2 = Hash256::repeat_byte(0xbb); + + let root1 = compute_signing_root(&proof, domain1); + let root2 = compute_signing_root(&proof, domain2); + + assert_ne!(root1, root2); + } + + #[test] + fn test_compute_execution_proof_domain() { + let spec = get_fulu_spec(); + let genesis_validators_root = Hash256::repeat_byte(0xcd); + + let domain1 = + compute_execution_proof_domain(ForkName::Fulu, genesis_validators_root, &spec); + + // Domain should be deterministic + let domain2 = + compute_execution_proof_domain(ForkName::Fulu, genesis_validators_root, &spec); + assert_eq!(domain1, domain2); + + // Different genesis_validators_root should produce different domain + let different_root = Hash256::repeat_byte(0xef); + let domain3 = compute_execution_proof_domain(ForkName::Fulu, different_root, &spec); + assert_ne!(domain1, domain3); + } + + #[test] + fn test_verify_with_different_genesis_validators_root() { + let keypair = Keypair::random(); + let spec = get_fulu_spec(); + let genesis_validators_root = Hash256::repeat_byte(0xcd); + let different_root = Hash256::repeat_byte(0xef); + let proof = create_test_proof(vec![1, 2, 3, 4]); + + // Sign with one genesis_validators_root + let signed = sign_proof( + &proof, + &keypair, + ForkName::Fulu, + genesis_validators_root, + &spec, + ); + + // Verify with different genesis_validators_root + let result = verify_signed_execution_proof_signature::( + &signed, + &keypair.pk.compress(), + ForkName::Fulu, + different_root, + &spec, + ); + + // Should fail because domain computation uses genesis_validators_root + assert!(matches!( + result, + Err(BeaconChainError::ExecutionProofError( + ExecutionProofError::InvalidSignature + )) + )); + } +} diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index 816e75fd242..9315db37c2d 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -1,4 +1,3 @@ -use crate::beacon_block_streamer::Error as BlockStreamerError; use crate::beacon_chain::ForkChoiceError; use crate::beacon_fork_choice_store::Error as ForkChoiceStoreError; use crate::data_availability_checker::AvailabilityCheckError; @@ -8,6 +7,7 @@ use crate::observed_aggregates::Error as ObservedAttestationsError; use crate::observed_attesters::Error as ObservedAttestersError; use crate::observed_block_producers::Error as ObservedBlockProducersError; use crate::observed_data_sidecars::Error as ObservedDataSidecarsError; +use crate::{beacon_block_streamer::Error as BlockStreamerError, eip8025::ExecutionProofError}; use bls::PublicKeyBytes; use execution_layer::PayloadStatus; use fork_choice::ExecutionStatus; @@ -248,6 +248,7 @@ pub enum BeaconChainError { }, SkipProposerPreparation, FailedColumnCustodyInfoUpdate, + ExecutionProofError(ExecutionProofError), } easy_from_to!(SlotProcessingError, BeaconChainError); @@ -277,6 +278,7 @@ easy_from_to!(EpochCacheError, BeaconChainError); easy_from_to!(LightClientError, BeaconChainError); easy_from_to!(MilhouseError, BeaconChainError); easy_from_to!(AttestationError, BeaconChainError); +easy_from_to!(ExecutionProofError, BeaconChainError); #[derive(Debug)] pub enum BlockProductionError { diff --git a/beacon_node/beacon_chain/src/events.rs b/beacon_node/beacon_chain/src/events.rs index 63be944eea2..4684db96ba9 100644 --- a/beacon_node/beacon_chain/src/events.rs +++ b/beacon_node/beacon_chain/src/events.rs @@ -1,4 +1,6 @@ -pub use eth2::types::{EventKind, SseBlock, SseFinalizedCheckpoint, SseHead}; +pub use eth2::types::{ + EventKind, SseBlock, SseExecutionProofValidated, SseFinalizedCheckpoint, SseHead, +}; use tokio::sync::broadcast; use tokio::sync::broadcast::{Receiver, Sender, error::SendError}; use tracing::trace; @@ -26,6 +28,7 @@ pub struct ServerSentEventHandler { attester_slashing_tx: Sender>, bls_to_execution_change_tx: Sender>, block_gossip_tx: Sender>, + execution_proof_validated_tx: Sender>, } impl ServerSentEventHandler { @@ -53,6 +56,7 @@ impl ServerSentEventHandler { let (attester_slashing_tx, _) = broadcast::channel(capacity); let (bls_to_execution_change_tx, _) = broadcast::channel(capacity); let (block_gossip_tx, _) = broadcast::channel(capacity); + let (execution_proof_validated_tx, _) = broadcast::channel(capacity); Self { attestation_tx, @@ -74,6 +78,7 @@ impl ServerSentEventHandler { attester_slashing_tx, bls_to_execution_change_tx, block_gossip_tx, + execution_proof_validated_tx, } } @@ -162,6 +167,10 @@ impl ServerSentEventHandler { .block_gossip_tx .send(kind) .map(|count| log_count("block gossip", count)), + EventKind::ExecutionProofValidated(_) => self + .execution_proof_validated_tx + .send(kind) + .map(|count| log_count("execution proof validated", count)), }; if let Err(SendError(event)) = result { trace!(?event, "No receivers registered to listen for event"); @@ -311,4 +320,12 @@ impl ServerSentEventHandler { pub fn has_block_gossip_subscribers(&self) -> bool { self.block_gossip_tx.receiver_count() > 0 } + + pub fn subscribe_execution_proof_validated(&self) -> Receiver> { + self.execution_proof_validated_tx.subscribe() + } + + pub fn has_execution_proof_validated_subscribers(&self) -> bool { + self.execution_proof_validated_tx.receiver_count() > 0 + } } diff --git a/beacon_node/beacon_chain/src/execution_payload.rs b/beacon_node/beacon_chain/src/execution_payload.rs index 9459b1acd7d..a14c6e302f1 100644 --- a/beacon_node/beacon_chain/src/execution_payload.rs +++ b/beacon_node/beacon_chain/src/execution_payload.rs @@ -136,7 +136,26 @@ async fn notify_new_payload( .ok_or(ExecutionPayloadError::NoExecutionConnection)?; let execution_block_hash = block.execution_payload()?.block_hash(); - let new_payload_response = execution_layer.notify_new_payload(block.try_into()?).await; + let new_payload_request: NewPayloadRequest<'_, ::EthSpec> = + block.try_into()?; + let new_payload_request_root = new_payload_request.tree_hash_root(); + let block_root = block.tree_hash_root(); + let new_payload_response = execution_layer + .notify_new_payload(new_payload_request) + .await; + + // Store bidirectional mapping for EIP-8025 execution proofs + // This enables proofs to be mapped to beacon blocks for fork choice updates + // TODO: If we store proofs in Store then we can remove the need for this mapping and just store the block root in the proof. + // TODO: We should consider if this is the optimal mapping. We could consider using the execution block hash. + debug!( + ?block_root, + ?new_payload_request_root, + "Stored request_root mapping in cache" + ); + chain + .store + .put_request_root_mapping(new_payload_request_root, block_root, block.slot()); match new_payload_response { Ok(status) => match status { diff --git a/beacon_node/beacon_chain/src/internal_events.rs b/beacon_node/beacon_chain/src/internal_events.rs new file mode 100644 index 00000000000..12f5e2e573d --- /dev/null +++ b/beacon_node/beacon_chain/src/internal_events.rs @@ -0,0 +1,49 @@ +//! Internal event bus for `BeaconChain`. +//! +//! Variants wrap the actual message and result types that already flow through the node — +//! no new summary types are introduced. The channel is lazily initialised on the first call to +//! [`BeaconChain::subscribe_internal_events`], so there is zero overhead in production runs where +//! no subscriber is ever registered. + +use std::sync::Arc; +use types::execution::eip8025::{ProofByRootIdentifier, ProofStatus, SignedExecutionProof}; +use types::{Hash256, Slot}; + +/// Capacity of the internal event broadcast channel. +/// +/// The channel is lazily initialised only when a subscriber calls +/// [`BeaconChain::subscribe_internal_events`], so this has no overhead in +/// production where no subscriber is ever registered. The value is sized +/// generously to absorb the burst of events emitted when a late-joining node +/// rapidly imports a backlog of blocks during sync. +pub const INTERNAL_EVENT_CHANNEL_CAPACITY: usize = 16384; + +/// Event emitted on the per-node internal event bus. +/// +/// Each variant wraps the real message or result type that is already present at the emission +/// site, so tests can inspect the full payload without lossy conversion. +#[derive(Debug, Clone)] +pub enum InternalBeaconNodeEvent { + /// A `SignedExecutionProof` arrived via gossip, before dedup/verification. + GossipExecutionProof(Arc), + /// A `SignedExecutionProof` arrived via RPC sync, before dedup/verification. + RpcExecutionProof(Arc), + /// An outbound `ExecutionProofsByRange` RPC request was sent to a peer. + OutboundExecutionProofsByRange { start_slot: Slot, count: u64 }, + /// An outbound `ExecutionProofsByRoot` RPC request was sent to a peer. + OutboundExecutionProofsByRoot { + identifiers: Vec, + }, + /// `verify_execution_proof` completed; carries the status and, when the block is known, + /// its root and slot. + ExecutionProofVerified { + request_root: Hash256, + status: ProofStatus, + block: Option<(Hash256, Slot)>, + }, + /// `verify_execution_proof` returned an error. + ExecutionProofVerificationFailed { + request_root: Hash256, + error: String, + }, +} diff --git a/beacon_node/beacon_chain/src/invalid_proof_tracker.rs b/beacon_node/beacon_chain/src/invalid_proof_tracker.rs new file mode 100644 index 00000000000..ea94ce029c8 --- /dev/null +++ b/beacon_node/beacon_chain/src/invalid_proof_tracker.rs @@ -0,0 +1,343 @@ +//! Persistent tracker for validators that sign invalid execution proofs. +//! +//! When `ProofStatus::Invalid` is returned for a BLS-valid proof, the signing validator is +//! recorded here. Future proofs from banned validators are ignored without wasting +//! verification resources. +//! +//! Design decisions: +//! - Ban threshold: 1 (a single signed invalid proof is sufficient) +//! - Ban scope: all proof types from the banned validator + +use bls::PublicKeyBytes; +use ssz::{Decode, Encode}; +use ssz_derive::{Decode as DeriveDecode, Encode as DeriveEncode}; +use std::collections::HashSet; +use std::sync::Arc; +use store::{DBColumn, Error as StoreError, HotColdDB, ItemStore, StoreItem}; +use types::{EthSpec, Hash256}; + +/// 32-byte key for accessing the persisted tracker. All zero because the column acts as namespace. +pub const INVALID_PROOF_TRACKER_DB_KEY: Hash256 = Hash256::ZERO; + +/// Tracks validators that have signed invalid execution proofs. +/// +/// The in-memory set is the source of truth during operation. Changes are persisted +/// to `HotColdDB` so bans survive restarts. +/// +/// Validators are identified by their public key (48-byte compressed BLS key). +#[derive(Debug, Default)] +pub struct InvalidProofTracker { + /// Set of validator public keys that are banned (signed at least one invalid proof). + banned_validators: HashSet, +} + +/// Information recorded when a validator is banned. +#[derive(Debug, Clone)] +pub struct InvalidProofRecord { + pub validator_pubkey: PublicKeyBytes, + pub request_root: Hash256, + pub proof_type: u8, +} + +/// SSZ-serializable wrapper for persisting the banned validator set. +/// +/// Each entry is a 48-byte compressed BLS public key, serialised as a flat `Vec>`. +/// We store the keys as raw byte vectors because `PublicKeyBytes` is a fixed-size 48-byte +/// array that SSZ-encodes as a fixed-length container, and wrapping in `Vec` gives us +/// a straightforward variable-length list for the outer container. +#[derive(Debug, Clone, DeriveEncode, DeriveDecode)] +struct PersistedInvalidProofTracker { + /// Sorted list of banned validator public keys (each 48 bytes). + banned_validators: Vec>, +} + +impl StoreItem for PersistedInvalidProofTracker { + fn db_column() -> DBColumn { + DBColumn::InvalidProofTracker + } + + fn as_store_bytes(&self) -> Vec { + self.as_ssz_bytes() + } + + fn from_store_bytes(bytes: &[u8]) -> Result { + Self::from_ssz_bytes(bytes).map_err(Into::into) + } +} + +impl InvalidProofTracker { + /// Load a tracker from the database. Returns `Default` if no persisted state exists. + pub fn load_from_store, Cold: ItemStore>( + store: &Arc>, + ) -> Self { + match store.get_item::(&INVALID_PROOF_TRACKER_DB_KEY) { + Ok(Some(persisted)) => { + let banned_validators: HashSet = persisted + .banned_validators + .into_iter() + .filter_map(|bytes| { + PublicKeyBytes::deserialize(&bytes) + .map_err(|e| { + tracing::warn!( + error = ?e, + "Skipping invalid pubkey bytes in persisted tracker" + ); + e + }) + .ok() + }) + .collect(); + let count = banned_validators.len(); + if count > 0 { + tracing::info!( + count, + "Loaded invalid proof tracker from disk — {} validators banned", + count, + ); + } + InvalidProofTracker { banned_validators } + } + Ok(None) => { + tracing::debug!("No persisted invalid proof tracker found, starting fresh"); + InvalidProofTracker::default() + } + Err(e) => { + tracing::warn!( + error = ?e, + "Failed to load invalid proof tracker from disk, starting fresh" + ); + InvalidProofTracker::default() + } + } + } + + /// Persist the current state to the database. + pub fn persist_to_store, Cold: ItemStore>( + &self, + store: &Arc>, + ) -> Result<(), StoreError> { + let mut sorted: Vec> = self + .banned_validators + .iter() + .map(|pk| pk.serialize().to_vec()) + .collect(); + sorted.sort_unstable(); + let persisted = PersistedInvalidProofTracker { + banned_validators: sorted, + }; + store.put_item(&INVALID_PROOF_TRACKER_DB_KEY, &persisted) + } + + /// Check whether a validator is banned. + pub fn is_banned(&self, validator_pubkey: &PublicKeyBytes) -> bool { + self.banned_validators.contains(validator_pubkey) + } + + /// Record that a validator signed an invalid proof. Returns `true` if this is a new ban. + /// + /// Note: The caller is responsible for calling `persist_to_store` after this method + /// to ensure the ban survives restarts. + pub fn record_invalid_proof(&mut self, record: InvalidProofRecord) -> bool { + let is_new = self.banned_validators.insert(record.validator_pubkey); + if is_new { + tracing::warn!( + validator_pubkey = ?record.validator_pubkey, + ?record.request_root, + proof_type = record.proof_type, + "Banning validator for signing invalid execution proof" + ); + } + is_new + } + + /// Unban a specific validator. + /// + /// Note: The caller is responsible for calling `persist_to_store` after this method. + pub fn unban(&mut self, validator_pubkey: &PublicKeyBytes) -> bool { + self.banned_validators.remove(validator_pubkey) + } + + /// Clear all bans. + /// + /// Note: The caller is responsible for calling `persist_to_store` after this method. + pub fn clear(&mut self) { + self.banned_validators.clear(); + } + + /// Number of banned validators (for metrics / tests). + pub fn banned_count(&self) -> usize { + self.banned_validators.len() + } + + /// List all banned validator public keys. + pub fn banned_validators(&self) -> impl Iterator + '_ { + self.banned_validators.iter() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Generate a deterministic pubkey from a seed index using the standard test utility. + fn test_pubkey(index: usize) -> PublicKeyBytes { + types::test_utils::generate_deterministic_keypair(index) + .pk + .compress() + } + + fn make_record(seed: usize) -> InvalidProofRecord { + InvalidProofRecord { + validator_pubkey: test_pubkey(seed), + request_root: Hash256::repeat_byte(0x01), + proof_type: 1, + } + } + + #[test] + fn ban_on_first_invalid_proof() { + let mut tracker = InvalidProofTracker::default(); + let pk = test_pubkey(42); + assert!(!tracker.is_banned(&pk)); + + let is_new = tracker.record_invalid_proof(make_record(42)); + assert!(is_new); + assert!(tracker.is_banned(&pk)); + } + + #[test] + fn duplicate_ban_returns_false() { + let mut tracker = InvalidProofTracker::default(); + tracker.record_invalid_proof(make_record(42)); + + let is_new = tracker.record_invalid_proof(make_record(42)); + assert!(!is_new); + assert_eq!(tracker.banned_count(), 1); + } + + #[test] + fn unban_removes_validator() { + let mut tracker = InvalidProofTracker::default(); + let pk = test_pubkey(42); + tracker.record_invalid_proof(make_record(42)); + + assert!(tracker.unban(&pk)); + assert!(!tracker.is_banned(&pk)); + } + + #[test] + fn clear_removes_all() { + let mut tracker = InvalidProofTracker::default(); + tracker.record_invalid_proof(make_record(1)); + tracker.record_invalid_proof(make_record(2)); + tracker.record_invalid_proof(make_record(3)); + + tracker.clear(); + assert_eq!(tracker.banned_count(), 0); + } + + #[test] + fn ban_scope_is_all_types() { + let mut tracker = InvalidProofTracker::default(); + let pk = test_pubkey(42); + // Ban was recorded for proof_type=1, but ban is key-scoped, not type-scoped + tracker.record_invalid_proof(make_record(42)); + // is_banned doesn't take proof_type — all types are banned + assert!(tracker.is_banned(&pk)); + } + + #[test] + fn ssz_round_trip() { + let mut tracker = InvalidProofTracker::default(); + tracker.record_invalid_proof(make_record(10)); + tracker.record_invalid_proof(make_record(20)); + tracker.record_invalid_proof(make_record(5)); + + // Serialize + let mut sorted: Vec> = tracker + .banned_validators() + .map(|pk| pk.serialize().to_vec()) + .collect(); + sorted.sort_unstable(); + let persisted = PersistedInvalidProofTracker { + banned_validators: sorted.clone(), + }; + let bytes = persisted.as_store_bytes(); + + // Deserialize + let restored = + PersistedInvalidProofTracker::from_store_bytes(&bytes).expect("SSZ decode failed"); + assert_eq!(restored.banned_validators, sorted); + } + + /// Helper: create an ephemeral MemoryStore for persistence tests. + fn open_test_store() -> Arc< + store::HotColdDB< + types::MinimalEthSpec, + store::MemoryStore, + store::MemoryStore, + >, + > { + Arc::new( + store::HotColdDB::open_ephemeral( + store::config::StoreConfig::default(), + Arc::new(types::MinimalEthSpec::default_spec()), + ) + .expect("Failed to open ephemeral store"), + ) + } + + #[test] + fn empty_start_fallback() { + // Loading from an empty store should return a default (empty) tracker. + let store = open_test_store(); + let tracker = InvalidProofTracker::load_from_store(&store); + assert_eq!(tracker.banned_count(), 0); + assert!(!tracker.is_banned(&test_pubkey(1))); + } + + #[test] + fn persist_and_reload() { + let store = open_test_store(); + + // Ban some validators and persist. + let mut tracker = InvalidProofTracker::default(); + tracker.record_invalid_proof(make_record(10)); + tracker.record_invalid_proof(make_record(20)); + tracker.record_invalid_proof(make_record(5)); + tracker.persist_to_store(&store).expect("Failed to persist"); + + // Drop the tracker and reload from the same store — simulates restart. + drop(tracker); + let reloaded = InvalidProofTracker::load_from_store(&store); + + assert_eq!(reloaded.banned_count(), 3); + assert!(reloaded.is_banned(&test_pubkey(5))); + assert!(reloaded.is_banned(&test_pubkey(10))); + assert!(reloaded.is_banned(&test_pubkey(20))); + assert!(!reloaded.is_banned(&test_pubkey(99))); + } + + #[test] + fn persist_after_unban_survives_reload() { + let store = open_test_store(); + + // Ban two validators. + let mut tracker = InvalidProofTracker::default(); + tracker.record_invalid_proof(make_record(10)); + tracker.record_invalid_proof(make_record(20)); + tracker.persist_to_store(&store).expect("Failed to persist"); + + // Unban one and re-persist. + tracker.unban(&test_pubkey(10)); + tracker + .persist_to_store(&store) + .expect("Failed to persist after unban"); + + // Reload — should reflect the unban. + let reloaded = InvalidProofTracker::load_from_store(&store); + assert_eq!(reloaded.banned_count(), 1); + assert!(!reloaded.is_banned(&test_pubkey(10))); + assert!(reloaded.is_banned(&test_pubkey(20))); + } +} diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index f92030a6714..80496900109 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -20,6 +20,7 @@ pub mod custody_context; pub mod data_availability_checker; pub mod data_column_verification; mod early_attester_cache; +pub mod eip8025; mod errors; pub mod events; pub mod execution_payload; @@ -29,6 +30,8 @@ pub mod fork_revert; pub mod graffiti_calculator; pub mod historical_blocks; pub mod historical_data_columns; +pub mod internal_events; +pub mod invalid_proof_tracker; pub mod kzg_utils; pub mod light_client_finality_update_verification; pub mod light_client_optimistic_update_verification; @@ -40,6 +43,7 @@ pub mod observed_aggregates; mod observed_attesters; pub mod observed_block_producers; pub mod observed_data_sidecars; +pub mod observed_execution_proofs; pub mod observed_operations; mod observed_slashable; pub mod persisted_beacon_chain; diff --git a/beacon_node/beacon_chain/src/observed_execution_proofs.rs b/beacon_node/beacon_chain/src/observed_execution_proofs.rs new file mode 100644 index 00000000000..0a2ecb74b0f --- /dev/null +++ b/beacon_node/beacon_chain/src/observed_execution_proofs.rs @@ -0,0 +1,208 @@ +//! Deduplication cache for execution proofs received via gossip. +//! +//! Implements IGNORE-2 and IGNORE-3 from the EIP-8025 p2p-interface spec: +//! - IGNORE-2: No valid proof already received for `(request_root, proof_type)` +//! - IGNORE-3: First proof from validator for `(request_root, proof_type, validator_index)` +//! +//! Entries are evicted at finalization: proofs for finalized blocks are irrelevant. + +use bls::PublicKeyBytes; +use std::collections::{HashMap, HashSet}; +use types::{Hash256, ProofType, Slot}; + +/// Gossip deduplication cache for execution proofs. +/// +/// Checked *before* BLS/proof-engine verification to avoid redundant work. +#[derive(Debug, Default)] +pub struct ObservedExecutionProofs { + /// Tracks `(request_root, proof_type)` pairs for which we already have a *valid* proof. + /// Used to implement IGNORE-2. + valid_proofs: HashMap<(Hash256, ProofType), ()>, + + /// Tracks `(request_root, proof_type, validator_pubkey)` triples we have already attempted + /// to verify (regardless of outcome). Used to implement IGNORE-3. + seen_from_validator: HashSet<(Hash256, ProofType, PublicKeyBytes)>, + + /// Maps slot → set of request roots observed at that slot. Populated when a valid/accepted + /// proof is observed. Used to prune `valid_proofs` and `seen_from_validator` at finalization. + slot_to_request_roots: HashMap>, +} + +/// Result of checking the dedup cache. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ProofObservation { + /// We already have a valid proof for this `(request_root, proof_type)` — IGNORE-2. + AlreadyHaveValidProof, + /// We already saw a proof from this validator for this `(request_root, proof_type)` — IGNORE-3. + DuplicateFromValidator, + /// First time seeing this proof — proceed with verification. + New, +} + +impl ObservedExecutionProofs { + /// Check whether a proof should be processed or ignored based on the dedup rules. + /// + /// This does *not* insert the proof into the cache; call [`observe_verification_attempt`] + /// and [`observe_valid_proof`] after verification completes. + pub fn check( + &self, + request_root: Hash256, + proof_type: ProofType, + validator_pubkey: &PublicKeyBytes, + ) -> ProofObservation { + // IGNORE-2: already have a valid proof for this (root, type) + if self.valid_proofs.contains_key(&(request_root, proof_type)) { + return ProofObservation::AlreadyHaveValidProof; + } + + // IGNORE-3: already saw a proof from this validator for this (root, type) + if self + .seen_from_validator + .contains(&(request_root, proof_type, *validator_pubkey)) + { + return ProofObservation::DuplicateFromValidator; + } + + ProofObservation::New + } + + /// Record that we attempted to verify a proof from this validator. + /// Must be called for every verification attempt, regardless of outcome. + pub fn observe_verification_attempt( + &mut self, + request_root: Hash256, + proof_type: ProofType, + validator_pubkey: PublicKeyBytes, + ) { + self.seen_from_validator + .insert((request_root, proof_type, validator_pubkey)); + } + + /// Record that a valid proof was received for `(request_root, proof_type)` at `slot`. + pub fn observe_valid_proof( + &mut self, + request_root: Hash256, + proof_type: ProofType, + slot: Slot, + ) { + self.valid_proofs.insert((request_root, proof_type), ()); + self.slot_to_request_roots + .entry(slot) + .or_default() + .insert(request_root); + } + + /// Prune entries for request roots whose slot is at or below `finalized_slot`. + /// + /// Call at finalization. Any proof for a finalized block will never need dedup again. + /// Entries in `seen_from_validator` without a known slot (e.g. for proofs that failed + /// BLS or engine verification) are retained — those validators are typically banned anyway. + pub fn prune(&mut self, finalized_slot: Slot) { + let pruned_roots: HashSet = self + .slot_to_request_roots + .extract_if(|&slot, _| slot <= finalized_slot) + .flat_map(|(_, roots)| roots) + .collect(); + self.valid_proofs + .retain(|(root, _), _| !pruned_roots.contains(root)); + self.seen_from_validator + .retain(|(root, _, _)| !pruned_roots.contains(root)); + } + + /// Number of valid-proof entries (for metrics / tests). + pub fn valid_proof_count(&self) -> usize { + self.valid_proofs.len() + } + + /// Number of seen-from-validator entries (for metrics / tests). + pub fn seen_from_validator_count(&self) -> usize { + self.seen_from_validator.len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Generate a deterministic pubkey from a seed index using the standard test utility. + fn test_pubkey(index: usize) -> PublicKeyBytes { + types::test_utils::generate_deterministic_keypair(index) + .pk + .compress() + } + + #[test] + fn new_proof_is_observed() { + let cache = ObservedExecutionProofs::default(); + let root = Hash256::repeat_byte(0x01); + let pk = test_pubkey(42); + assert_eq!(cache.check(root, 1, &pk), ProofObservation::New); + } + + #[test] + fn ignore_2_valid_proof_dedup() { + let mut cache = ObservedExecutionProofs::default(); + let root = Hash256::repeat_byte(0x01); + let pk = test_pubkey(99); + + cache.observe_valid_proof(root, 1, Slot::new(1)); + + // Same (root, type) from a different validator → still IGNORE + assert_eq!( + cache.check(root, 1, &pk), + ProofObservation::AlreadyHaveValidProof + ); + + // Different type → New + assert_eq!(cache.check(root, 2, &pk), ProofObservation::New); + } + + #[test] + fn ignore_3_validator_dedup() { + let mut cache = ObservedExecutionProofs::default(); + let root = Hash256::repeat_byte(0x01); + let pk_42 = test_pubkey(42); + let pk_43 = test_pubkey(43); + + cache.observe_verification_attempt(root, 1, pk_42); + + assert_eq!( + cache.check(root, 1, &pk_42), + ProofObservation::DuplicateFromValidator + ); + + // Same validator, different type → New + assert_eq!(cache.check(root, 2, &pk_42), ProofObservation::New); + + // Different validator, same type → New + assert_eq!(cache.check(root, 1, &pk_43), ProofObservation::New); + } + + #[test] + fn prune_removes_finalized_roots() { + let mut cache = ObservedExecutionProofs::default(); + let root_a = Hash256::repeat_byte(0x01); + let root_b = Hash256::repeat_byte(0x02); + let pk_42 = test_pubkey(42); + let pk_43 = test_pubkey(43); + let pk_99 = test_pubkey(99); + + // root_a at slot 10 (will be finalized), root_b at slot 20 (will be retained). + cache.observe_valid_proof(root_a, 1, Slot::new(10)); + cache.observe_valid_proof(root_b, 1, Slot::new(20)); + cache.observe_verification_attempt(root_a, 1, pk_42); + cache.observe_verification_attempt(root_b, 1, pk_43); + + cache.prune(Slot::new(15)); + + assert_eq!(cache.valid_proof_count(), 1); + assert_eq!(cache.seen_from_validator_count(), 1); + // root_b still tracked + assert_eq!( + cache.check(root_b, 1, &pk_99), + ProofObservation::AlreadyHaveValidProof + ); + // root_a gone → New + assert_eq!(cache.check(root_a, 1, &pk_42), ProofObservation::New); + } +} diff --git a/beacon_node/beacon_chain/src/schema_change.rs b/beacon_node/beacon_chain/src/schema_change.rs index ddc59783394..5a11e3a5e00 100644 --- a/beacon_node/beacon_chain/src/schema_change.rs +++ b/beacon_node/beacon_chain/src/schema_change.rs @@ -5,6 +5,7 @@ mod migration_schema_v25; mod migration_schema_v26; mod migration_schema_v27; mod migration_schema_v28; +mod migration_schema_v29; use crate::beacon_chain::BeaconChainTypes; use std::sync::Arc; @@ -88,6 +89,14 @@ pub fn migrate_schema( let ops = migration_schema_v28::downgrade_from_v28::(db.clone())?; db.store_schema_version_atomically(to, ops) } + (SchemaVersion(28), SchemaVersion(29)) => { + let ops = migration_schema_v29::upgrade_to_v29()?; + db.store_schema_version_atomically(to, ops) + } + (SchemaVersion(29), SchemaVersion(28)) => { + let ops = migration_schema_v29::downgrade_from_v29()?; + db.store_schema_version_atomically(to, ops) + } // Anything else is an error. (_, _) => Err(HotColdDBError::UnsupportedSchemaVersion { target_version: to, diff --git a/beacon_node/beacon_chain/src/schema_change/migration_schema_v29.rs b/beacon_node/beacon_chain/src/schema_change/migration_schema_v29.rs new file mode 100644 index 00000000000..fc1c4892d22 --- /dev/null +++ b/beacon_node/beacon_chain/src/schema_change/migration_schema_v29.rs @@ -0,0 +1,20 @@ +use store::{DBColumn, Error, KeyValueStoreOp}; +use tracing::info; +use types::Hash256; + +pub const PROOF_ENGINE_DB_KEY: Hash256 = Hash256::ZERO; + +/// Upgrade to v29: no-op. The new `ProofEngine` column is populated lazily at runtime. +pub fn upgrade_to_v29() -> Result, Error> { + info!("Upgrading to v29 (ProofEngine column — no data migration needed)"); + Ok(vec![]) +} + +/// Downgrade from v29: delete any persisted ProofEngine state. +pub fn downgrade_from_v29() -> Result, Error> { + info!("Downgrading from v29 (deleting ProofEngine state)"); + Ok(vec![KeyValueStoreOp::DeleteKey( + DBColumn::ProofEngine, + PROOF_ENGINE_DB_KEY.as_slice().to_vec(), + )]) +} diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index b6c235a4cb0..b73aa968e9d 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -804,12 +804,7 @@ where pub fn shutdown_reasons(&self) -> Vec { let mutex = self.shutdown_receiver.clone(); let mut receiver = mutex.lock(); - std::iter::from_fn(move || match receiver.try_next() { - Ok(Some(s)) => Some(s), - Ok(None) => panic!("shutdown sender dropped"), - Err(_) => None, - }) - .collect() + std::iter::from_fn(move || receiver.try_recv().ok()).collect() } pub fn get_current_state(&self) -> BeaconState { diff --git a/beacon_node/beacon_chain/tests/attestation_verification.rs b/beacon_node/beacon_chain/tests/attestation_verification.rs index 208798dfdfc..37990edbb34 100644 --- a/beacon_node/beacon_chain/tests/attestation_verification.rs +++ b/beacon_node/beacon_chain/tests/attestation_verification.rs @@ -1274,6 +1274,7 @@ async fn attestation_validator_receive_proposer_reward_and_withdrawals() { } #[tokio::test] +#[allow(clippy::result_large_err)] async fn attestation_to_finalized_block() { let harness = get_harness(VALIDATOR_COUNT); diff --git a/beacon_node/beacon_chain/tests/payload_invalidation.rs b/beacon_node/beacon_chain/tests/payload_invalidation.rs index 5bd43835e33..f9e69ee2e3a 100644 --- a/beacon_node/beacon_chain/tests/payload_invalidation.rs +++ b/beacon_node/beacon_chain/tests/payload_invalidation.rs @@ -1164,6 +1164,7 @@ async fn payload_preparation_before_transition_block() { } #[tokio::test] +#[allow(clippy::result_large_err)] async fn attesting_to_optimistic_head() { let mut rig = InvalidPayloadRig::new(); rig.move_to_terminal_block(); diff --git a/beacon_node/beacon_chain/tests/schema_stability.rs b/beacon_node/beacon_chain/tests/schema_stability.rs index db7f7dbdbbd..ef7e5e59179 100644 --- a/beacon_node/beacon_chain/tests/schema_stability.rs +++ b/beacon_node/beacon_chain/tests/schema_stability.rs @@ -107,7 +107,7 @@ fn check_db_columns() { let expected_columns = vec![ "bma", "blk", "blb", "bdc", "bdi", "ste", "hsd", "hsn", "bsn", "bsd", "bss", "bs3", "bcs", "bst", "exp", "bch", "opo", "etc", "frk", "pkc", "brp", "bsx", "bsr", "bbx", "bbr", "bhr", - "brm", "dht", "cus", "otb", "bhs", "olc", "lcu", "scb", "scm", "dmy", + "brm", "dht", "cus", "otb", "bhs", "olc", "lcu", "scb", "scm", "dmy", "prf", "ipt", ]; assert_eq!(expected_columns, current_columns); } diff --git a/beacon_node/beacon_chain/tests/store_tests.rs b/beacon_node/beacon_chain/tests/store_tests.rs index ba0621ae720..0876b961ad7 100644 --- a/beacon_node/beacon_chain/tests/store_tests.rs +++ b/beacon_node/beacon_chain/tests/store_tests.rs @@ -1119,6 +1119,7 @@ fn get_state_for_block(harness: &TestHarness, block_root: Hash256) -> BeaconStat } /// Check the invariants that apply to `shuffling_is_compatible`. +#[allow(clippy::result_large_err)] fn check_shuffling_compatible( harness: &TestHarness, head_state: &BeaconState, diff --git a/beacon_node/beacon_processor/src/lib.rs b/beacon_node/beacon_processor/src/lib.rs index d9ae0e23451..109103234c4 100644 --- a/beacon_node/beacon_processor/src/lib.rs +++ b/beacon_node/beacon_processor/src/lib.rs @@ -332,7 +332,7 @@ impl BeaconProcessorSend { } } -pub type AsyncFn = Pin + Send + Sync>>; +pub type AsyncFn = Pin + Send>>; pub type BlockingFn = Box; pub type BlockingFnWithManualSendOnIdle = Box; pub enum BlockingOrAsync { @@ -409,6 +409,12 @@ pub enum Work { DataColumnsByRootsRequest(BlockingFn), DataColumnsByRangeRequest(BlockingFn), GossipBlsToExecutionChange(BlockingFn), + /// EIP-8025: A signed execution proof has been received over gossip. + GossipExecutionProof(AsyncFn), + /// EIP-8025: Serve an ExecutionProofsByRange RPC request. + ExecutionProofsByRangeRequest(BlockingFn), + /// EIP-8025: Serve an ExecutionProofsByRoot RPC request. + ExecutionProofsByRootRequest(BlockingFn), LightClientBootstrapRequest(BlockingFn), LightClientOptimisticUpdateRequest(BlockingFn), LightClientFinalityUpdateRequest(BlockingFn), @@ -461,6 +467,9 @@ pub enum WorkType { DataColumnsByRootsRequest, DataColumnsByRangeRequest, GossipBlsToExecutionChange, + GossipExecutionProof, + ExecutionProofsByRangeRequest, + ExecutionProofsByRootRequest, LightClientBootstrapRequest, LightClientOptimisticUpdateRequest, LightClientFinalityUpdateRequest, @@ -496,6 +505,7 @@ impl Work { WorkType::GossipLightClientOptimisticUpdate } Work::GossipBlsToExecutionChange(_) => WorkType::GossipBlsToExecutionChange, + Work::GossipExecutionProof(_) => WorkType::GossipExecutionProof, Work::RpcBlock { .. } => WorkType::RpcBlock, Work::RpcBlobs { .. } => WorkType::RpcBlobs, Work::RpcCustodyColumn { .. } => WorkType::RpcCustodyColumn, @@ -510,6 +520,8 @@ impl Work { Work::BlobsByRootsRequest(_) => WorkType::BlobsByRootsRequest, Work::DataColumnsByRootsRequest(_) => WorkType::DataColumnsByRootsRequest, Work::DataColumnsByRangeRequest(_) => WorkType::DataColumnsByRangeRequest, + Work::ExecutionProofsByRangeRequest(_) => WorkType::ExecutionProofsByRangeRequest, + Work::ExecutionProofsByRootRequest(_) => WorkType::ExecutionProofsByRootRequest, Work::LightClientBootstrapRequest(_) => WorkType::LightClientBootstrapRequest, Work::LightClientOptimisticUpdateRequest(_) => { WorkType::LightClientOptimisticUpdateRequest @@ -933,6 +945,10 @@ impl BeaconProcessor { Some(item) } else if let Some(item) = work_queues.dcbrange_queue.pop() { Some(item) + } else if let Some(item) = work_queues.epbroots_queue.pop() { + Some(item) + } else if let Some(item) = work_queues.epbrange_queue.pop() { + Some(item) // Check slashings after all other consensus messages so we prioritize // following head. // @@ -952,6 +968,9 @@ impl BeaconProcessor { work_queues.gossip_bls_to_execution_change_queue.pop() { Some(item) + // EIP-8025: Process execution proofs + } else if let Some(item) = work_queues.gossip_execution_proof_queue.pop() { + Some(item) // Check the priority 1 API requests after we've // processed all the interesting things from the network // and things required for us to stay in good repute @@ -1143,6 +1162,9 @@ impl BeaconProcessor { Work::GossipBlsToExecutionChange { .. } => work_queues .gossip_bls_to_execution_change_queue .push(work, work_id), + Work::GossipExecutionProof { .. } => { + work_queues.gossip_execution_proof_queue.push(work, work_id) + } Work::BlobsByRootsRequest { .. } => { work_queues.blob_broots_queue.push(work, work_id) } @@ -1152,6 +1174,13 @@ impl BeaconProcessor { Work::DataColumnsByRangeRequest { .. } => { work_queues.dcbrange_queue.push(work, work_id) } + // EIP-8025: Dedicated queues for serving execution proof RPC requests. + Work::ExecutionProofsByRangeRequest { .. } => { + work_queues.epbrange_queue.push(work, work_id) + } + Work::ExecutionProofsByRootRequest { .. } => { + work_queues.epbroots_queue.push(work, work_id) + } Work::UnknownLightClientOptimisticUpdate { .. } => work_queues .unknown_light_client_update_queue .push(work, work_id), @@ -1226,9 +1255,14 @@ impl BeaconProcessor { WorkType::BlobsByRootsRequest => work_queues.blob_broots_queue.len(), WorkType::DataColumnsByRootsRequest => work_queues.dcbroots_queue.len(), WorkType::DataColumnsByRangeRequest => work_queues.dcbrange_queue.len(), + WorkType::ExecutionProofsByRangeRequest => work_queues.epbrange_queue.len(), + WorkType::ExecutionProofsByRootRequest => work_queues.epbroots_queue.len(), WorkType::GossipBlsToExecutionChange => { work_queues.gossip_bls_to_execution_change_queue.len() } + WorkType::GossipExecutionProof => { + work_queues.gossip_execution_proof_queue.len() + } WorkType::LightClientBootstrapRequest => { work_queues.lc_bootstrap_queue.len() } @@ -1379,7 +1413,8 @@ impl BeaconProcessor { Work::RpcBlock { process_fn } | Work::RpcBlobs { process_fn } | Work::RpcCustodyColumn(process_fn) - | Work::ColumnReconstruction(process_fn) => task_spawner.spawn_async(process_fn), + | Work::ColumnReconstruction(process_fn) + | Work::GossipExecutionProof(process_fn) => task_spawner.spawn_async(process_fn), Work::IgnoredRpcBlock { process_fn } => task_spawner.spawn_blocking(process_fn), Work::GossipBlock(work) | Work::GossipBlobSidecar(work) @@ -1389,7 +1424,9 @@ impl BeaconProcessor { Work::BlobsByRangeRequest(process_fn) | Work::BlobsByRootsRequest(process_fn) | Work::DataColumnsByRootsRequest(process_fn) - | Work::DataColumnsByRangeRequest(process_fn) => { + | Work::DataColumnsByRangeRequest(process_fn) + | Work::ExecutionProofsByRangeRequest(process_fn) + | Work::ExecutionProofsByRootRequest(process_fn) => { task_spawner.spawn_blocking(process_fn) } Work::BlocksByRangeRequest(work) | Work::BlocksByRootsRequest(work) => { diff --git a/beacon_node/beacon_processor/src/scheduler/work_queue.rs b/beacon_node/beacon_processor/src/scheduler/work_queue.rs index c6f74961d17..246133ce1f4 100644 --- a/beacon_node/beacon_processor/src/scheduler/work_queue.rs +++ b/beacon_node/beacon_processor/src/scheduler/work_queue.rs @@ -135,6 +135,9 @@ pub struct BeaconProcessorQueueLengths { dcbroots_queue: usize, dcbrange_queue: usize, gossip_bls_to_execution_change_queue: usize, + gossip_execution_proof_queue: usize, + epbroots_queue: usize, + epbrange_queue: usize, lc_bootstrap_queue: usize, lc_rpc_optimistic_update_queue: usize, lc_rpc_finality_update_queue: usize, @@ -201,6 +204,10 @@ impl BeaconProcessorQueueLengths { dcbroots_queue: 1024, dcbrange_queue: 1024, gossip_bls_to_execution_change_queue: 16384, + // EIP-8025: Queue for execution proofs + gossip_execution_proof_queue: 64, + epbroots_queue: 1024, + epbrange_queue: 1024, lc_gossip_finality_update_queue: 1024, lc_gossip_optimistic_update_queue: 1024, lc_bootstrap_queue: 1024, @@ -245,6 +252,12 @@ pub struct WorkQueues { pub dcbroots_queue: FifoQueue>, pub dcbrange_queue: FifoQueue>, pub gossip_bls_to_execution_change_queue: FifoQueue>, + /// EIP-8025: Queue for execution proofs from gossip. + pub gossip_execution_proof_queue: FifoQueue>, + /// EIP-8025: Queue for serving ExecutionProofsByRoot RPC requests. + pub epbroots_queue: FifoQueue>, + /// EIP-8025: Queue for serving ExecutionProofsByRange RPC requests. + pub epbrange_queue: FifoQueue>, pub lc_gossip_finality_update_queue: FifoQueue>, pub lc_gossip_optimistic_update_queue: FifoQueue>, pub lc_bootstrap_queue: FifoQueue>, @@ -310,6 +323,12 @@ impl WorkQueues { let gossip_bls_to_execution_change_queue = FifoQueue::new(queue_lengths.gossip_bls_to_execution_change_queue); + // EIP-8025: Execution proof queue + let gossip_execution_proof_queue = + FifoQueue::new(queue_lengths.gossip_execution_proof_queue); + let epbroots_queue = FifoQueue::new(queue_lengths.epbroots_queue); + let epbrange_queue = FifoQueue::new(queue_lengths.epbrange_queue); + let lc_gossip_optimistic_update_queue = FifoQueue::new(queue_lengths.lc_gossip_optimistic_update_queue); let lc_gossip_finality_update_queue = @@ -357,6 +376,9 @@ impl WorkQueues { dcbroots_queue, dcbrange_queue, gossip_bls_to_execution_change_queue, + gossip_execution_proof_queue, + epbroots_queue, + epbrange_queue, lc_gossip_optimistic_update_queue, lc_gossip_finality_update_queue, lc_bootstrap_queue, diff --git a/beacon_node/execution_layer/Cargo.toml b/beacon_node/execution_layer/Cargo.toml index c443e945743..29c86d45ef0 100644 --- a/beacon_node/execution_layer/Cargo.toml +++ b/beacon_node/execution_layer/Cargo.toml @@ -10,14 +10,18 @@ alloy-primitives = { workspace = true } alloy-rlp = { workspace = true } alloy-rpc-types-eth = { workspace = true } arc-swap = "1.6.0" +async-stream = { workspace = true } +async-trait = "0.1" bls = { workspace = true } builder_client = { path = "../builder_client" } bytes = { workspace = true } eth2 = { workspace = true, features = ["events", "lighthouse"] } ethereum_serde_utils = { workspace = true } ethereum_ssz = { workspace = true } +ethereum_ssz_derive = { workspace = true } fixed_bytes = { workspace = true } fork_choice = { workspace = true } +futures = { workspace = true } hash-db = "0.15.2" hash256-std-hasher = "0.15.2" hex = { workspace = true } @@ -32,6 +36,7 @@ parking_lot = { workspace = true } pretty_reqwest_error = { workspace = true } rand = { workspace = true } reqwest = { workspace = true } +reqwest-eventsource = { workspace = true } sensitive_url = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } @@ -39,6 +44,7 @@ sha2 = { workspace = true } slot_clock = { workspace = true } ssz_types = { workspace = true } state_processing = { workspace = true } +store = { workspace = true } strum = { workspace = true } superstruct = { workspace = true } task_executor = { workspace = true } @@ -53,3 +59,6 @@ typenum = { workspace = true } types = { workspace = true } warp = { workspace = true } zeroize = { workspace = true } + +[dev-dependencies] +anyhow = { workspace = true } diff --git a/beacon_node/execution_layer/src/eip8025/errors.rs b/beacon_node/execution_layer/src/eip8025/errors.rs new file mode 100644 index 00000000000..154dce218e8 --- /dev/null +++ b/beacon_node/execution_layer/src/eip8025/errors.rs @@ -0,0 +1,187 @@ +//! Error types for EIP-8025 proof engine operations. + +use pretty_reqwest_error::PrettyReqwestError; +use std::fmt; +use types::{ExecutionBlockHash, Hash256}; + +/// Errors that can occur during proof engine operations. +#[derive(Debug)] +pub enum ProofEngineError { + /// The proof type is invalid. + InvalidProofType(String), + /// The header format is invalid. + InvalidHeaderFormat(String), + /// The payload is invalid. + InvalidPayload(String), + /// Proof generation is unavailable. + ProofGenerationUnavailable(String), + /// HTTP request failed. + HttpClientError(PrettyReqwestError), + /// JSON-RPC error from the execution engine. + JsonRpcError { code: i64, message: String }, + /// Failed to serialize/deserialize. + SerdeError(serde_json::Error), + /// SSZ error. + SszError(ssz_types::Error), + /// SSE stream error. + SseError(String), + /// The specified fork is not supported. + ForkNotSupported(String), + /// The execution engine does not support the requested proof type. + ProofTypeNotSupported(u8), + /// Timeout waiting for proof engine response. + Timeout, + /// Engine is not available. + EngineUnavailable, + /// State-related errors. + StateError(ProofEngineStateError), +} + +/// Errors related to the proof engine state. +#[derive(Debug)] +pub enum ProofEngineStateError { + /// The block hash for the given request root was not found in the tree state. + BlockHashNotFoundForRequestRoot { + request_root: Hash256, + block_hash: ExecutionBlockHash, + }, + /// The request root associated with the proof has not been observed in a beacon block. + ProofRequestRootNotSeen(Hash256), + /// The request root was not found in the buffer when promotion was attempted. + BufferedRequestNotFound(Hash256), + /// The block number for the given block hash was not found. + BlockNumberNotFound(ExecutionBlockHash), +} + +impl std::error::Error for ProofEngineError {} +impl std::error::Error for ProofEngineStateError {} + +impl ProofEngineError { + /// Returns the JSON-RPC error code if this is a JSON-RPC error. + pub fn rpc_error_code(&self) -> Option { + match self { + ProofEngineError::JsonRpcError { code, .. } => Some(*code), + _ => None, + } + } + + /// Returns true if this error indicates the proof type is not supported. + pub fn is_not_supported(&self) -> bool { + matches!(self, ProofEngineError::ProofTypeNotSupported(_)) + } +} + +// JSON-RPC error codes for EIP-8025 +pub mod error_codes { + /// Invalid header format - The new payload request header structure is malformed + pub const INVALID_HEADER_FORMAT: i64 = -39002; + /// Invalid payload - The execution payload is invalid + pub const INVALID_PAYLOAD: i64 = -39003; + /// Proof generation unavailable - The client cannot generate proofs + pub const PROOF_GENERATION_UNAVAILABLE: i64 = -39004; +} + +impl fmt::Display for ProofEngineError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ProofEngineError::InvalidProofType(msg) => { + write!(f, "Invalid proof type: {}", msg) + } + ProofEngineError::InvalidHeaderFormat(msg) => { + write!(f, "Invalid header format: {}", msg) + } + ProofEngineError::InvalidPayload(msg) => { + write!(f, "Invalid payload: {}", msg) + } + ProofEngineError::ProofGenerationUnavailable(msg) => { + write!(f, "Proof generation unavailable: {}", msg) + } + ProofEngineError::HttpClientError(err) => { + write!(f, "HTTP request failed: {}", err) + } + ProofEngineError::JsonRpcError { code, message } => { + write!(f, "JSON-RPC error (code {}): {}", code, message) + } + ProofEngineError::SerdeError(msg) => { + write!(f, "Serialization error: {}", msg) + } + ProofEngineError::SszError(err) => { + write!(f, "SSZ error: {}", err) + } + ProofEngineError::SseError(msg) => { + write!(f, "SSE stream error: {}", msg) + } + ProofEngineError::ForkNotSupported(fork) => { + write!(f, "Fork not supported: {}", fork) + } + ProofEngineError::ProofTypeNotSupported(proof_type) => { + write!(f, "Proof type {} not supported", proof_type) + } + ProofEngineError::Timeout => { + write!(f, "Proof engine request timed out") + } + ProofEngineError::EngineUnavailable => { + write!(f, "Proof engine is unavailable") + } + ProofEngineError::StateError(state_error) => { + write!(f, "State error: {:?}", state_error) + } + } + } +} + +impl fmt::Display for ProofEngineStateError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ProofEngineStateError::BlockHashNotFoundForRequestRoot { + request_root, + block_hash, + } => write!( + f, + "Block hash {:?} not found for request root {:?}", + block_hash, request_root + ), + ProofEngineStateError::ProofRequestRootNotSeen(request_root) => write!( + f, + "Proof request root {:?} has not been seen in a beacon block", + request_root + ), + ProofEngineStateError::BufferedRequestNotFound(request_root) => { + write!(f, "Buffered request with root {:?} not found", request_root) + } + ProofEngineStateError::BlockNumberNotFound(block_hash) => { + write!(f, "Block number not found for block hash {:?}", block_hash) + } + } + } +} + +impl From for ProofEngineError { + fn from(e: serde_json::Error) -> Self { + ProofEngineError::SerdeError(e) + } +} + +impl From for ProofEngineError { + fn from(e: ssz_types::Error) -> Self { + ProofEngineError::SszError(e) + } +} + +impl From for ProofEngineError { + fn from(e: reqwest::Error) -> Self { + ProofEngineError::HttpClientError(e.into()) + } +} + +impl From for ProofEngineError { + fn from(e: PrettyReqwestError) -> Self { + ProofEngineError::HttpClientError(e) + } +} + +impl From for ProofEngineError { + fn from(e: ProofEngineStateError) -> Self { + ProofEngineError::StateError(e) + } +} diff --git a/beacon_node/execution_layer/src/eip8025/metrics.rs b/beacon_node/execution_layer/src/eip8025/metrics.rs new file mode 100644 index 00000000000..2133f9682c0 --- /dev/null +++ b/beacon_node/execution_layer/src/eip8025/metrics.rs @@ -0,0 +1,97 @@ +pub use metrics::*; +use std::sync::LazyLock; + +// ── Label constants ─────────────────────────────────────────────────────────── + +pub const VALID: &str = "valid"; +pub const INVALID: &str = "invalid"; +pub const BUFFERED: &str = "buffered"; +pub const ERROR: &str = "error"; +pub const SUCCESS: &str = "success"; + +// ── Proof engine counters ───────────────────────────────────────────────────── + +/// Total proof generation requests sent to the proof node, labelled by proof type. +pub static PROOF_ENGINE_REQUESTS_TOTAL: LazyLock> = LazyLock::new(|| { + try_create_int_counter_vec( + "proof_engine_requests_total", + "Total proof generation requests sent to the proof node", + &["proof_type"], + ) +}); + +/// Total proof verification attempts, labelled by proof type and outcome +/// (`valid`, `invalid`, `buffered`, `error`). +pub static PROOF_ENGINE_VERIFICATIONS_TOTAL: LazyLock> = + LazyLock::new(|| { + try_create_int_counter_vec( + "proof_engine_verifications_total", + "Total proof verification attempts by outcome", + &["proof_type", "status"], + ) + }); + +/// Total `forkchoice_updated` calls, labelled by outcome (`success`, `error`). +pub static PROOF_ENGINE_FORKCHOICE_UPDATES_TOTAL: LazyLock> = + LazyLock::new(|| { + try_create_int_counter_vec( + "proof_engine_forkchoice_updates_total", + "Total forkchoice_updated calls by outcome", + &["status"], + ) + }); + +/// Total `new_payload` calls processed by the proof engine. +pub static PROOF_ENGINE_NEW_PAYLOADS_TOTAL: LazyLock> = LazyLock::new(|| { + try_create_int_counter( + "proof_engine_new_payloads_total", + "Total new_payload calls processed by the proof engine", + ) +}); + +// ── Proof engine gauges ─────────────────────────────────────────────────────── + +/// Current number of proofs held in the pre-request buffer (request root not yet seen). +pub static PROOF_ENGINE_BUFFERED_PROOF_COUNT: LazyLock> = LazyLock::new(|| { + try_create_int_gauge( + "proof_engine_buffered_proof_count", + "Proofs buffered awaiting their new_payload request root", + ) +}); + +/// Current number of requests tracked in the proof engine state tree. +pub static PROOF_ENGINE_TREE_SIZE: LazyLock> = LazyLock::new(|| { + try_create_int_gauge( + "proof_engine_tree_size", + "Number of payload requests currently tracked in the proof engine state tree", + ) +}); + +/// Current number of payload requests in the proof engine buffer awaiting promotion. +pub static PROOF_ENGINE_BUFFER_SIZE: LazyLock> = LazyLock::new(|| { + try_create_int_gauge( + "proof_engine_buffer_size", + "Number of payload requests in the proof engine buffer awaiting promotion", + ) +}); + +/// Current number of payload requests with insufficient proofs for promotion. +pub static PROOF_ENGINE_MISSING_PROOF_COUNT: LazyLock> = LazyLock::new(|| { + try_create_int_gauge( + "proof_engine_missing_proof_count", + "Number of payload requests currently missing sufficient proofs", + ) +}); + +// ── Proof engine histograms ─────────────────────────────────────────────────── + +/// Duration of `verify_execution_proof` calls, labelled by proof type. +pub static PROOF_ENGINE_VERIFICATION_DURATION: LazyLock> = + LazyLock::new(|| { + try_create_histogram_vec_with_buckets( + "proof_engine_verification_duration_seconds", + "Duration of proof verification calls", + decimal_buckets(-3, 1), + &["proof_type"], + ) + }); diff --git a/beacon_node/execution_layer/src/eip8025/mod.rs b/beacon_node/execution_layer/src/eip8025/mod.rs new file mode 100644 index 00000000000..5c262231d4c --- /dev/null +++ b/beacon_node/execution_layer/src/eip8025/mod.rs @@ -0,0 +1,28 @@ +//! EIP-8025: Optional Execution Proofs +//! +//! This module provides the execution layer integration for EIP-8025 optional proofs. +//! It includes: +//! - HttpProofEngine combining transport with proof state management +//! - ProofNodeClient trait for low-level transport abstraction (REST+SSZ+SSE) +//! - HttpProofNodeClient for production HTTP transport +//! - SSE event types for proof completion streaming + +pub mod errors; +pub mod metrics; +pub mod persisted_state; +pub mod proof_engine; +pub mod proof_node_client; +pub mod state; +#[cfg(test)] +mod tests; +pub mod types; + +pub use errors::ProofEngineError; +pub use persisted_state::{PROOF_ENGINE_DB_KEY, PersistedProofEngineState}; +pub use proof_engine::HttpProofEngine; +pub use proof_node_client::{ + HttpProofNodeClient, PROOF_ENGINE_TIMEOUT, ProofNodeClient, ProofRequestResponse, +}; +pub use types::{ + ProofComplete, ProofEvent, ProofEventInfo, ProofFailure, ProofType, SseEventParts, +}; diff --git a/beacon_node/execution_layer/src/eip8025/persisted_state.rs b/beacon_node/execution_layer/src/eip8025/persisted_state.rs new file mode 100644 index 00000000000..8491fb076e3 --- /dev/null +++ b/beacon_node/execution_layer/src/eip8025/persisted_state.rs @@ -0,0 +1,493 @@ +//! Persistent storage types for ProofEngine state (EIP-8025). +//! +//! These structs are the SSZ-serializable forms of the in-memory `State`, `TreeState`, +//! and `RequestBuffer`. HashMaps/HashSets are flattened to Vecs for SSZ compatibility. + +use super::state::{PayloadRequest, RequestBuffer, RequestMetadata, State, TreeState}; +use crate::ForkchoiceState; +use ssz::{Decode, Encode}; +use ssz_derive::{Decode, Encode}; +use std::collections::{BTreeMap, HashMap, HashSet}; +use store::{DBColumn, Error as StoreError, KeyValueStoreOp, StoreConfig, StoreItem}; +use types::{ExecutionBlockHash, Hash256, SignedExecutionProof}; + +/// Version field for future format migrations within the ProofEngine state. +pub const PROOF_ENGINE_STATE_VERSION: u64 = 1; + +/// Top-level persisted state for the ProofEngine. +#[derive(Clone, Debug, PartialEq, Encode, Decode)] +pub struct PersistedProofEngineState { + /// Schema version for future migrations. + pub version: u64, + /// The last fork choice state marked as valid. + pub last_valid_fcs: ForkchoiceState, + /// The latest observed fork choice state. + pub latest_fcs: Option, + /// Persisted tree state. + pub tree: PersistedTreeState, + /// Persisted request buffer. + pub buffer: PersistedRequestBuffer, +} + +/// Persisted form of TreeState. HashMaps flattened to Vecs for SSZ. +#[derive(Clone, Debug, PartialEq, Encode, Decode)] +pub struct PersistedTreeState { + pub proofs_by_block_hash: Vec, + pub request_root_to_block_hash: Vec, + pub parent_to_children: Vec, + pub block_number_to_block_hash: Vec, + pub current_canonical_head: ExecutionBlockHash, +} + +/// Flattened PayloadRequest: RequestMetadata + Vec. +#[derive(Clone, Debug, PartialEq, Encode, Decode)] +pub struct PersistedBlockProofs { + pub block_hash: ExecutionBlockHash, + pub request_root: Hash256, + pub parent_hash: ExecutionBlockHash, + pub block_number: u64, + pub proofs: Vec, +} + +#[derive(Clone, Debug, PartialEq, Encode, Decode)] +pub struct RequestRootMapping { + pub request_root: Hash256, + pub block_hash: ExecutionBlockHash, +} + +#[derive(Clone, Debug, PartialEq, Encode, Decode)] +pub struct PersistedParentChildren { + pub parent: ExecutionBlockHash, + pub children: Vec, +} + +#[derive(Clone, Debug, PartialEq, Encode, Decode)] +pub struct PersistedBlockNumberMapping { + pub block_number: u64, + pub block_hashes: Vec, +} + +#[derive(Clone, Debug, PartialEq, Encode, Decode)] +pub struct PersistedRequestBuffer { + pub requests: Vec, +} + +/// Fixed database key for the single `PersistedProofEngineState` record. +pub const PROOF_ENGINE_DB_KEY: Hash256 = Hash256::ZERO; + +impl StoreItem for PersistedProofEngineState { + fn db_column() -> DBColumn { + DBColumn::ProofEngine + } + + fn as_store_bytes(&self) -> Vec { + self.as_ssz_bytes() + } + + fn from_store_bytes(bytes: &[u8]) -> Result { + Self::from_ssz_bytes(bytes).map_err(Into::into) + } +} + +impl PersistedProofEngineState { + /// Decompress and decode from bytes using zstd (mirrors `PersistedForkChoiceV28::from_bytes`). + pub fn from_bytes(bytes: &[u8], store_config: &StoreConfig) -> Result { + let decompressed_bytes = store_config + .decompress_bytes(bytes) + .map_err(StoreError::Compression)?; + Self::from_ssz_bytes(&decompressed_bytes).map_err(Into::into) + } + + /// Encode and compress to bytes using zstd (mirrors `PersistedForkChoiceV28::as_bytes`). + pub fn as_bytes(&self, store_config: &StoreConfig) -> Result, StoreError> { + let ssz_bytes = self.as_ssz_bytes(); + store_config + .compress_bytes(&ssz_bytes) + .map_err(StoreError::Compression) + } + + /// Produce a compressed `KeyValueStoreOp` for atomic persistence. + pub fn as_kv_store_op( + &self, + key: Hash256, + store_config: &StoreConfig, + ) -> Result { + Ok(KeyValueStoreOp::PutKeyValue( + DBColumn::ProofEngine, + key.as_slice().to_vec(), + self.as_bytes(store_config)?, + )) + } + + pub fn from_state(state: &State) -> Self { + Self { + version: PROOF_ENGINE_STATE_VERSION, + last_valid_fcs: state.last_valid_fcs, + latest_fcs: state.latest_fcs, + tree: PersistedTreeState::from_tree(&state.tree), + buffer: PersistedRequestBuffer::from_buffer(&state.buffer), + } + } + + pub fn to_state(&self) -> State { + State { + latest_fcs: self.latest_fcs, + last_valid_fcs: self.last_valid_fcs, + tree: self.tree.to_tree(), + buffer: self.buffer.to_buffer(), + min_required_proofs: types::MIN_REQUIRED_EXECUTION_PROOFS, + } + } +} + +impl PersistedTreeState { + fn from_tree(tree: &TreeState) -> Self { + let proofs_by_block_hash = tree + .proofs_by_block_hash + .iter() + .map(|(block_hash, payload_req)| PersistedBlockProofs { + block_hash: *block_hash, + request_root: payload_req.metadata.request_root, + parent_hash: payload_req.metadata.parent_hash, + block_number: payload_req.metadata.block_number, + proofs: payload_req.proofs.clone(), + }) + .collect(); + + let request_root_to_block_hash = tree + .request_root_to_block_hash + .iter() + .map(|(root, hash)| RequestRootMapping { + request_root: *root, + block_hash: *hash, + }) + .collect(); + + let parent_to_children = tree + .parent_to_children + .iter() + .map(|(parent, children)| PersistedParentChildren { + parent: *parent, + children: children.iter().copied().collect(), + }) + .collect(); + + let block_number_to_block_hash = tree + .block_number_to_block_hash + .iter() + .map(|(num, hashes)| PersistedBlockNumberMapping { + block_number: *num, + block_hashes: hashes.iter().copied().collect(), + }) + .collect(); + + Self { + proofs_by_block_hash, + request_root_to_block_hash, + parent_to_children, + block_number_to_block_hash, + current_canonical_head: tree.current_canonical_head, + } + } + + fn to_tree(&self) -> TreeState { + let proofs_by_block_hash: HashMap = self + .proofs_by_block_hash + .iter() + .map(|p| { + ( + p.block_hash, + PayloadRequest { + metadata: RequestMetadata { + request_root: p.request_root, + block_hash: p.block_hash, + parent_hash: p.parent_hash, + block_number: p.block_number, + }, + proofs: p.proofs.clone(), + }, + ) + }) + .collect(); + + let request_root_to_block_hash: HashMap = self + .request_root_to_block_hash + .iter() + .map(|m| (m.request_root, m.block_hash)) + .collect(); + + let parent_to_children: HashMap> = self + .parent_to_children + .iter() + .map(|p| (p.parent, p.children.iter().copied().collect())) + .collect(); + + let block_number_to_block_hash: BTreeMap> = self + .block_number_to_block_hash + .iter() + .map(|m| (m.block_number, m.block_hashes.iter().copied().collect())) + .collect(); + + TreeState { + proofs_by_block_hash, + request_root_to_block_hash, + parent_to_children, + block_number_to_block_hash, + current_canonical_head: self.current_canonical_head, + } + } +} + +impl PersistedRequestBuffer { + fn from_buffer(buffer: &RequestBuffer) -> Self { + let requests = buffer + .proofs + .values() + .map(|payload_req| PersistedBlockProofs { + block_hash: payload_req.metadata.block_hash, + request_root: payload_req.metadata.request_root, + parent_hash: payload_req.metadata.parent_hash, + block_number: payload_req.metadata.block_number, + proofs: payload_req.proofs.clone(), + }) + .collect(); + Self { requests } + } + + fn to_buffer(&self) -> RequestBuffer { + let proofs: HashMap = self + .requests + .iter() + .map(|p| { + ( + p.request_root, + PayloadRequest { + metadata: RequestMetadata { + request_root: p.request_root, + block_hash: p.block_hash, + parent_hash: p.parent_hash, + block_number: p.block_number, + }, + proofs: p.proofs.clone(), + }, + ) + }) + .collect(); + RequestBuffer { proofs } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::eip8025::state::test_utils::*; + use store::StoreItem; + use types::MIN_REQUIRED_EXECUTION_PROOFS; + + /// Builds a fully-populated `State` with canonical chain, a fork (in buffer), and both FCS + /// fields set, then verifies a `from_state` → `to_state` round-trip preserves all data. + #[test] + fn test_state_serialization_round_trip() { + // 3-block canonical chain; fork from block 1 with 0 proofs so it stays in buffer, additional fork with 3 proofs so we have a promoted fork as well. + let fixture = TestStateFixtureBuilder::simple_chain() + .with_fork(1, 2, Some(0)) + .with_fork(1, 3, Some(3)) + .build(); + + let mut state = State::new(); + + // Populate canonical chain into tree and set latest_fcs via the empty-tree path. + fixture.bootstrap_canonical(&mut state).unwrap(); + + // Insert fork blocks into buffer (0 proofs → not promoted). + fixture.insert_fork(&mut state, 0, None).unwrap(); + + // Issue a valid forkchoice update so last_valid_fcs points into the tree. + let head = fixture.canonical_block_hash(2); + let safe = fixture.canonical_block_hash(1); + let finalized = fixture.canonical_block_hash(0); + state + .forkchoice_updated(create_forkchoice_state(head, safe, finalized)) + .unwrap(); + + // Sanity: both FCS fields should be populated. + assert!(state.latest_fcs.is_some()); + + // --- Round-trip --- + let persisted = PersistedProofEngineState::from_state(&state); + let restored = persisted.to_state(); + + // FCS fields. + assert_eq!(restored.last_valid_fcs, state.last_valid_fcs); + assert_eq!(restored.latest_fcs, state.latest_fcs); + + // min_required_proofs is not persisted — always restored to the constant. + assert_eq!(restored.min_required_proofs, MIN_REQUIRED_EXECUTION_PROOFS); + + // Tree: proofs_by_block_hash. + assert_eq!( + restored.tree.proofs_by_block_hash.len(), + state.tree.proofs_by_block_hash.len() + ); + for (hash, req) in &state.tree.proofs_by_block_hash { + let r = restored.tree.proofs_by_block_hash.get(hash).unwrap(); + assert_eq!(r.metadata.request_root, req.metadata.request_root); + assert_eq!(r.metadata.block_hash, req.metadata.block_hash); + assert_eq!(r.metadata.parent_hash, req.metadata.parent_hash); + assert_eq!(r.metadata.block_number, req.metadata.block_number); + assert_eq!(r.proofs, req.proofs); + } + + // Tree: request_root_to_block_hash. + assert_eq!( + restored.tree.request_root_to_block_hash, + state.tree.request_root_to_block_hash + ); + + // Tree: parent_to_children (HashSet equality via HashMap comparison). + assert_eq!( + restored.tree.parent_to_children, + state.tree.parent_to_children + ); + + // Tree: block_number_to_block_hash (BTreeMap). + assert_eq!( + restored.tree.block_number_to_block_hash, + state.tree.block_number_to_block_hash + ); + + // Tree: current_canonical_head. + assert_eq!( + restored.tree.current_canonical_head, + state.tree.current_canonical_head + ); + + // Buffer: entries match. + assert_eq!(restored.buffer.proofs.len(), state.buffer.proofs.len()); + for (root, req) in &state.buffer.proofs { + let r = restored.buffer.proofs.get(root).unwrap(); + assert_eq!(r.metadata.request_root, req.metadata.request_root); + assert_eq!(r.metadata.block_hash, req.metadata.block_hash); + assert_eq!(r.metadata.parent_hash, req.metadata.parent_hash); + assert_eq!(r.metadata.block_number, req.metadata.block_number); + assert_eq!(r.proofs, req.proofs); + } + } + + /// Encodes a `PersistedProofEngineState` via `StoreItem::as_store_bytes`, then decodes with + /// `StoreItem::from_store_bytes` and asserts all fields are equal. + #[test] + fn test_encode_decode_round_trip() { + let fixture = TestStateFixtureBuilder::simple_chain() + .with_fork(1, 2, Some(0)) + .with_fork(1, 3, Some(3)) + .build(); + + let mut state = State::new(); + fixture.bootstrap_canonical(&mut state).unwrap(); + fixture.insert_fork(&mut state, 0, None).unwrap(); + + let head = fixture.canonical_block_hash(2); + let safe = fixture.canonical_block_hash(1); + let finalized = fixture.canonical_block_hash(0); + state + .forkchoice_updated(create_forkchoice_state(head, safe, finalized)) + .unwrap(); + + let persisted = PersistedProofEngineState::from_state(&state); + + let bytes = persisted.as_store_bytes(); + let decoded = PersistedProofEngineState::from_store_bytes(&bytes).unwrap(); + + assert_eq!(decoded.version, persisted.version); + assert_eq!(decoded.last_valid_fcs, persisted.last_valid_fcs); + assert_eq!(decoded.latest_fcs, persisted.latest_fcs); + + assert_eq!( + decoded.tree.proofs_by_block_hash, + persisted.tree.proofs_by_block_hash + ); + assert_eq!( + decoded.tree.request_root_to_block_hash, + persisted.tree.request_root_to_block_hash + ); + + // Sort children within each parent entry for determinism. + assert_eq!( + decoded.tree.parent_to_children.len(), + persisted.tree.parent_to_children.len() + ); + let mut orig_ptc = persisted.tree.parent_to_children.clone(); + let mut dec_ptc = decoded.tree.parent_to_children.clone(); + orig_ptc.sort_by_key(|p| p.parent.into_root()); + dec_ptc.sort_by_key(|p| p.parent.into_root()); + for (o, d) in orig_ptc.iter().zip(dec_ptc.iter()) { + assert_eq!(o.parent, d.parent); + let mut oc = o.children.clone(); + let mut dc = d.children.clone(); + oc.sort_by_key(|h| h.into_root()); + dc.sort_by_key(|h| h.into_root()); + assert_eq!(oc, dc); + } + + assert_eq!( + decoded.tree.block_number_to_block_hash, + persisted.tree.block_number_to_block_hash + ); + assert_eq!( + decoded.tree.current_canonical_head, + persisted.tree.current_canonical_head + ); + assert_eq!(decoded.buffer.requests, persisted.buffer.requests); + } + + /// Verifies that compress → decompress round-trip via `as_bytes`/`from_bytes` + /// preserves all fields, and that compressed output is smaller than raw SSZ. + #[test] + fn test_compressed_round_trip() { + let fixture = TestStateFixtureBuilder::simple_chain() + .with_fork(1, 2, Some(0)) + .with_fork(1, 3, Some(3)) + .build(); + + let mut state = State::new(); + fixture.bootstrap_canonical(&mut state).unwrap(); + fixture.insert_fork(&mut state, 0, None).unwrap(); + + let head = fixture.canonical_block_hash(2); + let safe = fixture.canonical_block_hash(1); + let finalized = fixture.canonical_block_hash(0); + state + .forkchoice_updated(create_forkchoice_state(head, safe, finalized)) + .unwrap(); + + let persisted = PersistedProofEngineState::from_state(&state); + let store_config = StoreConfig::default(); + + // Compress. + let compressed = persisted.as_bytes(&store_config).unwrap(); + let raw_ssz = persisted.as_store_bytes(); + + // Compressed should differ from raw SSZ (zstd adds framing even if not smaller). + assert_ne!(compressed, raw_ssz); + + // Decompress and verify equality. + let decoded = PersistedProofEngineState::from_bytes(&compressed, &store_config).unwrap(); + assert_eq!(decoded.version, persisted.version); + assert_eq!(decoded.last_valid_fcs, persisted.last_valid_fcs); + assert_eq!(decoded.latest_fcs, persisted.latest_fcs); + assert_eq!( + decoded.tree.proofs_by_block_hash, + persisted.tree.proofs_by_block_hash + ); + assert_eq!( + decoded.tree.request_root_to_block_hash, + persisted.tree.request_root_to_block_hash + ); + assert_eq!( + decoded.tree.current_canonical_head, + persisted.tree.current_canonical_head + ); + assert_eq!(decoded.buffer.requests, persisted.buffer.requests); + } +} diff --git a/beacon_node/execution_layer/src/eip8025/proof_engine.rs b/beacon_node/execution_layer/src/eip8025/proof_engine.rs new file mode 100644 index 00000000000..0e4fe50a32a --- /dev/null +++ b/beacon_node/execution_layer/src/eip8025/proof_engine.rs @@ -0,0 +1,270 @@ +//! HTTP proof engine implementation for EIP-8025. +//! +//! Provides an HTTP implementation with an internal proof cache. +//! HTTP transport is delegated to a [`ProofNodeClient`] implementation. + +use super::errors::ProofEngineError; +use super::metrics; +use super::persisted_state::PersistedProofEngineState; +use super::proof_node_client::{HttpProofNodeClient, ProofNodeClient}; +use super::types::ProofEvent; +use crate::{ + ForkchoiceState, ForkchoiceUpdatedResponse, MissingProofInfo, NewPayloadRequest, + PayloadStatusV1, PayloadStatusV1Status, + eip8025::state::{RequestMetadata, State}, +}; +use bytes::Bytes; +use futures::stream::Stream; +use parking_lot::RwLock; +use sensitive_url::SensitiveUrl; +use ssz::Encode; +use std::collections::HashMap; +use std::pin::Pin; +use std::time::Duration; + +use types::execution::eip8025::{ProofAttributes, ProofStatus, SignedExecutionProof}; +use types::{EthSpec, Hash256}; + +// ─── HttpProofEngine ───────────────────────────────────────────────────────── + +/// Proof engine with internal proof storage. +/// +/// - Stores ALL unfinalized proofs indexed by new_payload_request_root (unbounded) +/// - Delegates transport to a [`ProofNodeClient`] implementation +/// - Prunes proofs when finalization events occur +pub struct HttpProofEngine { + /// Transport client for proof engine REST+SSZ+SSE API. + proof_node: Box, + /// The internal state storing execution proofs in a tree structure and buffer. + state: RwLock, + /// Buffered proofs for request roots not yet seen. + buffered_proofs: RwLock>>, +} + +impl HttpProofEngine { + /// Create a new proof engine backed by the HTTP proof node client. + pub fn new(url: SensitiveUrl, timeout: Option) -> Self { + Self::with_proof_node(HttpProofNodeClient::new(url, timeout)) + } + + /// Create a proof engine backed by a custom [`ProofNodeClient`] implementation. + /// + /// Useful for injecting a [`MockProofNodeClient`] in tests. + /// + /// [`MockProofNodeClient`]: super::super::test_utils::MockProofNodeClient + pub fn with_proof_node(proof_node: impl ProofNodeClient + 'static) -> Self { + Self { + proof_node: Box::new(proof_node), + state: RwLock::new(State::new()), + buffered_proofs: RwLock::new(HashMap::new()), + } + } + + /// Subscribe to method-invocation events emitted by a mock proof node client. + /// + /// Returns `None` for production (HTTP) clients. + pub fn subscribe_client_events( + &self, + ) -> Option> { + self.proof_node.subscribe_client_events() + } + + /// Subscribe to SSE proof events from the proof engine. + pub fn subscribe_proof_events( + &self, + filter_root: Option, + ) -> Pin> + Send + '_>> { + self.proof_node.subscribe_proof_events(filter_root) + } + + /// Download a completed execution proof by proof type. + pub async fn get_proof( + &self, + new_payload_request_root: Hash256, + proof_type: u8, + ) -> Result { + self.proof_node + .get_proof(new_payload_request_root, proof_type) + .await + } + + /// Get all proofs for a given new_payload_request_root. + pub fn get_proofs_by_root(&self, root: &Hash256) -> Vec { + self.state + .read() + .get_proofs(root) + .map(<[SignedExecutionProof]>::to_vec) + .unwrap_or_default() + } + + /// Return all buffer entries that do not yet have sufficient proofs for promotion. + /// + /// `MissingProofInfo.root` is populated with the new-payload request root. + /// The beacon chain layer replaces it with the beacon block root before the + /// sync manager issues `ExecutionProofsByRoot` RPC requests. + pub fn missing_proofs(&self) -> Vec { + self.state.read().missing_proofs() + } + + /// Verify an individual execution proof via the proof engine. + pub async fn verify_execution_proof( + &self, + proof: &SignedExecutionProof, + ) -> Result { + let proof_type_str = crate::eip8025::ProofType::from_u8(proof.proof_type()) + .map(|pt| pt.as_str()) + .unwrap_or("unknown"); + + if !self + .state + .read() + .contains_request_root(&proof.request_root()) + { + tracing::info!(target: "execution_layer", "Received proof for unknown request root {}, buffering", proof.request_root()); + let mut buf = self.buffered_proofs.write(); + buf.entry(proof.request_root()) + .or_default() + .push(proof.clone()); + let total: usize = buf.values().map(|v| v.len()).sum(); + metrics::set_gauge(&metrics::PROOF_ENGINE_BUFFERED_PROOF_COUNT, total as i64); + metrics::inc_counter_vec( + &metrics::PROOF_ENGINE_VERIFICATIONS_TOTAL, + &[proof_type_str, metrics::BUFFERED], + ); + return Ok(ProofStatus::Syncing); + } + + let timer = metrics::start_timer_vec( + &metrics::PROOF_ENGINE_VERIFICATION_DURATION, + &[proof_type_str], + ); + let verify_result = self + .proof_node + .verify_proof(proof.request_root(), proof.proof_type(), proof.proof_data()) + .await; + drop(timer); + + let status = verify_result.inspect_err(|_e| { + metrics::inc_counter_vec( + &metrics::PROOF_ENGINE_VERIFICATIONS_TOTAL, + &[proof_type_str, metrics::ERROR], + ); + })?; + + if status.is_valid() { + metrics::inc_counter_vec( + &metrics::PROOF_ENGINE_VERIFICATIONS_TOTAL, + &[proof_type_str, metrics::VALID], + ); + return Ok(self.state.write().insert_proof(proof.clone())?); + } + + metrics::inc_counter_vec( + &metrics::PROOF_ENGINE_VERIFICATIONS_TOTAL, + &[proof_type_str, metrics::INVALID], + ); + Ok(status) + } + + /// Buffer a new payload request for proof association. + pub async fn new_payload( + &self, + request: &NewPayloadRequest<'_, E>, + ) -> Result { + metrics::inc_counter(&metrics::PROOF_ENGINE_NEW_PAYLOADS_TOTAL); + let request: RequestMetadata = request.into(); + let buffered_proofs = self + .buffered_proofs + .write() + .remove(&request.request_root) + .unwrap_or_default(); + { + let mut state = self.state.write(); + state.buffer_request(request); + metrics::set_gauge( + &metrics::PROOF_ENGINE_BUFFER_SIZE, + state.buffer_len() as i64, + ); + } // guard dropped before the await loop below + + let mut status = PayloadStatusV1Status::Syncing; + for proof in buffered_proofs { + let proof_status = self.verify_execution_proof(&proof).await?; + if proof_status.is_valid() { + status = PayloadStatusV1Status::Valid; + } + } + + Ok(PayloadStatusV1 { + status, + latest_valid_hash: None, + validation_error: None, + }) + } + + /// Notify the proof engine of a forkchoice update. + pub fn forkchoice_updated( + &self, + forkchoice_state: ForkchoiceState, + ) -> Result { + tracing::info!(target: "execution_layer", "Received forkchoice update: head {}, safe {}, finalized {}", forkchoice_state.head_block_hash, forkchoice_state.safe_block_hash, forkchoice_state.finalized_block_hash); + let result = self.state.write().forkchoice_updated(forkchoice_state); + match &result { + Ok(response) => { + let status = if response.payload_status.status == PayloadStatusV1Status::Syncing { + "syncing" + } else { + metrics::SUCCESS + }; + metrics::inc_counter_vec( + &metrics::PROOF_ENGINE_FORKCHOICE_UPDATES_TOTAL, + &[status], + ); + let state = self.state.read(); + metrics::set_gauge(&metrics::PROOF_ENGINE_TREE_SIZE, state.tree_len() as i64); + metrics::set_gauge( + &metrics::PROOF_ENGINE_MISSING_PROOF_COUNT, + state.missing_proofs().len() as i64, + ); + } + Err(_) => { + metrics::inc_counter_vec( + &metrics::PROOF_ENGINE_FORKCHOICE_UPDATES_TOTAL, + &[metrics::ERROR], + ); + } + } + Ok(result?) + } + + /// Request proof generation from the proof engine. + /// + /// SSZ-encodes the payload then sends it to `POST /v1/execution_proof_requests`. + /// Returns the `new_payload_request_root` identifying this request. + pub async fn request_proofs( + &self, + new_payload_request: NewPayloadRequest<'_, E>, + proof_attributes: ProofAttributes, + ) -> Result { + for &proof_type in &proof_attributes.proof_types { + if let Ok(pt) = crate::eip8025::ProofType::from_u8(proof_type) { + metrics::inc_counter_vec(&metrics::PROOF_ENGINE_REQUESTS_TOTAL, &[pt.as_str()]); + } + } + self.proof_node + .request_proofs(new_payload_request.as_ssz_bytes(), proof_attributes) + .await + } + + /// Snapshot the current state into a persisted form for serialization. + pub fn to_persisted(&self) -> PersistedProofEngineState { + let state = self.state.read(); + PersistedProofEngineState::from_state(&state) + } + + /// Restore in-memory state from a previously persisted snapshot. + pub fn restore_from_persisted(&self, persisted: PersistedProofEngineState) { + let restored = persisted.to_state(); + *self.state.write() = restored; + } +} diff --git a/beacon_node/execution_layer/src/eip8025/proof_node_client.rs b/beacon_node/execution_layer/src/eip8025/proof_node_client.rs new file mode 100644 index 00000000000..af974ad617e --- /dev/null +++ b/beacon_node/execution_layer/src/eip8025/proof_node_client.rs @@ -0,0 +1,262 @@ +//! Proof node client trait and HTTP implementation for EIP-8025. +//! +//! [`ProofNodeClient`] abstracts over the transport used to communicate with the +//! proof engine. [`HttpProofNodeClient`] is the production implementation. + +use super::errors::ProofEngineError; +use bytes::Bytes; +use futures::stream::Stream; +use reqwest::Client; +use reqwest_eventsource::{Event, EventSource}; +use sensitive_url::SensitiveUrl; +use std::pin::Pin; +use std::time::Duration; +use tokio_stream::StreamExt; + +use super::types::{ProofEvent, ProofType, SseEventParts}; +use types::Hash256; +use types::execution::eip8025::{ProofAttributes, ProofStatus}; + +/// Default timeout for proof engine requests (1 second per spec). +pub const PROOF_ENGINE_TIMEOUT: Duration = Duration::from_secs(1); + +const PATH_PROOF_REQUESTS: &str = "/v1/execution_proof_requests"; +const PATH_PROOF_VERIFICATIONS: &str = "/v1/execution_proof_verifications"; +const PATH_PROOFS: &str = "/v1/execution_proofs"; + +const QUERY_PROOF_TYPES: &str = "proof_types"; +const QUERY_NEW_PAYLOAD_REQUEST_ROOT: &str = "new_payload_request_root"; +const QUERY_PROOF_TYPE: &str = "proof_type"; + +const HEADER_CONTENT_TYPE: &str = "content-type"; +const HEADER_VALUE_SSZ: &str = "application/octet-stream"; + +// ─── ProofNodeClient Trait ─────────────────────────────────────────────────── + +/// Transport abstraction for communicating with a proof engine. +/// +/// The SSZ encoding of the payload is done by the caller ([`HttpProofEngine`]) +/// before invoking [`request_proofs`], so implementations receive raw bytes and +/// do not need to be generic over [`EthSpec`]. +/// +/// [`HttpProofEngine`]: super::proof_engine::HttpProofEngine +/// [`request_proofs`]: ProofNodeClient::request_proofs +/// [`EthSpec`]: types::EthSpec +#[async_trait::async_trait] +pub trait ProofNodeClient: Send + Sync { + /// Submit an SSZ-encoded `NewPayloadRequest` to the proof engine. + /// + /// Returns the `new_payload_request_root` assigned by the proof engine. + async fn request_proofs( + &self, + ssz_body: Vec, + proof_attributes: ProofAttributes, + ) -> Result; + + /// Verify a single proof via the proof engine. + async fn verify_proof( + &self, + root: Hash256, + proof_type: u8, + proof_data: &[u8], + ) -> Result; + + /// Download a completed proof by root and proof type. + async fn get_proof(&self, root: Hash256, proof_type: u8) -> Result; + + /// Subscribe to SSE proof events from the proof engine. + /// + /// When `filter_root` is provided, only events for that root are yielded. + fn subscribe_proof_events( + &self, + filter_root: Option, + ) -> Pin> + Send + '_>>; + + /// Subscribe to method-invocation events emitted by a mock client. + /// + /// Returns `None` for production clients; overridden in [`MockProofNodeClient`] to + /// expose its internal broadcast channel for test assertions. + /// + /// [`MockProofNodeClient`]: crate::test_utils::MockProofNodeClient + fn subscribe_client_events( + &self, + ) -> Option> { + None + } +} + +// ─── REST API Response Types ───────────────────────────────────────────────── + +/// Response for `POST /v1/execution_proof_requests`. +#[derive(Debug, Clone, serde::Deserialize)] +pub struct ProofRequestResponse { + pub new_payload_request_root: Hash256, +} + +/// Response for `POST /v1/execution_proof_verifications`. +#[derive(Debug, Clone, serde::Deserialize)] +struct ProofVerificationResponse { + status: ProofVerificationStatus, +} + +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +enum ProofVerificationStatus { + Valid, + Invalid, +} + +// ─── HttpProofNodeClient ───────────────────────────────────────────────────── + +/// HTTP implementation of [`ProofNodeClient`]. +/// +/// Handles all network I/O — SSZ body transport, HTTP requests, SSE streams. +/// No proof state management; that stays in [`HttpProofEngine`]. +/// +/// [`HttpProofEngine`]: super::proof_engine::HttpProofEngine +pub struct HttpProofNodeClient { + client: Client, + url: SensitiveUrl, + timeout: Duration, +} + +impl HttpProofNodeClient { + /// Create a new HTTP proof node client. + pub fn new(url: SensitiveUrl, timeout: Option) -> Self { + let client = Client::builder() + .build() + .expect("Failed to build HTTP client"); + + Self { + client, + url, + timeout: timeout.unwrap_or(PROOF_ENGINE_TIMEOUT), + } + } + + /// Build a URL from the base URL and a path. + fn url(&self, path: &str) -> reqwest::Url { + let mut url = self.url.expose_full().clone(); + url.set_path(path); + url + } +} + +#[async_trait::async_trait] +impl ProofNodeClient for HttpProofNodeClient { + /// `POST /v1/execution_proof_requests?proof_types=reth-sp1,ethrex-risc0` + /// + /// Converts EIP-8025 `u8` proof types to string identifiers for the wire + /// format. + async fn request_proofs( + &self, + ssz_body: Vec, + proof_attributes: ProofAttributes, + ) -> Result { + // Convert u8 proof types to string identifiers. + // proof node expects: `proof_types=reth-sp1,ethrex-risc0` + let proof_types_csv = proof_attributes + .proof_types + .iter() + .map(|t| ProofType::from_u8(*t).map(|pt| pt.as_str().to_string())) + .collect::, _>>()? + .join(","); + + let response: ProofRequestResponse = self + .client + .post(self.url(PATH_PROOF_REQUESTS)) + .query(&[(QUERY_PROOF_TYPES, &proof_types_csv)]) + .header(HEADER_CONTENT_TYPE, HEADER_VALUE_SSZ) + .body(ssz_body) + .timeout(self.timeout) + .send() + .await? + .error_for_status()? + .json() + .await?; + + Ok(response.new_payload_request_root) + } + + /// `POST /v1/execution_proof_verifications?new_payload_request_root=...&proof_type=reth-sp1` + /// + /// Converts the `u8` proof type to a string identifier for the query param. + async fn verify_proof( + &self, + root: Hash256, + proof_type: u8, + proof_data: &[u8], + ) -> Result { + let proof_type_str = ProofType::from_u8(proof_type)?; + let response: ProofVerificationResponse = self + .client + .post(self.url(PATH_PROOF_VERIFICATIONS)) + .query(&[ + (QUERY_NEW_PAYLOAD_REQUEST_ROOT, &root.to_string()), + (QUERY_PROOF_TYPE, &proof_type_str.to_string()), + ]) + .header(HEADER_CONTENT_TYPE, HEADER_VALUE_SSZ) + .body(proof_data.to_vec()) + .timeout(self.timeout) + .send() + .await? + .error_for_status()? + .json() + .await?; + + match response.status { + ProofVerificationStatus::Valid => Ok(ProofStatus::Valid), + ProofVerificationStatus::Invalid => Ok(ProofStatus::Invalid), + } + } + + /// `GET /v1/execution_proofs/{root}/{proof_type}` + /// + /// Uses string identifier in the URL path (e.g. `/reth-sp1`). + async fn get_proof(&self, root: Hash256, proof_type: u8) -> Result { + let proof_type_str = ProofType::from_u8(proof_type)?; + Ok(self + .client + .get(self.url(&format!("{PATH_PROOFS}/{root}/{proof_type_str}"))) + .timeout(self.timeout) + .send() + .await? + .error_for_status()? + .bytes() + .await?) + } + + /// Opens `GET /v1/execution_proof_requests` as an SSE stream. + fn subscribe_proof_events( + &self, + filter_root: Option, + ) -> Pin> + Send + '_>> { + let client = self.client.clone(); + let url = self.url(PATH_PROOF_REQUESTS); + + Box::pin(async_stream::try_stream! { + let builder = if let Some(root) = filter_root { + client.get(url).query(&[(QUERY_NEW_PAYLOAD_REQUEST_ROOT, &root.to_string())]) + } else { + client.get(url) + }; + let mut es = EventSource::new(builder) + .map_err(|e| ProofEngineError::SseError( + format!("failed to create event source: {e}") + ))?; + + while let Some(event) = es.next().await { + match event { + Ok(Event::Open) => {} + Ok(Event::Message(message)) => { + yield ProofEvent::try_from(SseEventParts(&message.event, &message.data))?; + } + Err(error) => { + es.close(); + Err(ProofEngineError::SseError(error.to_string()))?; + } + } + } + }) + } +} diff --git a/beacon_node/execution_layer/src/eip8025/state.rs b/beacon_node/execution_layer/src/eip8025/state.rs new file mode 100644 index 00000000000..3b14ec83ec3 --- /dev/null +++ b/beacon_node/execution_layer/src/eip8025/state.rs @@ -0,0 +1,1757 @@ +use crate::{ + ForkchoiceState, ForkchoiceUpdatedResponse, MissingProofInfo, PayloadStatusV1, + PayloadStatusV1Status, +}; +use crate::{NewPayloadRequest, eip8025::errors::ProofEngineStateError}; +use std::collections::btree_map::Entry; +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::mem; +use tree_hash::TreeHash; +use types::{EthSpec, ExecutionBlockHash, Hash256, SignedExecutionProof}; +use types::{MIN_REQUIRED_EXECUTION_PROOFS, ProofStatus}; + +// TODO: Consider refactoring to use proto-array style state structure for better performance. +// TODO: Add metrics for latency, state size, buffer size, proof counts, etc. +// TODO: If we continue to use HashMaps then consider using ahash or foldhash for better performance (keys are cryptographic digests and as such random). + +#[derive(Debug, Clone)] +pub struct State { + /// The latest fork choice state received that has not yet been marked as valid. + pub latest_fcs: Option, + /// The last fork choice state that was marked as valid. + pub last_valid_fcs: ForkchoiceState, + /// State of the execution proofs tree. + pub tree: TreeState, + /// Buffer of unassociated execution proofs. + pub buffer: RequestBuffer, + /// The minimum number of proofs required for a request to be promotable from buffer to tree. + pub min_required_proofs: usize, +} + +impl Default for State { + /// Create a new State with default min required proofs. + fn default() -> Self { + Self { + latest_fcs: None, + last_valid_fcs: ForkchoiceState { + head_block_hash: ExecutionBlockHash::zero(), + safe_block_hash: ExecutionBlockHash::zero(), + finalized_block_hash: ExecutionBlockHash::zero(), + }, + tree: TreeState::default(), + buffer: RequestBuffer::default(), + min_required_proofs: MIN_REQUIRED_EXECUTION_PROOFS, + } + } +} + +impl State { + /// Create a new State with default values. + pub fn new() -> Self { + Self::default() + } + + /// Return all buffer entries on the ancestor path required to satisfy `latest_fcs`, + /// including entries that already have sufficient proofs. + /// + /// Complete entries are returned so the sync layer can include them as skip-filters in + /// `ExecutionProofsByRange` requests, telling the serving peer not to re-send proofs + /// for blocks the requester already holds. Callers should inspect `existing_proof_types` + /// against the configured proof type set to determine which entries are still missing. + /// + /// If `latest_fcs` is unset there is no pending fork-choice update to satisfy, so + /// nothing is returned. Otherwise the buffer is walked backwards from + /// `latest_fcs.head_block_hash` until a block is not found in the buffer. + pub fn missing_proofs(&self) -> Vec { + let Some(latest_fcs) = &self.latest_fcs else { + return vec![]; + }; + + // Build block_hash → &PayloadRequest for O(1) lookup during the walk. + let buffer_by_block_hash: HashMap = self + .buffer + .proofs + .values() + .map(|p| (p.metadata.block_hash, p)) + .collect(); + + // Walk backwards from the FCS head through buffer entries, collecting all + // entries (missing and complete). Stop when a block is not in the buffer + // (reached the tree or an unseen block). + let mut result = Vec::new(); + let mut current = latest_fcs.head_block_hash; + loop { + let Some(req) = buffer_by_block_hash.get(¤t) else { + break; + }; + result.push(MissingProofInfo { + root: req.metadata.request_root, + existing_proof_types: req.proofs.iter().map(|p| p.message.proof_type).collect(), + slot: Default::default(), // populated by BeaconChain::missing_execution_proofs() + }); + current = req.metadata.parent_hash; + } + + result + } + + /// Number of payload requests currently tracked in the tree. + pub fn tree_len(&self) -> usize { + self.tree.proofs_by_block_hash.len() + } + + /// Number of payload requests currently in the pre-tree buffer. + pub fn buffer_len(&self) -> usize { + self.buffer.proofs.len() + } + + /// Check if the state contains any proofs associated with the given new payload request root. + pub fn contains_request_root(&self, request_root: &Hash256) -> bool { + self.tree + .request_root_to_block_hash + .contains_key(request_root) + || self.buffer.proofs.contains_key(request_root) + } + + /// Buffer a new payload request for future proof association. + pub fn buffer_request(&mut self, request: RequestMetadata) { + if self + .tree + .request_root_to_block_hash + .contains_key(&request.request_root) + { + tracing::warn!(target: "execution_layer", request_root = ?request.request_root, "Attempting to buffer a request that is already associated with a block hash in the tree - skipping buffer insertion"); + return; + } + + if self.buffer.proofs.contains_key(&request.request_root) { + tracing::debug!(target: "execution_layer", request_root = ?request.request_root, "Request is already buffered - skipping buffer insertion"); + return; + } + + self.buffer.insert(request); + } + + /// Validate and update the latest fork choice state. + pub fn forkchoice_updated( + &mut self, + forkchoice_state: ForkchoiceState, + ) -> Result { + let head = forkchoice_state.head_block_hash; + let safe = forkchoice_state.safe_block_hash; + let finalized = forkchoice_state.finalized_block_hash; + + // When tree is empty, always update last_valid_fcs to track finalized block + // This allows finalized to advance during sync before any blocks are promoted + // TODO: Reconsider this logic - maybe we just always update the finalized block in last_valid_fcs and allow syncing until we have observed the head block hash? + if self.tree.is_empty() && finalized != ExecutionBlockHash::zero() { + // Create a baseline forkchoice state anchored at finalized block + let bootstrap_fcs = ForkchoiceState { + head_block_hash: finalized, + safe_block_hash: finalized, + finalized_block_hash: finalized, + }; + self.last_valid_fcs = bootstrap_fcs; + self.latest_fcs = Some(forkchoice_state); + self.tree.current_canonical_head = finalized; + + tracing::info!(target: "execution_layer", ?finalized, "Updated last_valid_fcs to finalized block (tree empty)"); + + // Check if any buffered requests can be promoted based on the new last_valid_fcs. + let mut promote_requests = Vec::new(); + for request in self.buffer.proofs.keys() { + if self.can_promote(request)? { + promote_requests.push(*request); + } + } + // Promote any buffered requests that can now be associated with the tree state. + for request_root in promote_requests { + if let Some(latest_canonical_head) = self.promote_buffered_requests(request_root)? { + tracing::info!(target: "execution_layer", ?latest_canonical_head, "Updated canonical head after promoting buffered proofs"); + } + } + + return Ok(self.forkchoice_response_syncing()); + } + + let new_safe_zero = safe.is_zero(); + let new_finalized_zero = finalized.is_zero(); + let safe = if !new_safe_zero { + safe + } else { + self.last_valid_fcs.safe_block_hash + }; + let finalized = if !new_finalized_zero { + finalized + } else { + self.last_valid_fcs.finalized_block_hash + }; + + // If we have not observed the head block hash yet, we cannot validate the forkchoice + if !self.tree.proofs_by_block_hash.contains_key(&head) { + tracing::debug!(target: "execution_layer", ?head, "Forkchoice update head not found in tree state, marking as syncing"); + self.latest_fcs = Some(forkchoice_state); + return Ok(self.forkchoice_response_syncing()); + } + + // Validate that the safe block is in the tree (this is a quick sanity check so we don't have to traverse the tree) + if !new_safe_zero && !self.tree.proofs_by_block_hash.contains_key(&safe) { + tracing::warn!(target: "execution_layer", ?safe, "Forkchoice update safe block hash not found in tree state - invalid forkchoice"); + return Ok(self.forkchoice_response_invalid()); + } + + // Validate that the finalized block is in the tree (this is a quick sanity check so we don't have to traverse the tree) + if !new_finalized_zero && !self.tree.proofs_by_block_hash.contains_key(&finalized) { + tracing::warn!(target: "execution_layer", ?finalized, "Forkchoice update finalized block hash not found in tree state - invalid forkchoice"); + return Ok(self.forkchoice_response_invalid()); + } + + // Validate the ancestry chain: head -> safe -> finalized + if !self.is_descendant(safe, head) { + tracing::error!(target: "execution_layer", ?head, ?safe, "Forkchoice update is invalid - safe block is not an ancestor of head"); + return Ok(self.forkchoice_response_invalid()); + } + + if !new_safe_zero && !self.is_descendant(finalized, safe) { + tracing::error!(target: "execution_layer", ?safe, ?finalized, "Forkchoice update is invalid - finalized block is not an ancestor of safe"); + return Ok(self.forkchoice_response_invalid()); + } + + if !self.is_descendant(self.last_valid_fcs.finalized_block_hash, finalized) { + tracing::error!(target: "execution_layer", ?head, ?safe, ?finalized, "Forkchoice update is invalid - new finalized block is not a descendant of last valid finalized block"); + return Ok(self.forkchoice_response_invalid()); + } + + // Determine if we need to update the canonical head + let update_canonical_head = if head == self.tree.current_canonical_head { + tracing::debug!(target: "execution_layer", ?head, "Forkchoice update head matches current canonical head"); + false + } else if self.is_descendant(head, self.tree.current_canonical_head) { + tracing::debug!(target: "execution_layer", ?head, "Forkchoice update head is a ancestor of current canonical head - skip head update"); + false + } else { + tracing::debug!(target: "execution_layer", ?head, "Forkchoice update head is on a fork, updating canonical head pending validation"); + true + }; + + if update_canonical_head { + tracing::info!(target: "execution_layer", ?head, "Updating canonical head to new forkchoice head"); + self.tree.current_canonical_head = head; + } + + let prune_finalized = + !new_finalized_zero && (self.last_valid_fcs.finalized_block_hash != finalized); + + if prune_finalized { + self.prune_finalized_sidechains(finalized)?; + } + + self.last_valid_fcs = ForkchoiceState { + head_block_hash: head, + safe_block_hash: safe, + finalized_block_hash: finalized, + }; + Ok(self.forkchoice_response_valid()) + } + + /// Get all execution proofs associated with the given new payload request root. + pub fn get_proofs(&self, root: &Hash256) -> Option<&[SignedExecutionProof]> { + self.tree + .request_root_to_block_hash + .get(root) + .and_then(|h| self.tree.proofs_by_block_hash.get(h)) + .map(|p| p.proofs.as_slice()) + .or_else(|| self.buffer.proofs.get(root).map(|b| b.proofs.as_slice())) + .filter(|slice| !slice.is_empty()) + } + + /// Insert a new execution proof into state. + pub fn insert_proof( + &mut self, + proof: SignedExecutionProof, + ) -> Result { + let request_root = proof.request_root(); + + // Insert into the tree if associated block hash is found. + if let Some(block_hash) = self.tree.request_root_to_block_hash.get(&request_root) { + // Insert into the tree associated with the block hash. + let proofs = self.tree.proofs_by_block_hash.get_mut(block_hash).ok_or( + ProofEngineStateError::BlockHashNotFoundForRequestRoot { + request_root, + block_hash: *block_hash, + }, + )?; + proofs.proofs.push(proof); + return Ok(ProofStatus::Accepted); + } + + // Insert into the buffer if associated request root is found. + if let Some(buffered_request) = self.buffer.proofs.get_mut(&request_root) { + buffered_request.proofs.push(proof); + } else { + // We only process proofs that are associated with a request root from an observed beacon block. + return Err(ProofEngineStateError::ProofRequestRootNotSeen(request_root)); + }; + + if self.can_promote(&request_root)? + && let Some(latest_canonical_head) = self.promote_buffered_requests(request_root)? + { + tracing::info!(target: "execution_layer", ?latest_canonical_head, "Updated canonical head after promoting buffered proofs"); + return Ok(ProofStatus::Valid); + } + + Ok(ProofStatus::Accepted) + } + + /// Promote buffered requests that can now be associated with the tree state. + /// + /// Returns the latest canonical head if it was updated. + fn promote_buffered_requests( + &mut self, + request_root: Hash256, + ) -> Result, ProofEngineStateError> { + let (block_hash, updated_head) = self.promote_buffered_request(request_root)?; + let mut latest_head = if updated_head { + Some(self.tree.current_canonical_head) + } else { + None + }; + + // Promote any child requests that can now be associated that have sufficient proofs. + let mut queue = vec![block_hash]; + while let Some(parent_hash) = queue.pop() { + let promotable_roots: Vec = self + .buffer + .proofs + .iter() + .filter(|(_, buffered)| { + buffered.metadata.parent_hash == parent_hash + && buffered.proofs.len() >= MIN_REQUIRED_EXECUTION_PROOFS + }) + .map(|(root, _)| *root) + .collect(); + + for request_root in promotable_roots { + let (block_hash, updated_head) = self.promote_buffered_request(request_root)?; + if updated_head { + latest_head = Some(self.tree.current_canonical_head); + } + queue.push(block_hash); + } + } + + Ok(latest_head) + } + + /// Promote a buffered request into the tree state. + /// + /// Returns the block hash and whether the canonical head was updated. + fn promote_buffered_request( + &mut self, + request_root: Hash256, + ) -> Result<(ExecutionBlockHash, bool), ProofEngineStateError> { + let buffered_request = self + .buffer + .proofs + .remove(&request_root) + .ok_or(ProofEngineStateError::BufferedRequestNotFound(request_root))?; + let RequestMetadata { + block_hash, + parent_hash, + .. + } = buffered_request.metadata; + + self.tree + .block_number_to_block_hash + .entry(buffered_request.metadata.block_number) + .or_default() + .insert(block_hash); + + self.tree + .parent_to_children + .entry(parent_hash) + .or_default() + .insert(block_hash); + self.tree + .proofs_by_block_hash + .insert(block_hash, buffered_request); + self.tree + .request_root_to_block_hash + .insert(request_root, block_hash); + + // If the promoted block is the parent of the current canonical head, update the canonical head to the promoted block. + if self.tree.current_canonical_head == parent_hash { + self.tree.current_canonical_head = block_hash; + return Ok((block_hash, true)); + } + + // If the promoted block is equal to the current canonical head, we return the block hash and return true to indicate the tree head has been updated. + if self.tree.current_canonical_head == block_hash { + return Ok((block_hash, true)); + } + + Ok((block_hash, false)) + } + + fn forkchoice_response_valid(&self) -> ForkchoiceUpdatedResponse { + ForkchoiceUpdatedResponse { + payload_status: PayloadStatusV1 { + status: PayloadStatusV1Status::Valid, + latest_valid_hash: self.tree.current_canonical_head.into(), + validation_error: None, + }, + payload_id: None, + } + } + + fn forkchoice_response_syncing(&self) -> ForkchoiceUpdatedResponse { + ForkchoiceUpdatedResponse { + payload_status: PayloadStatusV1 { + status: PayloadStatusV1Status::Syncing, + latest_valid_hash: None, + validation_error: None, + }, + payload_id: None, + } + } + + fn forkchoice_response_invalid(&self) -> ForkchoiceUpdatedResponse { + ForkchoiceUpdatedResponse { + payload_status: PayloadStatusV1 { + status: PayloadStatusV1Status::Invalid, + latest_valid_hash: self.tree.current_canonical_head.into(), + validation_error: Some("invalid forkchoice state".to_string()), + }, + payload_id: None, + } + } + + /// Check if a block can be promoted from buffer to tree. + /// + /// A block can be promoted if: + /// 1. Its parent is already in the tree (normal case), OR + /// 2. It's a finalized block: + /// - Block hash matches last_valid_fcs.finalized_block_hash + fn can_promote(&self, request: &Hash256) -> Result { + let request = self + .buffer + .proofs + .get(request) + .ok_or(ProofEngineStateError::BufferedRequestNotFound(*request))?; + + if request.proofs.len() < self.min_required_proofs { + return Ok(false); + } + + // Normal case: parent already in tree + if self + .tree + .proofs_by_block_hash + .contains_key(&request.metadata.parent_hash) + { + return Ok(true); + } + + // Bootstrap case: allow finalized block when starting empty tree + if request.metadata.block_hash == self.tree.current_canonical_head + || request.metadata.parent_hash == self.tree.current_canonical_head + { + tracing::debug!(target: "execution_layer", block_hash = ?request.metadata.block_hash, "Allowing promotion of finalized block during bootstrap"); + return Ok(true); + } + + Ok(false) + } + + /// Check if `target` is a descendant of `ancestor` in the tree. + fn is_descendant(&self, ancestor: ExecutionBlockHash, target: ExecutionBlockHash) -> bool { + let mut current = target; + + loop { + if current == ancestor { + return true; + } + + let Some(proofs) = self.tree.proofs_by_block_hash.get(¤t) else { + return false; + }; + + current = proofs.metadata.parent_hash; + } + } + + fn block_number_for_hash(&self, block_hash: ExecutionBlockHash) -> Option { + self.tree + .proofs_by_block_hash + .get(&block_hash) + .map(|p| p.metadata.block_number) + } + + // TODO: We should also prune buffered requests that are associated with sidechains that have been removed using parent to children mapping. + fn prune_finalized_sidechains( + &mut self, + finalized_hash: ExecutionBlockHash, + ) -> Result<(), ProofEngineStateError> { + // Get the finalized block number. + // TODO: Maybe this should just return SYNCING instead. + let finalized_number = self + .block_number_for_hash(finalized_hash) + .ok_or(ProofEngineStateError::BlockNumberNotFound(finalized_hash))?; + + // Remove buffered proofs below or at the finalized block number. + self.buffer.proofs.retain(|_root, entry| { + (entry.metadata.block_number > finalized_number) + || (entry.metadata.block_hash == finalized_hash) + }); + + // Remove all blocks with a block number below the finalized number. + let mut block_hashes_to_remove = self + .tree + .block_number_to_block_hash + .split_off(&finalized_number); + mem::swap( + &mut block_hashes_to_remove, + &mut self.tree.block_number_to_block_hash, + ); + + for hashes in block_hashes_to_remove.into_values().flatten() { + // Remove all block hash from state. We ignore returned children as they will have been + // removed in this loop already. Any children on sidechains with a higher block number will be + // removed in the next step. + let _ = self.remove_request(hashes)?; + } + + // Remove all block hashes at the finalized block number except the finalized hash. + let mut to_remove: Vec<_> = if let Some(hashes) = self + .tree + .block_number_to_block_hash + .get_mut(&finalized_number) + { + let mut to_remove = mem::replace(hashes, HashSet::from([finalized_hash])); + to_remove.remove(&finalized_hash); + to_remove.into_iter().collect() + } else { + return Ok(()); + }; + + // Recursively remove children of the removed block hashes. + while let Some(block_hash) = to_remove.pop() { + if let Some(children) = self.remove_request(block_hash)? { + to_remove.extend(children); + } + } + + Ok(()) + } + + /// Remove a request and its associated proofs from the tree state. + fn remove_request( + &mut self, + block_hash: ExecutionBlockHash, + ) -> Result>, ProofEngineStateError> { + // TODO: Update to proper error handling + let entry = self + .tree + .proofs_by_block_hash + .remove(&block_hash) + .ok_or(ProofEngineStateError::BlockNumberNotFound(block_hash))?; + self.tree + .request_root_to_block_hash + .remove(&entry.metadata.request_root); + let children = self.tree.parent_to_children.remove(&block_hash); + if let Entry::Occupied(mut occ) = self + .tree + .block_number_to_block_hash + .entry(entry.metadata.block_number) + { + occ.get_mut().remove(&block_hash); + if occ.get().is_empty() { + occ.remove(); + } + } + Ok(children) + } + + /// Create a new State with the specified minimum required proofs for promotion. + #[cfg(test)] + pub fn with_min_required_proofs(min_required_proofs: usize) -> Self { + Self { + latest_fcs: None, + last_valid_fcs: ForkchoiceState { + head_block_hash: ExecutionBlockHash::zero(), + safe_block_hash: ExecutionBlockHash::zero(), + finalized_block_hash: ExecutionBlockHash::zero(), + }, + tree: TreeState::default(), + buffer: RequestBuffer::default(), + min_required_proofs, + } + } +} + +/// Keeps track of execution proofs in a tree structure. +/// +/// - All proofs are associated with EL blocks connected to the current canonical chain. +#[derive(Debug, Default, Clone)] +pub struct TreeState { + /// Map of execution block hash to execution proofs. + pub proofs_by_block_hash: HashMap, + /// Map of new payload request root to execution block hash. + pub request_root_to_block_hash: HashMap, + /// Map of parent block hash to child block hashes. + pub parent_to_children: HashMap>, + /// Map of block number to block hashes at that height. + pub block_number_to_block_hash: BTreeMap>, + /// The current canonical head block hash. + pub current_canonical_head: ExecutionBlockHash, +} + +impl TreeState { + /// Check if the tree is empty (no blocks inserted yet) + pub fn is_empty(&self) -> bool { + self.proofs_by_block_hash.is_empty() + } +} + +/// A buffer of new payload requests and their associated execution proofs. +#[derive(Debug, Default, Clone)] +pub struct RequestBuffer { + /// Map of new payload request root to execution proofs. + pub proofs: HashMap, +} + +impl RequestBuffer { + /// Insert a new payload request into the buffer. + /// + /// This will not overwrite existing requests. + pub fn insert(&mut self, request: RequestMetadata) { + self.proofs + .entry(request.request_root) + .or_insert_with(|| PayloadRequest::new(request)); + } +} + +#[derive(Debug, Clone)] +pub struct PayloadRequest { + /// The new payload request root associated with these proofs. + pub metadata: RequestMetadata, + /// Collection of signed execution proofs. + pub proofs: Vec, +} + +impl PayloadRequest { + pub fn new(metadata: RequestMetadata) -> Self { + Self { + metadata, + proofs: Vec::new(), + } + } +} + +#[derive(Debug, Default, Clone)] +pub struct RequestMetadata { + /// The new payload request root associated with the request. + pub request_root: Hash256, + /// The execution block hash associated with the new payload request. + pub block_hash: ExecutionBlockHash, + /// The parent block hash of the new payload request. + pub parent_hash: ExecutionBlockHash, + /// The block number of the new payload request. + pub block_number: u64, +} + +impl From<&NewPayloadRequest<'_, E>> for RequestMetadata { + fn from(request: &NewPayloadRequest<'_, E>) -> Self { + Self { + request_root: request.clone().tree_hash_root(), + block_hash: request.block_hash(), + parent_hash: request.parent_hash(), + block_number: request.block_number(), + } + } +} + +#[cfg(test)] +pub mod test_utils { + use super::*; + use bls::SignatureBytes; + use ssz_types::VariableList; + use types::{ExecutionProof, PublicInput}; + + pub fn test_hash(byte: u8) -> Hash256 { + Hash256::repeat_byte(byte) + } + + pub fn test_exec_hash(byte: u8) -> ExecutionBlockHash { + ExecutionBlockHash::repeat_byte(byte) + } + + pub fn create_request_metadata( + request_root: Hash256, + block_hash: ExecutionBlockHash, + parent_hash: ExecutionBlockHash, + block_number: u64, + ) -> RequestMetadata { + RequestMetadata { + request_root, + block_hash, + parent_hash, + block_number, + } + } + + pub fn create_signed_proof( + request_root: Hash256, + validator_index: u64, + ) -> SignedExecutionProof { + create_signed_proof_with_type(request_root, validator_index, 1) + } + + pub fn create_signed_proof_with_type( + request_root: Hash256, + validator_index: u64, + proof_type: u8, + ) -> SignedExecutionProof { + SignedExecutionProof { + message: ExecutionProof { + proof_data: VariableList::new(vec![0xaa, 0xbb, 0xcc]).unwrap(), + proof_type, + public_input: PublicInput { + new_payload_request_root: request_root, + }, + }, + validator_index, + signature: SignatureBytes::empty(), + } + } + + pub fn create_forkchoice_state( + head: ExecutionBlockHash, + safe: ExecutionBlockHash, + finalized: ExecutionBlockHash, + ) -> ForkchoiceState { + ForkchoiceState { + head_block_hash: head, + safe_block_hash: safe, + finalized_block_hash: finalized, + } + } + + /// Test data provider for state tests + /// + /// Generates payload requests, proofs, and hashes. + pub struct TestStateFixture { + /// Generated block data + /// blocks[0] = canonical chain + /// blocks[1] = fork 0 + /// blocks[2] = fork 1 + /// etc. + pub blocks: Vec>, + } + + impl TestStateFixture { + /// Get the genesis fcs + /// + /// Defined as the first block in the canonical chain + pub fn genesis_fcs(&self) -> ForkchoiceState { + let finalized_block = &self.blocks[0][0]; + create_forkchoice_state( + finalized_block.metadata.block_hash, + finalized_block.metadata.block_hash, + finalized_block.metadata.block_hash, + ) + } + + /// Get canonical chain block data + pub fn canonical(&self, index: usize) -> &PayloadRequest { + &self.blocks[0][index] + } + + /// Get fork block data + pub fn fork(&self, fork_id: usize, index: usize) -> &PayloadRequest { + &self.blocks[fork_id + 1][index] + } + + /// Get canonical block hash + pub fn canonical_block_hash(&self, index: usize) -> ExecutionBlockHash { + self.canonical(index).metadata.block_hash + } + + /// Get fork block hash + pub fn fork_block_hash(&self, fork_id: usize, index: usize) -> ExecutionBlockHash { + self.fork(fork_id, index).metadata.block_hash + } + + /// Get canonical request root + pub fn canonical_request_root(&self, index: usize) -> Hash256 { + self.canonical(index).metadata.request_root + } + + /// Get canonical metadata + pub fn canonical_metadata(&self, index: usize) -> RequestMetadata { + self.canonical(index).metadata.clone() + } + + /// Get fork metadata + pub fn fork_metadata(&self, fork_id: usize, index: usize) -> RequestMetadata { + self.fork(fork_id, index).metadata.clone() + } + + /// Get canonical proofs + pub fn canonical_proofs(&self, index: usize) -> &[SignedExecutionProof] { + &self.canonical(index).proofs + } + + /// Get fork proofs + pub fn fork_proofs(&self, fork_id: usize, index: usize) -> &[SignedExecutionProof] { + &self.fork(fork_id, index).proofs + } + + pub fn bootstrap_canonical(&self, state: &mut State) -> anyhow::Result<()> { + state.forkchoice_updated(self.genesis_fcs())?; + self.insert_canonical(state, None)?; + Ok(()) + } + + /// Insert the canonical chain into state (buffer + add proofs) + pub fn insert_canonical( + &self, + state: &mut State, + block_index: Option, + ) -> anyhow::Result<()> { + let range = match block_index { + Some(i) => i..=i, + None => 0..=self.blocks[0].len() - 1, + }; + for index in range { + state.buffer_request(self.canonical_metadata(index)); + for proof in self.canonical_proofs(index) { + let _ = state.insert_proof(proof.clone())?; + } + } + Ok(()) + } + + /// Insert a fork into state (buffer + add proofs) + pub fn insert_fork( + &self, + state: &mut State, + fork_id: usize, + block_index: Option, + ) -> anyhow::Result<()> { + let range = match block_index { + Some(i) => i..=i, + None => 0..=self.blocks[fork_id + 1].len() - 1, + }; + for index in range { + state.buffer_request(self.fork_metadata(fork_id, index)); + for proof in self.fork_proofs(fork_id, index) { + let _ = state.insert_proof(proof.clone())?; + } + } + + Ok(()) + } + } + + /// Builder for test state fixture + pub struct TestStateFixtureBuilder { + /// Number of blocks in canonical chain + pub canonical_chain_length: usize, + + /// Fork configurations (branch_point, fork_length, proofs_per_block) + pub forks: Vec<(usize, usize, Option)>, + + /// Default proofs per block + pub proofs_per_block: usize, + + /// Starting block number + pub starting_block_number: u64, + } + + impl Default for TestStateFixtureBuilder { + fn default() -> Self { + Self::new() + } + } + + impl TestStateFixtureBuilder { + /// Create new builder + pub fn new() -> Self { + Self { + canonical_chain_length: 0, + forks: Vec::new(), + proofs_per_block: MIN_REQUIRED_EXECUTION_PROOFS, + starting_block_number: 0, + } + } + + /// Create a simple chain with 3 blocks in the canonical chain + pub fn simple_chain() -> Self { + Self::new().with_canonical_chain(3) + } + + /// Set default proofs per block + pub fn with_proofs_per_block(mut self, proofs: usize) -> Self { + self.proofs_per_block = proofs; + self + } + + /// Set canonical chain length + pub fn with_canonical_chain(mut self, length: usize) -> Self { + self.canonical_chain_length = length; + self + } + + /// Add a fork (uses default proofs per block) + pub fn with_fork( + mut self, + branch_point: usize, + fork_length: usize, + proofs_per_block: Option, + ) -> Self { + self.forks + .push((branch_point, fork_length, proofs_per_block)); + self + } + + /// Build the fixture + pub fn build(self) -> TestStateFixture { + let mut fixture = TestStateFixture { + blocks: vec![Vec::new()], // Start with empty canonical chain + }; + + // Generate canonical chain (chain_id = 0) + for i in 0..self.canonical_chain_length { + let parent_hash = if i == 0 { + test_exec_hash(0xff) // Genesis parent + } else { + fixture.blocks[0][i - 1].metadata.block_hash + }; + + let block_number = self.starting_block_number + i as u64; + let block_data = self.generate_block( + 0, // chain_id + i, // block index within chain + parent_hash, + block_number, + self.proofs_per_block, + ); + + fixture.blocks[0].push(block_data); + } + + // Generate forks + for (fork_idx, (branch_point, fork_length, custom_proofs)) in + self.forks.iter().enumerate() + { + let proof_count = custom_proofs.unwrap_or(self.proofs_per_block); + let mut fork_blocks: Vec = Vec::new(); + + for i in 0..*fork_length { + let parent_hash = if i == 0 { + // First fork block connects to canonical chain + fixture.blocks[0][*branch_point].metadata.block_hash + } else { + // Subsequent blocks connect to previous fork block + fork_blocks[i - 1].metadata.block_hash + }; + + let block_number = + self.starting_block_number + *branch_point as u64 + i as u64 + 1; + + let block_data = self.generate_block( + fork_idx + 1, // chain_id (fork 0 = chain 1, fork 1 = chain 2, etc.) + i, + parent_hash, + block_number, + proof_count, + ); + + fork_blocks.push(block_data); + } + + fixture.blocks.push(fork_blocks); + } + + fixture + } + + /// Generate data for a single block + pub fn generate_block( + &self, + chain_id: usize, + block_index: usize, + parent_hash: ExecutionBlockHash, + block_number: u64, + proof_count: usize, + ) -> PayloadRequest { + // Create unique hashes based on chain_id and block_index + let hash_seed = (chain_id * 1000 + block_index) % 256; + let block_hash = test_exec_hash(hash_seed as u8); + let request_root = test_hash(((hash_seed + 0x10) % 256) as u8); + + let metadata = + create_request_metadata(request_root, block_hash, parent_hash, block_number); + + // Generate proofs with distinct proof types to avoid deduplication. + let mut proofs = Vec::new(); + for i in 0..proof_count { + proofs.push(create_signed_proof_with_type( + request_root, + request_root.0[0] as u64 + i as u64, + (i as u8).wrapping_add(1), // types 1, 2, 3, ... (avoid 0) + )); + } + + PayloadRequest { metadata, proofs } + } + } +} // end test_utils + +#[cfg(test)] +mod tests { + use super::test_utils::*; + use super::*; + + #[test] + fn test_buffer_request_new() { + let fixture = TestStateFixtureBuilder::new() + .with_canonical_chain(1) + .build(); + + let request = fixture.canonical(0); + + let mut state = State::new(); + state.buffer_request(request.metadata.clone()); + + assert_eq!( + state.buffer.proofs.len(), + 1, + "buffer should contain exactly one request" + ); + assert!( + state + .buffer + .proofs + .contains_key(&request.metadata.request_root), + "buffer should contain the request root" + ); + let buffered = state + .buffer + .proofs + .get(&request.metadata.request_root) + .expect("buffered request should exist"); + assert_eq!( + buffered.metadata.block_hash, request.metadata.block_hash, + "buffered request should have correct block hash" + ); + assert_eq!( + buffered.proofs.len(), + 0, + "newly buffered request should have no proofs" + ); + } + + #[test] + fn test_buffer_request_preserves_proofs_on_duplicate() -> anyhow::Result<()> { + let fixture = TestStateFixtureBuilder::new() + .with_proofs_per_block(4) + .with_canonical_chain(1) + .build(); + let mut state = State::with_min_required_proofs(3); + + // Buffer request + let request = fixture.canonical(0); + state.buffer_request(request.metadata.clone()); + + // Add multiple proofs + for i in 0..2 { + state.insert_proof(request.proofs[i].clone())?; + } + + // Verify proofs exist + let proofs_before = state + .buffer + .proofs + .get(&request.metadata.request_root) + .expect("request should be buffered") + .proofs + .len(); + assert_eq!( + proofs_before, 2, + "should have 2 proofs before re-buffer attempt" + ); + + // Attempt to buffer again + state.buffer_request(request.metadata.clone()); + + // Verify proofs preserved + assert_eq!( + state.buffer.proofs.len(), + 1, + "buffer should still contain exactly one request" + ); + let proofs_after = state + .buffer + .proofs + .get(&request.metadata.request_root) + .expect("request should still be buffered") + .proofs + .len(); + assert_eq!( + proofs_after, 2, + "all proofs should be preserved after duplicate buffer attempt" + ); + + Ok(()) + } + + #[test] + fn test_buffer_request_skips_if_promoted_exists() -> anyhow::Result<()> { + let fixture = TestStateFixtureBuilder::simple_chain().build(); + let mut state = State::new(); + fixture.bootstrap_canonical(&mut state)?; + + let request = fixture.canonical(2); + + // Assert promoted + assert!( + state + .tree + .proofs_by_block_hash + .contains_key(&request.metadata.block_hash), + "block should be promoted to tree" + ); + assert!( + !state + .buffer + .proofs + .contains_key(&request.metadata.request_root), + "block should be removed from buffer after promotion" + ); + + // Try buffer again + state.buffer_request(request.metadata.clone()); + + // Verify it stays in tree and is not re-added to buffer + assert!( + state + .tree + .proofs_by_block_hash + .contains_key(&request.metadata.block_hash), + "block should remain in tree" + ); + assert!( + !state + .buffer + .proofs + .contains_key(&request.metadata.request_root), + "block should not be added back to buffer" + ); + + Ok(()) + } + + #[test] + fn test_insert_proof_unknown_request_root() { + let fixture = TestStateFixtureBuilder::new() + .with_canonical_chain(1) + .build(); + let mut state = State::new(); + + let request = fixture.canonical(0); + let result = state.insert_proof(request.proofs[0].clone()); + + assert!( + result.is_err(), + "inserting proof for unknown request root should return error" + ); + match result { + Err(ProofEngineStateError::ProofRequestRootNotSeen(root)) => { + assert_eq!( + root, request.metadata.request_root, + "error should contain the unknown root" + ); + } + _ => panic!("expected ProofRequestRootNotSeen error"), + } + } + + #[test] + fn test_promotion() -> anyhow::Result<()> { + let fixture = TestStateFixtureBuilder::simple_chain() + .with_proofs_per_block(4) + .with_fork(1, 1, None) + .build(); + let mut state = State::with_min_required_proofs(4); + + let request = fixture.canonical(0); + state.forkchoice_updated(fixture.genesis_fcs())?; + state.buffer_request(request.metadata.clone()); + for i in 0..request.proofs.len() - 1 { + assert_eq!( + state + .insert_proof(request.proofs[i].clone()) + .expect("proof insertion should succeed"), + ProofStatus::Accepted, + "proof insertion should be accepted before reaching threshold" + ); + } + + // Verify no promotion yet + assert!( + state + .buffer + .proofs + .contains_key(&request.metadata.request_root), + "request should still be in buffer before reaching proof threshold" + ); + assert!( + !state + .tree + .proofs_by_block_hash + .contains_key(&request.metadata.block_hash), + "block should not be in tree before reaching proof threshold" + ); + + // Insert final proof to trigger promotion + assert_eq!( + state + .insert_proof(request.proofs[request.proofs.len() - 1].clone()) + .expect("proof insertion should succeed"), + ProofStatus::Valid + ); + + // Verify promotion occurred + assert!( + !state + .buffer + .proofs + .contains_key(&request.metadata.request_root), + "promoted request should be removed from buffer" + ); + assert!( + state + .tree + .proofs_by_block_hash + .contains_key(&request.metadata.block_hash), + "promoted request should be added to tree" + ); + assert!( + state + .tree + .request_root_to_block_hash + .contains_key(&request.metadata.request_root), + "request root mapping should be created" + ); + assert_eq!( + state.tree.current_canonical_head, request.metadata.block_hash, + "canonical head should be updated to child of previous head" + ); + + // Verify parent-child relationship + let children = state + .tree + .parent_to_children + .get(&request.metadata.parent_hash) + .expect("parent should have children"); + assert!( + children.contains(&request.metadata.block_hash), + "parent should reference child in parent_to_children map" + ); + + // Verify block number mapping + let blocks_at_height = state + .tree + .block_number_to_block_hash + .get(&0) + .expect("height 0 should exist"); + assert!( + blocks_at_height.contains(&request.metadata.block_hash), + "block should be in block_number_to_block_hash map" + ); + + // Now insert canonical block 2 with all proof - there should be no promotion yet as block 1 is not in the tree + fixture.insert_canonical(&mut state, Some(2))?; + + // Verify block 2 is still in buffer + let request2 = fixture.canonical(2); + assert!( + state + .buffer + .proofs + .contains_key(&request2.metadata.request_root), + "block 2 should remain in buffer as parent is not in tree" + ); + + // Now insert block 1 insert the buffer and this should cascade promote block 1 and block 2 and update the canonical head to block 2 + fixture.insert_canonical(&mut state, Some(1))?; + + // Verify block 1 promoted + let request1 = fixture.canonical(1); + assert!( + !state + .buffer + .proofs + .contains_key(&request1.metadata.request_root), + "block 1 should be promoted from buffer" + ); + assert!( + state + .tree + .proofs_by_block_hash + .contains_key(&request1.metadata.block_hash), + "block 1 should be in tree" + ); + + // Verify block 2 promoted + assert!( + !state + .buffer + .proofs + .contains_key(&request2.metadata.request_root), + "block 2 should be promoted from buffer" + ); + assert!( + state + .tree + .proofs_by_block_hash + .contains_key(&request2.metadata.block_hash), + "block 2 should be in tree" + ); + + // Verify canonical head updated to block 2 + assert_eq!( + state.tree.current_canonical_head, request2.metadata.block_hash, + "canonical head should be updated to block 2" + ); + + // Now lets insert the fork into the tree and assert its promoted but does not affect the canonical head + fixture.insert_fork(&mut state, 0, None)?; + + // Verify fork block promoted + let fork_request = fixture.fork(0, 0); + assert!( + !state + .buffer + .proofs + .contains_key(&fork_request.metadata.request_root), + "fork block should be promoted from buffer" + ); + assert!( + state + .tree + .proofs_by_block_hash + .contains_key(&fork_request.metadata.block_hash), + "fork block should be in tree" + ); + assert_eq!( + state.tree.current_canonical_head, request2.metadata.block_hash, + "canonical head should remain at block 2 after fork promotion" + ); + + Ok(()) + } + + #[test] + fn test_forkchoice_updated_head_not_in_tree() -> anyhow::Result<()> { + let mut state = State::new(); + let fixture = TestStateFixtureBuilder::simple_chain().build(); + + // Bootstrap and insert canonical chain + fixture.bootstrap_canonical(&mut state)?; + + // Update forkchoice with unknown head + let finalized_hash = fixture.canonical_block_hash(0); + let safe_hash = fixture.canonical_block_hash(0); + let unknown_head_hash = test_exec_hash(0xee); + let fcs = create_forkchoice_state(unknown_head_hash, safe_hash, finalized_hash); + + // Perform forkchoice update + let response = state.forkchoice_updated(fcs)?; + + assert_eq!( + response.payload_status.status, + PayloadStatusV1Status::Syncing, + "forkchoice update with unknown head should return SYNCING" + ); + + Ok(()) + } + + #[test] + fn test_forkchoice_invalid_ancestry_chain() -> anyhow::Result<()> { + let mut state = State::new(); + let fixture = TestStateFixtureBuilder::simple_chain() + .with_fork(1, 1, None) + .build(); + + // Bootstrap and insert canonical chain + fixture.bootstrap_canonical(&mut state)?; + + // Create a forkchoice state where the safe is not an ancestor of head and is not in the tree + let head_hash = fixture.canonical_block_hash(2); + let finalized_hash = fixture.canonical_block_hash(0); + let unknown_safe_hash = test_exec_hash(0xee); + let fcs = create_forkchoice_state(head_hash, unknown_safe_hash, finalized_hash); + + // Perform forkchoice update + let response = state.forkchoice_updated(fcs)?; + + // Verify INVALID response + assert_eq!( + response.payload_status.status, + PayloadStatusV1Status::Invalid, + "forkchoice update with invalid ancestry should return INVALID" + ); + + // Create a forkchoice state where the finalized is not an ancestor of safe and is not in the tree + let safe_hash = fixture.canonical_block_hash(1); + let unknown_finalized_hash = test_exec_hash(0xee); + let fcs = create_forkchoice_state(head_hash, safe_hash, unknown_finalized_hash); + + // Perform forkchoice update + let response = state.forkchoice_updated(fcs)?; + + // Verify INVALID response + assert_eq!( + response.payload_status.status, + PayloadStatusV1Status::Invalid, + "forkchoice update with invalid ancestry should return INVALID" + ); + + // Create a forkchoice state where safe is not an ancestor of head but is in the tree + let unknown_safe_hash = fixture.fork_block_hash(0, 0); + let fcs = create_forkchoice_state(head_hash, unknown_safe_hash, finalized_hash); + + // Perform forkchoice update + let response = state.forkchoice_updated(fcs)?; + + // Verify INVALID response + assert_eq!( + response.payload_status.status, + PayloadStatusV1Status::Invalid, + "forkchoice update with invalid ancestry should return INVALID" + ); + + Ok(()) + } + + #[test] + fn test_valid_forkchoice_update_with_new_fork_head() -> anyhow::Result<()> { + let fixture = TestStateFixtureBuilder::simple_chain() + .with_fork(1, 1, None) + .build(); + let mut state = State::new(); + + // Bootstrap and insert canonical chain + fixture.bootstrap_canonical(&mut state)?; + + // Extract canonical block hashes + let block_0_hash = fixture.canonical_block_hash(0); + let block_1_hash = fixture.canonical_block_hash(1); + let block_2_hash = fixture.canonical_block_hash(2); + + // Assert that the tree canonical head is block 2 + assert_eq!( + state.tree.current_canonical_head, block_2_hash, + "canonical head should be block 2" + ); + + // Create and update forkchoice state pointing to block 1 as head and block 0 as safe/finalized + let fcs = create_forkchoice_state(block_1_hash, block_0_hash, block_0_hash); + let response = state.forkchoice_updated(fcs)?; + + // Assert that the response is VALID and the canonical head remains at block 2 + assert_eq!( + response.payload_status.status, + PayloadStatusV1Status::Valid, + "forkchoice update should return VALID" + ); + assert_eq!( + state.tree.current_canonical_head, block_2_hash, + "canonical head should not change when updating to ancestor" + ); + + // Create and update forkchoice state pointing to block 2 as head and block 1 as safe and block 0 as finalized + let fcs = create_forkchoice_state(block_2_hash, block_1_hash, block_0_hash); + + // Perform forkchoice update + let response = state.forkchoice_updated(fcs).unwrap(); + + assert_eq!( + response.payload_status.status, + PayloadStatusV1Status::Valid, + "forkchoice update should return VALID" + ); + assert_eq!( + state.tree.current_canonical_head, block_2_hash, + "canonical head should not revert to ancestor" + ); + + // Insert the fork chain and update forkchoice to point to the fork head + fixture.insert_fork(&mut state, 0, None)?; + let fork_head_hash = fixture.fork_block_hash(0, 0); + let fcs = create_forkchoice_state(fork_head_hash, block_1_hash, block_0_hash); + + // Perform forkchoice update + let response = state.forkchoice_updated(fcs)?; + + // Verify VALID response and head updated to fork + assert_eq!( + response.payload_status.status, + PayloadStatusV1Status::Valid, + "forkchoice update to fork head should return VALID" + ); + assert_eq!( + state.tree.current_canonical_head, fork_head_hash, + "canonical head should be updated to fork head" + ); + + Ok(()) + } + + // TODO: We need to update this test when we update the prune logic for fork -> buffer mapping + #[test] + fn test_prune() -> anyhow::Result<()> { + let fixture = TestStateFixtureBuilder::simple_chain() + .with_proofs_per_block(4) + .with_fork(0, 4, None) + .with_fork(0, 4, Some(1)) + .build(); + let mut state = State::with_min_required_proofs(4); + // Bootstrap with canonical chain + fixture.bootstrap_canonical(&mut state)?; + + // Insert fork chain which should also insert the fork block into the tree + fixture.insert_fork(&mut state, 0, None)?; + + // Insert another fork with only 1 proof to ensure it is not promoted to the tree + // TODO: When logic is added to prune buffer properly then add this. + + // Assert tree contains expected blocks + assert_eq!( + state.tree.proofs_by_block_hash.len(), + 7, + "tree should contain 7 blocks before pruning" + ); + + // Issue forkchoice update that will prune the sidechain from the tree. + let finalized_hash = fixture.canonical_block_hash(1); + let safe_hash = finalized_hash; + let head_hash = fixture.canonical_block_hash(2); + let fcs = create_forkchoice_state(head_hash, safe_hash, finalized_hash); + + // Perform forkchoice update + let response = state.forkchoice_updated(fcs)?; + + // Assert the response is VALID + assert_eq!( + response.payload_status.status, + PayloadStatusV1Status::Valid, + "forkchoice update should return VALID" + ); + + // Assert that the fork chain has been pruned from the tree as has the canonical block 0 but the canonical blocks 1 and 2 remain + assert_eq!( + state.tree.proofs_by_block_hash.len(), + 2, + "tree should contain 2 blocks after pruning" + ); + + Ok(()) + } + + #[test] + fn test_get_proofs_from_tree() -> anyhow::Result<()> { + let fixture = TestStateFixtureBuilder::simple_chain().build(); + let mut state = State::new(); + + // Bootstrap and insert canonical chain + fixture.bootstrap_canonical(&mut state)?; + + // Retrieve proofs for genesis request root + let genesis_request = fixture.canonical(0); + let proofs = state.get_proofs(&genesis_request.metadata.request_root); + + assert!(proofs.is_some(), "should retrieve proofs from tree"); + assert_eq!( + proofs.unwrap().len(), + MIN_REQUIRED_EXECUTION_PROOFS, + "should retrieve all proofs from tree" + ); + + Ok(()) + } + + #[test] + fn test_get_proofs_from_buffer() -> anyhow::Result<()> { + let fixture = TestStateFixtureBuilder::simple_chain() + .with_fork(0, 1, Some(1)) + .build(); + let mut state = State::new(); + + // Bootstrap and insert canonical chain + fixture.bootstrap_canonical(&mut state)?; + + // Insert fork into state (this will be buffered only) + fixture.insert_fork(&mut state, 0, None)?; + + // Retrieve proofs for fork request root + let fork_request = fixture.fork(0, 0); + let proofs = state.get_proofs(&fork_request.metadata.request_root); + + assert!(proofs.is_some(), "should retrieve proofs from buffer"); + assert_eq!( + proofs.unwrap().len(), + 1, + "should retrieve all proofs from buffer" + ); + + Ok(()) + } + + #[test] + fn test_get_proofs_empty_list() { + let fixture = TestStateFixtureBuilder::simple_chain().build(); + let mut state = State::new(); + + // Insert a request into the buffer with no proofs + let request = fixture.canonical(0); + state.buffer_request(request.metadata.clone()); + + // Retrieve proofs for the request root + let proofs = state.get_proofs(&request.metadata.request_root); + + // The request exists in the buffer but has no proofs, so it should return None + assert!( + proofs.is_none(), + "should return None for known request with no proofs" + ); + } + + #[test] + fn test_tree_state_consistency_after_promotion() -> anyhow::Result<()> { + let fixture = TestStateFixtureBuilder::simple_chain().build(); + let mut state = State::new(); + + // Bootstrap and insert canonical chain + fixture.bootstrap_canonical(&mut state).unwrap(); + + // Extract block hashes and request roots for all blocks in the canonical chain + let genesis_hash = fixture.canonical_block_hash(0); + let block1_hash = fixture.canonical_block_hash(1); + let block2_hash = fixture.canonical_block_hash(2); + + let genesis_root = fixture.canonical_request_root(0); + let block1_root = fixture.canonical_request_root(1); + let block2_root = fixture.canonical_request_root(2); + + // Verify all tree mappings are consistent + + // proofs_by_block_hash + assert_eq!( + state.tree.proofs_by_block_hash.len(), + 3, + "tree should contain exactly 3 blocks" + ); + + // request_root_to_block_hash + assert_eq!( + state.tree.request_root_to_block_hash.len(), + 3, + "request_root_to_block_hash should have 3 entries" + ); + assert_eq!( + state + .tree + .request_root_to_block_hash + .get(&genesis_root) + .copied(), + Some(genesis_hash), + "genesis root should map to genesis hash" + ); + assert_eq!( + state + .tree + .request_root_to_block_hash + .get(&block1_root) + .copied(), + Some(block1_hash), + "block1 root should map to block1 hash" + ); + assert_eq!( + state + .tree + .request_root_to_block_hash + .get(&block2_root) + .copied(), + Some(block2_hash), + "block2 root should map to block2 hash" + ); + + // parent_to_children + let genesis_parent = test_exec_hash(0xff); + let genesis_parent_children = state + .tree + .parent_to_children + .get(&genesis_parent) + .expect("genesis parent should have children"); + assert!( + genesis_parent_children.contains(&genesis_hash), + "genesis parent should reference genesis" + ); + + let genesis_children = state + .tree + .parent_to_children + .get(&genesis_hash) + .expect("genesis should have children"); + assert!( + genesis_children.contains(&block1_hash), + "genesis should reference block1" + ); + + let block1_children = state + .tree + .parent_to_children + .get(&block1_hash) + .expect("block1 should have children"); + assert!( + block1_children.contains(&block2_hash), + "block1 should reference block2" + ); + + // block_number_to_block_hash + assert!( + state + .tree + .block_number_to_block_hash + .get(&0) + .unwrap() + .contains(&genesis_hash), + "genesis should be at height 0" + ); + assert!( + state + .tree + .block_number_to_block_hash + .get(&1) + .unwrap() + .contains(&block1_hash), + "block1 should be at height 1" + ); + assert!( + state + .tree + .block_number_to_block_hash + .get(&2) + .unwrap() + .contains(&block2_hash), + "block2 should be at height 2" + ); + + Ok(()) + } +} diff --git a/beacon_node/execution_layer/src/eip8025/tests.rs b/beacon_node/execution_layer/src/eip8025/tests.rs new file mode 100644 index 00000000000..657f6ce7517 --- /dev/null +++ b/beacon_node/execution_layer/src/eip8025/tests.rs @@ -0,0 +1,287 @@ +//! Unit tests for [`HttpProofEngine`] using [`MockProofNodeClient`]. + +use crate::eip8025::proof_engine::HttpProofEngine; +use crate::eip8025::proof_node_client::ProofNodeClient; +use crate::test_utils::{MockClientEvent, MockProofNodeClient, make_test_fulu_ssz}; +use bls::{FixedBytesExtended, SignatureBytes}; +use futures::StreamExt; +use tokio::time::{Duration, timeout}; +use types::execution::eip8025::{ + ExecutionProof, ProofAttributes, PublicInput, SignedExecutionProof, +}; +use types::{Hash256, MainnetEthSpec}; + +// ─── helpers ───────────────────────────────────────────────────────────────── + +fn make_proof(request_root: Hash256, proof_type: u8) -> SignedExecutionProof { + SignedExecutionProof { + message: ExecutionProof { + proof_data: Default::default(), + proof_type, + public_input: PublicInput { + new_payload_request_root: request_root, + }, + }, + validator_index: 0, + signature: SignatureBytes::empty(), + } +} + +/// Receive the next [`MockClientEvent`] within 2 seconds. +async fn next_event(rx: &mut tokio::sync::broadcast::Receiver) -> MockClientEvent { + timeout(Duration::from_secs(2), rx.recv()) + .await + .expect("timed out waiting for MockClientEvent") + .expect("channel closed") +} + +// ─── MockProofNodeClient tests ──────────────────────────────────────────────── + +/// `request_proofs` decodes SSZ, records the body, and emits `ProofRequested`. +#[tokio::test] +async fn mock_client_request_proofs_emits_event() { + let mock = MockProofNodeClient::::new(0); + let mut rx = mock.subscribe_client_events(); + + let (body, expected_root) = make_test_fulu_ssz::(Hash256::repeat_byte(0xAA)); + let attrs = ProofAttributes { + proof_types: vec![1, 2], + }; + + let root = mock + .request_proofs(body.clone(), attrs.clone()) + .await + .expect("request_proofs should succeed"); + + assert_eq!(root, expected_root); + assert_eq!(mock.request_count(), 1); + + let event = next_event(&mut rx).await; + assert!(matches!( + event, + MockClientEvent::ProofRequested { ssz_body, proof_attributes, root: r } + if r == root && ssz_body == body && proof_attributes == attrs + )); +} + +/// `verify_proof` emits `ProofVerified`. +#[tokio::test] +async fn mock_client_verify_proof_emits_event() { + let mock = MockProofNodeClient::::new(0); + let mut rx = mock.subscribe_client_events(); + + let root = Hash256::repeat_byte(0xBB); + let _ = mock.verify_proof(root, 1, &[]).await.unwrap(); + + let event = next_event(&mut rx).await; + assert!(matches!( + event, + MockClientEvent::ProofVerified { root: r, proof_type: 1 } if r == root + )); +} + +/// `get_proof` emits `ProofFetched`. +#[tokio::test] +async fn mock_client_get_proof_emits_event() { + let mock = MockProofNodeClient::::new(0); + let mut rx = mock.subscribe_client_events(); + + let root = Hash256::repeat_byte(0xCC); + let _ = mock.get_proof(root, 2).await.unwrap(); + + let event = next_event(&mut rx).await; + assert!(matches!( + event, + MockClientEvent::ProofFetched { root: r, proof_type: 2 } if r == root + )); +} + +/// `request_proofs` broadcasts a `ProofComplete` SSE event for each proof type. +#[tokio::test] +async fn mock_client_request_proofs_broadcasts_sse_events() { + let mock = MockProofNodeClient::::new(0); + let mut sse = mock.subscribe_proof_events(None); + + let attrs = ProofAttributes { + proof_types: vec![0, 1], + }; + let (body, expected_root) = make_test_fulu_ssz::(Hash256::repeat_byte(0x42)); + let root = mock + .request_proofs(body, attrs) + .await + .expect("request_proofs should succeed"); + + assert_eq!(root, expected_root); + + for expected_type in [0u8, 1u8] { + let event = timeout(Duration::from_secs(2), sse.next()) + .await + .expect("timed out waiting for SSE event") + .expect("stream ended") + .expect("stream error"); + assert_eq!(event.new_payload_request_root(), root); + assert_eq!(event.proof_type(), expected_type); + } +} + +/// Multiple subscribers each receive every event independently. +#[tokio::test] +async fn mock_client_multiple_subscribers_each_get_events() { + let mock = MockProofNodeClient::::new(0); + let mut rx1 = mock.subscribe_client_events(); + let mut rx2 = mock.subscribe_client_events(); + + let (body, _) = make_test_fulu_ssz::(Hash256::repeat_byte(0x01)); + let _ = mock + .request_proofs( + body, + ProofAttributes { + proof_types: vec![], + }, + ) + .await + .unwrap(); + + assert!(matches!( + next_event(&mut rx1).await, + MockClientEvent::ProofRequested { .. } + )); + assert!(matches!( + next_event(&mut rx2).await, + MockClientEvent::ProofRequested { .. } + )); +} + +/// Different SSZ bodies produce different roots (computed via tree-hash). +#[tokio::test] +async fn mock_client_computes_distinct_roots_from_ssz() { + let mock = MockProofNodeClient::::new(0); + let attrs = ProofAttributes { + proof_types: vec![], + }; + + let (body1, expected1) = make_test_fulu_ssz::(Hash256::repeat_byte(0x01)); + let (body2, expected2) = make_test_fulu_ssz::(Hash256::repeat_byte(0x02)); + let (body3, expected3) = make_test_fulu_ssz::(Hash256::repeat_byte(0x03)); + + let root1 = mock.request_proofs(body1, attrs.clone()).await.unwrap(); + let root2 = mock.request_proofs(body2, attrs.clone()).await.unwrap(); + let root3 = mock.request_proofs(body3, attrs).await.unwrap(); + + assert_eq!(root1, expected1); + assert_eq!(root2, expected2); + assert_eq!(root3, expected3); + assert_ne!(root1, root2); + assert_ne!(root2, root3); + assert_eq!(mock.request_count(), 3); +} + +// ─── HttpProofEngine tests ──────────────────────────────────────────────────── + +/// `verify_execution_proof` returns `Syncing` for an unknown root and does NOT +/// call `verify_proof` on the underlying client. +#[tokio::test] +async fn engine_verify_proof_unknown_root_returns_syncing() { + let mock = MockProofNodeClient::::new(0); + let mut rx = mock.subscribe_client_events(); + let engine = HttpProofEngine::with_proof_node(mock); + + let proof = make_proof(Hash256::repeat_byte(0xAB), 0); + let status = engine + .verify_execution_proof(&proof) + .await + .expect("verify should not error"); + + assert!( + status.is_syncing(), + "expected Syncing for unknown root, got {status:?}" + ); + + // verify_proof on the client must not be called for unknown roots. + assert!( + timeout(Duration::from_millis(50), rx.recv()).await.is_err(), + "verify_proof should not be called for an unknown root" + ); +} + +/// `get_proof` delegates to the underlying client and emits `ProofFetched`. +#[tokio::test] +async fn engine_get_proof_delegates_to_client() { + let mock = MockProofNodeClient::::new(0); + let mut rx = mock.subscribe_client_events(); + let engine = HttpProofEngine::with_proof_node(mock); + + let root = Hash256::repeat_byte(0xDE); + let bytes = engine + .get_proof(root, 3) + .await + .expect("get_proof should succeed"); + + assert_eq!(bytes.as_ref(), &[0xDE, 0xAD, 0xBE, 0xEF]); + + let event = next_event(&mut rx).await; + assert!(matches!( + event, + MockClientEvent::ProofFetched { root: r, proof_type: 3 } if r == root + )); +} + +/// A proof received before the matching payload is buffered (`Syncing`), and +/// the buffer grows while no `ProofVerified` event is emitted. +#[tokio::test] +async fn engine_unknown_root_proof_is_buffered() { + let mock = MockProofNodeClient::::new(0); + let mut rx = mock.subscribe_client_events(); + let engine = HttpProofEngine::with_proof_node(mock); + + let root = Hash256::from_low_u64_be(42); + let proof = make_proof(root, 0); + + // First call: root unknown → Syncing, proof buffered. + let status = engine.verify_execution_proof(&proof).await.unwrap(); + assert!(status.is_syncing(), "expected Syncing, got {status:?}"); + + // The proof must not reach the engine state (tree/buffer promotion requires new_payload). + assert_eq!(engine.get_proofs_by_root(&root).len(), 0); + + // No ProofVerified event should have been emitted. + assert!( + timeout(Duration::from_millis(50), rx.recv()).await.is_err(), + "verify_proof should not be called for an unknown root" + ); +} + +/// `subscribe_proof_events` with a root filter only forwards matching events. +#[tokio::test] +async fn engine_subscribe_proof_events_filters_by_root() { + let mock = MockProofNodeClient::::new(0); + let attrs = ProofAttributes { + proof_types: vec![0], + }; + + let (body1, root1) = make_test_fulu_ssz::(Hash256::from_low_u64_be(1)); + let (body2, _root2) = make_test_fulu_ssz::(Hash256::from_low_u64_be(2)); + + // Subscribe before making requests. + let mut filtered = mock.subscribe_proof_events(Some(root1)); + + // root1 matches the filter; root2 should be silently dropped. + let _ = mock.request_proofs(body1, attrs.clone()).await.unwrap(); + let _ = mock.request_proofs(body2, attrs).await.unwrap(); + + // Only the event for root1 should arrive on the filtered stream. + let event = timeout(Duration::from_secs(2), filtered.next()) + .await + .expect("timed out") + .expect("stream ended") + .expect("stream error"); + assert_eq!(event.new_payload_request_root(), root1); + + // No second event for root2 should arrive within a short window. + assert!( + timeout(Duration::from_millis(100), filtered.next()) + .await + .is_err(), + "filtered stream should not forward events for other roots" + ); +} diff --git a/beacon_node/execution_layer/src/eip8025/types.rs b/beacon_node/execution_layer/src/eip8025/types.rs new file mode 100644 index 00000000000..a5df6d75552 --- /dev/null +++ b/beacon_node/execution_layer/src/eip8025/types.rs @@ -0,0 +1,281 @@ +//! API types for EIP-8025 proof engine communication. +//! +//! This module contains: +//! - [`ProofType`]: an independent string enum that mirrors the +//! proof node API's `ProofType` exactly. +//! - SSE event types broadcast by the proof engine. +//! +//! ## ProofType encoding +//! +//! EIP-8025 uses `u8` for `ProofType` in SSZ containers (consensus layer). +//! The proof node API uses kebab-case string identifiers +//! (`"reth-sp1"`, `"ethrex-risc0"`, etc.) in HTTP query params, URL paths, +//! and SSE event payloads. +//! +//! [`ProofType`] bridges this gap: the [`HttpProofNodeClient`] converts +//! between `u8` (internal) and string (wire) at the HTTP boundary. + +use super::errors::ProofEngineError; +use serde::{Deserialize, Deserializer, Serialize}; +use std::fmt; +use std::str::FromStr; +use types::Hash256; + +// ─── ProofType ───────────────────────────────────────────────────────────── + +/// Proof type identifiers. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(into = "String", try_from = "String")] +#[repr(u8)] +pub enum ProofType { + EthrexRisc0 = 0, + EthrexSP1 = 1, + EthrexZisk = 2, + RethOpenVM = 3, + RethRisc0 = 4, + RethSP1 = 5, + RethZisk = 6, +} + +impl ProofType { + /// Canonical string representation, matching exactly. + pub fn as_str(&self) -> &'static str { + match self { + Self::EthrexRisc0 => "ethrex-risc0", + Self::EthrexSP1 => "ethrex-sp1", + Self::EthrexZisk => "ethrex-zisk", + Self::RethOpenVM => "reth-openvm", + Self::RethRisc0 => "reth-risc0", + Self::RethSP1 => "reth-sp1", + Self::RethZisk => "reth-zisk", + } + } + + /// Convert from EIP-8025 `u8` proof type to a string identifier. + /// + /// The mapping follows the order defined in the `ProofType` enum. + pub fn from_u8(value: u8) -> Result { + match value { + 0 => Ok(Self::EthrexRisc0), + 1 => Ok(Self::EthrexSP1), + 2 => Ok(Self::EthrexZisk), + 3 => Ok(Self::RethOpenVM), + 4 => Ok(Self::RethRisc0), + 5 => Ok(Self::RethSP1), + 6 => Ok(Self::RethZisk), + _ => Err(ProofEngineError::InvalidProofType(format!( + "no mapping for proof type {value}" + ))), + } + } + + /// Convert back to EIP-8025 `u8` proof type. + pub fn to_u8(self) -> u8 { + self as u8 + } + + /// All known proof type variants. + pub fn all() -> &'static [ProofType] { + &[ + Self::EthrexRisc0, + Self::EthrexSP1, + Self::EthrexZisk, + Self::RethOpenVM, + Self::RethRisc0, + Self::RethSP1, + Self::RethZisk, + ] + } +} + +impl FromStr for ProofType { + type Err = ProofEngineError; + + fn from_str(s: &str) -> Result { + match s { + "ethrex-risc0" => Ok(Self::EthrexRisc0), + "ethrex-sp1" => Ok(Self::EthrexSP1), + "ethrex-zisk" => Ok(Self::EthrexZisk), + "reth-openvm" => Ok(Self::RethOpenVM), + "reth-risc0" => Ok(Self::RethRisc0), + "reth-sp1" => Ok(Self::RethSP1), + "reth-zisk" => Ok(Self::RethZisk), + _ => Err(ProofEngineError::InvalidProofType(format!( + "unknown proof type: {s}" + ))), + } + } +} + +impl fmt::Display for ProofType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +impl From for String { + fn from(pt: ProofType) -> Self { + pt.as_str().to_string() + } +} + +impl TryFrom for ProofType { + type Error = ProofEngineError; + + fn try_from(s: String) -> Result { + s.parse() + } +} + +// ─── ProofTypes ───────────────────────────────────────────────────────────── + +/// ProofTypes defines the support proof types. +/// +/// This type allows us to implement `Default` with reasonable defaults. +/// +/// The default is `[EthrexRisc0, EthrexSP1, EthrexZisk, RethOpenVM]` (wire +/// values 0–3), matching the `--proof-types` CLI flag default. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ProofTypes(pub Vec); + +impl Default for ProofTypes { + fn default() -> Self { + Self(vec![ + ProofType::EthrexRisc0, + ProofType::EthrexSP1, + ProofType::EthrexZisk, + ProofType::RethOpenVM, + ]) + } +} + +impl std::ops::Deref for ProofTypes { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From> for ProofTypes { + fn from(v: Vec) -> Self { + Self(v) + } +} + +// ─── SSE Event Types ──────────────────────────────────────────────────────── + +/// SSE event types broadcast by the proof engine. +#[derive(Debug, Clone, PartialEq)] +pub enum ProofEvent { + /// A proof completed successfully. + ProofComplete(ProofComplete), + /// A proof failed. + ProofFailure(ProofFailure), + /// Witness fetch timed out. + WitnessTimeout(ProofEventInfo), + /// Proof generation timed out. + ProofTimeout(ProofEventInfo), +} + +/// Payload for a successful proof event. +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct ProofComplete { + pub new_payload_request_root: Hash256, + #[serde(deserialize_with = "deserialize_proof_type")] + pub proof_type: u8, +} + +/// Payload for a failed proof event. +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct ProofFailure { + pub new_payload_request_root: Hash256, + #[serde(deserialize_with = "deserialize_proof_type")] + pub proof_type: u8, + pub error: String, +} + +/// Common info for timeout events. +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct ProofEventInfo { + pub new_payload_request_root: Hash256, + #[serde(deserialize_with = "deserialize_proof_type")] + pub proof_type: u8, +} + +/// Deserialize `proof_type` from either a string (`"reth-sp1"`) or a +/// numeric value (`0`). This allows Lighthouse to consume SSE events from both +/// servers (string format) and test mocks (numeric format). +fn deserialize_proof_type<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum ProofTypeValue { + Number(u8), + String(String), + } + + match ProofTypeValue::deserialize(deserializer)? { + ProofTypeValue::Number(n) => Ok(n), + ProofTypeValue::String(s) => { + // Try parsing as string identifier first. + if let Ok(pt) = s.parse::() { + return Ok(pt.to_u8()); + } + // Fall back to parsing as numeric string (e.g. "0"). + s.parse::().map_err(serde::de::Error::custom) + } + } +} + +/// SSE event name + JSON data pair used to construct a [`ProofEvent`]. +pub struct SseEventParts<'a>(pub &'a str, pub &'a str); + +impl<'a> TryFrom> for ProofEvent { + type Error = ProofEngineError; + + fn try_from(parts: SseEventParts<'a>) -> Result { + let SseEventParts(name, data) = parts; + match name { + "proof_complete" => Ok(Self::ProofComplete( + serde_json::from_str(data).map_err(ProofEngineError::SerdeError)?, + )), + "proof_failure" => Ok(Self::ProofFailure( + serde_json::from_str(data).map_err(ProofEngineError::SerdeError)?, + )), + "witness_timeout" => Ok(Self::WitnessTimeout( + serde_json::from_str(data).map_err(ProofEngineError::SerdeError)?, + )), + "proof_timeout" => Ok(Self::ProofTimeout( + serde_json::from_str(data).map_err(ProofEngineError::SerdeError)?, + )), + other => Err(ProofEngineError::SseError(format!( + "unknown SSE event type: {other}" + ))), + } + } +} + +impl ProofEvent { + /// Returns the `new_payload_request_root` from the event. + pub fn new_payload_request_root(&self) -> Hash256 { + match self { + Self::ProofComplete(inner) => inner.new_payload_request_root, + Self::ProofFailure(inner) => inner.new_payload_request_root, + Self::WitnessTimeout(inner) => inner.new_payload_request_root, + Self::ProofTimeout(inner) => inner.new_payload_request_root, + } + } + + /// Returns the proof type from the event. + pub fn proof_type(&self) -> u8 { + match self { + Self::ProofComplete(inner) => inner.proof_type, + Self::ProofFailure(inner) => inner.proof_type, + Self::WitnessTimeout(inner) => inner.proof_type, + Self::ProofTimeout(inner) => inner.proof_type, + } + } +} diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index 32090bccfc9..2c2fe8f7b2e 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -26,7 +26,7 @@ pub use types::{ use types::{ ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionRequests, - KzgProofs, + KzgProofs, Slot, }; use types::{GRAFFITI_BYTES_LEN, Graffiti}; @@ -61,7 +61,7 @@ pub enum Error { ExecutionHeadBlockNotFound, ParentHashEqualsBlockHash(ExecutionBlockHash), PayloadIdUnavailable, - SszError(ssz_types::Error), + Ssz(ssz_types::Error), DeserializeWithdrawals(ssz_types::Error), DeserializeDepositRequests(ssz_types::Error), DeserializeWithdrawalRequests(ssz_types::Error), @@ -106,7 +106,7 @@ impl From for Error { impl From for Error { fn from(e: ssz_types::Error) -> Self { - Error::SszError(e) + Error::Ssz(e) } } @@ -120,6 +120,12 @@ pub enum PayloadStatusV1Status { InvalidBlockHash, } +impl PayloadStatusV1Status { + pub fn is_syncing(&self) -> bool { + matches!(self, PayloadStatusV1Status::Syncing) + } +} + #[derive(Clone, Debug, PartialEq)] pub struct PayloadStatusV1 { pub status: PayloadStatusV1Status, @@ -250,6 +256,23 @@ impl From for SsePayloadAttributes { } } +/// Info about a buffered proof request that is missing sufficient proofs. +/// +/// The `root` field is dual-purpose: +/// - At the execution-layer level it holds the **new-payload request root**. +/// - After `BeaconChain::missing_execution_proofs()` performs the store LRU lookup it is +/// replaced with the corresponding **beacon block root** so the sync layer can issue +/// `ExecutionProofsByRoot` RPC requests directly. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct MissingProofInfo { + /// New-payload request root (EL) or beacon block root (sync layer). + pub root: Hash256, + /// Proof types already received for this request root (to avoid redundant requests). + pub existing_proof_types: Vec, + /// Beacon slot of the block whose proofs are missing. + pub slot: Slot, +} + #[derive(Clone, Debug, PartialEq)] pub struct ForkchoiceUpdatedResponse { pub payload_status: PayloadStatusV1, diff --git a/beacon_node/execution_layer/src/engine_api/new_payload_request.rs b/beacon_node/execution_layer/src/engine_api/new_payload_request.rs index ba94296b859..ff3d3a7260e 100644 --- a/beacon_node/execution_layer/src/engine_api/new_payload_request.rs +++ b/beacon_node/execution_layer/src/engine_api/new_payload_request.rs @@ -1,8 +1,11 @@ use crate::{Error, block_hash::calculate_execution_block_hash, metrics}; use crate::versioned_hashes::verify_versioned_hashes; +use ssz_derive::Encode as SszEncode; +use ssz_types::VariableList; use state_processing::per_block_processing::deneb::kzg_commitment_to_versioned_hash; use superstruct::superstruct; +use tree_hash_derive::TreeHash; use types::{ BeaconBlockRef, BeaconStateError, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadRef, Hash256, VersionedHash, @@ -14,7 +17,7 @@ use types::{ #[superstruct( variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), - variant_attributes(derive(Clone, Debug, PartialEq),), + variant_attributes(derive(Clone, Debug, PartialEq, SszEncode, TreeHash),), map_into(ExecutionPayload), map_ref_into(ExecutionPayloadRef), cast_error( @@ -26,7 +29,9 @@ use types::{ expr = "BeaconStateError::IncorrectStateVariant" ) )] -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, SszEncode, TreeHash)] +#[tree_hash(enum_behaviour = "transparent")] +#[ssz(enum_behaviour = "transparent")] pub struct NewPayloadRequest<'block, E: EthSpec> { #[superstruct( only(Bellatrix), @@ -44,7 +49,7 @@ pub struct NewPayloadRequest<'block, E: EthSpec> { #[superstruct(only(Gloas), partial_getter(rename = "execution_payload_gloas"))] pub execution_payload: &'block ExecutionPayloadGloas, #[superstruct(only(Deneb, Electra, Fulu, Gloas))] - pub versioned_hashes: Vec, + pub versioned_hashes: VariableList, #[superstruct(only(Deneb, Electra, Fulu, Gloas))] pub parent_beacon_block_root: Hash256, #[superstruct(only(Electra, Fulu, Gloas))] @@ -196,7 +201,8 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> .blob_kzg_commitments .iter() .map(kzg_commitment_to_versioned_hash) - .collect(), + .collect::>() + .try_into()?, parent_beacon_block_root: block_ref.parent_root, })), BeaconBlockRef::Electra(block_ref) => Ok(Self::Electra(NewPayloadRequestElectra { @@ -206,7 +212,8 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> .blob_kzg_commitments .iter() .map(kzg_commitment_to_versioned_hash) - .collect(), + .collect::>() + .try_into()?, parent_beacon_block_root: block_ref.parent_root, execution_requests: &block_ref.body.execution_requests, })), @@ -217,7 +224,8 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> .blob_kzg_commitments .iter() .map(kzg_commitment_to_versioned_hash) - .collect(), + .collect::>() + .try_into()?, parent_beacon_block_root: block_ref.parent_root, execution_requests: &block_ref.body.execution_requests, })), diff --git a/beacon_node/execution_layer/src/engines.rs b/beacon_node/execution_layer/src/engines.rs index 3e6f78abbe9..a65cbd00d29 100644 --- a/beacon_node/execution_layer/src/engines.rs +++ b/beacon_node/execution_layer/src/engines.rs @@ -6,6 +6,7 @@ use crate::engine_api::{ }; use crate::{ClientVersionV1, HttpJsonRpc}; use lru::LruCache; +use ssz_derive::{Decode, Encode}; use std::future::Future; use std::num::NonZeroUsize; use std::sync::Arc; @@ -100,13 +101,24 @@ impl State { } } -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Debug, Encode, Decode)] pub struct ForkchoiceState { pub head_block_hash: ExecutionBlockHash, pub safe_block_hash: ExecutionBlockHash, pub finalized_block_hash: ExecutionBlockHash, } +impl ForkchoiceState { + /// Creates a `ForkchoiceState` with all block hashes set to the genesis hash. + pub fn new_genesis(genesis_hash: ExecutionBlockHash) -> Self { + Self { + head_block_hash: genesis_hash, + safe_block_hash: genesis_hash, + finalized_block_hash: genesis_hash, + } + } +} + #[derive(Hash, PartialEq, std::cmp::Eq)] struct PayloadIdCacheKey { pub head_block_hash: ExecutionBlockHash, diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 33b83aab09f..43c1482af4f 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -60,6 +60,7 @@ use types::{ }; mod block_hash; +pub mod eip8025; mod engine_api; pub mod engines; mod keccak; @@ -69,6 +70,20 @@ mod payload_status; pub mod test_utils; pub mod versioned_hashes; +/// Combine two optional results, preferring `Ok` values over `Err` values. +/// +/// If both are `Some`, the first `Ok` is returned. If only one is `Ok`, that one wins. +/// If both are `Err`, the first error is returned. +fn prefer_ok(a: Option>, b: Option>) -> Option> { + match (a, b) { + (Some(Ok(val)), _) => Some(Ok(val)), + (_, Some(Ok(val))) => Some(Ok(val)), + (some @ Some(_), _) => some, + (_, some @ Some(_)) => some, + (None, None) => None, + } +} + /// Indicates the default jwt authenticated execution endpoint. pub const DEFAULT_EXECUTION_ENDPOINT: &str = "http://localhost:8551/"; @@ -142,6 +157,10 @@ impl TryFrom> for ProvenancedPayload for Error { } } +impl From for Error { + fn from(e: eip8025::errors::ProofEngineError) -> Self { + Error::ProofEngineError(e) + } +} + pub enum BlockProposalContentsType { Full(BlockProposalContents>), Blinded(BlockProposalContents>), @@ -426,7 +451,8 @@ pub enum SubmitBlindedBlockResponse { type PayloadContentsRefTuple<'a, E> = (ExecutionPayloadRef<'a, E>, Option<&'a BlobsBundle>); struct Inner { - engine: Arc, + /// Traditional execution engine (optional). + engine: Option>, builder: ArcSwapOption, execution_engine_forkchoice_lock: Mutex<()>, suggested_fee_recipient: Option
, @@ -440,12 +466,19 @@ struct Inner { /// This is used *only* in the informational sync status endpoint, so that a VC using this /// node can prefer another node with a healthier EL. last_new_payload_errored: RwLock, + /// EIP-8025: Optional execution proof engine. + proof_engine: Option>, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Config { - /// Endpoint url for EL nodes that are running the engine api. + /// Endpoint url for EL nodes that are running the engine api (optional). pub execution_endpoint: Option, + /// Endpoint url for EIP-8025 proof engine (optional). + pub proof_engine_endpoint: Option, + /// Proof types supported by this client. + #[serde(default)] + pub proof_types: eip8025::types::ProofTypes, /// Endpoint urls for services providing the builder api. pub builder_url: Option, /// The timeout value used when making a request to fetch a block header @@ -480,7 +513,9 @@ impl ExecutionLayer { /// Instantiate `Self` with an Execution engine specified in `Config`, using JSON-RPC via HTTP. pub fn from_config(config: Config, executor: TaskExecutor) -> Result { let Config { - execution_endpoint: url, + execution_endpoint, + proof_engine_endpoint, + proof_types: _, // consumed at the network layer via NetworkConfig builder_url, builder_user_agent, builder_header_timeout, @@ -493,50 +528,81 @@ impl ExecutionLayer { execution_timeout_multiplier, } = config; - let execution_url = url.ok_or(Error::NoEngine)?; - - // Use the default jwt secret path if not provided via cli. - let secret_file = secret_file.unwrap_or_else(|| default_datadir.join(DEFAULT_JWT_FILE)); - - let jwt_key = if secret_file.exists() { - // Read secret from file if it already exists - std::fs::read_to_string(&secret_file) - .map_err(|e| format!("Failed to read JWT secret file. Error: {:?}", e)) - .and_then(|ref s| { - let secret = JwtKey::from_slice( - &hex::decode(strip_prefix(s.trim_end())) - .map_err(|e| format!("Invalid hex string: {:?}", e))?, - )?; - Ok(secret) - }) - .map_err(Error::InvalidJWTSecret) - } else { - // Create a new file and write a randomly generated secret to it if file does not exist - warn!(path = %secret_file.display(),"No JWT found on disk. Generating"); - std::fs::File::options() - .write(true) - .create_new(true) - .open(&secret_file) - .map_err(|e| format!("Failed to open JWT secret file. Error: {:?}", e)) - .and_then(|mut f| { - let secret = auth::JwtKey::random(); - f.write_all(secret.hex_string().as_bytes()) - .map_err(|e| format!("Failed to write to JWT secret file: {:?}", e))?; - Ok(secret) - }) - .map_err(Error::InvalidJWTSecret) - }?; + // Validation: at least one endpoint must be provided + if execution_endpoint.is_none() && proof_engine_endpoint.is_none() { + return Err(Error::NoExecutionEndpoint); + } + + // Create Engine if execution_endpoint is provided + let engine: Option> = if let Some(execution_url) = execution_endpoint { + // Use the default jwt secret path if not provided via cli. + let secret_file = secret_file.unwrap_or_else(|| default_datadir.join(DEFAULT_JWT_FILE)); + + let jwt_key = if secret_file.exists() { + // Read secret from file if it already exists + std::fs::read_to_string(&secret_file) + .map_err(|e| format!("Failed to read JWT secret file. Error: {:?}", e)) + .and_then(|ref s| { + let secret = JwtKey::from_slice( + &hex::decode(strip_prefix(s.trim_end())) + .map_err(|e| format!("Invalid hex string: {:?}", e))?, + )?; + Ok(secret) + }) + .map_err(Error::InvalidJWTSecret) + } else { + // Create a new file and write a randomly generated secret to it if file does not exist + warn!(path = %secret_file.display(),"No JWT found on disk. Generating"); + std::fs::File::options() + .write(true) + .create_new(true) + .open(&secret_file) + .map_err(|e| format!("Failed to open JWT secret file. Error: {:?}", e)) + .and_then(|mut f| { + let secret = auth::JwtKey::random(); + f.write_all(secret.hex_string().as_bytes()) + .map_err(|e| format!("Failed to write to JWT secret file: {:?}", e))?; + Ok(secret) + }) + .map_err(Error::InvalidJWTSecret) + }?; - let engine: Engine = { let auth = Auth::new(jwt_key, jwt_id, jwt_version); debug!(endpoint = %execution_url, jwt_path = ?secret_file.as_path(),"Loaded execution endpoint"); let api = HttpJsonRpc::new_with_auth(execution_url, auth, execution_timeout_multiplier) .map_err(Error::ApiError)?; - Engine::new(api, executor.clone()) + + Some(Arc::new(Engine::new(api, executor.clone()))) + } else { + None }; + // Create ProofEngine if proof_engine_endpoint is provided. + // Mock URLs of the form "http://mock/{n}/" look up a pre-registered MockProofNodeClient + // from the global registry instead of opening a real HTTP connection — useful for tests + // and simulation. + let proof_engine: Option> = + if let Some(proof_url) = proof_engine_endpoint { + if let Some(idx) = test_utils::parse_mock_index(proof_url.expose_full().as_str()) { + let mock = test_utils::get_mock_proof_engine::(idx).unwrap_or_else(|| { + debug!( + idx, + "No pre-registered mock; creating MockProofNodeClient on the fly" + ); + test_utils::register_mock_proof_engine::(idx, 0) + }); + debug!(idx, "Instantiating mock proof engine from registry"); + Some(Arc::new(eip8025::HttpProofEngine::with_proof_node(mock))) + } else { + debug!(endpoint = %proof_url, "Loaded proof engine endpoint"); + Some(Arc::new(eip8025::HttpProofEngine::new(proof_url, None))) + } + } else { + None + }; + let inner = Inner { - engine: Arc::new(engine), + engine, builder: ArcSwapOption::empty(), execution_engine_forkchoice_lock: <_>::default(), suggested_fee_recipient, @@ -546,6 +612,7 @@ impl ExecutionLayer { executor, payload_cache: PayloadCache::default(), last_new_payload_errored: RwLock::new(false), + proof_engine, }; let el = Self { @@ -564,8 +631,21 @@ impl ExecutionLayer { Ok(el) } - fn engine(&self) -> &Arc { - &self.inner.engine + pub fn engine(&self) -> Option<&Arc> { + self.inner.engine.as_ref() + } + + pub fn proof_engine(&self) -> Option> { + self.inner.proof_engine.clone() + } + + /// Subscribe to method-invocation events emitted by a mock proof node client. + /// + /// Returns `None` if no proof engine is configured or the client is a production HTTP client. + pub fn subscribe_proof_node_client_events( + &self, + ) -> Option> { + self.inner.proof_engine.as_ref()?.subscribe_client_events() } pub fn builder(&self) -> Option> { @@ -626,14 +706,17 @@ impl ExecutionLayer { /// Get the current difficulty of the PoW chain. pub async fn get_current_difficulty(&self) -> Result, ApiError> { - let block = self - .engine() - .api - .get_block_by_number(BlockByNumberQuery::Tag(LATEST_TAG)) - .await? - .ok_or(ApiError::ExecutionHeadBlockNotFound)?; - Ok(block.total_difficulty) + if let Some(engine) = self.engine() { + engine + .api + .get_block_by_number(BlockByNumberQuery::Tag(LATEST_TAG)) + .await + .map(|opt| opt.and_then(|block| block.total_difficulty)) + } else { + Ok(None) + } } + /// Note: this function returns a mutex guard, be careful to avoid deadlocks. async fn execution_blocks( &self, @@ -644,8 +727,13 @@ impl ExecutionLayer { /// Gives access to a channel containing if the last engine state is online or not. /// /// This can be called several times. - pub async fn get_responsiveness_watch(&self) -> WatchStream { - self.engine().watch_state().await + /// Returns None if no engine is configured. + pub async fn get_responsiveness_watch(&self) -> Option> { + if let Some(engine) = self.engine() { + Some(engine.watch_state().await) + } else { + None + } } /// Note: this function returns a mutex guard, be careful to avoid deadlocks. @@ -674,6 +762,11 @@ impl ExecutionLayer { /// Spawns a routine which attempts to keep the execution engine online. pub fn spawn_watchdog_routine(&self, slot_clock: S) { + // If there is no engine, there is no need to spawn the watchdog routine. + if self.engine().is_none() { + return; + } + let watchdog = |el: ExecutionLayer| async move { // Run one task immediately. el.watchdog_task().await; @@ -693,7 +786,9 @@ impl ExecutionLayer { /// Performs a single execution of the watchdog routine. pub async fn watchdog_task(&self) { - self.engine().upcheck().await; + if let Some(engine) = self.engine() { + engine.upcheck().await; + } } /// Spawns a routine which cleans the cached proposer data periodically. @@ -736,7 +831,11 @@ impl ExecutionLayer { /// Returns `true` if the execution engine is synced and reachable. pub async fn is_synced(&self) -> bool { - self.engine().is_synced().await + if let Some(engine) = self.engine() { + engine.is_synced().await + } else { + true + } } /// Execution nodes return a "SYNCED" response when they do not have any peers. @@ -747,12 +846,16 @@ impl ExecutionLayer { /// Returns the `Self::is_synced` response if unable to get latest block. pub async fn is_synced_for_notifier(&self, current_slot: Slot) -> bool { let synced = self.is_synced().await; - if synced - && let Ok(Some(block)) = self - .engine() + let block = if let Some(engine) = self.engine() { + engine .api .get_block_by_number(BlockByNumberQuery::Tag(LATEST_TAG)) .await + } else { + Ok(None) + }; + if synced + && let Ok(Some(block)) = block && block.block_number == 0 && current_slot > 0 { @@ -768,7 +871,12 @@ impl ExecutionLayer { /// be used to give an indication on the HTTP API that the node's execution layer is struggling, /// which can in turn be used by the VC. pub async fn is_offline_or_erroring(&self) -> bool { - self.engine().is_offline().await || *self.inner.last_new_payload_errored.read().await + let engine_offline = if let Some(engine) = self.engine() { + engine.is_offline().await + } else { + false + }; + engine_offline || *self.inner.last_new_payload_errored.read().await } /// Updates the proposer preparation data provided by validators @@ -1258,7 +1366,9 @@ impl ExecutionLayer { .. } = payload_parameters; - self.engine() + let engine = self.engine().ok_or(Error::NoEngine)?; + + engine .request(move |engine| async move { let payload_id = if let Some(id) = engine .get_payload_id(&parent_hash, payload_attributes) @@ -1376,10 +1486,30 @@ impl ExecutionLayer { let block_hash = new_payload_request.block_hash(); let parent_hash = new_payload_request.parent_hash(); - let result = self - .engine() - .request(|engine| engine.api.new_payload(new_payload_request)) - .await; + let engine_result = if let Some(engine) = self.engine() { + Some( + engine + .request(|engine| engine.api.new_payload(new_payload_request.clone())) + .await, + ) + } else { + None + }; + + let proof_engine_result = if let Some(proof_engine) = self.proof_engine() { + match proof_engine.new_payload(&new_payload_request).await { + Ok(status) => Some(Ok(status)), + Err(e) => { + debug!(error = ?e, "Proof engine new_payload error (non-fatal)"); + None + } + } + } else { + None + }; + + let result = prefer_ok(engine_result, proof_engine_result) + .expect("at least one of engine or proof engine must be present"); if let Ok(status) = &result { let status_str = <&'static str>::from(status.status); @@ -1405,7 +1535,9 @@ impl ExecutionLayer { /// Update engine sync status. pub async fn upcheck(&self) { - self.engine().upcheck().await; + if let Some(engine) = self.engine() { + engine.upcheck().await; + } } /// Register that the given `validator_index` is going to produce a block at `slot`. @@ -1512,18 +1644,36 @@ impl ExecutionLayer { finalized_block_hash, }; - self.engine() - .set_latest_forkchoice_state(forkchoice_state) - .await; + let engine_result = if let Some(engine) = self.engine() { + engine.set_latest_forkchoice_state(forkchoice_state).await; - let result = self - .engine() - .request(|engine| async move { + Some( engine - .notify_forkchoice_updated(forkchoice_state, payload_attributes) - .await - }) - .await; + .request(|engine| async move { + engine + .notify_forkchoice_updated(forkchoice_state, payload_attributes) + .await + }) + .await, + ) + } else { + None + }; + + let proof_engine_result = if let Some(proof_engine) = self.proof_engine() { + match proof_engine.forkchoice_updated(forkchoice_state) { + Ok(response) => Some(Ok(response)), + Err(e) => { + debug!(error = ?e, "Proof engine forkchoice_updated error (non-fatal)"); + None + } + } + } else { + None + }; + + let result = prefer_ok(engine_result, proof_engine_result) + .expect("at least one of engine or proof engine must be present"); if let Ok(status) = &result { metrics::inc_counter_vec( @@ -1553,10 +1703,31 @@ impl ExecutionLayer { &self, age_limit: Option, ) -> Result { - self.engine() - .request(|engine| engine.get_engine_capabilities(age_limit)) - .await - .map_err(Into::into) + if let Some(engine) = self.engine() { + Ok(engine + .request(|engine| engine.get_engine_capabilities(age_limit)) + .await?) + } else { + Ok(EngineCapabilities { + new_payload_v1: true, + new_payload_v2: true, + new_payload_v3: true, + new_payload_v4: true, + forkchoice_updated_v1: true, + forkchoice_updated_v2: true, + forkchoice_updated_v3: true, + get_payload_bodies_by_hash_v1: false, + get_payload_bodies_by_range_v1: false, + get_payload_v1: true, + get_payload_v2: true, + get_payload_v3: true, + get_payload_v4: true, + get_payload_v5: true, + get_client_version_v1: false, + get_blobs_v1: false, + get_blobs_v2: false, + }) + } } /// Returns the execution engine version resulting from a call to @@ -1572,14 +1743,16 @@ impl ExecutionLayer { &self, age_limit: Option, ) -> Result, Error> { - let versions = self - .engine() - .request(|engine| engine.get_engine_version(age_limit)) - .await - .map_err(Into::::into)?; - metrics::expose_execution_layer_info(&versions); - - Ok(versions) + if let Some(engine) = self.engine() { + let versions = engine + .request(|engine| engine.get_engine_version(age_limit)) + .await + .map_err(Into::::into)?; + metrics::expose_execution_layer_info(&versions); + Ok(versions) + } else { + Ok(vec![]) + } } /// Used during block production to determine if the merge has been triggered. @@ -1599,39 +1772,42 @@ impl ExecutionLayer { &[metrics::GET_TERMINAL_POW_BLOCK_HASH], ); - let hash_opt = self - .engine() - .request(|engine| async move { - let terminal_block_hash = spec.terminal_block_hash; - if terminal_block_hash != ExecutionBlockHash::zero() { - if self - .get_pow_block(engine, terminal_block_hash) - .await? - .is_some() - { - return Ok(Some(terminal_block_hash)); - } else { - return Ok(None); + let hash_opt = if let Some(engine) = self.engine() { + engine + .request(|engine| async move { + let terminal_block_hash = spec.terminal_block_hash; + if terminal_block_hash != ExecutionBlockHash::zero() { + if self + .get_pow_block(engine, terminal_block_hash) + .await? + .is_some() + { + return Ok(Some(terminal_block_hash)); + } else { + return Ok(None); + } } - } - let block = self.get_pow_block_at_total_difficulty(engine, spec).await?; - if let Some(pow_block) = block { - // If `terminal_block.timestamp == transition_block.timestamp`, - // we violate the invariant that a block's timestamp must be - // strictly greater than its parent's timestamp. - // The execution layer will reject a fcu call with such payload - // attributes leading to a missed block. - // Hence, we return `None` in such a case. - if pow_block.timestamp >= timestamp { - return Ok(None); + let block = self.get_pow_block_at_total_difficulty(engine, spec).await?; + if let Some(pow_block) = block { + // If `terminal_block.timestamp == transition_block.timestamp`, + // we violate the invariant that a block's timestamp must be + // strictly greater than its parent's timestamp. + // The execution layer will reject a fcu call with such payload + // attributes leading to a missed block. + // Hence, we return `None` in such a case. + if pow_block.timestamp >= timestamp { + return Ok(None); + } } - } - Ok(block.map(|b| b.block_hash)) - }) - .await - .map_err(Box::new) - .map_err(Error::EngineError)?; + Ok(block.map(|b| b.block_hash)) + }) + .await + .map_err(Box::new) + .map_err(Error::EngineError)? + } else { + None + }; if let Some(hash) = &hash_opt { info!( @@ -1728,21 +1904,25 @@ impl ExecutionLayer { &[metrics::IS_VALID_TERMINAL_POW_BLOCK_HASH], ); - self.engine() - .request(|engine| async move { - if let Some(pow_block) = self.get_pow_block(engine, block_hash).await? - && let Some(pow_parent) = - self.get_pow_block(engine, pow_block.parent_hash).await? - { - return Ok(Some( - self.is_valid_terminal_pow_block(pow_block, pow_parent, spec), - )); - } - Ok(None) - }) - .await - .map_err(Box::new) - .map_err(Error::EngineError) + if let Some(engine) = self.engine() { + engine + .request(|engine| async move { + if let Some(pow_block) = self.get_pow_block(engine, block_hash).await? + && let Some(pow_parent) = + self.get_pow_block(engine, pow_block.parent_hash).await? + { + return Ok(Some( + self.is_valid_terminal_pow_block(pow_block, pow_parent, spec), + )); + } + Ok(None) + }) + .await + .map_err(Box::new) + .map_err(Error::EngineError) + } else { + Ok(None) + } } /// This function should remain internal. @@ -1788,13 +1968,17 @@ impl ExecutionLayer { &self, hashes: Vec, ) -> Result>>, Error> { - self.engine() - .request(|engine: &Engine| async move { - engine.api.get_payload_bodies_by_hash_v1(hashes).await - }) - .await - .map_err(Box::new) - .map_err(Error::EngineError) + if let Some(engine) = self.engine() { + engine + .request(|engine: &Engine| async move { + engine.api.get_payload_bodies_by_hash_v1(hashes).await + }) + .await + .map_err(Box::new) + .map_err(Error::EngineError) + } else { + Ok(vec![None; hashes.len()]) + } } pub async fn get_payload_bodies_by_range( @@ -1803,16 +1987,20 @@ impl ExecutionLayer { count: u64, ) -> Result>>, Error> { let _timer = metrics::start_timer(&metrics::EXECUTION_LAYER_GET_PAYLOAD_BODIES_BY_RANGE); - self.engine() - .request(|engine: &Engine| async move { - engine - .api - .get_payload_bodies_by_range_v1(start, count) - .await - }) - .await - .map_err(Box::new) - .map_err(Error::EngineError) + if let Some(engine) = self.engine() { + engine + .request(|engine: &Engine| async move { + engine + .api + .get_payload_bodies_by_range_v1(start, count) + .await + }) + .await + .map_err(Box::new) + .map_err(Error::EngineError) + } else { + Ok(vec![None; count as usize]) + } } /// Fetch a full payload from the execution node. @@ -1872,6 +2060,7 @@ impl ExecutionLayer { if capabilities.get_blobs_v1 { self.engine() + .expect("capabilities only returns get_blobs_v1=true if engine is present") .request(|engine| async move { engine.api.get_blobs_v1(query).await }) .await .map_err(Box::new) @@ -1889,6 +2078,7 @@ impl ExecutionLayer { if capabilities.get_blobs_v2 { self.engine() + .expect("capabilities only returns get_blobs_v2=true if engine is present") .request(|engine| async move { engine.api.get_blobs_v2(query).await }) .await .map_err(Box::new) @@ -1902,11 +2092,15 @@ impl ExecutionLayer { &self, query: BlockByNumberQuery<'_>, ) -> Result, Error> { - self.engine() - .request(|engine| async move { engine.api.get_block_by_number(query).await }) - .await - .map_err(Box::new) - .map_err(Error::EngineError) + if let Some(engine) = self.engine() { + engine + .request(|engine| async move { engine.api.get_block_by_number(query).await }) + .await + .map_err(Box::new) + .map_err(Error::EngineError) + } else { + Ok(None) + } } pub async fn propose_blinded_beacon_block( @@ -2199,16 +2393,14 @@ fn verify_builder_bid( bid.data.message.value().to_i64(), ); - let expected_withdrawals_root = payload_attributes - .withdrawals() - .ok() - .cloned() - .map(|withdrawals| { + let expected_withdrawals_root = match payload_attributes.withdrawals().ok().cloned() { + Some(withdrawals) => Some( Withdrawals::::try_from(withdrawals) .map_err(InvalidBuilderPayload::SszTypesError) - .map(|w| w.tree_hash_root()) - }) - .transpose()?; + .map(|w| w.tree_hash_root())?, + ), + None => None, + }; let payload_withdrawals_root = header.withdrawals_root().ok(); let expected_gas_limit = proposer_gas_limit @@ -2351,7 +2543,10 @@ mod test { MockExecutionLayer::default_params(runtime.task_executor.clone()) .move_to_block_prior_to_terminal_block() .with_terminal_block(|spec, el, _| async move { - el.engine().upcheck().await; + el.engine() + .expect("engine is configured in default test mock execution layer parameters") + .upcheck() + .await; assert_eq!( el.get_terminal_pow_block_hash(&spec, timestamp_now()) .await @@ -2378,7 +2573,10 @@ mod test { MockExecutionLayer::default_params(runtime.task_executor.clone()) .move_to_block_prior_to_terminal_block() .with_terminal_block(|spec, el, _| async move { - el.engine().upcheck().await; + el.engine() + .expect("engine is configured in default test mock execution layer parameters") + .upcheck() + .await; assert_eq!( el.get_terminal_pow_block_hash(&spec, timestamp_now()) .await @@ -2406,7 +2604,10 @@ mod test { MockExecutionLayer::default_params(runtime.task_executor.clone()) .move_to_terminal_block() .with_terminal_block(|spec, el, terminal_block| async move { - el.engine().upcheck().await; + el.engine() + .expect("engine is configured in default test mock execution layer parameters") + .upcheck() + .await; assert_eq!( el.is_valid_terminal_pow_block_hash(terminal_block.unwrap().block_hash, &spec) .await @@ -2423,7 +2624,10 @@ mod test { MockExecutionLayer::default_params(runtime.task_executor.clone()) .move_to_terminal_block() .with_terminal_block(|spec, el, terminal_block| async move { - el.engine().upcheck().await; + el.engine() + .expect("engine is configured in default test mock execution layer parameters") + .upcheck() + .await; let invalid_terminal_block = terminal_block.unwrap().parent_hash; assert_eq!( @@ -2442,7 +2646,10 @@ mod test { MockExecutionLayer::default_params(runtime.task_executor.clone()) .move_to_terminal_block() .with_terminal_block(|spec, el, _| async move { - el.engine().upcheck().await; + el.engine() + .expect("engine is configured in default test mock execution layer parameters") + .upcheck() + .await; let missing_terminal_block = ExecutionBlockHash::repeat_byte(42); assert_eq!( diff --git a/beacon_node/execution_layer/src/test_utils/mock_event_stream.rs b/beacon_node/execution_layer/src/test_utils/mock_event_stream.rs new file mode 100644 index 00000000000..400efdc7e2d --- /dev/null +++ b/beacon_node/execution_layer/src/test_utils/mock_event_stream.rs @@ -0,0 +1,116 @@ +//! Assertion helpers for [`MockClientEvent`] broadcast streams. +//! +//! [`MockEventStream`] wraps a `broadcast::Receiver` and provides +//! ergonomic methods for collecting and asserting on events in integration tests, +//! eliminating the `timeout + loop + counter` boilerplate. + +use super::MockClientEvent; +use std::time::Duration; +use tokio::sync::broadcast; + +/// Error type for [`MockEventStream`] assertions. +#[derive(Debug)] +pub struct MockStreamError(String); + +impl std::fmt::Display for MockStreamError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl std::error::Error for MockStreamError {} + +/// Wraps a `broadcast::Receiver` with assertion helpers. +pub struct MockEventStream { + rx: broadcast::Receiver, +} + +impl From> for MockEventStream { + fn from(rx: broadcast::Receiver) -> Self { + Self { rx } + } +} + +impl MockEventStream { + /// Collect `n` events matching `predicate` within `timeout`, or return an error. + pub async fn collect_n( + &mut self, + n: usize, + predicate: impl Fn(&MockClientEvent) -> bool, + timeout: Duration, + ) -> Result, MockStreamError> { + tokio::time::timeout(timeout, async { + let mut collected = Vec::with_capacity(n); + loop { + match self.rx.recv().await { + Ok(event) if predicate(&event) => { + collected.push(event); + if collected.len() >= n { + return Ok(collected); + } + } + Ok(_) => {} + Err(broadcast::error::RecvError::Lagged(skipped)) => { + return Err(MockStreamError(format!( + "event stream lagged, skipped {skipped} events" + ))); + } + Err(broadcast::error::RecvError::Closed) => { + return Err(MockStreamError(format!( + "event stream closed before collecting {n} events (got {})", + collected.len() + ))); + } + } + } + }) + .await + .map_err(|_| { + MockStreamError(format!( + "timed out after {timeout:?} waiting for {n} events" + )) + })? + } + + /// Expect at least `n` `ProofRequested` events within `timeout`. + pub async fn expect_proof_requests( + &mut self, + n: usize, + timeout: Duration, + ) -> Result, MockStreamError> { + self.collect_n( + n, + |e| matches!(e, MockClientEvent::ProofRequested { .. }), + timeout, + ) + .await + } + + /// Expect at least `n` `ProofVerified` events within `timeout`. + pub async fn expect_proof_verified( + &mut self, + n: usize, + timeout: Duration, + ) -> Result, MockStreamError> { + self.collect_n( + n, + |e| matches!(e, MockClientEvent::ProofVerified { .. }), + timeout, + ) + .await + } + + /// Expect at least `n` `ProofFetched` events within `timeout`. + pub async fn expect_proof_fetched( + &mut self, + n: usize, + timeout: Duration, + ) -> Result, MockStreamError> { + self.collect_n( + n, + |e| matches!(e, MockClientEvent::ProofFetched { .. }), + timeout, + ) + .await + } +} diff --git a/beacon_node/execution_layer/src/test_utils/mock_proof_node_client.rs b/beacon_node/execution_layer/src/test_utils/mock_proof_node_client.rs new file mode 100644 index 00000000000..04654bc809b --- /dev/null +++ b/beacon_node/execution_layer/src/test_utils/mock_proof_node_client.rs @@ -0,0 +1,326 @@ +//! Mock [`ProofNodeClient`] for unit testing [`HttpProofEngine`]. +//! +//! [`MockProofNodeClient`] implements [`ProofNodeClient`] entirely in memory — +//! no HTTP server required. It records received requests, broadcasts proof +//! events after a configurable delay, and always returns `Valid` for verification. +//! +//! [`ProofNodeClient`]: crate::eip8025::ProofNodeClient +//! [`HttpProofEngine`]: crate::eip8025::HttpProofEngine + +use crate::eip8025::errors::ProofEngineError; +use crate::eip8025::proof_node_client::ProofNodeClient; +use crate::eip8025::types::{ProofComplete, ProofEvent}; +use bytes::Bytes; +use futures::stream::Stream; +use parking_lot::Mutex; +use ssz::{Decode, Encode}; +use ssz_derive::{Decode as SszDecode, Encode as SszEncode}; +use ssz_types::VariableList; +use std::collections::HashMap; +use std::marker::PhantomData; +use std::pin::Pin; +use std::sync::{Arc, LazyLock}; +use std::time::Duration; +use superstruct::superstruct; +use tokio::sync::broadcast; +use tokio_stream::StreamExt; +use tokio_stream::wrappers::BroadcastStream; +use tree_hash::TreeHash; +use tree_hash_derive::TreeHash as TreeHashDerive; +use types::execution::eip8025::{ProofAttributes, ProofStatus}; +use types::{ + BeaconStateError, EthSpec, ExecutionPayloadBellatrix, ExecutionPayloadCapella, + ExecutionPayloadDeneb, ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, + ExecutionRequests, Hash256, MainnetEthSpec, VersionedHash, +}; + +/// Owned version of `NewPayloadRequest` used only for SSZ decoding inside the mock. +/// +/// The production `NewPayloadRequest<'block, E>` holds `&'block` references (zero-copy +/// during block processing), which prevents deriving `ssz::Decode`. This local owned +/// superstruct enum mirrors all fork variants with owned fields and is used exclusively +/// to decode the SSZ bytes sent to `request_proofs` and compute `tree_hash_root`. +#[superstruct( + variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), + variant_attributes(derive(SszEncode, SszDecode, TreeHashDerive)), + cast_error( + ty = "BeaconStateError", + expr = "BeaconStateError::IncorrectStateVariant" + ), + partial_getter_error( + ty = "BeaconStateError", + expr = "BeaconStateError::IncorrectStateVariant" + ) +)] +#[derive(SszEncode, SszDecode, TreeHashDerive)] +#[ssz(enum_behaviour = "transparent")] +#[tree_hash(enum_behaviour = "transparent")] +pub struct OwnedNewPayloadRequest { + #[superstruct( + only(Bellatrix), + partial_getter(rename = "execution_payload_bellatrix") + )] + pub execution_payload: ExecutionPayloadBellatrix, + #[superstruct(only(Capella), partial_getter(rename = "execution_payload_capella"))] + pub execution_payload: ExecutionPayloadCapella, + #[superstruct(only(Deneb), partial_getter(rename = "execution_payload_deneb"))] + pub execution_payload: ExecutionPayloadDeneb, + #[superstruct(only(Electra), partial_getter(rename = "execution_payload_electra"))] + pub execution_payload: ExecutionPayloadElectra, + #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))] + pub execution_payload: ExecutionPayloadFulu, + #[superstruct(only(Gloas), partial_getter(rename = "execution_payload_gloas"))] + pub execution_payload: ExecutionPayloadGloas, + #[superstruct(only(Deneb, Electra, Fulu, Gloas))] + pub versioned_hashes: VariableList, + #[superstruct(only(Deneb, Electra, Fulu, Gloas))] + pub parent_beacon_block_root: Hash256, + #[superstruct(only(Electra, Fulu, Gloas))] + pub execution_requests: ExecutionRequests, +} + +/// Events emitted by [`MockProofNodeClient`] for each method invocation. +/// +/// Subscribe via [`MockProofNodeClient::subscribe_client_events`] to observe +/// calls in tests without polling shared state. +#[derive(Debug, Clone)] +pub enum MockClientEvent { + /// Emitted when [`ProofNodeClient::request_proofs`] is called. + ProofRequested { + ssz_body: Vec, + proof_attributes: ProofAttributes, + root: Hash256, + }, + /// Emitted when [`ProofNodeClient::verify_proof`] is called. + ProofVerified { root: Hash256, proof_type: u8 }, + /// Emitted when [`ProofNodeClient::get_proof`] is called. + ProofFetched { root: Hash256, proof_type: u8 }, +} + +/// The registry stores a concrete `MockProofNodeClient` as a +/// non-generic stand-in. All fields are Arc-wrapped, so `get_mock_proof_engine` +/// can construct a `MockProofNodeClient` for any `E` by sharing those Arcs. +static MOCK_REGISTRY: LazyLock< + parking_lot::Mutex>>>, +> = LazyLock::new(|| parking_lot::Mutex::new(HashMap::new())); + +/// Register a mock at `index`. Must be called before `ExecutionLayer::from_config`. +/// +/// Stores the mock as `MainnetEthSpec` internally and returns a `MockProofNodeClient` +/// that shares the same Arc-backed state but decodes SSZ using `E`. +pub fn register_mock_proof_engine( + index: usize, + callback_delay_ms: u64, +) -> MockProofNodeClient { + let stored = Arc::new(MockProofNodeClient::::new( + callback_delay_ms, + )); + let typed = MockProofNodeClient:: { + requests: stored.requests.clone(), + event_tx: stored.event_tx.clone(), + call_tx: stored.call_tx.clone(), + proof_generation_delay: stored.proof_generation_delay, + _phantom: PhantomData, + }; + MOCK_REGISTRY.lock().insert(index, stored); + typed +} + +/// Fetch a registered mock by index as a `MockProofNodeClient`. +/// +/// Constructs the typed client by sharing the Arc fields of the stored +/// `MockProofNodeClient`, so all state (requests, events) is shared. +pub fn get_mock_proof_engine(index: usize) -> Option> { + MOCK_REGISTRY + .lock() + .get(&index) + .map(|stored| MockProofNodeClient:: { + requests: stored.requests.clone(), + event_tx: stored.event_tx.clone(), + call_tx: stored.call_tx.clone(), + proof_generation_delay: stored.proof_generation_delay, + _phantom: PhantomData, + }) +} + +/// URL encoding an index: `"http://mock/{n}/"`. +pub fn mock_proof_engine_url(index: usize) -> String { + format!("http://mock/{}/", index) +} + +/// Parse the index from a mock URL. Returns `None` for non-mock URLs. +pub fn parse_mock_index(url: &str) -> Option { + url.strip_prefix("http://mock/").map(|s| { + let s = s.strip_suffix('/').unwrap_or(s); + if s.is_empty() { + 0 + } else { + s.parse().unwrap_or(0) + } + }) +} + +/// Build a test SSZ body encoding a `NewPayloadRequestFulu` with the given +/// parent beacon block root. Returns `(ssz_bytes, expected_tree_hash_root)`. +pub fn make_test_fulu_ssz(parent_root: Hash256) -> (Vec, Hash256) { + let request = OwnedNewPayloadRequestFulu:: { + execution_payload: ExecutionPayloadFulu::default(), + versioned_hashes: VariableList::default(), + parent_beacon_block_root: parent_root, + execution_requests: ExecutionRequests::default(), + }; + let request = OwnedNewPayloadRequest::Fulu(request); + (request.as_ssz_bytes(), request.tree_hash_root()) +} + +/// In-memory proof node client for testing, generic over [`EthSpec`]. +/// +/// Each call to [`request_proofs`] decodes the SSZ body using `E`, records the +/// raw SSZ body, and schedules a [`ProofEvent::ProofComplete`] event for each +/// requested proof type after `callback_delay_ms` milliseconds. +/// +/// Call [`subscribe_client_events`] to receive a [`MockClientEvent`] stream +/// that fires once per method invocation — useful for asserting that the proof +/// engine issues the expected calls without polling shared state. +/// +/// [`request_proofs`]: MockProofNodeClient::request_proofs +/// [`subscribe_client_events`]: MockProofNodeClient::subscribe_client_events +pub struct MockProofNodeClient { + /// Received SSZ request bodies in order of arrival. + requests: Arc>>>, + /// Broadcast channel for in-memory SSE events. + event_tx: broadcast::Sender, + /// Broadcast channel for method-invocation events. + call_tx: broadcast::Sender, + /// Delay in milliseconds before broadcasting proof complete events. + proof_generation_delay: u64, + _phantom: PhantomData, +} + +impl Clone for MockProofNodeClient { + fn clone(&self) -> Self { + Self { + requests: self.requests.clone(), + event_tx: self.event_tx.clone(), + call_tx: self.call_tx.clone(), + proof_generation_delay: self.proof_generation_delay, + _phantom: PhantomData, + } + } +} + +impl MockProofNodeClient { + /// Create a new unregistered mock client. + /// + /// `callback_delay_ms` controls how long after `request_proofs` the + /// proof complete events are broadcast. + pub fn new(callback_delay_ms: u64) -> Self { + let (event_tx, _) = broadcast::channel(256); + let (call_tx, _) = broadcast::channel(256); + Self { + requests: Arc::new(Mutex::new(Vec::new())), + event_tx, + call_tx, + proof_generation_delay: callback_delay_ms, + _phantom: PhantomData, + } + } + + /// Returns the number of proof requests received. + pub fn request_count(&self) -> usize { + self.requests.lock().len() + } + + /// Returns a clone of all received SSZ request bodies. + pub fn received_requests(&self) -> Vec> { + self.requests.lock().clone() + } + + /// Subscribe to method-invocation events. + /// + /// Each call to `request_proofs`, `verify_proof`, or `get_proof` on this + /// client sends one [`MockClientEvent`] to all active receivers. Use this + /// in tests to assert that the proof engine issues the expected calls. + pub fn subscribe_client_events(&self) -> broadcast::Receiver { + self.call_tx.subscribe() + } +} + +#[async_trait::async_trait] +impl ProofNodeClient for MockProofNodeClient { + async fn request_proofs( + &self, + ssz_body: Vec, + proof_attributes: ProofAttributes, + ) -> Result { + let root = OwnedNewPayloadRequest::::from_ssz_bytes(&ssz_body) + .map_err(|e| ProofEngineError::InvalidPayload(format!("SSZ decode failed: {e:?}")))? + .tree_hash_root(); + + self.requests.lock().push(ssz_body.clone()); + + let _ = self.call_tx.send(MockClientEvent::ProofRequested { + ssz_body, + proof_attributes: proof_attributes.clone(), + root, + }); + + let event_tx = self.event_tx.clone(); + let delay = self.proof_generation_delay; + let proof_types = proof_attributes.proof_types.clone(); + + tokio::spawn(async move { + tokio::time::sleep(Duration::from_millis(delay)).await; + for proof_type in proof_types { + let _ = event_tx.send(ProofEvent::ProofComplete(ProofComplete { + new_payload_request_root: root, + proof_type, + })); + } + }); + + Ok(root) + } + + async fn verify_proof( + &self, + root: Hash256, + proof_type: u8, + _proof_data: &[u8], + ) -> Result { + let _ = self + .call_tx + .send(MockClientEvent::ProofVerified { root, proof_type }); + Ok(ProofStatus::Valid) + } + + async fn get_proof(&self, root: Hash256, proof_type: u8) -> Result { + let _ = self + .call_tx + .send(MockClientEvent::ProofFetched { root, proof_type }); + Ok(Bytes::from(vec![0xDE, 0xAD, 0xBE, 0xEF])) + } + + fn subscribe_client_events( + &self, + ) -> Option> { + Some(self.call_tx.subscribe()) + } + + fn subscribe_proof_events( + &self, + filter_root: Option, + ) -> Pin> + Send + '_>> { + let rx = self.event_tx.subscribe(); + let stream = BroadcastStream::new(rx).filter_map(move |result| match result { + Ok(event) => { + if filter_root.is_some_and(|root| event.new_payload_request_root() != root) { + return None; + } + Some(Ok(event)) + } + Err(_) => None, + }); + Box::pin(stream) + } +} diff --git a/beacon_node/execution_layer/src/test_utils/mod.rs b/beacon_node/execution_layer/src/test_utils/mod.rs index 2465a41d8b6..0ec6bcf42b3 100644 --- a/beacon_node/execution_layer/src/test_utils/mod.rs +++ b/beacon_node/execution_layer/src/test_utils/mod.rs @@ -33,7 +33,12 @@ pub use execution_block_generator::{ }; pub use hook::Hook; pub use mock_builder::{MockBuilder, Operation, mock_builder_extra_data}; +pub use mock_event_stream::{MockEventStream, MockStreamError}; pub use mock_execution_layer::MockExecutionLayer; +pub use mock_proof_node_client::{ + MockClientEvent, MockProofNodeClient, OwnedNewPayloadRequest, get_mock_proof_engine, + make_test_fulu_ssz, mock_proof_engine_url, parse_mock_index, register_mock_proof_engine, +}; pub const DEFAULT_TERMINAL_DIFFICULTY: u64 = 6400; pub const DEFAULT_TERMINAL_BLOCK: u64 = 64; @@ -56,8 +61,8 @@ pub const DEFAULT_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilities { get_payload_v4: true, get_payload_v5: true, get_client_version_v1: true, - get_blobs_v1: true, - get_blobs_v2: true, + get_blobs_v1: false, + get_blobs_v2: false, }; pub static DEFAULT_CLIENT_VERSION: LazyLock = @@ -72,7 +77,9 @@ mod execution_block_generator; mod handle_rpc; mod hook; mod mock_builder; +mod mock_event_stream; mod mock_execution_layer; +pub(crate) mod mock_proof_node_client; /// Configuration for the MockExecutionLayer. #[derive(Clone)] diff --git a/beacon_node/http_api/src/attestation_performance.rs b/beacon_node/http_api/src/attestation_performance.rs index 6e285829d22..19793ce9b1b 100644 --- a/beacon_node/http_api/src/attestation_performance.rs +++ b/beacon_node/http_api/src/attestation_performance.rs @@ -32,6 +32,7 @@ impl From for AttestationPerformanceError { } } +#[allow(clippy::result_large_err)] pub fn get_attestation_performance( target: String, query: AttestationPerformanceQuery, @@ -111,6 +112,7 @@ pub fn get_attestation_performance( let first_block = chain .get_blinded_block(first_block_root) .and_then(|maybe_block| { + #[allow(clippy::result_large_err)] maybe_block.ok_or(BeaconChainError::MissingBeaconBlock(*first_block_root)) }) .map_err(unhandled_error)?; @@ -119,6 +121,7 @@ pub fn get_attestation_performance( let prior_block = chain .get_blinded_block(&first_block.parent_root()) .and_then(|maybe_block| { + #[allow(clippy::result_large_err)] maybe_block .ok_or_else(|| BeaconChainError::MissingBeaconBlock(first_block.parent_root())) }) @@ -131,7 +134,10 @@ pub fn get_attestation_performance( // to cache states so that future calls are faster. let state = chain .get_state(&state_root, Some(prior_slot), true) - .and_then(|maybe_state| maybe_state.ok_or(BeaconChainError::MissingBeaconState(state_root))) + .and_then(|maybe_state| { + #[allow(clippy::result_large_err)] + maybe_state.ok_or(BeaconChainError::MissingBeaconState(state_root)) + }) .map_err(unhandled_error)?; // Allocate an AttestationPerformance vector for each validator in the range. @@ -199,6 +205,7 @@ pub fn get_attestation_performance( chain .get_blinded_block(root) .and_then(|maybe_block| { + #[allow(clippy::result_large_err)] maybe_block.ok_or(BeaconChainError::MissingBeaconBlock(*root)) }) .map_err(unhandled_error) diff --git a/beacon_node/http_api/src/attester_duties.rs b/beacon_node/http_api/src/attester_duties.rs index b42e474b5c4..f9132e83630 100644 --- a/beacon_node/http_api/src/attester_duties.rs +++ b/beacon_node/http_api/src/attester_duties.rs @@ -79,6 +79,7 @@ fn cached_attestation_duties( /// Compute some attester duties by reading a `BeaconState` from disk, completely ignoring the /// shuffling cache. +#[allow(clippy::result_large_err)] fn compute_historic_attester_duties( request_epoch: Epoch, request_indices: &[u64], @@ -151,6 +152,7 @@ fn compute_historic_attester_duties( let duties = request_indices .iter() .map(|&validator_index| { + #[allow(clippy::result_large_err)] state .get_attestation_duties(validator_index as usize, relative_epoch) .map_err(BeaconChainError::from) diff --git a/beacon_node/http_api/src/block_packing_efficiency.rs b/beacon_node/http_api/src/block_packing_efficiency.rs index 3772470b281..3f1501e9f8d 100644 --- a/beacon_node/http_api/src/block_packing_efficiency.rs +++ b/beacon_node/http_api/src/block_packing_efficiency.rs @@ -236,6 +236,7 @@ impl PackingEfficiencyHandler { } } +#[allow(clippy::result_large_err)] pub fn get_block_packing_efficiency( query: BlockPackingEfficiencyQuery, chain: Arc>, @@ -278,6 +279,7 @@ pub fn get_block_packing_efficiency( let first_block = chain .get_blinded_block(first_block_root) .and_then(|maybe_block| { + #[allow(clippy::result_large_err)] maybe_block.ok_or(BeaconChainError::MissingBeaconBlock(*first_block_root)) }) .map_err(unhandled_error)?; @@ -290,6 +292,7 @@ pub fn get_block_packing_efficiency( let starting_state = chain .get_state(&starting_state_root, Some(prior_slot), true) .and_then(|maybe_state| { + #[allow(clippy::result_large_err)] maybe_state.ok_or(BeaconChainError::MissingBeaconState(starting_state_root)) }) .map_err(unhandled_error)?; @@ -392,6 +395,7 @@ pub fn get_block_packing_efficiency( chain .get_blinded_block(root) .and_then(|maybe_block| { + #[allow(clippy::result_large_err)] maybe_block.ok_or(BeaconChainError::MissingBeaconBlock(*root)) }) .map_err(unhandled_error) diff --git a/beacon_node/http_api/src/block_rewards.rs b/beacon_node/http_api/src/block_rewards.rs index 891f024bf9c..85b1a3ce49d 100644 --- a/beacon_node/http_api/src/block_rewards.rs +++ b/beacon_node/http_api/src/block_rewards.rs @@ -12,6 +12,7 @@ use warp_utils::reject::{beacon_state_error, custom_bad_request, unhandled_error const STATE_CACHE_SIZE: NonZeroUsize = new_non_zero_usize(2); /// Fetch block rewards for blocks from the canonical chain. +#[allow(clippy::result_large_err)] pub fn get_block_rewards( query: BlockRewardsQuery, chain: Arc>, @@ -46,7 +47,10 @@ pub fn get_block_rewards( // to cache states so that future calls are faster. let mut state = chain .get_state(&state_root, Some(prior_slot), true) - .and_then(|maybe_state| maybe_state.ok_or(BeaconChainError::MissingBeaconState(state_root))) + .and_then(|maybe_state| { + #[allow(clippy::result_large_err)] + maybe_state.ok_or(BeaconChainError::MissingBeaconState(state_root)) + }) .map_err(unhandled_error)?; state @@ -58,6 +62,7 @@ pub fn get_block_rewards( let block_replayer = BlockReplayer::new(state, &chain.spec) .pre_block_hook(Box::new(|state, block| { + #[allow(clippy::result_large_err)] state.build_all_committee_caches(&chain.spec)?; // Compute block reward. diff --git a/beacon_node/http_api/src/eip8025.rs b/beacon_node/http_api/src/eip8025.rs new file mode 100644 index 00000000000..b9fbfda3280 --- /dev/null +++ b/beacon_node/http_api/src/eip8025.rs @@ -0,0 +1,216 @@ +//! EIP-8025: Optional Execution Proofs - HTTP API Endpoints +//! +//! This module provides HTTP API endpoints for: +//! - GET `/eth/v1/beacon/proofs/execution_proofs/{block_id}` - Retrieve execution proofs for a block +//! - POST `/eth/v1/beacon/execution_proofs` - Submit pre-signed execution proofs + +use crate::block_id::BlockId; +use beacon_chain::{BeaconChain, BeaconChainTypes}; +use lighthouse_network::rpc::methods::ExecutionProofStatus; +use lighthouse_network::{NetworkGlobals, PubsubMessage}; +use network::NetworkMessage; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use tokio::sync::mpsc::UnboundedSender; +use tracing::{debug, warn}; +use types::{ProofStatus, SignedExecutionProof}; +use warp::Reply; +use warp::http::Response; +use warp::hyper::Body; +use warp_utils::reject::{custom_bad_request, custom_server_error}; + +/// Response for GET /eth/v1/beacon/proofs/execution_proofs/{block_id} +#[derive(Debug, Serialize, Deserialize)] +pub struct ExecutionProofsResponse { + pub execution_optimistic: bool, + pub finalized: bool, + pub data: Vec, +} + +/// Request body for POST /eth/v1/beacon/execution_proofs +#[derive(Debug, Serialize, Deserialize)] +pub struct SubmitExecutionProofsRequest { + /// Pre-signed execution proofs from validators + pub proofs: Vec, +} + +/// Get execution proofs for a given block. +/// +/// Returns execution proofs from the ProofEngine for the block's execution payload. +/// This endpoint requires `--proof-engine-endpoint` to be configured. +pub fn get_execution_proofs( + block_id: BlockId, + chain: Arc>, +) -> Result { + // Get the execution layer's proof engine — presence is the only gate. + let execution_layer = chain + .execution_layer + .as_ref() + .ok_or_else(|| custom_server_error("Execution layer not available".to_string()))?; + + let proof_engine = execution_layer.proof_engine().ok_or_else(|| { + custom_bad_request( + "Proof engine not configured. Start with --proof-engine-endpoint to enable EIP-8025." + .to_string(), + ) + })?; + + // Get the block to retrieve its execution payload root + let (block_root, execution_optimistic, finalized) = block_id.root(&chain)?; + + // Get proofs from the proof engine + let request_root = chain + .store + .get_request_root_by_block_root(&block_root) + .ok_or(custom_server_error("request block is unknown".to_string()))?; + let proofs = proof_engine.get_proofs_by_root(&request_root); + + debug!( + block_root = ?block_root, + num_proofs = proofs.len(), + "Retrieved execution proofs for block" + ); + + Ok(ExecutionProofsResponse { + execution_optimistic, + finalized, + data: proofs, + }) +} + +/// Submit signed execution proofs. +/// +/// This endpoint is used by validator clients to submit execution proofs that have been +/// signed by a validator. The proofs will be verified, stored in the ProofEngine, and +/// gossiped to the network. +pub async fn submit_execution_proofs( + request: SubmitExecutionProofsRequest, + chain: Arc>, + network_globals: Arc>, + network_send: UnboundedSender>, +) -> Result, warp::Rejection> { + // TODO: should we add a verify: bool to verify_execution_proof to allow skipping verification checks from this endpoint if we trust the source? + + // Require proof engine to be configured — presence is the only gate. + if chain + .execution_layer + .as_ref() + .and_then(|el| el.proof_engine()) + .is_none() + { + return Err(custom_bad_request( + "Proof engine not configured. Start with --proof-engine-endpoint to enable EIP-8025." + .to_string(), + )); + } + + if request.proofs.is_empty() { + return Err(custom_bad_request("No proofs provided".to_string())); + } + + // Process each signed proof + for signed_proof in request.proofs { + let signed_proof = Arc::new(signed_proof); + let request_root = signed_proof.request_root(); + let proof_type = signed_proof.proof_type(); + let validator_index = signed_proof.validator_index(); + + debug!( + ?request_root, + proof_type, validator_index, "Processing submitted signed execution proof" + ); + + let validator_pubkey = chain + .validator_pubkey_bytes(validator_index as usize) + .map_err(|e| custom_bad_request(format!("Pubkey lookup failed: {e:?}")))? + .ok_or_else(|| custom_bad_request("Unknown validator index".to_string()))?; + + // Verify proof (BLS signature + execution engine + fork choice update) + let (status, verified_block) = chain + .verify_execution_proof(signed_proof.clone(), validator_pubkey) + .await + .map_err(|e| { + warn!( + error = ?e, + ?request_root, + proof_type, + validator_index, + "Signed proof validation failed" + ); + custom_bad_request(format!("Proof validation failed: {e:?}")) + })?; + + // Update local execution proof status watermark if the proof was fully valid. + if status.is_valid() + && let Some((block_root, slot)) = verified_block + { + network_globals.set_local_execution_proof_status(ExecutionProofStatus { + slot: slot.as_u64(), + block_root, + }); + }; + + // Only propagate proofs the execution engine accepted as valid or tentatively accepted. + // Invalid, Syncing, and NotSupported proofs must not be gossiped. + match status { + ProofStatus::Valid | ProofStatus::Accepted => { + if let Err(e) = network_send.send(NetworkMessage::Publish { + messages: vec![PubsubMessage::ExecutionProof(signed_proof)], + }) { + warn!( + error = ?e, + ?request_root, + proof_type, + "Failed to gossip signed proof" + ); + } + debug!( + ?request_root, + proof_type, validator_index, "Signed execution proof verified and gossiped" + ); + } + ProofStatus::Invalid => { + return Err(custom_bad_request(format!( + "Proof {request_root:?} is invalid" + ))); + } + ProofStatus::Syncing => { + debug!( + ?request_root, + proof_type, + validator_index, + "Proof skipped: node is still syncing the associated block" + ); + } + ProofStatus::NotSupported => { + debug!( + ?request_root, + proof_type, + validator_index, + "Proof skipped: proof type not supported by local engine" + ); + } + } + } + + Ok(warp::reply().into_response()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_execution_proofs_response_serialization() { + let response = ExecutionProofsResponse { + execution_optimistic: false, + finalized: true, + data: vec![], + }; + + let json = serde_json::to_string(&response).unwrap(); + assert!(json.contains("execution_optimistic")); + assert!(json.contains("finalized")); + assert!(json.contains("data")); + } +} diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 58cd2a3bdbc..b84d0068cf8 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -16,6 +16,7 @@ mod build_block_contents; mod builder_states; mod custody; mod database; +mod eip8025; mod light_client; mod metrics; mod peer; @@ -542,6 +543,20 @@ pub fn serve( .map(move || TaskSpawner::new(beacon_processor_send.clone())) .boxed(); + // Create a `warp` filter that provides direct access to the `BeaconProcessorSend`. + let beacon_processor_send_direct = ctx.beacon_processor_send.clone(); + let _beacon_processor_send_filter = warp::any() + .map(move || beacon_processor_send_direct.clone()) + .and_then(|send| async move { + match send { + Some(send) => Ok(send), + None => Err(warp_utils::reject::custom_server_error( + "Beacon processor unavailable".to_string(), + )), + } + }) + .boxed(); + let duplicate_block_status_code = ctx.config.duplicate_block_status_code; /* @@ -1786,6 +1801,52 @@ pub fn serve( }, ); + /* + * EIP-8025: beacon/execution_proofs + */ + + let beacon_proofs_path = eth_v1 + .clone() + .and(warp::path("beacon")) + .and(warp::path("execution_proofs")) + .and(task_spawner_filter.clone()) + .and(chain_filter.clone()); + + // GET beacon/execution_proofs/{block_id} + let get_beacon_execution_proofs = beacon_proofs_path + .clone() + .and(block_id_or_err) + .and(warp::path::end()) + .then( + |task_spawner: TaskSpawner, + chain: Arc>, + block_id: BlockId| { + task_spawner.blocking_json_task(Priority::P1, move || { + eip8025::get_execution_proofs(block_id, chain) + }) + }, + ); + + // POST beacon/execution_proofs + let post_prover_execution_proofs = beacon_proofs_path + .clone() + .and(warp::path::end()) + .and(warp_utils::json::json()) + .and(network_globals.clone()) + .and(network_tx_filter.clone()) + .then( + |task_spawner: TaskSpawner, + chain: Arc>, + proofs: eip8025::SubmitExecutionProofsRequest, + network_globals: Arc>, + network_send: UnboundedSender>| { + task_spawner.spawn_async_with_rejection(Priority::P1, async move { + eip8025::submit_execution_proofs(proofs, chain, network_globals, network_send) + .await + }) + }, + ); + /* * config */ @@ -3171,6 +3232,9 @@ pub fn serve( api_types::EventTopic::BlockGossip => { event_handler.subscribe_block_gossip() } + api_types::EventTopic::ExecutionProofValidated => { + event_handler.subscribe_execution_proof_validated() + } }; receivers.push( @@ -3289,6 +3353,7 @@ pub fn serve( .uor(get_beacon_pool_voluntary_exits) .uor(get_beacon_pool_bls_to_execution_changes) .uor(get_beacon_rewards_blocks) + .uor(get_beacon_execution_proofs) .uor(get_config_fork_schedule) .uor(get_config_spec) .uor(get_config_deposit_contract) @@ -3380,6 +3445,7 @@ pub fn serve( .uor(post_lighthouse_add_peer) .uor(post_lighthouse_remove_peer) .uor(post_lighthouse_custody_backfill) + .uor(post_prover_execution_proofs) .recover(warp_utils::reject::handle_rejection), ), ) diff --git a/beacon_node/http_api/src/sync_committee_rewards.rs b/beacon_node/http_api/src/sync_committee_rewards.rs index 9bc1f6ead4d..479dda451d4 100644 --- a/beacon_node/http_api/src/sync_committee_rewards.rs +++ b/beacon_node/http_api/src/sync_committee_rewards.rs @@ -45,6 +45,7 @@ pub fn compute_sync_committee_rewards( Ok((data, execution_optimistic, finalized)) } +#[allow(clippy::result_large_err)] pub fn get_state_before_applying_block( chain: Arc>, block: &SignedBlindedBeaconBlock, @@ -52,6 +53,7 @@ pub fn get_state_before_applying_block( let parent_block: SignedBlindedBeaconBlock = chain .get_blinded_block(&block.parent_root()) .and_then(|maybe_block| { + #[allow(clippy::result_large_err)] maybe_block.ok_or_else(|| BeaconChainError::MissingBeaconBlock(block.parent_root())) }) .map_err(|e| custom_not_found(format!("Parent block is not available! {:?}", e)))?; @@ -61,6 +63,7 @@ pub fn get_state_before_applying_block( let parent_state = chain .get_state(&parent_block.state_root(), Some(parent_block.slot()), true) .and_then(|maybe_state| { + #[allow(clippy::result_large_err)] maybe_state .ok_or_else(|| BeaconChainError::MissingBeaconState(parent_block.state_root())) }) diff --git a/beacon_node/http_api/src/sync_committees.rs b/beacon_node/http_api/src/sync_committees.rs index 6e2f4c95851..313ffb6fb94 100644 --- a/beacon_node/http_api/src/sync_committees.rs +++ b/beacon_node/http_api/src/sync_committees.rs @@ -136,6 +136,7 @@ fn duties_from_state_load( } } +#[allow(clippy::result_large_err)] fn verify_unknown_validators( duties: Vec, BeaconStateError>>, request_epoch: Epoch, @@ -147,6 +148,7 @@ fn verify_unknown_validators( duties .into_iter() .map(|res| { + #[allow(clippy::result_large_err)] res.or_else(|err| { // Make sure the validator is really unknown w.r.t. the request_epoch if let BeaconStateError::UnknownValidator(idx) = err { diff --git a/beacon_node/http_api/src/task_spawner.rs b/beacon_node/http_api/src/task_spawner.rs index 834cd29971f..fed466b8f4f 100644 --- a/beacon_node/http_api/src/task_spawner.rs +++ b/beacon_node/http_api/src/task_spawner.rs @@ -107,7 +107,7 @@ impl TaskSpawner { pub async fn spawn_async_with_rejection( self, priority: Priority, - func: impl Future> + Send + Sync + 'static, + func: impl Future> + Send + 'static, ) -> Response { let result = self .spawn_async_with_rejection_no_conversion(priority, func) @@ -122,7 +122,7 @@ impl TaskSpawner { pub async fn spawn_async_with_rejection_no_conversion( self, priority: Priority, - func: impl Future> + Send + Sync + 'static, + func: impl Future> + Send + 'static, ) -> Result { if let Some(beacon_processor_send) = &self.beacon_processor_send { // Create a wrapper future that will execute `func` and send the diff --git a/beacon_node/http_api/src/ui.rs b/beacon_node/http_api/src/ui.rs index 1538215a0b5..5111f4c71cf 100644 --- a/beacon_node/http_api/src/ui.rs +++ b/beacon_node/http_api/src/ui.rs @@ -20,6 +20,7 @@ pub struct ValidatorCountResponse { pub exited_slashed: u64, } +#[allow(clippy::result_large_err)] pub fn get_validator_count( chain: Arc>, ) -> Result { @@ -36,27 +37,30 @@ pub fn get_validator_count( chain .with_head(|head| { - let state = &head.beacon_state; - let epoch = state.current_epoch(); - for validator in state.validators() { - let status = - ValidatorStatus::from_validator(validator, epoch, spec.far_future_epoch); - - match status { - ValidatorStatus::ActiveOngoing => active_ongoing += 1, - ValidatorStatus::ActiveExiting => active_exiting += 1, - ValidatorStatus::ActiveSlashed => active_slashed += 1, - ValidatorStatus::PendingInitialized => pending_initialized += 1, - ValidatorStatus::PendingQueued => pending_queued += 1, - ValidatorStatus::WithdrawalPossible => withdrawal_possible += 1, - ValidatorStatus::WithdrawalDone => withdrawal_done += 1, - ValidatorStatus::ExitedUnslashed => exited_unslashed += 1, - ValidatorStatus::ExitedSlashed => exited_slashed += 1, - // Since we are not invoking `superset`, all other variants will be 0. - _ => (), + #[allow(clippy::result_large_err)] + { + let state = &head.beacon_state; + let epoch = state.current_epoch(); + for validator in state.validators() { + let status = + ValidatorStatus::from_validator(validator, epoch, spec.far_future_epoch); + + match status { + ValidatorStatus::ActiveOngoing => active_ongoing += 1, + ValidatorStatus::ActiveExiting => active_exiting += 1, + ValidatorStatus::ActiveSlashed => active_slashed += 1, + ValidatorStatus::PendingInitialized => pending_initialized += 1, + ValidatorStatus::PendingQueued => pending_queued += 1, + ValidatorStatus::WithdrawalPossible => withdrawal_possible += 1, + ValidatorStatus::WithdrawalDone => withdrawal_done += 1, + ValidatorStatus::ExitedUnslashed => exited_unslashed += 1, + ValidatorStatus::ExitedSlashed => exited_slashed += 1, + // Since we are not invoking `superset`, all other variants will be 0. + _ => (), + } } + Ok::<(), BeaconChainError>(()) } - Ok::<(), BeaconChainError>(()) }) .map_err(unhandled_error)?; diff --git a/beacon_node/lighthouse_network/src/config.rs b/beacon_node/lighthouse_network/src/config.rs index 416ca73e08e..bc0b2ae3669 100644 --- a/beacon_node/lighthouse_network/src/config.rs +++ b/beacon_node/lighthouse_network/src/config.rs @@ -125,6 +125,13 @@ pub struct Config { /// Whether light client protocols should be enabled. pub enable_light_client_server: bool, + /// Whether to subscribe to the EIP-8025 execution proof gossip topic. + /// Set to `true` only when `--proof-engine-endpoint` is configured. + pub enable_execution_proof: bool, + + /// Proof types supported by this client. + pub proof_types: Option>, + /// Configuration for the outbound rate limiter (requests made by this node). pub outbound_rate_limiter_config: Option, @@ -359,6 +366,8 @@ impl Default for Config { proposer_only: false, metrics_enabled: false, enable_light_client_server: true, + enable_execution_proof: false, + proof_types: None, outbound_rate_limiter_config: None, invalid_block_storage: None, inbound_rate_limiter_config: None, diff --git a/beacon_node/lighthouse_network/src/discovery/enr.rs b/beacon_node/lighthouse_network/src/discovery/enr.rs index 4c285ea86c8..1f43f66642e 100644 --- a/beacon_node/lighthouse_network/src/discovery/enr.rs +++ b/beacon_node/lighthouse_network/src/discovery/enr.rs @@ -29,6 +29,8 @@ pub const ATTESTATION_BITFIELD_ENR_KEY: &str = "attnets"; pub const SYNC_COMMITTEE_BITFIELD_ENR_KEY: &str = "syncnets"; /// The ENR field specifying the peerdas custody group count. pub const PEERDAS_CUSTODY_GROUP_COUNT_ENR_KEY: &str = "cgc"; +/// The ENR field indicating execution proof engine support. +pub const EXECUTION_PROOF_ENR_KEY: &str = "eproof"; /// Extension trait for ENR's within Eth2. pub trait Eth2Enr { @@ -47,6 +49,9 @@ pub trait Eth2Enr { fn next_fork_digest(&self) -> Result<[u8; 4], &'static str>; fn eth2(&self) -> Result; + + /// Whether this node has an execution proof engine configured and can serve execution proofs. + fn execution_proof_enabled(&self) -> bool; } impl Eth2Enr for Enr { @@ -99,6 +104,12 @@ impl Eth2Enr for Enr { EnrForkId::from_ssz_bytes(ð2_bytes).map_err(|_| "Could not decode EnrForkId") } + + fn execution_proof_enabled(&self) -> bool { + self.get_decodable::(EXECUTION_PROOF_ENR_KEY) + .and_then(|r| r.ok()) + .unwrap_or(false) + } } /// Either use the given ENR or load an ENR from file if it exists and matches the current NodeId @@ -284,6 +295,11 @@ pub fn build_enr( builder.add_value(NEXT_FORK_DIGEST_ENR_KEY, &next_fork_digest); } + // advertise execution proof engine support if configured + if config.enable_execution_proof { + builder.add_value(EXECUTION_PROOF_ENR_KEY, &true); + } + builder .build(enr_key) .map_err(|e| format!("Could not build Local ENR: {:?}", e)) @@ -308,11 +324,12 @@ fn compare_enr(local_enr: &Enr, disk_enr: &Enr) -> bool { && (local_enr.udp4().is_none() || local_enr.udp4() == disk_enr.udp4()) && (local_enr.udp6().is_none() || local_enr.udp6() == disk_enr.udp6()) // we need the ATTESTATION_BITFIELD_ENR_KEY and SYNC_COMMITTEE_BITFIELD_ENR_KEY and - // PEERDAS_CUSTODY_GROUP_COUNT_ENR_KEY key to match, otherwise we use a new ENR. This will - // likely only be true for non-validating nodes. + // PEERDAS_CUSTODY_GROUP_COUNT_ENR_KEY and EXECUTION_PROOF_ENR_KEY to match, otherwise we + // use a new ENR. This will likely only be true for non-validating nodes. && local_enr.get_decodable::(ATTESTATION_BITFIELD_ENR_KEY) == disk_enr.get_decodable(ATTESTATION_BITFIELD_ENR_KEY) && local_enr.get_decodable::(SYNC_COMMITTEE_BITFIELD_ENR_KEY) == disk_enr.get_decodable(SYNC_COMMITTEE_BITFIELD_ENR_KEY) && local_enr.get_decodable::(PEERDAS_CUSTODY_GROUP_COUNT_ENR_KEY) == disk_enr.get_decodable(PEERDAS_CUSTODY_GROUP_COUNT_ENR_KEY) + && local_enr.get_decodable::(EXECUTION_PROOF_ENR_KEY) == disk_enr.get_decodable(EXECUTION_PROOF_ENR_KEY) } /// Loads enr from the given directory diff --git a/beacon_node/lighthouse_network/src/discovery/mod.rs b/beacon_node/lighthouse_network/src/discovery/mod.rs index 939eca3b946..ebe8e76e8b7 100644 --- a/beacon_node/lighthouse_network/src/discovery/mod.rs +++ b/beacon_node/lighthouse_network/src/discovery/mod.rs @@ -560,6 +560,8 @@ impl Discovery { } // Data column subnets are computed from node ID. No subnet bitfield in the ENR. Subnet::DataColumn(_) => return Ok(()), + // ExecutionProof capability is set once at startup via build_enr(); not toggled. + Subnet::ExecutionProof => return Ok(()), } // replace the global version @@ -904,6 +906,7 @@ impl Discovery { Subnet::Attestation(_) => "attestation", Subnet::SyncCommittee(_) => "sync_committee", Subnet::DataColumn(_) => "data_column", + Subnet::ExecutionProof => "execution_proof", }; if let Some(v) = metrics::get_int_counter( diff --git a/beacon_node/lighthouse_network/src/discovery/subnet_predicate.rs b/beacon_node/lighthouse_network/src/discovery/subnet_predicate.rs index 757dbb58534..a403c478fbb 100644 --- a/beacon_node/lighthouse_network/src/discovery/subnet_predicate.rs +++ b/beacon_node/lighthouse_network/src/discovery/subnet_predicate.rs @@ -41,6 +41,7 @@ where false } } + Subnet::ExecutionProof => enr.execution_proof_enabled(), }); if !predicate { diff --git a/beacon_node/lighthouse_network/src/peer_manager/mod.rs b/beacon_node/lighthouse_network/src/peer_manager/mod.rs index 43a44c85fc8..4c29c1023ad 100644 --- a/beacon_node/lighthouse_network/src/peer_manager/mod.rs +++ b/beacon_node/lighthouse_network/src/peer_manager/mod.rs @@ -22,6 +22,7 @@ pub use libp2p::identity::Keypair; pub mod peerdb; +use crate::discovery::enr::Eth2Enr; use crate::peer_manager::peerdb::client::ClientKind; use crate::types::GossipKind; use libp2p::multiaddr; @@ -59,6 +60,8 @@ pub const PEER_RECONNECTION_TIMEOUT: Duration = Duration::from_secs(600); pub const MIN_SYNC_COMMITTEE_PEERS: u64 = 2; /// Avoid pruning sampling peers if subnet peer count is below this number. pub const MIN_SAMPLING_COLUMN_SUBNET_PEERS: u64 = 2; +/// Minimum connected peers that advertise execution proof engine support. +pub const MIN_EXECUTION_PROOF_PEERS: u64 = 1; /// A fraction of `PeerManager::target_peers` that we allow to connect to us in excess of /// `PeerManager::target_peers`. For clarity, if `PeerManager::target_peers` is 50 and /// PEER_EXCESS_FACTOR = 0.1 we allow 10% more nodes, i.e 55. @@ -353,6 +356,13 @@ impl PeerManager { let results_count = results.len(); let connected_or_dialing = self.network_globals.connected_or_dialing_peers(); for (enr, min_ttl) in results { + let peer_id = enr.peer_id(); + + self.network_globals + .peers + .write() + .update_peer_enr_if_missing(&peer_id, enr.clone()); + // There are two conditions in deciding whether to dial this peer. // 1. If we are less than our max connections. Discovery queries are executed to reach // our target peers, so its fine to dial up to our max peers (which will get pruned @@ -368,7 +378,6 @@ impl PeerManager { { // This should be updated with the peer dialing. In fact created once the peer is // dialed - let peer_id = enr.peer_id(); if let Some(min_ttl) = min_ttl { self.network_globals .peers @@ -599,6 +608,10 @@ impl PeerManager { Protocol::BlobsByRoot => PeerAction::MidToleranceError, Protocol::DataColumnsByRoot => PeerAction::MidToleranceError, Protocol::DataColumnsByRange => PeerAction::MidToleranceError, + Protocol::ExecutionProofsByRange => PeerAction::MidToleranceError, + Protocol::ExecutionProofsByRoot => PeerAction::MidToleranceError, + // ExecutionProofStatus is a soft informational request; rate limiting is fine. + Protocol::ExecutionProofStatus => return, Protocol::Goodbye => PeerAction::LowToleranceError, Protocol::MetaData => PeerAction::LowToleranceError, Protocol::Status => PeerAction::LowToleranceError, @@ -619,6 +632,9 @@ impl PeerManager { Protocol::BlobsByRoot => return, Protocol::DataColumnsByRoot => return, Protocol::DataColumnsByRange => return, + Protocol::ExecutionProofsByRange => return, + Protocol::ExecutionProofsByRoot => return, + Protocol::ExecutionProofStatus => return, Protocol::Goodbye => return, Protocol::LightClientBootstrap => return, Protocol::LightClientOptimisticUpdate => return, @@ -642,6 +658,9 @@ impl PeerManager { Protocol::BlobsByRoot => PeerAction::MidToleranceError, Protocol::DataColumnsByRoot => PeerAction::MidToleranceError, Protocol::DataColumnsByRange => PeerAction::MidToleranceError, + Protocol::ExecutionProofsByRange => PeerAction::MidToleranceError, + Protocol::ExecutionProofsByRoot => PeerAction::MidToleranceError, + Protocol::ExecutionProofStatus => return, Protocol::LightClientBootstrap => return, Protocol::LightClientOptimisticUpdate => return, Protocol::LightClientFinalityUpdate => return, @@ -1009,6 +1028,30 @@ impl PeerManager { } } + /// Trigger subnet-targeted discovery when we are below the minimum number of connected peers + /// that advertise execution proof engine support (`ep=true` ENR). + fn maintain_proof_capable_peers(&mut self) { + let count = self + .network_globals + .peers + .read() + .good_peers_on_subnet(Subnet::ExecutionProof) + .count() as u64; + if count < MIN_EXECUTION_PROOF_PEERS { + debug!( + count, + target = MIN_EXECUTION_PROOF_PEERS, + "Insufficient proof-capable peers; triggering discovery" + ); + self.events.push(PeerManagerEvent::DiscoverSubnetPeers(vec![ + SubnetDiscovery { + subnet: Subnet::ExecutionProof, + min_ttl: None, + }, + ])); + } + } + /// This function checks the status of our current peers and optionally requests a discovery /// query if we need to find more peers to maintain the current number of peers fn maintain_peer_count(&mut self, dialing_peers: usize) { @@ -1079,6 +1122,8 @@ impl PeerManager { Subnet::DataColumn(id) => { peer_info.custody_subnets.insert(id); } + // ExecutionProof is a capability flag, not a subnet tracked in peer_subnet_info. + Subnet::ExecutionProof => {} } } @@ -1163,6 +1208,27 @@ impl PeerManager { return true; } + // Protect proof-capable peers when at or below the minimum threshold + if self.network_globals.execution_proof() { + let is_proof_capable = candidate_info + .info + .enr() + .is_some_and(|enr| enr.execution_proof_enabled()); + if is_proof_capable { + let proof_capable_count = peer_subnet_info + .values() + .filter(|p| { + p.info + .enr() + .is_some_and(|enr| enr.execution_proof_enabled()) + }) + .count(); + if proof_capable_count <= MIN_EXECUTION_PROOF_PEERS as usize { + return true; + } + } + } + // Check attestation subnet to avoid pruning from subnets with the lowest peer count let attestation_subnet_counts: HashMap = peer_subnet_info .values() @@ -1447,6 +1513,11 @@ impl PeerManager { // Maintain minimum count for sync committee peers. self.maintain_sync_committee_peers(); + // Maintain minimum count for execution-proof-capable peers. + if self.network_globals.execution_proof() { + self.maintain_proof_capable_peers(); + } + // Prune any excess peers back to our target in such a way that incentivises good scores and // a uniform distribution of subnets. self.prune_excess_peers(); diff --git a/beacon_node/lighthouse_network/src/peer_manager/peerdb.rs b/beacon_node/lighthouse_network/src/peer_manager/peerdb.rs index 11ce7853507..a46edac2a5c 100644 --- a/beacon_node/lighthouse_network/src/peer_manager/peerdb.rs +++ b/beacon_node/lighthouse_network/src/peer_manager/peerdb.rs @@ -1,5 +1,5 @@ use crate::discovery::CombinedKey; -use crate::discovery::enr::PEERDAS_CUSTODY_GROUP_COUNT_ENR_KEY; +use crate::discovery::enr::{EXECUTION_PROOF_ENR_KEY, PEERDAS_CUSTODY_GROUP_COUNT_ENR_KEY}; use crate::{Enr, Gossipsub, PeerId, SyncInfo, metrics, multiaddr::Multiaddr, types::Subnet}; use itertools::Itertools; use logging::crit; @@ -695,6 +695,15 @@ impl PeerDB { } } + /// Stores the ENR for a peer if they don't already have one recorded. + pub(super) fn update_peer_enr_if_missing(&mut self, peer_id: &PeerId, enr: Enr) { + if let Some(info) = self.peer_info_mut(peer_id) + && info.enr().is_none() + { + info.set_enr(enr); + } + } + /// Update min ttl of a peer. // VISIBILITY: Only the peer manager can update the min_ttl pub(super) fn update_min_ttl(&mut self, peer_id: &PeerId, min_ttl: Instant) { @@ -853,6 +862,42 @@ impl PeerDB { peer_id } + /// Like `__add_connected_peer_testing_only`, but sets `ep=true` in the peer's ENR so that + /// `execution_proof_enabled()` returns true for this peer. MUST ONLY BE USED IN TESTS. + pub fn __add_connected_proof_capable_peer_testing_only( + &mut self, + enr_key: CombinedKey, + ) -> PeerId { + let mut enr = Enr::builder().build(&enr_key).unwrap(); + let peer_id = enr.peer_id(); + enr.insert(EXECUTION_PROOF_ENR_KEY, &true, &enr_key) + .expect("bool can be encoded"); + + self.update_connection_state( + &peer_id, + NewConnectionState::Connected { + enr: Some(enr), + seen_address: Multiaddr::empty(), + direction: ConnectionDirection::Outgoing, + }, + ); + + self.update_sync_status( + &peer_id, + SyncStatus::Synced { + info: SyncInfo { + head_slot: Slot::new(0), + head_root: Hash256::ZERO, + finalized_epoch: Epoch::new(0), + finalized_root: Hash256::ZERO, + earliest_available_slot: Some(Slot::new(0)), + }, + }, + ); + + peer_id + } + /// The connection state of the peer has been changed. Modify the peer in the db to ensure all /// variables are in sync with libp2p. /// Updating the state can lead to a `BanOperation` which needs to be processed via the peer diff --git a/beacon_node/lighthouse_network/src/peer_manager/peerdb/peer_info.rs b/beacon_node/lighthouse_network/src/peer_manager/peerdb/peer_info.rs index c289cb9a69c..a6e51e37692 100644 --- a/beacon_node/lighthouse_network/src/peer_manager/peerdb/peer_info.rs +++ b/beacon_node/lighthouse_network/src/peer_manager/peerdb/peer_info.rs @@ -92,6 +92,10 @@ impl PeerInfo { /// Returns if the peer is subscribed to a given `Subnet` from the metadata attnets/syncnets field. /// Also returns true if the peer is assigned to custody a given data column `Subnet` computed from the metadata `custody_group_count` field or ENR `cgc` field. pub fn on_subnet_metadata(&self, subnet: &Subnet) -> bool { + // ExecutionProof capability is advertised in the ENR, not a metadata bitfield. + if matches!(subnet, Subnet::ExecutionProof) { + return self.enr().is_some_and(|enr| enr.execution_proof_enabled()); + } if let Some(meta_data) = &self.meta_data { match subnet { Subnet::Attestation(id) => { @@ -105,6 +109,7 @@ impl PeerInfo { Subnet::DataColumn(subnet_id) => { return self.is_assigned_to_custody_subnet(subnet_id); } + Subnet::ExecutionProof => unreachable!("handled above"), } } false diff --git a/beacon_node/lighthouse_network/src/rpc/codec.rs b/beacon_node/lighthouse_network/src/rpc/codec.rs index 3611f023917..a7c84556836 100644 --- a/beacon_node/lighthouse_network/src/rpc/codec.rs +++ b/beacon_node/lighthouse_network/src/rpc/codec.rs @@ -21,7 +21,7 @@ use types::{ LightClientOptimisticUpdate, LightClientUpdate, SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockBellatrix, SignedBeaconBlockCapella, SignedBeaconBlockDeneb, SignedBeaconBlockElectra, SignedBeaconBlockFulu, - SignedBeaconBlockGloas, + SignedBeaconBlockGloas, execution::eip8025::SignedExecutionProof, }; use unsigned_varint::codec::Uvi; @@ -80,6 +80,9 @@ impl SSZSnappyInboundCodec { RpcSuccessResponse::BlobsByRoot(res) => res.as_ssz_bytes(), RpcSuccessResponse::DataColumnsByRoot(res) => res.as_ssz_bytes(), RpcSuccessResponse::DataColumnsByRange(res) => res.as_ssz_bytes(), + RpcSuccessResponse::ExecutionProofsByRange(res) => res.as_ssz_bytes(), + RpcSuccessResponse::ExecutionProofsByRoot(res) => res.as_ssz_bytes(), + RpcSuccessResponse::ExecutionProofStatus(s) => s.as_ssz_bytes(), RpcSuccessResponse::LightClientBootstrap(res) => res.as_ssz_bytes(), RpcSuccessResponse::LightClientOptimisticUpdate(res) => res.as_ssz_bytes(), RpcSuccessResponse::LightClientFinalityUpdate(res) => res.as_ssz_bytes(), @@ -163,6 +166,7 @@ impl Decoder for SSZSnappyInboundCodec { if self.protocol.versioned_protocol == SupportedProtocol::MetaDataV3 { return Ok(Some(RequestType::MetaData(MetadataRequest::new_v3()))); } + // ExecutionProofStatus now carries a 40-byte body; handled in the normal decode path below. let Some(length) = handle_length(&mut self.inner, &mut self.len, src)? else { return Ok(None); }; @@ -363,7 +367,10 @@ impl Encoder> for SSZSnappyOutboundCodec { RequestType::Ping(req) => req.as_ssz_bytes(), RequestType::LightClientBootstrap(req) => req.as_ssz_bytes(), RequestType::LightClientUpdatesByRange(req) => req.as_ssz_bytes(), - // no metadata to encode + RequestType::ExecutionProofsByRange(req) => req.as_ssz_bytes(), + RequestType::ExecutionProofsByRoot(req) => req.identifiers.as_ssz_bytes(), + RequestType::ExecutionProofStatus(s) => s.as_ssz_bytes(), + // no body to encode for these request types RequestType::MetaData(_) | RequestType::LightClientOptimisticUpdate | RequestType::LightClientFinalityUpdate => return Ok(()), @@ -587,6 +594,23 @@ fn handle_rpc_request( LightClientUpdatesByRangeRequest::from_ssz_bytes(decoded_buffer)?, ))) } + SupportedProtocol::ExecutionProofsByRangeV1 => { + let max_filters = spec.max_request_blocks(current_fork); + Ok(Some(RequestType::ExecutionProofsByRange( + ExecutionProofsByRangeRequest::from_ssz_bytes_with_max( + decoded_buffer, + max_filters, + )?, + ))) + } + SupportedProtocol::ExecutionProofsByRootV1 => Ok(Some(RequestType::ExecutionProofsByRoot( + ExecutionProofsByRootRequest { + identifiers: RuntimeVariableList::from_ssz_bytes( + decoded_buffer, + spec.max_request_blocks(current_fork), + )?, + }, + ))), // MetaData requests return early from InboundUpgrade and do not reach the decoder. // Handle this case just for completeness. SupportedProtocol::MetaDataV3 => { @@ -614,6 +638,9 @@ fn handle_rpc_request( Ok(Some(RequestType::MetaData(MetadataRequest::new_v1()))) } } + SupportedProtocol::ExecutionProofStatusV1 => Ok(Some(RequestType::ExecutionProofStatus( + ExecutionProofStatus::from_ssz_bytes(decoded_buffer)?, + ))), } } @@ -841,6 +868,21 @@ fn handle_rpc_response( ), )), }, + SupportedProtocol::ExecutionProofsByRangeV1 => { + Ok(Some(RpcSuccessResponse::ExecutionProofsByRange(Arc::new( + SignedExecutionProof::from_ssz_bytes(decoded_buffer)?, + )))) + } + SupportedProtocol::ExecutionProofsByRootV1 => { + Ok(Some(RpcSuccessResponse::ExecutionProofsByRoot(Arc::new( + SignedExecutionProof::from_ssz_bytes(decoded_buffer)?, + )))) + } + SupportedProtocol::ExecutionProofStatusV1 => { + Ok(Some(RpcSuccessResponse::ExecutionProofStatus( + ExecutionProofStatus::from_ssz_bytes(decoded_buffer)?, + ))) + } SupportedProtocol::BlocksByRootV2 => match fork_name { Some(ForkName::Altair) => Ok(Some(RpcSuccessResponse::BlocksByRoot(Arc::new( SignedBeaconBlock::Altair(SignedBeaconBlockAltair::from_ssz_bytes(decoded_buffer)?), @@ -1284,6 +1326,15 @@ mod tests { RequestType::LightClientUpdatesByRange(light_client_updates_by_range) ) } + RequestType::ExecutionProofsByRange(ep_range) => { + assert_eq!(decoded, RequestType::ExecutionProofsByRange(ep_range)) + } + RequestType::ExecutionProofsByRoot(ep_root) => { + assert_eq!(decoded, RequestType::ExecutionProofsByRoot(ep_root)) + } + RequestType::ExecutionProofStatus(s) => { + assert_eq!(decoded, RequestType::ExecutionProofStatus(s)) + } } } diff --git a/beacon_node/lighthouse_network/src/rpc/config.rs b/beacon_node/lighthouse_network/src/rpc/config.rs index b0ee6fea64b..8833b547d11 100644 --- a/beacon_node/lighthouse_network/src/rpc/config.rs +++ b/beacon_node/lighthouse_network/src/rpc/config.rs @@ -97,6 +97,9 @@ pub struct RateLimiterConfig { pub(super) light_client_optimistic_update_quota: Quota, pub(super) light_client_finality_update_quota: Quota, pub(super) light_client_updates_by_range_quota: Quota, + pub(super) execution_proofs_by_range_quota: Quota, + pub(super) execution_proofs_by_root_quota: Quota, + pub(super) execution_proof_status_quota: Quota, } impl RateLimiterConfig { @@ -126,6 +129,13 @@ impl RateLimiterConfig { pub const DEFAULT_LIGHT_CLIENT_OPTIMISTIC_UPDATE_QUOTA: Quota = Quota::one_every(10); pub const DEFAULT_LIGHT_CLIENT_FINALITY_UPDATE_QUOTA: Quota = Quota::one_every(10); pub const DEFAULT_LIGHT_CLIENT_UPDATES_BY_RANGE_QUOTA: Quota = Quota::one_every(10); + // Execution proofs are comparable to data columns in bandwidth. + pub const DEFAULT_EXECUTION_PROOFS_BY_RANGE_QUOTA: Quota = + Quota::n_every(NonZeroU64::new(128).unwrap(), 10); + pub const DEFAULT_EXECUTION_PROOFS_BY_ROOT_QUOTA: Quota = + Quota::n_every(NonZeroU64::new(128).unwrap(), 10); + pub const DEFAULT_EXECUTION_PROOF_STATUS_QUOTA: Quota = + Quota::n_every(NonZeroU64::new(5).unwrap(), 15); } impl Default for RateLimiterConfig { @@ -146,6 +156,9 @@ impl Default for RateLimiterConfig { Self::DEFAULT_LIGHT_CLIENT_OPTIMISTIC_UPDATE_QUOTA, light_client_finality_update_quota: Self::DEFAULT_LIGHT_CLIENT_FINALITY_UPDATE_QUOTA, light_client_updates_by_range_quota: Self::DEFAULT_LIGHT_CLIENT_UPDATES_BY_RANGE_QUOTA, + execution_proofs_by_range_quota: Self::DEFAULT_EXECUTION_PROOFS_BY_RANGE_QUOTA, + execution_proofs_by_root_quota: Self::DEFAULT_EXECUTION_PROOFS_BY_ROOT_QUOTA, + execution_proof_status_quota: Self::DEFAULT_EXECUTION_PROOF_STATUS_QUOTA, } } } @@ -205,6 +218,9 @@ impl FromStr for RateLimiterConfig { let mut light_client_optimistic_update_quota = None; let mut light_client_finality_update_quota = None; let mut light_client_updates_by_range_quota = None; + let mut execution_proofs_by_range_quota = None; + let mut execution_proofs_by_root_quota = None; + let mut execution_proof_status_quota = None; for proto_def in s.split(';') { let ProtocolQuota { protocol, quota } = proto_def.parse()?; @@ -239,6 +255,15 @@ impl FromStr for RateLimiterConfig { light_client_updates_by_range_quota = light_client_updates_by_range_quota.or(quota) } + Protocol::ExecutionProofsByRange => { + execution_proofs_by_range_quota = execution_proofs_by_range_quota.or(quota) + } + Protocol::ExecutionProofsByRoot => { + execution_proofs_by_root_quota = execution_proofs_by_root_quota.or(quota) + } + Protocol::ExecutionProofStatus => { + execution_proof_status_quota = execution_proof_status_quota.or(quota) + } } } Ok(RateLimiterConfig { @@ -265,6 +290,12 @@ impl FromStr for RateLimiterConfig { .unwrap_or(Self::DEFAULT_LIGHT_CLIENT_FINALITY_UPDATE_QUOTA), light_client_updates_by_range_quota: light_client_updates_by_range_quota .unwrap_or(Self::DEFAULT_LIGHT_CLIENT_UPDATES_BY_RANGE_QUOTA), + execution_proofs_by_range_quota: execution_proofs_by_range_quota + .unwrap_or(Self::DEFAULT_EXECUTION_PROOFS_BY_RANGE_QUOTA), + execution_proofs_by_root_quota: execution_proofs_by_root_quota + .unwrap_or(Self::DEFAULT_EXECUTION_PROOFS_BY_ROOT_QUOTA), + execution_proof_status_quota: execution_proof_status_quota + .unwrap_or(Self::DEFAULT_EXECUTION_PROOF_STATUS_QUOTA), }) } } diff --git a/beacon_node/lighthouse_network/src/rpc/methods.rs b/beacon_node/lighthouse_network/src/rpc/methods.rs index 0539877c722..b1b72b11def 100644 --- a/beacon_node/lighthouse_network/src/rpc/methods.rs +++ b/beacon_node/lighthouse_network/src/rpc/methods.rs @@ -13,6 +13,7 @@ use std::sync::Arc; use strum::IntoStaticStr; use superstruct::superstruct; use types::data::BlobIdentifier; +use types::execution::eip8025::{ProofByRootIdentifier, SignedExecutionProof}; use types::light_client::consts::MAX_REQUEST_LIGHT_CLIENT_UPDATES; use types::{ ChainSpec, ColumnIndex, DataColumnSidecar, DataColumnsByRootIdentifier, Epoch, EthSpec, @@ -573,6 +574,147 @@ impl LightClientUpdatesByRangeRequest { } } +/// The peer's current execution proof verification status, exchanged via the +/// `ExecutionProofStatus` RPC protocol. +#[derive(Encode, Decode, Default, Copy, Clone, Debug, PartialEq)] +pub struct ExecutionProofStatus { + /// The block root of the latest block verified by this peer. + pub block_root: Hash256, + /// The slot of the latest block verified by this peer. + pub slot: u64, +} + +/// Request execution proofs for a slot range from a peer. +/// +/// `proof_filters` is an optional per-block filter that tells the peer which proof types we still +/// need for specific blocks in the range. Blocks not listed in `proof_filters` will have all known +/// proof types returned; blocks listed will only have the specified types returned. This avoids +/// transferring proof types the requester already holds. +/// +/// Matches the `ExecutionProofsByRange` request type in the EIP-8025 p2p spec. +#[derive(Clone, Debug, PartialEq)] +pub struct ExecutionProofsByRangeRequest { + /// The starting slot to request execution proofs. + pub start_slot: u64, + /// The number of slots from the start slot. + pub count: u64, + /// Per-block proof-type filters for blocks in the range where only some proof types are needed. + /// Empty list means "return all proof types for every block in the range." + pub proof_filters: RuntimeVariableList, +} + +impl ExecutionProofsByRangeRequest { + pub fn max_requested(&self) -> u64 { + use typenum::Unsigned; + use types::execution::eip8025::MaxExecutionProofsPerPayload; + self.count + .saturating_mul(MaxExecutionProofsPerPayload::to_u64()) + } + + /// Minimum SSZ encoded byte length: the two fixed `u64` fields plus the 4-byte offset pointer + /// for the variable-length `proof_filters` list. + pub fn ssz_min_len() -> usize { + // start_slot (8) + count (8) + proof_filters offset (4) + 20 + } + + /// Maximum SSZ encoded byte length when `proof_filters` holds up to `max_request_blocks` + /// entries. + /// + /// Each `ProofByRootIdentifier` is a variable-length SSZ container: + /// - `block_root`: 32 bytes (fixed) + /// - `proof_types` offset field: 4 bytes (within the container fixed portion) + /// - `proof_types` content: at most `MAX_EXECUTION_PROOFS_PER_PAYLOAD` × 1 byte = 4 bytes + /// + /// A `List` of `max_request_blocks` variable-length items also requires a 4-byte offset table + /// entry per item. + pub fn ssz_max_len(max_request_blocks: usize) -> usize { + const MAX_PROOF_BY_ROOT_IDENTIFIER_BYTES: usize = 32 + 4 + 4; + Self::ssz_min_len() + max_request_blocks * (4 + MAX_PROOF_BY_ROOT_IDENTIFIER_BYTES) + } + + /// Decode from SSZ bytes, supplying a runtime maximum for the `proof_filters` list length. + pub fn from_ssz_bytes_with_max( + bytes: &[u8], + max_filters: usize, + ) -> Result { + let mut builder = ssz::SszDecoderBuilder::new(bytes); + builder.register_type::()?; + builder.register_type::()?; + builder.register_anonymous_variable_length_item()?; + let mut decoder = builder.build()?; + Ok(Self { + start_slot: decoder.decode_next::()?, + count: decoder.decode_next::()?, + proof_filters: decoder.decode_next_with(|slice| { + RuntimeVariableList::from_ssz_bytes(slice, max_filters) + })?, + }) + } +} + +impl ssz::Encode for ExecutionProofsByRangeRequest { + fn is_ssz_fixed_len() -> bool { + false + } + + fn ssz_append(&self, buf: &mut Vec) { + // Fixed portion: start_slot (8) + count (8) + proof_filters offset (4) = 20 bytes. + let num_fixed_bytes = 8 + 8 + ssz::BYTES_PER_LENGTH_OFFSET; + let mut encoder = ssz::SszEncoder::container(buf, num_fixed_bytes); + encoder.append(&self.start_slot); + encoder.append(&self.count); + encoder.append(&self.proof_filters); + encoder.finalize(); + } + + fn ssz_bytes_len(&self) -> usize { + 8 + 8 + ssz::BYTES_PER_LENGTH_OFFSET + self.proof_filters.ssz_bytes_len() + } +} + +impl std::fmt::Display for ExecutionProofsByRangeRequest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Request: ExecutionProofsByRange: Start Slot: {}, Count: {}, Filters: {}", + self.start_slot, + self.count, + self.proof_filters.len() + ) + } +} + +/// Request execution proofs for specific blocks by root from a peer. +/// +/// `List[ProofByRootIdentifier, MAX_BLOCKS_BY_ROOT]`. +#[derive(Clone, Debug, PartialEq)] +pub struct ExecutionProofsByRootRequest { + /// Each entry identifies a block root and the proof types we currently have for it. + pub identifiers: RuntimeVariableList, +} + +impl ExecutionProofsByRootRequest { + pub fn new( + identifiers: Vec, + max_request_blocks: usize, + ) -> Result { + let identifiers = RuntimeVariableList::new(identifiers, max_request_blocks) + .map_err(|e| format!("ExecutionProofsByRootRequest too many identifiers: {e:?}"))?; + Ok(Self { identifiers }) + } +} + +impl std::fmt::Display for ExecutionProofsByRootRequest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Request: ExecutionProofsByRoot: Number of Requested Identifiers: {}", + self.identifiers.len() + ) + } +} + /* RPC Handling and Grouping */ // Collection of enums and structs used by the Codecs to encode/decode RPC messages @@ -612,11 +754,20 @@ pub enum RpcSuccessResponse { /// A response to a get DATA_COLUMN_SIDECARS_BY_RANGE request. DataColumnsByRange(Arc>), + /// A response to a get EXECUTION_PROOFS_BY_RANGE request. + ExecutionProofsByRange(Arc), + + /// A response to a get EXECUTION_PROOFS_BY_ROOT request. + ExecutionProofsByRoot(Arc), + /// A PONG response to a PING request. Pong(Ping), /// A response to a META_DATA request. MetaData(Arc>), + + /// A response to an EXECUTION_PROOF_STATUS request. + ExecutionProofStatus(ExecutionProofStatus), } /// Indicates which response is being terminated by a stream termination response. @@ -642,6 +793,12 @@ pub enum ResponseTermination { /// Light client updates by range stream termination. LightClientUpdatesByRange, + + /// Execution proofs by range stream termination. + ExecutionProofsByRange, + + /// Execution proofs by root stream termination. + ExecutionProofsByRoot, } impl ResponseTermination { @@ -654,6 +811,8 @@ impl ResponseTermination { ResponseTermination::DataColumnsByRoot => Protocol::DataColumnsByRoot, ResponseTermination::DataColumnsByRange => Protocol::DataColumnsByRange, ResponseTermination::LightClientUpdatesByRange => Protocol::LightClientUpdatesByRange, + ResponseTermination::ExecutionProofsByRange => Protocol::ExecutionProofsByRange, + ResponseTermination::ExecutionProofsByRoot => Protocol::ExecutionProofsByRoot, } } } @@ -756,6 +915,9 @@ impl RpcSuccessResponse { } RpcSuccessResponse::LightClientFinalityUpdate(_) => Protocol::LightClientFinalityUpdate, RpcSuccessResponse::LightClientUpdatesByRange(_) => Protocol::LightClientUpdatesByRange, + RpcSuccessResponse::ExecutionProofsByRange(_) => Protocol::ExecutionProofsByRange, + RpcSuccessResponse::ExecutionProofsByRoot(_) => Protocol::ExecutionProofsByRoot, + RpcSuccessResponse::ExecutionProofStatus(_) => Protocol::ExecutionProofStatus, } } @@ -772,7 +934,12 @@ impl RpcSuccessResponse { Self::LightClientFinalityUpdate(r) => Some(r.get_attested_header_slot()), Self::LightClientOptimisticUpdate(r) => Some(r.get_slot()), Self::LightClientUpdatesByRange(r) => Some(r.attested_header_slot()), - Self::MetaData(_) | Self::Status(_) | Self::Pong(_) => None, + Self::MetaData(_) + | Self::Status(_) + | Self::Pong(_) + | Self::ExecutionProofsByRange(_) + | Self::ExecutionProofsByRoot(_) + | Self::ExecutionProofStatus(_) => None, } } } @@ -860,6 +1027,23 @@ impl std::fmt::Display for RpcSuccessResponse { update.signature_slot(), ) } + RpcSuccessResponse::ExecutionProofsByRange(proof) => { + write!( + f, + "ExecutionProofsByRange: validator_index: {}", + proof.validator_index + ) + } + RpcSuccessResponse::ExecutionProofsByRoot(proof) => { + write!( + f, + "ExecutionProofsByRoot: validator_index: {}", + proof.validator_index + ) + } + RpcSuccessResponse::ExecutionProofStatus(s) => { + write!(f, "ExecutionProofStatus: slot={}", s.slot) + } } } } diff --git a/beacon_node/lighthouse_network/src/rpc/mod.rs b/beacon_node/lighthouse_network/src/rpc/mod.rs index 7c43018af83..6c7b1e2d781 100644 --- a/beacon_node/lighthouse_network/src/rpc/mod.rs +++ b/beacon_node/lighthouse_network/src/rpc/mod.rs @@ -155,6 +155,7 @@ pub struct RPC { events: Vec>, fork_context: Arc, enable_light_client_server: bool, + enable_execution_proof: bool, /// A sequential counter indicating when data gets modified. seq_number: u64, } @@ -163,6 +164,7 @@ impl RPC { pub fn new( fork_context: Arc, enable_light_client_server: bool, + enable_execution_proof: bool, inbound_rate_limiter_config: Option, outbound_rate_limiter_config: Option, seq_number: u64, @@ -184,6 +186,7 @@ impl RPC { events: Vec::new(), fork_context, enable_light_client_server, + enable_execution_proof, seq_number, } } @@ -319,6 +322,7 @@ where fork_context: self.fork_context.clone(), max_rpc_size: self.fork_context.spec.max_payload_size as usize, enable_light_client_server: self.enable_light_client_server, + enable_execution_proof: self.enable_execution_proof, phantom: PhantomData, }, (), @@ -342,6 +346,7 @@ where fork_context: self.fork_context.clone(), max_rpc_size: self.fork_context.spec.max_payload_size as usize, enable_light_client_server: self.enable_light_client_server, + enable_execution_proof: self.enable_execution_proof, phantom: PhantomData, }, (), diff --git a/beacon_node/lighthouse_network/src/rpc/protocol.rs b/beacon_node/lighthouse_network/src/rpc/protocol.rs index 366515d42f6..d5435b1721c 100644 --- a/beacon_node/lighthouse_network/src/rpc/protocol.rs +++ b/beacon_node/lighthouse_network/src/rpc/protocol.rs @@ -16,6 +16,8 @@ use tokio_util::{ codec::Framed, compat::{Compat, FuturesAsyncReadCompatExt}, }; +use typenum::Unsigned; +use types::execution::eip8025::MaxExecutionProofsPerPayload; use types::{ BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BlobSidecar, ChainSpec, DataColumnSidecar, EmptyBlock, Epoch, EthSpec, EthSpecId, ForkContext, ForkName, LightClientBootstrap, @@ -118,6 +120,19 @@ pub static LIGHT_CLIENT_UPDATES_BY_RANGE_DENEB_MAX: LazyLock = pub static LIGHT_CLIENT_UPDATES_BY_RANGE_ELECTRA_MAX: LazyLock = LazyLock::new(|| LightClientUpdate::::ssz_max_len_for_fork(ForkName::Electra)); +/// Minimum SSZ size of a `SignedExecutionProof` (empty proof_data): +/// - message offset: 4 bytes +/// - validator_index: 8 bytes +/// - signature: 96 bytes +/// - ExecutionProof fixed header (proof_data offset + proof_type + public_input): 4 + 1 + 32 = 37 +pub const SIGNED_EXECUTION_PROOF_MIN_SIZE: usize = 4 + 8 + 96 + 37; + +/// Maximum SSZ size of a `SignedExecutionProof` (MaxProofSize = 307200 bytes): +/// - SignedExecutionProof fixed header: 4 + 8 + 96 = 108 bytes +/// - ExecutionProof fixed header: 4 + 1 + 32 = 37 bytes +/// - proof_data: MaxProofSize = 75 * 4096 = 307200 bytes +pub const SIGNED_EXECUTION_PROOF_MAX_SIZE: usize = 4 + 8 + 96 + 37 + 307200; + /// The protocol prefix the RPC protocol id. const PROTOCOL_PREFIX: &str = "/eth2/beacon_chain/req"; /// The number of seconds to wait for the first bytes of a request once a protocol has been @@ -267,6 +282,15 @@ pub enum Protocol { /// The `LightClientUpdatesByRange` protocol name #[strum(serialize = "light_client_updates_by_range")] LightClientUpdatesByRange, + /// The `ExecutionProofsByRange` protocol name. + #[strum(serialize = "execution_proofs_by_range")] + ExecutionProofsByRange, + /// The `ExecutionProofsByRoot` protocol name. + #[strum(serialize = "execution_proofs_by_root")] + ExecutionProofsByRoot, + /// The `ExecutionProofStatus` protocol name. + #[strum(serialize = "execution_proof_status")] + ExecutionProofStatus, } impl Protocol { @@ -286,6 +310,9 @@ impl Protocol { Protocol::LightClientOptimisticUpdate => None, Protocol::LightClientFinalityUpdate => None, Protocol::LightClientUpdatesByRange => None, + Protocol::ExecutionProofsByRange => Some(ResponseTermination::ExecutionProofsByRange), + Protocol::ExecutionProofsByRoot => Some(ResponseTermination::ExecutionProofsByRoot), + Protocol::ExecutionProofStatus => None, } } } @@ -318,6 +345,9 @@ pub enum SupportedProtocol { LightClientOptimisticUpdateV1, LightClientFinalityUpdateV1, LightClientUpdatesByRangeV1, + ExecutionProofsByRangeV1, + ExecutionProofsByRootV1, + ExecutionProofStatusV1, } impl SupportedProtocol { @@ -342,6 +372,9 @@ impl SupportedProtocol { SupportedProtocol::LightClientOptimisticUpdateV1 => "1", SupportedProtocol::LightClientFinalityUpdateV1 => "1", SupportedProtocol::LightClientUpdatesByRangeV1 => "1", + SupportedProtocol::ExecutionProofsByRangeV1 => "1", + SupportedProtocol::ExecutionProofsByRootV1 => "1", + SupportedProtocol::ExecutionProofStatusV1 => "1", } } @@ -368,6 +401,9 @@ impl SupportedProtocol { } SupportedProtocol::LightClientFinalityUpdateV1 => Protocol::LightClientFinalityUpdate, SupportedProtocol::LightClientUpdatesByRangeV1 => Protocol::LightClientUpdatesByRange, + SupportedProtocol::ExecutionProofsByRangeV1 => Protocol::ExecutionProofsByRange, + SupportedProtocol::ExecutionProofsByRootV1 => Protocol::ExecutionProofsByRoot, + SupportedProtocol::ExecutionProofStatusV1 => Protocol::ExecutionProofStatus, } } @@ -426,6 +462,7 @@ pub struct RPCProtocol { pub fork_context: Arc, pub max_rpc_size: usize, pub enable_light_client_server: bool, + pub enable_execution_proof: bool, pub phantom: PhantomData, } @@ -450,6 +487,20 @@ impl UpgradeInfo for RPCProtocol { Encoding::SSZSnappy, )); } + if self.enable_execution_proof { + supported_protocols.push(ProtocolId::new( + SupportedProtocol::ExecutionProofsByRangeV1, + Encoding::SSZSnappy, + )); + supported_protocols.push(ProtocolId::new( + SupportedProtocol::ExecutionProofsByRootV1, + Encoding::SSZSnappy, + )); + supported_protocols.push(ProtocolId::new( + SupportedProtocol::ExecutionProofStatusV1, + Encoding::SSZSnappy, + )); + } supported_protocols } } @@ -534,7 +585,18 @@ impl ProtocolId { LightClientUpdatesByRangeRequest::ssz_min_len(), LightClientUpdatesByRangeRequest::ssz_max_len(), ), - Protocol::MetaData => RpcLimits::new(0, 0), // Metadata requests are empty + Protocol::MetaData => RpcLimits::new(0, 0), + Protocol::ExecutionProofsByRange => RpcLimits::new( + ExecutionProofsByRangeRequest::ssz_min_len(), + ExecutionProofsByRangeRequest::ssz_max_len(spec.max_request_blocks_upper_bound()), + ), + // ExecutionProofsByRoot request is List[ProofByRootIdentifier, MAX_BLOCKS_BY_ROOT. + Protocol::ExecutionProofsByRoot => RpcLimits::new(0, spec.max_blocks_by_root_request), + // ExecutionProofStatus request carries the local node's status. + Protocol::ExecutionProofStatus => RpcLimits::new( + ExecutionProofStatus::ssz_fixed_len(), + ExecutionProofStatus::ssz_fixed_len(), + ), } } @@ -576,6 +638,15 @@ impl ProtocolId { Protocol::LightClientUpdatesByRange => { rpc_light_client_updates_by_range_limits_by_fork(fork_context.current_fork_name()) } + Protocol::ExecutionProofsByRange | Protocol::ExecutionProofsByRoot => RpcLimits::new( + SIGNED_EXECUTION_PROOF_MIN_SIZE, + SIGNED_EXECUTION_PROOF_MAX_SIZE, + ), + // ExecutionProofStatus response is fixed-size SSZ. + Protocol::ExecutionProofStatus => RpcLimits::new( + ExecutionProofStatus::ssz_fixed_len(), + ExecutionProofStatus::ssz_fixed_len(), + ), } } @@ -601,7 +672,11 @@ impl ProtocolId { | SupportedProtocol::MetaDataV1 | SupportedProtocol::MetaDataV2 | SupportedProtocol::MetaDataV3 - | SupportedProtocol::GoodbyeV1 => false, + | SupportedProtocol::GoodbyeV1 + // Execution proof types are not fork-dependent, no context bytes needed. + | SupportedProtocol::ExecutionProofsByRangeV1 + | SupportedProtocol::ExecutionProofsByRootV1 + | SupportedProtocol::ExecutionProofStatusV1 => false, } } } @@ -696,6 +771,7 @@ where SupportedProtocol::LightClientFinalityUpdateV1 => { Ok((RequestType::LightClientFinalityUpdate, socket)) } + // ExecutionProofStatus now carries a 40-byte body; fall through to normal decoder. _ => { match tokio::time::timeout( Duration::from_secs(REQUEST_TIMEOUT), @@ -729,6 +805,9 @@ pub enum RequestType { LightClientOptimisticUpdate, LightClientFinalityUpdate, LightClientUpdatesByRange(LightClientUpdatesByRangeRequest), + ExecutionProofsByRange(ExecutionProofsByRangeRequest), + ExecutionProofsByRoot(ExecutionProofsByRootRequest), + ExecutionProofStatus(ExecutionProofStatus), Ping(Ping), MetaData(MetadataRequest), } @@ -754,6 +833,10 @@ impl RequestType { RequestType::LightClientOptimisticUpdate => 1, RequestType::LightClientFinalityUpdate => 1, RequestType::LightClientUpdatesByRange(req) => req.count, + RequestType::ExecutionProofsByRange(req) => req.max_requested(), + RequestType::ExecutionProofsByRoot(req) => (req.identifiers.len() as u64) + .saturating_mul(MaxExecutionProofsPerPayload::to_u64()), + RequestType::ExecutionProofStatus(_) => 1, } } @@ -793,6 +876,9 @@ impl RequestType { RequestType::LightClientUpdatesByRange(_) => { SupportedProtocol::LightClientUpdatesByRangeV1 } + RequestType::ExecutionProofsByRange(_) => SupportedProtocol::ExecutionProofsByRangeV1, + RequestType::ExecutionProofsByRoot(_) => SupportedProtocol::ExecutionProofsByRootV1, + RequestType::ExecutionProofStatus(_) => SupportedProtocol::ExecutionProofStatusV1, } } @@ -808,6 +894,8 @@ impl RequestType { RequestType::BlobsByRoot(_) => ResponseTermination::BlobsByRoot, RequestType::DataColumnsByRoot(_) => ResponseTermination::DataColumnsByRoot, RequestType::DataColumnsByRange(_) => ResponseTermination::DataColumnsByRange, + RequestType::ExecutionProofsByRange(_) => ResponseTermination::ExecutionProofsByRange, + RequestType::ExecutionProofsByRoot(_) => ResponseTermination::ExecutionProofsByRoot, RequestType::Status(_) => unreachable!(), RequestType::Goodbye(_) => unreachable!(), RequestType::Ping(_) => unreachable!(), @@ -816,6 +904,7 @@ impl RequestType { RequestType::LightClientFinalityUpdate => unreachable!(), RequestType::LightClientOptimisticUpdate => unreachable!(), RequestType::LightClientUpdatesByRange(_) => unreachable!(), + RequestType::ExecutionProofStatus(_) => unreachable!(), } } @@ -879,6 +968,18 @@ impl RequestType { SupportedProtocol::LightClientUpdatesByRangeV1, Encoding::SSZSnappy, )], + RequestType::ExecutionProofsByRange(_) => vec![ProtocolId::new( + SupportedProtocol::ExecutionProofsByRangeV1, + Encoding::SSZSnappy, + )], + RequestType::ExecutionProofsByRoot(_) => vec![ProtocolId::new( + SupportedProtocol::ExecutionProofsByRootV1, + Encoding::SSZSnappy, + )], + RequestType::ExecutionProofStatus(_) => vec![ProtocolId::new( + SupportedProtocol::ExecutionProofStatusV1, + Encoding::SSZSnappy, + )], } } @@ -898,6 +999,9 @@ impl RequestType { RequestType::LightClientOptimisticUpdate => true, RequestType::LightClientFinalityUpdate => true, RequestType::LightClientUpdatesByRange(_) => true, + RequestType::ExecutionProofsByRange(_) => false, + RequestType::ExecutionProofsByRoot(_) => false, + RequestType::ExecutionProofStatus(_) => true, } } } @@ -1019,6 +1123,11 @@ impl std::fmt::Display for RequestType { RequestType::LightClientUpdatesByRange(_) => { write!(f, "Light client updates by range request") } + RequestType::ExecutionProofsByRange(req) => write!(f, "{}", req), + RequestType::ExecutionProofsByRoot(req) => write!(f, "{}", req), + RequestType::ExecutionProofStatus(s) => { + write!(f, "ExecutionProofStatus(slot={})", s.slot) + } } } } diff --git a/beacon_node/lighthouse_network/src/rpc/rate_limiter.rs b/beacon_node/lighthouse_network/src/rpc/rate_limiter.rs index 8b364f506cc..84f57fcdffe 100644 --- a/beacon_node/lighthouse_network/src/rpc/rate_limiter.rs +++ b/beacon_node/lighthouse_network/src/rpc/rate_limiter.rs @@ -113,6 +113,12 @@ pub struct RPCRateLimiter { lc_finality_update_rl: Limiter, /// LightClientUpdatesByRange rate limiter. lc_updates_by_range_rl: Limiter, + /// ExecutionProofsByRange rate limiter. + ep_by_range_rl: Limiter, + /// ExecutionProofsByRoot rate limiter. + ep_by_root_rl: Limiter, + /// ExecutionProofStatus rate limiter. + ep_status_rl: Limiter, fork_context: Arc, } @@ -156,6 +162,12 @@ pub struct RPCRateLimiterBuilder { lc_finality_update_quota: Option, /// Quota for the LightClientUpdatesByRange protocol. lc_updates_by_range_quota: Option, + /// Quota for the ExecutionProofsByRange protocol. + ep_by_range_quota: Option, + /// Quota for the ExecutionProofsByRoot protocol. + ep_by_root_quota: Option, + /// Quota for the ExecutionProofStatus protocol. + ep_status_quota: Option, } impl RPCRateLimiterBuilder { @@ -177,6 +189,9 @@ impl RPCRateLimiterBuilder { Protocol::LightClientOptimisticUpdate => self.lc_optimistic_update_quota = q, Protocol::LightClientFinalityUpdate => self.lc_finality_update_quota = q, Protocol::LightClientUpdatesByRange => self.lc_updates_by_range_quota = q, + Protocol::ExecutionProofsByRange => self.ep_by_range_quota = q, + Protocol::ExecutionProofsByRoot => self.ep_by_root_quota = q, + Protocol::ExecutionProofStatus => self.ep_status_quota = q, } self } @@ -221,6 +236,18 @@ impl RPCRateLimiterBuilder { .dcbrange_quota .ok_or("DataColumnsByRange quota not specified")?; + let ep_by_range_quota = self + .ep_by_range_quota + .ok_or("ExecutionProofsByRange quota not specified")?; + + let ep_by_root_quota = self + .ep_by_root_quota + .ok_or("ExecutionProofsByRoot quota not specified")?; + + let ep_status_quota = self + .ep_status_quota + .ok_or("ExecutionProofStatus quota not specified")?; + // create the rate limiters let ping_rl = Limiter::from_quota(ping_quota)?; let metadata_rl = Limiter::from_quota(metadata_quota)?; @@ -236,6 +263,9 @@ impl RPCRateLimiterBuilder { let lc_optimistic_update_rl = Limiter::from_quota(lc_optimistic_update_quota)?; let lc_finality_update_rl = Limiter::from_quota(lc_finality_update_quota)?; let lc_updates_by_range_rl = Limiter::from_quota(lc_updates_by_range_quota)?; + let ep_by_range_rl = Limiter::from_quota(ep_by_range_quota)?; + let ep_by_root_rl = Limiter::from_quota(ep_by_root_quota)?; + let ep_status_rl = Limiter::from_quota(ep_status_quota)?; // check for peers to prune every 30 seconds, starting in 30 seconds let prune_every = tokio::time::Duration::from_secs(30); @@ -259,6 +289,9 @@ impl RPCRateLimiterBuilder { lc_optimistic_update_rl, lc_finality_update_rl, lc_updates_by_range_rl, + ep_by_range_rl, + ep_by_root_rl, + ep_status_rl, init_time: Instant::now(), fork_context, }) @@ -312,6 +345,9 @@ impl RPCRateLimiter { light_client_optimistic_update_quota, light_client_finality_update_quota, light_client_updates_by_range_quota, + execution_proofs_by_range_quota, + execution_proofs_by_root_quota, + execution_proof_status_quota, } = config; Self::builder() @@ -338,6 +374,15 @@ impl RPCRateLimiter { Protocol::LightClientUpdatesByRange, light_client_updates_by_range_quota, ) + .set_quota( + Protocol::ExecutionProofsByRange, + execution_proofs_by_range_quota, + ) + .set_quota( + Protocol::ExecutionProofsByRoot, + execution_proofs_by_root_quota, + ) + .set_quota(Protocol::ExecutionProofStatus, execution_proof_status_quota) .build(fork_context) } @@ -376,6 +421,9 @@ impl RPCRateLimiter { Protocol::LightClientOptimisticUpdate => &mut self.lc_optimistic_update_rl, Protocol::LightClientFinalityUpdate => &mut self.lc_finality_update_rl, Protocol::LightClientUpdatesByRange => &mut self.lc_updates_by_range_rl, + Protocol::ExecutionProofsByRange => &mut self.ep_by_range_rl, + Protocol::ExecutionProofsByRoot => &mut self.ep_by_root_rl, + Protocol::ExecutionProofStatus => &mut self.ep_status_rl, }; check(limiter) } @@ -400,6 +448,9 @@ impl RPCRateLimiter { lc_optimistic_update_rl, lc_finality_update_rl, lc_updates_by_range_rl, + ep_by_range_rl, + ep_by_root_rl, + ep_status_rl, fork_context: _, } = self; @@ -417,6 +468,9 @@ impl RPCRateLimiter { lc_optimistic_update_rl.prune(time_since_start); lc_finality_update_rl.prune(time_since_start); lc_updates_by_range_rl.prune(time_since_start); + ep_by_range_rl.prune(time_since_start); + ep_by_root_rl.prune(time_since_start); + ep_status_rl.prune(time_since_start); } } diff --git a/beacon_node/lighthouse_network/src/service/api_types.rs b/beacon_node/lighthouse_network/src/service/api_types.rs index f1a4d87de76..e2babc31f6e 100644 --- a/beacon_node/lighthouse_network/src/service/api_types.rs +++ b/beacon_node/lighthouse_network/src/service/api_types.rs @@ -1,10 +1,13 @@ -use crate::rpc::methods::{ResponseTermination, RpcResponse, RpcSuccessResponse, StatusMessage}; +use crate::rpc::methods::{ + ExecutionProofStatus, ResponseTermination, RpcResponse, RpcSuccessResponse, StatusMessage, +}; use libp2p::PeerId; use std::fmt::{Display, Formatter}; use std::sync::Arc; use types::{ BlobSidecar, DataColumnSidecar, Epoch, EthSpec, LightClientBootstrap, LightClientFinalityUpdate, LightClientOptimisticUpdate, LightClientUpdate, SignedBeaconBlock, + execution::eip8025::SignedExecutionProof, }; pub type Id = u32; @@ -30,6 +33,12 @@ pub enum SyncRequestId { BlobsByRange(BlobsByRangeRequestId), /// Data columns by range request DataColumnsByRange(DataColumnsByRangeRequestId), + /// Execution proofs by range request + ExecutionProofsByRange(ExecutionProofsByRangeRequestId), + /// Execution proofs by root request + ExecutionProofsByRoot(ExecutionProofsByRootRequestId), + /// Execution proof status request + ExecutionProofStatus(ExecutionProofStatusRequestId), } /// Request ID for data_columns_by_root requests. Block lookups do not issue this request directly. @@ -75,6 +84,24 @@ pub enum DataColumnsByRangeRequester { CustodyBackfillSync(CustodyBackFillBatchRequestId), } +/// Request ID for execution_proofs_by_range requests. +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] +pub struct ExecutionProofsByRangeRequestId { + pub id: Id, +} + +/// Request ID for execution_proofs_by_root requests. +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] +pub struct ExecutionProofsByRootRequestId { + pub id: Id, +} + +/// Request ID for execution_proof_status requests. +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] +pub struct ExecutionProofStatusRequestId { + pub id: Id, +} + /// Block components by range request for range sync. Includes an ID for downstream consumers to /// handle retries and tie all their sub requests together. #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] @@ -172,6 +199,12 @@ pub enum Response { LightClientFinalityUpdate(Arc>), /// A response to a LightClientUpdatesByRange request. LightClientUpdatesByRange(Option>>), + /// A response to a get EXECUTION_PROOFS_BY_RANGE request. A None response signals end of batch. + ExecutionProofsByRange(Option>), + /// A response to a get EXECUTION_PROOFS_BY_ROOT request. A None response signals end of batch. + ExecutionProofsByRoot(Option>), + /// A response to an EXECUTION_PROOF_STATUS request. + ExecutionProofStatus(ExecutionProofStatus), } impl std::convert::From> for RpcResponse { @@ -217,6 +250,17 @@ impl std::convert::From> for RpcResponse { RpcResponse::StreamTermination(ResponseTermination::LightClientUpdatesByRange) } }, + Response::ExecutionProofsByRange(r) => match r { + Some(p) => RpcResponse::Success(RpcSuccessResponse::ExecutionProofsByRange(p)), + None => RpcResponse::StreamTermination(ResponseTermination::ExecutionProofsByRange), + }, + Response::ExecutionProofsByRoot(r) => match r { + Some(p) => RpcResponse::Success(RpcSuccessResponse::ExecutionProofsByRoot(p)), + None => RpcResponse::StreamTermination(ResponseTermination::ExecutionProofsByRoot), + }, + Response::ExecutionProofStatus(s) => { + RpcResponse::Success(RpcSuccessResponse::ExecutionProofStatus(s)) + } } } } @@ -234,6 +278,9 @@ macro_rules! impl_display { // Since each request Id is deeply nested with various types, if rendered with Debug on logs they // take too much visual space. This custom Display implementations make the overall Id short while // not losing information +impl_display!(ExecutionProofsByRangeRequestId, "ExecProofsByRange/{}", id); +impl_display!(ExecutionProofsByRootRequestId, "ExecProofsByRoot/{}", id); +impl_display!(ExecutionProofStatusRequestId, "ExecProofStatus/{}", id); impl_display!(BlocksByRangeRequestId, "{}/{}", id, parent_request_id); impl_display!(BlobsByRangeRequestId, "{}/{}", id, parent_request_id); impl_display!(DataColumnsByRangeRequestId, "{}/{}", id, parent_request_id); diff --git a/beacon_node/lighthouse_network/src/service/gossip_cache.rs b/beacon_node/lighthouse_network/src/service/gossip_cache.rs index 120b9e6c245..5b31823d2d2 100644 --- a/beacon_node/lighthouse_network/src/service/gossip_cache.rs +++ b/beacon_node/lighthouse_network/src/service/gossip_cache.rs @@ -44,6 +44,8 @@ pub struct GossipCache { light_client_finality_update: Option, /// Timeout for light client optimistic updates. light_client_optimistic_update: Option, + /// EIP-8025: Timeout for execution proofs. + execution_proof: Option, } #[derive(Default)] @@ -75,6 +77,8 @@ pub struct GossipCacheBuilder { light_client_finality_update: Option, /// Timeout for light client optimistic updates. light_client_optimistic_update: Option, + /// EIP-8025: Timeout for execution proofs. + execution_proof: Option, } #[allow(dead_code)] @@ -167,6 +171,7 @@ impl GossipCacheBuilder { bls_to_execution_change, light_client_finality_update, light_client_optimistic_update, + execution_proof, } = self; GossipCache { expirations: DelayQueue::default(), @@ -184,6 +189,7 @@ impl GossipCacheBuilder { bls_to_execution_change: bls_to_execution_change.or(default_timeout), light_client_finality_update: light_client_finality_update.or(default_timeout), light_client_optimistic_update: light_client_optimistic_update.or(default_timeout), + execution_proof: execution_proof.or(default_timeout), } } } @@ -211,6 +217,7 @@ impl GossipCache { GossipKind::BlsToExecutionChange => self.bls_to_execution_change, GossipKind::LightClientFinalityUpdate => self.light_client_finality_update, GossipKind::LightClientOptimisticUpdate => self.light_client_optimistic_update, + GossipKind::ExecutionProof => self.execution_proof, }; let Some(expire_timeout) = expire_timeout else { return; diff --git a/beacon_node/lighthouse_network/src/service/mod.rs b/beacon_node/lighthouse_network/src/service/mod.rs index 4eebda1decb..90550884f04 100644 --- a/beacon_node/lighthouse_network/src/service/mod.rs +++ b/beacon_node/lighthouse_network/src/service/mod.rs @@ -9,7 +9,7 @@ use crate::peer_manager::{ peerdb::score::PeerAction, peerdb::score::ReportSource, }; use crate::peer_manager::{MIN_OUTBOUND_ONLY_FACTOR, PEER_EXCESS_FACTOR, PRIORITY_PEER_EXCESS}; -use crate::rpc::methods::MetadataRequest; +use crate::rpc::methods::{ExecutionProofStatus, MetadataRequest}; use crate::rpc::{ GoodbyeReason, HandlerErr, InboundRequestId, Protocol, RPC, RPCError, RPCMessage, RPCReceived, RequestType, ResponseTermination, RpcResponse, RpcSuccessResponse, @@ -105,6 +105,11 @@ pub enum NetworkEvent { ZeroListeners, /// A peer has an updated custody group count from MetaData. PeerUpdatedCustodyGroupCount(PeerId), + /// A peer sent us their `ExecutionProofStatus` in the body of an inbound request. + PeerExecutionProofStatus { + peer_id: PeerId, + status: ExecutionProofStatus, + }, } pub type Gossipsub = gossipsub::Behaviour; @@ -297,13 +302,17 @@ impl Network { let all_topics_for_digests = current_and_future_digests .map(|(epoch, digest)| { let fork = ctx.chain_spec.fork_name_at_epoch(epoch); - all_topics_at_fork::(fork, &ctx.chain_spec) - .into_iter() - .map(|topic| { - Topic::new(GossipTopic::new(topic, GossipEncoding::default(), digest)) - .into() - }) - .collect::>() + all_topics_at_fork::( + fork, + &ctx.chain_spec, + network_globals.execution_proof(), + ) + .into_iter() + .map(|topic| { + Topic::new(GossipTopic::new(topic, GossipEncoding::default(), digest)) + .into() + }) + .collect::>() }) .collect::>(); @@ -370,6 +379,7 @@ impl Network { let eth2_rpc = RPC::new( ctx.fork_context.clone(), config.enable_light_client_server, + config.enable_execution_proof, config.inbound_rate_limiter_config.clone(), config.outbound_rate_limiter_config.clone(), seq_number, @@ -1607,6 +1617,42 @@ impl Network { request_type, }) } + RequestType::ExecutionProofsByRange(_) => { + metrics::inc_counter_vec( + &metrics::TOTAL_RPC_REQUESTS, + &["execution_proofs_by_range"], + ); + Some(NetworkEvent::RequestReceived { + peer_id, + inbound_request_id, + request_type, + }) + } + RequestType::ExecutionProofsByRoot(_) => { + metrics::inc_counter_vec( + &metrics::TOTAL_RPC_REQUESTS, + &["execution_proofs_by_root"], + ); + Some(NetworkEvent::RequestReceived { + peer_id, + inbound_request_id, + request_type, + }) + } + RequestType::ExecutionProofStatus(peer_status) => { + // Respond immediately with our local status. + let local_status = + *self.network_globals.local_execution_proof_status.read(); + let response = RpcResponse::Success( + RpcSuccessResponse::ExecutionProofStatus(local_status), + ); + self.send_response(peer_id, inbound_request_id, response); + // Route peer's status to sync layer so it populates the ProofSync cache. + Some(NetworkEvent::PeerExecutionProofStatus { + peer_id, + status: peer_status, + }) + } } } Ok(RPCReceived::Response(id, resp)) => { @@ -1667,6 +1713,19 @@ impl Network { peer_id, Response::LightClientUpdatesByRange(Some(update)), ), + RpcSuccessResponse::ExecutionProofsByRange(proof) => self.build_response( + id, + peer_id, + Response::ExecutionProofsByRange(Some(proof)), + ), + RpcSuccessResponse::ExecutionProofsByRoot(proof) => self.build_response( + id, + peer_id, + Response::ExecutionProofsByRoot(Some(proof)), + ), + RpcSuccessResponse::ExecutionProofStatus(status) => { + self.build_response(id, peer_id, Response::ExecutionProofStatus(status)) + } } } Ok(RPCReceived::EndOfStream(id, termination)) => { @@ -1680,6 +1739,12 @@ impl Network { ResponseTermination::LightClientUpdatesByRange => { Response::LightClientUpdatesByRange(None) } + ResponseTermination::ExecutionProofsByRange => { + Response::ExecutionProofsByRange(None) + } + ResponseTermination::ExecutionProofsByRoot => { + Response::ExecutionProofsByRoot(None) + } }; self.build_response(id, peer_id, response) } diff --git a/beacon_node/lighthouse_network/src/service/utils.rs b/beacon_node/lighthouse_network/src/service/utils.rs index 63f22be5e2c..c6487b05e7d 100644 --- a/beacon_node/lighthouse_network/src/service/utils.rs +++ b/beacon_node/lighthouse_network/src/service/utils.rs @@ -275,6 +275,7 @@ pub(crate) fn create_whitelist_filter( add(BlsToExecutionChange); add(LightClientFinalityUpdate); add(LightClientOptimisticUpdate); + add(ExecutionProof); for id in 0..spec.attestation_subnet_count { add(Attestation(SubnetId::new(id))); } diff --git a/beacon_node/lighthouse_network/src/types/globals.rs b/beacon_node/lighthouse_network/src/types/globals.rs index df8dbdc559e..681c6bcc642 100644 --- a/beacon_node/lighthouse_network/src/types/globals.rs +++ b/beacon_node/lighthouse_network/src/types/globals.rs @@ -1,7 +1,7 @@ //! A collection of variables that are accessible outside of the network thread itself. use super::TopicConfig; use crate::peer_manager::peerdb::PeerDB; -use crate::rpc::{MetaData, MetaDataV3}; +use crate::rpc::{MetaData, MetaDataV3, methods::ExecutionProofStatus}; use crate::types::{BackFillState, SyncState}; use crate::{Client, Enr, GossipTopic, Multiaddr, NetworkConfig, PeerId}; use eth2::lighthouse::sync_state::CustodyBackFillState; @@ -24,6 +24,13 @@ pub struct NetworkGlobals { pub peers: RwLock>, // The local meta data of our node. pub local_metadata: RwLock>, + /// The local execution proof status of our node. + /// + /// Updated via `set_local_execution_proof_status` whenever the beacon chain + /// successfully verifies an execution proof (see `verify_execution_proof` in + /// `beacon_chain.rs`). Sent to peers during `ExecutionProofStatus` RPC exchanges + /// so they can use our status for peer selection. + pub local_execution_proof_status: RwLock, /// The current gossipsub topic subscriptions. pub gossipsub_subscriptions: RwLock>, /// The current sync status of the node. @@ -90,6 +97,7 @@ impl NetworkGlobals { peer_id: RwLock::new(enr.peer_id()), listen_multiaddrs: RwLock::new(Vec::new()), local_metadata: RwLock::new(local_metadata), + local_execution_proof_status: RwLock::new(ExecutionProofStatus::default()), peers: RwLock::new(PeerDB::new(trusted_peers, disable_peer_scoring)), gossipsub_subscriptions: RwLock::new(HashSet::new()), sync_state: RwLock::new(SyncState::Stalled), @@ -186,6 +194,16 @@ impl NetworkGlobals { self.peers.read().trusted_peers() } + /// Returns the local execution proof status. + pub fn local_execution_proof_status(&self) -> ExecutionProofStatus { + *self.local_execution_proof_status.read() + } + + /// Updates the local execution proof status. + pub fn set_local_execution_proof_status(&self, status: ExecutionProofStatus) { + *self.local_execution_proof_status.write() = status; + } + /// Updates the syncing state of the node. /// /// The old state is returned @@ -228,6 +246,7 @@ impl NetworkGlobals { enable_light_client_server: self.config.enable_light_client_server, subscribe_all_subnets: self.config.subscribe_all_subnets, sampling_subnets: self.sampling_subnets.read().clone(), + execution_proof: self.config.enable_execution_proof, } } @@ -262,6 +281,10 @@ impl NetworkGlobals { let enr = discv5::enr::Enr::builder().build(&enr_key).unwrap(); NetworkGlobals::new(enr, metadata, trusted_peers, false, config, spec) } + + pub fn execution_proof(&self) -> bool { + self.config.enable_execution_proof + } } #[cfg(test)] diff --git a/beacon_node/lighthouse_network/src/types/pubsub.rs b/beacon_node/lighthouse_network/src/types/pubsub.rs index 72f2873def9..1840433f38f 100644 --- a/beacon_node/lighthouse_network/src/types/pubsub.rs +++ b/beacon_node/lighthouse_network/src/types/pubsub.rs @@ -14,8 +14,8 @@ use types::{ SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockBellatrix, SignedBeaconBlockCapella, SignedBeaconBlockDeneb, SignedBeaconBlockElectra, SignedBeaconBlockFulu, SignedBeaconBlockGloas, SignedBlsToExecutionChange, - SignedContributionAndProof, SignedVoluntaryExit, SingleAttestation, SubnetId, - SyncCommitteeMessage, SyncSubnetId, + SignedContributionAndProof, SignedExecutionProof, SignedVoluntaryExit, SingleAttestation, + SubnetId, SyncCommitteeMessage, SyncSubnetId, }; #[derive(Debug, Clone, PartialEq)] @@ -46,6 +46,8 @@ pub enum PubsubMessage { LightClientFinalityUpdate(Box>), /// Gossipsub message providing notification of a light client optimistic update. LightClientOptimisticUpdate(Box>), + /// EIP-8025: Gossipsub message providing notification of a signed execution proof. + ExecutionProof(Arc), } // Implements the `DataTransform` trait of gossipsub to employ snappy compression @@ -149,6 +151,7 @@ impl PubsubMessage { PubsubMessage::LightClientOptimisticUpdate(_) => { GossipKind::LightClientOptimisticUpdate } + PubsubMessage::ExecutionProof(_) => GossipKind::ExecutionProof, } } @@ -387,6 +390,14 @@ impl PubsubMessage { light_client_optimistic_update, ))) } + GossipKind::ExecutionProof => { + // EIP-8025: Nodes only subscribe to this topic when a proof engine is + // configured (opt-in per node). No fork check needed — subscription + // itself is the gate. + let execution_proof = SignedExecutionProof::from_ssz_bytes(data) + .map_err(|e| format!("{:?}", e))?; + Ok(PubsubMessage::ExecutionProof(Arc::new(execution_proof))) + } } } } @@ -413,6 +424,7 @@ impl PubsubMessage { PubsubMessage::BlsToExecutionChange(data) => data.as_ssz_bytes(), PubsubMessage::LightClientFinalityUpdate(data) => data.as_ssz_bytes(), PubsubMessage::LightClientOptimisticUpdate(data) => data.as_ssz_bytes(), + PubsubMessage::ExecutionProof(data) => data.as_ssz_bytes(), } } } @@ -472,6 +484,14 @@ impl std::fmt::Display for PubsubMessage { PubsubMessage::LightClientOptimisticUpdate(_data) => { write!(f, "Light CLient Optimistic Update") } + PubsubMessage::ExecutionProof(data) => { + write!( + f, + "Execution Proof: request_root: {:?}, proof_type: {}", + data.request_root(), + data.proof_type() + ) + } } } } diff --git a/beacon_node/lighthouse_network/src/types/subnet.rs b/beacon_node/lighthouse_network/src/types/subnet.rs index 1892dcc83af..c18f948654c 100644 --- a/beacon_node/lighthouse_network/src/types/subnet.rs +++ b/beacon_node/lighthouse_network/src/types/subnet.rs @@ -14,6 +14,8 @@ pub enum Subnet { SyncCommittee(SyncSubnetId), /// Represents a gossipsub data column subnet. DataColumn(DataColumnSubnetId), + /// EIP-8025: Capability flag — peers with a proof engine (ENR `ep=true`). + ExecutionProof, } /// A subnet to discover peers on along with the instant after which it's no longer useful. diff --git a/beacon_node/lighthouse_network/src/types/topics.rs b/beacon_node/lighthouse_network/src/types/topics.rs index 0c988f35c39..6d0ac31b3a8 100644 --- a/beacon_node/lighthouse_network/src/types/topics.rs +++ b/beacon_node/lighthouse_network/src/types/topics.rs @@ -25,12 +25,15 @@ pub const SYNC_COMMITTEE_PREFIX_TOPIC: &str = "sync_committee_"; pub const BLS_TO_EXECUTION_CHANGE_TOPIC: &str = "bls_to_execution_change"; pub const LIGHT_CLIENT_FINALITY_UPDATE: &str = "light_client_finality_update"; pub const LIGHT_CLIENT_OPTIMISTIC_UPDATE: &str = "light_client_optimistic_update"; +/// EIP-8025: Topic for publishing signed execution proofs. +pub const EXECUTION_PROOF_TOPIC: &str = "execution_proof"; #[derive(Debug)] pub struct TopicConfig { pub enable_light_client_server: bool, pub subscribe_all_subnets: bool, pub sampling_subnets: HashSet, + pub execution_proof: bool, } /// Returns all the topics the node should subscribe at `fork_name` @@ -85,6 +88,12 @@ pub fn core_topics_to_subscribe( } } + // EIP-8025: Subscribe to execution proof topic only when a proof engine is configured. + // This is an opt-in per-node feature, not tied to any fork. + if opts.execution_proof { + topics.push(GossipKind::ExecutionProof); + } + topics } @@ -109,17 +118,23 @@ pub fn is_fork_non_core_topic(topic: &GossipTopic, _fork_name: ForkName) -> bool | GossipKind::SignedContributionAndProof | GossipKind::BlsToExecutionChange | GossipKind::LightClientFinalityUpdate - | GossipKind::LightClientOptimisticUpdate => false, + | GossipKind::LightClientOptimisticUpdate + | GossipKind::ExecutionProof => false, } } -pub fn all_topics_at_fork(fork: ForkName, spec: &ChainSpec) -> Vec { +pub fn all_topics_at_fork( + fork: ForkName, + spec: &ChainSpec, + execution_proof: bool, +) -> Vec { // Compute the worst case of all forks let sampling_subnets = HashSet::from_iter(spec.all_data_column_sidecar_subnets()); let opts = TopicConfig { enable_light_client_server: true, subscribe_all_subnets: true, sampling_subnets, + execution_proof, }; core_topics_to_subscribe::(fork, &opts, spec) } @@ -169,6 +184,8 @@ pub enum GossipKind { LightClientFinalityUpdate, /// Topic for publishing optimistic updates for light clients. LightClientOptimisticUpdate, + /// EIP-8025: Topic for publishing signed execution proofs. + ExecutionProof, } impl std::fmt::Display for GossipKind { @@ -251,6 +268,7 @@ impl GossipTopic { BLS_TO_EXECUTION_CHANGE_TOPIC => GossipKind::BlsToExecutionChange, LIGHT_CLIENT_FINALITY_UPDATE => GossipKind::LightClientFinalityUpdate, LIGHT_CLIENT_OPTIMISTIC_UPDATE => GossipKind::LightClientOptimisticUpdate, + EXECUTION_PROOF_TOPIC => GossipKind::ExecutionProof, topic => match subnet_topic_index(topic) { Some(kind) => kind, None => return Err(format!("Unknown topic: {}", topic)), @@ -272,6 +290,7 @@ impl GossipTopic { GossipKind::Attestation(subnet_id) => Some(Subnet::Attestation(*subnet_id)), GossipKind::SyncCommitteeMessage(subnet_id) => Some(Subnet::SyncCommittee(*subnet_id)), GossipKind::DataColumnSidecar(subnet_id) => Some(Subnet::DataColumn(*subnet_id)), + GossipKind::ExecutionProof => Some(Subnet::ExecutionProof), _ => None, } } @@ -316,6 +335,7 @@ impl std::fmt::Display for GossipTopic { GossipKind::BlsToExecutionChange => BLS_TO_EXECUTION_CHANGE_TOPIC.into(), GossipKind::LightClientFinalityUpdate => LIGHT_CLIENT_FINALITY_UPDATE.into(), GossipKind::LightClientOptimisticUpdate => LIGHT_CLIENT_OPTIMISTIC_UPDATE.into(), + GossipKind::ExecutionProof => EXECUTION_PROOF_TOPIC.into(), }; write!( f, @@ -334,6 +354,7 @@ impl From for GossipKind { Subnet::Attestation(s) => GossipKind::Attestation(s), Subnet::SyncCommittee(s) => GossipKind::SyncCommitteeMessage(s), Subnet::DataColumn(s) => GossipKind::DataColumnSidecar(s), + Subnet::ExecutionProof => GossipKind::ExecutionProof, } } } @@ -514,6 +535,7 @@ mod tests { enable_light_client_server: false, subscribe_all_subnets: false, sampling_subnets: sampling_subnets.clone(), + execution_proof: false, } } @@ -558,6 +580,7 @@ mod tests { let s = HashSet::from_iter([1, 2].map(DataColumnSubnetId::new)); let mut topic_config = get_topic_config(&s); topic_config.enable_light_client_server = true; + topic_config.execution_proof = true; let latest_fork = *ForkName::list_all().last().unwrap(); let topics = core_topics_to_subscribe::(latest_fork, &topic_config, &spec); @@ -575,6 +598,10 @@ mod tests { for subnet in s { expected_topics.push(GossipKind::DataColumnSidecar(subnet)); } + // EIP-8025: ExecutionProof topic is added when execution_proof is enabled in TopicConfig + if topic_config.execution_proof { + expected_topics.push(GossipKind::ExecutionProof); + } // Need to check all the topics exist in an order independent manner for expected_topic in expected_topics { assert!( diff --git a/beacon_node/network/Cargo.toml b/beacon_node/network/Cargo.toml index bf261965760..090bb51025d 100644 --- a/beacon_node/network/Cargo.toml +++ b/beacon_node/network/Cargo.toml @@ -18,6 +18,7 @@ anyhow = { workspace = true } async-channel = { workspace = true } beacon_chain = { workspace = true } beacon_processor = { workspace = true } +bls = { workspace = true } delay_map = { workspace = true } educe = { workspace = true } ethereum_ssz = { workspace = true } @@ -50,7 +51,6 @@ typenum = { workspace = true } types = { workspace = true } [dev-dependencies] -bls = { workspace = true } eth2 = { workspace = true } eth2_network_config = { workspace = true } genesis = { workspace = true } diff --git a/beacon_node/network/src/metrics.rs b/beacon_node/network/src/metrics.rs index cea06a28c86..48c11223f61 100644 --- a/beacon_node/network/src/metrics.rs +++ b/beacon_node/network/src/metrics.rs @@ -508,6 +508,87 @@ pub static SYNC_UNKNOWN_NETWORK_REQUESTS: LazyLock> = Lazy ) }); +/* + * Proof Sync Metrics + */ + +/// Total `ExecutionProofsByRange` requests sent, labelled by outcome (`success`, `error`). +pub static PROOF_SYNC_RANGE_REQUESTS_TOTAL: LazyLock> = LazyLock::new(|| { + try_create_int_counter_vec( + "proof_sync_range_requests_total", + "Total ExecutionProofsByRange requests dispatched by proof sync", + &["result"], + ) +}); + +/// Total `ExecutionProofsByRoot` batch requests sent, labelled by outcome (`success`, `error`). +pub static PROOF_SYNC_ROOT_REQUESTS_TOTAL: LazyLock> = LazyLock::new(|| { + try_create_int_counter_vec( + "proof_sync_root_requests_total", + "Total ExecutionProofsByRoot batch requests dispatched by proof sync", + &["result"], + ) +}); + +/// Total `ExecutionProofStatus` requests sent to peers. +pub static PROOF_SYNC_STATUS_REQUESTS_TOTAL: LazyLock> = LazyLock::new(|| { + try_create_int_counter( + "proof_sync_status_requests_total", + "Total ExecutionProofStatus requests sent to peers", + ) +}); + +/// Total `ExecutionProofStatus` responses received from peers, labelled by `verified` +/// (`true` if the peer's slot/root pair was confirmed on our canonical chain). +pub static PROOF_SYNC_STATUS_RESPONSES_TOTAL: LazyLock> = + LazyLock::new(|| { + try_create_int_counter_vec( + "proof_sync_status_responses_total", + "Total ExecutionProofStatus responses received from peers", + &["verified"], + ) + }); + +/// Total proof-capable peer disconnects observed by proof sync. +pub static PROOF_SYNC_PEER_DISCONNECTS_TOTAL: LazyLock> = LazyLock::new(|| { + try_create_int_counter( + "proof_sync_peer_disconnects_total", + "Total proof-capable peer disconnects observed by proof sync", + ) +}); + +/// Current proof sync state: 0 = Idle, 1 = Waiting, 2 = Syncing. +pub static PROOF_SYNC_STATE: LazyLock> = LazyLock::new(|| { + try_create_int_gauge( + "proof_sync_state", + "Current proof sync state (0=Idle, 1=Waiting, 2=Syncing)", + ) +}); + +/// Number of proof-capable peers currently tracked by proof sync. +pub static PROOF_SYNC_PEER_COUNT: LazyLock> = LazyLock::new(|| { + try_create_int_gauge( + "proof_sync_peer_count", + "Number of proof-capable peers tracked by proof sync", + ) +}); + +/// Whether a `ExecutionProofsByRange` request is currently in-flight (0 or 1). +pub static PROOF_SYNC_RANGE_REQUEST_IN_FLIGHT: LazyLock> = LazyLock::new(|| { + try_create_int_gauge( + "proof_sync_range_request_in_flight", + "1 if a ExecutionProofsByRange request is currently in-flight, 0 otherwise", + ) +}); + +/// Whether a `ExecutionProofsByRoot` batch request is currently in-flight (0 or 1). +pub static PROOF_SYNC_ROOT_REQUEST_IN_FLIGHT: LazyLock> = LazyLock::new(|| { + try_create_int_gauge( + "proof_sync_root_request_in_flight", + "1 if a ExecutionProofsByRoot batch request is currently in-flight, 0 otherwise", + ) +}); + /* * Block Delay Metrics */ diff --git a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs index ca259129348..dc317f5ff37 100644 --- a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs @@ -7,19 +7,32 @@ use crate::{ use beacon_chain::blob_verification::{GossipBlobError, GossipVerifiedBlob}; use beacon_chain::block_verification_types::AsBlock; use beacon_chain::data_column_verification::{GossipDataColumnError, GossipVerifiedDataColumn}; +use beacon_chain::events::{EventKind, SseExecutionProofValidated}; +use beacon_chain::internal_events::InternalBeaconNodeEvent; use beacon_chain::store::Error; use beacon_chain::{ AvailabilityProcessingStatus, BeaconChainError, BeaconChainTypes, BlockError, ForkChoiceError, GossipVerifiedBlock, NotifyExecutionLayer, attestation_verification::{self, Error as AttnError, VerifiedAttestation}, data_availability_checker::AvailabilityCheckErrorCategory, + eip8025::ExecutionProofError, light_client_finality_update_verification::Error as LightClientFinalityUpdateError, light_client_optimistic_update_verification::Error as LightClientOptimisticUpdateError, + observed_execution_proofs::ProofObservation, observed_operations::ObservationOutcome, sync_committee_verification::{self, Error as SyncCommitteeError}, validator_monitor::{get_block_delay_ms, get_slot_delay_ms}, }; -use beacon_processor::{Work, WorkEvent}; +use beacon_processor::{ + DuplicateCache, GossipAggregatePackage, GossipAttestationBatch, Work, WorkEvent, + work_reprocessing_queue::{ + QueuedAggregate, QueuedColumnReconstruction, QueuedGossipBlock, QueuedLightClientUpdate, + QueuedUnaggregate, ReprocessQueueMessage, + }, +}; +use bls::PublicKeyBytes; +use execution_layer::eip8025::ProofEngineError; +use lighthouse_network::rpc::methods::ExecutionProofStatus; use lighthouse_network::{Client, MessageAcceptance, MessageId, PeerAction, PeerId, ReportSource}; use lighthouse_tracing::{ SPAN_PROCESS_GOSSIP_BLOB, SPAN_PROCESS_GOSSIP_BLOCK, SPAN_PROCESS_GOSSIP_DATA_COLUMN, @@ -38,18 +51,10 @@ use tracing::{Instrument, Span, debug, error, info, instrument, trace, warn}; use types::{ Attestation, AttestationData, AttestationRef, AttesterSlashing, BlobSidecar, DataColumnSidecar, DataColumnSubnetId, EthSpec, Hash256, IndexedAttestation, LightClientFinalityUpdate, - LightClientOptimisticUpdate, ProposerSlashing, SignedAggregateAndProof, SignedBeaconBlock, - SignedBlsToExecutionChange, SignedContributionAndProof, SignedVoluntaryExit, SingleAttestation, - Slot, SubnetId, SyncCommitteeMessage, SyncSubnetId, block::BlockImportSource, -}; - -use beacon_processor::work_reprocessing_queue::QueuedColumnReconstruction; -use beacon_processor::{ - DuplicateCache, GossipAggregatePackage, GossipAttestationBatch, - work_reprocessing_queue::{ - QueuedAggregate, QueuedGossipBlock, QueuedLightClientUpdate, QueuedUnaggregate, - ReprocessQueueMessage, - }, + LightClientOptimisticUpdate, ProofStatus, ProofType, ProposerSlashing, SignedAggregateAndProof, + SignedBeaconBlock, SignedBlsToExecutionChange, SignedContributionAndProof, + SignedExecutionProof, SignedVoluntaryExit, SingleAttestation, Slot, SubnetId, + SyncCommitteeMessage, SyncSubnetId, block::BlockImportSource, }; /// Set to `true` to introduce stricter penalties for peers who send some types of late consensus @@ -1862,6 +1867,467 @@ impl NetworkBeaconProcessor { metrics::inc_counter(&metrics::BEACON_PROCESSOR_BLS_TO_EXECUTION_CHANGE_IMPORTED_TOTAL); } + /// Process a signed execution proof received from the gossip network. + /// + /// Steps (EIP-8025 peer scoring & validator tracking): + /// 1. **Dedup check**: ignore if we already hold a valid proof for this request root + /// (`IGNORE-2`), or if this validator has already submitted a proof (`IGNORE-3`). + /// 2. **Validator ban check**: ignore proofs from validators that have previously submitted + /// an invalid proof. + /// 3. **Verification**: runs BLS signature verification and proof engine validation via + /// `BeaconChain::verify_execution_proof`. Errors are classified and translated into gossip + /// acceptance decisions and peer penalties (see `classify_execution_proof_error`). + /// 4. **Post-verification**: on success the proof is recorded for future dedup; on + /// `ProofStatus::Invalid` the signing validator is banned and the relay peer is penalised. + pub async fn process_gossip_execution_proof( + self: &Arc, + message_id: MessageId, + peer_id: PeerId, + execution_proof: Arc, + ) { + // Extract metadata for logging and dedup checks. + let request_root = execution_proof.request_root(); + let proof_type = execution_proof.proof_type(); + let validator_index = execution_proof.validator_index(); + + // Ignore proofs whose type is not in our configured set. + if !self.proof_types.iter().any(|t| t.to_u8() == proof_type) { + warn!( + proof_type, + %request_root, + "Ignoring gossip execution proof: proof type not in configured set" + ); + self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); + return; + } + + if self.chain.internal_event_sender().is_some() { + self.chain + .emit_internal_event(InternalBeaconNodeEvent::GossipExecutionProof( + execution_proof.clone(), + )); + } + + // Resolve the validator's public key from the pubkey cache. + // This is needed because tracking structures use pubkeys, not indices. + let Ok(Some(validator_pubkey)) = + self.chain.validator_pubkey_bytes(validator_index as usize) + else { + debug!( + validator_index, + "Ignoring execution proof: validator index not in pubkey cache" + ); + self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject); + self.gossip_penalize_peer( + peer_id, + PeerAction::LowToleranceError, + "execution_proof_invalid_validator", + ); + return; + }; + + if !self.should_process_execution_proof( + request_root, + proof_type, + &validator_pubkey, + validator_index, + ) { + self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); + return; + } + + // Clone the inner message before `execution_proof` is consumed by verification below. + // The clone only reaches the SSE handler when there are active subscribers, so the + // allocation is rare on the common (no-subscriber) path. + let execution_proof_message = execution_proof.message.clone(); + + // ── Verify the execution proof (BLS + proof engine) ───────────────── + let verification_result = self + .chain + .verify_execution_proof(execution_proof, validator_pubkey) + .await; + + if self.chain.internal_event_sender().is_some() { + self.chain.emit_internal_event(match &verification_result { + Ok((status, block)) => InternalBeaconNodeEvent::ExecutionProofVerified { + request_root, + status: *status, + block: *block, + }, + Err(e) => InternalBeaconNodeEvent::ExecutionProofVerificationFailed { + request_root, + error: format!("{e:?}"), + }, + }); + } + + // Determine gossip propagation behaviour for valid/accepted proofs. + // If we have an execution proof subscriber we assume a validator will re-sign the proof + // and therefore we do not propagate this proof to peers. + let gossip_behaviour = if let Ok((proof_status, block)) = &verification_result + && (proof_status.is_valid() || proof_status.is_accepted()) + && let Some(event_handler) = self.chain.event_handler.as_ref() + && event_handler.has_execution_proof_validated_subscribers() + && let Some((_block_root, slot)) = block + { + event_handler.register(EventKind::ExecutionProofValidated( + SseExecutionProofValidated { + execution_proof: execution_proof_message, + epoch: slot.epoch(T::EthSpec::slots_per_epoch()).as_u64(), + }, + )); + MessageAcceptance::Ignore + } else { + MessageAcceptance::Accept + }; + + // ── Error-differentiated peer scoring ──────────────────────────────── + match verification_result { + Err(e) => { + let (acceptance, peer_action, reason) = + if let BeaconChainError::ExecutionProofError(ref epe) = e { + Self::classify_execution_proof_error(epe) + } else { + ( + MessageAcceptance::Ignore, + None, + "execution_proof_internal_error", + ) + }; + + if peer_action.is_some() { + warn!( + ?request_root, + validator_index, + %peer_id, + error = ?e, + reason, + "Error verifying execution proof for gossip" + ); + } else { + debug!( + ?request_root, + validator_index, + %peer_id, + error = ?e, + reason, + "Execution proof verification failed (local/infra)" + ); + } + + self.propagate_validation_result(message_id, peer_id, acceptance); + if let Some(action) = peer_action { + self.gossip_penalize_peer(peer_id, action, reason); + } + } + Ok((ProofStatus::Valid, verified_block)) => { + debug!( + ?request_root, + validator_index, proof_type, "Execution proof is valid" + ); + + if let Some((block_root, slot)) = verified_block { + self.network_globals + .set_local_execution_proof_status(ExecutionProofStatus { + slot: slot.as_u64(), + block_root, + }); + } + self.propagate_validation_result(message_id, peer_id, gossip_behaviour); + } + Ok((ProofStatus::Invalid, _)) => { + warn!( + ?request_root, + %peer_id, + validator_index, + proof_type, + "Execution proof is invalid — banning validator, penalizing relay peer" + ); + + self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject); + // MidTolerance instead of Fatal — relay peers don't choose what they forward. + self.gossip_penalize_peer( + peer_id, + PeerAction::LowToleranceError, + "invalid_execution_proof", + ); + } + Ok((ProofStatus::Accepted, _)) => { + debug!( + ?request_root, + validator_index, + proof_type, + "Execution proof is accepted but not fully verified" + ); + self.propagate_validation_result(message_id, peer_id, gossip_behaviour); + } + Ok((ProofStatus::Syncing, _)) => { + debug!( + ?request_root, + validator_index, + proof_type, + "Execution proof cannot be fully verified while syncing" + ); + self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); + } + Ok((ProofStatus::NotSupported, _)) => { + debug!( + ?request_root, + validator_index, proof_type, "Execution proof type not supported" + ); + self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); + } + }; + } + + /// Process an execution proof received via RPC (not gossip). + /// + /// Applies the same verification, dedup, and post-verification logic as + /// [`Self::process_gossip_execution_proof`], with these differences: + /// - No gossip propagation. + /// - Errors are logged at `debug` rather than triggering error-differentiated peer scoring. + /// + /// TODO: add batch BLS verification for RPC proofs to amortise signature-check cost + /// across multiple proofs received in the same range response. + pub async fn process_rpc_execution_proof( + self: &Arc, + peer_id: PeerId, + execution_proof: Arc, + ) { + let request_root = execution_proof.request_root(); + let proof_type = execution_proof.proof_type(); + let validator_index = execution_proof.validator_index(); + + // Ignore proofs whose type is not in our configured set. + if !self.proof_types.iter().any(|t| t.to_u8() == proof_type) { + warn!( + proof_type, + %request_root, + "Ignoring RPC execution proof: proof type not in configured set" + ); + return; + } + + if self.chain.internal_event_sender().is_some() { + self.chain + .emit_internal_event(InternalBeaconNodeEvent::RpcExecutionProof( + execution_proof.clone(), + )); + } + + let Ok(Some(validator_pubkey)) = + self.chain.validator_pubkey_bytes(validator_index as usize) + else { + debug!( + validator_index, + "Ignoring RPC execution proof: validator index not in pubkey cache" + ); + return; + }; + + if !self.should_process_execution_proof( + request_root, + proof_type, + &validator_pubkey, + validator_index, + ) { + return; + } + + let verification_result = self + .chain + .verify_execution_proof(execution_proof, validator_pubkey) + .await; + + if self.chain.internal_event_sender().is_some() { + self.chain.emit_internal_event(match &verification_result { + Ok((status, block)) => InternalBeaconNodeEvent::ExecutionProofVerified { + request_root, + status: *status, + block: *block, + }, + Err(e) => InternalBeaconNodeEvent::ExecutionProofVerificationFailed { + request_root, + error: format!("{e:?}"), + }, + }); + } + + match verification_result { + Err(e) => { + debug!(%peer_id, error = ?e, "Error verifying RPC execution proof"); + } + Ok((ProofStatus::Valid, verified_block)) => { + debug!(%peer_id, "RPC execution proof valid"); + if let Some((block_root, slot)) = verified_block { + self.network_globals + .set_local_execution_proof_status(ExecutionProofStatus { + slot: slot.as_u64(), + block_root, + }); + } + } + Ok((ProofStatus::Invalid, _)) => { + warn!( + %peer_id, + validator_index, + ?request_root, + proof_type, + "RPC execution proof invalid — banning validator, penalizing peer" + ); + self.send_network_message(NetworkMessage::ReportPeer { + peer_id, + action: PeerAction::LowToleranceError, + source: ReportSource::SyncService, + msg: "invalid_rpc_execution_proof", + }); + } + Ok((ProofStatus::Accepted, _)) => { + debug!(%peer_id, "RPC execution proof accepted"); + } + Ok((ProofStatus::NotSupported, _)) => { + debug!(%peer_id, "RPC execution proof type not supported by local engine"); + } + Ok((ProofStatus::Syncing, _)) => { + debug!(%peer_id, "RPC execution proof received while block still syncing"); + } + } + } + + /// Classify a [`BeaconChainError`] from execution proof verification into the gossip + /// acceptance decision, an optional peer penalty, and a short reason string for logging. + /// + /// Returns `(MessageAcceptance, Option, reason)`. + /// - `Some(PeerAction)` means the peer sent something demonstrably wrong and should be scored. + /// - `None` means the failure is local/infra — no peer fault. + fn classify_execution_proof_error( + e: &ExecutionProofError, + ) -> (MessageAcceptance, Option, &'static str) { + match e { + // Crypto failures → REJECT + LowTolerance + ExecutionProofError::InvalidSignature + | ExecutionProofError::InvalidSignatureFormat + | ExecutionProofError::InvalidValidatorPubkey + | ExecutionProofError::EmptyProofData => ( + MessageAcceptance::Reject, + Some(PeerAction::LowToleranceError), + "execution_proof_crypto_failure", + ), + + // Invalid validator → REJECT + LowTolerance + ExecutionProofError::InvalidValidatorIndex => ( + MessageAcceptance::Reject, + Some(PeerAction::LowToleranceError), + "execution_proof_invalid_validator", + ), + + // Malformed proof (from proof engine) → REJECT + LowTolerance + ExecutionProofError::ProofEngineError( + ProofEngineError::InvalidPayload(_) | ProofEngineError::InvalidHeaderFormat(_), + ) => ( + MessageAcceptance::Reject, + Some(PeerAction::LowToleranceError), + "execution_proof_malformed", + ), + + // Bad proof type → REJECT + MidTolerance + ExecutionProofError::ProofEngineError(ProofEngineError::InvalidProofType(_)) => ( + MessageAcceptance::Reject, + Some(PeerAction::MidToleranceError), + "execution_proof_bad_type", + ), + + // Local infra errors → IGNORE, no penalty + ExecutionProofError::ProofEngineError( + ProofEngineError::Timeout + | ProofEngineError::HttpClientError(_) + | ProofEngineError::EngineUnavailable, + ) + | ExecutionProofError::NoExecutionLayer + | ExecutionProofError::StateError(_) => ( + MessageAcceptance::Ignore, + None, + "execution_proof_local_infra", + ), + + // Unsupported → IGNORE, no penalty + ExecutionProofError::ProofEngineError( + ProofEngineError::ProofTypeNotSupported(_) | ProofEngineError::ForkNotSupported(_), + ) => ( + MessageAcceptance::Ignore, + None, + "execution_proof_unsupported", + ), + + // Unknown state → IGNORE, no penalty + ExecutionProofError::UnknownRequestRoot(_) + | ExecutionProofError::ProofEngineError(ProofEngineError::StateError(_)) => ( + MessageAcceptance::Ignore, + None, + "execution_proof_unknown_state", + ), + + // Catch-all for unexpected variants. No penalty. + _ => ( + MessageAcceptance::Ignore, + None, + "execution_proof_internal_error", + ), + } + } + + /// Returns `true` if the proof should proceed to verification, `false` if it should be + /// dropped. Covers two cases: + /// - **Dedup**: we already hold a valid proof for this request root (`IGNORE-2`), or this + /// validator has already submitted a proof for it (`IGNORE-3`). + /// - **Validator ban**: the validator has previously submitted an invalid proof. + fn should_process_execution_proof( + &self, + request_root: Hash256, + proof_type: ProofType, + validator_pubkey: &PublicKeyBytes, + validator_index: u64, + ) -> bool { + // Scoped to drop the read lock before returning. + { + let dedup = self.chain.observed_execution_proofs.read(); + match dedup.check(request_root, proof_type, validator_pubkey) { + ProofObservation::AlreadyHaveValidProof => { + debug!( + ?request_root, + proof_type, + "Ignoring execution proof: valid proof already received (IGNORE-2)" + ); + return false; + } + ProofObservation::DuplicateFromValidator => { + debug!( + ?request_root, + proof_type, + validator_index, + "Ignoring execution proof: duplicate from validator (IGNORE-3)" + ); + return false; + } + ProofObservation::New => {} + } + } + + // Scoped to drop the read lock before returning. + { + let tracker = self.chain.invalid_proof_tracker.read(); + if tracker.is_banned(validator_pubkey) { + debug!( + ?request_root, + validator_index, "Ignoring execution proof from banned validator" + ); + return false; + } + } + + true + } + /// Process the sync committee signature received from the gossip network and: /// /// - If it passes gossip propagation criteria, tell the network thread to forward it. diff --git a/beacon_node/network/src/network_beacon_processor/mod.rs b/beacon_node/network/src/network_beacon_processor/mod.rs index fd9c2c1e55c..81d6762b5b0 100644 --- a/beacon_node/network/src/network_beacon_processor/mod.rs +++ b/beacon_node/network/src/network_beacon_processor/mod.rs @@ -11,6 +11,7 @@ use beacon_processor::{ BeaconProcessorSend, DuplicateCache, GossipAggregatePackage, GossipAttestationPackage, Work, WorkEvent as BeaconWorkEvent, }; +use execution_layer::eip8025::types::ProofTypes; use lighthouse_network::rpc::InboundRequestId; use lighthouse_network::rpc::methods::{ BlobsByRangeRequest, BlobsByRootRequest, DataColumnsByRangeRequest, DataColumnsByRootRequest, @@ -61,6 +62,7 @@ pub struct NetworkBeaconProcessor { pub network_globals: Arc>, pub invalid_block_storage: InvalidBlockStorage, pub executor: TaskExecutor, + pub proof_types: ProofTypes, } // Publish blobs in batches of exponentially increasing size. @@ -423,6 +425,47 @@ impl NetworkBeaconProcessor { }) } + /// EIP-8025: Create a new `Work` event for some signed execution proof. + pub fn send_gossip_execution_proof( + self: &Arc, + message_id: MessageId, + peer_id: PeerId, + execution_proof: Arc, + ) -> Result<(), Error> { + let processor = self.clone(); + let process_fn = async move { + processor + .process_gossip_execution_proof(message_id, peer_id, execution_proof) + .await + }; + + self.try_send(BeaconWorkEvent { + drop_during_sync: false, + work: Work::GossipExecutionProof(Box::pin(process_fn)), + }) + } + + /// EIP-8025: Verify an execution proof received over RPC. + /// + /// Reuses `GossipExecutionProof` work queue — the verification logic is identical. + /// Peer penalization on invalid proof uses `ReportSource::SyncService`. + pub fn send_rpc_execution_proof( + self: &Arc, + peer_id: PeerId, + execution_proof: Arc, + ) -> Result<(), Error> { + let processor = self.clone(); + let process_fn = async move { + processor + .process_rpc_execution_proof(peer_id, execution_proof) + .await + }; + self.try_send(BeaconWorkEvent { + drop_during_sync: false, + work: Work::GossipExecutionProof(Box::pin(process_fn)), + }) + } + /// Create a new `Work` event for some block, where the result from computation (if any) is /// sent to the other side of `result_tx`. pub fn send_rpc_beacon_block( @@ -667,6 +710,42 @@ impl NetworkBeaconProcessor { }) } + /// Create a new work event to serve an `ExecutionProofsByRange` RPC request (EIP-8025). + pub fn send_execution_proofs_by_range_request( + self: &Arc, + peer_id: PeerId, + inbound_request_id: InboundRequestId, + request: lighthouse_network::rpc::methods::ExecutionProofsByRangeRequest, + ) -> Result<(), Error> { + let processor = self.clone(); + let process_fn = move || { + processor.handle_execution_proofs_by_range_request(peer_id, inbound_request_id, request) + }; + + self.try_send(BeaconWorkEvent { + drop_during_sync: false, + work: Work::ExecutionProofsByRangeRequest(Box::new(process_fn)), + }) + } + + /// Create a new work event to serve an `ExecutionProofsByRoot` RPC request (EIP-8025). + pub fn send_execution_proofs_by_root_request( + self: &Arc, + peer_id: PeerId, + inbound_request_id: InboundRequestId, + request: lighthouse_network::rpc::methods::ExecutionProofsByRootRequest, + ) -> Result<(), Error> { + let processor = self.clone(); + let process_fn = move || { + processor.handle_execution_proofs_by_root_request(peer_id, inbound_request_id, request) + }; + + self.try_send(BeaconWorkEvent { + drop_during_sync: false, + work: Work::ExecutionProofsByRootRequest(Box::new(process_fn)), + }) + } + /// Create a new work event to process `LightClientBootstrap`s from the RPC network. pub fn send_light_client_bootstrap_request( self: &Arc, @@ -1079,6 +1158,7 @@ impl NetworkBeaconProcessor> { network_globals, invalid_block_storage: InvalidBlockStorage::Disabled, executor, + proof_types: ProofTypes::default(), }; (network_beacon_processor, beacon_processor_rx) diff --git a/beacon_node/network/src/network_beacon_processor/rpc_methods.rs b/beacon_node/network/src/network_beacon_processor/rpc_methods.rs index e443eb78d89..b882dffb9b8 100644 --- a/beacon_node/network/src/network_beacon_processor/rpc_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/rpc_methods.rs @@ -7,6 +7,7 @@ use beacon_chain::{BeaconChainError, BeaconChainTypes, BlockProcessStatus, WhenS use itertools::{Itertools, process_results}; use lighthouse_network::rpc::methods::{ BlobsByRangeRequest, BlobsByRootRequest, DataColumnsByRangeRequest, DataColumnsByRootRequest, + ExecutionProofsByRangeRequest, ExecutionProofsByRootRequest, }; use lighthouse_network::rpc::*; use lighthouse_network::{PeerId, ReportSource, Response, SyncInfo}; @@ -1323,6 +1324,150 @@ impl NetworkBeaconProcessor { Ok(()) } + /// Handle an `ExecutionProofsByRange` request from the peer (EIP-8025). + /// + /// Streams `SignedExecutionProof` objects known for the requested slot range, filtered by + /// `proof_filters` when present. For blocks listed in `proof_filters`: + /// - a non-empty `proof_types` list → serve only those types + /// - an empty `proof_types` list → skip the block entirely (requester already has all proofs) + /// + /// Blocks absent from `proof_filters` receive all known proof types. + pub fn handle_execution_proofs_by_range_request( + &self, + peer_id: PeerId, + inbound_request_id: InboundRequestId, + req: ExecutionProofsByRangeRequest, + ) { + self.terminate_response_stream( + peer_id, + inbound_request_id, + self.handle_execution_proofs_by_range_request_inner(peer_id, inbound_request_id, req), + Response::ExecutionProofsByRange, + ); + } + + fn handle_execution_proofs_by_range_request_inner( + &self, + peer_id: PeerId, + inbound_request_id: InboundRequestId, + req: ExecutionProofsByRangeRequest, + ) -> Result<(), (RpcErrorResponse, &'static str)> { + debug!( + %peer_id, + start_slot = req.start_slot, + count = req.count, + num_filters = req.proof_filters.len(), + "Received ExecutionProofsByRange Request" + ); + + // Build a lookup map: block_root → requested proof types from proof_filters. + // Blocks not listed in proof_filters will have all known proof types served. + let filter_map: std::collections::HashMap<_, _> = req + .proof_filters + .iter() + .map(|id| (id.block_root, &id.proof_types)) + .collect(); + + let block_roots = self.get_block_roots_for_slot_range( + req.start_slot, + req.count, + "ExecutionProofsByRange", + )?; + + let mut proofs_sent = 0usize; + for block_root in block_roots { + let allowed_types = filter_map.get(&block_root); + for proof in self.chain.get_execution_proofs_by_block_root(block_root) { + // If this block has a filter entry: + // - empty proof_types → skip the block entirely (requester already complete) + // - non-empty → serve only the listed types + // An absent entry means "return all types". + if let Some(types) = allowed_types + && (types.is_empty() || !types.contains(&proof.message.proof_type)) + { + continue; + } + self.send_network_message(NetworkMessage::SendResponse { + peer_id, + inbound_request_id, + response: Response::ExecutionProofsByRange(Some(Arc::new(proof))), + }); + proofs_sent += 1; + } + } + + debug!( + %peer_id, + start_slot = req.start_slot, + count = req.count, + returned = proofs_sent, + "ExecutionProofsByRange Response processed" + ); + + Ok(()) + } + + /// Handle an `ExecutionProofsByRoot` request from the peer (EIP-8025). + /// + /// Streams all `SignedExecutionProof` objects known for the requested beacon block roots. + pub fn handle_execution_proofs_by_root_request( + &self, + peer_id: PeerId, + inbound_request_id: InboundRequestId, + req: ExecutionProofsByRootRequest, + ) { + self.terminate_response_stream( + peer_id, + inbound_request_id, + self.handle_execution_proofs_by_root_request_inner(peer_id, inbound_request_id, req), + Response::ExecutionProofsByRoot, + ); + } + + fn handle_execution_proofs_by_root_request_inner( + &self, + peer_id: PeerId, + inbound_request_id: InboundRequestId, + req: ExecutionProofsByRootRequest, + ) -> Result<(), (RpcErrorResponse, &'static str)> { + debug!( + %peer_id, + num_identifiers = req.identifiers.len(), + "Received ExecutionProofsByRoot Request" + ); + + let mut proofs_sent = 0usize; + for identifier in req.identifiers.iter() { + for proof in self + .chain + .get_execution_proofs_by_block_root(identifier.block_root) + { + // Only return proof types the requester asked for. + // An empty list means the requester wants all types. + if !identifier.proof_types.is_empty() + && !identifier.proof_types.contains(&proof.message.proof_type) + { + continue; + } + self.send_network_message(NetworkMessage::SendResponse { + peer_id, + inbound_request_id, + response: Response::ExecutionProofsByRoot(Some(Arc::new(proof))), + }); + proofs_sent += 1; + } + } + + debug!( + %peer_id, + num_identifiers = req.identifiers.len(), + returned = proofs_sent, + "ExecutionProofsByRoot Response processed" + ); + + Ok(()) + } + /// Helper function to ensure single item protocol always end with either a single chunk or an /// error fn terminate_response_single_item Response>( diff --git a/beacon_node/network/src/network_beacon_processor/tests.rs b/beacon_node/network/src/network_beacon_processor/tests.rs index 49da522c9a1..29c6996a7c8 100644 --- a/beacon_node/network/src/network_beacon_processor/tests.rs +++ b/beacon_node/network/src/network_beacon_processor/tests.rs @@ -272,6 +272,7 @@ impl TestRig { network_globals: network_globals.clone(), invalid_block_storage: InvalidBlockStorage::Disabled, executor: executor.clone(), + proof_types: execution_layer::eip8025::types::ProofTypes::default(), }; let network_beacon_processor = Arc::new(network_beacon_processor); diff --git a/beacon_node/network/src/router.rs b/beacon_node/network/src/router.rs index 60fe094bb7c..0be4f5bcf22 100644 --- a/beacon_node/network/src/router.rs +++ b/beacon_node/network/src/router.rs @@ -11,7 +11,9 @@ use crate::status::status_message; use crate::sync::SyncMessage; use beacon_chain::{BeaconChain, BeaconChainTypes}; use beacon_processor::{BeaconProcessorSend, DuplicateCache}; +use execution_layer::eip8025::types::ProofTypes; use futures::prelude::*; +use lighthouse_network::rpc::methods::ExecutionProofStatus; use lighthouse_network::rpc::*; use lighthouse_network::{ MessageId, NetworkGlobals, PeerId, PubsubMessage, Response, @@ -24,6 +26,7 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tokio::sync::mpsc; use tokio_stream::wrappers::UnboundedReceiverStream; use tracing::{debug, error, trace, warn}; +use types::execution::eip8025::SignedExecutionProof; use types::{BlobSidecar, DataColumnSidecar, EthSpec, ForkContext, SignedBeaconBlock}; /// Handles messages from the network and routes them to the appropriate service to be handled. @@ -73,6 +76,11 @@ pub enum RouterMessage { StatusPeer(PeerId), /// The peer has an updated custody group count from METADATA. PeerUpdatedCustodyGroupCount(PeerId), + /// A peer sent their `ExecutionProofStatus` as an inbound request body. + PeerExecutionProofStatus { + peer_id: PeerId, + status: ExecutionProofStatus, + }, } impl Router { @@ -86,6 +94,7 @@ impl Router { invalid_block_storage: InvalidBlockStorage, beacon_processor_send: BeaconProcessorSend, fork_context: Arc, + proof_types: ProofTypes, ) -> Result>, String> { trace!("Service starting"); @@ -103,6 +112,7 @@ impl Router { network_globals: network_globals.clone(), invalid_block_storage, executor: executor.clone(), + proof_types: proof_types.clone(), }; let network_beacon_processor = Arc::new(network_beacon_processor); @@ -114,6 +124,7 @@ impl Router { network_beacon_processor.clone(), sync_recv, fork_context, + proof_types, ); // generate the Message handler @@ -180,6 +191,13 @@ impl Router { RouterMessage::PubsubMessage(id, peer_id, gossip, should_process) => { self.handle_gossip(id, peer_id, gossip, should_process); } + RouterMessage::PeerExecutionProofStatus { peer_id, status } => { + self.send_to_sync(SyncMessage::RpcExecutionProofStatus { + peer_id, + request_id: None, + status, + }); + } } } @@ -272,6 +290,24 @@ impl Router { request, ), ), + RequestType::ExecutionProofsByRange(request) => self + .handle_beacon_processor_send_result( + self.network_beacon_processor + .send_execution_proofs_by_range_request( + peer_id, + inbound_request_id, + request, + ), + ), + RequestType::ExecutionProofsByRoot(request) => self + .handle_beacon_processor_send_result( + self.network_beacon_processor + .send_execution_proofs_by_root_request( + peer_id, + inbound_request_id, + request, + ), + ), _ => {} } } @@ -309,6 +345,19 @@ impl Router { Response::DataColumnsByRange(data_column) => { self.on_data_columns_by_range_response(peer_id, app_request_id, data_column); } + Response::ExecutionProofsByRange(execution_proof) => { + self.on_execution_proofs_by_range_response( + peer_id, + app_request_id, + execution_proof, + ); + } + Response::ExecutionProofsByRoot(execution_proof) => { + self.on_execution_proofs_by_root_response(peer_id, app_request_id, execution_proof); + } + Response::ExecutionProofStatus(status) => { + self.on_execution_proof_status_response(peer_id, app_request_id, status); + } // Light client responses should not be received Response::LightClientBootstrap(_) | Response::LightClientOptimisticUpdate(_) @@ -486,6 +535,20 @@ impl Router { bls_to_execution_change, ), ), + // EIP-8025: Route execution proof messages to the gossip handler + PubsubMessage::ExecutionProof(execution_proof) => { + trace!( + %peer_id, + "Received execution proof" + ); + self.handle_beacon_processor_send_result( + self.network_beacon_processor.send_gossip_execution_proof( + message_id, + peer_id, + execution_proof, + ), + ) + } } } @@ -727,6 +790,63 @@ impl Router { } } + pub fn on_execution_proofs_by_range_response( + &mut self, + peer_id: PeerId, + app_request_id: AppRequestId, + execution_proof: Option>, + ) { + trace!(%peer_id, "Received ExecutionProofsByRange Response"); + if let AppRequestId::Sync(sync_request_id) = app_request_id { + self.send_to_sync(SyncMessage::RpcExecutionProof { + peer_id, + sync_request_id, + execution_proof, + }); + } else { + crit!("All execution proofs by range responses should belong to sync"); + } + } + + pub fn on_execution_proofs_by_root_response( + &mut self, + peer_id: PeerId, + app_request_id: AppRequestId, + execution_proof: Option>, + ) { + trace!(%peer_id, "Received ExecutionProofsByRoot Response"); + if let AppRequestId::Sync(sync_request_id) = app_request_id { + self.send_to_sync(SyncMessage::RpcExecutionProof { + peer_id, + sync_request_id, + execution_proof, + }); + } else { + crit!("All execution proofs by root responses should belong to sync"); + } + } + + fn on_execution_proof_status_response( + &mut self, + peer_id: PeerId, + app_request_id: AppRequestId, + status: ExecutionProofStatus, + ) { + // `request_id` is `Some` here because this is an outbound response (the peer responded + // to our request). The `None` case is for inbound requests (the peer sent us their status + // unsolicited) and is handled via `RouterMessage::PeerExecutionProofStatus`. + if let AppRequestId::Sync(SyncRequestId::ExecutionProofStatus(request_id)) = app_request_id + { + self.send_to_sync(SyncMessage::RpcExecutionProofStatus { + peer_id, + request_id: Some(request_id), + status, + }); + } else { + debug!(%peer_id, "ExecutionProofStatus response with unexpected request id"); + } + } + fn handle_beacon_processor_send_result( &mut self, result: Result<(), crate::network_beacon_processor::Error>, diff --git a/beacon_node/network/src/service.rs b/beacon_node/network/src/service.rs index 0869b442aec..5fdc1cc7ab7 100644 --- a/beacon_node/network/src/service.rs +++ b/beacon_node/network/src/service.rs @@ -11,11 +11,12 @@ use futures::channel::mpsc::Sender; use futures::future::OptionFuture; use futures::prelude::*; +use execution_layer::eip8025::types::{ProofType, ProofTypes}; use lighthouse_network::Enr; use lighthouse_network::identity::Keypair; use lighthouse_network::rpc::InboundRequestId; use lighthouse_network::rpc::RequestType; -use lighthouse_network::rpc::methods::RpcResponse; +use lighthouse_network::rpc::methods::{ExecutionProofStatus, RpcResponse}; use lighthouse_network::service::Network; use lighthouse_network::types::GossipKind; use lighthouse_network::{ @@ -291,6 +292,11 @@ impl NetworkService { ) .await?; + network_globals.set_local_execution_proof_status(ExecutionProofStatus { + slot: 0, + block_root: beacon_chain.genesis_block_root, + }); + // Repopulate the DHT with stored ENR's if discovery is not disabled. if !config.disable_discovery { let enrs_to_load = load_dht::(store.clone()); @@ -312,6 +318,18 @@ impl NetworkService { // launch derived network services // router task + let proof_types = config + .proof_types + .as_deref() + .map(|types| { + ProofTypes::from( + types + .iter() + .filter_map(|&t| ProofType::from_u8(t).ok()) + .collect::>(), + ) + }) + .unwrap_or_default(); let router_send = Router::spawn( beacon_chain.clone(), network_globals.clone(), @@ -320,6 +338,7 @@ impl NetworkService { invalid_block_storage, beacon_processor_send, fork_context.clone(), + proof_types, )?; // attestation and sync committee subnet service @@ -560,6 +579,9 @@ impl NetworkService { } } } + NetworkEvent::PeerExecutionProofStatus { peer_id, status } => { + self.send_to_router(RouterMessage::PeerExecutionProofStatus { peer_id, status }); + } NetworkEvent::NewListenAddr(multiaddr) => { self.network_globals .listen_multiaddrs diff --git a/beacon_node/network/src/sync/backfill_sync/mod.rs b/beacon_node/network/src/sync/backfill_sync/mod.rs index 9802ec56a16..e6b1a4b6a9d 100644 --- a/beacon_node/network/src/sync/backfill_sync/mod.rs +++ b/beacon_node/network/src/sync/backfill_sync/mod.rs @@ -82,8 +82,10 @@ pub enum SyncStart { /// The chain started syncing or is already syncing. Syncing { /// The number of slots that have been processed so far. + #[cfg_attr(feature = "disable-backfill", allow(dead_code))] completed: usize, /// The number of slots still to be processed. + #[cfg_attr(feature = "disable-backfill", allow(dead_code))] remaining: usize, }, /// The chain didn't start syncing. @@ -156,6 +158,7 @@ pub struct BackFillSync { network_globals: Arc>, } +#[cfg_attr(feature = "disable-backfill", allow(dead_code))] impl BackFillSync { pub fn new( beacon_chain: Arc>, @@ -1192,6 +1195,7 @@ impl BackFillSync { } /// Error kind for attempting to restart the sync from beacon chain parameters. +#[cfg_attr(feature = "disable-backfill", allow(dead_code))] enum ResetEpochError { /// The chain has already completed. SyncCompleted, diff --git a/beacon_node/network/src/sync/block_sidecar_coupling.rs b/beacon_node/network/src/sync/block_sidecar_coupling.rs index 6f563820f7c..7468aae428e 100644 --- a/beacon_node/network/src/sync/block_sidecar_coupling.rs +++ b/beacon_node/network/src/sync/block_sidecar_coupling.rs @@ -196,9 +196,7 @@ impl RangeBlockComponentsRequest { &mut self, spec: &ChainSpec, ) -> Option>, CouplingError>> { - let Some(blocks) = self.blocks_request.to_finished() else { - return None; - }; + let blocks = self.blocks_request.to_finished()?; // Increment the attempt once this function returns the response or errors match &mut self.block_data_request { @@ -206,9 +204,7 @@ impl RangeBlockComponentsRequest { Some(Self::responses_with_blobs(blocks.to_vec(), vec![], spec)) } RangeBlockDataRequest::Blobs(request) => { - let Some(blobs) = request.to_finished() else { - return None; - }; + let blobs = request.to_finished()?; Some(Self::responses_with_blobs( blocks.to_vec(), blobs.to_vec(), @@ -224,9 +220,7 @@ impl RangeBlockComponentsRequest { let mut data_columns = vec![]; let mut column_to_peer_id: HashMap = HashMap::new(); for req in requests.values() { - let Some(data) = req.to_finished() else { - return None; - }; + let data = req.to_finished()?; data_columns.extend(data.clone()) } diff --git a/beacon_node/network/src/sync/custody_backfill_sync/mod.rs b/beacon_node/network/src/sync/custody_backfill_sync/mod.rs index bb2c6799f1d..0b40731f293 100644 --- a/beacon_node/network/src/sync/custody_backfill_sync/mod.rs +++ b/beacon_node/network/src/sync/custody_backfill_sync/mod.rs @@ -125,6 +125,7 @@ pub struct CustodyBackFillSync { network_globals: Arc>, } +#[cfg_attr(feature = "disable-backfill", allow(dead_code))] impl CustodyBackFillSync { pub fn new( beacon_chain: Arc>, @@ -378,9 +379,7 @@ impl CustodyBackFillSync { /// Creates the next required batch from the chain. If there are no more batches required, /// `None` is returned. fn include_next_batch(&mut self) -> Option { - let Some(column_da_boundary) = self.beacon_chain.get_column_da_boundary() else { - return None; - }; + let column_da_boundary = self.beacon_chain.get_column_da_boundary()?; // Skip all batches (Epochs) that don't have missing columns. for epoch in Epoch::range_inclusive_rev(self.to_be_downloaded, column_da_boundary) { diff --git a/beacon_node/network/src/sync/manager.rs b/beacon_node/network/src/sync/manager.rs index 338f21ce987..21fe1cc996c 100644 --- a/beacon_node/network/src/sync/manager.rs +++ b/beacon_node/network/src/sync/manager.rs @@ -33,12 +33,15 @@ //! needs to be searched for (i.e if an attestation references an unknown block) this manager can //! search for the block and subsequently search for parents if needed. -use super::backfill_sync::{BackFillSync, ProcessResult, SyncStart}; +#[cfg(not(feature = "disable-backfill"))] +use super::backfill_sync::SyncStart; +use super::backfill_sync::{BackFillSync, ProcessResult}; use super::block_lookups::BlockLookups; use super::network_context::{ CustodyByRootResult, RangeBlockComponent, RangeRequestId, RpcEvent, SyncNetworkContext, }; use super::peer_sync_info::{PeerSyncType, remote_sync_type}; +use super::proof_sync::ProofSync; use super::range_sync::{EPOCHS_PER_BATCH, RangeSync, RangeSyncType}; use crate::network_beacon_processor::{ChainSegmentProcessId, NetworkBeaconProcessor}; use crate::service::NetworkMessage; @@ -53,14 +56,17 @@ use beacon_chain::validator_monitor::timestamp_now; use beacon_chain::{ AvailabilityProcessingStatus, BeaconChain, BeaconChainTypes, BlockError, EngineState, }; +use execution_layer::eip8025::types::ProofTypes; use futures::StreamExt; use lighthouse_network::SyncInfo; use lighthouse_network::rpc::RPCError; +use lighthouse_network::rpc::methods::ExecutionProofStatus; use lighthouse_network::service::api_types::{ BlobsByRangeRequestId, BlocksByRangeRequestId, ComponentsByRangeRequestId, CustodyBackFillBatchRequestId, CustodyBackfillBatchId, CustodyRequester, DataColumnsByRangeRequestId, DataColumnsByRangeRequester, DataColumnsByRootRequestId, - DataColumnsByRootRequester, Id, SingleLookupReqId, SyncRequestId, + DataColumnsByRootRequester, ExecutionProofStatusRequestId, Id, SingleLookupReqId, + SyncRequestId, }; use lighthouse_network::types::{NetworkGlobals, SyncState}; use lighthouse_network::{PeerAction, PeerId}; @@ -72,6 +78,7 @@ use std::sync::Arc; use std::time::Duration; use tokio::sync::mpsc; use tracing::{debug, error, info, trace}; +use types::execution::eip8025::SignedExecutionProof; use types::{ BlobSidecar, DataColumnSidecar, EthSpec, ForkContext, Hash256, SignedBeaconBlock, Slot, }; @@ -132,6 +139,22 @@ pub enum SyncMessage { seen_timestamp: Duration, }, + /// An execution proof has been received from the RPC. + RpcExecutionProof { + sync_request_id: SyncRequestId, + peer_id: PeerId, + execution_proof: Option>, + }, + + /// An ExecutionProofStatus response has been received from the RPC (outbound), + /// or a peer has sent us their status in an inbound request body. + /// `request_id` is `None` for the inbound (peer-initiated) case. + RpcExecutionProofStatus { + peer_id: PeerId, + request_id: Option, + status: ExecutionProofStatus, + }, + /// A block with an unknown parent has been received. UnknownParentBlock(PeerId, Arc>, Hash256), @@ -179,6 +202,7 @@ pub enum SyncMessage { /// The type of processing specified for a received block. #[derive(Debug, Clone)] +#[allow(clippy::enum_variant_names)] pub enum BlockProcessType { SingleBlock { id: Id }, SingleBlob { id: Id }, @@ -248,6 +272,9 @@ pub struct SyncManager { /// The object handling long-range batch load-balanced syncing. range_sync: RangeSync, + /// EIP-8025: catch-up mechanism for missing execution proofs. + proof_sync: ProofSync, + /// Backfill syncing. backfill_sync: BackFillSync, @@ -271,6 +298,7 @@ pub fn spawn( beacon_processor: Arc>, sync_recv: mpsc::UnboundedReceiver>, fork_context: Arc, + proof_types: ProofTypes, ) { assert!( beacon_chain @@ -287,6 +315,7 @@ pub fn spawn( beacon_processor, sync_recv, fork_context, + proof_types, ); // spawn the sync manager thread @@ -301,6 +330,7 @@ impl SyncManager { beacon_processor: Arc>, sync_recv: mpsc::UnboundedReceiver>, fork_context: Arc, + proof_types: ProofTypes, ) -> Self { let network_globals = beacon_processor.network_globals.clone(); Self { @@ -311,14 +341,19 @@ impl SyncManager { beacon_processor.clone(), beacon_chain.clone(), fork_context.clone(), + proof_types, ), range_sync: RangeSync::new(beacon_chain.clone()), backfill_sync: BackFillSync::new(beacon_chain.clone(), network_globals.clone()), - custody_backfill_sync: CustodyBackFillSync::new(beacon_chain.clone(), network_globals), + custody_backfill_sync: CustodyBackFillSync::new( + beacon_chain.clone(), + network_globals.clone(), + ), block_lookups: BlockLookups::new(), notified_unknown_roots: LRUTimeCache::new(Duration::from_secs( NOTIFIED_UNKNOWN_ROOT_EXPIRY_SECONDS, )), + proof_sync: ProofSync::new(beacon_chain.clone()), } } @@ -368,6 +403,26 @@ impl SyncManager { self.handle_new_execution_engine_state(state); } + #[cfg(test)] + pub(crate) fn poll_proof_sync(&mut self) { + self.proof_sync.poll(&mut self.network); + } + + #[cfg(test)] + pub(crate) fn proof_sync(&self) -> &super::proof_sync::ProofSync { + &self.proof_sync + } + + #[cfg(test)] + pub(crate) fn proof_sync_mut(&mut self) -> &mut super::proof_sync::ProofSync { + &mut self.proof_sync + } + + #[cfg(test)] + pub(crate) fn start_proof_sync(&mut self) { + self.proof_sync.start(&mut self.network); + } + fn network_globals(&self) -> &NetworkGlobals { self.network.network_globals() } @@ -423,6 +478,10 @@ impl SyncManager { } } + if self.network.is_proof_capable_peer(&peer_id) { + self.proof_sync.add_peer(peer_id, &mut self.network); + } + self.update_sync_state(); // Try to make progress on custody requests that are waiting for peers @@ -503,6 +562,18 @@ impl SyncManager { SyncRequestId::DataColumnsByRange(req_id) => { self.on_data_columns_by_range_response(req_id, peer_id, RpcEvent::RPCError(error)) } + SyncRequestId::ExecutionProofsByRange(req_id) => { + debug!(%peer_id, ?req_id, "Execution proofs by range request failed"); + self.proof_sync.on_range_request_error(&req_id); + } + SyncRequestId::ExecutionProofsByRoot(req_id) => { + debug!(%peer_id, ?req_id, "Execution proofs by root request failed"); + self.proof_sync.on_root_request_error(&req_id); + } + SyncRequestId::ExecutionProofStatus(id) => { + self.proof_sync + .on_peer_execution_proof_status_error(peer_id, id); + } } } @@ -522,6 +593,7 @@ impl SyncManager { self.range_sync.peer_disconnect(&mut self.network, peer_id); let _ = self.backfill_sync.peer_disconnected(peer_id); self.block_lookups.peer_disconnected(peer_id); + self.proof_sync.on_proof_capable_peer_disconnected(peer_id); // Regardless of the outcome, we update the sync status. self.update_sync_state(); @@ -611,6 +683,7 @@ impl SyncManager { // If we synced a peer between status messages, most likely the peer has // advanced and will produce a head chain on re-status. Otherwise it will shift // to being synced + #[cfg_attr(feature = "disable-backfill", allow(unused_mut))] let mut sync_state = { let head = self.chain.best_slot(); let current_slot = self.chain.slot().unwrap_or_else(|_| Slot::new(0)); @@ -697,6 +770,7 @@ impl SyncManager { self.backfill_sync.pause(); self.custody_backfill_sync .pause("Range sync in progress".to_string()); + self.proof_sync.pause(); SyncState::SyncingFinalized { start_slot, @@ -710,6 +784,7 @@ impl SyncManager { self.backfill_sync.pause(); self.custody_backfill_sync .pause("Range sync in progress".to_string()); + self.proof_sync.pause(); SyncState::SyncingHead { start_slot, @@ -726,15 +801,11 @@ impl SyncManager { // If we have become synced - Subscribe to all the core subnet topics // We don't need to subscribe if the old state is a state that would have already // invoked this call. - if new_state.is_synced() - && !matches!( - old_state, - SyncState::Synced - | SyncState::BackFillSyncing { .. } - | SyncState::CustodyBackFillSyncing { .. } - ) - { + if new_state.is_synced() && !old_state.is_synced() { self.network.subscribe_core_topics(); + if self.network_globals().config.enable_execution_proof { + self.proof_sync.start(&mut self.network); + } } } } @@ -750,7 +821,10 @@ impl SyncManager { .as_ref() .map(|el| el.get_responsiveness_watch()) .into(); - futures::stream::iter(ee_responsiveness_watch.await).flatten() + match ee_responsiveness_watch.await.flatten() { + Some(watch) => watch.left_stream(), + None => futures::stream::empty().right_stream(), + } }; // min(LOOKUP_MAX_DURATION_*) is 15 seconds. The cost of calling prune_lookups more often is @@ -767,6 +841,9 @@ impl SyncManager { self.chain.slot_clock.slot_duration().as_secs() * T::EthSpec::slots_per_epoch(); let mut epoch_interval = tokio::time::interval(Duration::from_secs(epoch_duration)); + // Poll ProofSync every slot to request any missing execution proofs. + let mut proof_sync_interval = tokio::time::interval(self.chain.slot_clock.slot_duration()); + // process any inbound messages loop { tokio::select! { @@ -788,6 +865,9 @@ impl SyncManager { _ = epoch_interval.tick() => { self.update_sync_state(); } + _ = proof_sync_interval.tick() => { + self.proof_sync.poll(&mut self.network); + } } } } @@ -833,6 +913,21 @@ impl SyncManager { } => { self.rpc_data_column_received(sync_request_id, peer_id, data_column, seen_timestamp) } + SyncMessage::RpcExecutionProof { + sync_request_id, + peer_id, + execution_proof, + } => { + self.rpc_execution_proof_received(sync_request_id, peer_id, execution_proof); + } + SyncMessage::RpcExecutionProofStatus { + peer_id, + request_id, + status, + } => { + self.proof_sync + .on_peer_execution_proof_status(peer_id, request_id, status); + } SyncMessage::UnknownParentBlock(peer_id, block, block_root) => { let block_slot = block.slot(); let parent_root = block.parent_root(); @@ -1186,6 +1281,39 @@ impl SyncManager { } } + fn rpc_execution_proof_received( + &mut self, + sync_request_id: SyncRequestId, + peer_id: PeerId, + execution_proof: Option>, + ) { + let Some(proof) = execution_proof else { + // Stream termination: clean up tracking map entry. + match &sync_request_id { + SyncRequestId::ExecutionProofsByRange(id) => { + self.proof_sync.on_range_request_terminated(id); + } + SyncRequestId::ExecutionProofsByRoot(id) => { + self.proof_sync.on_root_request_terminated(id); + } + other => { + debug!(%peer_id, ?other, "Unexpected sync_request_id for execution proof stream termination"); + } + } + return; + }; + + // Forward to the beacon processor for async BLS + proof engine verification. + // Peer penalization for invalid proofs is handled inside `process_rpc_execution_proof`. + if let Err(e) = self + .network + .beacon_processor() + .send_rpc_execution_proof(peer_id, proof) + { + debug!(%peer_id, error = ?e, "Failed to send RPC execution proof to beacon processor"); + } + } + fn on_single_blob_response( &mut self, id: SingleLookupReqId, diff --git a/beacon_node/network/src/sync/mod.rs b/beacon_node/network/src/sync/mod.rs index 054bab654c2..025bd9e792d 100644 --- a/beacon_node/network/src/sync/mod.rs +++ b/beacon_node/network/src/sync/mod.rs @@ -9,6 +9,7 @@ mod custody_backfill_sync; pub mod manager; mod network_context; mod peer_sync_info; +mod proof_sync; mod range_data_column_batch_request; mod range_sync; #[cfg(test)] diff --git a/beacon_node/network/src/sync/network_context.rs b/beacon_node/network/src/sync/network_context.rs index 00d4bf865d8..6ee866b37e1 100644 --- a/beacon_node/network/src/sync/network_context.rs +++ b/beacon_node/network/src/sync/network_context.rs @@ -18,17 +18,25 @@ use crate::sync::block_sidecar_coupling::CouplingError; use crate::sync::network_context::requests::BlobsByRootSingleBlockRequest; use crate::sync::range_data_column_batch_request::RangeDataColumnBatchRequest; use beacon_chain::block_verification_types::RpcBlock; +use beacon_chain::internal_events::InternalBeaconNodeEvent; use beacon_chain::{BeaconChain, BeaconChainTypes, BlockProcessStatus, EngineState}; use custody::CustodyRequestResult; +use execution_layer::MissingProofInfo; +use execution_layer::eip8025::types::ProofTypes; use fnv::FnvHashMap; -use lighthouse_network::rpc::methods::{BlobsByRangeRequest, DataColumnsByRangeRequest}; +use lighthouse_network::Eth2Enr; +use lighthouse_network::rpc::methods::{ + BlobsByRangeRequest, DataColumnsByRangeRequest, ExecutionProofStatus, + ExecutionProofsByRangeRequest, ExecutionProofsByRootRequest, +}; use lighthouse_network::rpc::{BlocksByRangeRequest, GoodbyeReason, RPCError, RequestType}; pub use lighthouse_network::service::api_types::RangeRequestId; use lighthouse_network::service::api_types::{ AppRequestId, BlobsByRangeRequestId, BlocksByRangeRequestId, ComponentsByRangeRequestId, CustodyBackFillBatchRequestId, CustodyBackfillBatchId, CustodyId, CustodyRequester, DataColumnsByRangeRequestId, DataColumnsByRangeRequester, DataColumnsByRootRequestId, - DataColumnsByRootRequester, Id, SingleLookupReqId, SyncRequestId, + DataColumnsByRootRequester, ExecutionProofStatusRequestId, ExecutionProofsByRangeRequestId, + ExecutionProofsByRootRequestId, Id, SingleLookupReqId, SyncRequestId, }; use lighthouse_network::{Client, NetworkGlobals, PeerAction, PeerId, ReportSource}; use lighthouse_tracing::{SPAN_OUTGOING_BLOCK_BY_ROOT_REQUEST, SPAN_OUTGOING_RANGE_REQUEST}; @@ -40,6 +48,7 @@ use requests::{ }; #[cfg(test)] use slot_clock::SlotClock; +use ssz_types::{RuntimeVariableList, VariableList}; use std::collections::hash_map::Entry; use std::collections::{HashMap, HashSet}; use std::fmt::Debug; @@ -52,7 +61,7 @@ use tracing::{Span, debug, debug_span, error, warn}; use types::data::FixedBlobSidecarList; use types::{ BlobSidecar, BlockImportSource, ColumnIndex, DataColumnSidecar, DataColumnSidecarList, EthSpec, - ForkContext, Hash256, SignedBeaconBlock, Slot, + ForkContext, Hash256, SignedBeaconBlock, Slot, execution::eip8025::ProofByRootIdentifier, }; pub mod custody; @@ -96,6 +105,7 @@ pub type CustodyByRootResult = Result<(DataColumnSidecarList, PeerGroup, Duration), RpcResponseError>; #[derive(Debug)] +#[allow(clippy::enum_variant_names)] pub enum RpcResponseError { RpcError(#[allow(dead_code)] RPCError), VerifyError(LookupVerifyError), @@ -114,9 +124,32 @@ pub enum RpcRequestSendError { /// Type of peer missing that caused a `RpcRequestSendError::NoPeers` #[derive(Debug, PartialEq, Eq)] +#[allow(clippy::enum_variant_names)] pub enum NoPeerError { BlockPeer, CustodyPeer(ColumnIndex), + /// No connected peer with `execution_proof_enabled()` in their ENR. + ProofPeer, +} + +/// Age threshold for considering a cached `ExecutionProofStatus` stale enough to re-query. +pub const EXECUTION_PROOF_STATUS_REFRESH_THRESHOLD: std::time::Duration = + std::time::Duration::from_secs(300); + +/// A peer's `ExecutionProofStatus` plus the time it was received and whether it was verified. +pub struct CachedExecutionProofStatus { + pub status: ExecutionProofStatus, + pub timestamp: std::time::Instant, + /// `true` if `status.block_root` was confirmed against our local chain. + /// `false` if the peer's claimed slot was ahead of our head at cache time (optimistic). + pub verified: bool, +} + +impl CachedExecutionProofStatus { + /// Returns `true` if this entry should be refreshed: either it is unverified or has expired. + pub fn needs_refresh(&self) -> bool { + !self.verified || self.timestamp.elapsed() > EXECUTION_PROOF_STATUS_REFRESH_THRESHOLD + } } #[derive(Debug, PartialEq, Eq)] @@ -234,6 +267,9 @@ pub struct SyncNetworkContext { pub chain: Arc>, fork_context: Arc, + + /// Proof types to request from peers. Populated from the `--proof-types` CLI flag. + proof_types: ProofTypes, } /// Small enumeration to make dealing with block and blob requests easier. @@ -277,6 +313,7 @@ impl SyncNetworkContext> { Arc::new(beacon_processor), beacon_chain, fork_context, + ProofTypes::default(), ) } } @@ -287,6 +324,7 @@ impl SyncNetworkContext { network_beacon_processor: Arc>, chain: Arc>, fork_context: Arc, + proof_types: ProofTypes, ) -> Self { SyncNetworkContext { network_send, @@ -304,6 +342,7 @@ impl SyncNetworkContext { network_beacon_processor, chain, fork_context, + proof_types, } } @@ -335,6 +374,7 @@ impl SyncNetworkContext { network_beacon_processor: _, chain: _, fork_context: _, + proof_types: _, } = self; let blocks_by_root_ids = blocks_by_root_requests @@ -375,10 +415,202 @@ impl SyncNetworkContext { .custody_peers_for_column(column_index) } + /// Send a `ExecutionProofsByRange` request to the given proof-capable peer. + /// + /// `filter_entries` contains `MissingProofInfo` entries for blocks within the requested slot + /// range that should appear in `proof_filters`: + /// - entries with non-empty `existing_proof_types` → peer returns only the missing types + /// - entries with all types already present (`needed` is empty) → peer skips the block + /// entirely (requester already holds all proofs for it) + /// + /// Blocks with no existing proofs at all are excluded from `filter_entries`; the peer + /// will return all known proof types for them by default. + /// + /// Callers should use `find_best_proof_capable_peer` to select the peer first. + pub fn request_execution_proofs_by_range( + &mut self, + peer_id: PeerId, + start_slot: Slot, + count: u64, + filter_entries: &[MissingProofInfo], + ) -> Result { + let id = ExecutionProofsByRangeRequestId { id: self.next_id() }; + + // Build proof_filters from filter_entries: + // - partial blocks: proof_types = types still needed (non-empty) + // - complete blocks: proof_types = [] → peer skips the block entirely + // - fully-missing blocks (existing empty): excluded — peer returns all types by default + let max_request_blocks = self + .chain + .spec + .max_request_blocks(self.fork_context.current_fork_name()); + let mut filter_items: Vec = Vec::new(); + for info in filter_entries { + if info.existing_proof_types.is_empty() { + // Fully missing: no filter entry; peer returns all proof types by default. + continue; + } + let needed: Vec = self + .proof_types + .iter() + .map(|t| t.to_u8()) + .filter(|t| !info.existing_proof_types.contains(t)) + .collect(); + // needed may be empty for complete blocks — that is intentional. + // An empty proof_types list tells the peer to skip this block entirely. + let proof_types = VariableList::new(needed) + .map_err(|e| RpcRequestSendError::InternalError(format!("proof_types: {e:?}")))?; + filter_items.push(ProofByRootIdentifier { + block_root: info.root, + proof_types, + }); + } + let proof_filters = + RuntimeVariableList::new(filter_items, max_request_blocks).map_err(|e| { + RpcRequestSendError::InternalError(format!("proof_filters too long: {e:?}")) + })?; + + let request = ExecutionProofsByRangeRequest { + start_slot: start_slot.as_u64(), + count, + proof_filters, + }; + self.network_send + .send(NetworkMessage::SendRequest { + peer_id, + request: RequestType::ExecutionProofsByRange(request), + app_request_id: AppRequestId::Sync(SyncRequestId::ExecutionProofsByRange(id)), + }) + .map_err(|_| RpcRequestSendError::InternalError("network send error".to_owned()))?; + if self.chain.internal_event_sender().is_some() { + self.chain.emit_internal_event( + InternalBeaconNodeEvent::OutboundExecutionProofsByRange { start_slot, count }, + ); + } + debug!( + method = "ExecutionProofsByRange", + %start_slot, + count, + peer = %peer_id, + %id, + "Sync RPC request sent" + ); + Ok(id) + } + + /// Send a `ExecutionProofsByRoot` request for all `missing` proofs to `peer_id`. + /// + /// Each entry in `missing` contributes one `ProofByRootIdentifier`. The `proof_types` field + /// is populated with the configured proof types that are not already present in + /// `info.existing_proof_types`, so we only request proof types we still need. + /// + /// Callers should use `find_best_proof_capable_peer` to select the peer first. + pub fn request_execution_proofs_by_root( + &mut self, + peer_id: PeerId, + missing: &[MissingProofInfo], + ) -> Result { + let mut identifiers = Vec::with_capacity(missing.len()); + for info in missing { + let needed: Vec = self + .proof_types + .iter() + .map(|t| t.to_u8()) + .filter(|t| !info.existing_proof_types.contains(t)) + .collect(); + let proof_types = VariableList::new(needed) + .map_err(|e| RpcRequestSendError::InternalError(format!("proof_types: {e:?}")))?; + identifiers.push(ProofByRootIdentifier { + block_root: info.root, + proof_types, + }); + } + let max_request_blocks = self + .chain + .spec + .max_request_blocks(self.fork_context.current_fork_name()); + let request = ExecutionProofsByRootRequest::new(identifiers, max_request_blocks) + .map_err(RpcRequestSendError::InternalError)?; + let id = ExecutionProofsByRootRequestId { id: self.next_id() }; + if self.chain.internal_event_sender().is_some() { + self.chain.emit_internal_event( + InternalBeaconNodeEvent::OutboundExecutionProofsByRoot { + identifiers: request.identifiers.to_vec(), + }, + ); + } + self.network_send + .send(NetworkMessage::SendRequest { + peer_id, + request: RequestType::ExecutionProofsByRoot(request), + app_request_id: AppRequestId::Sync(SyncRequestId::ExecutionProofsByRoot(id)), + }) + .map_err(|_| RpcRequestSendError::InternalError("network send error".to_owned()))?; + debug!( + method = "ExecutionProofsByRoot", + num_roots = missing.len(), + peer = %peer_id, + %id, + "Sync RPC request sent" + ); + Ok(id) + } + + /// Send an `ExecutionProofStatus` request to `peer_id`. + /// + /// The request body carries our local execution proof status so the peer can cache it. + /// Returns the newly-allocated request ID on success. + pub fn request_execution_proof_status( + &mut self, + peer_id: PeerId, + ) -> Result { + let id = ExecutionProofStatusRequestId { id: self.next_id() }; + let local_status = self.local_execution_proof_status(); + self.network_send + .send(NetworkMessage::SendRequest { + peer_id, + request: RequestType::ExecutionProofStatus(local_status), + app_request_id: AppRequestId::Sync(SyncRequestId::ExecutionProofStatus(id)), + }) + .map_err(|_| RpcRequestSendError::InternalError("network send error".to_owned()))?; + debug!( + method = "ExecutionProofStatus", + peer = %peer_id, + %id, + "Sync RPC request sent" + ); + Ok(id) + } + pub fn network_globals(&self) -> &NetworkGlobals { &self.network_beacon_processor.network_globals } + pub fn local_execution_proof_status(&self) -> ExecutionProofStatus { + self.network_globals().local_execution_proof_status() + } + + /// Number of proof types this node is configured to request. + /// + /// Used by [`ProofSync`] to compute request byte sizes without needing access to the + /// full `ProofTypes` set. + pub fn configured_proof_types_count(&self) -> usize { + self.proof_types.len() + } + + /// Returns `true` if the peer has `execution_proof_enabled()` in their ENR. + pub fn is_proof_capable_peer(&self, peer_id: &PeerId) -> bool { + self.network_globals() + .peers + .read() + .peer_info(peer_id) + .is_some_and(|info| { + info.enr() + .map(|enr| enr.execution_proof_enabled()) + .unwrap_or(false) + }) + } + /// Returns the Client type of the peer if known pub fn client_type(&self, peer_id: &PeerId) -> Client { self.network_globals() @@ -432,6 +664,7 @@ impl SyncNetworkContext { network_beacon_processor: _, chain: _, fork_context: _, + proof_types: _, // Don't use a fallback match. We want to be sure that all requests are considered when // adding new ones } = self; diff --git a/beacon_node/network/src/sync/proof_sync.rs b/beacon_node/network/src/sync/proof_sync.rs new file mode 100644 index 00000000000..34b804b1251 --- /dev/null +++ b/beacon_node/network/src/sync/proof_sync.rs @@ -0,0 +1,607 @@ +//! Catch-up mechanism for EIP-8025 execution proofs. +//! +//! Defines [`ProofSync`], the subsystem responsible for requesting execution proofs +//! that are missing from the local proof engine after block sync completes. It manages +//! peer status tracking and, at each slot tick, consults the proof engine directly to +//! decide the most bandwidth-efficient request strategy: +//! +//! - The SSZ-encoded sizes of an `ExecutionProofsByRange` request (20-byte fixed header +//! plus `proof_filters` for partially-held blocks) and an `ExecutionProofsByRoot` request +//! (one identifier per missing block) are compared over the full set of servable missing +//! proofs. Whichever encoding is smaller is used. +//! - `proof_filters` lets the server skip proof types the requester already holds, so +//! partially-covered blocks do not waste bandwidth even in a range request. +//! +//! The protocol is driven entirely by what the proof engine reports as missing, not by +//! the distance between peer and local verified heads. + +use super::network_context::{CachedExecutionProofStatus, SyncNetworkContext}; +use crate::metrics; +use beacon_chain::{BeaconChain, BeaconChainTypes, WhenSlotSkipped}; +use execution_layer::MissingProofInfo; +use lighthouse_network::PeerId; +use lighthouse_network::rpc::methods::ExecutionProofStatus; +use lighthouse_network::service::api_types::{ + ExecutionProofStatusRequestId, ExecutionProofsByRangeRequestId, ExecutionProofsByRootRequestId, +}; +use std::collections::HashMap; +use std::sync::Arc; +use std::time::Instant; +use tracing::{debug, info}; +use types::{Hash256, Slot}; + +/// Tracks the single in-flight `ExecutionProofsByRange` request. +/// +/// The request ID and serving peer are always set and cleared together, so they are +/// co-located. +pub(crate) struct ByRangeRequest { + pub(crate) id: ExecutionProofsByRangeRequestId, + pub(crate) peer_id: PeerId, +} + +/// Tracks the single in-flight `ExecutionProofsByRoot` batch request. +pub(crate) struct ByRootRequest { + pub(crate) id: ExecutionProofsByRootRequestId, + pub(crate) peer_id: PeerId, +} + +/// Operating mode for the proof sync subsystem. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ProofSyncState { + /// Range sync is active; proof sync is paused. + Idle, + /// Proof sync is active. Each poll queries the proof engine for missing proofs and + /// chooses between range or by-root requests based on byte-efficiency. + Syncing, +} + +/// Number of slot ticks to skip after a proof response stream completes before issuing +/// the next request. Gives the beacon processor time to import received proofs so they +/// no longer appear in `missing_execution_proofs()`. +const POST_REQUEST_COOLDOWN_SLOTS: u64 = 1; + +/// Proof sync subsystem for EIP-8025. +/// +/// Operates as a two-state machine: `Idle` while range sync is active, `Syncing` once +/// activated. In `Syncing`, each poll queries the proof engine for missing proofs and +/// chooses the most byte-efficient request strategy (range vs by-root). A brief +/// `post_request_cooldown` counter prevents immediate re-requesting after a response +/// stream completes, giving the beacon processor time to import the received proofs. +/// In-flight responses are always processed regardless of state transitions. +pub struct ProofSync { + chain: Arc>, + state: ProofSyncState, + /// Tracks the single in-flight `ExecutionProofsByRange` request (ID + serving peer). + range_request: Option, + /// Tracks the single in-flight `ExecutionProofsByRoot` batch request (ID + serving peer). + root_request: Option, + /// Slot ticks remaining before the next request may be issued after a response stream + /// completes. Set to `POST_REQUEST_COOLDOWN_SLOTS` on termination, decremented each + /// poll, and blocks new requests until it reaches zero. + post_request_cooldown: u64, + /// Cached `ExecutionProofStatus` responses, keyed by peer. + peer_statuses: HashMap, + /// In-flight `ExecutionProofStatus` request IDs, keyed by peer. + status_in_flight: HashMap, + /// Suppresses repeated "no proof-capable peer" logs: set when the message is first + /// emitted, cleared when a peer becomes available. + logged_no_peer: bool, + /// Injected missing-proof list for unit testing by-root behaviour. + #[cfg(test)] + pub test_missing_proofs: Option>, +} + +impl ProofSync { + /// Creates a new `ProofSync` instance in the `Idle` state. + pub fn new(chain: Arc>) -> Self { + Self { + state: ProofSyncState::Idle, + range_request: None, + root_request: None, + chain, + post_request_cooldown: 0, + peer_statuses: HashMap::default(), + status_in_flight: HashMap::default(), + logged_no_peer: false, + #[cfg(test)] + test_missing_proofs: None, + } + } + + /// Returns the current state of the proof sync subsystem. + #[cfg(test)] + pub fn state(&self) -> ProofSyncState { + self.state + } + + #[cfg(test)] + pub fn by_root_request(&self) -> Option<&ByRootRequest> { + self.root_request.as_ref() + } + + #[cfg(test)] + pub fn set_state(&mut self, state: ProofSyncState) { + self.state = state; + } + + #[cfg(test)] + pub fn by_range_request(&self) -> Option<&ByRangeRequest> { + self.range_request.as_ref() + } + + #[cfg(test)] + pub fn peer_status(&self, peer_id: &PeerId) -> Option<&CachedExecutionProofStatus> { + self.peer_statuses.get(peer_id) + } + + /// Called by `SyncManager` when range sync completes. + /// + /// Kicks off peer status refreshes and transitions directly to `Syncing`. The proof + /// engine is the authoritative source of missing proofs — it only reports entries after + /// blocks are imported, so no artificial delay is needed before the first poll. + pub fn start(&mut self, cx: &mut SyncNetworkContext) { + info!("ProofSync: starting"); + self.post_request_cooldown = 0; + self.refresh_peer_statuses(cx); + self.state = ProofSyncState::Syncing; + metrics::set_gauge(&metrics::PROOF_SYNC_STATE, 1); + } + + /// Called by `SyncManager` when range sync re-enters. + /// + /// Stops new proof requests from being issued. Any already in-flight responses + /// are still processed as they arrive. + pub fn pause(&mut self) { + debug!("ProofSync: pausing"); + self.state = ProofSyncState::Idle; + metrics::set_gauge(&metrics::PROOF_SYNC_STATE, 0); + } + + /// Drive one polling cycle. + /// + /// In `Syncing`, consults the proof engine for missing proofs and decides the most + /// request-efficient strategy: + /// + /// - Finds the consecutive run of missing slots with the highest byte savings over + /// an equivalent `ExecutionProofsByRoot` request. + /// - If a run's savings are positive it sends `ExecutionProofsByRange` for that run + /// (with `proof_filters` covering partially-held blocks so the peer skips redundant + /// proof types). + /// - Otherwise sends a single `ExecutionProofsByRoot` batch for all servable missing + /// proofs. + /// + /// Does nothing if a range request is already in-flight or a post-request cooldown is + /// active. Peer status refreshes run in the background and do not block request dispatch. + pub fn poll(&mut self, cx: &mut SyncNetworkContext) { + if self.state == ProofSyncState::Idle { + return; + } + + // Drain post-request cooldown before issuing the next request. + if self.post_request_cooldown > 0 { + self.post_request_cooldown -= 1; + return; + } + + // If a range request is already in-flight, wait for it to drain. + if self.range_request.is_some() { + return; + } + + // Ask the proof engine what it still needs — this is the authoritative source. + #[cfg(not(test))] + let missing = self.chain.missing_execution_proofs(); + #[cfg(test)] + let missing = self + .test_missing_proofs + .clone() + .unwrap_or_else(|| self.chain.missing_execution_proofs()); + + if missing.is_empty() { + return; + } + + let Some((peer_id, peer_slot)) = self.best_peer(cx) else { + return; + }; + + // Keep only entries the best peer can serve; sort by slot for run analysis. + let mut servable: Vec = missing + .into_iter() + .filter(|info| { + if peer_slot < info.slot { + debug!( + block_root = %info.root, + slot = %info.slot, + %peer_slot, + "ProofSync: best peer slot behind missing block, skipping" + ); + false + } else { + true + } + }) + .collect(); + + if servable.is_empty() { + return; + } + + servable.sort_unstable_by_key(|m| m.slot); + + let num_types = cx.configured_proof_types_count(); + + // Partition into blocks still needing at least one proof type and blocks + // that are already complete in the proof engine buffer. + let (actually_missing, complete_in_window): (Vec, Vec) = + servable + .into_iter() + .partition(|m| m.existing_proof_types.len() < num_types); + + if actually_missing.is_empty() { + return; + } + + // Build filter_entries for the range request: + // - partial entries (some types present, some missing) → peer returns only needed types + // - complete entries → peer skips the block entirely (empty proof_types in filter) + // Fully-missing entries are excluded from the filter; the peer returns all types for them. + let filter_entries: Vec = actually_missing + .iter() + .filter(|m| !m.existing_proof_types.is_empty()) + .cloned() + .chain(complete_in_window) + .collect(); + + let range_bytes = by_range_request_size(&filter_entries, num_types); + let root_bytes = by_root_request_size(&actually_missing, num_types); + + // A single range request covers all servable slots with a fixed 20-byte header plus + // proof_filters for partial and complete blocks. If cheaper than naming every block + // individually in a root request, use range; otherwise use root. + if range_bytes < root_bytes { + let start_slot = actually_missing[0].slot; + // count spans from first to last missing slot inclusive. + let count = + (actually_missing.last().expect("non-empty").slot - start_slot).as_u64() + 1; + + match cx.request_execution_proofs_by_range(peer_id, start_slot, count, &filter_entries) + { + Ok(id) => { + debug!( + start_slot = %start_slot, + count, + num_filters = filter_entries.len(), + range_bytes, + root_bytes, + "ProofSync: range request sent" + ); + self.range_request = Some(ByRangeRequest { id, peer_id }); + metrics::inc_counter_vec( + &metrics::PROOF_SYNC_RANGE_REQUESTS_TOTAL, + &["success"], + ); + metrics::set_gauge(&metrics::PROOF_SYNC_RANGE_REQUEST_IN_FLIGHT, 1); + } + Err(e) => { + debug!(error = ?e, "ProofSync: range request error"); + metrics::inc_counter_vec(&metrics::PROOF_SYNC_RANGE_REQUESTS_TOTAL, &["error"]); + } + } + return; + } + + // Root request is smaller — name every block and proof type still needed. + if self.root_request.is_some() { + return; + } + + match cx.request_execution_proofs_by_root(peer_id, &actually_missing) { + Ok(id) => { + debug!( + num_roots = actually_missing.len(), + root_bytes, range_bytes, "ProofSync: by-root batch sent" + ); + self.root_request = Some(ByRootRequest { id, peer_id }); + metrics::inc_counter_vec(&metrics::PROOF_SYNC_ROOT_REQUESTS_TOTAL, &["success"]); + metrics::set_gauge(&metrics::PROOF_SYNC_ROOT_REQUEST_IN_FLIGHT, 1); + } + Err(e) => { + debug!(error = ?e, "ProofSync: failed to send proof batch request"); + metrics::inc_counter_vec(&metrics::PROOF_SYNC_ROOT_REQUESTS_TOTAL, &["error"]); + } + } + } + + /// Called when an `ExecutionProofsByRange` RPC stream terminates (response `None`). + /// + /// Clears the in-flight request and starts a brief cooldown so the beacon processor + /// has time to import the received proofs before the next request is issued. + pub fn on_range_request_terminated(&mut self, id: &ExecutionProofsByRangeRequestId) { + if self.range_request.as_ref().map(|r| &r.id) == Some(id) { + debug!("ProofSync: range stream complete"); + self.range_request = None; + self.post_request_cooldown = POST_REQUEST_COOLDOWN_SLOTS; + metrics::set_gauge(&metrics::PROOF_SYNC_RANGE_REQUEST_IN_FLIGHT, 0); + } + } + + /// Called when an `ExecutionProofsByRange` RPC request errors. + /// + /// Clears the in-flight range request so the next `poll()` can retry. + pub fn on_range_request_error(&mut self, id: &ExecutionProofsByRangeRequestId) { + if self.range_request.as_ref().map(|r| &r.id) == Some(id) { + debug!("ProofSync: range request failed, will retry next poll"); + self.range_request = None; + metrics::set_gauge(&metrics::PROOF_SYNC_RANGE_REQUEST_IN_FLIGHT, 0); + } + } + + /// Called when an `ExecutionProofsByRoot` RPC request errors. + /// + /// Clears the in-flight root request so the next `poll()` can retry. + pub fn on_root_request_error(&mut self, id: &ExecutionProofsByRootRequestId) { + if self.root_request.as_ref().map(|r| &r.id) == Some(id) { + debug!("ProofSync: root batch request failed, will retry next poll"); + self.root_request = None; + metrics::set_gauge(&metrics::PROOF_SYNC_ROOT_REQUEST_IN_FLIGHT, 0); + } + } + + /// Called when an `ExecutionProofsByRoot` RPC stream terminates (response `None`). + /// + /// Clears the in-flight root request and starts a brief cooldown so the beacon + /// processor has time to import the received proofs before the next request is issued. + pub fn on_root_request_terminated(&mut self, id: &ExecutionProofsByRootRequestId) { + if self.root_request.as_ref().map(|r| &r.id) == Some(id) { + self.root_request = None; + self.post_request_cooldown = POST_REQUEST_COOLDOWN_SLOTS; + metrics::set_gauge(&metrics::PROOF_SYNC_ROOT_REQUEST_IN_FLIGHT, 0); + } + } + + /// Called when a proof-capable peer connects. + /// + /// Always issues a fresh `ExecutionProofStatus` request, overwriting any stale + /// in-flight entry from a prior connection. + pub fn add_peer(&mut self, peer_id: PeerId, cx: &mut SyncNetworkContext) { + match cx.request_execution_proof_status(peer_id) { + Ok(id) => { + debug!(%peer_id, %id, "ProofSync: queried peer execution proof status"); + self.status_in_flight.insert(peer_id, id); + metrics::inc_counter(&metrics::PROOF_SYNC_STATUS_REQUESTS_TOTAL); + } + Err(e) => { + debug!(error = ?e, %peer_id, "ProofSync: failed to query peer status on connect"); + } + } + } + + /// Called when a proof-capable peer disconnects. + /// + /// Removes the peer's cached status and any in-flight status request. If this peer + /// was serving the active range or root request, that request is also cleared so the + /// next `poll()` can retry with a different peer. + pub fn on_proof_capable_peer_disconnected(&mut self, peer_id: &PeerId) { + self.peer_statuses.remove(peer_id); + self.status_in_flight.remove(peer_id); + if self + .range_request + .as_ref() + .map(|r| &r.peer_id) + .filter(|p| *p == peer_id) + .is_some() + { + self.range_request = None; + metrics::set_gauge(&metrics::PROOF_SYNC_RANGE_REQUEST_IN_FLIGHT, 0); + } + if self + .root_request + .as_ref() + .map(|r| &r.peer_id) + .filter(|p| *p == peer_id) + .is_some() + { + self.root_request = None; + metrics::set_gauge(&metrics::PROOF_SYNC_ROOT_REQUEST_IN_FLIGHT, 0); + } + metrics::inc_counter(&metrics::PROOF_SYNC_PEER_DISCONNECTS_TOTAL); + metrics::set_gauge( + &metrics::PROOF_SYNC_PEER_COUNT, + self.peer_statuses.len() as i64, + ); + } + + /// Called when an `ExecutionProofStatus` arrives from a peer. + /// + /// `request_id` is `Some` for responses to our outbound requests and `None` for + /// peer-initiated status announcements. + /// + /// The status is stored with a `verified` flag: `true` if the peer's announced + /// `(slot, block_root)` pair matches our canonical chain at that slot, `false` if + /// the slot is ahead of our head (and therefore unverifiable locally). A mismatch — + /// where the slot is within our chain but the root differs — causes the status to be + /// discarded and the peer's re-poll timer to be reset. + pub fn on_peer_execution_proof_status( + &mut self, + peer_id: PeerId, + _request_id: Option, + status: ExecutionProofStatus, + ) { + debug!( + %peer_id, + slot = status.slot, + block_root = %status.block_root, + "ProofSync: received ExecutionProofStatus" + ); + + let best_slot = self.chain.best_slot(); + let verified = if status.slot <= best_slot.as_u64() { + match self + .chain + .block_root_at_slot(Slot::new(status.slot), WhenSlotSkipped::None) + { + Ok(Some(root)) if root == status.block_root => true, + _ => { + debug!( + %peer_id, + slot = status.slot, + claimed_root = %status.block_root, + "ProofSync: peer block root mismatch, ignoring status" + ); + self.on_peer_status_failed(peer_id); + return; + } + } + } else { + false + }; + + self.status_in_flight.remove(&peer_id); + self.peer_statuses.insert( + peer_id, + CachedExecutionProofStatus { + status, + timestamp: Instant::now(), + verified, + }, + ); + let verified_label = if verified { "true" } else { "false" }; + metrics::inc_counter_vec( + &metrics::PROOF_SYNC_STATUS_RESPONSES_TOTAL, + &[verified_label], + ); + metrics::set_gauge( + &metrics::PROOF_SYNC_PEER_COUNT, + self.peer_statuses.len() as i64, + ); + } + + /// Called when an outbound `ExecutionProofStatus` request errors. + /// + /// Delegates to `on_peer_status_failed`, which resets the peer's re-poll timer to + /// defer the next refresh attempt. + pub fn on_peer_execution_proof_status_error( + &mut self, + peer_id: PeerId, + request_id: ExecutionProofStatusRequestId, + ) { + debug!(%peer_id, %request_id, "ProofSync: ExecutionProofStatus request failed (soft)"); + self.on_peer_status_failed(peer_id); + } + + /// Clears the in-flight status entry and resets the peer's timestamp to defer re-polling. + /// Inserts a zero-slot placeholder if no prior entry exists. + fn on_peer_status_failed(&mut self, peer_id: PeerId) { + debug!(%peer_id, "ProofSync: peer status failed, deferring re-poll"); + self.status_in_flight.remove(&peer_id); + self.peer_statuses + .entry(peer_id) + .and_modify(|entry| entry.timestamp = Instant::now()) + .or_insert_with(|| CachedExecutionProofStatus { + status: ExecutionProofStatus { + slot: 0, + block_root: Hash256::ZERO, + }, + timestamp: Instant::now(), + verified: false, + }); + } + + /// Triggers refresh requests for stale or unverified peer entries. + fn refresh_peer_statuses(&mut self, cx: &mut SyncNetworkContext) { + for (peer_id, status) in self.peer_statuses.iter() { + if status.needs_refresh() && !self.status_in_flight.contains_key(peer_id) { + match cx.request_execution_proof_status(*peer_id) { + Ok(id) => { + self.status_in_flight.insert(*peer_id, id); + } + Err(e) => { + debug!(error = ?e, %peer_id, "ProofSync: failed to refresh status"); + } + } + } + } + } + + /// Triggers refresh requests for stale peer entries, then returns the peer with the + /// highest announced slot if all outstanding status polls have resolved. + /// + /// Verified peers are preferred (their slot is confirmed on-chain), but unverified + /// peers (whose announced slot is ahead of our head) are also eligible — the proofs + /// they serve are validated independently on receipt. + fn best_peer(&mut self, cx: &mut SyncNetworkContext) -> Option<(PeerId, Slot)> { + self.refresh_peer_statuses(cx); + + let result = self + .peer_statuses + .iter() + .max_by_key(|(_, c)| (c.verified, c.status.slot)) + .map(|(peer_id, c)| (*peer_id, Slot::new(c.status.slot))); + + match result { + None if !self.logged_no_peer => { + debug!("ProofSync: no proof-capable peer, will retry next poll"); + self.logged_no_peer = true; + } + Some(_) => { + self.logged_no_peer = false; + } + _ => {} + } + + result + } +} + +// ── Request-size helpers ────────────────────────────────────────────────────── + +/// SSZ encoded byte cost of one `ProofByRootIdentifier` entry when it appears inside a +/// variable-length list. +/// +/// Layout: +/// - 4 bytes — list offset-table entry (one u32 pointer per variable-length item) +/// - 32 bytes — `block_root: Hash256` (fixed field within the container) +/// - 4 bytes — SSZ offset for `proof_types` (variable field pointer within the container) +/// - `needed` × 1 byte — the proof type values themselves (`ProofType` encodes as one `u8`) +/// +/// `needed` = `num_configured_types` − `existing_proof_types.len()`, i.e. the types still +/// outstanding for this block. +fn per_identifier_ssz_bytes(info: &MissingProofInfo, num_configured_types: usize) -> usize { + let needed = num_configured_types.saturating_sub(info.existing_proof_types.len()); + 4 + 32 + 4 + needed +} + +/// Byte size of an `ExecutionProofsByRoot` request encoding `missing` identifiers. +/// +/// The request body is `List[ProofByRootIdentifier, ...]`; each entry pays the full +/// per-identifier cost regardless of whether it is fully or partially missing. +pub(crate) fn by_root_request_size( + missing: &[MissingProofInfo], + num_configured_types: usize, +) -> usize { + missing + .iter() + .map(|m| per_identifier_ssz_bytes(m, num_configured_types)) + .sum() +} + +/// Byte size of an `ExecutionProofsByRange` request. +/// +/// Layout: +/// - 20 bytes — fixed header: `start_slot` (8) + `count` (8) + `proof_filters` offset (4) +/// - `proof_filters` — partial entries (some types held, some needed) and complete entries +/// (empty `proof_types` list tells the peer to skip the block). Fully-missing blocks are +/// absent; the peer returns all proof types for them by default. +/// +/// `filter_entries` should contain partial and complete entries only (not fully-missing ones). +pub(crate) fn by_range_request_size( + filter_entries: &[MissingProofInfo], + num_configured_types: usize, +) -> usize { + let filter_bytes: usize = filter_entries + .iter() + .map(|m| per_identifier_ssz_bytes(m, num_configured_types)) + .sum(); + 20 + filter_bytes +} diff --git a/beacon_node/network/src/sync/range_data_column_batch_request.rs b/beacon_node/network/src/sync/range_data_column_batch_request.rs index b912a6badc9..52d7b63a031 100644 --- a/beacon_node/network/src/sync/range_data_column_batch_request.rs +++ b/beacon_node/network/src/sync/range_data_column_batch_request.rs @@ -71,9 +71,7 @@ impl RangeDataColumnBatchRequest { let mut column_to_peer_id: HashMap = HashMap::new(); for req in self.requests.values() { - let Some(columns) = req.to_finished() else { - return None; - }; + let columns = req.to_finished()?; for column in columns { received_columns_for_slot diff --git a/beacon_node/network/src/sync/tests/lookups.rs b/beacon_node/network/src/sync/tests/lookups.rs index 715928906ee..24c75f6d993 100644 --- a/beacon_node/network/src/sync/tests/lookups.rs +++ b/beacon_node/network/src/sync/tests/lookups.rs @@ -27,6 +27,7 @@ use beacon_chain::{ validator_monitor::timestamp_now, }; use beacon_processor::WorkEvent; +use execution_layer::eip8025::types::ProofTypes; use lighthouse_network::discovery::CombinedKey; use lighthouse_network::{ NetworkConfig, NetworkGlobals, PeerId, @@ -109,6 +110,17 @@ impl TestRig { init_tracing(); + let network_globals = beacon_processor.network_globals.clone(); + let sync_manager = SyncManager::new( + chain, + network_tx, + beacon_processor.into(), + // Pass empty recv not tied to any tx + mpsc::unbounded_channel().1, + fork_context, + ProofTypes::default(), + ); + TestRig { beacon_processor_rx, beacon_processor_rx_queue: vec![], @@ -117,15 +129,8 @@ impl TestRig { sync_rx, rng_08, rng, - network_globals: beacon_processor.network_globals.clone(), - sync_manager: SyncManager::new( - chain, - network_tx, - beacon_processor.into(), - // Pass empty recv not tied to any tx - mpsc::unbounded_channel().1, - fork_context, - ), + network_globals, + sync_manager, harness, fork_name, spec, @@ -353,6 +358,17 @@ impl TestRig { k256::ecdsa::SigningKey::random(&mut self.rng_08).into() } + pub fn new_connected_proof_capable_peer(&mut self) -> PeerId { + let key = self.determinstic_key(); + let peer_id = self + .network_globals + .peers + .write() + .__add_connected_proof_capable_peer_testing_only(key); + self.log(&format!("Added proof-capable peer {peer_id:?}")); + peer_id + } + pub fn new_connected_peers_for_peerdas(&mut self) { // Enough sampling peers with few columns for _ in 0..100 { @@ -705,7 +721,7 @@ impl TestRig { self.send_sync_message(SyncMessage::Disconnect(peer_id)); } - fn drain_network_rx(&mut self) { + pub fn drain_network_rx(&mut self) { while let Ok(event) = self.network_rx.try_recv() { self.network_rx_queue.push(event); } @@ -1968,9 +1984,7 @@ mod deneb_only { impl DenebTester { fn new(request_trigger: RequestTrigger) -> Option { - let Some(mut rig) = TestRig::test_setup_after_deneb_before_fulu() else { - return None; - }; + let mut rig = TestRig::test_setup_after_deneb_before_fulu()?; let (block, blobs) = rig.rand_block_and_blobs(NumBlobs::Random); let mut block = Arc::new(block); let mut blobs = blobs.into_iter().map(Arc::new).collect::>(); diff --git a/beacon_node/network/src/sync/tests/range.rs b/beacon_node/network/src/sync/tests/range.rs index cb728a90c1b..f555b1cade3 100644 --- a/beacon_node/network/src/sync/tests/range.rs +++ b/beacon_node/network/src/sync/tests/range.rs @@ -4,21 +4,26 @@ use crate::status::ToStatusMessage; use crate::sync::SyncMessage; use crate::sync::manager::SLOT_IMPORT_TOLERANCE; use crate::sync::network_context::RangeRequestId; +use crate::sync::proof_sync::ProofSyncState; use crate::sync::range_sync::RangeSyncType; use beacon_chain::data_column_verification::CustodyDataColumn; use beacon_chain::test_utils::{AttestationStrategy, BlockStrategy}; use beacon_chain::{EngineState, NotifyExecutionLayer, block_verification_types::RpcBlock}; use beacon_processor::WorkType; +use bls::SignatureBytes; +use execution_layer::MissingProofInfo; use lighthouse_network::rpc::RequestType; use lighthouse_network::rpc::methods::{ - BlobsByRangeRequest, DataColumnsByRangeRequest, OldBlocksByRangeRequest, + BlobsByRangeRequest, DataColumnsByRangeRequest, ExecutionProofStatus, OldBlocksByRangeRequest, OldBlocksByRangeRequestV2, StatusMessageV2, }; use lighthouse_network::service::api_types::{ AppRequestId, BlobsByRangeRequestId, BlocksByRangeRequestId, DataColumnsByRangeRequestId, + ExecutionProofStatusRequestId, ExecutionProofsByRangeRequestId, ExecutionProofsByRootRequestId, SyncRequestId, }; use lighthouse_network::{PeerId, SyncInfo}; +use std::sync::Arc; use std::time::Duration; use types::{ BlobSidecarList, BlockImportSource, Epoch, EthSpec, Hash256, MinimalEthSpec as E, @@ -323,6 +328,157 @@ impl TestRig { blocks_req_id.parent_request_id.requester } + /// Assert an `ExecutionProofsByRange` RPC was sent to the network. + /// Returns `(request_id, peer_id, start_slot)`. + fn find_execution_proofs_by_range_request( + &mut self, + ) -> (ExecutionProofsByRangeRequestId, PeerId) { + self.find_execution_proofs_by_range_request_with_slot().0 + } + + /// Assert an `ExecutionProofsByRange` RPC was sent; returns `((id, peer_id), start_slot)`. + fn find_execution_proofs_by_range_request_with_slot( + &mut self, + ) -> ((ExecutionProofsByRangeRequestId, PeerId), Slot) { + let ((id, peer_id), start_slot, _count) = + self.find_execution_proofs_by_range_request_params(); + ((id, peer_id), start_slot) + } + + /// Assert an `ExecutionProofsByRange` RPC was sent; returns `((id, peer_id), start_slot, count)`. + fn find_execution_proofs_by_range_request_params( + &mut self, + ) -> ((ExecutionProofsByRangeRequestId, PeerId), Slot, u64) { + self.pop_received_network_event(|ev| match ev { + NetworkMessage::SendRequest { + peer_id, + request: RequestType::ExecutionProofsByRange(req), + app_request_id: AppRequestId::Sync(SyncRequestId::ExecutionProofsByRange(id)), + } => Some(((*id, *peer_id), Slot::new(req.start_slot), req.count)), + _ => None, + }) + .unwrap_or_else(|e| panic!("Expected ExecutionProofsByRange request: {e:?}")) + } + + /// Assert no `ExecutionProofsByRange` RPC exists in the queue. + #[track_caller] + fn expect_no_execution_proof_range_request(&mut self) { + self.drain_network_rx(); + let found = self.network_rx_queue.iter().any(|ev| { + matches!( + ev, + NetworkMessage::SendRequest { + request: RequestType::ExecutionProofsByRange(_), + .. + } + ) + }); + assert!( + !found, + "Expected no ExecutionProofsByRange request, but one was found" + ); + } + + /// Assert an `ExecutionProofsByRoot` RPC was sent; return `(id, peer_id)`. + fn find_execution_proofs_by_root_request( + &mut self, + ) -> (ExecutionProofsByRootRequestId, PeerId) { + self.pop_received_network_event(|ev| match ev { + NetworkMessage::SendRequest { + peer_id, + request: RequestType::ExecutionProofsByRoot(_), + app_request_id: AppRequestId::Sync(SyncRequestId::ExecutionProofsByRoot(id)), + } => Some((*id, *peer_id)), + _ => None, + }) + .unwrap_or_else(|e| panic!("Expected ExecutionProofsByRoot request: {e:?}")) + } + + /// Send stream-termination for an `ExecutionProofsByRange` request. + fn terminate_execution_proofs_by_range( + &mut self, + req_id: ExecutionProofsByRangeRequestId, + peer_id: PeerId, + ) { + self.send_sync_message(SyncMessage::RpcExecutionProof { + sync_request_id: SyncRequestId::ExecutionProofsByRange(req_id), + peer_id, + execution_proof: None, + }); + } + + /// Send stream-termination for an `ExecutionProofsByRoot` request. + fn terminate_execution_proofs_by_root( + &mut self, + req_id: ExecutionProofsByRootRequestId, + peer_id: PeerId, + ) { + self.send_sync_message(SyncMessage::RpcExecutionProof { + sync_request_id: SyncRequestId::ExecutionProofsByRoot(req_id), + peer_id, + execution_proof: None, + }); + } + + /// Find an outbound `ExecutionProofStatus` RPC request; returns `(request_id, peer_id)`. + fn find_execution_proof_status_request(&mut self) -> (ExecutionProofStatusRequestId, PeerId) { + self.pop_received_network_event(|ev| match ev { + NetworkMessage::SendRequest { + peer_id, + request: RequestType::ExecutionProofStatus(_), + app_request_id: AppRequestId::Sync(SyncRequestId::ExecutionProofStatus(id)), + } => Some((*id, *peer_id)), + _ => None, + }) + .unwrap_or_else(|e| panic!("Expected ExecutionProofStatus request: {e:?}")) + } + + /// Drain all pending `ExecutionProofStatus` outbound requests from the network queue. + /// + /// Called after `start_proof_sync()` to prevent status requests from leaking into + /// assertions in the caller. + fn drain_execution_proof_status_requests(&mut self) { + self.drain_network_rx(); + self.network_rx_queue.retain(|ev| { + !matches!( + ev, + NetworkMessage::SendRequest { + request: RequestType::ExecutionProofStatus(_), + .. + } + ) + }); + } + + /// Connect a proof-capable peer and deliver an `ExecutionProofStatus` for `peer_slot`. + /// + /// For slots within our head the block root is looked up so the entry is verified. + /// For slots ahead of our head the entry is cached as unverified (optimistic) but + /// still eligible for peer selection. + fn new_proof_peer_with_status(&mut self, peer_slot: u64) -> PeerId { + let peer_id = self.new_connected_proof_capable_peer(); + let best_slot = self.harness.chain.best_slot(); + let block_root = if Slot::new(peer_slot) <= best_slot { + self.harness + .chain + .block_root_at_slot(Slot::new(peer_slot), beacon_chain::WhenSlotSkipped::None) + .ok() + .flatten() + .unwrap_or_else(Hash256::random) + } else { + Hash256::random() + }; + self.send_sync_message(SyncMessage::RpcExecutionProofStatus { + peer_id, + request_id: None, + status: ExecutionProofStatus { + slot: peer_slot, + block_root, + }, + }); + peer_id + } + fn find_and_complete_processing_chain_segment(&mut self, id: ChainSegmentProcessId) { self.pop_received_processor_event(|ev| { (ev.work_type() == WorkType::ChainSegment).then_some(()) @@ -601,3 +757,846 @@ fn finalized_sync_not_enough_custody_peers_on_start() { let last_epoch = advanced_epochs + EXTRA_SYNCED_EPOCHS; r.complete_and_process_range_sync_until(last_epoch, filter()); } + +// --- ProofSync state-machine tests --- + +// These tests exercise the `ProofSync` state machine directly, covering its full lifecycle: +// Idle → Syncing (range request when a consecutive run of fully-missing blocks saves bytes, +// by-root fill when all missing blocks are only partially missing). +// pause/resume semantics, concurrency cap, in-flight deduplication, and response forwarding. +// +// Range vs root decisions are driven by `test_missing_proofs` (injected into proof_sync) and +// a direct size comparison: by_range_request_size vs by_root_request_size over all servable +// missing proofs. Fully-missing blocks always prefer range; all-partial sets always prefer root. + +/// Build a fully-missing `MissingProofInfo` (no proof types held yet). +/// +/// Because no existing types are held, the size comparison always prefers a range request: +/// the 20-byte range header is smaller than the 40-byte per-identifier cost when all types +/// are needed. +fn missing_proof(root: Hash256) -> MissingProofInfo { + MissingProofInfo { + root, + existing_proof_types: vec![], + slot: Default::default(), + } +} + +/// Build a partially-missing `MissingProofInfo` (one proof type already held). +/// +/// Because the entry is partial, it must appear in `proof_filters` if included in a range +/// request — making the range request larger than the equivalent by-root identifier. +/// The size comparison therefore never prefers range for these entries; `poll()` falls back +/// to `ExecutionProofsByRoot`. +fn partial_missing_proof(root: Hash256) -> MissingProofInfo { + MissingProofInfo { + root, + existing_proof_types: vec![0u8], // one type already held; still needs the rest + slot: Default::default(), + } +} + +/// Build a minimal `SignedExecutionProof` suitable for RPC response messages. +fn make_signed_proof() -> Arc { + Arc::new(types::SignedExecutionProof { + message: types::ExecutionProof::default(), + validator_index: 0, + signature: SignatureBytes::empty(), + }) +} + +/// Test 1: The default initial state of ProofSync is Idle and polling it emits no network events. +#[test] +fn test_proof_sync_starts_in_idle() { + let mut rig = TestRig::test_setup(); + assert_eq!(rig.sync_manager.proof_sync().state(), ProofSyncState::Idle); + rig.sync_manager.poll_proof_sync(); + rig.expect_empty_network(); +} + +/// Test 2: After `start()`, the next `poll()` sends an `ExecutionProofsByRange` RPC. +/// A fully-missing block is cheaper to request via range (20 bytes) than by-root (40+ bytes). +#[test] +fn test_proof_sync_pending_range_issues_request_on_poll() { + let mut rig = TestRig::test_setup(); + let _proof_peer = rig.new_proof_peer_with_status(1); + rig.harness.advance_slot(); + + rig.sync_manager.start_proof_sync(); + assert_eq!( + rig.sync_manager.proof_sync().state(), + ProofSyncState::Syncing + ); + + // Inject a fully-missing proof — range request is always cheaper. + rig.sync_manager.proof_sync_mut().test_missing_proofs = + Some(vec![missing_proof(Hash256::random())]); + + rig.sync_manager.poll_proof_sync(); + let _ = rig.find_execution_proofs_by_range_request(); + assert!( + rig.sync_manager.proof_sync().by_range_request().is_some(), + "Range request should be in-flight after poll" + ); +} + +/// Test 3: With no proof-capable peer, `poll()` emits no request (soft failure). +/// Adding a peer and polling again sends the range request. +#[test] +fn test_proof_sync_no_peer_stays_pending() { + let mut rig = TestRig::test_setup(); + rig.harness.advance_slot(); + + rig.sync_manager.start_proof_sync(); + // Inject a fully-missing proof so the size comparison prefers range when a peer is found. + rig.sync_manager.proof_sync_mut().test_missing_proofs = + Some(vec![missing_proof(Hash256::random())]); + rig.sync_manager.poll_proof_sync(); + rig.expect_no_execution_proof_range_request(); + assert_eq!( + rig.sync_manager.proof_sync().state(), + ProofSyncState::Syncing + ); + assert!(rig.sync_manager.proof_sync().by_range_request().is_none()); + + // Adding a proof-capable peer; the next poll sends the request. + let _proof_peer = rig.new_proof_peer_with_status(1); + rig.sync_manager.poll_proof_sync(); + let _ = rig.find_execution_proofs_by_range_request(); + assert!(rig.sync_manager.proof_sync().by_range_request().is_some()); +} + +/// Test 4: While a range request is in-flight, `poll()` must not send any new requests. +#[test] +fn test_proof_sync_in_flight_poll_is_noop() { + let mut rig = TestRig::test_setup(); + let _proof_peer = rig.new_proof_peer_with_status(1); + rig.harness.advance_slot(); + + rig.sync_manager.start_proof_sync(); + rig.sync_manager.proof_sync_mut().test_missing_proofs = + Some(vec![missing_proof(Hash256::random())]); + rig.sync_manager.poll_proof_sync(); + let _ = rig.find_execution_proofs_by_range_request(); + rig.drain_execution_proof_status_requests(); + assert!(rig.sync_manager.proof_sync().by_range_request().is_some()); + + // A second poll while a range request is in-flight should produce nothing. + rig.sync_manager.poll_proof_sync(); + rig.expect_empty_network(); + assert!(rig.sync_manager.proof_sync().by_range_request().is_some()); +} + +/// Test 5: Stream termination with the correct ID clears the in-flight range request and +/// starts a post-request cooldown; state stays `Syncing`. +#[test] +fn test_proof_sync_range_termination_clears_request() { + let mut rig = TestRig::test_setup(); + let _proof_peer = rig.new_proof_peer_with_status(1); + rig.harness.advance_slot(); + + rig.sync_manager.start_proof_sync(); + rig.sync_manager.proof_sync_mut().test_missing_proofs = + Some(vec![missing_proof(Hash256::random())]); + rig.sync_manager.poll_proof_sync(); + let (req_id, peer_id) = rig.find_execution_proofs_by_range_request(); + assert!(rig.sync_manager.proof_sync().by_range_request().is_some()); + + rig.terminate_execution_proofs_by_range(req_id, peer_id); + assert_eq!( + rig.sync_manager.proof_sync().state(), + ProofSyncState::Syncing, + "State should remain Syncing after range termination" + ); + assert!( + rig.sync_manager.proof_sync().by_range_request().is_none(), + "Range request should be cleared after stream termination" + ); + // The cooldown blocks a new request for one slot tick. + rig.sync_manager.poll_proof_sync(); + rig.expect_no_execution_proof_range_request(); + assert!(rig.sync_manager.proof_sync().by_root_request().is_none()); +} + +/// Test 6: Stream termination with a wrong ID is ignored; the range request stays in-flight. +#[test] +fn test_proof_sync_wrong_id_termination_ignored() { + let mut rig = TestRig::test_setup(); + let _proof_peer = rig.new_proof_peer_with_status(1); + rig.harness.advance_slot(); + + rig.sync_manager.start_proof_sync(); + rig.sync_manager.proof_sync_mut().test_missing_proofs = + Some(vec![missing_proof(Hash256::random())]); + rig.sync_manager.poll_proof_sync(); + let (_req_id, peer_id) = rig.find_execution_proofs_by_range_request(); + assert!(rig.sync_manager.proof_sync().by_range_request().is_some()); + + // Terminate with a different (fake) ID — should be ignored. + let fake_id = ExecutionProofsByRangeRequestId { id: 9999 }; + rig.terminate_execution_proofs_by_range(fake_id, peer_id); + assert!( + rig.sync_manager.proof_sync().by_range_request().is_some(), + "Wrong ID should not clear the in-flight range request" + ); +} + +/// Test 7: With no missing proofs, `poll()` in `Syncing` is a no-op. +#[test] +fn test_proof_sync_fill_mode_no_missing_proofs() { + let mut rig = TestRig::test_setup(); + let _proof_peer = rig.new_proof_peer_with_status(0); + + // At genesis (gap = 0) start() transitions directly to by-root fill behaviour. + rig.sync_manager.start_proof_sync(); + rig.drain_execution_proof_status_requests(); + + // test_missing_proofs is None → chain returns empty → no requests sent. + rig.sync_manager.poll_proof_sync(); + rig.expect_empty_network(); + assert_eq!( + rig.sync_manager.proof_sync().state(), + ProofSyncState::Syncing + ); +} + +/// Test 8: With partially-missing proofs, `poll()` sends all roots in a single batched +/// `ExecutionProofsByRoot` request (partial entries make range more expensive than root). +#[test] +fn test_proof_sync_fill_mode_issues_by_root_requests() { + let mut rig = TestRig::test_setup(); + let _proof_peer = rig.new_proof_peer_with_status(0); + + rig.sync_manager.start_proof_sync(); + rig.drain_execution_proof_status_requests(); + + let missing = vec![ + partial_missing_proof(Hash256::random()), + partial_missing_proof(Hash256::random()), + ]; + rig.sync_manager.proof_sync_mut().test_missing_proofs = Some(missing); + rig.sync_manager.poll_proof_sync(); + + // All roots go in one batched request. + let _ = rig.find_execution_proofs_by_root_request(); + assert!(rig.sync_manager.proof_sync().by_root_request().is_some()); + rig.expect_empty_network(); +} + +/// Test 9: All missing proofs are included in one batch; a second poll while the batch +/// is in-flight emits no new requests. +#[test] +fn test_proof_sync_fill_mode_batches_all_roots() { + let mut rig = TestRig::test_setup(); + let _proof_peer = rig.new_proof_peer_with_status(0); + + rig.sync_manager.start_proof_sync(); + rig.drain_execution_proof_status_requests(); + + // Seed 6 distinct partial proofs; all go into one by-root batch request. + let missing: Vec = (0..6) + .map(|_| partial_missing_proof(Hash256::random())) + .collect(); + rig.sync_manager.proof_sync_mut().test_missing_proofs = Some(missing); + rig.sync_manager.poll_proof_sync(); + + // Exactly one batch request for all 6 roots. + let _ = rig.find_execution_proofs_by_root_request(); + assert!( + rig.sync_manager.proof_sync().by_root_request().is_some(), + "All roots go in a single in-flight batch" + ); + rig.expect_empty_network(); +} + +/// Test 10: While a by-root batch is in-flight, a second poll emits no new requests. +#[test] +fn test_proof_sync_fill_mode_skips_while_batch_in_flight() { + let mut rig = TestRig::test_setup(); + let _proof_peer = rig.new_proof_peer_with_status(0); + + rig.sync_manager.start_proof_sync(); + rig.drain_execution_proof_status_requests(); + + let missing = vec![ + partial_missing_proof(Hash256::random()), + partial_missing_proof(Hash256::random()), + ]; + rig.sync_manager.proof_sync_mut().test_missing_proofs = Some(missing); + + // First poll: one batch request with both roots. + rig.sync_manager.poll_proof_sync(); + let _ = rig.find_execution_proofs_by_root_request(); + assert!(rig.sync_manager.proof_sync().by_root_request().is_some()); + + // Second poll while batch is in-flight: no new requests. + rig.sync_manager.poll_proof_sync(); + rig.expect_empty_network(); + assert!( + rig.sync_manager.proof_sync().by_root_request().is_some(), + "In-flight batch should be unchanged on second poll" + ); +} + +/// Test 11: With no proof-capable peer, `poll()` in `Syncing` emits no requests. +#[test] +fn test_proof_sync_fill_mode_no_peer_breaks() { + let mut rig = TestRig::test_setup(); + // Force Syncing with no proof-capable peer — best_proof_peer() returns None. + rig.sync_manager + .proof_sync_mut() + .set_state(ProofSyncState::Syncing); + + let missing = vec![ + partial_missing_proof(Hash256::random()), + partial_missing_proof(Hash256::random()), + ]; + rig.sync_manager.proof_sync_mut().test_missing_proofs = Some(missing); + rig.sync_manager.poll_proof_sync(); + + rig.expect_empty_network(); + assert!( + rig.sync_manager.proof_sync().by_root_request().is_none(), + "NoPeer should leave no in-flight root request" + ); +} + +/// Test 12: `on_root_request_terminated` removes the entry from `in_flight`. +#[test] +fn test_proof_sync_on_request_terminated_clears_in_flight() { + let mut rig = TestRig::test_setup(); + let _proof_peer = rig.new_proof_peer_with_status(0); + + rig.sync_manager.start_proof_sync(); + rig.drain_execution_proof_status_requests(); + + let missing = vec![partial_missing_proof(Hash256::random())]; + rig.sync_manager.proof_sync_mut().test_missing_proofs = Some(missing); + rig.sync_manager.poll_proof_sync(); + + let (req_id, peer_id) = rig.find_execution_proofs_by_root_request(); + assert!(rig.sync_manager.proof_sync().by_root_request().is_some()); + + rig.terminate_execution_proofs_by_root(req_id, peer_id); + assert!( + rig.sync_manager.proof_sync().by_root_request().is_none(), + "root_request should be None after termination" + ); +} + +/// Test 13: `pause()` resets state to `Idle` but leaves in-flight by-root requests open +/// so that any responses that arrive can still be processed. +#[test] +fn test_proof_sync_pause_resets_to_idle() { + let mut rig = TestRig::test_setup(); + let _proof_peer = rig.new_proof_peer_with_status(0); + + rig.sync_manager.start_proof_sync(); + rig.drain_execution_proof_status_requests(); + + // Seed some partial proofs; poll sends one by-root batch request. + let missing = vec![ + partial_missing_proof(Hash256::random()), + partial_missing_proof(Hash256::random()), + ]; + rig.sync_manager.proof_sync_mut().test_missing_proofs = Some(missing); + rig.sync_manager.poll_proof_sync(); + let _ = rig.find_execution_proofs_by_root_request(); + assert!(rig.sync_manager.proof_sync().by_root_request().is_some()); + + // Pause resets state but preserves the in-flight by-root request. + rig.sync_manager.proof_sync_mut().pause(); + assert_eq!(rig.sync_manager.proof_sync().state(), ProofSyncState::Idle); + assert!( + rig.sync_manager.proof_sync().by_root_request().is_some(), + "in-flight by-root request is preserved across pause so responses can still land" + ); + + // Polling in Idle emits nothing new. + rig.sync_manager.poll_proof_sync(); + rig.expect_empty_network(); +} + +/// Test 14: Re-entering range sync (pause) then completing again issues a fresh range request. +#[test] +fn test_proof_sync_re_enter_range_resets_then_restarts() { + let mut rig = TestRig::test_setup(); + let _proof_peer = rig.new_proof_peer_with_status(1); + rig.harness.advance_slot(); + + // Inject a fully-missing proof so the size comparison prefers range. + rig.sync_manager.proof_sync_mut().test_missing_proofs = + Some(vec![missing_proof(Hash256::random())]); + + // First range request cycle. + rig.sync_manager.start_proof_sync(); + rig.sync_manager.poll_proof_sync(); + let (req_id, peer_id) = rig.find_execution_proofs_by_range_request(); + rig.terminate_execution_proofs_by_range(req_id, peer_id); + assert!(rig.sync_manager.proof_sync().by_range_request().is_none()); + + // Re-entering range sync pauses ProofSync. + rig.sync_manager.proof_sync_mut().pause(); + assert_eq!(rig.sync_manager.proof_sync().state(), ProofSyncState::Idle); + + // Range sync completes again → start() → Syncing. + rig.sync_manager.start_proof_sync(); + assert_eq!( + rig.sync_manager.proof_sync().state(), + ProofSyncState::Syncing + ); + + // New poll sends a fresh range request (same missing proof data still injected). + rig.sync_manager.poll_proof_sync(); + let _ = rig.find_execution_proofs_by_range_request(); + assert!(rig.sync_manager.proof_sync().by_range_request().is_some()); +} + +/// Test 15: With no missing proofs reported by the proof engine, no request of any kind +/// is emitted and the state stays `Syncing`. +#[test] +fn test_proof_sync_no_missing_proofs_no_request() { + let mut rig = TestRig::test_setup(); + let _proof_peer = rig.new_proof_peer_with_status(0); + + rig.sync_manager.start_proof_sync(); + rig.drain_execution_proof_status_requests(); + // test_missing_proofs = None → chain.missing_execution_proofs() returns empty. + rig.sync_manager.poll_proof_sync(); + + rig.expect_empty_network(); + assert_eq!( + rig.sync_manager.proof_sync().state(), + ProofSyncState::Syncing + ); + assert!(rig.sync_manager.proof_sync().by_range_request().is_none()); + assert!(rig.sync_manager.proof_sync().by_root_request().is_none()); +} + +/// Test 16: A proof arriving on an `ExecutionProofsByRange` stream must be forwarded +/// to the beacon processor as `GossipExecutionProof` work. +#[test] +fn test_proof_sync_range_response_forwarded_to_processor() { + let mut rig = TestRig::test_setup(); + let _proof_peer = rig.new_proof_peer_with_status(1); + rig.harness.advance_slot(); + + rig.sync_manager.start_proof_sync(); + rig.sync_manager.proof_sync_mut().test_missing_proofs = + Some(vec![missing_proof(Hash256::random())]); + rig.sync_manager.poll_proof_sync(); + let (req_id, peer_id) = rig.find_execution_proofs_by_range_request(); + assert!(rig.sync_manager.proof_sync().by_range_request().is_some()); + + // Send a proof (non-termination) response. + rig.send_sync_message(SyncMessage::RpcExecutionProof { + sync_request_id: SyncRequestId::ExecutionProofsByRange(req_id), + peer_id, + execution_proof: Some(make_signed_proof()), + }); + + rig.pop_received_processor_event(|ev| { + (ev.work_type() == WorkType::GossipExecutionProof).then_some(()) + }) + .unwrap_or_else(|e| panic!("Expected GossipExecutionProof work event: {e:?}")); + + // Range request is still in-flight (stream not yet terminated). + assert!(rig.sync_manager.proof_sync().by_range_request().is_some()); +} + +/// Test 17: A proof arriving on an `ExecutionProofsByRoot` stream must be forwarded +/// to the beacon processor as `GossipExecutionProof` work. +#[test] +fn test_proof_sync_root_response_forwarded_to_processor() { + let mut rig = TestRig::test_setup(); + let _proof_peer = rig.new_proof_peer_with_status(0); + + // Partial missing proofs are cheaper via by-root than range. + rig.sync_manager.start_proof_sync(); + rig.drain_execution_proof_status_requests(); + + let missing = vec![partial_missing_proof(Hash256::random())]; + rig.sync_manager.proof_sync_mut().test_missing_proofs = Some(missing); + rig.sync_manager.poll_proof_sync(); + + let (req_id, peer_id) = rig.find_execution_proofs_by_root_request(); + + // Send a proof (non-termination) response on the by-root stream. + rig.send_sync_message(SyncMessage::RpcExecutionProof { + sync_request_id: SyncRequestId::ExecutionProofsByRoot(req_id), + peer_id, + execution_proof: Some(make_signed_proof()), + }); + + rig.pop_received_processor_event(|ev| { + (ev.work_type() == WorkType::GossipExecutionProof).then_some(()) + }) + .unwrap_or_else(|e| panic!("Expected GossipExecutionProof work event: {e:?}")); +} + +/// Test 18: A peer that claims an implausible block root (slot we have, wrong root) +/// must be ignored — an implausible status must not overwrite a valid cached entry. +#[test] +fn test_implausible_block_root_ignored() { + let mut rig = TestRig::test_setup(); + let proof_peer = rig.new_connected_proof_capable_peer(); + + // Send a valid inbound status at slot 0 with the correct genesis block root. + let genesis_root = rig + .harness + .chain + .canonical_head + .cached_head() + .head_block_root(); + rig.send_sync_message(SyncMessage::RpcExecutionProofStatus { + peer_id: proof_peer, + request_id: None, + status: ExecutionProofStatus { + slot: 0, + block_root: genesis_root, + }, + }); + + assert!( + rig.sync_manager + .proof_sync() + .peer_status(&proof_peer) + .is_some(), + "Peer should be cached after valid status" + ); + assert_eq!( + rig.sync_manager + .proof_sync() + .peer_status(&proof_peer) + .map(|s| s.verified), + Some(true), + "Cached entry should be verified" + ); + + // Send an inbound status with a wrong block root for a slot we have. + rig.send_sync_message(SyncMessage::RpcExecutionProofStatus { + peer_id: proof_peer, + request_id: None, + status: ExecutionProofStatus { + slot: 0, + block_root: Hash256::random(), + }, + }); + + // The implausible status must be dropped; the original valid entry should remain. + assert!( + rig.sync_manager + .proof_sync() + .peer_status(&proof_peer) + .is_some(), + "Valid cached entry must survive an implausible status update" + ); + assert_eq!( + rig.sync_manager + .proof_sync() + .peer_status(&proof_peer) + .map(|s| s.verified), + Some(true), + "Cached entry should still be verified after implausible status" + ); +} + +/// Test 19: A peer that claims a slot ahead of our head is cached optimistically +/// with `verified = false`. +#[test] +fn test_optimistic_caching_for_ahead_peer() { + let mut rig = TestRig::test_setup(); + let proof_peer = rig.new_connected_proof_capable_peer(); + + // best_slot() == 0 at genesis; slot 999 is well ahead of our head. + rig.send_sync_message(SyncMessage::RpcExecutionProofStatus { + peer_id: proof_peer, + request_id: None, // inbound (peer-initiated) + status: ExecutionProofStatus { + slot: 999, + block_root: Hash256::random(), + }, + }); + + assert!( + rig.sync_manager + .proof_sync() + .peer_status(&proof_peer) + .is_some(), + "Ahead-of-head peer should be cached optimistically" + ); + assert_eq!( + rig.sync_manager + .proof_sync() + .peer_status(&proof_peer) + .map(|s| s.verified), + Some(false), + "Ahead-of-head peer must be cached as unverified" + ); +} + +/// Test 20: `start()` re-requests status for a peer whose cached entry is unverified. +/// +/// `needs_refresh()` returns `true` for unverified entries, so a subsequent `start()` +/// should issue a fresh `ExecutionProofStatus` request for that peer. +#[test] +fn test_start_refreshes_unverified_entries() { + let mut rig = TestRig::test_setup(); + let proof_peer = rig.new_connected_proof_capable_peer(); + + // Override the peer's initial (verified) cache entry with an optimistic one by sending + // an inbound status with a slot beyond our head — this makes the entry unverified. + rig.send_sync_message(SyncMessage::RpcExecutionProofStatus { + peer_id: proof_peer, + request_id: None, + status: ExecutionProofStatus { + slot: 999, + block_root: Hash256::random(), + }, + }); + assert_eq!( + rig.sync_manager + .proof_sync() + .peer_status(&proof_peer) + .map(|s| s.verified), + Some(false), + "Entry should be unverified after optimistic caching" + ); + + // First start(): peer is unverified → needs_refresh=true → sends a status request. + rig.sync_manager.start_proof_sync(); + let (id1, _) = rig.find_execution_proof_status_request(); + + // Respond with another optimistic status so the entry stays unverified. + rig.send_sync_message(SyncMessage::RpcExecutionProofStatus { + peer_id: proof_peer, + request_id: Some(id1), + status: ExecutionProofStatus { + slot: 999, + block_root: Hash256::random(), + }, + }); + assert_eq!( + rig.sync_manager + .proof_sync() + .peer_status(&proof_peer) + .map(|s| s.verified), + Some(false), + "Entry should still be unverified" + ); + + // Pause and restart — start() must re-request because the entry is still unverified. + rig.sync_manager.proof_sync_mut().pause(); + rig.sync_manager.start_proof_sync(); + + // If this succeeds, start() issued a fresh status request for the unverified peer. + let (_id2, peer2) = rig.find_execution_proof_status_request(); + assert_eq!( + peer2, proof_peer, + "New status request should target the same proof peer" + ); +} + +/// Test 21: An inbound `ExecutionProofStatus` (peer-initiated, `request_id = None`) +/// populates the proof-sync peer cache exactly as an outbound response does. +#[test] +fn test_inbound_status_populates_cache() { + let mut rig = TestRig::test_setup(); + let proof_peer = rig.new_connected_proof_capable_peer(); + + // Simulate a peer-initiated status exchange: the peer sends us their status. + rig.send_sync_message(SyncMessage::RpcExecutionProofStatus { + peer_id: proof_peer, + request_id: None, + status: ExecutionProofStatus { + slot: 42, + block_root: Hash256::random(), + }, + }); + + assert!( + rig.sync_manager + .proof_sync() + .peer_status(&proof_peer) + .is_some(), + "Inbound status must populate the cache" + ); + // Slot 42 > best_slot(0) → optimistically cached as unverified. + assert_eq!( + rig.sync_manager + .proof_sync() + .peer_status(&proof_peer) + .map(|s| s.verified), + Some(false), + "Slot ahead of head must be cached as unverified" + ); +} + +/// Test 22: `local_execution_proof_status` can be set and read back via `network_globals`. +/// +/// This verifies the `NetworkGlobals` getter/setter used by the proof-verified callback path +/// in `gossip_methods.rs`. +#[test] +fn test_local_execution_proof_status_read_write() { + let rig = TestRig::test_setup(); + + // Default should be zero slot. + let initial = rig.network_globals.local_execution_proof_status(); + assert_eq!(initial.slot, 0); + + // Set a new status and read it back. + let block_root = Hash256::repeat_byte(0xab); + rig.network_globals + .set_local_execution_proof_status(ExecutionProofStatus { + slot: 42, + block_root, + }); + + let updated = rig.network_globals.local_execution_proof_status(); + assert_eq!(updated.slot, 42); + assert_eq!(updated.block_root, block_root); +} + +/// Test 23: For a single fully-missing proof, the range request covers exactly that slot +/// (start_slot = proof slot, count = 1). +#[test] +fn test_proof_sync_range_covers_single_missing_slot() { + let mut rig = TestRig::test_setup(); + let _proof_peer = rig.new_proof_peer_with_status(10); + rig.harness.advance_slot(); + + rig.sync_manager.proof_sync_mut().test_missing_proofs = Some(vec![MissingProofInfo { + root: Hash256::random(), + existing_proof_types: vec![], + slot: Slot::new(5), + }]); + + rig.sync_manager.start_proof_sync(); + rig.sync_manager.poll_proof_sync(); + + let (_, start_slot, count) = rig.find_execution_proofs_by_range_request_params(); + assert_eq!( + start_slot.as_u64(), + 5, + "start_slot should be the proof's slot" + ); + assert_eq!(count, 1, "count should be 1 for a single missing slot"); +} + +/// Test 24: When all missing proofs have slots beyond the peer's announced slot, no request +/// is issued (the peer cannot serve them yet). +#[test] +fn test_proof_sync_no_request_when_missing_slot_ahead_of_peer() { + let mut rig = TestRig::test_setup(); + + // Peer only has proofs up to slot 5. + let _proof_peer = rig.new_proof_peer_with_status(5); + + // Inject a proof at slot 6 — beyond what the peer can serve. + rig.sync_manager.proof_sync_mut().test_missing_proofs = Some(vec![MissingProofInfo { + root: Hash256::random(), + existing_proof_types: vec![], + slot: Slot::new(6), + }]); + + rig.sync_manager.start_proof_sync(); + rig.drain_execution_proof_status_requests(); + rig.sync_manager.poll_proof_sync(); + + // No request should be emitted since the only missing slot is beyond peer_slot = 5. + rig.expect_no_execution_proof_range_request(); + rig.expect_empty_network(); +} + +/// Test 25: Non-consecutive fully-missing slots are covered by a single range request whose +/// `count` spans from the first slot to the last (inclusive), bridging any gaps. +/// +/// By-range request size = 20 bytes (no partial filters). +/// By-root request size = 44 + 44 = 88 bytes. +/// Range wins; count = last_slot − first_slot + 1. +#[test] +fn test_proof_sync_range_spans_non_consecutive_slots() { + let mut rig = TestRig::test_setup(); + let _proof_peer = rig.new_proof_peer_with_status(20); + rig.harness.advance_slot(); + + // Two fully-missing proofs at slots 5 and 10 (not consecutive). + rig.sync_manager.proof_sync_mut().test_missing_proofs = Some(vec![ + MissingProofInfo { + root: Hash256::random(), + existing_proof_types: vec![], + slot: Slot::new(5), + }, + MissingProofInfo { + root: Hash256::random(), + existing_proof_types: vec![], + slot: Slot::new(10), + }, + ]); + + rig.sync_manager.start_proof_sync(); + rig.sync_manager.poll_proof_sync(); + + let (_, start_slot, count) = rig.find_execution_proofs_by_range_request_params(); + assert_eq!( + start_slot.as_u64(), + 5, + "range should start at the earliest missing slot" + ); + assert_eq!( + count, 6, + "count should span from slot 5 to slot 10 inclusive (10 - 5 + 1 = 6)" + ); +} + +/// Test 26: When all missing proofs are partially held (some proof types already present), +/// every block must appear in `proof_filters` — making the range request larger than root. +/// The size comparison picks root even when slots are consecutive. +/// +/// By-range size = 20 + 43 + 43 = 106 bytes (both entries in proof_filters). +/// By-root size = 43 + 43 = 86 bytes. +/// Root wins. +/// +/// A mixed set (one fully-missing + one partial) is also tested: range wins there because +/// the fully-missing entry is free in range (not in proof_filters). +/// By-range = 20 + 43 = 63 bytes, by-root = 44 + 43 = 87 bytes → range cheaper. +#[test] +fn test_proof_sync_range_vs_root_size_decision() { + // ── All partial → root chosen ────────────────────────────────────────────── + { + let mut rig = TestRig::test_setup(); + let _proof_peer = rig.new_proof_peer_with_status(10); + rig.harness.advance_slot(); + + rig.sync_manager.proof_sync_mut().test_missing_proofs = Some(vec![ + partial_missing_proof(Hash256::random()), + partial_missing_proof(Hash256::random()), + ]); + rig.sync_manager.start_proof_sync(); + rig.drain_execution_proof_status_requests(); + rig.sync_manager.poll_proof_sync(); + + rig.expect_no_execution_proof_range_request(); + let _ = rig.find_execution_proofs_by_root_request(); + } + + // ── One fully-missing + one partial → range chosen ───────────────────────── + { + let mut rig = TestRig::test_setup(); + let _proof_peer = rig.new_proof_peer_with_status(10); + rig.harness.advance_slot(); + + rig.sync_manager.proof_sync_mut().test_missing_proofs = Some(vec![ + // fully missing at slot 0 — free in range, costs 44 in root + missing_proof(Hash256::random()), + // partial at slot 0 — costs 43 in both range (as filter) and root + partial_missing_proof(Hash256::random()), + ]); + rig.sync_manager.start_proof_sync(); + rig.sync_manager.poll_proof_sync(); + + // range_bytes = 20 + 43 = 63 < root_bytes = 44 + 43 = 87 + let (_, _, count) = rig.find_execution_proofs_by_range_request_params(); + assert_eq!(count, 1, "both entries are at slot 0; count = 1"); + } +} diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index e4c7c6ff1fe..3482ff6df83 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -817,8 +817,34 @@ pub fn cli_app() -> Command { .alias("execution-endpoints") .help("Server endpoint for an execution layer JWT-authenticated HTTP \ JSON-RPC connection. Uses the same endpoint to populate the \ - deposit cache.") - .required(true) + deposit cache. Optional - at least one of --execution-endpoint \ + or --proof-engine-endpoint must be provided.") + .required(false) + .action(ArgAction::Set) + .display_order(0) + ) + .arg( + Arg::new("proof-engine-endpoint") + .long("proof-engine-endpoint") + .value_name("PROOF-ENGINE-ENDPOINT") + .help("Server endpoint for an EIP-8025 proof engine HTTP JSON-RPC connection. \ + Does not require JWT authentication. Optional - at least one of \ + --execution-endpoint or --proof-engine-endpoint must be provided.") + .required(false) + .action(ArgAction::Set) + .display_order(0) + ) + .arg( + Arg::new("proof-types") + .long("proof-types") + .value_name("PROOF-TYPES") + .help("Comma-separated list of EIP-8025 proof type identifiers (u8) to request \ + from peers via ExecutionProofsByRoot and ExecutionProofsByRange RPC. \ + If not specified, defaults to '0,1,2,3' \ + (EthrexRisc0, EthrexSP1, EthrexZisk, RethOpenVM).") + .requires("proof-engine-endpoint") + .value_delimiter(',') + .value_parser(clap::value_parser!(u8)) .action(ArgAction::Set) .display_order(0) ) diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 26dd3b6642e..b36c9e2a7e3 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -14,6 +14,7 @@ use client::{ClientConfig, ClientGenesis}; use directory::{DEFAULT_BEACON_NODE_DIR, DEFAULT_NETWORK_DIR, DEFAULT_ROOT_DIR}; use environment::RuntimeContext; use execution_layer::DEFAULT_JWT_FILE; +use execution_layer::eip8025::types::{ProofType, ProofTypes}; use http_api::TlsConfig; use lighthouse_network::{Enr, Multiaddr, NetworkConfig, PeerIdSerialized, multiaddr::Protocol}; use network_utils::listen_addr::ListenAddress; @@ -269,42 +270,73 @@ pub fn get_config( client_config.http_metrics.allocator_metrics_enabled = false; } - // `--execution-endpoint` is required now. - let endpoints: String = clap_utils::parse_required(cli_args, "execution-endpoint")?; + // Parse execution endpoint (optional) + let execution_endpoint: Option = + if let Some(endpoints) = cli_args.get_one::("execution-endpoint") { + Some(parse_only_one_value( + endpoints.as_str(), + SensitiveUrl::parse, + "--execution-endpoint", + )?) + } else { + None + }; + + // Parse proof engine endpoint (optional) + let proof_engine_endpoint: Option = + if let Some(endpoints) = cli_args.get_one::("proof-engine-endpoint") { + Some(parse_only_one_value( + endpoints.as_str(), + SensitiveUrl::parse, + "--proof-engine-endpoint", + )?) + } else { + None + }; + + // Validation: at least one endpoint must be provided + if execution_endpoint.is_none() && proof_engine_endpoint.is_none() { + return Err( + "At least one of --execution-endpoint or --proof-engine-endpoint must be provided" + .to_string(), + ); + } + let mut el_config = execution_layer::Config::default(); - // Parse a single execution endpoint, logging warnings if multiple endpoints are supplied. - let execution_endpoint = parse_only_one_value( - endpoints.as_str(), - SensitiveUrl::parse, - "--execution-endpoint", - )?; - - // JWTs are required if `--execution-endpoint` is supplied. They can be either passed via - // file_path or directly as string. - let secret_file: PathBuf; - // Parse a single JWT secret from a given file_path, logging warnings if multiple are supplied. - if let Some(secret_files) = cli_args.get_one::("execution-jwt") { - secret_file = parse_only_one_value(secret_files, PathBuf::from_str, "--execution-jwt")?; - // Check if the JWT secret key is passed directly via cli flag and persist it to the default - // file location. - } else if let Some(jwt_secret_key) = cli_args.get_one::("execution-jwt-secret-key") { - use std::fs::File; - use std::io::Write; - secret_file = client_config.data_dir().join(DEFAULT_JWT_FILE); - let mut jwt_secret_key_file = File::create(secret_file.clone()) - .map_err(|e| format!("Error while creating jwt_secret_key file: {:?}", e))?; - jwt_secret_key_file - .write_all(jwt_secret_key.as_bytes()) - .map_err(|e| { - format!( - "Error occurred while writing to jwt_secret_key file: {:?}", - e - ) - })?; + // JWT is required only if execution_endpoint is provided + let secret_file: Option = if execution_endpoint.is_some() { + // Parse a single JWT secret from a given file_path, logging warnings if multiple are supplied. + if let Some(secret_files) = cli_args.get_one::("execution-jwt") { + Some(parse_only_one_value( + secret_files, + PathBuf::from_str, + "--execution-jwt", + )?) + // Check if the JWT secret key is passed directly via cli flag and persist it to the default + // file location. + } else if let Some(jwt_secret_key) = cli_args.get_one::("execution-jwt-secret-key") + { + use std::fs::File; + use std::io::Write; + let secret_file_path = client_config.data_dir().join(DEFAULT_JWT_FILE); + let mut jwt_secret_key_file = File::create(secret_file_path.clone()) + .map_err(|e| format!("Error while creating jwt_secret_key file: {:?}", e))?; + jwt_secret_key_file + .write_all(jwt_secret_key.as_bytes()) + .map_err(|e| { + format!( + "Error occurred while writing to jwt_secret_key file: {:?}", + e + ) + })?; + Some(secret_file_path) + } else { + return Err("--execution-jwt or --execution-jwt-secret-key is required when using --execution-endpoint".to_string()); + } } else { - return Err("Error! Please set either --execution-jwt file_path or --execution-jwt-secret-key directly via cli when using --execution-endpoint".to_string()); - } + None + }; // Parse and set the payload builder, if any. if let Some(endpoint) = cli_args.get_one::("builder") { @@ -321,8 +353,24 @@ pub fn get_config( } // Set config values from parse values. - el_config.secret_file = Some(secret_file.clone()); - el_config.execution_endpoint = Some(execution_endpoint.clone()); + el_config.secret_file = secret_file; + el_config.execution_endpoint = execution_endpoint; + el_config.proof_engine_endpoint = proof_engine_endpoint; + el_config.proof_types = if let Some(vals) = cli_args.get_many::("proof-types") { + let types = vals + .copied() + .map(ProofType::from_u8) + .collect::, _>>() + .map_err(|e| format!("Invalid --proof-types value: {e:?}"))?; + ProofTypes::from(types) + } else { + ProofTypes::default() + }; + // Gate execution proof gossip subscription on proof engine being configured. + client_config.network.enable_execution_proof = el_config.proof_engine_endpoint.is_some(); + // Mirror proof types as u8 wire values for the network layer. + client_config.network.proof_types = + Some(el_config.proof_types.iter().map(|t| t.to_u8()).collect()); el_config.suggested_fee_recipient = clap_utils::parse_optional(cli_args, "suggested-fee-recipient")?; el_config.jwt_id = clap_utils::parse_optional(cli_args, "execution-jwt-id")?; diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 8eec4d5eceb..e781eb6d9ee 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -44,6 +44,12 @@ use types::data::{ColumnIndex, DataColumnSidecar, DataColumnSidecarList}; use types::*; use zstd::{Decoder, Encoder}; +/// Number of entries in the always-on EIP-8025 request root <-> block root mapping caches. +/// +/// Sized to cover several epochs of recent blocks: proofs are expected to arrive well within +/// this window after a block is imported. +const EIP8025_REQUEST_ROOT_CACHE_SIZE: usize = 512; + /// On-disk database that stores finalized states efficiently. /// /// Stores vector fields like the `block_roots` and `state_roots` separately, and only stores @@ -73,6 +79,16 @@ pub struct HotColdDB, Cold: ItemStore> { pub hot_db: Hot, /// LRU cache of deserialized blocks and blobs. Updated whenever a block or blob is loaded. block_cache: Option>>, + /// EIP-8025: always-on cache mapping request_root -> block_root. + /// + /// Kept separate from `block_cache` so it is always available regardless of whether the + /// block cache is enabled. Required for proof verification to look up the beacon block root + /// associated with an execution payload. + request_root_to_block_root: Mutex>, + /// EIP-8025: always-on cache mapping block_root -> request_root. + /// + /// Used by the HTTP API to retrieve the request root for a given block root. + block_root_to_request_root: Mutex>, /// Cache of beacon states. /// /// LOCK ORDERING: this lock must always be locked *after* the `split` if both are required. @@ -227,6 +243,8 @@ impl HotColdDB, MemoryStore> { // NOTE: Anchor slot is initialized to 0, which is only valid for new DBs. We shouldn't // be reusing memory stores, but if we want to do that we should redo this. + let eip8025_cache_size = NonZeroUsize::new(EIP8025_REQUEST_ROOT_CACHE_SIZE) + .expect("EIP8025_REQUEST_ROOT_CACHE_SIZE is non-zero"); let db = HotColdDB { split: RwLock::new(Split::default()), anchor_info: RwLock::new(ANCHOR_UNINITIALIZED), @@ -238,6 +256,8 @@ impl HotColdDB, MemoryStore> { block_cache: NonZeroUsize::new(config.block_cache_size) .map(BlockCache::new) .map(Mutex::new), + request_root_to_block_root: Mutex::new(LruCache::new(eip8025_cache_size)), + block_root_to_request_root: Mutex::new(LruCache::new(eip8025_cache_size)), state_cache: Mutex::new(StateCache::new( config.state_cache_size, config.state_cache_headroom, @@ -281,6 +301,8 @@ impl HotColdDB, BeaconNodeBackend> { let anchor_info = RwLock::new(Self::load_anchor_info(&hot_db)?); debug!(?anchor_info, "Loaded anchor info"); + let eip8025_cache_size = NonZeroUsize::new(EIP8025_REQUEST_ROOT_CACHE_SIZE) + .expect("EIP8025_REQUEST_ROOT_CACHE_SIZE is non-zero"); let db = HotColdDB { split: RwLock::new(Split::default()), anchor_info, @@ -292,6 +314,8 @@ impl HotColdDB, BeaconNodeBackend> { block_cache: NonZeroUsize::new(config.block_cache_size) .map(BlockCache::new) .map(Mutex::new), + request_root_to_block_root: Mutex::new(LruCache::new(eip8025_cache_size)), + block_root_to_request_root: Mutex::new(LruCache::new(eip8025_cache_size)), state_cache: Mutex::new(StateCache::new( config.state_cache_size, config.state_cache_headroom, @@ -1027,6 +1051,37 @@ impl, Cold: ItemStore> HotColdDB } } + /// Store bidirectional mapping between request_root and block_root (EIP-8025). + /// + /// This is in-memory only and not persisted to database in the initial implementation. + pub fn put_request_root_mapping(&self, request_root: Hash256, block_root: Hash256, slot: Slot) { + self.request_root_to_block_root + .lock() + .put(request_root, (block_root, slot)); + self.block_root_to_request_root + .lock() + .put(block_root, request_root); + } + + /// Look up block_root and slot by request_root (EIP-8025, cache-only, no database). + pub fn get_block_root_by_request_root( + &self, + request_root: &Hash256, + ) -> Option<(Hash256, Slot)> { + self.request_root_to_block_root + .lock() + .get(request_root) + .copied() + } + + /// Look up request_root by block_root (EIP-8025, cache-only, no database). + pub fn get_request_root_by_block_root(&self, block_root: &Hash256) -> Option { + self.block_root_to_request_root + .lock() + .get(block_root) + .copied() + } + /// Store a state in the store. pub fn put_state(&self, state_root: &Hash256, state: &BeaconState) -> Result<(), Error> { let mut ops: Vec = Vec::new(); diff --git a/beacon_node/store/src/lib.rs b/beacon_node/store/src/lib.rs index ae5b2e1e571..1e39723b26c 100644 --- a/beacon_node/store/src/lib.rs +++ b/beacon_node/store/src/lib.rs @@ -379,6 +379,12 @@ pub enum DBColumn { /// The dummy table is used to force the db to sync #[strum(serialize = "dmy")] Dummy, + /// For persisting ProofEngine state (EIP-8025). + #[strum(serialize = "prf")] + ProofEngine, + /// For persisting banned validators that signed invalid execution proofs (EIP-8025). + #[strum(serialize = "ipt")] + InvalidProofTracker, } /// A block from the database, which might have an execution payload or not. @@ -421,7 +427,9 @@ impl DBColumn { | Self::BeaconRestorePoint | Self::DhtEnrs | Self::CustodyContext - | Self::OptimisticTransitionBlock => 32, + | Self::OptimisticTransitionBlock + | Self::ProofEngine + | Self::InvalidProofTracker => 32, Self::BeaconBlockRoots | Self::BeaconDataColumnCustodyInfo | Self::BeaconBlockRootsChunked diff --git a/beacon_node/store/src/metadata.rs b/beacon_node/store/src/metadata.rs index cf494684515..215cdb2b64d 100644 --- a/beacon_node/store/src/metadata.rs +++ b/beacon_node/store/src/metadata.rs @@ -4,7 +4,7 @@ use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use types::{Hash256, Slot}; -pub const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(28); +pub const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(29); // All the keys that get stored under the `BeaconMeta` column. // diff --git a/book/src/help_bn.md b/book/src/help_bn.md index 5f3c43a7e42..0e6074a283e 100644 --- a/book/src/help_bn.md +++ b/book/src/help_bn.md @@ -5,7 +5,7 @@ The primary component which connects to the Ethereum 2.0 P2P network and downloads, verifies and stores blocks. Provides a HTTP API for querying the beacon chain and publishing messages to the network. -Usage: lighthouse beacon_node [OPTIONS] --execution-endpoint +Usage: lighthouse beacon_node [OPTIONS] Options: --auto-compact-db @@ -125,6 +125,8 @@ Options: --execution-endpoint Server endpoint for an execution layer JWT-authenticated HTTP JSON-RPC connection. Uses the same endpoint to populate the deposit cache. + Optional - at least one of --execution-endpoint or + --proof-engine-endpoint must be provided. --execution-jwt File path which contains the hex-encoded JWT secret for the execution endpoint provided in the --execution-endpoint flag. @@ -304,6 +306,15 @@ Options: which don't improve their payload after the first call, and high values are useful for ensuring the EL is given ample notice. Default: 1/3 of a slot. + --proof-engine-endpoint + Server endpoint for an EIP-8025 proof engine HTTP JSON-RPC connection. + Does not require JWT authentication. Optional - at least one of + --execution-endpoint or --proof-engine-endpoint must be provided. + --proof-types + Comma-separated list of EIP-8025 proof type identifiers (u8) to + request from peers via ExecutionProofsByRoot and + ExecutionProofsByRange RPC. If not specified, defaults to '0,1,2,3' + (EthrexRisc0, EthrexSP1, EthrexZisk, RethOpenVM). --proposer-reorg-cutoff Maximum delay after the start of the slot at which to propose a reorging block. Lower values can prevent failed reorgs by ensuring the diff --git a/book/src/help_vc.md b/book/src/help_vc.md index 2a9936d1d2f..88bbefafd69 100644 --- a/book/src/help_vc.md +++ b/book/src/help_vc.md @@ -115,6 +115,14 @@ Options: --network Name of the Eth2 chain Lighthouse will sync and follow. [possible values: mainnet, gnosis, chiado, sepolia, holesky, hoodi] + --proof-engine-endpoint + URL of the proof engine HTTP JSON-RPC endpoint for EIP-8025 execution + proofs. When set, the validator client will proactively monitor for + new blocks and request execution proofs from this endpoint. + --proof-types + Comma-separated list of proof type identifiers (u8) to request from + the proof engine (e.g., 0,1,2). If not specified, defaults to + '0,1,2,3' (EthrexRisc0, EthrexSP1, EthrexZisk, RethOpenVM). --proposer-nodes Comma-separated addresses to one or more beacon node HTTP APIs. These specify nodes that are used to send beacon block proposals. A failure diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index 8746e3c063c..7f3e98a86ce 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -1755,6 +1755,36 @@ impl BeaconNodeHttpClient { Ok(()) } + /// `POST beacon/execution_proofs` + /// + /// Submit signed execution proofs for EIP-8025 optional execution verification. + pub async fn post_beacon_execution_proofs( + &self, + proofs: &[SignedExecutionProof], + ) -> Result<(), Error> { + use serde::Serialize; + + #[derive(Serialize)] + struct SubmitExecutionProofsRequest { + proofs: Vec, + } + + let mut path = self.eth_path(V1)?; + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("beacon") + .push("execution_proofs"); + + let request = SubmitExecutionProofsRequest { + proofs: proofs.to_vec(), + }; + + self.post(path, &request).await?; + + Ok(()) + } + /// `POST beacon/rewards/sync_committee` pub async fn post_beacon_rewards_sync_committee( &self, diff --git a/common/eth2/src/lighthouse_vc/http_client.rs b/common/eth2/src/lighthouse_vc/http_client.rs index 3c850fcb052..eeac02dfc4d 100644 --- a/common/eth2/src/lighthouse_vc/http_client.rs +++ b/common/eth2/src/lighthouse_vc/http_client.rs @@ -681,4 +681,21 @@ impl ValidatorClientHttpClient { let url = self.make_graffiti_url(pubkey)?; self.delete(url).await } + + pub async fn post_execution_proof( + &self, + pubkey: &PublicKeyBytes, + req: SignExecutionProofRequest, + ) -> Result { + let mut path = self.server.expose_full().clone(); + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("lighthouse") + .push("validators") + .push(&pubkey.to_string()) + .push("execution_proofs"); + + self.post_with_raw_response(path, &req).await + } } diff --git a/common/eth2/src/lighthouse_vc/types.rs b/common/eth2/src/lighthouse_vc/types.rs index 07f8421dc5c..18395eef67d 100644 --- a/common/eth2/src/lighthouse_vc/types.rs +++ b/common/eth2/src/lighthouse_vc/types.rs @@ -207,3 +207,10 @@ pub struct UpdateCandidatesRequest { pub struct UpdateCandidatesResponse { pub new_beacon_nodes_list: Vec, } + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct SignExecutionProofRequest { + pub execution_proof: types::ExecutionProof, + #[serde(default)] + pub epoch: Option, +} diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index b1a61ce00cc..2db5967d846 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -1174,6 +1174,17 @@ impl<'de> ContextDeserialize<'de, ForkName> for SseExtendedPayloadAttributes { } } +/// SSE event payload for a validated execution proof (EIP-8025). +/// +/// Emitted by the beacon node when an `ExecutionProof` passes verification, +/// allowing validator clients to resign the proof with their own key. +#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)] +pub struct SseExecutionProofValidated { + pub execution_proof: ExecutionProof, + #[serde(with = "serde_utils::quoted_u64")] + pub epoch: u64, +} + #[derive(PartialEq, Debug, Serialize, Clone)] #[serde(bound = "E: EthSpec", untagged)] pub enum EventKind { @@ -1197,6 +1208,7 @@ pub enum EventKind { AttesterSlashing(Box>), BlsToExecutionChange(Box), BlockGossip(Box), + ExecutionProofValidated(SseExecutionProofValidated), } impl EventKind { @@ -1222,6 +1234,7 @@ impl EventKind { EventKind::AttesterSlashing(_) => "attester_slashing", EventKind::BlsToExecutionChange(_) => "bls_to_execution_change", EventKind::BlockGossip(_) => "block_gossip", + EventKind::ExecutionProofValidated(_) => "execution_proof_validated", } } @@ -1315,6 +1328,14 @@ impl EventKind { "block_gossip" => Ok(EventKind::BlockGossip(serde_json::from_str(data).map_err( |e| ServerError::InvalidServerSentEvent(format!("Block Gossip: {:?}", e)), )?)), + "execution_proof_validated" => Ok(EventKind::ExecutionProofValidated( + serde_json::from_str(data).map_err(|e| { + ServerError::InvalidServerSentEvent(format!( + "Execution Proof Validated: {:?}", + e + )) + })?, + )), _ => Err(ServerError::InvalidServerSentEvent( "Could not parse event tag".to_string(), )), @@ -1352,6 +1373,7 @@ pub enum EventTopic { ProposerSlashing, BlsToExecutionChange, BlockGossip, + ExecutionProofValidated, } impl FromStr for EventTopic { @@ -1379,6 +1401,7 @@ impl FromStr for EventTopic { "proposer_slashing" => Ok(EventTopic::ProposerSlashing), "bls_to_execution_change" => Ok(EventTopic::BlsToExecutionChange), "block_gossip" => Ok(EventTopic::BlockGossip), + "execution_proof_validated" => Ok(EventTopic::ExecutionProofValidated), _ => Err("event topic cannot be parsed.".to_string()), } } @@ -1407,6 +1430,7 @@ impl fmt::Display for EventTopic { EventTopic::ProposerSlashing => write!(f, "proposer_slashing"), EventTopic::BlsToExecutionChange => write!(f, "bls_to_execution_change"), EventTopic::BlockGossip => write!(f, "block_gossip"), + EventTopic::ExecutionProofValidated => write!(f, "execution_proof_validated"), } } } diff --git a/common/logging/Cargo.toml b/common/logging/Cargo.toml index 41c82dbd61b..75702669db0 100644 --- a/common/logging/Cargo.toml +++ b/common/logging/Cargo.toml @@ -5,7 +5,7 @@ authors = ["blacktemplar "] edition = { workspace = true } [features] -test_logger = [] # Print log output to stderr when running tests instead of dropping it +test_logger = [] [dependencies] chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } @@ -13,7 +13,7 @@ logroller = { workspace = true } metrics = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -tokio = { workspace = true, features = [ "time" ] } +tokio = { workspace = true, features = ["time"] } tracing = { workspace = true } tracing-appender = { workspace = true } tracing-core = { workspace = true } diff --git a/common/network_utils/Cargo.toml b/common/network_utils/Cargo.toml index 5206249e6f0..1040726afe4 100644 --- a/common/network_utils/Cargo.toml +++ b/common/network_utils/Cargo.toml @@ -6,7 +6,6 @@ edition = { workspace = true } [dependencies] discv5 = { workspace = true } libp2p-identity = "0.2" -lru_cache = { workspace = true } metrics = { workspace = true } multiaddr = "0.18.2" parking_lot = { workspace = true } diff --git a/common/network_utils/src/unused_port.rs b/common/network_utils/src/unused_port.rs index 212ae963e3c..7a3589ef90c 100644 --- a/common/network_utils/src/unused_port.rs +++ b/common/network_utils/src/unused_port.rs @@ -1,8 +1,18 @@ -use lru_cache::LRUTimeCache; use parking_lot::Mutex; -use std::net::{SocketAddr, TcpListener, UdpSocket}; +use std::collections::HashMap; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, TcpListener, UdpSocket}; use std::sync::LazyLock; -use std::time::Duration; + +/// Base port for the partitioned test port range (10000–29999). +const PARTITION_BASE: u16 = 10000; +/// Number of ports per process partition. +const PARTITION_SIZE: u16 = 200; +/// Total partitions; keeps the range within 10000–29999. +const NUM_PARTITIONS: u32 = 100; + +/// Maps partition base → next port to try within that partition. +static PORT_CURSORS: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); #[derive(Copy, Clone)] pub enum Transport { @@ -16,10 +26,14 @@ pub enum IpVersion { Ipv6, } -pub const CACHED_PORTS_TTL: Duration = Duration::from_secs(300); - -static FOUND_PORTS_CACHE: LazyLock>> = - LazyLock::new(|| Mutex::new(LRUTimeCache::new(CACHED_PORTS_TTL))); +/// Returns the start of this process's port partition, derived from the PID. +/// +/// Each OS process gets a distinct 200-port band, so concurrent test processes +/// (which nextest launches as separate PIDs) land in different bands and cannot +/// collide on the same port. +fn partition_base() -> u16 { + PARTITION_BASE + ((std::process::id() % NUM_PARTITIONS) as u16) * PARTITION_SIZE +} /// A convenience wrapper over [`zero_port`]. pub fn unused_tcp4_port() -> Result { @@ -41,59 +55,41 @@ pub fn unused_udp6_port() -> Result { zero_port(Transport::Udp, IpVersion::Ipv6) } -/// A bit of hack to find an unused port. +/// Finds an available port using a per-process port partition. /// -/// Does not guarantee that the given port is unused after the function exits, just that it was -/// unused before the function started (i.e., it does not reserve a port). +/// Each process is assigned a 200-port band derived from its PID, keeping +/// concurrent test processes out of each other's port ranges. A per-partition +/// cursor tracks the next port to try, so allocations advance sequentially +/// without rescanning already-used ports. /// -/// ## Notes +/// The lock is held for the duration of the scan to prevent two concurrent +/// callers within the same process from receiving the same port. /// -/// It is possible that users are unable to bind to the ports returned by this function as the OS -/// has a buffer period where it doesn't allow binding to the same port even after the socket is -/// closed. We might have to use SO_REUSEADDR socket option from `std::net2` crate in that case. +/// Returns an error if the partition is exhausted. pub fn zero_port(transport: Transport, ipv: IpVersion) -> Result { - let localhost = match ipv { - IpVersion::Ipv4 => std::net::Ipv4Addr::LOCALHOST.into(), - IpVersion::Ipv6 => std::net::Ipv6Addr::LOCALHOST.into(), + let localhost: IpAddr = match ipv { + IpVersion::Ipv4 => Ipv4Addr::LOCALHOST.into(), + IpVersion::Ipv6 => Ipv6Addr::LOCALHOST.into(), }; - let socket_addr = std::net::SocketAddr::new(localhost, 0); - let mut unused_port: u16; - loop { - unused_port = find_unused_port(transport, socket_addr)?; - let mut cache_lock = FOUND_PORTS_CACHE.lock(); - if !cache_lock.contains(&unused_port) { - cache_lock.insert(unused_port); - break; - } - } - Ok(unused_port) -} + let base = partition_base(); + let end = base + PARTITION_SIZE; + let mut cursors = PORT_CURSORS.lock(); + let start = *cursors.entry(base).or_insert(base); -fn find_unused_port(transport: Transport, socket_addr: SocketAddr) -> Result { - let local_addr = match transport { - Transport::Tcp => { - let listener = TcpListener::bind(socket_addr).map_err(|e| { - format!("Failed to create TCP listener to find unused port: {:?}", e) - })?; - listener.local_addr().map_err(|e| { - format!( - "Failed to read TCP listener local_addr to find unused port: {:?}", - e - ) - })? - } - Transport::Udp => { - let socket = UdpSocket::bind(socket_addr) - .map_err(|e| format!("Failed to create UDP socket to find unused port: {:?}", e))?; - socket.local_addr().map_err(|e| { - format!( - "Failed to read UDP socket local_addr to find unused port: {:?}", - e - ) - })? + for port in start..end { + let addr = SocketAddr::new(localhost, port); + let bindable = match transport { + Transport::Tcp => TcpListener::bind(addr).is_ok(), + Transport::Udp => UdpSocket::bind(addr).is_ok(), + }; + if bindable { + cursors.insert(base, port + 1); + return Ok(port); } - }; + } - Ok(local_addr.port()) + Err(format!( + "Could not find an unused port in {start}..{end} (pid-based partition).", + )) } diff --git a/consensus/fork_choice/tests/tests.rs b/consensus/fork_choice/tests/tests.rs index d3a84ee85be..46ac008b900 100644 --- a/consensus/fork_choice/tests/tests.rs +++ b/consensus/fork_choice/tests/tests.rs @@ -117,8 +117,7 @@ impl ForkChoiceTest { let mut shutdown_receiver = mutex.lock(); shutdown_receiver.close(); - let msg = shutdown_receiver.try_next().unwrap(); - msg.is_some() + shutdown_receiver.try_recv().is_ok() } /// Assert there was a shutdown signal sent by the beacon chain. diff --git a/consensus/proto_array/src/fork_choice_test_definition/execution_status.rs b/consensus/proto_array/src/fork_choice_test_definition/execution_status.rs index aa26a843069..c8cd427ac60 100644 --- a/consensus/proto_array/src/fork_choice_test_definition/execution_status.rs +++ b/consensus/proto_array/src/fork_choice_test_definition/execution_status.rs @@ -1,5 +1,6 @@ use super::*; +#[allow(clippy::vec_init_then_push)] pub fn get_execution_status_test_definition_01() -> ForkChoiceTestDefinition { let balances = vec![1; 2]; let mut ops = vec![]; @@ -402,6 +403,7 @@ pub fn get_execution_status_test_definition_01() -> ForkChoiceTestDefinition { } } +#[allow(clippy::vec_init_then_push)] pub fn get_execution_status_test_definition_02() -> ForkChoiceTestDefinition { let balances = vec![1; 2]; let mut ops = vec![]; @@ -766,6 +768,7 @@ pub fn get_execution_status_test_definition_02() -> ForkChoiceTestDefinition { } } +#[allow(clippy::vec_init_then_push)] pub fn get_execution_status_test_definition_03() -> ForkChoiceTestDefinition { let balances = vec![1_000; 2_000]; let mut ops = vec![]; diff --git a/consensus/proto_array/src/fork_choice_test_definition/ffg_updates.rs b/consensus/proto_array/src/fork_choice_test_definition/ffg_updates.rs index 3b31616145d..6aa45da5707 100644 --- a/consensus/proto_array/src/fork_choice_test_definition/ffg_updates.rs +++ b/consensus/proto_array/src/fork_choice_test_definition/ffg_updates.rs @@ -1,5 +1,6 @@ use super::*; +#[allow(clippy::vec_init_then_push)] pub fn get_ffg_case_01_test_definition() -> ForkChoiceTestDefinition { let balances = vec![1; 2]; let mut ops = vec![]; @@ -104,6 +105,7 @@ pub fn get_ffg_case_01_test_definition() -> ForkChoiceTestDefinition { } } +#[allow(clippy::vec_init_then_push)] pub fn get_ffg_case_02_test_definition() -> ForkChoiceTestDefinition { let balances = vec![1; 2]; let mut ops = vec![]; diff --git a/consensus/proto_array/src/fork_choice_test_definition/votes.rs b/consensus/proto_array/src/fork_choice_test_definition/votes.rs index 01994fff9b2..01eb12e85e5 100644 --- a/consensus/proto_array/src/fork_choice_test_definition/votes.rs +++ b/consensus/proto_array/src/fork_choice_test_definition/votes.rs @@ -1,5 +1,6 @@ use super::*; +#[allow(clippy::vec_init_then_push)] pub fn get_votes_test_definition() -> ForkChoiceTestDefinition { let mut balances = vec![1; 2]; let mut ops = vec![]; diff --git a/consensus/swap_or_not_shuffle/benches/benches.rs b/consensus/swap_or_not_shuffle/benches/benches.rs index f33556be386..5a9ba38f06a 100644 --- a/consensus/swap_or_not_shuffle/benches/benches.rs +++ b/consensus/swap_or_not_shuffle/benches/benches.rs @@ -1,4 +1,5 @@ -use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; +use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; +use std::hint::black_box; use swap_or_not_shuffle::{compute_shuffled_index, shuffle_list as fast_shuffle}; const SHUFFLE_ROUND_COUNT: u8 = 90; diff --git a/consensus/types/Cargo.toml b/consensus/types/Cargo.toml index 78c6f871cb4..8db97f83632 100644 --- a/consensus/types/Cargo.toml +++ b/consensus/types/Cargo.toml @@ -1,10 +1,7 @@ [package] name = "types" version = "0.2.1" -authors = [ - "Paul Hauner ", - "Age Manning ", -] +authors = ["Paul Hauner ", "Age Manning "] edition = { workspace = true } [features] diff --git a/consensus/types/benches/benches.rs b/consensus/types/benches/benches.rs index 397c33163e9..85d7de980b2 100644 --- a/consensus/types/benches/benches.rs +++ b/consensus/types/benches/benches.rs @@ -1,8 +1,9 @@ -use criterion::{BatchSize, BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; +use criterion::{BatchSize, BenchmarkId, Criterion, criterion_group, criterion_main}; use fixed_bytes::FixedBytesExtended; use milhouse::List; use rayon::prelude::*; use ssz::Encode; +use std::hint::black_box; use std::sync::Arc; use types::{ BeaconState, Epoch, Eth1Data, EthSpec, Hash256, MainnetEthSpec, Validator, diff --git a/consensus/types/src/core/chain_spec.rs b/consensus/types/src/core/chain_spec.rs index 1bdf6c2cb86..2cb24b3347b 100644 --- a/consensus/types/src/core/chain_spec.rs +++ b/consensus/types/src/core/chain_spec.rs @@ -36,6 +36,8 @@ pub enum Domain { SyncCommitteeSelectionProof, BeaconBuilder, PTCAttester, + /// EIP-8025: Domain for execution proof signatures. + ExecutionProof, ApplicationMask(ApplicationDomain), } @@ -302,6 +304,11 @@ pub struct ChainSpec { * Capella params */ pub(crate) domain_bls_to_execution_change: u32, + /* + * EIP-8025 params + */ + /// Domain for execution proof signatures (0x0D000000). + pub(crate) domain_execution_proof: u32, } impl ChainSpec { @@ -505,6 +512,7 @@ impl ChainSpec { Domain::SyncCommitteeSelectionProof => self.domain_sync_committee_selection_proof, Domain::ApplicationMask(application_domain) => application_domain.get_domain_constant(), Domain::BlsToExecutionChange => self.domain_bls_to_execution_change, + Domain::ExecutionProof => self.domain_execution_proof, } } @@ -692,6 +700,18 @@ impl ChainSpec { } } + /// Returns the highest possible value for max_request_blocks based on enabled forks. + /// + /// This is useful for upper bounds where no specific fork context is available, such as + /// computing SSZ size limits for RPC protocols that activate at Deneb or later. + pub fn max_request_blocks_upper_bound(&self) -> usize { + if self.deneb_fork_epoch.is_some() { + self.max_request_blocks_deneb as usize + } else { + self.max_request_blocks as usize + } + } + /// Returns the highest possible value for max_request_blobs based on enabled forks. /// /// This is useful for upper bounds in testing. @@ -812,9 +832,13 @@ impl ChainSpec { let blob_retention_epoch = current_epoch.saturating_sub(self.min_epochs_for_blob_sidecars_requests); match self.fulu_fork_epoch { + Some(_fulu_fork_epoch) if self.min_epochs_for_data_column_sidecars_requests == 0 => { + None + } Some(fulu_fork_epoch) if blob_retention_epoch > fulu_fork_epoch => Some( current_epoch.saturating_sub(self.min_epochs_for_data_column_sidecars_requests), ), + None if self.min_epochs_for_blob_sidecars_requests == 0 => None, _ => Some(std::cmp::max(fork_epoch, blob_retention_epoch)), } } @@ -1177,6 +1201,10 @@ impl ChainSpec { * Capella params */ domain_bls_to_execution_change: 10, + /* + * EIP-8025 params + */ + domain_execution_proof: 13, // 0x0D000000 } } @@ -1540,6 +1568,10 @@ impl ChainSpec { * Capella params */ domain_bls_to_execution_change: 10, + /* + * EIP-8025 params + */ + domain_execution_proof: 13, // 0x0D000000 } } } diff --git a/consensus/types/src/core/execution_block_hash.rs b/consensus/types/src/core/execution_block_hash.rs index 91c019ce040..41ea32a0a85 100644 --- a/consensus/types/src/core/execution_block_hash.rs +++ b/consensus/types/src/core/execution_block_hash.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::{fmt, ops::Deref}; use fixed_bytes::FixedBytesExtended; use rand::RngCore; @@ -12,6 +12,14 @@ use crate::{core::Hash256, test_utils::TestRandom}; #[serde(transparent)] pub struct ExecutionBlockHash(#[serde(with = "serde_utils::b256_hex")] pub Hash256); +impl Deref for ExecutionBlockHash { + type Target = Hash256; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl fmt::Debug for ExecutionBlockHash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { std::fmt::Debug::fmt(&self.0, f) diff --git a/consensus/types/src/execution/eip8025.rs b/consensus/types/src/execution/eip8025.rs new file mode 100644 index 00000000000..dea4624896c --- /dev/null +++ b/consensus/types/src/execution/eip8025.rs @@ -0,0 +1,371 @@ +//! EIP-8025: Optional Execution Proofs +//! +//! This module contains types for the EIP-8025 optional execution proofs feature. +//! See: https://eips.ethereum.org/EIPS/eip-8025 + +use crate::core::{Hash256, SignedRoot}; +use bls::SignatureBytes; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use ssz_types::VariableList; +use tree_hash_derive::TreeHash; + +#[cfg(feature = "arbitrary")] +use arbitrary::Arbitrary; + +/// Maximum proof size: 300 KiB (307200 bytes) +/// +/// Product of U75 * U4096 +pub type MaxProofSize = typenum::Prod; + +/// Proof data type +/// +/// VariableList of bytes with max length [`MaxProofSize`]` +pub type ProofData = VariableList; + +/// Maximum execution proofs per payload +pub type MaxExecutionProofsPerPayload = typenum::U4; + +/// Proof generation identifier (8 bytes) +pub type ProofGenId = [u8; 8]; + +/// Proof type identifier +pub type ProofType = u8; + +/// List of execution proofs per payload +pub type ExecutionProofList = VariableList; + +/// Domain type for execution proof signatures (0x0D000000) +pub const DOMAIN_EXECUTION_PROOF: [u8; 4] = [0x0D, 0x00, 0x00, 0x00]; + +/// Minimum required execution proofs for payload verification +pub const MIN_REQUIRED_EXECUTION_PROOFS: usize = 1; + +/// Public input of an [`ExecutionProof`]. +/// +/// Contains the tree hash root of the new payload request that the proof is associated with. +#[derive( + Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Encode, Decode, TreeHash, +)] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct PublicInput { + /// The tree hash root of the NewPayloadRequest associated with the proof. + pub new_payload_request_root: Hash256, +} + +/// The type of an execution proof. +/// +/// Contains the proof data, type, and public input that links it to a specific new payload request. +#[derive( + Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Encode, Decode, TreeHash, +)] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct ExecutionProof { + /// The proof data. + #[serde(with = "ssz_types::serde_utils::hex_var_list")] + pub proof_data: ProofData, + /// The type of proof. + pub proof_type: ProofType, + /// Public input linking the proof to a specific new payload request. + pub public_input: PublicInput, +} + +impl SignedRoot for ExecutionProof {} + +/// A signed execution proof from a validator. +/// +/// Contains the execution proof, the validator's index, and their BLS signature. +#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize, Encode, Decode, TreeHash)] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct SignedExecutionProof { + /// The execution proof message + pub message: ExecutionProof, + /// Index of the validator who signed this proof + #[serde(with = "serde_utils::quoted_u64")] + pub validator_index: u64, + /// BLS signature over the execution proof + pub signature: SignatureBytes, +} + +/// Identifies a block root and the proof types being requested for it. +/// +/// Matches the `ProofByRootIdentifier` container in the EIP-8025 p2p spec. +#[derive( + Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Encode, Decode, TreeHash, +)] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct ProofByRootIdentifier { + /// The beacon block root whose execution proofs are being requested. + pub block_root: Hash256, + /// Proof types the requester still needs for this block root. + pub proof_types: VariableList, +} + +/// Proof attributes for requesting proof generation. +/// +/// Specifies which types of proofs should be generated for a payload. +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct ProofAttributes { + /// List of proof types to generate + pub proof_types: Vec, +} + +// ============================================================================= +// Status Types +// ============================================================================= + +/// Status returned from proof verification operations. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum ProofStatus { + /// The proof is valid. + Valid, + /// The proof/header verification failed. + Invalid, + /// The proof is valid but does not change the canonical head. + Accepted, + /// The proof type is not supported by this client. + NotSupported, + /// The request root that the proof is associated with is not yet known. + Syncing, +} + +impl ProofStatus { + /// Returns true if the status indicates successful verification. + pub fn is_valid(&self) -> bool { + matches!(self, ProofStatus::Valid) + } + + /// Returns true if the status indicates the node is still syncing proofs. + pub fn is_syncing(&self) -> bool { + matches!(self, ProofStatus::Syncing) + } + + /// Returns true if the status indicates the node has accepted the proof. + pub fn is_accepted(&self) -> bool { + matches!(self, ProofStatus::Accepted) + } +} + +impl std::fmt::Display for ProofStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ProofStatus::Valid => { + write!(f, "VALID") + } + ProofStatus::Invalid => write!(f, "INVALID"), + ProofStatus::Accepted => write!(f, "ACCEPTED"), + ProofStatus::NotSupported => write!(f, "NOT_SUPPORTED"), + ProofStatus::Syncing => write!(f, "SYNCING"), + } + } +} + +/// A generated proof with its tracking ID. +/// +/// Used when receiving proofs from the proof engine via the beacon API. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct GeneratedProof { + /// The proof generation ID for tracking + #[serde(with = "serde_utils::bytes_8_hex")] + pub proof_gen_id: ProofGenId, + /// The generated execution proof + pub execution_proof: ExecutionProof, +} + +// ============================================================================= +// Utility Implementations +// ============================================================================= + +impl ExecutionProof { + /// Returns true if the proof data is empty. + pub fn is_empty(&self) -> bool { + self.proof_data.is_empty() + } + + /// Returns the size of the proof data in bytes. + pub fn proof_size(&self) -> usize { + self.proof_data.len() + } +} + +impl SignedExecutionProof { + /// Returns a reference to the underlying execution proof. + pub fn proof(&self) -> &ExecutionProof { + &self.message + } + + /// Returns the proof data of the underlying execution proof. + pub fn proof_data(&self) -> &ProofData { + &self.message.proof_data + } + + /// Returns the new payload request root this proof validates. + pub fn request_root(&self) -> Hash256 { + self.message.public_input.new_payload_request_root + } + + /// Returns the proof type. + pub fn proof_type(&self) -> ProofType { + self.message.proof_type + } + + /// Returns the validator index that signed this proof. + pub fn validator_index(&self) -> u64 { + self.validator_index + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ssz::{Decode, Encode}; + + #[test] + fn public_input_round_trip() { + let input = PublicInput { + new_payload_request_root: Hash256::repeat_byte(0xab), + }; + let encoded = input.as_ssz_bytes(); + let decoded = PublicInput::from_ssz_bytes(&encoded).unwrap(); + assert_eq!(input, decoded); + } + + #[test] + fn execution_proof_round_trip() { + let proof = ExecutionProof { + proof_data: VariableList::new(vec![1u8, 2, 3, 4]).unwrap(), + proof_type: 1, + public_input: PublicInput { + new_payload_request_root: Hash256::repeat_byte(0xcd), + }, + }; + let encoded = proof.as_ssz_bytes(); + let decoded = ExecutionProof::from_ssz_bytes(&encoded).unwrap(); + assert_eq!(proof, decoded); + } + + #[test] + fn signed_execution_proof_round_trip() { + let signed_proof = SignedExecutionProof { + message: ExecutionProof { + proof_data: VariableList::new(vec![5u8, 6, 7, 8]).unwrap(), + proof_type: 2, + public_input: PublicInput { + new_payload_request_root: Hash256::repeat_byte(0xef), + }, + }, + validator_index: 42, + signature: SignatureBytes::empty(), + }; + let encoded = signed_proof.as_ssz_bytes(); + let decoded = SignedExecutionProof::from_ssz_bytes(&encoded).unwrap(); + assert_eq!(signed_proof, decoded); + } + + #[test] + fn execution_proof_is_empty() { + let empty_proof = ExecutionProof { + proof_data: VariableList::new(vec![]).unwrap(), + proof_type: 1, + public_input: PublicInput { + new_payload_request_root: Hash256::ZERO, + }, + }; + assert!(empty_proof.is_empty()); + + let non_empty_proof = ExecutionProof { + proof_data: VariableList::new(vec![1u8, 2, 3]).unwrap(), + proof_type: 1, + public_input: PublicInput { + new_payload_request_root: Hash256::ZERO, + }, + }; + assert!(!non_empty_proof.is_empty()); + } + + #[test] + fn execution_proof_size() { + let proof = ExecutionProof { + proof_data: VariableList::new(vec![1u8, 2, 3, 4, 5]).unwrap(), + proof_type: 1, + public_input: PublicInput { + new_payload_request_root: Hash256::ZERO, + }, + }; + assert_eq!(proof.proof_size(), 5); + + let empty_proof = ExecutionProof::default(); + assert_eq!(empty_proof.proof_size(), 0); + } + + #[test] + fn signed_execution_proof_accessors() { + let request_root = Hash256::repeat_byte(0xab); + let proof_type = 42u8; + let validator_index = 123u64; + + let signed_proof = SignedExecutionProof { + message: ExecutionProof { + proof_data: VariableList::new(vec![1u8, 2, 3]).unwrap(), + proof_type, + public_input: PublicInput { + new_payload_request_root: request_root, + }, + }, + validator_index, + signature: SignatureBytes::empty(), + }; + + assert_eq!(signed_proof.request_root(), request_root); + assert_eq!(signed_proof.proof_type(), proof_type); + assert_eq!(signed_proof.validator_index(), validator_index); + assert_eq!(signed_proof.proof().proof_type, proof_type); + } + + #[test] + fn proof_status_is_valid() { + assert!(ProofStatus::Valid.is_valid()); + assert!(!ProofStatus::Invalid.is_valid()); + assert!(!ProofStatus::Accepted.is_valid()); + assert!(!ProofStatus::NotSupported.is_valid()); + } + + #[test] + fn proof_status_is_syncing() { + assert!(ProofStatus::Syncing.is_syncing()); + assert!(!ProofStatus::Accepted.is_syncing()); + assert!(!ProofStatus::Valid.is_syncing()); + assert!(!ProofStatus::Invalid.is_syncing()); + assert!(!ProofStatus::NotSupported.is_syncing()); + } + + #[test] + fn generated_proof_json_round_trip() { + let proof = GeneratedProof { + proof_gen_id: [1, 2, 3, 4, 5, 6, 7, 8], + execution_proof: ExecutionProof { + proof_data: VariableList::new(vec![0xaa, 0xbb, 0xcc]).unwrap(), + proof_type: 1, + public_input: PublicInput { + new_payload_request_root: Hash256::repeat_byte(0xde), + }, + }, + }; + + let json = serde_json::to_string(&proof).unwrap(); + let decoded: GeneratedProof = serde_json::from_str(&json).unwrap(); + assert_eq!(proof, decoded); + } + + #[test] + fn proof_attributes_default() { + let attrs = ProofAttributes::default(); + assert!(attrs.proof_types.is_empty()); + + let attrs_with_types = ProofAttributes { + proof_types: vec![1, 2, 3], + }; + assert_eq!(attrs_with_types.proof_types.len(), 3); + } +} diff --git a/consensus/types/src/execution/execution_requests.rs b/consensus/types/src/execution/execution_requests.rs index 92d717778e3..c14ac5ea0de 100644 --- a/consensus/types/src/execution/execution_requests.rs +++ b/consensus/types/src/execution/execution_requests.rs @@ -3,7 +3,7 @@ use context_deserialize::context_deserialize; use educe::Educe; use ethereum_hashing::{DynamicContext, Sha256Context}; use serde::{Deserialize, Serialize}; -use ssz::Encode; +use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use ssz_types::VariableList; use test_random_derive::TestRandom; @@ -71,6 +71,44 @@ impl ExecutionRequests { requests_list } + pub fn from_execution_requests_list(requests_list: Vec) -> Result { + let mut execution_requests = ExecutionRequests::default(); + + for request_bytes in requests_list { + let prefix = request_bytes + .first() + .copied() + .ok_or_else(|| "Empty request bytes".to_string())?; + let request_type = RequestType::from_u8(prefix) + .ok_or_else(|| format!("Invalid request type prefix: {}", prefix))?; + + let request_data = request_bytes + .get(1..) + .ok_or_else(|| "Empty request data".to_string())?; + match request_type { + RequestType::Deposit => { + let deposits = DepositRequests::::from_ssz_bytes(request_data) + .map_err(|e| format!("Failed to decode deposit requests: {:?}", e))?; + execution_requests.deposits = deposits; + } + RequestType::Withdrawal => { + let withdrawals = WithdrawalRequests::::from_ssz_bytes(request_data) + .map_err(|e| format!("Failed to decode withdrawal requests: {:?}", e))?; + execution_requests.withdrawals = withdrawals; + } + RequestType::Consolidation => { + let consolidations = ConsolidationRequests::::from_ssz_bytes(request_data) + .map_err(|e| { + format!("Failed to decode consolidation requests: {:?}", e) + })?; + execution_requests.consolidations = consolidations; + } + } + } + + Ok(execution_requests) + } + /// Generate the execution layer `requests_hash` based on EIP-7685. /// /// `sha256(sha256(requests_0) ++ sha256(requests_1) ++ ...)` diff --git a/consensus/types/src/execution/mod.rs b/consensus/types/src/execution/mod.rs index a3d4ed87301..b24e8fc37ce 100644 --- a/consensus/types/src/execution/mod.rs +++ b/consensus/types/src/execution/mod.rs @@ -4,6 +4,7 @@ mod execution_block_header; mod execution_payload; mod bls_to_execution_change; mod dumb_macros; +pub mod eip8025; mod execution_payload_bid; mod execution_payload_envelope; mod execution_payload_header; @@ -41,3 +42,10 @@ pub use payload::{ pub use signed_bls_to_execution_change::SignedBlsToExecutionChange; pub use signed_execution_payload_bid::SignedExecutionPayloadBid; pub use signed_execution_payload_envelope::SignedExecutionPayloadEnvelope; + +// EIP-8025: Optional Execution Proofs +pub use eip8025::{ + DOMAIN_EXECUTION_PROOF, ExecutionProof, ExecutionProofList, GeneratedProof, + MIN_REQUIRED_EXECUTION_PROOFS, MaxExecutionProofsPerPayload, ProofAttributes, + ProofByRootIdentifier, ProofGenId, ProofStatus, ProofType, PublicInput, SignedExecutionProof, +}; diff --git a/consensus/types/src/fork/fork_name.rs b/consensus/types/src/fork/fork_name.rs index e9ec5fbe41e..65c56bd4f14 100644 --- a/consensus/types/src/fork/fork_name.rs +++ b/consensus/types/src/fork/fork_name.rs @@ -217,7 +217,7 @@ impl ForkName { ║ III DECEMBER MMXXV ║ ║ ║ ╚═══════════════════════════════════════╝ - + ============================================================================= |||| |||| |---------------------------------------------------------------------------| diff --git a/deny.toml b/deny.toml index 54ede06429c..3b230155f79 100644 --- a/deny.toml +++ b/deny.toml @@ -10,12 +10,15 @@ deny = [ { crate = "protobuf", reason = "use quick-protobuf instead" }, { crate = "derivative", reason = "use educe or derive_more instead" }, { crate = "ark-ff", reason = "present in Cargo.lock but not needed by Lighthouse" }, + { crate = "openssl", reason = "non-Rust dependency, use rustls instead" }, { crate = "strum", deny-multiple-versions = true, reason = "takes a long time to compile" }, { crate = "reqwest", deny-multiple-versions = true, reason = "takes a long time to compile" }, { crate = "aes", deny-multiple-versions = true, reason = "takes a long time to compile" }, { crate = "sha2", deny-multiple-versions = true, reason = "takes a long time to compile" }, { crate = "pbkdf2", deny-multiple-versions = true, reason = "takes a long time to compile" }, { crate = "scrypt", deny-multiple-versions = true, reason = "takes a long time to compile" }, + { crate = "syn", deny-multiple-versions = true, reason = "takes a long time to compile" }, + { crate = "uuid", deny-multiple-versions = true, reason = "dependency hygiene" }, ] [sources] diff --git a/lcli/Dockerfile b/lcli/Dockerfile index f1e4bd8ee04..959519fe8a1 100644 --- a/lcli/Dockerfile +++ b/lcli/Dockerfile @@ -1,7 +1,7 @@ # `lcli` requires the full project to be in scope, so this should be built either: # - from the `lighthouse` dir with the command: `docker build -f ./lcli/Dockerflie .` # - from the current directory with the command: `docker build -f ./Dockerfile ../` -FROM rust:1.88.0-bullseye AS builder +FROM rust:1.91.0-bullseye AS builder RUN apt-get update && apt-get -y upgrade && apt-get install -y cmake libclang-dev COPY . lighthouse ARG FEATURES diff --git a/lighthouse/Cargo.toml b/lighthouse/Cargo.toml index ebe00c9be59..7d070aecd27 100644 --- a/lighthouse/Cargo.toml +++ b/lighthouse/Cargo.toml @@ -4,7 +4,7 @@ version = { workspace = true } authors = ["Sigma Prime "] edition = { workspace = true } autotests = false -rust-version = "1.88.0" +rust-version = "1.91.0" # Prevent cargo-udeps from flagging the dummy package `target_check`, which exists only # to assert properties of the compilation target. diff --git a/lighthouse/environment/Cargo.toml b/lighthouse/environment/Cargo.toml index 6d6ffa1725f..669bd0db278 100644 --- a/lighthouse/environment/Cargo.toml +++ b/lighthouse/environment/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.2" authors = ["Paul Hauner "] edition = { workspace = true } +[features] +test-utils = [] + [dependencies] async-channel = { workspace = true } clap = { workspace = true } diff --git a/lighthouse/environment/src/lib.rs b/lighthouse/environment/src/lib.rs index 6694c673ed5..b43b7744bd4 100644 --- a/lighthouse/environment/src/lib.rs +++ b/lighthouse/environment/src/lib.rs @@ -21,7 +21,7 @@ use task_executor::{ShutdownReason, TaskExecutor}; use tokio::runtime::{Builder as RuntimeBuilder, Runtime}; use tracing::{error, info, warn}; use tracing_subscriber::filter::LevelFilter; -use types::{EthSpec, GnosisEthSpec, MainnetEthSpec, MinimalEthSpec}; +use types::{ChainSpec, EthSpec, GnosisEthSpec, MainnetEthSpec, MinimalEthSpec}; #[cfg(target_family = "unix")] use { @@ -33,6 +33,9 @@ use { #[cfg(not(target_family = "unix"))] use {futures::channel::oneshot, std::cell::RefCell}; +#[cfg(feature = "test-utils")] +pub mod test_utils; + pub mod tracing_common; pub const SSE_LOG_CHANNEL_SIZE: usize = 2048; @@ -284,6 +287,14 @@ impl EnvironmentBuilder { Ok(self) } + /// Map the `ChainSpec` used for the environment using the provided function. + pub fn map_spec(mut self, f: impl FnOnce(&mut ChainSpec)) -> Self { + let mut spec = Arc::unwrap_or_clone(self.eth2_config.spec); + f(&mut spec); + self.eth2_config.spec = spec.into(); + self + } + /// Consumes the builder, returning an `Environment`. pub fn build(self) -> Result, String> { let (signal, exit) = async_channel::bounded(1); @@ -302,6 +313,25 @@ impl EnvironmentBuilder { eth2_network_config: self.eth2_network_config.map(Arc::new), }) } + + #[cfg(feature = "test-utils")] + pub fn build_test_environment(self) -> Result, String> { + let (signal, exit) = async_channel::bounded(1); + let (signal_tx, signal_rx) = channel(1); + Ok(test_utils::TestEnvironment { + executor: TaskExecutor::new( + tokio::runtime::Handle::try_current().expect("failed to get tokio handle"), + exit.clone(), + signal_tx.clone(), + ), + signal_rx: Some(signal_rx), + signal: Some(signal), + sse_logging_components: self.sse_logging_components, + eth_spec_instance: self.eth_spec_instance, + eth2_config: self.eth2_config, + eth2_network_config: self.eth2_network_config.map(Arc::new), + }) + } } /// An environment where Lighthouse services can run. Used to start a production beacon node or diff --git a/lighthouse/environment/src/test_utils.rs b/lighthouse/environment/src/test_utils.rs new file mode 100644 index 00000000000..75d48b65f78 --- /dev/null +++ b/lighthouse/environment/src/test_utils.rs @@ -0,0 +1,24 @@ +use super::*; +use task_executor::TaskExecutor; + +pub struct TestEnvironment { + pub executor: TaskExecutor, + pub signal_rx: Option>, + pub signal: Option>, + pub sse_logging_components: Option, + pub eth_spec_instance: E, + pub eth2_config: Eth2Config, + pub eth2_network_config: Option>, +} + +impl TestEnvironment { + pub fn core_context(&self) -> RuntimeContext { + RuntimeContext { + executor: self.executor.clone(), + eth_spec_instance: self.eth_spec_instance.clone(), + eth2_config: self.eth2_config.clone(), + eth2_network_config: self.eth2_network_config.clone(), + sse_logging_components: self.sse_logging_components.clone(), + } + } +} diff --git a/scripts/local_testnet/network_params_eip8025.yaml b/scripts/local_testnet/network_params_eip8025.yaml new file mode 100644 index 00000000000..cd70704d0ea --- /dev/null +++ b/scripts/local_testnet/network_params_eip8025.yaml @@ -0,0 +1,39 @@ +# EIP-8025 multi-node testnet configuration. +# +# Uses MockProofNodeClient via the http://mock/{n}/ URL pattern. +# See start_eip8025_testnet.sh for usage. +# +# Full configuration reference: https://github.com/ethpandaops/ethereum-package#configuration +participants: + # Supernode participants with proof engine enabled + - cl_type: lighthouse + cl_image: lighthouse:local + el_type: geth + el_image: ethereum/client-go:latest + supernode: true + cl_extra_params: + - --target-peers=3 + - --proof-engine-endpoint=http://mock/0/ + vc_extra_params: + - --proof-engine-endpoint=http://mock/0/ + count: 2 + # Non-supernode participants with proof engine enabled + - cl_type: lighthouse + cl_image: lighthouse:local + el_type: geth + el_image: ethereum/client-go:latest + supernode: false + cl_extra_params: + - --target-peers=3 + - --proof-engine-endpoint=http://mock/0/ + vc_extra_params: + - --proof-engine-endpoint=http://mock/0/ + count: 2 +network_params: + fulu_fork_epoch: 0 + seconds_per_slot: 6 +snooper_enabled: false +global_log_level: debug +additional_services: + - dora + - prometheus_grafana diff --git a/scripts/local_testnet/network_params_eip8025_zkboost.yaml b/scripts/local_testnet/network_params_eip8025_zkboost.yaml new file mode 100644 index 00000000000..0d7568f6274 --- /dev/null +++ b/scripts/local_testnet/network_params_eip8025_zkboost.yaml @@ -0,0 +1,76 @@ +# EIP-8025 testnet with zkboost-server backends via native ethereum-package integration. +# +# Run with: +# kurtosis run --enclave eip8025-zkboost \ +# github.com/frisitano/ethereum-package@feat/integrate-zkboost \ +# --args-file scripts/local_testnet/network_params_eip8025_zkboost.yaml +# +# For the mock-only path (no zkboost sidecar), use network_params_eip8025.yaml instead. + +# ── Ethereum package participants ──────────────────────────────────────────── +participants: + # Supernode participants — proof engine points to zkboost-1 + - cl_type: lighthouse + cl_image: lighthouse:local + el_type: reth + el_image: ghcr.io/paradigmxyz/reth + supernode: true + cl_extra_params: + - --target-peers=3 + - --proof-engine-endpoint=http://zkboost-1:3000 + - --proof-types=6 + vc_extra_params: + - --proof-engine-endpoint=http://zkboost-1:3000 + - --proof-types=6 + count: 2 + # Non-supernode participants — proof engine points to zkboost-2 + - cl_type: lighthouse + cl_image: lighthouse:local + el_type: reth + el_image: ghcr.io/paradigmxyz/reth + supernode: false + cl_extra_params: + - --target-peers=3 + - --proof-engine-endpoint=http://zkboost-2:3000 + - --proof-types=6 + vc_extra_params: + - --proof-engine-endpoint=http://zkboost-2:3000 + - --proof-types=6 + count: 2 + +network_params: + fulu_fork_epoch: 0 + seconds_per_slot: 6 + +snooper_enabled: false +global_log_level: debug + +additional_services: + - zkboost + - dora + - prometheus + - grafana + +# ── zkboost-server configuration ───────────────────────────────────────────── +# Processed natively by ethereum-package; see src/zkboost/zkboost_launcher.star. +zkboost_params: + image: ghcr.io/eth-act/zkboost/zkboost-server:0.3.0 + port: 3000 + witness_timeout_secs: 12 + proof_timeout_secs: 300 + witness_cache_size: 128 + proof_cache_size: 128 + # Two instances: each connected to its own EL, serving one group of participants. + # el_participant_index is 0-based into the flat participant list after count expansion: + # 0 = el-1-reth-lighthouse (first supernode) + # 1 = el-2-reth-lighthouse (second supernode) + instances: + - name: zkboost-1 + el_participant_index: 0 + - name: zkboost-2 + el_participant_index: 1 + zkvms: + - kind: mock + proof_type: reth-zisk + mock_proving_time_ms: 300 + mock_proof_size: 1024 diff --git a/scripts/local_testnet/network_params_eip8025_zkboost_gpu.yaml b/scripts/local_testnet/network_params_eip8025_zkboost_gpu.yaml new file mode 100644 index 00000000000..fb97f84afc5 --- /dev/null +++ b/scripts/local_testnet/network_params_eip8025_zkboost_gpu.yaml @@ -0,0 +1,100 @@ +# EIP-8025 testnet with zkboost-server backed by real GPU (ZisK) provers. +# +# Uses ere-server-zisk (CUDA) containers for reth-zisk and ethrex-zisk proving, +# mirroring the docker-compose setup in: +# https://github.com/eth-act/zkboost/blob/master/docker/example/testnet/docker-compose.yml +# +# Run with: +# kurtosis run --enclave eip8025-zkboost-gpu \ +# github.com/frisitano/ethereum-package@feat/integrate-zkboost \ +# --args-file scripts/local_testnet/network_params_eip8025_zkboost_gpu.yaml +# +# Prerequisites: +# - NVIDIA GPUs with drivers installed (≥8 GPUs recommended: 4 per prover type) +# - NVIDIA Container Toolkit configured for Docker +# - ~5-10 min startup time for ZisK setup on first run + +# ── Ethereum package participants ──────────────────────────────────────────── +participants: + # Supernode participants + - cl_type: lighthouse + cl_image: lighthouse:local + el_type: reth + el_image: ghcr.io/paradigmxyz/reth + supernode: true + cl_extra_params: + - --target-peers=3 + - --proof-engine-endpoint=http://zkboost-1:3000 + - --proof-types=6 + vc_extra_params: + - --proof-engine-endpoint=http://zkboost-1:3000 + - --proof-types=6 + count: 2 + # Non-supernode participants + - cl_type: lighthouse + cl_image: lighthouse:local + el_type: reth + el_image: ghcr.io/paradigmxyz/reth + supernode: false + cl_extra_params: + - --target-peers=3 + - --proof-engine-endpoint=http://zkboost-1:3000 + - --proof-types=6 + vc_extra_params: + - --proof-engine-endpoint=http://zkboost-1:3000 + - --proof-types=6 + count: 2 + +network_params: + fulu_fork_epoch: 0 + seconds_per_slot: 6 + +snooper_enabled: false +global_log_level: debug + +additional_services: + - zkboost + - dora + - prometheus + - grafana + +# ── zkboost-server configuration ───────────────────────────────────────────── +zkboost_params: + image: ghcr.io/eth-act/zkboost/zkboost-server:0.3.0 + port: 3000 + witness_timeout_secs: 12 + proof_timeout_secs: 300 + witness_cache_size: 128 + proof_cache_size: 128 + instances: + - name: zkboost-1 + el_participant_index: 0 + zkvms: + # reth-zisk GPU prover — uses GPUs 0-3 on the host + - kind: ere_server + proof_type: reth-zisk + image: ghcr.io/eth-act/ere/ere-server-zisk:0.6.0-cuda + program_url: https://github.com/eth-act/ere/releases/download/v0.7.0/stateless-validator-reth-zisk + port: 3000 + shm_size_mb: 32768 # 32 GiB — ZisK requires large shared memory for GPU proving + gpu_count: 4 + ulimits: + memlock: -1 # unlimited memory lock — required for CUDA unified memory + env: + RUST_LOG: info + ERE_ZISK_SETUP_ON_INIT: "1" + ERE_ZISK_START_SERVER_TIMEOUT_SEC: "600" + # ethrex-zisk GPU prover — uses GPUs 4-7 on the host + - kind: ere_server + proof_type: ethrex-zisk + image: ghcr.io/eth-act/ere/ere-server-zisk:0.6.0-cuda + program_url: https://github.com/eth-act/ere/releases/download/v0.7.0/stateless-validator-ethrex-zisk + port: 3000 + shm_size_mb: 32768 + gpu_count: 4 + ulimits: + memlock: -1 + env: + RUST_LOG: info + ERE_ZISK_SETUP_ON_INIT: "1" + ERE_ZISK_START_SERVER_TIMEOUT_SEC: "600" diff --git a/scripts/local_testnet/start_eip8025_testnet.sh b/scripts/local_testnet/start_eip8025_testnet.sh new file mode 100755 index 00000000000..21cc60ebace --- /dev/null +++ b/scripts/local_testnet/start_eip8025_testnet.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash + +# Start a local EIP-8025 testnet with mock proof engines using Kurtosis. +# +# Requires: docker, kurtosis, yq +# +# This script builds Lighthouse and launches a Kurtosis enclave using +# network_params_eip8025.yaml. Mock proof engines are enabled via the +# http://mock/0/ URL pattern (no special build feature required). + +set -Eeuo pipefail + +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +ROOT_DIR="$SCRIPT_DIR/../.." +ENCLAVE_NAME=eip8025-testnet +NETWORK_PARAMS_FILE=$SCRIPT_DIR/network_params_eip8025.yaml +ETHEREUM_PKG_VERSION=main + +BUILD_IMAGE=true +KEEP_ENCLAVE=false + +# Get options +while getopts "e:n:bkh" flag; do + case "${flag}" in + e) ENCLAVE_NAME=${OPTARG};; + n) NETWORK_PARAMS_FILE=${OPTARG};; + b) BUILD_IMAGE=false;; + k) KEEP_ENCLAVE=true;; + h) + echo "Start a local EIP-8025 testnet with Kurtosis." + echo + echo "usage: $0 " + echo + echo "Options:" + echo " -e: enclave name default: $ENCLAVE_NAME" + echo " -n: kurtosis network params file path default: $NETWORK_PARAMS_FILE" + echo " -b: skip building Lighthouse docker image" + echo " -k: keep existing enclave (don't destroy first)" + echo " -h: this help" + exit + ;; + esac +done + +LH_IMAGE_NAME=$(yq eval ".participants[0].cl_image" "$NETWORK_PARAMS_FILE") + +for cmd in docker kurtosis yq; do + if ! command -v "$cmd" &> /dev/null; then + echo "$cmd is not installed. Please install $cmd and try again." + exit 1 + fi +done + +if [ "$KEEP_ENCLAVE" = false ]; then + kurtosis enclave rm -f "$ENCLAVE_NAME" 2>/dev/null || true +fi + +if [ "$BUILD_IMAGE" = true ]; then + echo "Building Lighthouse Docker image." + docker build \ + --build-arg FEATURES=portable,spec-minimal \ + -f "$ROOT_DIR/Dockerfile" \ + -t "$LH_IMAGE_NAME" \ + "$ROOT_DIR" +else + echo "Skipping Lighthouse Docker image build." +fi + +echo "Starting EIP-8025 testnet enclave: $ENCLAVE_NAME" +kurtosis run --enclave "$ENCLAVE_NAME" \ + "github.com/ethpandaops/ethereum-package@$ETHEREUM_PKG_VERSION" \ + --args-file "$NETWORK_PARAMS_FILE" + +echo "EIP-8025 testnet started!" +echo +echo "Useful commands:" +echo " kurtosis enclave inspect $ENCLAVE_NAME" +echo " kurtosis service logs $ENCLAVE_NAME cl-1-lighthouse-geth" +echo " kurtosis enclave rm -f $ENCLAVE_NAME" diff --git a/scripts/local_testnet/start_eip8025_zkboost_testnet.sh b/scripts/local_testnet/start_eip8025_zkboost_testnet.sh new file mode 100755 index 00000000000..0807f8e61b4 --- /dev/null +++ b/scripts/local_testnet/start_eip8025_zkboost_testnet.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash + +# Start a local EIP-8025 testnet with zkboost-server backends using Kurtosis. +# +# Builds a Lighthouse Docker image then launches a Kurtosis enclave via the +# ethereum-package with native zkboost support. +# +# For the mock-only path (no zkboost), use start_eip8025_testnet.sh instead. +# +# Requires: docker, kurtosis, yq + +set -Eeuo pipefail + +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +ROOT_DIR="$SCRIPT_DIR/../.." +ENCLAVE_NAME=eip8025-zkboost +NETWORK_PARAMS_FILE=$SCRIPT_DIR/network_params_eip8025_zkboost.yaml +ETHEREUM_PKG=github.com/frisitano/ethereum-package@feat/integrate-zkboost + +BUILD_IMAGE=true +KEEP_ENCLAVE=false + +# Get options +while getopts "e:n:p:bkh" flag; do + case "${flag}" in + e) ENCLAVE_NAME=${OPTARG};; + n) NETWORK_PARAMS_FILE=${OPTARG};; + p) ETHEREUM_PKG=${OPTARG};; + b) BUILD_IMAGE=false;; + k) KEEP_ENCLAVE=true;; + h) + echo "Start a local EIP-8025 testnet with zkboost backends." + echo + echo "usage: $0 " + echo + echo "Options:" + echo " -e: enclave name default: $ENCLAVE_NAME" + echo " -n: kurtosis network params file path default: $NETWORK_PARAMS_FILE" + echo " -p: ethereum-package path or GitHub ref default: $ETHEREUM_PKG" + echo " -b: skip building Lighthouse docker image" + echo " -k: keep existing enclave (don't destroy first)" + echo " -h: this help" + exit + ;; + esac +done + +LH_IMAGE_NAME=$(yq eval ".participants[0].cl_image" "$NETWORK_PARAMS_FILE") + +for cmd in docker kurtosis yq; do + if ! command -v "$cmd" &> /dev/null; then + echo "$cmd is not installed. Please install $cmd and try again." + exit 1 + fi +done + +if [ "$KEEP_ENCLAVE" = false ]; then + kurtosis enclave rm -f "$ENCLAVE_NAME" 2>/dev/null || true +fi + +if [ "$BUILD_IMAGE" = true ]; then + echo "Building Lighthouse Docker image ($LH_IMAGE_NAME)." + docker build \ + --build-arg FEATURES=portable,spec-minimal \ + -f "$ROOT_DIR/Dockerfile" \ + -t "$LH_IMAGE_NAME" \ + "$ROOT_DIR" +else + echo "Skipping Lighthouse Docker image build." +fi + +echo "Starting EIP-8025 zkboost testnet enclave: $ENCLAVE_NAME" +echo " ethereum-package: $ETHEREUM_PKG" +kurtosis run --enclave "$ENCLAVE_NAME" \ + "$ETHEREUM_PKG" \ + --args-file "$NETWORK_PARAMS_FILE" + +echo "" +echo "EIP-8025 zkboost testnet started!" +echo "" +echo "Useful commands:" +echo " kurtosis enclave inspect $ENCLAVE_NAME" +echo " kurtosis service logs $ENCLAVE_NAME cl-1-lighthouse-reth" +echo " kurtosis service logs $ENCLAVE_NAME zkboost-1" +echo " kurtosis service logs $ENCLAVE_NAME zkboost-2" +echo " kurtosis enclave rm -f $ENCLAVE_NAME" diff --git a/slasher/service/src/service.rs b/slasher/service/src/service.rs index c0e6a8a0cd8..c1a5f1c4e1d 100644 --- a/slasher/service/src/service.rs +++ b/slasher/service/src/service.rs @@ -171,6 +171,7 @@ impl SlasherService { Self::process_proposer_slashings(beacon_chain, slasher, network_sender); } + #[allow(clippy::result_large_err)] fn process_attester_slashings( beacon_chain: &BeaconChain, slasher: &Slasher, @@ -223,6 +224,7 @@ impl SlasherService { } } + #[allow(clippy::result_large_err)] fn process_proposer_slashings( beacon_chain: &BeaconChain, slasher: &Slasher, diff --git a/testing/node_test_rig/Cargo.toml b/testing/node_test_rig/Cargo.toml index 0d9db528da4..ba1b99ffe45 100644 --- a/testing/node_test_rig/Cargo.toml +++ b/testing/node_test_rig/Cargo.toml @@ -7,12 +7,28 @@ edition = { workspace = true } [dependencies] beacon_node = { workspace = true } beacon_node_fallback = { workspace = true } +bls = { workspace = true } +bytes = { workspace = true } environment = { workspace = true } -eth2 = { workspace = true } +eth2 = { workspace = true, features = ["events"] } +ethereum_ssz = { workspace = true } +ethereum_ssz_derive = { workspace = true } execution_layer = { workspace = true } +futures = { workspace = true } +hex = { workspace = true } +parking_lot = { workspace = true } +reqwest = { workspace = true } sensitive_url = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +ssz_types = { workspace = true } +task_executor = { workspace = true } tempfile = { workspace = true } tokio = { workspace = true } +tokio-stream = { workspace = true } +tracing = { workspace = true } +tree_hash = { workspace = true } types = { workspace = true } validator_client = { workspace = true } validator_dir = { workspace = true, features = ["insecure_keys"] } +validator_store = { workspace = true } diff --git a/testing/node_test_rig/src/lib.rs b/testing/node_test_rig/src/lib.rs index e49d11ee1eb..b6ed74b879e 100644 --- a/testing/node_test_rig/src/lib.rs +++ b/testing/node_test_rig/src/lib.rs @@ -4,6 +4,7 @@ use beacon_node::ProductionBeaconNode; use environment::RuntimeContext; +use eth2::lighthouse_vc::http_client::ValidatorClientHttpClient; use eth2::{BeaconNodeHttpClient, Timeouts, reqwest::ClientBuilder}; use sensitive_url::SensitiveUrl; use std::path::PathBuf; @@ -22,7 +23,7 @@ pub use eth2; pub use execution_layer::test_utils::{ Config as MockServerConfig, MockExecutionConfig, MockServer, }; -pub use validator_client::Config as ValidatorConfig; +pub use validator_client::{ApiSecret, Config as ValidatorConfig}; /// The global timeout for HTTP requests to the beacon node. const HTTP_TIMEOUT: Duration = Duration::from_secs(8); @@ -227,6 +228,24 @@ impl LocalValidatorClient { .expect("should start validator services"); Ok(Self { client, files }) } + + pub fn http_client(&self) -> Result, String> { + let client = if let Some(listen_addr) = self.client.listen_addr() { + let token_path = self.client.config().http_api.http_token_path.clone(); + let api_secret = ApiSecret::create_or_open(token_path)?; + let validator_client_url: SensitiveUrl = SensitiveUrl::parse( + format!("http://{}:{}", listen_addr.ip(), listen_addr.port()).as_str(), + ) + .map_err(|e| format!("Unable to parse validator client URL: {:?}", e))?; + Some( + ValidatorClientHttpClient::new(validator_client_url, api_secret.api_token()) + .map_err(|e| format!("failed to create http client: {:?}", e))?, + ) + } else { + None + }; + Ok(client) + } } /// Provides an execution engine api server that is running in the current process on a given tokio executor (it diff --git a/testing/proof_engine/Cargo.toml b/testing/proof_engine/Cargo.toml new file mode 100644 index 00000000000..60d645bc3e6 --- /dev/null +++ b/testing/proof_engine/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "proof_engine_test" +edition.workspace = true +version.workspace = true + +[dependencies] +anyhow = { workspace = true } +execution_layer = { workspace = true } +futures = { workspace = true } +network = { workspace = true, features = ["disable-backfill"] } +simulator = { path = "../simulator", features = ["test-utils"] } +task_executor = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +types = { workspace = true } + diff --git a/testing/proof_engine/src/lib.rs b/testing/proof_engine/src/lib.rs new file mode 100644 index 00000000000..bbd9fdda012 --- /dev/null +++ b/testing/proof_engine/src/lib.rs @@ -0,0 +1,216 @@ +//! Integration tests for the EIP-8025 proof engine, using [`ProofEngineTestRig`]. + +mod rig; +pub use rig::ProofEngineTestRig; + +#[cfg(test)] +mod test { + use std::time::Duration; + + use futures::{TryFutureExt, try_join}; + use simulator::test_utils::{Epoch, InternalBeaconNodeEvent, StateId}; + + use super::ProofEngineTestRig; + + #[tokio::test] + #[cfg_attr(debug_assertions, ignore = "too slow in debug mode")] + async fn test_proof_engine_basic() -> anyhow::Result<()> { + let mut rig = ProofEngineTestRig::standard().await?; + rig.fixture.payloads_valid(); + rig.fixture.wait_for_genesis().await?; + + // Subscribe to both execution-layer events (proof requested by the mock engine) and + // chain-level events (gossip proof received and verified by the beacon chain). + let mut gen_events = rig.proof_generator_events(0)?; + let mut verifier_chain = rig.proof_verifier_chain_events(0)?; + + // The proof generator must receive at least one proof request from the EL. + gen_events + .expect_proof_requests(1, Duration::from_secs(30)) + .await?; + + // The verifier must receive the gossip proof and verify it on-chain. + verifier_chain + .collect_n( + 1, + |e| matches!(e, InternalBeaconNodeEvent::GossipExecutionProof(_)), + Duration::from_secs(60), + ) + .await?; + verifier_chain + .collect_n( + 1, + |e| matches!(e, InternalBeaconNodeEvent::ExecutionProofVerified { .. }), + Duration::from_secs(30), + ) + .await?; + + Ok(()) + } + + #[tokio::test] + #[cfg_attr(debug_assertions, ignore = "too slow in debug mode")] + async fn test_proof_engine_sync() -> anyhow::Result<()> { + let mut rig = ProofEngineTestRig::sync_topology().await?; + rig.fixture.payloads_valid(); + rig.fixture.wait_for_genesis().await?; + + // Let the proof generator accumulate some proofs before the verifier joins. + tokio::time::sleep(Duration::from_secs(30)).await; + + // Add a proof verifier and subscribe to both its mock and internal event streams. + let (mut mock_events, mut verifier_chain) = rig.add_proof_verifier_and_subscribe().await?; + + // The late-joining verifier must issue at least one outbound RPC proof request to + // catch up with proofs it missed while offline. + verifier_chain + .collect_n( + 1, + |e| { + matches!( + e, + InternalBeaconNodeEvent::OutboundExecutionProofsByRange { .. } + | InternalBeaconNodeEvent::OutboundExecutionProofsByRoot { .. } + ) + }, + Duration::from_secs(60), + ) + .await?; + + // An execution proof must arrive via RPC and be verified on-chain, concurrently with + // the mock engine confirming it was processed. + try_join!( + verifier_chain.collect_n( + 1, + |e| matches!(e, InternalBeaconNodeEvent::RpcExecutionProof(_)), + Duration::from_secs(60), + ), + mock_events + .expect_proof_verified(1, Duration::from_secs(60)) + .map_err(anyhow::Error::from), + )?; + + verifier_chain + .collect_n( + 1, + |e| matches!(e, InternalBeaconNodeEvent::ExecutionProofVerified { .. }), + Duration::from_secs(30), + ) + .await?; + + Ok(()) + } + + /// Assert that the proof verifier receives gossip proofs from the generator and that the + /// full pipeline — gossip arrival → chain verification — completes successfully. + #[tokio::test] + #[cfg_attr(debug_assertions, ignore = "too slow in debug mode")] + async fn test_proof_verifier_receives_proofs() -> anyhow::Result<()> { + let mut rig = ProofEngineTestRig::standard().await?; + rig.fixture.payloads_valid(); + rig.fixture.wait_for_genesis().await?; + + // Subscribe to both streams before proofs start flowing so no events are missed. + let mut mock_events = rig.proof_verifier_events(0)?; + let mut chain_events = rig.proof_verifier_chain_events(0)?; + + // Mock engine confirms the proof was processed by the EL. + mock_events + .expect_proof_verified(1, Duration::from_secs(60)) + .await?; + + // Chain events confirm the full gossip pipeline: arrival then on-chain verification. + // Events are buffered since subscription, so these complete immediately. + chain_events + .collect_n( + 1, + |e| matches!(e, InternalBeaconNodeEvent::GossipExecutionProof(_)), + Duration::from_secs(60), + ) + .await?; + chain_events + .collect_n( + 1, + |e| { + matches!( + e, + InternalBeaconNodeEvent::ExecutionProofVerified { status, .. } + if status.is_valid() + ) + }, + Duration::from_secs(30), + ) + .await?; + + Ok(()) + } + + /// Assert that two independent proof generators each receive proof requests, and that the + /// verifier receives gossip proofs from the network. + #[tokio::test] + #[cfg_attr(debug_assertions, ignore = "too slow in debug mode")] + async fn test_multi_generator_proof_requests() -> anyhow::Result<()> { + let mut rig = ProofEngineTestRig::multi_generator().await?; + rig.fixture.payloads_valid(); + rig.fixture.wait_for_genesis().await?; + + let mut gen0 = rig.proof_generator_events(0)?; + let mut gen1 = rig.proof_generator_events(1)?; + let mut verifier_chain = rig.proof_verifier_chain_events(0)?; + + // Both generators must independently receive proof requests from their EL. + try_join!( + gen0.expect_proof_requests(1, Duration::from_secs(30)), + gen1.expect_proof_requests(1, Duration::from_secs(30)), + )?; + + // The verifier must receive at least one gossip proof from the network and verify it. + verifier_chain + .collect_n( + 1, + |e| matches!(e, InternalBeaconNodeEvent::GossipExecutionProof(_)), + Duration::from_secs(60), + ) + .await?; + verifier_chain + .collect_n( + 1, + |e| matches!(e, InternalBeaconNodeEvent::ExecutionProofVerified { .. }), + Duration::from_secs(30), + ) + .await?; + + Ok(()) + } + + /// Assert that the network reaches finality (epoch ≥ 2) while the proof engine is running. + #[tokio::test] + #[cfg_attr(debug_assertions, ignore = "too slow in debug mode")] + async fn test_network_finalizes_with_proofs() -> anyhow::Result<()> { + let mut rig = ProofEngineTestRig::standard().await?; + rig.fixture.payloads_valid(); + rig.fixture.wait_for_genesis().await?; + + // MinimalEthSpec: 8 slots/epoch. Finality of epoch 2 requires epochs 3-4 to elapse. + // 4 epochs * 8 slots * 1s = 32s minimum; use 45s for margin. + tokio::time::sleep(Duration::from_secs(45)).await; + + // Check finality on the default node and the proof generator independently. + for node in [rig.default_node(0)?, rig.proof_generator_node(0)?] { + let checkpoint = node + .get_beacon_states_finality_checkpoints(StateId::Head) + .await + .map_err(|e| anyhow::anyhow!("{e:?}"))? + .ok_or_else(|| anyhow::anyhow!("no finality checkpoint response"))? + .data + .finalized; + assert!( + checkpoint.epoch >= Epoch::new(2), + "expected finality at epoch ≥ 2, got {}", + checkpoint.epoch + ); + } + + Ok(()) + } +} diff --git a/testing/proof_engine/src/rig.rs b/testing/proof_engine/src/rig.rs new file mode 100644 index 00000000000..f4fa74d9f3e --- /dev/null +++ b/testing/proof_engine/src/rig.rs @@ -0,0 +1,231 @@ +//! [`ProofEngineTestRig`] — a thin wrapper over [`TestNetworkFixture`] for EIP-8025 tests. +//! +//! Provides a clean API for building standard proof engine test topologies and asserting +//! on mock proof engine events, insulating individual tests from `LocalNetwork` internals. + +use anyhow::anyhow; +use simulator::test_utils::{ + BeaconNodeHttpClient, Epoch, EventStream, InternalBeaconNodeEvent, LocalNetworkParams, + NodeType, TestNetworkFixture, TestNetworkFixtureBuilder, +}; +use types::MinimalEthSpec; + +pub use simulator::test_utils::MockEventStream; + +pub type E = MinimalEthSpec; + +/// Test harness for EIP-8025 proof engine integration tests. +pub struct ProofEngineTestRig { + pub fixture: TestNetworkFixture, +} + +impl ProofEngineTestRig { + /// Wrap a fixture directly. + pub fn new(fixture: TestNetworkFixture) -> Self { + Self { fixture } + } + + /// Standard topology: 1 vanilla node + 1 proof generator + 1 proof verifier. + /// All forks activate at genesis, 1-second slots. + pub async fn standard() -> anyhow::Result { + Ok(Self::new(base_builder().build().await?)) + } + + /// Sync topology: 1 vanilla node + 1 proof generator, no verifier, 1 delayed node slot. + /// Used for testing late-joining proof verifier sync recovery. + pub async fn sync_topology() -> anyhow::Result { + Ok(Self::new( + base_builder() + .map_spec(|spec| { + // Collapse all columns onto a single subnet so the small network can cover them. + spec.data_column_sidecar_subnet_count = 1; + spec.number_of_custody_groups = 8; + }) + .map_network_params(|params| { + params.proof_verifier_nodes = 0; + params.delayed_nodes = 1; + }) + .build() + .await?, + )) + } + + /// Multi-generator topology: 1 vanilla node + 2 proof generators + 1 proof verifier. + /// Used for testing that each generator is independently wired. + pub async fn multi_generator() -> anyhow::Result { + Ok(Self::new( + base_builder() + .map_network_params(|params| { + params.proof_generator_nodes = 2; + }) + .build() + .await?, + )) + } + + /// Subscribe to the nth proof generator node's event stream (0-indexed). + pub fn proof_generator_events(&self, n: usize) -> anyhow::Result { + let idx = self.fixture.config.network_params.node_count + n; + self.fixture + .network + .node_subscribe_client_events(idx) + .map(MockEventStream::from) + .ok_or_else(|| anyhow!("no proof generator at index {n}")) + } + + /// Subscribe to the nth proof verifier node's event stream (0-indexed). + pub fn proof_verifier_events(&self, n: usize) -> anyhow::Result { + let params = &self.fixture.config.network_params; + let idx = params.node_count + params.proof_generator_nodes + n; + self.fixture + .network + .node_subscribe_client_events(idx) + .map(MockEventStream::from) + .ok_or_else(|| anyhow!("no proof verifier at index {n}")) + } + + /// Subscribe to the internal event bus for the nth default node (0-indexed). + pub fn default_node_chain_events( + &self, + n: usize, + ) -> anyhow::Result> { + self.fixture + .network + .node_subscribe_internal_events(n) + .map(EventStream::from) + .ok_or_else(|| anyhow!("no default node at index {n}")) + } + + /// Subscribe to the internal event bus for the nth proof generator node (0-indexed). + pub fn proof_generator_chain_events( + &self, + n: usize, + ) -> anyhow::Result> { + let idx = self.fixture.config.network_params.node_count + n; + self.fixture + .network + .node_subscribe_internal_events(idx) + .map(EventStream::from) + .ok_or_else(|| anyhow!("no proof generator at index {n}")) + } + + /// Subscribe to the internal event bus for the nth proof verifier node (0-indexed). + pub fn proof_verifier_chain_events( + &self, + n: usize, + ) -> anyhow::Result> { + let params = &self.fixture.config.network_params; + let idx = params.node_count + params.proof_generator_nodes + n; + self.fixture + .network + .node_subscribe_internal_events(idx) + .map(EventStream::from) + .ok_or_else(|| anyhow!("no proof verifier at index {n}")) + } + + /// Return HTTP clients for all beacon nodes in the network. + pub fn remote_nodes(&self) -> anyhow::Result> { + self.fixture + .network + .remote_nodes() + .map_err(anyhow::Error::msg) + } + + /// Return an HTTP client for the nth default node (0-indexed). + pub fn default_node(&self, n: usize) -> anyhow::Result { + let idx = n; + self.fixture + .network + .remote_node(idx) + .ok_or_else(|| anyhow!("no default node at index {n}")) + } + + /// Return an HTTP client for the nth proof generator node (0-indexed). + pub fn proof_generator_node(&self, n: usize) -> anyhow::Result { + let idx = self.fixture.config.network_params.node_count + n; + self.fixture + .network + .remote_node(idx) + .ok_or_else(|| anyhow!("no proof generator node at index {n}")) + } + + /// Return an HTTP client for the nth proof verifier node (0-indexed). + pub fn proof_verifier_node(&self, n: usize) -> anyhow::Result { + let params = &self.fixture.config.network_params; + let idx = params.node_count + params.proof_generator_nodes + n; + self.fixture + .network + .remote_node(idx) + .ok_or_else(|| anyhow!("no proof verifier node at index {n}")) + } + + /// Add a proof verifier node dynamically and return its mock and internal event streams. + pub async fn add_proof_verifier_and_subscribe( + &self, + ) -> anyhow::Result<(MockEventStream, EventStream)> { + let client_config = self.fixture.config.client.clone(); + let exec_config = self.fixture.config.execution.clone(); + + // Await the node start so we know its index in beacon_nodes before subscribing. + // Spawning + sleeping is unreliable on slow CI runners where node startup takes + // longer than the fixed sleep duration. + self.fixture + .network + .add_beacon_node(client_config, exec_config, NodeType::ProofVerifier) + .await + .map_err(anyhow::Error::msg)?; + + // The new verifier is the last beacon node; subscribe to both event streams. + let idx = self + .fixture + .network + .beacon_nodes + .read() + .len() + .saturating_sub(1); + let mock = self + .fixture + .network + .node_subscribe_client_events(idx) + .map(MockEventStream::from) + .ok_or_else(|| anyhow!("newly added verifier node has no mock event stream"))?; + let chain = self + .fixture + .network + .node_subscribe_internal_events(idx) + .map(EventStream::from) + .ok_or_else(|| anyhow!("newly added verifier node has no beacon chain"))?; + Ok((mock, chain)) + } + + /// Builder escape hatch for custom topologies. + pub fn builder() -> TestNetworkFixtureBuilder { + base_builder() + } +} + +/// Base builder shared by all standard topologies. +fn base_builder() -> TestNetworkFixtureBuilder { + TestNetworkFixture::builder() + .map_spec(|spec| { + spec.seconds_per_slot = 1; + spec.slot_duration_ms = 1000; + spec.min_genesis_time = 0; + spec.altair_fork_epoch = Some(Epoch::new(0)); + spec.bellatrix_fork_epoch = Some(Epoch::new(0)); + spec.capella_fork_epoch = Some(Epoch::new(0)); + spec.deneb_fork_epoch = Some(Epoch::new(0)); + spec.electra_fork_epoch = Some(Epoch::new(0)); + spec.fulu_fork_epoch = Some(Epoch::new(0)); + }) + .with_network_params(LocalNetworkParams { + validator_count: 4, + node_count: 1, + proposer_nodes: 0, + extra_nodes: 0, + proof_generator_nodes: 1, + proof_verifier_nodes: 1, + delayed_nodes: 0, + genesis_delay: 40, + }) +} diff --git a/testing/proof_engine_zkboost/Cargo.lock b/testing/proof_engine_zkboost/Cargo.lock new file mode 100644 index 00000000000..6e81ed9aa9c --- /dev/null +++ b/testing/proof_engine_zkboost/Cargo.lock @@ -0,0 +1,10425 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addchain" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e33f6a175ec6a9e0aca777567f9ff7c3deefc255660df887e7fa3585e9801d8" +dependencies = [ + "num-bigint 0.3.3", + "num-integer", + "num-traits", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[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 1.0.4", + "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 1.0.4", + "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 = "alloy-chains" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e9e31d834fe25fe991b8884e4b9f0e59db4a97d86e05d1464d6899c013cd62" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "num_enum", + "serde", + "strum", +] + +[[package]] +name = "alloy-consensus" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20d33348e61388eb90da06e176030abf496120e54795273210eeea2cd76339c4" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-trie 0.9.5", + "alloy-tx-macros", + "auto_impl", + "borsh", + "c-kzg", + "derive_more 2.1.1", + "either", + "k256", + "once_cell", + "rand 0.8.5", + "secp256k1", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-consensus-any" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d145b64057ea2b66c6146817bee0ffc3adfb3f51c2b60f282f5200b23a6b4bfa" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-eip2124" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "741bdd7499908b3aa0b159bba11e71c8cddd009a2c2eb7a06e825f1ec87900a5" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "crc", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-eip2930" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9441120fa82df73e8959ae0e4ab8ade03de2aaae61be313fbf5746277847ce25" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "serde", +] + +[[package]] +name = "alloy-eip7702" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2919c5a56a1007492da313e7a3b6d45ef5edc5d33416fdec63c0d7a2702a0d20" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "k256", + "serde", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-eip7928" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8222b1d88f9a6d03be84b0f5e76bb60cd83991b43ad8ab6477f0e4a7809b98d" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "serde", +] + +[[package]] +name = "alloy-eips" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5194943ebbbf25d308e13797b275dd1bf41487f22156dd2c8e17d24726a03888" +dependencies = [ + "alloy-eip2124", + "alloy-eip2930", + "alloy-eip7702", + "alloy-eip7928", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "auto_impl", + "borsh", + "c-kzg", + "derive_more 2.1.1", + "either", + "serde", + "serde_with", + "sha2", +] + +[[package]] +name = "alloy-evm" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b99ba7b74a87176f31ee1cd26768f7155b0eeff61ed925f59b13085ffe5f891" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-hardforks", + "alloy-primitives", + "alloy-sol-types", + "auto_impl", + "derive_more 2.1.1", + "revm", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-genesis" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3de7e0b146edaf966a1d2b5320903ce4a3e4e8452e4710585c0a22967216366" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "alloy-trie 0.9.5", + "borsh", + "serde", + "serde_with", +] + +[[package]] +name = "alloy-hardforks" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83ba208044232d14d4adbfa77e57d6329f51bc1acc21f5667bb7db72d88a0831" +dependencies = [ + "alloy-chains", + "alloy-eip2124", + "alloy-primitives", + "auto_impl", + "dyn-clone", +] + +[[package]] +name = "alloy-json-abi" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9dbe713da0c737d9e5e387b0ba790eb98b14dd207fe53eef50e19a5a8ec3dac" +dependencies = [ + "alloy-primitives", + "alloy-sol-type-parser", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-network-primitives" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6e4eef41e348207945ae12d445d1d168ade6ada09161b0c9b05169f47ca81f" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-primitives" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3b431b4e72cd8bd0ec7a50b4be18e73dab74de0dba180eef171055e5d5926e" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if 1.0.4", + "const-hex", + "derive_more 2.1.1", + "foldhash 0.2.0", + "getrandom 0.4.2", + "hashbrown 0.16.1", + "indexmap 2.13.0", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand 0.9.2", + "rapidhash", + "ruint", + "rustc-hash 2.1.1", + "serde", + "sha3", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e93e50f64a77ad9c5470bf2ad0ca02f228da70c792a8f06634801e202579f35e" +dependencies = [ + "alloy-rlp-derive", + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8849c74c9ca0f5a03da1c865e3eb6f768df816e67dd3721a398a8a7e398011" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "alloy-rpc-types-debug" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d701b7e29f69634e6cd5111e7cee333278557f7bf5d5e4764026e56adec75e1e" +dependencies = [ + "alloy-primitives", + "derive_more 2.1.1", + "serde", + "serde_with", +] + +[[package]] +name = "alloy-rpc-types-engine" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0bb761c5b208dcb938badf0b1243f623b1b80c15282f85fbeb45efa18f1120" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "derive_more 2.1.1", + "jsonwebtoken", + "rand 0.8.5", + "serde", + "strum", +] + +[[package]] +name = "alloy-rpc-types-eth" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f71d86495861c7b6cff4ed7e0c114f13c8d12c5406f03e88981b834c18715aa8" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-sol-types", + "itertools 0.14.0", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-serde" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54d6afd8bd1ec6d34a01d03d3a6c547cfcb197dd631cc8908d183da69b7c4c92" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-sol-macro" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab81bab693da9bb79f7a95b64b394718259fdd7e41dceeced4cad57cb71c4f6a" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489f1620bb7e2483fb5819ed01ab6edc1d2f93939dce35a5695085a1afd1d699" +dependencies = [ + "alloy-sol-macro-input", + "const-hex", + "heck", + "indexmap 2.13.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "sha3", + "syn 2.0.117", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56cef806ad22d4392c5fc83cf8f2089f988eb99c7067b4e0c6f1971fc1cca318" +dependencies = [ + "const-hex", + "dunce", + "heck", + "macro-string", + "proc-macro2", + "quote", + "syn 2.0.117", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6df77fea9d6a2a75c0ef8d2acbdfd92286cc599983d3175ccdc170d3433d249" +dependencies = [ + "serde", + "winnow 0.7.15", +] + +[[package]] +name = "alloy-sol-types" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64612d29379782a5dde6f4b6570d9c756d734d760c0c94c254d361e678a6591f" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-macro", + "serde", +] + +[[package]] +name = "alloy-trie" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "983d99aa81f586cef9dae38443245e585840fcf0fc58b09aee0b1f27aed1d500" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arrayvec", + "derive_more 2.1.1", + "nybbles 0.3.4", + "smallvec", + "tracing", +] + +[[package]] +name = "alloy-trie" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f14b5d9b2c2173980202c6ff470d96e7c5e202c65a9f67884ad565226df7fbb" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "derive_more 2.1.1", + "nybbles 0.4.8", + "serde", + "smallvec", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "alloy-tx-macros" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7a6b9edd1f6ea02b4f131d76b1aaf01b6ae53fbf91edec97a4e18ef60c1a0a" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[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 = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arc-swap" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a07d1f37ff60921c83bdfc7407723bdefe89b44b98a9b772f225c8f9d67141a6" +dependencies = [ + "rustversion", +] + +[[package]] +name = "archery" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a8da9bc4c4053ee067669762bcaeea6e241841295a2b6c948312dad6ef4cc02" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "ark-bls12-381" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df4dcc01ff89867cd86b0da835f23c3f02738353aaee7dde7495af71363b8d5" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", +] + +[[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 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-poly", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rustc_version 0.4.1", + "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 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[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.117", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint 0.4.6", +] + +[[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 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint 0.4.6", +] + +[[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.117", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[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.5", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "asn1_der" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4858a9d740c5007a9069007c3b4e91152d0506f13c1b31dd49051fd537656156" + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "aurora-engine-modexp" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518bc5745a6264b5fd7b09dffb9667e400ee9e2bbe18555fac75e1fe9afa0df9" +dependencies = [ + "hex", + "num", +] + +[[package]] +name = "auto_impl" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core 0.4.5", + "bytes", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-util", + "itoa", + "matchit 0.7.3", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core 0.5.6", + "axum-macros", + "base64 0.22.1", + "bytes", + "form_urlencoded", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-util", + "itoa", + "matchit 0.8.4", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper", + "tokio", + "tokio-tungstenite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-extra" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9963ff19f40c6102c76756ef0a46004c0d58957d87259fc9208ff8441c12ab96" +dependencies = [ + "axum 0.8.8", + "axum-core 0.5.6", + "bytes", + "futures-util", + "headers 0.4.1", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "serde_core", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base256emoji" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" +dependencies = [ + "const-str", + "match-lookup", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[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 = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "serde", + "unty", +] + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.117", + "which", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +dependencies = [ + "serde_core", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "serde", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79834656f71332577234b50bfc009996f7449e0c056884e6a02492ded0ca2f3" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "blake3" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if 1.0.4", + "constant_time_eq", + "cpufeatures", +] + +[[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 = "bls" +version = "0.2.0" +dependencies = [ + "alloy-primitives", + "arbitrary", + "blst", + "ethereum_hashing 0.8.0", + "ethereum_serde_utils", + "ethereum_ssz 0.10.1", + "fixed_bytes 0.1.0", + "hex", + "rand 0.9.2", + "safe_arith", + "serde", + "tree_hash 0.12.1", + "zeroize", +] + +[[package]] +name = "bls" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?branch=unstable#c7055b604f9958db410b2e42023763cb19dd7138" +dependencies = [ + "alloy-primitives", + "blst", + "ethereum_hashing 0.8.0", + "ethereum_serde_utils", + "ethereum_ssz 0.10.1", + "fixed_bytes 0.1.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "hex", + "rand 0.9.2", + "safe_arith", + "serde", + "tree_hash 0.12.1", + "zeroize", +] + +[[package]] +name = "bls12_381" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c196a77437e7cc2fb515ce413a6401291578b5afc8ecb29a3c7ab957f05941" +dependencies = [ + "ff 0.12.1", + "group 0.12.1", + "pairing 0.22.0", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "bls12_381" +version = "0.8.0" +source = "git+https://github.com/lambdaclass/bls12_381?branch=expose-fp-struct#219174187bd78154cec35b0809799fc2c991a579" +dependencies = [ + "digest 0.10.7", + "ff 0.13.1", + "group 0.13.0", + "pairing 0.23.0", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "blst" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + +[[package]] +name = "blstrs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8a8ed6fefbeef4a8c7b460e4110e12c5e22a5b7cf32621aae6ad650c4dcf29" +dependencies = [ + "blst", + "byte-slice-cast", + "ff 0.13.1", + "group 0.13.0", + "pairing 0.23.0", + "rand_core 0.6.4", + "serde", + "subtle", +] + +[[package]] +name = "borsh" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "builder_client" +version = "0.1.0" +dependencies = [ + "bls 0.2.0", + "context_deserialize", + "eth2", + "ethereum_ssz 0.10.1", + "lighthouse_version", + "reqwest", + "sensitive_url", + "serde", + "serde_json", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[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.117", +] + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +dependencies = [ + "serde", +] + +[[package]] +name = "c-kzg" +version = "2.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6648ed1e4ea8e8a1a4a2c78e1cda29a3fd500bc622899c340d8525ea9a76b24a" +dependencies = [ + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] + +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.27", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "cc" +version = "1.2.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "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 = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_derive" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "clap_utils" +version = "0.1.0" +dependencies = [ + "alloy-primitives", + "clap", + "dirs", + "eth2_network_config", + "ethereum_ssz 0.10.1", + "hex", + "serde", + "serde_json", + "serde_yaml", + "types 0.2.1", +] + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "compare_fields" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f45d0b4d61b582303179fb7a1a142bc9d647b7583db3b0d5f25a21d286fab9" +dependencies = [ + "compare_fields_derive", + "itertools 0.14.0", +] + +[[package]] +name = "compare_fields_derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ff1dbbda10d495b2c92749c002b2025e0be98f42d1741ecc9ff820d2f04dce" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "concat-kdf" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d72c1252426a83be2092dd5884a5f6e3b8e7180f6891b6263d2c21b92ec8816" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils 0.8.21", +] + +[[package]] +name = "const-hex" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b" +dependencies = [ + "cfg-if 1.0.4", + "cpufeatures", + "proptest", + "serde_core", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-str" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" + +[[package]] +name = "const_format" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] + +[[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 = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "context_deserialize" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c523eea4af094b5970c321f4604abc42c5549d3cbae332e98325403fbbdbf70" +dependencies = [ + "context_deserialize_derive", + "serde", +] + +[[package]] +name = "context_deserialize_derive" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7bf98c48ffa511b14bb3c76202c24a8742cea1efa9570391c5d41373419a09" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[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 = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if 1.0.4", +] + +[[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.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" +dependencies = [ + "cfg-if 0.1.10", + "crossbeam-channel 0.4.4", + "crossbeam-deque 0.7.4", + "crossbeam-epoch 0.8.2", + "crossbeam-queue 0.2.3", + "crossbeam-utils 0.7.2", +] + +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel 0.5.15", + "crossbeam-deque 0.8.6", + "crossbeam-epoch 0.9.18", + "crossbeam-queue 0.3.12", + "crossbeam-utils 0.8.21", +] + +[[package]] +name = "crossbeam-channel" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" +dependencies = [ + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils 0.8.21", +] + +[[package]] +name = "crossbeam-deque" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20ff29ded3204c5106278a81a38f4b482636ed4fa1e6cfbeef193291beb29ed" +dependencies = [ + "crossbeam-epoch 0.8.2", + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch 0.9.18", + "crossbeam-utils 0.8.21", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "crossbeam-utils 0.7.2", + "lazy_static", + "maybe-uninit", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils 0.8.21", +] + +[[package]] +name = "crossbeam-queue" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" +dependencies = [ + "cfg-if 0.1.10", + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils 0.8.21", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[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 = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if 1.0.4", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rustc_version 0.4.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[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", + "serde", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "data-encoding-macro" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" +dependencies = [ + "data-encoding", + "syn 2.0.117", +] + +[[package]] +name = "datatest-stable" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833306ca7eec4d95844e65f0d7502db43888c5c1006c6c517e8cf51a27d15431" +dependencies = [ + "camino", + "fancy-regex", + "libtest-mimic", + "walkdir", +] + +[[package]] +name = "db-key" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72465f46d518f6015d9cf07f7f3013a95dd6b9c2747c3d65ae0cce43929d14f" + +[[package]] +name = "delay_map" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88e365f083a5cb5972d50ce8b1b2c9f125dc5ec0f50c0248cfb568ae59efcf0b" +dependencies = [ + "futures", + "tokio", + "tokio-util", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive-where" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl 1.0.0", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl 2.1.1", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "convert_case 0.6.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "unicode-xid", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case 0.10.0", + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "syn 2.0.117", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[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 = "directory" +version = "0.1.0" +dependencies = [ + "clap", + "clap_utils", + "eth2_network_config", +] + +[[package]] +name = "dirs" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "discv5" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7999df38d0bd8f688212e1a4fae31fd2fea6d218649b9cd7c40bf3ec1318fc" +dependencies = [ + "aes", + "aes-gcm", + "alloy-rlp", + "arrayvec", + "ctr", + "delay_map", + "enr", + "fnv", + "futures", + "hashlink", + "hex", + "hkdf", + "lazy_static", + "libp2p-identity", + "more-asserts", + "multiaddr", + "parking_lot", + "rand 0.8.5", + "smallvec", + "socket2 0.6.3", + "tokio", + "tracing", + "uint 0.10.0", + "zeroize", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[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 0.10.7", + "elliptic-curve", + "rfc6979", + "serdect", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[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.117", +] + +[[package]] +name = "eip4844" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82ab45fc63db6bbe5c3eb7c79303b2aff7ee529c991b2111c46879d1ea38407e" +dependencies = [ + "ekzg-bls12-381", + "ekzg-maybe-rayon", + "ekzg-polynomial", + "ekzg-serialization", + "ekzg-single-open", + "ekzg-trusted-setup", + "hex", + "itertools 0.14.0", + "serde", + "serde_json", + "sha2", +] + +[[package]] +name = "eip_3076" +version = "0.1.0" +dependencies = [ + "bls 0.2.0", + "ethereum_serde_utils", + "fixed_bytes 0.1.0", + "serde", + "types 0.2.1", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "ekzg-bls12-381" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c599a59deba6188afd9f783507e4d89efc997f0fa340a758f0d0992b322416" +dependencies = [ + "blst", + "blstrs", + "ff 0.13.1", + "group 0.13.0", + "pairing 0.23.0", + "subtle", +] + +[[package]] +name = "ekzg-erasure-codes" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8474a41a30ddd2b651798b1aa9ce92011207c3667186fe9044184683250109e7" +dependencies = [ + "ekzg-bls12-381", + "ekzg-polynomial", +] + +[[package]] +name = "ekzg-maybe-rayon" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf94d1385185c1f7caef4973be49702c7d9ffdeaf832d126dbb9ed6efe09d40" + +[[package]] +name = "ekzg-multi-open" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d37456a32cf79bdbddd6685a2adec73210e2d60332370bc0e9a502b6d93beb" +dependencies = [ + "ekzg-bls12-381", + "ekzg-maybe-rayon", + "ekzg-polynomial", + "sha2", +] + +[[package]] +name = "ekzg-polynomial" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704751bac85af4754bb8a14457ef24d820738062d0b6f3763534d0980b1a1e81" +dependencies = [ + "ekzg-bls12-381", + "ekzg-maybe-rayon", +] + +[[package]] +name = "ekzg-serialization" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb983d9f75b2804c00246def8d52c01cf05f70c22593b8d314fbcf0cf89042b" +dependencies = [ + "ekzg-bls12-381", + "hex", +] + +[[package]] +name = "ekzg-single-open" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799d5806d51e1453fa0f528d6acf4127e2a89e98312c826151ebc24ee3448ec3" +dependencies = [ + "ekzg-bls12-381", + "ekzg-polynomial", + "itertools 0.14.0", +] + +[[package]] +name = "ekzg-trusted-setup" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85314d56718dc2c6dd77c3b3630f1839defcb6f47d9c20195608a0f7976095ab" +dependencies = [ + "ekzg-bls12-381", + "ekzg-serialization", + "hex", + "serde", + "serde_json", +] + +[[package]] +name = "elf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" + +[[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 0.10.7", + "ff 0.13.1", + "generic-array", + "group 0.13.0", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if 1.0.4", +] + +[[package]] +name = "enr" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "851bd664a3d3a3c175cff92b2f0df02df3c541b4895d0ae307611827aae46152" +dependencies = [ + "alloy-rlp", + "base64 0.22.1", + "bytes", + "ed25519-dalek", + "hex", + "k256", + "log", + "rand 0.8.5", + "serde", + "sha3", + "zeroize", +] + +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "envy" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965" +dependencies = [ + "serde", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "ere-io" +version = "0.3.0" +source = "git+https://github.com/eth-act/ere?tag=v0.3.0#ffc0e230cf4387fb22eb755f0fb323b38294520f" +dependencies = [ + "bincode 2.0.1", + "rkyv", + "serde", +] + +[[package]] +name = "ere-platform-trait" +version = "0.3.0" +source = "git+https://github.com/eth-act/ere?tag=v0.3.0#ffc0e230cf4387fb22eb755f0fb323b38294520f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "ere-server" +version = "0.3.0" +source = "git+https://github.com/eth-act/ere?tag=v0.3.0#ffc0e230cf4387fb22eb755f0fb323b38294520f" +dependencies = [ + "anyhow", + "bincode 2.0.1", + "ere-zkvm-interface", + "prost", + "serde", + "thiserror 2.0.18", + "tokio", + "twirp", +] + +[[package]] +name = "ere-zkvm-interface" +version = "0.3.0" +source = "git+https://github.com/eth-act/ere?tag=v0.3.0#ffc0e230cf4387fb22eb755f0fb323b38294520f" +dependencies = [ + "anyhow", + "auto_impl", + "bincode 2.0.1", + "clap", + "indexmap 2.13.0", + "serde", + "strum", + "thiserror 2.0.18", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "escape8259" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" + +[[package]] +name = "eth2" +version = "0.1.0" +dependencies = [ + "bls 0.2.0", + "context_deserialize", + "educe", + "eip_3076", + "eth2_keystore", + "ethereum_serde_utils", + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "futures", + "futures-util", + "mediatype", + "pretty_reqwest_error", + "proto_array", + "reqwest", + "reqwest-eventsource", + "sensitive_url", + "serde", + "serde_json", + "ssz_types 0.14.0", + "superstruct", + "types 0.2.1", + "zeroize", +] + +[[package]] +name = "eth2_config" +version = "0.2.0" +dependencies = [ + "paste", + "types 0.2.1", +] + +[[package]] +name = "eth2_interop_keypairs" +version = "0.2.0" +dependencies = [ + "bls 0.2.0", + "ethereum_hashing 0.8.0", + "hex", + "num-bigint 0.4.6", + "serde", + "serde_yaml", +] + +[[package]] +name = "eth2_interop_keypairs" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?branch=unstable#c7055b604f9958db410b2e42023763cb19dd7138" +dependencies = [ + "bls 0.2.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "ethereum_hashing 0.8.0", + "hex", + "num-bigint 0.4.6", + "serde", + "serde_yaml", +] + +[[package]] +name = "eth2_key_derivation" +version = "0.1.0" +dependencies = [ + "bls 0.2.0", + "num-bigint-dig", + "ring", + "sha2", + "zeroize", +] + +[[package]] +name = "eth2_keystore" +version = "0.1.0" +dependencies = [ + "aes", + "bls 0.2.0", + "cipher", + "ctr", + "eth2_key_derivation", + "hex", + "hmac", + "pbkdf2", + "rand 0.9.2", + "scrypt", + "serde", + "serde_json", + "serde_repr", + "sha2", + "unicode-normalization", + "uuid", + "zeroize", +] + +[[package]] +name = "eth2_network_config" +version = "0.2.0" +dependencies = [ + "bytes", + "discv5", + "eth2_config", + "fixed_bytes 0.1.0", + "kzg 0.1.0", + "pretty_reqwest_error", + "reqwest", + "sensitive_url", + "serde_yaml", + "sha2", + "tracing", + "types 0.2.1", + "url", + "zip", +] + +[[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 0.13.1", + "uint 0.10.0", +] + +[[package]] +name = "ethereum_hashing" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c853bd72c9e5787f8aafc3df2907c2ed03cff3150c3acd94e2e53a98ab70a8ab" +dependencies = [ + "cpufeatures", + "ring", + "sha2", +] + +[[package]] +name = "ethereum_hashing" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa93f58bb1eb3d1e556e4f408ef1dac130bad01ac37db4e7ade45de40d1c86a" +dependencies = [ + "cpufeatures", + "ring", + "sha2", +] + +[[package]] +name = "ethereum_serde_utils" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dc1355dbb41fbbd34ec28d4fb2a57d9a70c67ac3c19f6a5ca4d4a176b9e997a" +dependencies = [ + "alloy-primitives", + "hex", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "ethereum_ssz" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dcddb2554d19cde19b099fadddde576929d7a4d0c1cd3512d1fd95cf174375c" +dependencies = [ + "alloy-primitives", + "ethereum_serde_utils", + "itertools 0.13.0", + "serde", + "serde_derive", + "smallvec", + "typenum", +] + +[[package]] +name = "ethereum_ssz" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2128a84f7a3850d54ee343334e3392cca61f9f6aa9441eec481b9394b43c238b" +dependencies = [ + "alloy-primitives", + "context_deserialize", + "ethereum_serde_utils", + "itertools 0.14.0", + "serde", + "serde_derive", + "smallvec", + "typenum", +] + +[[package]] +name = "ethereum_ssz_derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a657b6b3b7e153637dc6bdc6566ad9279d9ee11a15b12cfb24a2e04360637e9f" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ethereum_ssz_derive" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd596f91cff004fc8d02be44c21c0f9b93140a04b66027ae052f5f8e05b48eba" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ethrex-blockchain" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "bytes", + "ethrex-common", + "ethrex-crypto", + "ethrex-metrics", + "ethrex-rlp", + "ethrex-storage", + "ethrex-trie", + "ethrex-vm", + "hex", + "rustc-hash 2.1.1", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "ethrex-common" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "bytes", + "crc32fast", + "ethereum-types", + "ethrex-crypto", + "ethrex-rlp", + "ethrex-trie", + "hex", + "hex-literal", + "k256", + "kzg-rs", + "lazy_static", + "libc", + "once_cell", + "rayon", + "rkyv", + "rustc-hash 2.1.1", + "serde", + "serde_json", + "sha2", + "sha3", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "url", +] + +[[package]] +name = "ethrex-crypto" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "c-kzg", + "kzg-rs", + "thiserror 2.0.18", + "tiny-keccak", +] + +[[package]] +name = "ethrex-l2-common" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "bytes", + "ethereum-types", + "ethrex-common", + "ethrex-crypto", + "ethrex-rlp", + "ethrex-storage", + "ethrex-trie", + "ethrex-vm", + "hex", + "k256", + "lambdaworks-crypto", + "rkyv", + "serde", + "serde_with", + "sha3", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "ethrex-levm" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "ark-bn254", + "ark-ec", + "ark-ff 0.5.0", + "bitvec", + "bls12_381 0.8.0", + "bytes", + "datatest-stable", + "derive_more 1.0.0", + "ethrex-common", + "ethrex-crypto", + "ethrex-rlp", + "k256", + "lambdaworks-math", + "lazy_static", + "malachite", + "p256", + "ripemd", + "rustc-hash 2.1.1", + "serde", + "serde_json", + "sha2", + "sha3", + "strum", + "thiserror 2.0.18", + "walkdir", +] + +[[package]] +name = "ethrex-metrics" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "axum 0.8.8", + "ethrex-common", + "prometheus 0.13.4", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "ethrex-p2p" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "aes", + "async-trait", + "bytes", + "concat-kdf", + "crossbeam 0.8.4", + "ctr", + "ethereum-types", + "ethrex-blockchain", + "ethrex-common", + "ethrex-crypto", + "ethrex-rlp", + "ethrex-storage", + "ethrex-threadpool", + "ethrex-trie", + "futures", + "hex", + "hmac", + "indexmap 2.13.0", + "lazy_static", + "prometheus 0.14.0", + "rand 0.8.5", + "rayon", + "rustc-hash 2.1.1", + "secp256k1", + "serde", + "serde_json", + "sha2", + "snap", + "spawned-concurrency", + "spawned-rt", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + +[[package]] +name = "ethrex-rlp" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "bytes", + "ethereum-types", + "hex", + "lazy_static", + "snap", + "thiserror 2.0.18", + "tinyvec", +] + +[[package]] +name = "ethrex-rpc" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "axum 0.8.8", + "axum-extra", + "bytes", + "envy", + "ethereum-types", + "ethrex-blockchain", + "ethrex-common", + "ethrex-crypto", + "ethrex-metrics", + "ethrex-p2p", + "ethrex-rlp", + "ethrex-storage", + "ethrex-trie", + "ethrex-vm", + "hex", + "hex-literal", + "jsonwebtoken", + "rand 0.8.5", + "reqwest", + "secp256k1", + "serde", + "serde_json", + "sha2", + "spawned-concurrency", + "spawned-rt", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tower-http", + "tracing", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "ethrex-storage" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "anyhow", + "async-trait", + "bytes", + "ethereum-types", + "ethrex-common", + "ethrex-crypto", + "ethrex-rlp", + "ethrex-trie", + "hex", + "lru 0.16.3", + "qfilter", + "rayon", + "rustc-hash 2.1.1", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "ethrex-threadpool" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "crossbeam 0.8.4", +] + +[[package]] +name = "ethrex-trie" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "anyhow", + "bytes", + "crossbeam 0.8.4", + "digest 0.10.7", + "ethereum-types", + "ethrex-crypto", + "ethrex-rlp", + "ethrex-threadpool", + "hex", + "lazy_static", + "rkyv", + "rustc-hash 2.1.1", + "serde", + "serde_json", + "smallvec", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "ethrex-vm" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "bincode 1.3.3", + "bytes", + "derive_more 1.0.0", + "dyn-clone", + "ethereum-types", + "ethrex-common", + "ethrex-crypto", + "ethrex-levm", + "ethrex-rlp", + "ethrex-trie", + "lazy_static", + "rkyv", + "serde", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "eventsource-stream" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" +dependencies = [ + "futures-core", + "nom", + "pin-project-lite", +] + +[[package]] +name = "execution_layer" +version = "0.1.0" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth", + "arc-swap", + "async-stream", + "async-trait", + "bls 0.2.0", + "builder_client", + "bytes", + "eth2", + "ethereum_serde_utils", + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "fixed_bytes 0.1.0", + "fork_choice", + "futures", + "hash-db", + "hash256-std-hasher", + "hex", + "jsonwebtoken", + "keccak-hash", + "kzg 0.1.0", + "lighthouse_version", + "logging", + "lru 0.12.5", + "metrics 0.2.0", + "parking_lot", + "pretty_reqwest_error", + "rand 0.9.2", + "reqwest", + "reqwest-eventsource", + "sensitive_url", + "serde", + "serde_json", + "sha2", + "slot_clock", + "ssz_types 0.14.0", + "state_processing", + "store", + "strum", + "superstruct", + "task_executor", + "tempfile", + "tokio", + "tokio-stream", + "tracing", + "tree_hash 0.12.1", + "tree_hash_derive 0.12.1", + "triehash", + "typenum", + "types 0.2.1", + "warp", + "zeroize", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fancy-regex" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" +dependencies = [ + "bit-set", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "bitvec", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "bitvec", + "byteorder", + "ff_derive", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "ff_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f10d12652036b0e99197587c6ba87a8fc3031986499973c030d8b44fcc151b60" +dependencies = [ + "addchain", + "num-bigint 0.3.3", + "num-integer", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ffi-opaque" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec54ac60a7f2ee9a97cad9946f9bf629a3bc6a7ae59e68983dc9318f5a54b81a" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[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.5", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fixed-map" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ed19add84e8cb9e8cc5f7074de0324247149ffef0b851e215fb0edc50c229b" +dependencies = [ + "fixed-map-derive", +] + +[[package]] +name = "fixed-map-derive" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dc7a9cb3326bafb80642c5ce99b39a2c0702d4bfa8ee8a3e773791a6cbe2407" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "fixed_bytes" +version = "0.1.0" +dependencies = [ + "alloy-primitives", + "safe_arith", +] + +[[package]] +name = "fixed_bytes" +version = "0.1.0" +source = "git+https://github.com/sigp/lighthouse?branch=unstable#c7055b604f9958db410b2e42023763cb19dd7138" +dependencies = [ + "alloy-primitives", + "safe_arith", +] + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[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 = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "fork_choice" +version = "0.1.0" +dependencies = [ + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "fixed_bytes 0.1.0", + "logging", + "metrics 0.2.0", + "proto_array", + "state_processing", + "superstruct", + "tracing", + "types 0.2.1", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[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.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + +[[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 1.0.4", + "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 1.0.4", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if 1.0.4", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", + "memuse", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.1", + "rand 0.8.5", + "rand_core 0.6.4", + "rand_xorshift 0.3.0", + "subtle", +] + +[[package]] +name = "guest" +version = "0.5.0" +source = "git+https://github.com/eth-act/ere-guests?tag=v0.6.0#64c94bb3da631101a6cb2f276c89392cb7c3426f" +dependencies = [ + "ere-io", + "ere-platform-trait", + "sha2", +] + +[[package]] +name = "guest_program" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "bincode 1.3.3", + "bytes", + "ethrex-blockchain", + "ethrex-common", + "ethrex-crypto", + "ethrex-l2-common", + "ethrex-rlp", + "ethrex-storage", + "ethrex-trie", + "ethrex-vm", + "hex", + "rkyv", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.4.0", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "halo2" +version = "0.1.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a23c779b38253fe1538102da44ad5bd5378495a61d2c4ee18d64eaa61ae5995" +dependencies = [ + "halo2_proofs", +] + +[[package]] +name = "halo2_proofs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e925780549adee8364c7f2b685c753f6f3df23bde520c67416e93bf615933760" +dependencies = [ + "blake2b_simd", + "ff 0.12.1", + "group 0.12.1", + "pasta_curves 0.4.1", + "rand_core 0.6.4", + "rayon", +] + +[[package]] +name = "hash-db" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" + +[[package]] +name = "hash256-std-hasher" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" +dependencies = [ + "crunchy", +] + +[[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", + "equivalent", + "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", + "serde", + "serde_core", +] + +[[package]] +name = "hashlink" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core 0.2.0", + "http 0.2.12", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" +dependencies = [ + "base64 0.22.1", + "bytes", + "headers-core 0.3.0", + "http 1.4.0", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http 0.2.12", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http 1.4.0", +] + +[[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-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[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 0.10.7", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.4.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.4.0", + "hyper 1.8.1", + "hyper-util", + "rustls 0.23.37", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.8.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.8.1", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.3", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[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 = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[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 0.6.1", +] + +[[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.117", +] + +[[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.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.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 = "int_to_bytes" +version = "0.2.0" +dependencies = [ + "bytes", +] + +[[package]] +name = "int_to_bytes" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?branch=unstable#c7055b604f9958db410b2e42023763cb19dd7138" +dependencies = [ + "bytes", +] + +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e7418f59cc01c88316161279a7f665217ae316b388e58a0d10e29f54f1e5eb" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[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 = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64 0.22.1", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "jubjub" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a575df5f985fe1cd5b2b05664ff6accfc46559032b954529fd225a2168d27b0f" +dependencies = [ + "bitvec", + "bls12_381 0.7.1", + "ff 0.12.1", + "group 0.12.1", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if 1.0.4", + "ecdsa", + "elliptic-curve", + "once_cell", + "serdect", + "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 = "keccak-asm" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b646a74e746cd25045aa0fd42f4f7f78aa6d119380182c7e63a5593c4ab8df6f" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + +[[package]] +name = "keccak-hash" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b286e6b663fb926e1eeb68528e69cb70ed46c6d65871a21b2215ae8154c6d3c" +dependencies = [ + "primitive-types 0.12.2", + "tiny-keccak", +] + +[[package]] +name = "kzg" +version = "0.1.0" +dependencies = [ + "arbitrary", + "c-kzg", + "educe", + "ethereum_hashing 0.8.0", + "ethereum_serde_utils", + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "hex", + "rayon", + "rust_eth_kzg", + "serde", + "serde_json", + "tracing", + "tree_hash 0.12.1", +] + +[[package]] +name = "kzg" +version = "0.1.0" +source = "git+https://github.com/sigp/lighthouse?branch=unstable#c7055b604f9958db410b2e42023763cb19dd7138" +dependencies = [ + "educe", + "ethereum_hashing 0.8.0", + "ethereum_serde_utils", + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "hex", + "rayon", + "rust_eth_kzg", + "serde", + "serde_json", + "tracing", + "tree_hash 0.12.1", +] + +[[package]] +name = "kzg-rs" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8b4f55c3dedcfaa8668de1dfc8469e7a32d441c28edf225ed1f566fb32977d" +dependencies = [ + "ff 0.13.1", + "hex", + "serde_arrays", + "sha2", + "sp1_bls12_381", + "spin", +] + +[[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.5", + "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 0.4.6", + "num-traits", + "rand 0.8.5", + "rayon", + "serde", + "serde_json", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "leveldb" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32651baaaa5596b3a6e0bee625e73fd0334c167db0ea5ac68750ef9a629a2d6a" +dependencies = [ + "db-key", + "leveldb-sys", + "libc", +] + +[[package]] +name = "leveldb-sys" +version = "2.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd94a4d0242a437e5e41a27c782b69a624469ca1c4d1e5cb3c337f74a8031d4" +dependencies = [ + "cmake", + "ffi-opaque", + "libc", + "num_cpus", +] + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if 1.0.4", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libp2p-identity" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c7892c221730ba55f7196e98b0b8ba5e04b4155651736036628e9f73ed6fc3" +dependencies = [ + "asn1_der", + "bs58", + "ed25519-dalek", + "hkdf", + "k256", + "multihash", + "quick-protobuf", + "sha2", + "thiserror 2.0.18", + "tracing", + "zeroize", +] + +[[package]] +name = "libredox" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +dependencies = [ + "libc", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b4103cffefa72eb8428cb6b47d6627161e51c2739fc5e3b734584157bc642a" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libtest-mimic" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e6ba06f0ade6e504aff834d7c34298e5155c6baca353cc6a4aaff2f9fd7f33" +dependencies = [ + "anstream", + "anstyle", + "clap", + "escape8259", +] + +[[package]] +name = "lighthouse_version" +version = "8.1.2" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[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.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "logging" +version = "0.2.0" +dependencies = [ + "chrono", + "logroller", + "metrics 0.2.0", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-appender", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "workspace_members", +] + +[[package]] +name = "logroller" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83db12bbf439ebe64c0b0e4402f435b6f866db498fc1ae17e1b5d1a01625e2be" +dependencies = [ + "chrono", + "flate2", + "regex", + "thiserror 1.0.69", +] + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "lru" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "macro-string" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[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 = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "match-lookup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "mediatype" +version = "0.19.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33746aadcb41349ec291e7f2f0a3aa6834d1d7c58066fb4b01f68efc4c4b7631" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memoffset" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memuse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d97bbf43eb4f088f8ca469930cde17fa036207c9a5e02ccc5107c4e8b17c964" + +[[package]] +name = "merkle_proof" +version = "0.2.0" +dependencies = [ + "alloy-primitives", + "ethereum_hashing 0.8.0", + "fixed_bytes 0.1.0", + "safe_arith", +] + +[[package]] +name = "merkle_proof" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?branch=unstable#c7055b604f9958db410b2e42023763cb19dd7138" +dependencies = [ + "alloy-primitives", + "ethereum_hashing 0.8.0", + "fixed_bytes 0.1.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "safe_arith", +] + +[[package]] +name = "metastruct" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969a1be9bd80794bdf93b23ab552c2ec6f3e83b33164824553fd996cdad513b8" +dependencies = [ + "metastruct_macro", +] + +[[package]] +name = "metastruct_macro" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de9164f767d73a507c19205868c84da411dc7795f4bdabf497d3dd93cfef9930" +dependencies = [ + "darling 0.23.0", + "itertools 0.14.0", + "proc-macro2", + "quote", + "smallvec", + "syn 2.0.117", +] + +[[package]] +name = "metrics" +version = "0.2.0" +dependencies = [ + "prometheus 0.13.4", +] + +[[package]] +name = "metrics" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" +dependencies = [ + "ahash", + "portable-atomic", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034" +dependencies = [ + "base64 0.22.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls", + "hyper-util", + "indexmap 2.13.0", + "ipnet", + "metrics 0.24.3", + "metrics-util", + "quanta", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-util" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8496cc523d1f94c1385dd8f0f0c2c480b2b8aeccb5b7e4485ad6365523ae376" +dependencies = [ + "crossbeam-epoch 0.9.18", + "crossbeam-utils 0.8.21", + "hashbrown 0.15.5", + "metrics 0.24.3", + "quanta", + "rand 0.9.2", + "rand_xoshiro", + "sketches-ddsketch", +] + +[[package]] +name = "milhouse" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "259dd9da2ae5e0278b95da0b7ecef9c18c309d0a2d9e6db57ed33b9e8910c5e7" +dependencies = [ + "alloy-primitives", + "context_deserialize", + "educe", + "ethereum_hashing 0.8.0", + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "itertools 0.13.0", + "parking_lot", + "rayon", + "serde", + "smallvec", + "tree_hash 0.12.1", + "triomphe", + "typenum", + "vec_map", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "more-asserts" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e" + +[[package]] +name = "mpt" +version = "0.1.0" +source = "git+https://github.com/eth-act/zkvm-ethereum-mpt.git?rev=a1e44638c49c4e16751a0b915593fce98ab6bdef#a1e44638c49c4e16751a0b915593fce98ab6bdef" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "alloy-trie 0.8.1", + "arrayvec", +] + +[[package]] +name = "multiaddr" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6351f60b488e04c1d21bc69e56b89cb3f5e8f5d22557d6e8031bdfd79b6961" +dependencies = [ + "arrayref", + "byteorder", + "data-encoding", + "libp2p-identity", + "multibase", + "multihash", + "percent-encoding", + "serde", + "static_assertions", + "unsigned-varint", + "url", +] + +[[package]] +name = "multibase" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" +dependencies = [ + "base-x", + "base256emoji", + "data-encoding", + "data-encoding-macro", +] + +[[package]] +name = "multihash" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" +dependencies = [ + "core2", + "unsigned-varint", +] + +[[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.117", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint 0.4.6", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "serde", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[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-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint 0.4.6", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[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 = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "nybbles" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983bb634df7248924ee0c4c3a749609b5abcb082c28fffe3254b3eb3602b307" +dependencies = [ + "const-hex", + "smallvec", +] + +[[package]] +name = "nybbles" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d49ff0c0d00d4a502b39df9af3a525e1efeb14b9dabb5bb83335284c1309210" +dependencies = [ + "alloy-rlp", + "cfg-if 1.0.4", + "proptest", + "ruint", + "serde", + "smallvec", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "op-alloy-consensus" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736381a95471d23e267263cfcee9e1d96d30b9754a94a2819148f83379de8a86" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "derive_more 2.1.1", + "serde", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +dependencies = [ + "bitflags", + "cfg-if 1.0.4", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[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 = "p3-bn254-fr" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9abf208fbfe540d6e2a6caaa2a9a345b1c8cb23ffdcdfcc6987244525d4fc821" +dependencies = [ + "ff 0.13.1", + "num-bigint 0.4.6", + "p3-field", + "p3-poseidon2", + "p3-symmetric", + "rand 0.8.5", + "serde", +] + +[[package]] +name = "p3-challenger" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b725b453bbb35117a1abf0ddfd900b0676063d6e4231e0fa6bb0d76018d8ad" +dependencies = [ + "p3-field", + "p3-maybe-rayon", + "p3-symmetric", + "p3-util", + "serde", + "tracing", +] + +[[package]] +name = "p3-dft" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56a1f81101bff744b7ebba7f4497e917a2c6716d6e62736e4a56e555a2d98cb7" +dependencies = [ + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", + "tracing", +] + +[[package]] +name = "p3-field" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36459d4acb03d08097d713f336c7393990bb489ab19920d4f68658c7a5c10968" +dependencies = [ + "itertools 0.12.1", + "num-bigint 0.4.6", + "num-traits", + "p3-util", + "rand 0.8.5", + "serde", +] + +[[package]] +name = "p3-koala-bear" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1f52bcb6be38bdc8fa6b38b3434d4eedd511f361d4249fd798c6a5ef817b40" +dependencies = [ + "num-bigint 0.4.6", + "p3-field", + "p3-mds", + "p3-poseidon2", + "p3-symmetric", + "rand 0.8.5", + "serde", +] + +[[package]] +name = "p3-matrix" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5583e9cd136a4095a25c41a9edfdcce2dfae58ef01639317813bdbbd5b55c583" +dependencies = [ + "itertools 0.12.1", + "p3-field", + "p3-maybe-rayon", + "p3-util", + "rand 0.8.5", + "serde", + "tracing", +] + +[[package]] +name = "p3-maybe-rayon" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e524d47a49fb4265611303339c4ef970d892817b006cc330dad18afb91e411b1" + +[[package]] +name = "p3-mds" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f6cb8edcb276033d43769a3725570c340d2ed6f35c3cca4cddeee07718fa376" +dependencies = [ + "itertools 0.12.1", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-symmetric", + "p3-util", + "rand 0.8.5", +] + +[[package]] +name = "p3-poseidon2" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a26197df2097b98ab7038d59a01e1fe1a0f545e7e04aa9436b2454b1836654f" +dependencies = [ + "gcd", + "p3-field", + "p3-mds", + "p3-symmetric", + "rand 0.8.5", + "serde", +] + +[[package]] +name = "p3-symmetric" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a1d3b5202096bca57cde912fbbb9cbaedaf5ac7c42a924c7166b98709d64d21" +dependencies = [ + "itertools 0.12.1", + "p3-field", + "serde", +] + +[[package]] +name = "p3-util" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec5f0388aa6d935ca3a17444086120f393f0b2f0816010b5ff95998c1c4095e3" +dependencies = [ + "serde", +] + +[[package]] +name = "pairing" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135590d8bdba2b31346f9cd1fb2a912329f5135e832a4f422942eb6ead8b6b3b" +dependencies = [ + "group 0.12.1", +] + +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group 0.13.0", +] + +[[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.117", +] + +[[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 1.0.4", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pasta_curves" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc65faf8e7313b4b1fbaa9f7ca917a0eed499a9663be71477f87993604341d8" +dependencies = [ + "blake2b_simd", + "ff 0.12.1", + "group 0.12.1", + "lazy_static", + "rand 0.8.5", + "static_assertions", + "subtle", +] + +[[package]] +name = "pasta_curves" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" +dependencies = [ + "blake2b_simd", + "ff 0.13.1", + "group 0.13.0", + "lazy_static", + "rand 0.8.5", + "static_assertions", + "subtle", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if 1.0.4", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[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 = "pretty_reqwest_error" +version = "0.1.0" +dependencies = [ + "reqwest", + "sensitive_url", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[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.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec 0.6.0", + "uint 0.9.5", +] + +[[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 0.7.1", + "impl-rlp", + "impl-serde", + "uint 0.10.0", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.8+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "procfs" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" +dependencies = [ + "bitflags", + "hex", + "lazy_static", + "procfs-core", + "rustix 0.38.44", +] + +[[package]] +name = "procfs-core" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" +dependencies = [ + "bitflags", + "hex", +] + +[[package]] +name = "prometheus" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" +dependencies = [ + "cfg-if 1.0.4", + "fnv", + "lazy_static", + "libc", + "memchr", + "parking_lot", + "procfs", + "protobuf 2.28.0", + "thiserror 1.0.69", +] + +[[package]] +name = "prometheus" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ca5326d8d0b950a9acd87e6a3f94745394f62e4dae1b1ee22b2bc0c394af43a" +dependencies = [ + "cfg-if 1.0.4", + "fnv", + "lazy_static", + "memchr", + "parking_lot", + "protobuf 3.7.2", + "thiserror 2.0.18", +] + +[[package]] +name = "proof_engine_zkboost_test" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum 0.7.9", + "bytes", + "ethereum_ssz 0.10.1", + "execution_layer", + "futures", + "metrics-exporter-prometheus", + "reqwest", + "sensitive_url", + "serde", + "serde_json", + "strum", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "tree_hash 0.12.1", + "types 0.2.1", + "url", + "zkboost-server", + "zkboost-types", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "num-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_xorshift 0.4.0", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "proto_array" +version = "0.2.0" +dependencies = [ + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "fixed_bytes 0.1.0", + "safe_arith", + "serde", + "serde_yaml", + "superstruct", + "types 0.2.1", +] + +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" + +[[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.117", +] + +[[package]] +name = "qfilter" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "746341cd2357c9a4df2d951522b4a8dd1ef553e543119899ad7bf87e938c8fbe" +dependencies = [ + "xxhash-rust", +] + +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils 0.8.21", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-protobuf" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" +dependencies = [ + "byteorder", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.1", + "rustls 0.23.37", + "socket2 0.6.3", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash 2.1.1", + "rustls 0.23.37", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.3", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +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 = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a063ea72381527c2a0561da9c80000ef822bdd7c3241b1cc1b12100e3df081ee" +dependencies = [ + "ptr_meta", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", + "serde", +] + +[[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", + "serde", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rand_xoshiro" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rapidhash" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" +dependencies = [ + "rustversion", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +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 0.8.6", + "crossbeam-utils 0.8.21", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rend" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadadef317c2f20755a64d7fdc48f9e7178ee6b0e1f7fce33fa60f1d68a276e6" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.37", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.26.4", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "reqwest-eventsource" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632c55746dbb44275691640e7b40c907c16a2dc1a5842aa98aaec90da6ec6bde" +dependencies = [ + "eventsource-stream", + "futures-core", + "futures-timer", + "mime", + "nom", + "pin-project-lite", + "reqwest", + "thiserror 1.0.69", +] + +[[package]] +name = "reth-chainspec" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-genesis", + "alloy-primitives", + "alloy-trie 0.9.5", + "auto_impl", + "derive_more 2.1.1", + "reth-ethereum-forks", + "reth-network-peers", + "reth-primitives-traits", + "serde_json", +] + +[[package]] +name = "reth-codecs" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-trie 0.9.5", + "bytes", + "modular-bitfield", + "op-alloy-consensus", + "reth-codecs-derive", + "reth-zstd-compressors", + "serde", +] + +[[package]] +name = "reth-codecs-derive" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "reth-consensus" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "auto_impl", + "reth-execution-types", + "reth-primitives-traits", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-consensus-common" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "reth-chainspec", + "reth-consensus", + "reth-primitives-traits", +] + +[[package]] +name = "reth-db-models" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "reth-primitives-traits", +] + +[[package]] +name = "reth-errors" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "reth-consensus", + "reth-execution-errors", + "reth-storage-errors", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-ethereum-consensus" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "reth-chainspec", + "reth-consensus", + "reth-consensus-common", + "reth-execution-types", + "reth-primitives-traits", + "tracing", +] + +[[package]] +name = "reth-ethereum-forks" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-eip2124", + "alloy-hardforks", + "alloy-primitives", + "auto_impl", + "once_cell", +] + +[[package]] +name = "reth-ethereum-primitives" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-serde", + "reth-codecs", + "reth-primitives-traits", + "serde", + "serde_with", +] + +[[package]] +name = "reth-evm" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-primitives", + "auto_impl", + "derive_more 2.1.1", + "futures-util", + "reth-execution-errors", + "reth-execution-types", + "reth-primitives-traits", + "reth-storage-api", + "reth-storage-errors", + "reth-trie-common", + "revm", +] + +[[package]] +name = "reth-evm-ethereum" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-primitives", + "alloy-rpc-types-engine", + "reth-chainspec", + "reth-ethereum-forks", + "reth-ethereum-primitives", + "reth-evm", + "reth-execution-types", + "reth-primitives-traits", + "reth-storage-errors", + "revm", +] + +[[package]] +name = "reth-execution-errors" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-evm", + "alloy-primitives", + "alloy-rlp", + "nybbles 0.4.8", + "reth-storage-errors", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-execution-types" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-primitives", + "derive_more 2.1.1", + "reth-ethereum-primitives", + "reth-primitives-traits", + "reth-trie-common", + "revm", +] + +[[package]] +name = "reth-network-peers" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "serde_with", + "thiserror 2.0.18", + "url", +] + +[[package]] +name = "reth-payload-validator" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-rpc-types-engine", + "reth-primitives-traits", +] + +[[package]] +name = "reth-primitives-traits" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-trie 0.9.5", + "auto_impl", + "bytes", + "derive_more 2.1.1", + "once_cell", + "op-alloy-consensus", + "reth-codecs", + "revm-bytecode", + "revm-primitives", + "revm-state", + "secp256k1", + "serde", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-prune-types" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-primitives", + "derive_more 2.1.1", + "strum", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-revm" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-primitives", + "reth-primitives-traits", + "reth-storage-api", + "reth-storage-errors", + "revm", +] + +[[package]] +name = "reth-stages-types" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-primitives", + "reth-trie-common", +] + +[[package]] +name = "reth-stateless" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-genesis", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-debug", + "alloy-trie 0.9.5", + "itertools 0.14.0", + "k256", + "reth-chainspec", + "reth-consensus", + "reth-errors", + "reth-ethereum-consensus", + "reth-ethereum-primitives", + "reth-evm", + "reth-primitives-traits", + "reth-revm", + "reth-trie-common", + "reth-trie-sparse", + "serde", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-static-file-types" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-primitives", + "derive_more 2.1.1", + "fixed-map", + "serde", + "strum", +] + +[[package]] +name = "reth-storage-api" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rpc-types-engine", + "auto_impl", + "reth-chainspec", + "reth-db-models", + "reth-ethereum-primitives", + "reth-execution-types", + "reth-primitives-traits", + "reth-prune-types", + "reth-stages-types", + "reth-storage-errors", + "reth-trie-common", + "revm-database", +] + +[[package]] +name = "reth-storage-errors" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "derive_more 2.1.1", + "reth-primitives-traits", + "reth-prune-types", + "reth-static-file-types", + "revm-database-interface", + "revm-state", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-trie-common" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "alloy-rlp", + "alloy-trie 0.9.5", + "derive_more 2.1.1", + "itertools 0.14.0", + "nybbles 0.4.8", + "reth-primitives-traits", + "revm-database", +] + +[[package]] +name = "reth-trie-sparse" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "alloy-trie 0.9.5", + "auto_impl", + "reth-execution-errors", + "reth-primitives-traits", + "reth-trie-common", + "smallvec", + "tracing", +] + +[[package]] +name = "reth-zstd-compressors" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "zstd", +] + +[[package]] +name = "revm" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2aabdebaa535b3575231a88d72b642897ae8106cf6b0d12eafc6bfdf50abfc7" +dependencies = [ + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database", + "revm-database-interface", + "revm-handler", + "revm-inspector", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-bytecode" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d1e5c1eaa44d39d537f668bc5c3409dc01e5c8be954da6c83370bbdf006457" +dependencies = [ + "bitvec", + "phf", + "revm-primitives", + "serde", +] + +[[package]] +name = "revm-context" +version = "13.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "892ff3e6a566cf8d72ffb627fdced3becebbd9ba64089c25975b9b028af326a5" +dependencies = [ + "bitvec", + "cfg-if 1.0.4", + "derive-where", + "revm-bytecode", + "revm-context-interface", + "revm-database-interface", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-context-interface" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57f61cc6d23678c4840af895b19f8acfbbd546142ec8028b6526c53cc1c16c98" +dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "auto_impl", + "either", + "revm-database-interface", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-database" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "529528d0b05fe646be86223032c3e77aa8b05caa2a35447d538c55965956a511" +dependencies = [ + "revm-bytecode", + "revm-database-interface", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-database-interface" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7bf93ac5b91347c057610c0d96e923db8c62807e03f036762d03e981feddc1d" +dependencies = [ + "auto_impl", + "either", + "revm-primitives", + "revm-state", + "thiserror 2.0.18", +] + +[[package]] +name = "revm-handler" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cd0e43e815a85eded249df886c4badec869195e70cdd808a13cfca2794622d2" +dependencies = [ + "auto_impl", + "derive-where", + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database-interface", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-inspector" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3ccad59db91ef93696536a0dbaf2f6f17cfe20d4d8843ae118edb7e97947ef" +dependencies = [ + "auto_impl", + "either", + "revm-context", + "revm-database-interface", + "revm-handler", + "revm-interpreter", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-interpreter" +version = "32.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11406408597bc249392d39295831c4b641b3a6f5c471a7c41104a7a1e3564c07" +dependencies = [ + "revm-bytecode", + "revm-context-interface", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-precompile" +version = "32.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2ec11f45deec71e4945e1809736bb20d454285f9167ab53c5159dae1deb603f" +dependencies = [ + "ark-bls12-381", + "ark-bn254", + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "arrayref", + "aurora-engine-modexp", + "cfg-if 1.0.4", + "k256", + "p256", + "revm-primitives", + "ripemd", + "sha2", +] + +[[package]] +name = "revm-primitives" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfb5ce6cf18b118932bcdb7da05cd9c250f2cb9f64131396b55f3fe3537c35" +dependencies = [ + "alloy-primitives", + "num_enum", + "once_cell", + "serde", +] + +[[package]] +name = "revm-state" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "311720d4f0f239b041375e7ddafdbd20032a33b7bae718562ea188e188ed9fd3" +dependencies = [ + "alloy-eip7928", + "bitflags", + "revm-bytecode", + "revm-primitives", + "serde", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if 1.0.4", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "rkyv" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a30e631b7f4a03dee9056b8ef6982e8ba371dd5bedb74d3ec86df4499132c70" +dependencies = [ + "bytecheck", + "bytes", + "hashbrown 0.16.1", + "indexmap 2.13.0", + "munge", + "ptr_meta", + "rancor", + "rend", + "rkyv_derive", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8100bb34c0a1d0f907143db3149e6b4eea3c33b9ee8b189720168e818303986f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[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 = "rpds" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ef5140bcb576bfd6d56cd2de709a7d17851ac1f3805e67fe9d99e42a11821f" +dependencies = [ + "archery", +] + +[[package]] +name = "rsqlite-vfs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d" +dependencies = [ + "hashbrown 0.16.1", + "thiserror 2.0.18", +] + +[[package]] +name = "ruint" +version = "1.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "ark-ff 0.5.0", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "parity-scale-codec", + "primitive-types 0.12.2", + "proptest", + "rand 0.8.5", + "rand 0.9.2", + "rlp 0.5.2", + "ruint-macro", + "serde_core", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + +[[package]] +name = "rusqlite" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c93dd1c9683b438c392c492109cb702b8090b2bfc8fed6f6e4eb4523f17af3" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", + "sqlite-wasm-rs", +] + +[[package]] +name = "rust_eth_kzg" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1522b7a740cd7f5bc52ea49863618511c8de138dcdf3f8a80b15b3f764942a5b" +dependencies = [ + "eip4844", + "ekzg-bls12-381", + "ekzg-erasure-codes", + "ekzg-multi-open", + "ekzg-serialization", + "ekzg-trusted-setup", + "hex", + "serde", + "serde_json", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.27", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "aws-lc-rs", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.103.10", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[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 = "safe_arith" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b147bb6111014916d3ef9d4c85173124a8e12193a67f6176d67244afd558d6c1" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2", + "salsa20", + "sha2", +] + +[[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", + "serdect", + "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.5", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + +[[package]] +name = "sensitive_url" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7b0221fa9905eec4163dbf7660b1876cc95663af1deddc3e19ebe49167c58c" +dependencies = [ + "serde", + "url", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_arrays" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a16b99c5ea4fe3daccd14853ad260ec00ea043b2708d1fd1da3106dcd8d9df" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_spanned" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.13.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if 1.0.4", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if 1.0.4", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sha3-asm" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b31139435f327c93c6038ed350ae4588e2c70a13d50599509fee6349967ba35a" +dependencies = [ + "cc", + "cfg-if 1.0.4", +] + +[[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 = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[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 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "sketches-ddsketch" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slop-algebra" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "691beea96fd18d4881f9ca1cb4e58194dac6366f24956a2fdae00c8ee382a0c9" +dependencies = [ + "itertools 0.14.0", + "p3-field", + "serde", +] + +[[package]] +name = "slop-bn254" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1852499c245f7f3dec23408b4930b3ea7570ae914b9c31f12950ac539d85ee" +dependencies = [ + "ff 0.13.1", + "p3-bn254-fr", + "serde", + "slop-algebra", + "slop-challenger", + "slop-poseidon2", + "slop-symmetric", + "zkhash", +] + +[[package]] +name = "slop-challenger" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4349af93602f3876a3eda948a74d9d16d774c401dfe25f41a45ffd84f230bc1" +dependencies = [ + "futures", + "p3-challenger", + "serde", + "slop-algebra", + "slop-symmetric", +] + +[[package]] +name = "slop-koala-bear" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "574784c044d11cf9d8238dc18bce9b897bc34d0fb1daaceafd75ebb400084016" +dependencies = [ + "lazy_static", + "p3-koala-bear", + "serde", + "slop-algebra", + "slop-challenger", + "slop-poseidon2", + "slop-symmetric", +] + +[[package]] +name = "slop-poseidon2" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5af617970b63e8d7199204bc02996745b6c35c39f2b513a118c62c7b1a0b2f1b" +dependencies = [ + "p3-poseidon2", +] + +[[package]] +name = "slop-primitives" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58d82c53508f3ebff8acdabb5db2584f37686257a2549a17c977cf30cd9e24e6" +dependencies = [ + "slop-algebra", +] + +[[package]] +name = "slop-symmetric" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15acfa7f567ffa4f36de134492632a397c33fa6af2e48894e50978b52eeeb871" +dependencies = [ + "p3-symmetric", +] + +[[package]] +name = "slot_clock" +version = "0.2.0" +dependencies = [ + "metrics 0.2.0", + "parking_lot", + "types 0.2.1", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "arbitrary", + "serde", +] + +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "sp1-lib" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "517e820776910468611149dda66791bdb700c1b7d68b96f0ea2e604f00ad8771" +dependencies = [ + "bincode 1.3.3", + "serde", + "sp1-primitives", +] + +[[package]] +name = "sp1-primitives" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f395525b4fc46d37136f45be264c81718a67f4409c14c547ff491a263e019e7" +dependencies = [ + "bincode 1.3.3", + "blake3", + "elf", + "hex", + "itertools 0.14.0", + "lazy_static", + "num-bigint 0.4.6", + "serde", + "sha2", + "slop-algebra", + "slop-bn254", + "slop-challenger", + "slop-koala-bear", + "slop-poseidon2", + "slop-primitives", + "slop-symmetric", +] + +[[package]] +name = "sp1_bls12_381" +version = "0.8.0-sp1-6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23e41cd36168cc2e51e5d3e35ff0c34b204d945769a65591a76286d04b51e43" +dependencies = [ + "cfg-if 1.0.4", + "ff 0.13.1", + "group 0.13.0", + "pairing 0.23.0", + "rand_core 0.6.4", + "sp1-lib", + "subtle", +] + +[[package]] +name = "sparsestate" +version = "0.1.0" +source = "git+https://github.com/eth-act/zkvm-ethereum-mpt.git?rev=a1e44638c49c4e16751a0b915593fce98ab6bdef#a1e44638c49c4e16751a0b915593fce98ab6bdef" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "alloy-trie 0.9.5", + "mpt", + "reth-errors", + "reth-revm", + "reth-stateless", + "reth-trie-common", +] + +[[package]] +name = "spawned-concurrency" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3ec6b3c003075f7d1c4c6475308243e853c9a78149b84b1f8b64d5bed49d49" +dependencies = [ + "futures", + "pin-project-lite", + "spawned-rt", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "spawned-rt" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cca60c56b1c60b94dd314edce5ea1a98b6037cca3b44d73828e647bad4dae46c" +dependencies = [ + "crossbeam 0.7.3", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlite-wasm-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4206ed3a67690b9c29b77d728f6acc3ce78f16bf846d83c94f76400320181b" +dependencies = [ + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", +] + +[[package]] +name = "ssz_types" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b55bedc9a18ed2860a46d6beb4f4082416ee1d60be0cc364cebdcdddc7afd4" +dependencies = [ + "ethereum_serde_utils", + "ethereum_ssz 0.9.1", + "itertools 0.13.0", + "serde", + "serde_derive", + "smallvec", + "tree_hash 0.10.0", + "typenum", +] + +[[package]] +name = "ssz_types" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc20a89bab2dabeee65e9c9eb96892dc222c23254b401e1319b85efd852fa31" +dependencies = [ + "context_deserialize", + "educe", + "ethereum_serde_utils", + "ethereum_ssz 0.10.1", + "itertools 0.14.0", + "serde", + "serde_derive", + "smallvec", + "tree_hash 0.12.1", + "typenum", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "state_processing" +version = "0.2.0" +dependencies = [ + "arbitrary", + "bls 0.2.0", + "educe", + "ethereum_hashing 0.8.0", + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "fixed_bytes 0.1.0", + "int_to_bytes 0.2.0", + "integer-sqrt", + "itertools 0.14.0", + "merkle_proof 0.2.0", + "metrics 0.2.0", + "milhouse", + "rand 0.9.2", + "rayon", + "safe_arith", + "smallvec", + "ssz_types 0.14.0", + "test_random_derive 0.2.0", + "tracing", + "tree_hash 0.12.1", + "typenum", + "types 0.2.1", +] + +[[package]] +name = "stateless-validator-common" +version = "0.5.0" +source = "git+https://github.com/eth-act/ere-guests?tag=v0.6.0#64c94bb3da631101a6cb2f276c89392cb7c3426f" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "anyhow", + "ethereum_ssz 0.9.1", + "ethereum_ssz_derive 0.9.1", + "rkyv", + "serde", + "serde_with", + "sha2", + "ssz_types 0.11.0", + "tree_hash 0.10.0", + "tree_hash_derive 0.10.0", + "typenum", +] + +[[package]] +name = "stateless-validator-ethrex" +version = "0.5.0" +source = "git+https://github.com/eth-act/ere-guests?tag=v0.6.0#64c94bb3da631101a6cb2f276c89392cb7c3426f" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-rlp", + "anyhow", + "bytes", + "ere-io", + "ere-zkvm-interface", + "ethrex-common", + "ethrex-rlp", + "ethrex-rpc", + "ethrex-vm", + "guest", + "guest_program", + "reth-stateless", + "rkyv", + "stateless-validator-common", + "stateless-validator-reth", +] + +[[package]] +name = "stateless-validator-reth" +version = "0.5.0" +source = "git+https://github.com/eth-act/ere-guests?tag=v0.6.0#64c94bb3da631101a6cb2f276c89392cb7c3426f" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-engine", + "anyhow", + "ere-io", + "ere-zkvm-interface", + "ethereum_ssz 0.9.1", + "guest", + "once_cell", + "reth-chainspec", + "reth-ethereum-primitives", + "reth-evm-ethereum", + "reth-payload-validator", + "reth-primitives-traits", + "reth-stateless", + "serde", + "serde_with", + "sha2", + "sparsestate", + "ssz_types 0.11.0", + "stateless-validator-common", + "tree_hash 0.10.0", + "tree_hash_derive 0.10.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "store" +version = "0.2.0" +dependencies = [ + "bls 0.2.0", + "db-key", + "directory", + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "fixed_bytes 0.1.0", + "itertools 0.14.0", + "leveldb", + "logging", + "lru 0.12.5", + "metrics 0.2.0", + "milhouse", + "parking_lot", + "safe_arith", + "serde", + "smallvec", + "ssz_types 0.14.0", + "state_processing", + "strum", + "superstruct", + "tracing", + "tracing-subscriber", + "typenum", + "types 0.2.1", + "xdelta3", + "zstd", +] + +[[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.117", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "superstruct" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae4a9ccd7882533c1f210e400763ec6ee64c390fc12248c238276281863719e" +dependencies = [ + "darling 0.23.0", + "itertools 0.14.0", + "proc-macro2", + "quote", + "smallvec", + "syn 2.0.117", +] + +[[package]] +name = "swap_or_not_shuffle" +version = "0.2.0" +dependencies = [ + "alloy-primitives", + "ethereum_hashing 0.8.0", + "fixed_bytes 0.1.0", +] + +[[package]] +name = "swap_or_not_shuffle" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?branch=unstable#c7055b604f9958db410b2e42023763cb19dd7138" +dependencies = [ + "alloy-primitives", + "ethereum_hashing 0.8.0", + "fixed_bytes 0.1.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53f425ae0b12e2f5ae65542e00898d500d4d318b4baf09f40fd0d410454e9947" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "task_executor" +version = "0.1.0" +dependencies = [ + "async-channel", + "futures", + "metrics 0.2.0", + "num_cpus", + "rayon", + "tokio", + "tracing", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "terminal_size" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +dependencies = [ + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "test_random_derive" +version = "0.2.0" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "test_random_derive" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?branch=unstable#c7055b604f9958db410b2e42023763cb19dd7138" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[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.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[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.117", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if 1.0.4", +] + +[[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.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +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 = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +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.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.3", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls 0.23.37", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "futures-util", + "pin-project-lite", + "slab", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.24.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01f2eadbbc6b377a847be05f60791ef1058d9f696ecb51d2c07fe911d8569d8e" +dependencies = [ + "indexmap 2.13.0", + "serde_core", + "serde_spanned", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.8+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" +dependencies = [ + "indexmap 2.13.0", + "toml_datetime 1.1.0+spec-1.1.0", + "toml_parser", + "winnow 1.0.0", +] + +[[package]] +name = "toml_parser" +version = "1.1.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +dependencies = [ + "winnow 1.0.0", +] + +[[package]] +name = "toml_writer" +version = "1.1.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[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-appender" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +dependencies = [ + "crossbeam-channel 0.5.15", + "thiserror 2.0.18", + "time", + "tracing-subscriber", +] + +[[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.117", +] + +[[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-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "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", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "tree_hash" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee44f4cef85f88b4dea21c0b1f58320bdf35715cf56d840969487cff00613321" +dependencies = [ + "alloy-primitives", + "ethereum_hashing 0.7.0", + "ethereum_ssz 0.9.1", + "smallvec", + "typenum", +] + +[[package]] +name = "tree_hash" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fd51aa83d2eb83b04570808430808b5d24fdbf479a4d5ac5dee4a2e2dd2be4" +dependencies = [ + "alloy-primitives", + "ethereum_hashing 0.8.0", + "ethereum_ssz 0.10.1", + "smallvec", + "typenum", +] + +[[package]] +name = "tree_hash_derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bee2ea1551f90040ab0e34b6fb7f2fa3bad8acc925837ac654f2c78a13e3089" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tree_hash_derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8840ad4d852e325d3afa7fde8a50b2412f89dce47d7eb291c0cc7f87cd040f38" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "triehash" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1631b201eb031b563d2e85ca18ec8092508e262a3196ce9bd10a67ec87b9f5c" +dependencies = [ + "hash-db", + "rlp 0.5.2", +] + +[[package]] +name = "triomphe" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" +dependencies = [ + "serde", + "stable_deref_trait", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http 1.4.0", + "httparse", + "log", + "rand 0.9.2", + "sha1", + "thiserror 2.0.18", + "utf-8", +] + +[[package]] +name = "twirp" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c52cc4e4423b6b3e2e2659523c8c9e19af514a06422fe77a95d86f6bf3478a" +dependencies = [ + "anyhow", + "async-trait", + "axum 0.8.8", + "futures", + "http 1.4.0", + "http-body-util", + "hyper 1.8.1", + "prost", + "reqwest", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tower", + "url", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "types" +version = "0.2.1" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "bls 0.2.0", + "compare_fields", + "context_deserialize", + "educe", + "eth2_interop_keypairs 0.2.0", + "ethereum_hashing 0.8.0", + "ethereum_serde_utils", + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "fixed_bytes 0.1.0", + "hex", + "int_to_bytes 0.2.0", + "itertools 0.14.0", + "kzg 0.1.0", + "maplit", + "merkle_proof 0.2.0", + "metastruct", + "milhouse", + "parking_lot", + "rand 0.9.2", + "rand_xorshift 0.4.0", + "rayon", + "regex", + "rpds", + "rusqlite", + "safe_arith", + "serde", + "serde_json", + "serde_yaml", + "smallvec", + "ssz_types 0.14.0", + "superstruct", + "swap_or_not_shuffle 0.2.0", + "tempfile", + "test_random_derive 0.2.0", + "tracing", + "tree_hash 0.12.1", + "tree_hash_derive 0.12.1", + "typenum", +] + +[[package]] +name = "types" +version = "0.2.1" +source = "git+https://github.com/sigp/lighthouse?branch=unstable#c7055b604f9958db410b2e42023763cb19dd7138" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "bls 0.2.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "compare_fields", + "context_deserialize", + "educe", + "eth2_interop_keypairs 0.2.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "ethereum_hashing 0.8.0", + "ethereum_serde_utils", + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "fixed_bytes 0.1.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "hex", + "int_to_bytes 0.2.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "itertools 0.14.0", + "kzg 0.1.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "maplit", + "merkle_proof 0.2.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "metastruct", + "milhouse", + "parking_lot", + "rand 0.9.2", + "rand_xorshift 0.4.0", + "rayon", + "regex", + "rpds", + "safe_arith", + "serde", + "serde_json", + "serde_yaml", + "smallvec", + "ssz_types 0.14.0", + "superstruct", + "swap_or_not_shuffle 0.2.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "tempfile", + "test_random_derive 0.2.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "tracing", + "tree_hash 0.12.1", + "tree_hash_derive 0.12.1", + "typenum", +] + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[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 = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da36089a805484bcccfffe0739803392c8298778a2d2f09febf76fac5ad9025b" + +[[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 = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "unsigned-varint" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "warp" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers 0.3.9", + "http 0.2.12", + "hyper 0.14.32", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project", + "rustls-pemfile", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls 0.25.0", + "tokio-util", + "tower-service", + "tracing", +] + +[[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.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +dependencies = [ + "cfg-if 1.0.4", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +dependencies = [ + "cfg-if 1.0.4", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "semver 1.0.27", +] + +[[package]] +name = "web-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + +[[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 = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[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.117", +] + +[[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.117", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[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.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[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 = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.13.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap 2.13.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver 1.0.27", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "workspace_members" +version = "0.1.0" +dependencies = [ + "cargo_metadata", + "quote", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xdelta3" +version = "0.1.5" +source = "git+https://github.com/sigp/xdelta3-rs?rev=4db64086bb02e9febb584ba93b9d16bb2ae3825a#4db64086bb02e9febb584ba93b9d16bb2ae3825a" +dependencies = [ + "bindgen", + "cc", + "futures-io", + "futures-util", + "libc", + "log", + "rand 0.8.5", +] + +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "serde", + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zip" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" +dependencies = [ + "arbitrary", + "crc32fast", + "flate2", + "indexmap 2.13.0", + "memchr", + "zopfli", +] + +[[package]] +name = "zkboost-server" +version = "0.1.0" +source = "git+https://github.com/eth-act/zkboost?branch=master#cbeae7023bf32b4441751c76fc5d2f400524153a" +dependencies = [ + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "anyhow", + "axum 0.8.8", + "bincode 1.3.3", + "bytes", + "clap", + "ere-server", + "ere-zkvm-interface", + "lru 0.12.5", + "metrics 0.24.3", + "metrics-exporter-prometheus", + "rand 0.9.2", + "reqwest", + "reth-ethereum-primitives", + "reth-stateless", + "serde", + "serde_json", + "sha2", + "stateless-validator-ethrex", + "stateless-validator-reth", + "strum", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "toml_edit 0.24.1+spec-1.1.0", + "tower-http", + "tracing", + "tracing-subscriber", + "url", + "zkboost-types", +] + +[[package]] +name = "zkboost-types" +version = "0.1.0" +source = "git+https://github.com/eth-act/zkboost?branch=master#cbeae7023bf32b4441751c76fc5d2f400524153a" +dependencies = [ + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "serde", + "serde_json", + "ssz_types 0.14.0", + "strum", + "superstruct", + "tree_hash 0.12.1", + "tree_hash_derive 0.12.1", + "types 0.2.1 (git+https://github.com/sigp/lighthouse?branch=unstable)", +] + +[[package]] +name = "zkhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4352d1081da6922701401cdd4cbf29a2723feb4cfabb5771f6fee8e9276da1c7" +dependencies = [ + "ark-ff 0.4.2", + "ark-std 0.4.0", + "bitvec", + "blake2", + "bls12_381 0.7.1", + "byteorder", + "cfg-if 1.0.4", + "group 0.12.1", + "group 0.13.0", + "halo2", + "hex", + "jubjub", + "lazy_static", + "pasta_curves 0.5.1", + "rand 0.8.5", + "serde", + "sha2", + "sha3", + "subtle", +] + +[[package]] +name = "zlib-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/testing/proof_engine_zkboost/Cargo.toml b/testing/proof_engine_zkboost/Cargo.toml new file mode 100644 index 00000000000..ac7f950f53e --- /dev/null +++ b/testing/proof_engine_zkboost/Cargo.toml @@ -0,0 +1,38 @@ +[workspace] +members = ["."] +resolver = "2" + +[workspace.dependencies] +zkboost-server = { git = "https://github.com/eth-act/zkboost", branch = "master" } +zkboost-types = { git = "https://github.com/eth-act/zkboost", branch = "master" } + +[package] +name = "proof_engine_zkboost_test" +version = "0.1.0" +edition = "2024" + +[features] +portable = ["types/portable"] + +[dependencies] +anyhow = "1" +axum = "0.7" +bytes = "1" +ethereum_ssz = { version = "0.10.0", features = ["context_deserialize"] } +execution_layer = { path = "../../beacon_node/execution_layer" } +futures = "0.3" +metrics-exporter-prometheus = "0.16" +reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "stream", "rustls-tls"] } +sensitive_url = { version = "0.1", features = ["serde"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +strum = { version = "0.27", features = ["derive"] } +tokio = { version = "1", features = ["rt-multi-thread", "sync", "signal", "macros"] } +tokio-stream = { version = "0.1", features = ["sync"] } +tokio-util = { version = "0.7", features = ["codec", "compat", "time"] } +tracing = "0.1" +tree_hash = "0.12.0" +types = { path = "../../consensus/types" } +url = "2" +zkboost-server = { workspace = true } +zkboost-types = { workspace = true } diff --git a/testing/proof_engine_zkboost/src/lib.rs b/testing/proof_engine_zkboost/src/lib.rs new file mode 100644 index 00000000000..2ba7f4e985d --- /dev/null +++ b/testing/proof_engine_zkboost/src/lib.rs @@ -0,0 +1,306 @@ +//! Integration tests verifying wire-level compatibility between Lighthouse's +//! [`HttpProofNodeClient`] and the **real** zkBoost server. +//! +//! ## Architecture +//! +//! This test crate starts the real `zkBoostServer` (from the `zkboost-server` crate) +//! with mock zkVM backends, and validates that Lighthouse's `HttpProofNodeClient` +//! speaks the correct wire protocol against it. +//! +//! A lightweight mock Execution Layer serves fixture data (chain config + +//! execution witness) so the server can generate witnesses without a real node. +//! +//! ## What is validated +//! +//! - Lighthouse sends zkBoost string proof types in query params, URL paths, SSE +//! - The real server accepts Lighthouse's requests and returns valid responses +//! - SSE events with string `proof_type` values are correctly deserialized to u8 +//! - Full lifecycle: request → SSE event → proof download → verification + +pub mod zkboost_harness; + +#[cfg(test)] +mod tests { + use crate::zkboost_harness::{FIXTURE_NEW_PAYLOAD_REQUEST, ZkboostTestHarness}; + use execution_layer::eip8025::{HttpProofNodeClient, ProofNodeClient, ProofType}; + use execution_layer::test_utils::OwnedNewPayloadRequest; + use futures::StreamExt; + use sensitive_url::SensitiveUrl; + use ssz::Decode; + use std::time::Duration; + use tokio::time::timeout; + use tree_hash::TreeHash; + use types::MainnetEthSpec; + use types::execution::eip8025::ProofAttributes; + use zkboost_types::ProofType as ZkBoostProofType; + + /// Helper: create an `HttpProofNodeClient` pointing at the test server. + fn client_for(url: &str) -> HttpProofNodeClient { + let sensitive_url = SensitiveUrl::parse(url).expect("server URL should be valid"); + HttpProofNodeClient::new(sensitive_url, None) + } + + /// The u8 value for `EthrexZisk` (our default test proof type). + fn ethrex_zisk_u8() -> u8 { + ProofType::EthrexZisk.to_u8() + } + + // ─── Test 1: request_proofs succeeds against real server ───────────────── + + /// Verifies that `HttpProofNodeClient::request_proofs` sends the correct + /// wire format (string proof types in query param, SSZ body) and the real + /// zkBoost server accepts it and returns a root. + #[tokio::test] + async fn test_request_proofs_accepted_by_real_server() { + let harness = ZkboostTestHarness::start(3000).await; + let client = client_for(&harness.url()); + + let attrs = ProofAttributes { + proof_types: vec![ethrex_zisk_u8()], + }; + + let root = client + .request_proofs(FIXTURE_NEW_PAYLOAD_REQUEST.to_vec(), attrs) + .await + .expect("request_proofs should succeed against real server"); + + let expected_root = + OwnedNewPayloadRequest::::from_ssz_bytes(FIXTURE_NEW_PAYLOAD_REQUEST) + .expect("fixture SSZ should decode to a valid NewPayloadRequest") + .tree_hash_root(); + + assert_eq!( + root, expected_root, + "server root should match tree_hash_root of fixture payload" + ); + } + + // ─── Test 2: SSE events from real server are parsed correctly ──────────── + + /// Verifies that SSE events from the real zkBoost server (which use string + /// proof types like `"ethrex-zisk"`) are correctly deserialized by + /// Lighthouse's client back to u8 values. + #[tokio::test] + async fn test_sse_events_from_real_server() { + let harness = ZkboostTestHarness::start(1000).await; + let client = client_for(&harness.url()); + + let attrs = ProofAttributes { + proof_types: vec![ethrex_zisk_u8()], + }; + + // Subscribe to events before requesting proofs. + let mut event_stream = client.subscribe_proof_events(None); + + let root = client + .request_proofs(FIXTURE_NEW_PAYLOAD_REQUEST.to_vec(), attrs) + .await + .expect("request_proofs should succeed"); + + // Wait for a proof event from the real server. + let event = timeout(Duration::from_secs(30), event_stream.next()) + .await + .expect("timed out waiting for SSE event") + .expect("stream ended") + .expect("stream error"); + + assert_eq!(event.new_payload_request_root(), root); + assert_eq!( + event.proof_type(), + ethrex_zisk_u8(), + "string 'ethrex-zisk' from real server should deserialize to u8 {}", + ethrex_zisk_u8() + ); + } + + // ─── Test 3: get_proof downloads proof from real server ────────────────── + + /// Verifies that `get_proof` uses the string proof type in the URL path + /// and successfully downloads a proof from the real server after completion. + #[tokio::test] + async fn test_get_proof_from_real_server() { + let harness = ZkboostTestHarness::start(1000).await; + let client = client_for(&harness.url()); + + let attrs = ProofAttributes { + proof_types: vec![ethrex_zisk_u8()], + }; + + // Subscribe and wait for proof completion. + let mut events = client.subscribe_proof_events(None); + + let root = client + .request_proofs(FIXTURE_NEW_PAYLOAD_REQUEST.to_vec(), attrs) + .await + .expect("request should succeed"); + + // Wait for proof_complete event. + let _event = timeout(Duration::from_secs(30), events.next()) + .await + .expect("timed out waiting for event") + .expect("stream ended") + .expect("stream error"); + + // Download the proof using string proof type in URL path. + let proof_bytes = client + .get_proof(root, ethrex_zisk_u8()) + .await + .expect("get_proof should succeed with string proof type in URL"); + + assert!(!proof_bytes.is_empty(), "proof should not be empty"); + } + + // ─── Test 4: verify_proof against real server ──────────────────────────── + + /// Verifies that `verify_proof` sends the string proof type in query params + /// and the real server accepts the verification request. + #[tokio::test] + async fn test_verify_proof_against_real_server() { + let harness = ZkboostTestHarness::start(1000).await; + let client = client_for(&harness.url()); + + let attrs = ProofAttributes { + proof_types: vec![ethrex_zisk_u8()], + }; + + let mut events = client.subscribe_proof_events(None); + + let root = client + .request_proofs(FIXTURE_NEW_PAYLOAD_REQUEST.to_vec(), attrs) + .await + .expect("request should succeed"); + + // Wait for completion. + let _event = timeout(Duration::from_secs(30), events.next()) + .await + .expect("timed out") + .expect("stream ended") + .expect("stream error"); + + // Download proof. + let proof = client + .get_proof(root, ethrex_zisk_u8()) + .await + .expect("get_proof should succeed"); + + // Verify proof. + let status = client + .verify_proof(root, ethrex_zisk_u8(), &proof) + .await + .expect("verify_proof should succeed against real server"); + + assert_eq!(status, types::execution::eip8025::ProofStatus::Valid); + } + + // ─── Test 5: invalid u8 proof type is rejected by client ───────────────── + + /// Verifies that an unmapped u8 value (e.g. 99) fails at the Lighthouse + /// client level before even reaching the server. + #[tokio::test] + async fn test_invalid_proof_type_rejected_by_client() { + let harness = ZkboostTestHarness::start(0).await; + let client = client_for(&harness.url()); + + let result = client + .get_proof(types::Hash256::repeat_byte(0xAA), 99) + .await; + assert!( + result.is_err(), + "u8 value 99 has no zkBoost mapping — should error at client level" + ); + } + + // ─── Test 6: ZkBoostProofType matches zkboost-types::ProofType ────────── + + /// Validates that Lighthouse's `ZkBoostProofType` enum covers all known + /// zkBoost proof types with matching string representations. + #[tokio::test] + async fn test_zkboost_proof_type_matches_upstream() { + // Collect all upstream ProofType variants. + let upstream: Vec<(String, usize)> = ProofType::all() + .iter() + .enumerate() + .map(|(i, pt)| (pt.as_str().to_string(), i)) + .collect(); + + // Verify Lighthouse's ZkBoostProofType has matching variants. + for (s, i) in &upstream { + let pt: ZkBoostProofType = s + .parse() + .unwrap_or_else(|_| panic!("'{s}' should parse as ZkBoostProofType")); + assert_eq!( + pt.as_str(), + s.as_str(), + "string representation should match upstream" + ); + assert_eq!( + pt as u8, *i as u8, + "u8 mapping for '{s}' should match upstream ordinal {i}" + ); + } + + // Verify all Lighthouse variants are in the upstream list. + let upstream_strs: Vec<&str> = upstream.iter().map(|(s, _)| s.as_str()).collect(); + for pt in ProofType::all() { + assert!( + upstream_strs.contains(&pt.as_str()), + "Lighthouse variant {:?} should exist in upstream zkBoost", + pt + ); + } + + // Counts should match. + assert_eq!( + ProofType::all().len(), + upstream.len(), + "variant count should match between Lighthouse and zkBoost" + ); + } + + // ─── Test 7: full lifecycle (request → SSE → download → verify) ───────── + + /// End-to-end lifecycle against the real zkBoost server. + #[tokio::test] + async fn test_full_lifecycle_against_real_server() { + let harness = ZkboostTestHarness::start(1000).await; + let client = client_for(&harness.url()); + + let attrs = ProofAttributes { + proof_types: vec![ethrex_zisk_u8()], + }; + + let mut events = client.subscribe_proof_events(None); + + // Step 1: Request proof. + let root = client + .request_proofs(FIXTURE_NEW_PAYLOAD_REQUEST.to_vec(), attrs) + .await + .expect("request should succeed"); + + assert!(!root.is_zero()); + + // Step 2: Wait for SSE proof_complete event. + let event = timeout(Duration::from_secs(30), events.next()) + .await + .expect("timed out waiting for event") + .expect("stream ended") + .expect("stream error"); + + assert_eq!(event.new_payload_request_root(), root); + assert_eq!(event.proof_type(), ethrex_zisk_u8()); + + // Step 3: Download proof. + let proof = client + .get_proof(root, ethrex_zisk_u8()) + .await + .expect("get_proof should succeed"); + assert!(!proof.is_empty()); + + // Step 4: Verify proof. + let status = client + .verify_proof(root, ethrex_zisk_u8(), &proof) + .await + .expect("verify_proof should succeed"); + assert_eq!(status, types::execution::eip8025::ProofStatus::Valid); + } +} diff --git a/testing/proof_engine_zkboost/src/zkboost_harness.rs b/testing/proof_engine_zkboost/src/zkboost_harness.rs new file mode 100644 index 00000000000..7e37fb2ca57 --- /dev/null +++ b/testing/proof_engine_zkboost/src/zkboost_harness.rs @@ -0,0 +1,173 @@ +//! Test harness that starts the **real** zkBoost server with mock zkVM backends. +//! +//! This validates that Lighthouse's [`HttpProofNodeClient`] speaks the correct +//! wire protocol by testing it against the actual zkBoost server implementation. +//! +//! ## Architecture +//! +//! 1. A lightweight mock Execution Layer (EL) that serves fixture data for +//! `debug_chainConfig` and `debug_executionWitnessByBlockHash` JSON-RPC methods. +//! 2. The real `zkBoostServer` configured with `zkVMConfig::Mock` backends. +//! 3. Lighthouse's `HttpProofNodeClient` as the system under test. + +use axum::{Json, extract::State, routing::post}; +use bytes::Bytes; +use metrics_exporter_prometheus::PrometheusBuilder; +use serde_json::Value; +use std::net::Ipv4Addr; +use std::sync::Arc; +use tokio::net::TcpListener; +use tokio_util::sync::CancellationToken; +use zkboost_server::{ + config::{Config, zkVMConfig}, + server::zkBoostServer, +}; +use zkboost_types::ProofType; + +// ─── Fixture Data ──────────────────────────────────────────────────────────── + +/// SSZ-encoded NewPayloadRequest from zkBoost's test fixture. +pub const FIXTURE_NEW_PAYLOAD_REQUEST: &[u8] = + include_bytes!("../tests/fixture/new_payload_request.ssz"); + +/// Chain config JSON from zkBoost's test fixture. +const FIXTURE_CHAIN_CONFIG: &str = include_str!("../tests/fixture/chain_config.json"); + +/// Execution witness JSON from zkBoost's test fixture. +const FIXTURE_EXECUTION_WITNESS: &str = include_str!("../tests/fixture/execution_witness.json"); + +// ─── Mock Execution Layer ──────────────────────────────────────────────────── + +struct MockElState { + chain_config: Value, + witness: Value, +} + +/// Mock EL handler that responds to JSON-RPC requests with fixture data. +async fn mock_el_handler(State(state): State>, body: Bytes) -> Json { + let request: Value = serde_json::from_slice(&body).unwrap_or_default(); + let method = request["method"].as_str().unwrap_or(""); + + let result = match method { + "debug_chainConfig" => state.chain_config.clone(), + "debug_executionWitnessByBlockHash" => state.witness.clone(), + _ => Value::Null, + }; + + Json(serde_json::json!({ + "jsonrpc": "2.0", + "result": result, + "id": request["id"], + })) +} + +/// Start a mock execution layer server that serves fixture data. +async fn start_mock_el() -> url::Url { + let chain_config: Value = serde_json::from_str(FIXTURE_CHAIN_CONFIG) + .expect("fixture chain_config.json should be valid JSON"); + let witness: Value = serde_json::from_str(FIXTURE_EXECUTION_WITNESS) + .expect("fixture execution_witness.json should be valid JSON"); + + let state = Arc::new(MockElState { + chain_config, + witness, + }); + + let app = axum::Router::new() + .route("/", post(mock_el_handler)) + .with_state(state); + + let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)) + .await + .expect("failed to bind mock EL"); + let port = listener.local_addr().expect("no local addr").port(); + + tokio::spawn(async move { axum::serve(listener, app).await }); + + format!("http://127.0.0.1:{port}").parse().unwrap() +} + +// ─── Real zkBoost Server ───────────────────────────────────────────────────── + +/// Start the real zkBoost server with mock zkVM backends. +async fn start_zkboost_server( + el_endpoint: url::Url, + zkvm_configs: Vec, +) -> (url::Url, CancellationToken) { + let config = Config { + port: 0, + el_endpoint, + chain_config_path: None, + witness_timeout_secs: 120, + proof_timeout_secs: 120, + proof_cache_size: 128, + witness_cache_size: 128, + zkvm: zkvm_configs, + }; + + let metrics = PrometheusBuilder::new().build_recorder().handle(); + let shutdown = CancellationToken::new(); + let server = zkBoostServer::new(config, metrics) + .await + .expect("failed to create zkBoost server"); + let (addr, _handles) = server + .run(shutdown.clone()) + .await + .expect("failed to start zkBoost server"); + + let endpoint = format!("http://127.0.0.1:{}", addr.port()).parse().unwrap(); + (endpoint, shutdown) +} + +// ─── Test Harness ──────────────────────────────────────────────────────────── + +/// Test harness that manages a real zkBoost server with mock backends. +pub struct ZkboostTestHarness { + /// Base URL of the running zkBoost server. + pub endpoint: url::Url, + /// The proof type configured for the mock backend. + pub proof_type: ProofType, + /// Cancellation token for graceful shutdown. + shutdown: CancellationToken, +} + +impl ZkboostTestHarness { + /// Start a test harness with a single mock zkVM backend. + /// + /// The mock backend uses `EthrexZisk` by default (same as zkBoost's own + /// integration tests) with a configurable proving delay. + pub async fn start(mock_proving_time_ms: u64) -> Self { + Self::start_with_proof_type(ProofType::EthrexZisk, mock_proving_time_ms).await + } + + /// Start a test harness with a specific proof type. + pub async fn start_with_proof_type(proof_type: ProofType, mock_proving_time_ms: u64) -> Self { + let el_endpoint = start_mock_el().await; + + let zkvm_config = zkVMConfig::Mock { + proof_type, + mock_proving_time_ms, + mock_proof_size: 1024, + mock_failure: false, + }; + + let (endpoint, shutdown) = start_zkboost_server(el_endpoint, vec![zkvm_config]).await; + + Self { + endpoint, + proof_type, + shutdown, + } + } + + /// Return the base URL as a string. + pub fn url(&self) -> String { + self.endpoint.to_string().trim_end_matches('/').to_string() + } +} + +impl Drop for ZkboostTestHarness { + fn drop(&mut self) { + self.shutdown.cancel(); + } +} diff --git a/testing/proof_engine_zkboost/tests/fixture/chain_config.json b/testing/proof_engine_zkboost/tests/fixture/chain_config.json new file mode 100644 index 00000000000..82be0f85904 --- /dev/null +++ b/testing/proof_engine_zkboost/tests/fixture/chain_config.json @@ -0,0 +1,45 @@ +{ + "chainId": 3151908, + "homesteadBlock": 0, + "daoForkSupport": false, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "mergeNetsplitBlock": 0, + "shanghaiTime": 0, + "cancunTime": 0, + "pragueTime": 0, + "osakaTime": 0, + "bpo1Time": 0, + "terminalTotalDifficulty": 0, + "terminalTotalDifficultyPassed": true, + "depositContractAddress": "0x00000000219ab540356cbb839cbe05303d7705fa", + "blobSchedule": { + "bpo1": { + "baseFeeUpdateFraction": 8346193, + "max": 15, + "target": 10 + }, + "cancun": { + "baseFeeUpdateFraction": 3338477, + "max": 6, + "target": 3 + }, + "osaka": { + "baseFeeUpdateFraction": 5007716, + "max": 9, + "target": 6 + }, + "prague": { + "baseFeeUpdateFraction": 5007716, + "max": 9, + "target": 6 + } + } +} \ No newline at end of file diff --git a/testing/proof_engine_zkboost/tests/fixture/execution_witness.json b/testing/proof_engine_zkboost/tests/fixture/execution_witness.json new file mode 100644 index 00000000000..65887064f82 --- /dev/null +++ b/testing/proof_engine_zkboost/tests/fixture/execution_witness.json @@ -0,0 +1,50 @@ +{ + "state": [ + "0xf90171a046a9f1217c365990825b7d161fc23cae5688cfb6b2307efe4b732c723e03795880a0c0e0b54cb105bad41b4b925883507463ddfae71c619ba2e41d6d57da2a28effea0793c9db0e252f8f5c79a9d872efc5385ab632a9dc31217637b3509fcf6f0b010a077c059a2b360e9c967686a1302a40994cd63a81aa80a841991d8f3d7379b68eb80a0386a1e942dbe86342b17e2e8b28a259d6db65df8e05f944951a089bb9f3d989fa0315b6e4145b520b88ff5fb638b922671ee1ecbcb65b57b9a4be650ab1fce1d39a066e01acc8a9826bc3d5f5286819fc5883dfa30943331f1e7ff2968bfc57ea2d0a00f7041c0b666de2c820d816b27347738f0e8e2d4d7e1e94e2908b88bc3665a338080a012794aea34d39f220863a2977506ebe5555c2b6488a9469fed918b744f67d6d9a0ace6b45485050162428ffe70f5214d2350ed4890b94322bda3ba63a17342983aa0e20d629ffd2bee3848106f86b98c50a9de755283203bb778c19fa269c8ddb2e38080", + "0xe214a0daadd0f2cf85d5b6a644144de38d5eea115a4546c5efc75c3aee9934f46754a0", + "0xf90131a0e9355b99a40b0e92cc489d34c25f68648461fe0dfcefe3c861f1042ae7cbd522a0766a5ea5b9545a72a463a0fc6151efd1ea0e13f7bd151789dcbff75a1e73cd7180a06e27501c46d61120352d54c49863fcf0eaafcfbffdab9e9e09847d62beef79d88080a0731b30b1211ab24c3e719ad1774d6d450379c926217e248edc5c2a6812e0169480a02978c23bea458e7f47cdc57a9938245e2c763f556847e7e320f7f1bd844127628080a047b4dd8c12aa7dec12a56beb24dff26bd425669991ba54e9ec3225fe6293da24a039f39136138de3527d38db1831c98f8897eecd0a75a77129f6386184f28c779ba0fa149b424c332acc1c6967d908eab8e4922f27e00499ed6a1aca3b9975d87ef580a09f297d5e53d34bc2096cce66773c42bf5b177714e3bb9f180521045b34f7127d80", + "0xf90211a0fcf8a530a63eb8575eb9a70c95332fc1047b567be3d1da03a21c9917d92b14a6a006d47616df479b46b302f2a8b7ed03cb537f6cf7c551c15421c65db4e00fa97fa038e34f9e0e4830343ba24f5fcf0eba28d79cb86397adfb16a0169ee7f0180036a004f7ae295715850712c9dc7f1b9f973797b89ef0f46991203ac789210330a517a0dd3420839babaee761e7eaa38ad5f596b1a9b8716e7e9b9261949a964a5a7d61a0eea64374052ac460957bbc34a071cb8b25dcdf44d96785a55b34242914c83f9fa008763a217b516bcfeadc7f6849e812b392643f50a1d25f002ceec6c2ca0adcafa083c6979e463c02818ffeadaeeb8abc9f2f51e767fb9151a7fc89989eb40b57aca0cbcdc1d226a540c50cb1e615e7af99f171d4365b45734940e22d47ec4aa23a14a0be88e4724326382a8b56e2328eeef0ad51f18d5bae0e84296afe14c4028c4af9a018e0f191e57d4186717e0f3c9379d2438cec0babd12d3903a4ad560f017331bfa01796617427e67ed10cdf8a72b02689a700ba71eb93186a1b120c9ad0b0e56eaea0ad0bb86b47186c04223e85a9c33dd1c87dd6e5c17f753f4fd0a56772d8a78399a065fb94808e31ca248fb2d9de329b81735b22f75d109f389678c9965418bf1f16a06a2b50671c3f299bfd4b6cf43d6e5d6aafd4d3677c38a8af52a0cd7680de2b94a037ff00fbe2105bce0e6ed9ea80a1d67b8a476b1ff3d177ac9597a53241e47aa780", + "0xf901b1a027db720cbe694541a361e08b5450894ddce39b11113fe952080ad5f54ada6f4a80a0d2e57f615a47508c6e60935353428b9fc1cc75677a3eb8f5f73d61dd0aaff5f5a0ca976997ddaf06f18992f6207e4f6a05979d07acead96568058789017cc6d06ba04d78166b48044fdc28ed22d2fd39c8df6f8aaa04cb71d3a17286856f6893ff8380a0fc3b71c33e2e6b77c5e494c1db7fdbb447473f003daf378c7a63ba9bf3f0049da0a9c8e462df1860757a204a01fccc87b873837b0a32cbcc645fb663f3eb12a705a07b8e7a21c1178d28074f157b50fca85ee25c12568ff8e9706dcbcdacb77bf854a0973274526811393ea0bf4811ca9077531db00d06b86237a2ecd683f55ba4bcb0a091d9c76bfbc066e84f0b415c737ab8c477498701d920526db41690050cfade99a06aa67101d011d1c22fe739ef83b04b5214a3e2f8e1a2625d8bfdb116b447e86fa0244e4282dfec33c9bb765162ceee4f2e6390033a94b620d50a2fc6943ebd82fca0f3b039a4f32349e85c782d1164c1890e5bf16badc9ee4cf827db6afd2229dde6a0d9240a9d2d5851d05a97ff3305334dfdb0101e1e321fc279d2bb3cad6afa8fc88080", + "0xf90171a06664dd6bcbb08b83f84324db8cbaf2ceb221e49e66971369dd2257e947a3b13d80a0f4ec365c37413b5f9e7d38c3c6409922fa2a593757ef6176b7291ede5ae2b2d780a0614ab7fe84bea831a68e5e39c6e2d339db432b94dcd29ac75de694cfc6641496a036750a0cdda09ef53dc4a7510eb69e87fbafb1739f51d52c60214b7e0d276ddda04eb05cc2337a47e5d315fc9e2972f88b2282caecf7b79cb486ccf4e64ddf54cd80a0044dadb95a10fad8f922e38449d128807ed6c4b3e6af52d0faa865be8cb8847480a0d53e862eebd81f90452eada8434dfdd03a7ef3d06d6db3e68cbc7d05dff81ec0a0eb47388255e7ca68b42fa56180019c61e2dd301bfe20226d6a74d795f6b016a6a0c522d5defc176e5fa5fc0f16d95ad335f25668067c2c9a55db7d901fd8ac04c6a06c457c05a87c557f84f6d98cfb3754a20c1ded0550ef405433d3514f332c77df80a0d5758f21c6c63a45c81d16ecca352c41af637c1729f8866900efcf731dc10db280", + "0xf901518080a0f1a60e8881cfcb2dc50ba58c326ccc9a6da8287c1e5f56d2017563be700058c4a0616362468a3391221e3782da42e2d6fb8ea41da6bdd2d679e20bf0375c06158680a0ed2fba131fadeadeb1082f565fff16ceb008f693056e3140204716c0739cf1e08080a0cfcecd85b5b3b2b03c196589d3d3b9bcd0ddfc01f000cde9fe3cab41dc6a0a16a0b2a5565ce39d8b7fabb242f087f05b7273aef44094f4166046cddd978751c4bea06234ead07239df2c23d50d21d2e045332bb3e2fb0a402aae5780b823e7d5308680a0ebe51b14fea6aaa5c097f2506874e990813c36cd31399ee3d72666de2dde3fcca051eac0e6e8747ed945c8119613a8359cb76220e714610cf783388ce900153208a0e16e6773b65ff27c428b07407a2d2e479712166515a4a43ecc3c4444d77d4f34a0105bafd3bfbb01dd5f28afe06b314ccf6d5f1bddd1e2135dcc010cb3aedd1d4380", + "0xf869a02086c581c7d7b44eecbb92fd9e5867945ec1acdc0ea5bbabda21d17dddf06473b846f8440180a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a00345a365d2f4c5975b9f1599abe0a2ee76b7a3a731bc68781bd04c84e4858f50", + "0xf8518080a0a63eaef203909ce313085e71f47b6855ccd4fffe444fba1ec1efdab787203351808080a079af4179331361fad570767001c2b705c100b691c849c2bda966d070709c4bf880808080808080808080", + "0xf8f18080a0936cc4aad97b5838a8bc0dfa95a5ad0a0fe2c4681ffe209a0228098aad0c619080a0985a93e071c1474beffb3b3feefcf343ea7e4e002d8c6c7675de86cc9ebc27c0a022652c87d9810e05a254c5942729b67343e1069440f4bee452c8c7bb88d193e8a0975e4f968cf4537118047c31c634177fbec68949fd40003601aab2ff822bcc5580a04b66d0874dc47dba19ba179183342befb18cd80e6d4f85516123426f86e0d7afa081ce69bcb065377bda0ac34c8b05086357838440690dbcccba20f3e8cab1b88680808080a0dc281265feb5bcd82bc162628f62f92dc649b77c80a3ac5d14bdf3d367c495238080", + "0xf90211a040da929897ecf8fb0ecdda44d1c6aa37c7b5d19d0a6f1255c3aaac43b77f2d4ca05dbd3e7becc744398948292f4810e753b166b91cc1a763b214b24718e2bc432aa0d8222bc84a44e1d1d03bdae69910ff3b244c815fa99ef1a9aa6bb568cad6b35ca050e569e8e5e77ab130db2842f7598cee50d0b42cc2504a9df287175c307b23c3a01fd4c856668574229bec8b57377eb317351e0695f8c7c8239aa1016a73001b16a0e0cf581054d8c2bab1359dddde660c659c0e5d70ca2c03e667419d1bc9e45f05a0ebe2e281fc5af1d9bc149c1bf210d264f9b283a2c1760abf0ad5f48e08499ac8a08be4370ed1686f92ab5478848e85a1abab751c9c80e7f8d68daa8c3d8232356aa02fb840ef5765a4ceb26d610badea7ea799c28544f3b329b986c400ff272967d2a0e8393cb9738eea3a5031110dd9c2043e360267072374de576cdc9bc4fa015d39a046ff1faf6df6476a5a4d8f6ba32c8f38582b3a7bd4e12893c1712894ea39c017a008afbb10c9064b061ba3a17cfaf8c083b376a402c60704bc0afaa7a55d27f5c9a0582d0c27b5152cb3f3247a8752888739769fb2b6e3f7842298bc26b616773b88a0db4a8cc49ce3a0fefe00143359d4f0fa86026559ea073bb061b7aacd217ac037a0f31e8aa4efb4024c99d873f31485f1c496f484c345b1ec664f4ba723499e03f8a0672f74dceafee2ee98a97fb19f4afdb991ba8c1ee019438f15b809da4b427b5a80", + "0xf90211a04efbc90ce3b15216a559cdb50fb788b0af3916ef1777a585e7093e27cf4bc16da0047b79502e6ba90c8c1b4863e8380b3e6cc23da1c208f8e39c348a936af31ea5a03db8dd4c19ae2b67a736b757995cb7b57ed55ccdd34fb0ebe979a2dee0c66339a0471db2263571236146b863a32d0d1abe6e21a984998b2d7c0376b4243dca42d2a024e8f92fe5bdde58f4954f534b6f91659a8c0f889abdbf7eec9ab77a26478072a0f8afbd19dafaf176fc835595483ea85f554b1b840e8709b3c2a07715411ecb08a034cb0ac81dbce62a5c9855fe0311bd6827fecbb9aa741a7c8e8b7427f73b8716a0a2a9a28a4324e79e625b104a232620f515ff4a3428c78257bbec3621343ec11aa0b030f3e6c8e7b40bc5bea3da238bcf7546c521b7d6b72dbd98e3fbffb0d604ada0de4bf15b56b7a96707c9c6072d1f413322e563f04ec3c3f9fcf7719b073ed285a0c641efacb85f02a412724d2ba1a107c767d66f5258ae33c9c64bd1bcc4a64540a02e14db6c4900768cf91528d8e1b746f9ab032f277077459f5cc79d16b6be0dc3a006c495fb6961358f4bde6c279838bbc557f9927391b42070bd44b30ab824430fa07415c94beb78124e62f7f63ad7a64076cf7b004809565b8a63dedcacc1434ac5a092712479fda69c5e14b2085716b5e5ab229494f395740b941280432b831ed221a0176a9fce68e6fd07098e5bd0e742a828173ba4a7feed5b6455794caad04462a880", + "0xf869a0209d57be05dd69371c4dd2e871bce6e9f4124236825bb612ee18a45e5675be51b846f8440180a0a247228347f628c6463d5f2932202f269bcabe3dbc08a56392c2dc88e7e04249a06e49e66782037c0555897870e29fa5e552daf4719552131a0abce779daec0a5d", + "0xf90211a0e66e395bd17cd8e5cea8b1c1aad2bb861eeb8a2bb096ea6eddeb34422497bdaaa04c03fe869c8cde143d01ab6bfc09226ea42d9ad99a53263f69716a7186c0bf0aa077e46fe2af85fe2ea2de398481c148651e7ee82f27176160eb18b3a802661798a0c52146e012e5094a13d00fc9dbe596a0639c59e2587b7ac55038d3e52d4f4936a044cb808faa3a8e993889588681b030c9a97babe7e15fdb71be950e9a88a7e402a03deea8359c1b0971aa68d701e9cd18016134f5310b0e4a7d9833247db460a1f1a02cedc09ae6f35f5e75e4a65cee5fc753b113311d912b25fc289a872885415a8ca080b9f7d63a5ea0d7b20ace0018da20977a795543c0ab2d4035b60885e5d60828a0b8f2aa8b6816e39e58f9193d23f9573f75e4c0dea753b325da153a6fbdbaabd2a05126fa3c18c632812536718c92ed0747e4a610c245ea1234acbca7533f1506f2a014116df18532e1f44477d3cf371240e82d2cf7c02542d6da6ab56861626a0c24a0ad7eb60b7242bb4abab99f42056bcc64ae2de2b6182550cb6864c404b059fb3fa04e222b8402af16d6151aba0426b59a029db34ba31592f254ba8d6f64e59e07eba0eae43e73dfb5784c88f6424e4da4ac7aae2aa29f09cd528aab89a4003e3a4da7a023fc581b6065c3d34578d7119f3385df16ae9a24aab09a98877d36fd844f2933a0a4cb53144ee264a09401aabbebb43c80264ebfce063a70c28595e1b0c52fdd9c80", + "0xf90211a0f53fd45e8a28bfc7c92543aac0f242249bd15dc550b8d1d43defabfe1ff4622ba072d67f642876a04c9733ce298d4bb2fdc2eb041b6760ed0a3be006785b0705e0a0a86c39e9a32652492ee5240d1715c6a63537351d350754b62952760d8e1f944ba0e79513901b1f313c826300a31dff17f6adf9e2aeb895f730dbb93f0a96a86d9fa031c4646963f14566afb0e50a6c400d69c834c3b1fdb3909677856cbb576db4e5a09cdcc334e9d1c6451e5f5230efdd07ac62f48223d3a71b7082d1c9f3faee6af1a0f5ea37b375d1f04089104149dd9204aa0ff3c90167f7aca7da201905594300e4a076972cc63f4fabea810e87083ec1899b687d8748d26fe16fd4b6a13ae3e303f1a04ff31ed8ee553088b2e578f36bf3ed50d5cbd58611261be37633294dc61acca9a028b05d809456d53fc06c9b102d216cff567a7aca7c9d1cb4cdca67965f0ef4cda01556f03106eeb9fc5a473e8f7f042e57d827b78b76a5f7a8f5b187f8d897515da0f762ae6fe61a92321fb8d528b2f5f4b1b97a94ebf2d5ec0899e8f703fba9790da036affb194c9227b46dece3bc3e1e5ef56403db6c8e34fe1b8bb3ae197158b5d6a0db08702017c418fb841716b9c2454676fa632f607d5b261f55c7434dbc69c4d6a0d4da88e24a26de50f4f0d35a348e12da471480c6e612dacccbc594a61f58d74ca0aaba74a722fd0645b8b7a8886d0e891e04c4e57914480568f5334d7514391f6680", + "0x80", + "0xf8429f37d5f9b51ca71bda3c02250aa5ededabaa712e18e5f1714fde16280d94a4a3a1a0d5848dbf659bcc407318ddcac1ad62fb7b58c53df808ed0a560c8d4a94ac3e6f", + "0xf8689f3aea581b220579a2b99819299dd32c7c28a420018ecb0bde93af007ad89a31b846f8440180a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a078c6cb5202685228bbcbfb992b1c4e116c7ec5ef11e25b8e92716cfc628ddd60", + "0xf869a020d65eaa92c6bc4c13a5ec45527f0c18ea8932588728769ec7aecfe6d9f32e42b846f8440180a0a52d06d7443bb469a8ddfecb744e9750fa7284a237b31f7168562123b84c3547a0f57acd40259872606d76197ef052f3d35588dadf919ee1f0e3cb9b62d3f4b02c", + "0xf90211a065cb9654d83c2c587ff35d995153e55908ccc8d12f99cec6f0fca2174d0d4887a072c2cce9f8770d341a4cb7c7cdc53d75d6308b55e9f991bc8ba67b29434b61dfa0f2b29241a79b4cf67be8c19e0fc49894bbc908bdfaa864f313e640a9656271cca0a4f08ea6851799ebadce763bdb22c8511a37106f2b1f1a2e1da77743588a4751a0e473037e78e7f6b59faf7c818971524734244419165e3b52fd6747e4acbb3235a09f871e9dc9ad7e80a33f12dfb19ec657a944edd24ecd975367a4675d7a2760a0a0f3e41d9e7b89a679eba0c449b24e2f6b074dd4e65abc10fee304b97893689673a0ba956ceecf3546a048edbdb0e93c6bd5f9437ee2bc2eb547d95cad86e16e791ba0be49e1efa56a6325758e40aa25985c3f71f2d20888daa9efd8e2e9cd0d70826ca05d4d0edd678514b0b449d8689f7971252fd7b86378a102395d5ee769d709c2a1a0fcacd3004b2d9f8c601c667041baea5c7ad53bde430303ab3d2f5c765804cd82a0b7195c41d29afbb5b45413885333d6a19b0679d3a92a9f1198ab04689ac0518ca06675b419aca5f5ab938080fd8245ae9c388c144521ad7d4a57e8f36212e218a2a0ddfccdcd7960367614d844e7fca5cb92573ace5ca42ad9381dfc2c69e7f0f890a04651f6d80d233d28e5cda8940d11319698f604ee414041a9374a5ee3d7305b1fa0da847328820b77fcc53e716178f77359797b68b90e53117251c9115ee6fc428880" + ], + "codes": [ + "0x3373fffffffffffffffffffffffffffffffffffffffe1460cb5760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101f457600182026001905f5b5f82111560685781019083028483029004916001019190604d565b909390049250505036603814608857366101f457346101f4575f5260205ff35b34106101f457600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260385f601437604c5fa0600101600355005b6003546002548082038060101160df575060105b5f5b8181146101835782810160030260040181604c02815460601b8152601401816001015481526020019060020154807fffffffffffffffffffffffffffffffff00000000000000000000000000000000168252906010019060401c908160381c81600701538160301c81600601538160281c81600501538160201c81600401538160181c81600301538160101c81600201538160081c81600101535360010160e1565b910180921461019557906002556101a0565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14156101cd57505f5b6001546002828201116101e25750505f6101e8565b01600290035b5f555f600155604c025ff35b5f5ffd", + "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", + "0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500", + "0x3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd" + ], + "keys": [ + "0x000f3df6d732807ef1319fb7b8bb8522d0beac02", + "0x0000000000000000000000000000000000000000000000000000000000003808", + "0x0000000000000000000000000000000000000000000000000000000000001809", + "0x00000961ef480eb55e80d19ad83579a64c007002", + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000f90827f1c53a10cb7a02335b175320002935", + "0x00000000000000000000000000000000000000000000000000000000000004af", + "0x0000bbddc7ce488642fb579f8b00f3a590007251", + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000003" + ], + "headers": [ + "0xf9026fa084a5904e068368b6581e5afa05f96e3912068ab8ceee08ca76bdb9719bd1c090a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948943545177806ed17b9f23f0a21ee5948ecaa776a03bb7c2e1c292bc41a27064b9160eb131723e6c345851ee0c386f09115da5fae6a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000808204af8402255100808469aeca6d92726574682f76312e31302e312f6c696e7578a0f2940bf2aad7139113b79fcd654cb699530e993a33dc05a31ebfcf017643b55888000000000000000007a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218080a037afc7de70547b71e752341e78303f688e6f5b87e47367b747947d5d34af77a0a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + ] +} \ No newline at end of file diff --git a/testing/proof_engine_zkboost/tests/fixture/new_payload_request.ssz b/testing/proof_engine_zkboost/tests/fixture/new_payload_request.ssz new file mode 100644 index 00000000000..6ffe35cc644 Binary files /dev/null and b/testing/proof_engine_zkboost/tests/fixture/new_payload_request.ssz differ diff --git a/testing/simulator/Cargo.toml b/testing/simulator/Cargo.toml index a1b1b6f95d2..ae1b484a45f 100644 --- a/testing/simulator/Cargo.toml +++ b/testing/simulator/Cargo.toml @@ -3,22 +3,33 @@ name = "simulator" version = "0.2.0" authors = ["Paul Hauner "] edition = { workspace = true } + +[features] +test-utils = [] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = { workspace = true } +beacon_chain = { workspace = true } clap = { workspace = true } -environment = { workspace = true } +environment = { workspace = true, features = ["test-utils"] } +eth2 = { workspace = true, features = ["events"] } execution_layer = { workspace = true } futures = { workspace = true } kzg = { workspace = true } +lighthouse_network = { workspace = true } logging = { workspace = true } +network_utils = { workspace = true } node_test_rig = { path = "../node_test_rig" } parking_lot = { workspace = true } rayon = { workspace = true } sensitive_url = { workspace = true } serde_json = { workspace = true } +task_executor = { workspace = true } +tempfile = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } typenum = { workspace = true } types = { workspace = true } +validator_http_api = { workspace = true } diff --git a/testing/simulator/src/basic_sim.rs b/testing/simulator/src/basic_sim.rs index 13bfcb5fc35..cc1c3c32a01 100644 --- a/testing/simulator/src/basic_sim.rs +++ b/testing/simulator/src/basic_sim.rs @@ -1,4 +1,5 @@ use crate::local_network::LocalNetworkParams; +use crate::local_network::NodeType; use crate::local_network::TERMINAL_BLOCK; use crate::{LocalNetwork, checks}; use clap::ArgMatches; @@ -26,7 +27,7 @@ use tracing::Level; use types::{Epoch, EthSpec, MinimalEthSpec}; const END_EPOCH: u64 = 16; -const GENESIS_DELAY: u64 = 38; +const GENESIS_DELAY: u64 = 45; const ALTAIR_FORK_EPOCH: u64 = 0; const BELLATRIX_FORK_EPOCH: u64 = 0; const CAPELLA_FORK_EPOCH: u64 = 0; @@ -35,7 +36,7 @@ const ELECTRA_FORK_EPOCH: u64 = 2; // const FULU_FORK_EPOCH: u64 = 3; // const GLOAS_FORK_EPOCH: u64 = 4; -const SUGGESTED_FEE_RECIPIENT: [u8; 20] = +pub const SUGGESTED_FEE_RECIPIENT: [u8; 20] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; #[allow(clippy::large_stack_frames)] @@ -209,6 +210,9 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { extra_nodes, proposer_nodes, genesis_delay, + proof_generator_nodes: 0, + proof_verifier_nodes: 0, + delayed_nodes: 0, }, context.clone(), )) @@ -218,7 +222,11 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { // Add nodes to the network. for _ in 0..node_count { network - .add_beacon_node(beacon_config.clone(), mock_execution_config.clone(), false) + .add_beacon_node( + beacon_config.clone(), + mock_execution_config.clone(), + NodeType::Default, + ) .await?; } @@ -228,7 +236,11 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { for _ in 0..proposer_nodes { println!("Adding a proposer node"); network - .add_beacon_node(beacon_config.clone(), mock_execution_config.clone(), true) + .add_beacon_node( + beacon_config.clone(), + mock_execution_config.clone(), + NodeType::Proposer, + ) .await?; } @@ -259,7 +271,7 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { .await } else { network_1 - .add_validator_client(validator_config, i, files) + .add_validator_client(validator_config, i, files, NodeType::Default) .await } .expect("should add validator"); @@ -361,7 +373,7 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { network_1.add_beacon_node_with_delay( beacon_config.clone(), mock_execution_config.clone(), - END_EPOCH - 1, + END_EPOCH - 3, slot_duration, slots_per_epoch ), diff --git a/testing/simulator/src/fallback_sim.rs b/testing/simulator/src/fallback_sim.rs index 3d9a60abc7b..372290a3524 100644 --- a/testing/simulator/src/fallback_sim.rs +++ b/testing/simulator/src/fallback_sim.rs @@ -1,4 +1,4 @@ -use crate::local_network::LocalNetworkParams; +use crate::local_network::{LocalNetworkParams, NodeType}; use crate::{LocalNetwork, checks}; use clap::ArgMatches; @@ -22,7 +22,7 @@ use tracing_subscriber::prelude::*; use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt}; use types::{Epoch, EthSpec, MinimalEthSpec}; const END_EPOCH: u64 = 16; -const GENESIS_DELAY: u64 = 38; +const GENESIS_DELAY: u64 = 45; const ALTAIR_FORK_EPOCH: u64 = 0; const BELLATRIX_FORK_EPOCH: u64 = 0; const CAPELLA_FORK_EPOCH: u64 = 1; @@ -215,6 +215,9 @@ pub fn run_fallback_sim(matches: &ArgMatches) -> Result<(), String> { node_count, extra_nodes: 0, proposer_nodes: 0, + proof_generator_nodes: 0, + proof_verifier_nodes: 0, + delayed_nodes: 0, genesis_delay, }, context.clone(), @@ -225,7 +228,11 @@ pub fn run_fallback_sim(matches: &ArgMatches) -> Result<(), String> { // Add nodes to the network. for _ in 0..node_count { network - .add_beacon_node(beacon_config.clone(), mock_execution_config.clone(), false) + .add_beacon_node( + beacon_config.clone(), + mock_execution_config.clone(), + NodeType::Default, + ) .await?; } diff --git a/testing/simulator/src/lib.rs b/testing/simulator/src/lib.rs new file mode 100644 index 00000000000..b6c70d44969 --- /dev/null +++ b/testing/simulator/src/lib.rs @@ -0,0 +1,26 @@ +//! This crate provides various simulations that create both beacon nodes and validator clients, +//! each with `v` validators. +//! +//! When a simulation runs, there are checks made to ensure that all components are operating +//! as expected. If any of these checks fail, the simulation will exit immediately. +//! +//! ## Future works +//! +//! Presently all the beacon nodes and validator clients all log to stdout. Additionally, the +//! simulation uses `println` to communicate some info. It might be nice if the nodes logged to +//! easy-to-find files and stdout only contained info from the simulation. +//! +pub mod basic_sim; +pub mod checks; +pub mod cli; +pub mod fallback_sim; +pub mod local_network; +pub mod retry; + +pub use local_network::LocalNetwork; +pub use types::MinimalEthSpec; + +pub type E = MinimalEthSpec; + +#[cfg(feature = "test-utils")] +pub mod test_utils; diff --git a/testing/simulator/src/local_network.rs b/testing/simulator/src/local_network.rs index 58d7e1372fc..9d06f965002 100644 --- a/testing/simulator/src/local_network.rs +++ b/testing/simulator/src/local_network.rs @@ -1,8 +1,11 @@ use crate::checks::epoch_delay; +use beacon_chain::custody_context::NodeCustodyType; use kzg::trusted_setup::get_trusted_setup; +use lighthouse_network::types::Enr; +use network_utils::listen_addr::ListenAddress; use node_test_rig::{ ClientConfig, ClientGenesis, LocalBeaconNode, LocalExecutionNode, LocalValidatorClient, - MockExecutionConfig, MockServerConfig, ValidatorConfig, ValidatorFiles, + MockExecutionConfig, ValidatorConfig, ValidatorFiles, environment::RuntimeContext, eth2::{BeaconNodeHttpClient, types::StateId}, testing_client_config, @@ -15,23 +18,84 @@ use std::{ sync::Arc, time::{Duration, SystemTime, UNIX_EPOCH}, }; +use task_executor::TaskExecutor; +use tempfile::tempdir; use types::{ChainSpec, Epoch, EthSpec}; +use validator_http_api::{Config as ValidatorHttpConfig, PK_FILENAME}; -const BOOTNODE_PORT: u16 = 42424; -const QUIC_PORT: u16 = 43424; +pub const TERMINAL_BLOCK: u64 = 0; -pub const EXECUTION_PORT: u16 = 4000; +#[derive(Debug, Copy, Clone)] +pub enum NodeType { + Default, + Proposer, + ProofVerifier, + ProofGenerator, +} -pub const TERMINAL_BLOCK: u64 = 0; +impl NodeType { + /// Returns true if this node is a proposer node. + pub fn is_proposer(&self) -> bool { + matches!(self, NodeType::Proposer) + } + + /// Returns true if this node is a proof verifier node. + pub fn is_proof_verifier(&self) -> bool { + matches!(self, NodeType::ProofVerifier) + } + + /// Returns true if this node is a proof generator node. + pub fn is_proof_generator(&self) -> bool { + matches!(self, NodeType::ProofGenerator) + } + + /// Returns true if this node requires a proof node. + pub fn requires_proof_node(&self) -> bool { + matches!(self, NodeType::ProofVerifier | NodeType::ProofGenerator) + } + + /// Returns true if this node requires an execution node. + pub fn requires_execution_node(&self) -> bool { + matches!( + self, + NodeType::Default | NodeType::Proposer | NodeType::ProofGenerator + ) + } +} +#[derive(Debug, Clone)] pub struct LocalNetworkParams { pub validator_count: usize, pub node_count: usize, pub proposer_nodes: usize, + pub proof_generator_nodes: usize, + pub proof_verifier_nodes: usize, pub extra_nodes: usize, + pub delayed_nodes: usize, pub genesis_delay: u64, } +impl LocalNetworkParams { + pub fn node_type(&self, node_idx: usize) -> NodeType { + if node_idx < self.node_count { + NodeType::Default + } else if node_idx < self.node_count + self.proposer_nodes { + NodeType::Proposer + } else if node_idx < self.node_count + self.proposer_nodes + self.proof_generator_nodes { + NodeType::ProofGenerator + } else if node_idx + < self.node_count + + self.proposer_nodes + + self.proof_generator_nodes + + self.proof_verifier_nodes + { + NodeType::ProofVerifier + } else { + panic!("Invalid node index: {}", node_idx); + } + } +} + fn default_client_config(network_params: LocalNetworkParams, genesis_time: u64) -> ClientConfig { let mut beacon_config = testing_client_config(); @@ -39,8 +103,13 @@ fn default_client_config(network_params: LocalNetworkParams, genesis_time: u64) validator_count: network_params.validator_count, genesis_time, }; - beacon_config.network.target_peers = - network_params.node_count + network_params.proposer_nodes + network_params.extra_nodes - 1; + beacon_config.network.target_peers = network_params.node_count + + network_params.proposer_nodes + + network_params.proof_generator_nodes + + network_params.proof_verifier_nodes + + network_params.extra_nodes + + network_params.delayed_nodes + - 1; beacon_config.network.enr_address = (Some(Ipv4Addr::LOCALHOST), None); beacon_config.network.enable_light_client_server = true; beacon_config.network.discv5_config.enable_packet_filter = false; @@ -48,13 +117,6 @@ fn default_client_config(network_params: LocalNetworkParams, genesis_time: u64) beacon_config.chain.optimistic_finalized_sync = false; beacon_config.trusted_setup = get_trusted_setup(); - let el_config = execution_layer::Config { - execution_endpoint: Some( - SensitiveUrl::parse(&format!("http://localhost:{}", EXECUTION_PORT)).unwrap(), - ), - ..Default::default() - }; - beacon_config.execution_layer = Some(el_config); beacon_config } @@ -62,13 +124,7 @@ fn default_mock_execution_config( spec: &ChainSpec, genesis_time: u64, ) -> MockExecutionConfig { - let mut mock_execution_config = MockExecutionConfig { - server_config: MockServerConfig { - listen_port: EXECUTION_PORT, - ..Default::default() - }, - ..Default::default() - }; + let mut mock_execution_config = MockExecutionConfig::default(); if let Some(capella_fork_epoch) = spec.capella_fork_epoch { mock_execution_config.shanghai_time = Some( @@ -166,6 +222,11 @@ impl LocalNetwork { Ok((network, beacon_config, execution_config)) } + /// Returns the `TaskExecutor` used by this `LocalNetwork`. + pub fn executor(&self) -> &TaskExecutor { + &self.context.executor + } + /// Returns the number of beacon nodes in the network. /// /// Note: does not count nodes that are external to this `LocalNetwork` that may have connected @@ -195,16 +256,22 @@ impl LocalNetwork { mut beacon_config: ClientConfig, mock_execution_config: MockExecutionConfig, ) -> Result<(LocalBeaconNode, LocalExecutionNode), String> { + let listen = ListenAddress::unused_v4_ports(); + let v4 = listen.v4().expect("unused_v4_ports always returns V4"); beacon_config.network.set_ipv4_listening_address( - std::net::Ipv4Addr::UNSPECIFIED, - BOOTNODE_PORT, - BOOTNODE_PORT, - QUIC_PORT, + Ipv4Addr::UNSPECIFIED, + v4.tcp_port, + v4.disc_port, + v4.quic_port, ); - - beacon_config.network.enr_udp4_port = Some(BOOTNODE_PORT.try_into().expect("non zero")); - beacon_config.network.enr_tcp4_port = Some(BOOTNODE_PORT.try_into().expect("non zero")); beacon_config.network.discv5_config.table_filter = |_| true; + beacon_config.network.enr_udp4_port = std::num::NonZeroU16::new(v4.disc_port); + beacon_config.network.enr_tcp4_port = std::num::NonZeroU16::new(v4.tcp_port); + beacon_config.network.enr_quic4_port = std::num::NonZeroU16::new(v4.quic_port); + + // The boot node is a full data-availability node and should custody all columns from + // genesis. This ensures we have sufficient peers on each custody group. + beacon_config.chain.node_custody_type = NodeCustodyType::Supernode; let execution_node = LocalExecutionNode::new(self.context.clone(), mock_execution_config); @@ -223,37 +290,63 @@ impl LocalNetwork { async fn construct_beacon_node( &self, mut beacon_config: ClientConfig, - mut mock_execution_config: MockExecutionConfig, - is_proposer: bool, - ) -> Result<(LocalBeaconNode, LocalExecutionNode), String> { - let count = (self.beacon_node_count() + self.proposer_node_count()) as u16; - - // Set config. - let libp2p_tcp_port = BOOTNODE_PORT + count; - let discv5_port = BOOTNODE_PORT + count; + mock_execution_config: MockExecutionConfig, + node_type: NodeType, + ) -> Result<(LocalBeaconNode, Option>), String> { + let listen = ListenAddress::unused_v4_ports(); + let v4 = listen.v4().expect("unused_v4_ports always returns V4"); beacon_config.network.set_ipv4_listening_address( - std::net::Ipv4Addr::UNSPECIFIED, - libp2p_tcp_port, - discv5_port, - QUIC_PORT + count, + Ipv4Addr::UNSPECIFIED, + v4.tcp_port, + v4.disc_port, + v4.quic_port, ); - beacon_config.network.enr_udp4_port = Some(discv5_port.try_into().unwrap()); - beacon_config.network.enr_tcp4_port = Some(libp2p_tcp_port.try_into().unwrap()); beacon_config.network.discv5_config.table_filter = |_| true; - beacon_config.network.proposer_only = is_proposer; - - mock_execution_config.server_config.listen_port = EXECUTION_PORT + count; + beacon_config.network.enr_udp4_port = std::num::NonZeroU16::new(v4.disc_port); + beacon_config.network.enr_tcp4_port = std::num::NonZeroU16::new(v4.tcp_port); + beacon_config.network.enr_quic4_port = std::num::NonZeroU16::new(v4.quic_port); + beacon_config.network.proposer_only = node_type.is_proposer(); + + let execution_node = if node_type.requires_execution_node() { + let execution_node = + LocalExecutionNode::new(self.context.clone(), mock_execution_config); + + // Pair the beacon node and execution node. + beacon_config.execution_layer = Some(execution_layer::Config { + execution_endpoint: Some( + SensitiveUrl::parse(&execution_node.server.url()).unwrap(), + ), + default_datadir: execution_node.datadir.path().to_path_buf(), + secret_file: Some(execution_node.datadir.path().join("jwt.hex")), + ..Default::default() + }); + Some(execution_node) + } else { + beacon_config.execution_layer = None; + None + }; - // Construct execution node. - let execution_node = LocalExecutionNode::new(self.context.clone(), mock_execution_config); + if node_type.requires_proof_node() { + beacon_config.network.enable_execution_proof = true; + let bn_idx = self.beacon_nodes.read().len(); + let _: execution_layer::test_utils::MockProofNodeClient = + execution_layer::test_utils::register_mock_proof_engine(bn_idx, 400); + let mock_url = + SensitiveUrl::parse(&execution_layer::test_utils::mock_proof_engine_url(bn_idx)) + .expect("mock URL is valid"); + if let Some(el_config) = beacon_config.execution_layer.as_mut() { + el_config.proof_engine_endpoint = Some(mock_url); + } else { + beacon_config.execution_layer = Some(execution_layer::Config { + proof_engine_endpoint: Some(mock_url), + ..Default::default() + }); + } + } - // Pair the beacon node and execution node. - beacon_config.execution_layer = Some(execution_layer::Config { - execution_endpoint: Some(SensitiveUrl::parse(&execution_node.server.url()).unwrap()), - default_datadir: execution_node.datadir.path().to_path_buf(), - secret_file: Some(execution_node.datadir.path().join("jwt.hex")), - ..Default::default() - }); + if node_type.is_proof_verifier() { + beacon_config.chain.optimistic_finalized_sync = true; + } // Construct beacon node using the config, let beacon_node = LocalBeaconNode::production(self.context.clone(), beacon_config).await?; @@ -261,45 +354,59 @@ impl LocalNetwork { Ok((beacon_node, execution_node)) } + /// Returns the boot node's ENR once it has a valid (non-zero) TCP port, or an error if + /// the port isn't populated within 10 seconds. + async fn boot_node_enr(&self) -> Result, String> { + // If there are no beacon nodes yet, the network hasn't started — return None immediately. + if self.beacon_nodes.read().is_empty() { + return Ok(None); + } + + for _ in 0..100 { + if let Some(enr) = self + .beacon_nodes + .read() + .first() + .and_then(|bn| bn.client.enr()) + .filter(|e| e.tcp4().is_some_and(|p| p != 0) && e.udp4().is_some_and(|p| p != 0)) + { + return Ok(Some(enr)); + } + tokio::time::sleep(Duration::from_millis(100)).await; + } + Err("Boot node ENR did not get valid TCP and UDP ports within 10 seconds".to_string()) + } + /// Adds a beacon node to the network, connecting to the 0'th beacon node via ENR. pub async fn add_beacon_node( &self, mut beacon_config: ClientConfig, mock_execution_config: MockExecutionConfig, - is_proposer: bool, + node_type: NodeType, ) -> Result<(), String> { - let first_bn_exists: bool; - { - let read_lock = self.beacon_nodes.read(); - let boot_node = read_lock.first(); - first_bn_exists = boot_node.is_some(); - - if let Some(boot_node) = boot_node { - // Modify beacon_config to add boot node details. - beacon_config.network.boot_nodes_enr.push( - boot_node - .client - .enr() - .expect("Bootnode must have a network."), - ); - } - } - let (beacon_node, execution_node) = if first_bn_exists { - // Network already exists. We construct a new node. - self.construct_beacon_node(beacon_config, mock_execution_config, is_proposer) + let (beacon_node, execution_node) = if let Some(boot_node) = self.boot_node_enr().await? { + // Network already exists. The boot node ENR has a valid TCP port; use it to + // bootstrap the new node. + beacon_config.network.boot_nodes_enr.push(boot_node); + self.construct_beacon_node(beacon_config, mock_execution_config, node_type) .await? } else { // Network does not exist. We construct a boot node. - self.construct_boot_node(beacon_config, mock_execution_config) - .await? + let (bn, en) = self + .construct_boot_node(beacon_config, mock_execution_config) + .await?; + (bn, Some(en)) }; + // Add nodes to the network. - self.execution_nodes.write().push(execution_node); - if is_proposer { - self.proposer_nodes.write().push(beacon_node); - } else { - self.beacon_nodes.write().push(beacon_node); + if let Some(execution_node) = execution_node { + self.execution_nodes.write().push(execution_node); + } + match node_type { + NodeType::Proposer => self.proposer_nodes.write().push(beacon_node), + _ => self.beacon_nodes.write().push(beacon_node), } + Ok(()) } @@ -315,7 +422,7 @@ impl LocalNetwork { ) -> Result<(), String> { epoch_delay(Epoch::new(wait_until_epoch), slot_duration, slots_per_epoch).await; - self.add_beacon_node(beacon_config, mock_execution_config, false) + self.add_beacon_node(beacon_config, mock_execution_config, NodeType::Default) .await?; Ok(()) @@ -328,9 +435,10 @@ impl LocalNetwork { mut validator_config: ValidatorConfig, beacon_node: usize, validator_files: ValidatorFiles, + node_type: NodeType, ) -> Result<(), String> { + let beacon_node_idx = beacon_node; let context = self.context.clone(); - let self_1 = self.clone(); let socket_addr = { let read_lock = self.beacon_nodes.read(); let beacon_node = read_lock @@ -358,6 +466,27 @@ impl LocalNetwork { .unwrap(); validator_config.beacon_nodes = vec![beacon_node]; + if node_type.is_proof_generator() { + let token_path = tempdir().unwrap().path().join(PK_FILENAME); + validator_config.http_api = ValidatorHttpConfig { + enabled: true, + listen_addr: Ipv4Addr::LOCALHOST.into(), + listen_port: 0, // use random port + allow_origin: None, + allow_keystore_export: true, + store_passwords_in_secrets_dir: false, + http_token_path: token_path, + bn_long_timeouts: false, + }; + // Wire the VC's proof service to the same mock registered for this beacon node index. + validator_config.proof_engine_endpoint = Some( + SensitiveUrl::parse(&execution_layer::test_utils::mock_proof_engine_url( + beacon_node_idx, + )) + .expect("mock URL is valid"), + ); + } + // If we have a proposer node established, use it. if let Some(proposer_socket_addr) = proposer_socket_addr { let url = SensitiveUrl::parse( @@ -378,7 +507,8 @@ impl LocalNetwork { validator_files, ) .await?; - self_1.validator_clients.write().push(validator_client); + + self.validator_clients.write().push(validator_client); Ok(()) } @@ -389,7 +519,6 @@ impl LocalNetwork { validator_files: ValidatorFiles, ) -> Result<(), String> { let context = self.context.clone(); - let self_1 = self.clone(); let mut beacon_node_urls = vec![]; for beacon_node in beacon_nodes { let socket_addr = { @@ -417,10 +546,18 @@ impl LocalNetwork { validator_files, ) .await?; - self_1.validator_clients.write().push(validator_client); + self.validator_clients.write().push(validator_client); Ok(()) } + /// Return a HTTP client for the beacon node at `index` in the `beacon_nodes` vec. + pub fn remote_node(&self, index: usize) -> Option { + self.beacon_nodes + .read() + .get(index) + .and_then(|n| n.remote_node().ok()) + } + /// For all beacon nodes in `Self`, return a HTTP client to access each nodes HTTP API. pub fn remote_nodes(&self) -> Result, String> { let beacon_nodes = self.beacon_nodes.read(); @@ -444,6 +581,36 @@ impl LocalNetwork { .map(|body| body.unwrap().data.finalized.epoch) } + /// Subscribe to mock client events for a beacon node at a specific index. + pub fn node_subscribe_client_events( + &self, + index: usize, + ) -> Option> + { + self.beacon_nodes.read().get(index).and_then(|bn| { + bn.client + .beacon_chain() + .and_then(|chain| chain.execution_layer.as_ref().cloned()) + .and_then(|el| el.subscribe_proof_node_client_events()) + }) + } + + /// Subscribe to the internal event bus for a beacon node at a specific index. + /// + /// Returns `None` if the index is out of range or the beacon chain is unavailable. + pub fn node_subscribe_internal_events( + &self, + index: usize, + ) -> Option< + tokio::sync::broadcast::Receiver, + > { + self.beacon_nodes.read().get(index).and_then(|bn| { + bn.client + .beacon_chain() + .map(|chain| chain.subscribe_internal_events()) + }) + } + pub async fn duration_to_genesis(&self) -> Result { let nodes = self.remote_nodes().expect("Failed to get remote nodes"); let bootnode = nodes.first().expect("Should contain bootnode"); diff --git a/testing/simulator/src/main.rs b/testing/simulator/src/main.rs index 7bd6e546f75..075040cbd20 100644 --- a/testing/simulator/src/main.rs +++ b/testing/simulator/src/main.rs @@ -1,27 +1,7 @@ -//! This crate provides various simulations that create both beacon nodes and validator clients, -//! each with `v` validators. -//! -//! When a simulation runs, there are checks made to ensure that all components are operating -//! as expected. If any of these checks fail, the simulation will exit immediately. -//! -//! ## Future works -//! -//! Presently all the beacon nodes and validator clients all log to stdout. Additionally, the -//! simulation uses `println` to communicate some info. It might be nice if the nodes logged to -//! easy-to-find files and stdout only contained info from the simulation. -//! -mod basic_sim; -mod checks; -mod cli; -mod fallback_sim; -mod local_network; -mod retry; +use simulator::{basic_sim, fallback_sim}; +pub mod cli; use cli::cli_app; -use local_network::LocalNetwork; -use types::MinimalEthSpec; - -pub type E = MinimalEthSpec; fn main() { let matches = cli_app().get_matches(); diff --git a/testing/simulator/src/test_utils/builder.rs b/testing/simulator/src/test_utils/builder.rs new file mode 100644 index 00000000000..e2bbf467aac --- /dev/null +++ b/testing/simulator/src/test_utils/builder.rs @@ -0,0 +1,343 @@ +use crate::local_network::NodeType; + +use super::*; + +type ClientConfigTransform = Box; + +/// Builder for creating test networks with configurable parameters. +pub struct TestNetworkFixtureBuilder { + env: EnvironmentBuilder, + network_params: LocalNetworkParams, + logger_config: LoggerConfig, + disable_stdout: bool, + client_config_transform: Option, +} + +impl Default for TestNetworkFixtureBuilder { + fn default() -> Self { + Self { + env: EnvironmentBuilder::minimal(), + network_params: LocalNetworkParams { + validator_count: 4, + node_count: 2, + proposer_nodes: 0, + proof_generator_nodes: 0, + proof_verifier_nodes: 0, + extra_nodes: 0, + delayed_nodes: 0, + genesis_delay: 38, + }, + logger_config: LoggerConfig::default(), + disable_stdout: false, + client_config_transform: None, + } + } +} + +impl TestNetworkFixtureBuilder { + /// Set the `EnvironmentBuilder` to use for the network. + pub fn with_env(mut self, env: EnvironmentBuilder) -> Self { + self.env = env; + self + } + + /// Apply an arbitrary modification to the `EnvironmentBuilder` used for the network. + pub fn map_env(mut self, f: impl FnOnce(&mut EnvironmentBuilder)) -> Self { + f(&mut self.env); + self + } + + /// Apply an arbitrary modification to the `ChainSpec` used for the network. + pub fn map_spec(mut self, f: impl FnOnce(&mut ChainSpec)) -> Self { + self.env = self.env.map_spec(f); + self + } + + /// Set the log level. + pub fn with_log_level(mut self, level: LevelFilter) -> Self { + self.logger_config.debug_level = level; + self.logger_config.logfile_debug_level = level; + self + } + + /// Set the log directory. + pub fn with_log_dir(mut self, log_dir: PathBuf) -> Self { + self.logger_config.path = Some(log_dir); + self + } + + /// Apply an arbitrary modification to the `LoggerConfig` used for the network. + pub fn map_logger_config(mut self, f: impl FnOnce(&mut LoggerConfig)) -> Self { + f(&mut self.logger_config); + self + } + + /// Set the network params. + pub fn with_network_params(mut self, network_params: LocalNetworkParams) -> Self { + self.network_params = network_params; + self + } + + /// Apply an arbitrary modification to the `LocalNetworkParams` used for the network. + pub fn map_network_params(mut self, f: impl FnOnce(&mut LocalNetworkParams)) -> Self { + f(&mut self.network_params); + self + } + + /// Apply an arbitrary modification to the `ClientConfig` used for all beacon nodes. + /// + /// Multiple calls are composed in order: the first registered transform runs first. + pub fn map_client_config(mut self, f: impl FnOnce(&mut ClientConfig) + Send + 'static) -> Self { + self.client_config_transform = Some(match self.client_config_transform.take() { + None => Box::new(f), + Some(prev) => Box::new(move |config| { + prev(config); + f(config); + }), + }); + self + } + + /// Build the test network fixture with the specified configuration. + pub async fn build(self) -> anyhow::Result> { + info!(target: "simulator", "Building test network fixture"); + + // initialize the network + let (env, network_params, network, beacon_config, mock_execution_config) = + self.init_network().await?; + + // Initialize beacon nodes + Self::init_beacon_nodes( + &network, + &network_params, + &beacon_config, + &mock_execution_config, + ) + .await?; + + // Initialize validator clients + Self::init_validators(&network, &network_params).await?; + + Ok(TestNetworkFixture { + env, + network, + config: TestConfig { + client: beacon_config, + execution: mock_execution_config, + network_params, + }, + }) + } + + async fn init_validators( + network: &LocalNetwork, + network_params: &LocalNetworkParams, + ) -> anyhow::Result<()> { + info!(target: "simulator", "Building validator clients for {} validators", network_params.validator_count); + let network_params = network_params.clone(); + let task_executor = network.executor(); + + // Generate validator keystores in parallel to speed up setup time + let validator_files = task_executor + .spawn_blocking_handle( + move || -> anyhow::Result> { + let num_beacon_nodes = + network_params.node_count + network_params.proof_generator_nodes; + let validators_per_node = network_params.validator_count / num_beacon_nodes; + + (0..num_beacon_nodes) + .into_par_iter() + .map(|i| -> anyhow::Result { + info!(target: "simulator", + "Generating keystores for validator {} of {}", + i + 1, + num_beacon_nodes + ); + + let indices = (i * validators_per_node..(i + 1) * validators_per_node) + .collect::>(); + + ValidatorFiles::with_keystores(&indices).map_err(anyhow::Error::msg) + }) + .collect::>>() + }, + "validator_keystore_generation", + ) + .ok_or_else(|| anyhow::anyhow!("Failed to spawn blocking task"))? + .await??; + + for (i, files) in validator_files.into_iter().enumerate() { + let network = network.clone(); + let network_params = network_params.clone(); + + task_executor.spawn( + async move { + let mut validator_config = testing_validator_config(); + validator_config.validator_store.fee_recipient = + Some(Into::
::into(SUGGESTED_FEE_RECIPIENT)); + + // Enable broadcast on every 2nd node. + // TODO: do we need this? + if i % 4 == 0 { + validator_config.broadcast_topics = ApiTopic::all(); + let beacon_nodes = vec![i, (i + 1) % network_params.node_count]; + network + .add_validator_client_with_fallbacks( + validator_config, + beacon_nodes, + files, + ) + .await + } else { + let node_type = network_params.node_type(i); + network + .add_validator_client(validator_config, i, files, node_type) + .await + } + .expect("should add validator"); + }, + "validator_client_setup", + ) + } + + Ok(()) + } + + async fn init_beacon_nodes( + network: &LocalNetwork, + network_params: &LocalNetworkParams, + beacon_config: &ClientConfig, + mock_execution_config: &MockExecutionConfig, + ) -> anyhow::Result<()> { + // Build the full list of (NodeType, count) pairs, then spawn all nodes concurrently. + let node_types = [ + (NodeType::Default, network_params.node_count), + (NodeType::Proposer, network_params.proposer_nodes), + ( + NodeType::ProofGenerator, + network_params.proof_generator_nodes, + ), + (NodeType::ProofVerifier, network_params.proof_verifier_nodes), + ]; + + let total: usize = node_types.iter().map(|(_, n)| n).sum(); + info!(target: "simulator", "Spawning {total} beacon nodes in parallel"); + + let mut handles = Vec::with_capacity(total); + for (node_type, count) in node_types { + for _ in 0..count { + let net = network.clone(); + let config = beacon_config.clone(); + let mock_config = mock_execution_config.clone(); + let handle = network + .executor() + .spawn_handle( + async move { + net.add_beacon_node(config, mock_config, node_type) + .await + .map_err(anyhow::Error::msg) + .expect("should add beacon node"); + }, + "beacon_node_setup", + ) + .expect("Failed to spawn beacon node task"); + handles.push(handle); + } + } + + for handle in handles { + handle.await?; + } + + Ok(()) + } + + /// Initialize the network environment and create the local network instance. + async fn init_network( + self, + ) -> anyhow::Result<( + TestEnvironment, + LocalNetworkParams, + LocalNetwork, + ClientConfig, + MockExecutionConfig, + )> { + info!(target: "simulator", "Initializing test network environment and local network"); + let Self { + mut env, + network_params, + logger_config, + disable_stdout, + client_config_transform, + } = self; + + // Ensure the `ChainSpec` is configured with the correct genesis parameters based on the network params. + env = env.map_spec(|spec| { + spec.genesis_delay = network_params.genesis_delay; + spec.min_genesis_active_validator_count = network_params.validator_count as u64; + }); + + // Initialize logging + info!(target: "simulator", "Initializing logging with config: {:?}", logger_config); + + let file_mode = if logger_config.is_restricted { + 0o600 + } else { + 0o644 + }; + let (env, stdout_logging_layer, file_logging_layer, _see_logging_layer) = + env.init_tracing(logger_config.clone(), "lighthouse", file_mode); + + //TODO: optionally add discv5 logging layer for network tests + // Instantiate logging layers + let filters = build_workspace_filter().expect("should build workspace filter"); + let mut layers = vec![]; + + if let Some(layer) = (!disable_stdout).then(|| { + stdout_logging_layer + .with_filter(logger_config.debug_level) + .with_filter(filters.clone()) + .boxed() + }) { + layers.push(layer); + } + if let Some(file_logging_layer) = file_logging_layer { + layers.push( + file_logging_layer + .with_filter(logger_config.logfile_debug_level) + .with_filter(filters.clone()) + .boxed(), + ); + } + + // Initialize the subscriber with the configured layers + tracing_subscriber::registry().with(layers).try_init()?; + + // Instantiate the environment + let env = env.build_test_environment().map_err(anyhow::Error::msg)?; + + // Instantiate the local network + info!(target: "simulator", "Initializing local network with params: {:?}", network_params); + let (network, mut beacon_config, mock_execution_config) = + Box::pin(LocalNetwork::create_local_network( + None, + None, + network_params.clone(), + env.core_context(), + )) + .await + .map_err(anyhow::Error::msg)?; + + if let Some(transform) = client_config_transform { + transform(&mut beacon_config); + } + + Ok(( + env, + network_params, + network, + beacon_config, + mock_execution_config, + )) + } +} diff --git a/testing/simulator/src/test_utils/event_stream.rs b/testing/simulator/src/test_utils/event_stream.rs new file mode 100644 index 00000000000..ddd92835f2a --- /dev/null +++ b/testing/simulator/src/test_utils/event_stream.rs @@ -0,0 +1,56 @@ +//! Generic event stream wrapper for broadcast channels. +//! +//! [`EventStream`] wraps a `broadcast::Receiver` and provides ergonomic timeout-based +//! collection helpers used across simulator integration tests. + +use std::time::Duration; +use tokio::sync::broadcast; + +/// Wraps a `broadcast::Receiver` with assertion helpers. +pub struct EventStream { + rx: broadcast::Receiver, +} + +impl From> for EventStream { + fn from(rx: broadcast::Receiver) -> Self { + Self { rx } + } +} + +impl EventStream { + /// Collect `n` events matching `predicate` within `timeout`, or return an error. + pub async fn collect_n( + &mut self, + n: usize, + predicate: impl Fn(&E) -> bool, + timeout: Duration, + ) -> anyhow::Result> { + tokio::time::timeout(timeout, async { + let mut collected = Vec::with_capacity(n); + loop { + match self.rx.recv().await { + Ok(event) if predicate(&event) => { + collected.push(event); + if collected.len() >= n { + return Ok(collected); + } + } + Ok(_) => {} + Err(broadcast::error::RecvError::Lagged(skipped)) => { + return Err(anyhow::anyhow!( + "event stream lagged, skipped {skipped} events" + )); + } + Err(broadcast::error::RecvError::Closed) => { + return Err(anyhow::anyhow!( + "event stream closed before collecting {n} events (got {})", + collected.len() + )); + } + } + } + }) + .await + .map_err(|_| anyhow::anyhow!("timed out after {timeout:?} waiting for {n} events"))? + } +} diff --git a/testing/simulator/src/test_utils/mod.rs b/testing/simulator/src/test_utils/mod.rs new file mode 100644 index 00000000000..a54e249a363 --- /dev/null +++ b/testing/simulator/src/test_utils/mod.rs @@ -0,0 +1,98 @@ +//! Test network builder for creating local beacon node networks. +//! +//! Provides a builder pattern for setting up test networks with beacon nodes, +//! validator clients, and execution nodes. Used by simulator tests like +//! `basic_sim` and `proof_service_sim`. + +pub use crate::basic_sim::SUGGESTED_FEE_RECIPIENT; +pub use crate::local_network::{LocalNetwork, LocalNetworkParams, NodeType}; +pub use beacon_chain::internal_events::InternalBeaconNodeEvent; +pub use environment::LoggerConfig; +pub use environment::test_utils::TestEnvironment; +pub use eth2::{BeaconNodeHttpClient, types::StateId}; +pub use execution_layer::test_utils::{MockClientEvent, MockEventStream}; +mod event_stream; +pub use event_stream::EventStream; +pub use logging::build_workspace_filter; +pub use node_test_rig::ApiTopic; +pub use node_test_rig::{ + ClientConfig, MockExecutionConfig, ValidatorFiles, environment::EnvironmentBuilder, + testing_validator_config, +}; +use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use std::path::PathBuf; +pub use tracing::{info, level_filters::LevelFilter}; +use tracing_subscriber::{Layer, layer::SubscriberExt, util::SubscriberInitExt}; +pub use types::{Address, ChainSpec, Epoch, EthSpec, MinimalEthSpec}; + +mod builder; +pub use builder::TestNetworkFixtureBuilder; + +pub struct TestNetworkFixture { + pub env: TestEnvironment, + pub network: LocalNetwork, + pub config: TestConfig, +} + +pub struct TestConfig { + pub client: ClientConfig, + pub execution: MockExecutionConfig, + pub network_params: LocalNetworkParams, +} + +impl TestNetworkFixture { + pub fn builder() -> TestNetworkFixtureBuilder { + TestNetworkFixtureBuilder::default() + } + + /// Mark all payloads as valid on execution nodes. + pub fn payloads_valid(&mut self) { + self.network + .execution_nodes + .write() + .iter() + .for_each(|node| { + node.server.all_payloads_valid(); + }); + } + + /// Wait for the network to reach genesis by sleeping until the genesis time. + /// If genesis has already passed (late-joining node), returns immediately. + pub async fn wait_for_genesis(&self) -> anyhow::Result<()> { + if let Ok(duration) = self.network.duration_to_genesis().await { + tokio::time::sleep(duration).await; + } + Ok(()) + } +} + +// Ignore this for now because it conflicts with the `proof_engine` testing crate. +// We should migrate to defaulting to unused ports assigned by the OS instead of hardcoding ports. +#[tokio::test] +#[ignore] +async fn test_network_fixture_build() -> anyhow::Result<()> { + let mut fixture = TestNetworkFixtureBuilder::default() + .map_network_params(|params| { + params.genesis_delay = 20; + }) + .map_spec(|spec| { + spec.seconds_per_slot = 1; + spec.slot_duration_ms = 1000; + spec.min_genesis_time = 0; + spec.altair_fork_epoch = Some(Epoch::new(0)); + spec.bellatrix_fork_epoch = Some(Epoch::new(0)); + spec.capella_fork_epoch = Some(Epoch::new(0)); + spec.deneb_fork_epoch = Some(Epoch::new(0)); + spec.electra_fork_epoch = Some(Epoch::new(0)); + spec.fulu_fork_epoch = Some(Epoch::new(2)); + }) + .build() + .await?; + fixture.payloads_valid(); + + fixture.wait_for_genesis().await?; + + tokio::time::sleep(std::time::Duration::from_secs(60)).await; + + Ok(()) +} diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index 6990a2f61a7..65f5fb608c0 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -17,7 +17,8 @@ directory = { workspace = true } dirs = { workspace = true } doppelganger_service = { workspace = true } environment = { workspace = true } -eth2 = { workspace = true } +eth2 = { workspace = true, features = ["events"] } +execution_layer = { workspace = true } fdlimit = "0.3.0" graffiti_file = { workspace = true } hyper = { workspace = true } diff --git a/validator_client/http_api/src/lib.rs b/validator_client/http_api/src/lib.rs index a35b4ec6c6d..2bf910cd1b8 100644 --- a/validator_client/http_api/src/lib.rs +++ b/validator_client/http_api/src/lib.rs @@ -32,7 +32,7 @@ use eth2::lighthouse_vc::{ std_types::{AuthResponse, GetFeeRecipientResponse, GetGasLimitResponse}, types::{ self as api_types, GenericResponse, GetGraffitiResponse, Graffiti, SetGraffitiRequest, - UpdateCandidatesRequest, UpdateCandidatesResponse, + SignExecutionProofRequest, UpdateCandidatesRequest, UpdateCandidatesResponse, }, }; use health_metrics::observe::Observe; @@ -56,6 +56,7 @@ use tracing::{info, warn}; use types::{ChainSpec, ConfigAndPreset, EthSpec}; use validator_dir::Builder as ValidatorDirBuilder; use validator_services::block_service::BlockService; +use validator_services::proof_service::ProofService; use warp::{Filter, reply::Response, sse::Event}; use warp_utils::reject::convert_rejection; use warp_utils::task::blocking_json_task; @@ -81,7 +82,7 @@ impl From for Error { /// A wrapper around all the items required to spawn the HTTP server. /// /// The server will gracefully handle the case where any fields are `None`. -pub struct Context { +pub struct Context { pub task_executor: TaskExecutor, pub api_secret: ApiSecret, pub block_service: Option, T>>, @@ -94,6 +95,7 @@ pub struct Context { pub config: Config, pub sse_logging_components: Option, pub slot_clock: T, + pub proof_service: Option, T>>>, } /// Configuration for the HTTP server. @@ -249,6 +251,19 @@ pub fn serve( let inner_spec = ctx.spec.clone(); let spec_filter = warp::any().map(move || inner_spec.clone()); + let inner_proof_service = ctx.proof_service.clone(); + let proof_service_filter = warp::any() + .map(move || inner_proof_service.clone()) + .and_then( + |service: Option, T>>>| async move { + service.ok_or_else(|| { + warp_utils::reject::custom_not_found( + "proof service is not initialized.".to_string(), + ) + }) + }, + ); + let api_token_path_inner = api_token_path.clone(); let api_token_path_filter = warp::any().map(move || api_token_path_inner.clone()); @@ -513,7 +528,7 @@ pub fn serve( .and(validator_dir_filter.clone()) .and(secrets_dir_filter.clone()) .and(validator_store_filter.clone()) - .and(spec_filter) + .and(spec_filter.clone()) .and(task_executor_filter.clone()) .then( move |body: api_types::CreateValidatorsMnemonicRequest, @@ -1130,7 +1145,7 @@ pub fn serve( .and(warp::query::()) .and(warp::path::end()) .and(validator_store_filter.clone()) - .and(slot_clock_filter) + .and(slot_clock_filter.clone()) .and(task_executor_filter.clone()) .then( |pubkey: PublicKey, @@ -1157,6 +1172,38 @@ pub fn serve( }, ); + let post_execution_proofs = warp::path("lighthouse") + .and(warp::path("validators")) + .and(warp::path::param::()) + .and(warp::path("execution_proofs")) + .and(warp::path::end()) + .and(warp::body::json()) + .and(proof_service_filter.clone()) + .and(task_executor_filter.clone()) + .then( + |pubkey: PublicKey, + request: SignExecutionProofRequest, + proof_service: Arc, T>>, + task_executor: TaskExecutor| { + blocking_json_task(move || { + if let Some(handle) = task_executor.handle() { + handle + .block_on(proof_service.handle_proof_request( + pubkey, + request.execution_proof, + request.epoch, + )) + .map_err(warp_utils::reject::custom_server_error) + } else { + Err(warp_utils::reject::custom_server_error( + "Lighthouse shutting down".into(), + )) + } + }) + }, + ) + .map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::ACCEPTED)); + // GET /eth/v1/validator/{pubkey}/graffiti let get_graffiti = eth_v1 .and(warp::path("validator")) @@ -1377,6 +1424,7 @@ pub fn serve( .or(post_std_remotekeys) .or(post_graffiti) .or(post_lighthouse_beacon_update) + .or(post_execution_proofs) .recover(warp_utils::reject::handle_rejection), )) .or(warp::patch() diff --git a/validator_client/http_api/src/test_utils.rs b/validator_client/http_api/src/test_utils.rs index f83d9f4d526..42e6b00ca90 100644 --- a/validator_client/http_api/src/test_utils.rs +++ b/validator_client/http_api/src/test_utils.rs @@ -142,6 +142,7 @@ impl ApiTester { config: http_config, sse_logging_components: None, slot_clock, + proof_service: None, }); let ctx = context; let (shutdown_tx, shutdown_rx) = oneshot::channel(); diff --git a/validator_client/http_api/src/tests.rs b/validator_client/http_api/src/tests.rs index 5cb631983cc..404fc6c0f81 100644 --- a/validator_client/http_api/src/tests.rs +++ b/validator_client/http_api/src/tests.rs @@ -132,6 +132,7 @@ impl ApiTester { }, sse_logging_components: None, slot_clock: slot_clock.clone(), + proof_service: None, }); let ctx = context.clone(); let (listening_socket, server) = diff --git a/validator_client/initialized_validators/Cargo.toml b/validator_client/initialized_validators/Cargo.toml index 8b2ae62aea3..53191ffe1ee 100644 --- a/validator_client/initialized_validators/Cargo.toml +++ b/validator_client/initialized_validators/Cargo.toml @@ -12,7 +12,9 @@ eth2_keystore = { workspace = true } filesystem = { workspace = true } lockfile = { workspace = true } metrics = { workspace = true } +p12-keystore = "0.2" parking_lot = { workspace = true } +pem = "3" rand = { workspace = true } reqwest = { workspace = true } serde = { workspace = true } diff --git a/validator_client/initialized_validators/src/lib.rs b/validator_client/initialized_validators/src/lib.rs index db6d03174dd..8928e4f5084 100644 --- a/validator_client/initialized_validators/src/lib.rs +++ b/validator_client/initialized_validators/src/lib.rs @@ -397,6 +397,7 @@ pub fn load_pem_certificate>(pem_path: P) -> Result>( pkcs12_path: P, password: &str, @@ -406,7 +407,29 @@ pub fn load_pkcs12_identity>( .map_err(Error::InvalidWeb3SignerClientIdentityCertificateFile)? .read_to_end(&mut buf) .map_err(Error::InvalidWeb3SignerClientIdentityCertificateFile)?; - Identity::from_pkcs12_der(&buf, password) + + let keystore = p12_keystore::KeyStore::from_pkcs12(&buf, password).map_err(|e| { + Error::InvalidWeb3SignerClientIdentityCertificateFile(io::Error::new( + io::ErrorKind::InvalidData, + format!("PKCS12 parse error: {e:?}"), + )) + })?; + + let (_alias, key_chain) = keystore + .private_key_chain() + .ok_or(Error::MissingWeb3SignerClientIdentityCertificateFile)?; + + let key_pem = pem::encode(&pem::Pem::new("PRIVATE KEY", key_chain.key())); + let certs_pem: String = key_chain + .chain() + .iter() + .map(|cert| pem::encode(&pem::Pem::new("CERTIFICATE", cert.as_der()))) + .collect::>() + .join("\n"); + + let combined_pem = format!("{key_pem}\n{certs_pem}"); + + Identity::from_pem(combined_pem.as_bytes()) .map_err(Error::InvalidWeb3SignerClientIdentityCertificate) } diff --git a/validator_client/lighthouse_validator_store/src/lib.rs b/validator_client/lighthouse_validator_store/src/lib.rs index 3bea21a05d8..ebd68a9747a 100644 --- a/validator_client/lighthouse_validator_store/src/lib.rs +++ b/validator_client/lighthouse_validator_store/src/lib.rs @@ -1,5 +1,5 @@ use account_utils::validator_definitions::{PasswordStorage, ValidatorDefinition}; -use bls::{PublicKeyBytes, Signature}; +use bls::{PublicKeyBytes, Signature, SignatureBytes}; use doppelganger_service::DoppelgangerService; use eth2::types::PublishBlockRequest; use initialized_validators::InitializedValidators; @@ -19,12 +19,12 @@ use task_executor::TaskExecutor; use tracing::{error, info, instrument, warn}; use types::{ AbstractExecPayload, Address, AggregateAndProof, Attestation, BeaconBlock, BlindedPayload, - ChainSpec, ContributionAndProof, Domain, Epoch, EthSpec, Fork, Graffiti, Hash256, - SelectionProof, SignedAggregateAndProof, SignedBeaconBlock, SignedContributionAndProof, - SignedRoot, SignedValidatorRegistrationData, SignedVoluntaryExit, Slot, - SyncAggregatorSelectionData, SyncCommitteeContribution, SyncCommitteeMessage, - SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData, VoluntaryExit, - graffiti::GraffitiString, + ChainSpec, ContributionAndProof, Domain, Epoch, EthSpec, ExecutionProof, Fork, Graffiti, + Hash256, SelectionProof, SignedAggregateAndProof, SignedBeaconBlock, + SignedContributionAndProof, SignedExecutionProof, SignedRoot, SignedValidatorRegistrationData, + SignedVoluntaryExit, Slot, SyncAggregatorSelectionData, SyncCommitteeContribution, + SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData, + VoluntaryExit, graffiti::GraffitiString, }; use validator_store::{ DoppelgangerStatus, Error as ValidatorStoreError, ProposalData, SignedBlock, UnsignedBlock, @@ -1055,6 +1055,43 @@ impl ValidatorStore for LighthouseValidatorS Ok(SignedContributionAndProof { message, signature }) } + async fn sign_execution_proof( + &self, + validator_pubkey: PublicKeyBytes, + execution_proof: ExecutionProof, + signing_epoch: Epoch, + ) -> Result { + let signing_context = self.signing_context(Domain::ExecutionProof, signing_epoch); + let signing_method = self.doppelganger_bypassed_signing_method(validator_pubkey)?; + + let signature = signing_method + .get_signature::>( + SignableMessage::ExecutionProof(&execution_proof), + signing_context, + &self.spec, + &self.task_executor, + ) + .await?; + + let validator_index = self + .validator_index(&validator_pubkey) + .ok_or(Error::UnknownPubkey(validator_pubkey))?; + + let signature_bytes = SignatureBytes::deserialize(&signature.serialize()) + .map_err(|_| Error::Middleware("Failed to serialize signature".to_string()))?; + + validator_metrics::inc_counter_vec( + &validator_metrics::SIGNED_EXECUTION_PROOFS_TOTAL, + &[validator_metrics::SUCCESS], + ); + + Ok(SignedExecutionProof { + message: execution_proof, + validator_index, + signature: signature_bytes, + }) + } + /// Prune the slashing protection database so that it remains performant. /// /// This function will only do actual pruning periodically, so it should usually be diff --git a/validator_client/signing_method/src/lib.rs b/validator_client/signing_method/src/lib.rs index d0d98689526..4a19722263f 100644 --- a/validator_client/signing_method/src/lib.rs +++ b/validator_client/signing_method/src/lib.rs @@ -49,6 +49,8 @@ pub enum SignableMessage<'a, E: EthSpec, Payload: AbstractExecPayload = FullP SignedContributionAndProof(&'a ContributionAndProof), ValidatorRegistration(&'a ValidatorRegistrationData), VoluntaryExit(&'a VoluntaryExit), + /// EIP-8025: Execution proof for optional verification + ExecutionProof(&'a ExecutionProof), } impl> SignableMessage<'_, E, Payload> { @@ -70,6 +72,7 @@ impl> SignableMessage<'_, E, Payload SignableMessage::SignedContributionAndProof(c) => c.signing_root(domain), SignableMessage::ValidatorRegistration(v) => v.signing_root(domain), SignableMessage::VoluntaryExit(exit) => exit.signing_root(domain), + SignableMessage::ExecutionProof(proof) => proof.signing_root(domain), } } } @@ -231,6 +234,7 @@ impl SigningMethod { Web3SignerObject::ValidatorRegistration(v) } SignableMessage::VoluntaryExit(e) => Web3SignerObject::VoluntaryExit(e), + SignableMessage::ExecutionProof(p) => Web3SignerObject::ExecutionProof(p), }; // Determine the Web3Signer message type. diff --git a/validator_client/signing_method/src/web3signer.rs b/validator_client/signing_method/src/web3signer.rs index 246d9e9e091..0a3823e4c5d 100644 --- a/validator_client/signing_method/src/web3signer.rs +++ b/validator_client/signing_method/src/web3signer.rs @@ -19,6 +19,8 @@ pub enum MessageType { SyncCommitteeSelectionProof, SyncCommitteeContributionAndProof, ValidatorRegistration, + /// EIP-8025: Execution proof signing + ExecutionProof, } #[derive(Debug, PartialEq, Copy, Clone, Serialize)] @@ -75,6 +77,8 @@ pub enum Web3SignerObject<'a, E: EthSpec, Payload: AbstractExecPayload> { SyncAggregatorSelectionData(&'a SyncAggregatorSelectionData), ContributionAndProof(&'a ContributionAndProof), ValidatorRegistration(&'a ValidatorRegistrationData), + /// EIP-8025: Execution proof for optional verification + ExecutionProof(&'a ExecutionProof), } impl<'a, E: EthSpec, Payload: AbstractExecPayload> Web3SignerObject<'a, E, Payload> { @@ -140,6 +144,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> Web3SignerObject<'a, E, Pa MessageType::SyncCommitteeContributionAndProof } Web3SignerObject::ValidatorRegistration(_) => MessageType::ValidatorRegistration, + Web3SignerObject::ExecutionProof(_) => MessageType::ExecutionProof, } } } diff --git a/validator_client/slashing_protection/Cargo.toml b/validator_client/slashing_protection/Cargo.toml index b80da6c7867..08460547535 100644 --- a/validator_client/slashing_protection/Cargo.toml +++ b/validator_client/slashing_protection/Cargo.toml @@ -17,7 +17,7 @@ ethereum_serde_utils = { workspace = true } filesystem = { workspace = true } fixed_bytes = { workspace = true } r2d2 = { workspace = true } -r2d2_sqlite = "0.21.0" +r2d2_sqlite = "0.32" rusqlite = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/validator_client/src/cli.rs b/validator_client/src/cli.rs index 3e1c46097f0..c3569595f55 100644 --- a/validator_client/src/cli.rs +++ b/validator_client/src/cli.rs @@ -508,4 +508,26 @@ pub struct ValidatorClient { display_order = 0 )] pub web3_signer_max_idle_connections: Option, + + #[clap( + long, + value_name = "HTTP-JSON-RPC-URL", + help = "URL of the proof engine HTTP JSON-RPC endpoint for EIP-8025 execution proofs. \ + When set, the validator client will proactively monitor for new blocks and \ + request execution proofs from this endpoint.", + display_order = 0 + )] + pub proof_engine_endpoint: Option, + + #[clap( + long, + value_name = "TYPES", + value_delimiter = ',', + requires = "proof_engine_endpoint", + help = "Comma-separated list of proof type identifiers (u8) to request from the proof engine \ + (e.g., 0,1,2). If not specified, defaults to '0,1,2,3' \ + (EthrexRisc0, EthrexSP1, EthrexZisk, RethOpenVM).", + display_order = 0 + )] + pub proof_types: Option>, } diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index 1a286a74dc1..95b01ea10bc 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -8,6 +8,7 @@ use directory::{ get_network_dir, }; use eth2::types::{Graffiti, GraffitiPolicy}; +use execution_layer::eip8025::types::ProofTypes; use graffiti_file::GraffitiFile; use initialized_validators::Config as InitializedValidatorsConfig; use lighthouse_validator_store::Config as ValidatorStoreConfig; @@ -90,6 +91,12 @@ pub struct Config { #[serde(flatten)] pub initialized_validators: InitializedValidatorsConfig, pub disable_attesting: bool, + /// URL of the proof engine HTTP JSON-RPC endpoint for EIP-8025 execution proofs + pub proof_engine_endpoint: Option, + /// Proof types to request from the proof engine. Defaults to + /// `[EthrexRisc0, EthrexSP1, EthrexZisk, RethOpenVM]`. + #[serde(default)] + pub proof_types: ProofTypes, } impl Default for Config { @@ -136,6 +143,8 @@ impl Default for Config { distributed: false, initialized_validators: <_>::default(), disable_attesting: false, + proof_engine_endpoint: None, + proof_types: ProofTypes::default(), } } } @@ -281,6 +290,30 @@ impl Config { .web3_signer_max_idle_connections = Some(n); } + /* + * Proof Engine (EIP-8025) + */ + if let Some(proof_engine_endpoint) = validator_client_config.proof_engine_endpoint.as_ref() + { + config.proof_engine_endpoint = Some( + SensitiveUrl::parse(proof_engine_endpoint) + .map_err(|e| format!("Unable to parse proof engine URL: {:?}", e))?, + ); + } + + config.proof_types = if let Some(vals) = &validator_client_config.proof_types { + use execution_layer::eip8025::types::ProofType; + let types = vals + .iter() + .copied() + .map(ProofType::from_u8) + .collect::, _>>() + .map_err(|e| format!("Invalid --proof-types value: {e:?}"))?; + ProofTypes::from(types) + } else { + ProofTypes::default() + }; + /* * Http API server */ diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index b3cd3425f3d..e62254cad2a 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -36,7 +36,7 @@ use tokio::{ }; use tracing::{debug, error, info, warn}; use types::{EthSpec, Hash256}; -use validator_http_api::ApiSecret; +pub use validator_http_api::ApiSecret; use validator_services::notifier_service::spawn_notifier; use validator_services::{ attestation_service::{AttestationService, AttestationServiceBuilder}, @@ -44,6 +44,7 @@ use validator_services::{ duties_service::{self, DutiesService, DutiesServiceBuilder}, latency_service, preparation_service::{PreparationService, PreparationServiceBuilder}, + proof_service::ProofService, sync_committee_service::SyncCommitteeService, }; use validator_store::ValidatorStore as ValidatorStoreTrait; @@ -86,6 +87,7 @@ pub struct ProductionValidatorClient { http_api_listen_addr: Option, config: Config, genesis_time: u64, + proof_service: Option, SystemTimeSlotClock>>>, } impl ProductionValidatorClient { @@ -532,6 +534,36 @@ impl ProductionValidatorClient { context.executor.clone(), ); + // Create proof service (EIP-8025) if proof engine endpoint is configured + let proof_service = config.proof_engine_endpoint.as_ref().map(|endpoint| { + info!(endpoint = %endpoint, "Initializing proof engine client"); + let url_str = endpoint.expose_full(); + let proof_engine_client = Arc::new( + if let Some(idx) = execution_layer::test_utils::parse_mock_index(url_str.as_str()) { + let mock = execution_layer::test_utils::get_mock_proof_engine::(idx) + .unwrap_or_else(|| { + debug!( + idx, + "No pre-registered mock; creating MockProofNodeClient on the fly" + ); + execution_layer::test_utils::register_mock_proof_engine::(idx, 0) + }); + execution_layer::eip8025::HttpProofEngine::with_proof_node(mock) + } else { + execution_layer::eip8025::HttpProofEngine::new(endpoint.clone(), None) + }, + ); + + Arc::new(ProofService::new( + validator_store.clone(), + beacon_nodes.clone(), + proof_engine_client, + slot_clock.clone(), + context.executor.clone(), + config.proof_types.clone(), + )) + }); + Ok(Self { context, duties_service, @@ -545,6 +577,7 @@ impl ProductionValidatorClient { slot_clock, http_api_listen_addr: None, genesis_time, + proof_service, }) } @@ -571,6 +604,7 @@ impl ProductionValidatorClient { config: self.config.http_api.clone(), sse_logging_components: self.context.sse_logging_components.clone(), slot_clock: self.slot_clock.clone(), + proof_service: self.proof_service.clone(), }); let exit = self.context.executor.exit(); @@ -627,6 +661,14 @@ impl ProductionValidatorClient { info!("Doppelganger protection disabled.") } + // Start proof service (EIP-8025) if configured + if let Some(proof_service) = &self.proof_service { + proof_service + .clone() + .start_service() + .map_err(|e| format!("Unable to start proof service: {}", e))?; + } + let context = self.context.clone(); spawn_notifier( self.duties_service.clone(), @@ -645,6 +687,16 @@ impl ProductionValidatorClient { Ok(()) } + + /// Returns the listen address of the HTTP API, if enabled. + pub fn listen_addr(&self) -> Option { + self.http_api_listen_addr + } + + /// Returns a reference to the validator client config. + pub fn config(&self) -> &Config { + &self.config + } } async fn init_from_beacon_node( diff --git a/validator_client/validator_metrics/src/lib.rs b/validator_client/validator_metrics/src/lib.rs index 060d8a4edd2..e7e286b91de 100644 --- a/validator_client/validator_metrics/src/lib.rs +++ b/validator_client/validator_metrics/src/lib.rs @@ -121,6 +121,14 @@ pub static SIGNED_VALIDATOR_REGISTRATIONS_TOTAL: LazyLock> &["status"], ) }); +/// EIP-8025: Execution proof signing metric +pub static SIGNED_EXECUTION_PROOFS_TOTAL: LazyLock> = LazyLock::new(|| { + try_create_int_counter_vec( + "vc_signed_execution_proofs_total", + "Total count of ExecutionProof signings", + &["status"], + ) +}); pub static DUTIES_SERVICE_TIMES: LazyLock> = LazyLock::new(|| { try_create_histogram_vec( "vc_duties_service_task_times_seconds", diff --git a/validator_client/validator_services/Cargo.toml b/validator_client/validator_services/Cargo.toml index c9149409148..59eeb058569 100644 --- a/validator_client/validator_services/Cargo.toml +++ b/validator_client/validator_services/Cargo.toml @@ -8,13 +8,16 @@ authors = ["Sigma Prime "] beacon_node_fallback = { workspace = true } bls = { workspace = true } either = { workspace = true } -eth2 = { workspace = true } +eth2 = { workspace = true, features = ["events"] } +execution_layer = { workspace = true } futures = { workspace = true } graffiti_file = { workspace = true } logging = { workspace = true } parking_lot = { workspace = true } safe_arith = { workspace = true } +serde_json = { workspace = true } slot_clock = { workspace = true } +ssz_types = { workspace = true } task_executor = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } diff --git a/validator_client/validator_services/src/lib.rs b/validator_client/validator_services/src/lib.rs index 3b8bd9ae14b..40bc1af7287 100644 --- a/validator_client/validator_services/src/lib.rs +++ b/validator_client/validator_services/src/lib.rs @@ -4,5 +4,6 @@ pub mod duties_service; pub mod latency_service; pub mod notifier_service; pub mod preparation_service; +pub mod proof_service; pub mod sync; pub mod sync_committee_service; diff --git a/validator_client/validator_services/src/proof_service.rs b/validator_client/validator_services/src/proof_service.rs new file mode 100644 index 00000000000..fbb5a723178 --- /dev/null +++ b/validator_client/validator_services/src/proof_service.rs @@ -0,0 +1,539 @@ +//! EIP-8025 Execution Proof Service +//! +//! This service handles execution proof requests, signing and resigning workflows. +//! +//! Three concurrent tasks: +//! 1. **Beacon event monitor**: subscribes to beacon node SSE for new blocks and +//! validated-proof events — requests proofs and resigns validated proofs. +//! 2. **Proof engine event monitor**: subscribes to proof engine SSE for proof +//! completion/failure events — fetches completed proofs, signs them, and +//! submits to the beacon node. +//! 3. **Reactive HTTP handler**: receives proofs from proof engine callbacks. + +use beacon_node_fallback::BeaconNodeFallback; +use bls::PublicKey; +use eth2::types::{BlockId, EventKind, EventTopic, SseExecutionProofValidated}; +use execution_layer::NewPayloadRequest; +use execution_layer::eip8025::types::ProofTypes; +use execution_layer::eip8025::{HttpProofEngine, ProofEvent}; +use futures::StreamExt; +use parking_lot::RwLock; +use slot_clock::SlotClock; +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; +use std::time::{Duration, Instant}; +use task_executor::TaskExecutor; +use tracing::{debug, error, info, warn}; +use types::execution::eip8025::{ProofAttributes, ProofData, PublicInput}; +use types::{Epoch, EthSpec, ExecutionProof, Hash256}; +use validator_store::{DoppelgangerStatus, ValidatorStore}; + +/// Discard tracking entries older than this. +const PROOF_REQUEST_STALE_TIMEOUT: Duration = Duration::from_secs(300); + +/// An outstanding proof request awaiting completion from the proof engine. +struct OutstandingProofRequest { + /// Proof types we are still waiting for. + pending_proof_types: HashSet, + /// Slot of the block (for epoch derivation during signing). + slot: types::Slot, + /// When the request was made. + requested_at: Instant, +} + +/// Background service for execution proof handling +pub struct ProofService { + inner: Arc>, +} + +struct Inner { + validator_store: Arc, + beacon_nodes: Arc>, + proof_engine: Arc, + slot_clock: T, + executor: TaskExecutor, + proof_types: Vec, + /// Outstanding proof requests keyed by `new_payload_request_root`. + outstanding_requests: RwLock>, +} + +impl ProofService { + /// Create a new proof service + pub fn new( + validator_store: Arc, + beacon_nodes: Arc>, + proof_engine: Arc, + slot_clock: T, + executor: TaskExecutor, + proof_types: ProofTypes, + ) -> Self { + let proof_types: Vec = proof_types.iter().map(|t| t.to_u8()).collect(); + + Self { + inner: Arc::new(Inner { + validator_store, + beacon_nodes, + proof_engine, + slot_clock, + executor, + proof_types, + outstanding_requests: RwLock::new(HashMap::new()), + }), + } + } + + /// Start the proof service background tasks + pub fn start_service(self: Arc) -> Result<(), String> { + let inner = self.inner.clone(); + self.inner.executor.spawn( + async move { inner.monitor_events_task().await }, + "proof_service_monitor", + ); + + let inner = self.inner.clone(); + self.inner.executor.spawn( + async move { inner.monitor_proof_engine_events_task().await }, + "proof_service_proof_engine_monitor", + ); + + info!("Proof service started - monitoring beacon events and proof engine events"); + Ok(()) + } + + /// Public method called by HTTP API when proof engine callbacks with unsigned proof + /// + /// This is the reactive endpoint that receives proofs from the proof engine + /// and signs them with validator keys before submitting to beacon nodes. + pub async fn handle_proof_request( + &self, + pubkey: PublicKey, + execution_proof: ExecutionProof, + epoch: Option, + ) -> Result<(), String> { + self.inner + .sign_and_submit_proof(pubkey, execution_proof, epoch) + .await + } +} + +impl Inner { + // ─── Beacon node event monitoring (existing) ──────────────────────── + + /// Subscribe to both `Block` and `ExecutionProofValidated` events via a single SSE stream. + async fn subscribe_to_events( + &self, + ) -> Result< + impl futures::Stream, eth2::Error>>, + String, + > { + self.beacon_nodes + .first_success(|node| async move { + node.get_events::(&[EventTopic::Block, EventTopic::ExecutionProofValidated]) + .await + }) + .await + .map_err(|e| format!("All beacon nodes failed to provide event stream: {}", e)) + } + + /// Monitor block and validated-proof events over a single SSE connection. + async fn monitor_events_task(self: Arc) { + info!("Starting proof service event monitoring via SSE"); + + loop { + match self.subscribe_to_events().await { + Ok(mut stream) => { + info!("Successfully subscribed to block and execution proof events"); + + while let Some(event_result) = stream.next().await { + match event_result { + Ok(EventKind::Block(sse_block)) => { + if sse_block.execution_optimistic { + debug!( + slot = sse_block.slot.as_u64(), + "Skipping execution optimistic block" + ); + continue; + } + self.handle_block_event(sse_block.block, sse_block.slot) + .await; + } + Ok(EventKind::ExecutionProofValidated(proof_event)) => { + self.handle_validated_proof(proof_event).await; + } + Ok(_) => {} + Err(e) => { + warn!(error = %e, "Error receiving event, will reconnect"); + break; + } + } + } + + warn!("Event stream ended, reconnecting..."); + } + Err(e) => { + error!(error = %e, "Failed to subscribe to events, retrying..."); + } + } + + tokio::time::sleep(Duration::from_secs(2)).await; + } + } + + /// Handle a new block event by fetching the full block via RPC then requesting proofs. + async fn handle_block_event(&self, block_root: Hash256, slot: types::Slot) { + info!( + slot = slot.as_u64(), + block = %block_root, + "New block detected, fetching full block via RPC" + ); + + let signed_block = match self + .beacon_nodes + .first_success(|node| async move { + node.get_beacon_blocks::(BlockId::Root(block_root)) + .await + }) + .await + { + Ok(Some(response)) => response.data().clone(), + Ok(None) => { + warn!(block = %block_root, "Block not found on beacon node"); + return; + } + Err(e) => { + error!(block = %block_root, error = %e, "Failed to fetch block via RPC"); + return; + } + }; + + let new_payload_request = match NewPayloadRequest::try_from(signed_block.message()) { + Ok(req) => req, + Err(e) => { + error!(block = %block_root, error = ?e, "Failed to construct NewPayloadRequest"); + return; + } + }; + + let proof_attributes = ProofAttributes { + proof_types: self.proof_types.clone(), + }; + + match self + .proof_engine + .request_proofs(new_payload_request, proof_attributes) + .await + { + Ok(new_payload_request_root) => { + let pending_proof_types: HashSet = self.proof_types.iter().copied().collect(); + let num_types = pending_proof_types.len(); + self.outstanding_requests.write().insert( + new_payload_request_root, + OutstandingProofRequest { + pending_proof_types, + slot, + requested_at: Instant::now(), + }, + ); + debug!( + root = %new_payload_request_root, + block = %block_root, + num_proof_types = num_types, + "Proof generation requested, tracking for completion" + ); + } + Err(e) => { + error!(block = %block_root, error = ?e, "Failed to request proofs from proof engine"); + } + } + } + + /// Handle a validated proof event by resigning with the first local validator key. + async fn handle_validated_proof(&self, event: SseExecutionProofValidated) { + let execution_proof = event.execution_proof; + let epoch = Epoch::new(event.epoch); + + let Some(pubkey) = self + .validator_store + .voting_pubkeys::, _>(DoppelgangerStatus::ignored) + .first() + .cloned() + else { + warn!("No local validators available to resign proof"); + return; + }; + + match self + .validator_store + .sign_execution_proof(pubkey, execution_proof, epoch) + .await + { + Ok(signed_proof) => { + match self + .beacon_nodes + .first_success(move |node| { + let proof = signed_proof.clone(); + async move { node.post_beacon_execution_proofs(&[proof]).await } + }) + .await + { + Ok(_) => { + info!(?pubkey, "Resigned proof submitted"); + } + Err(e) => { + warn!(?pubkey, error = %e, "Failed to submit resigned proof"); + } + } + } + Err(e) => { + warn!(?pubkey, error = ?e, "Failed to sign proof for validator"); + } + } + } + + // ─── Proof engine event monitoring (new) ──────────────────────────── + + /// Monitor proof engine SSE events for proof completion, failure, and timeouts. + async fn monitor_proof_engine_events_task(self: Arc) { + info!("Starting proof engine event monitoring via SSE"); + + loop { + let mut stream = self.proof_engine.subscribe_proof_events(None); + + loop { + tokio::select! { + event = stream.next() => { + match event { + Some(Ok(proof_event)) => { + self.handle_proof_engine_event(proof_event).await; + } + Some(Err(e)) => { + warn!(error = %e, "Proof engine SSE error, will reconnect"); + break; + } + None => { + warn!("Proof engine SSE stream ended, reconnecting..."); + break; + } + } + } + _ = tokio::time::sleep(PROOF_REQUEST_STALE_TIMEOUT) => { + self.cleanup_stale_requests(); + } + } + } + + tokio::time::sleep(Duration::from_secs(2)).await; + } + } + + /// Process a single proof engine SSE event. + async fn handle_proof_engine_event(&self, event: ProofEvent) { + let root = event.new_payload_request_root(); + let proof_type = event.proof_type(); + + // Only process events for tracked requests and requested proof types. + let is_tracked = self + .outstanding_requests + .read() + .get(&root) + .map(|req| req.pending_proof_types.contains(&proof_type)) + .unwrap_or(false); + + if !is_tracked { + return; + } + + match event { + ProofEvent::ProofComplete(complete) => { + self.handle_proof_complete(complete.new_payload_request_root, complete.proof_type) + .await; + } + ProofEvent::ProofFailure(failure) => { + warn!( + root = %failure.new_payload_request_root, + proof_type = failure.proof_type, + error = %failure.error, + "Proof generation failed" + ); + self.remove_pending_proof_type( + failure.new_payload_request_root, + failure.proof_type, + ); + } + ProofEvent::WitnessTimeout(ref info) | ProofEvent::ProofTimeout(ref info) => { + warn!( + root = %info.new_payload_request_root, + proof_type = info.proof_type, + "Proof generation timed out" + ); + self.remove_pending_proof_type(info.new_payload_request_root, info.proof_type); + } + } + } + + /// Fetch a completed proof from the proof engine, sign it, and submit to the beacon node. + async fn handle_proof_complete(&self, root: Hash256, proof_type: u8) { + // Download proof bytes from proof engine. + let proof_bytes = match self.proof_engine.get_proof(root, proof_type).await { + Ok(bytes) => bytes, + Err(e) => { + error!(root = %root, proof_type, error = ?e, "Failed to fetch completed proof"); + return; + } + }; + + // Construct ExecutionProof. + let proof_data = match ProofData::new(proof_bytes.to_vec()) { + Ok(data) => data, + Err(e) => { + error!(root = %root, proof_type, error = ?e, "Proof data exceeds max size"); + return; + } + }; + + let execution_proof = ExecutionProof { + proof_data, + proof_type, + public_input: PublicInput { + new_payload_request_root: root, + }, + }; + + // Derive signing epoch from the stored slot. + let epoch = self + .outstanding_requests + .read() + .get(&root) + .map(|req| req.slot.epoch(S::E::slots_per_epoch())); + + let Some(epoch) = epoch else { + // Entry was removed (e.g. by stale cleanup) between the is_tracked check + // and here — nothing to do. + return; + }; + + // Resolve a safe validator for signing. + let Some(pubkey) = self + .validator_store + .voting_pubkeys::, _>(DoppelgangerStatus::only_safe) + .first() + .cloned() + else { + warn!("No safe validators available to sign completed proof"); + return; + }; + + // Sign and submit. + match self + .validator_store + .sign_execution_proof(pubkey, execution_proof, epoch) + .await + { + Ok(signed_proof) => { + match self + .beacon_nodes + .first_success(move |node| { + let proof = signed_proof.clone(); + async move { node.post_beacon_execution_proofs(&[proof]).await } + }) + .await + { + Ok(_) => { + info!(root = %root, proof_type, ?pubkey, "Completed proof signed and submitted"); + } + Err(e) => { + warn!(root = %root, proof_type, error = %e, "Failed to submit completed proof"); + } + } + } + Err(e) => { + warn!(root = %root, proof_type, error = ?e, "Failed to sign completed proof"); + } + } + + // Remove this proof type from the outstanding set. + self.remove_pending_proof_type(root, proof_type); + } + + // ─── Outstanding request management ───────────────────────────────── + + /// Remove a single proof type from an outstanding request. + /// + /// If all requested proof types have been resolved the entry is removed entirely. + fn remove_pending_proof_type(&self, root: Hash256, proof_type: u8) { + let mut requests = self.outstanding_requests.write(); + if let Some(entry) = requests.get_mut(&root) { + entry.pending_proof_types.remove(&proof_type); + if entry.pending_proof_types.is_empty() { + requests.remove(&root); + debug!(root = %root, "All proof types resolved, removing from tracker"); + } + } + } + + /// Remove outstanding requests that have exceeded the stale timeout. + fn cleanup_stale_requests(&self) { + let mut requests = self.outstanding_requests.write(); + let before = requests.len(); + requests.retain(|root, req| { + let stale = req.requested_at.elapsed() > PROOF_REQUEST_STALE_TIMEOUT; + if stale { + warn!(root = %root, "Removing stale proof request (timed out)"); + } + !stale + }); + let removed = before - requests.len(); + if removed > 0 { + info!(removed, "Cleaned up stale proof requests"); + } + } + + // ─── Reactive signing (existing) ──────────────────────────────────── + + /// Reactive: Sign and submit proof (called by HTTP API) + async fn sign_and_submit_proof( + &self, + pubkey: PublicKey, + execution_proof: ExecutionProof, + epoch: Option, + ) -> Result<(), String> { + // Determine epoch for signing context + let epoch = epoch.unwrap_or_else(|| { + self.slot_clock + .now() + .map(|slot| slot.epoch(S::E::slots_per_epoch())) + .unwrap_or(Epoch::new(0)) + }); + + let pubkey_bytes = pubkey.clone(); + info!( + validator = %pubkey, + %epoch, + "Signing execution proof" + ); + + // Sign the proof + let signed_proof = self + .validator_store + .sign_execution_proof(pubkey_bytes.into(), execution_proof, epoch) + .await + .map_err(|e| format!("Failed to sign execution proof: {:?}", e))?; + + // Submit to beacon node + let signed_proof_for_submission = signed_proof.clone(); + self.beacon_nodes + .first_success(move |node| { + let proof_clone = signed_proof_for_submission.clone(); + async move { node.post_beacon_execution_proofs(&[proof_clone]).await } + }) + .await + .map_err(|e| format!("Failed to submit proof to beacon node: {}", e))?; + + info!( + validator = %pubkey, + "Successfully submitted signed execution proof to beacon node" + ); + + Ok(()) + } +} diff --git a/validator_client/validator_store/src/lib.rs b/validator_client/validator_store/src/lib.rs index 2b472799d24..75cd191a19b 100644 --- a/validator_client/validator_store/src/lib.rs +++ b/validator_client/validator_store/src/lib.rs @@ -5,10 +5,11 @@ use std::fmt::Debug; use std::future::Future; use std::sync::Arc; use types::{ - Address, Attestation, AttestationError, BlindedBeaconBlock, Epoch, EthSpec, Graffiti, Hash256, - SelectionProof, SignedAggregateAndProof, SignedBlindedBeaconBlock, SignedContributionAndProof, - SignedValidatorRegistrationData, Slot, SyncCommitteeContribution, SyncCommitteeMessage, - SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData, + Address, Attestation, AttestationError, BlindedBeaconBlock, Epoch, EthSpec, ExecutionProof, + Graffiti, Hash256, SelectionProof, SignedAggregateAndProof, SignedBlindedBeaconBlock, + SignedContributionAndProof, SignedExecutionProof, SignedValidatorRegistrationData, Slot, + SyncCommitteeContribution, SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId, + ValidatorRegistrationData, }; #[derive(Debug, PartialEq, Clone)] @@ -160,6 +161,16 @@ pub trait ValidatorStore: Send + Sync { selection_proof: SyncSelectionProof, ) -> impl Future, Error>> + Send; + /// Signs an execution proof for EIP-8025. + /// + /// This allows validators to sign execution proofs for optional execution verification. + fn sign_execution_proof( + &self, + validator_pubkey: PublicKeyBytes, + execution_proof: ExecutionProof, + signing_epoch: Epoch, + ) -> impl Future>> + Send; + /// Prune the slashing protection database so that it remains performant. /// /// This function will only do actual pruning periodically, so it should usually be