diff --git a/.github/workflows/local-testnet.yml b/.github/workflows/local-testnet.yml
index c6f9c075db..c129c0ec95 100644
--- a/.github/workflows/local-testnet.yml
+++ b/.github/workflows/local-testnet.yml
@@ -14,7 +14,7 @@ concurrency:
jobs:
dockerfile-ubuntu:
- runs-on: ${{ github.repository == 'sigp/lighthouse' && fromJson('["self-hosted", "linux", "CI", "large"]') || 'ubuntu-latest' }}
+ runs-on: ${{ github.repository == 'sigp/lighthouse' && 'warp-ubuntu-latest-x64-8x' || 'ubuntu-latest' }}
steps:
- uses: actions/checkout@v5
@@ -31,7 +31,7 @@ jobs:
retention-days: 3
run-local-testnet:
- runs-on: ubuntu-22.04
+ runs-on: ${{ github.repository == 'sigp/lighthouse' && 'warp-ubuntu-latest-x64-8x' || 'ubuntu-latest' }}
needs: dockerfile-ubuntu
steps:
- uses: actions/checkout@v5
@@ -89,7 +89,7 @@ jobs:
${{ steps.assertoor_test_result.outputs.failed_test_details }}
EOF
)
-
+
echo "Test Result: $test_result"
echo "$test_status"
if ! [ "$test_result" == "success" ]; then
@@ -100,7 +100,7 @@ jobs:
doppelganger-protection-success-test:
needs: dockerfile-ubuntu
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
@@ -136,7 +136,7 @@ jobs:
doppelganger-protection-failure-test:
needs: dockerfile-ubuntu
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
@@ -173,7 +173,7 @@ jobs:
# Tests checkpoint syncing to a live network (current fork) and a running devnet (usually next scheduled fork)
checkpoint-sync-test:
name: checkpoint-sync-test-${{ matrix.network }}
- runs-on: ubuntu-latest
+ runs-on: ${{ github.repository == 'sigp/lighthouse' && 'warp-ubuntu-latest-x64-8x' || 'ubuntu-latest' }}
needs: dockerfile-ubuntu
if: contains(github.event.pull_request.labels.*.name, 'syncing')
continue-on-error: true
@@ -216,7 +216,7 @@ jobs:
# Test syncing from genesis on a local testnet. Aims to cover forward syncing both short and long distances.
genesis-sync-test:
name: genesis-sync-test-${{ matrix.fork }}-${{ matrix.offline_secs }}s
- runs-on: ubuntu-latest
+ runs-on: ${{ github.repository == 'sigp/lighthouse' && 'warp-ubuntu-latest-x64-8x' || 'ubuntu-latest' }}
needs: dockerfile-ubuntu
strategy:
matrix:
@@ -259,7 +259,7 @@ jobs:
# a PR is safe to merge. New jobs should be added here.
local-testnet-success:
name: local-testnet-success
- runs-on: ubuntu-latest
+ runs-on: ${{ github.repository == 'sigp/lighthouse' && 'warp-ubuntu-latest-x64-8x' || 'ubuntu-latest' }}
needs: [
'dockerfile-ubuntu',
'run-local-testnet',
@@ -272,4 +272,4 @@ jobs:
- name: Check that success job is dependent on all others
run: |
exclude_jobs='checkpoint-sync-test'
- ./scripts/ci/check-success-job.sh ./.github/workflows/local-testnet.yml local-testnet-success "$exclude_jobs"
+ ./scripts/ci/check-success-job.sh ./.github/workflows/local-testnet.yml local-testnet-success "$exclude_jobs"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 7c85cdd05c..f7b65f07c9 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -32,8 +32,7 @@ jobs:
matrix:
arch: [aarch64-unknown-linux-gnu,
x86_64-unknown-linux-gnu,
- aarch64-apple-darwin,
- x86_64-windows]
+ aarch64-apple-darwin]
include:
- arch: aarch64-unknown-linux-gnu
runner: ${{ github.repository == 'sigp/lighthouse' && fromJson('["self-hosted", "linux", "release", "large"]') || 'ubuntu-latest' }}
@@ -44,9 +43,6 @@ jobs:
- arch: aarch64-apple-darwin
runner: macos-14
profile: maxperf
- - arch: x86_64-windows
- runner: ${{ github.repository == 'sigp/lighthouse' && fromJson('["self-hosted", "windows", "release"]') || 'windows-2019' }}
- profile: maxperf
runs-on: ${{ matrix.runner }}
needs: extract-version
@@ -57,19 +53,6 @@ jobs:
if: env.SELF_HOSTED_RUNNERS == 'false'
run: rustup update stable
- # ==============================
- # Windows dependencies
- # ==============================
-
- - uses: KyleMayes/install-llvm-action@v1
- if: env.SELF_HOSTED_RUNNERS == 'false' && startsWith(matrix.arch, 'x86_64-windows')
- with:
- version: "17.0"
- directory: ${{ runner.temp }}/llvm
- - name: Set LIBCLANG_PATH
- if: startsWith(matrix.arch, 'x86_64-windows')
- run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV
-
# ==============================
# Builds
# ==============================
@@ -94,12 +77,7 @@ jobs:
if: matrix.arch == 'aarch64-apple-darwin'
run: cargo install --path lighthouse --force --locked --features portable,gnosis --profile ${{ matrix.profile }}
- - name: Build Lighthouse for Windows
- if: matrix.arch == 'x86_64-windows'
- run: cargo install --path lighthouse --force --locked --features portable,gnosis --profile ${{ matrix.profile }}
-
- name: Configure GPG and create artifacts
- if: startsWith(matrix.arch, 'x86_64-windows') != true
env:
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
@@ -118,20 +96,6 @@ jobs:
done
mv *tar.gz* ..
- - name: Configure GPG and create artifacts Windows
- if: startsWith(matrix.arch, 'x86_64-windows')
- env:
- GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
- GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
- run: |
- echo $env:GPG_SIGNING_KEY | gpg --batch --import
- mkdir artifacts
- move $env:USERPROFILE/.cargo/bin/lighthouse.exe ./artifacts
- cd artifacts
- tar -czf lighthouse-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.tar.gz lighthouse.exe
- gpg --passphrase "$env:GPG_PASSPHRASE" --batch --pinentry-mode loopback -ab lighthouse-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.tar.gz
- move *tar.gz* ..
-
# =======================================================================
# Upload artifacts
# This is required to share artifacts between different jobs
@@ -239,7 +203,6 @@ jobs:
|
| aarch64 | [lighthouse-${{ env.VERSION }}-aarch64-apple-darwin.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-aarch64-apple-darwin.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-aarch64-apple-darwin.tar.gz.asc) |
|
| x86_64 | [lighthouse-${{ env.VERSION }}-x86_64-unknown-linux-gnu.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-x86_64-unknown-linux-gnu.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-x86_64-unknown-linux-gnu.tar.gz.asc) |
|
| aarch64 | [lighthouse-${{ env.VERSION }}-aarch64-unknown-linux-gnu.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-aarch64-unknown-linux-gnu.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-aarch64-unknown-linux-gnu.tar.gz.asc) |
- |
| x86_64 | [lighthouse-${{ env.VERSION }}-x86_64-windows.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-x86_64-windows.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/lighthouse-${{ env.VERSION }}-x86_64-windows.tar.gz.asc) |
| | | | |
| **System** | **Option** | - | **Resource** |
|
| Docker | [${{ env.VERSION }}](https://hub.docker.com/r/${{ env.IMAGE_NAME }}/tags?page=1&ordering=last_updated&name=${{ env.VERSION }}) | [${{ env.IMAGE_NAME }}](https://hub.docker.com/r/${{ env.IMAGE_NAME }}) |
diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml
index 0201bf9ae3..cc7282c351 100644
--- a/.github/workflows/test-suite.yml
+++ b/.github/workflows/test-suite.yml
@@ -22,8 +22,6 @@ env:
# NOTE: this token is a personal access token on Jimmy's account due to the default GITHUB_TOKEN
# not having access to other repositories. We should eventually devise a better solution here.
LIGHTHOUSE_GITHUB_TOKEN: ${{ secrets.LIGHTHOUSE_GITHUB_TOKEN }}
- # Enable self-hosted runners for the sigp repo only.
- SELF_HOSTED_RUNNERS: ${{ github.repository == 'sigp/lighthouse' }}
# Disable incremental compilation
CARGO_INCREMENTAL: 0
# Enable portable to prevent issues with caching `blst` for the wrong CPU type
@@ -78,8 +76,7 @@ jobs:
name: release-tests-ubuntu
needs: [check-labels]
if: needs.check-labels.outputs.skip_ci != 'true'
- # Use self-hosted runners only on the sigp repo.
- runs-on: ${{ github.repository == 'sigp/lighthouse' && fromJson('["self-hosted", "linux", "CI", "large"]') || 'ubuntu-latest' }}
+ runs-on: ${{ github.repository == 'sigp/lighthouse' && 'warp-ubuntu-latest-x64-8x' || 'ubuntu-latest' }}
steps:
- uses: actions/checkout@v5
# Set Java version to 21. (required since Web3Signer 24.12.0).
@@ -88,7 +85,6 @@ jobs:
distribution: 'temurin'
java-version: '21'
- name: Get latest version of stable Rust
- if: env.SELF_HOSTED_RUNNERS == 'false'
uses: moonrepo/setup-rust@v1
with:
channel: stable
@@ -97,7 +93,6 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install Foundry (anvil)
- if: env.SELF_HOSTED_RUNNERS == 'false'
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly-ca67d15f4abd46394b324c50e21e66f306a1162d
@@ -107,50 +102,16 @@ jobs:
if: env.SELF_HOSTED_RUNNERS == 'true'
continue-on-error: true
run: sccache --show-stats
- release-tests-windows:
- name: release-tests-windows
- needs: [check-labels]
- if: needs.check-labels.outputs.skip_ci != 'true'
- runs-on: ${{ github.repository == 'sigp/lighthouse' && fromJson('["self-hosted", "windows", "CI"]') || 'windows-2019' }}
- steps:
- - uses: actions/checkout@v5
- - name: Get latest version of stable Rust
- if: env.SELF_HOSTED_RUNNERS == 'false'
- uses: moonrepo/setup-rust@v1
- with:
- channel: stable
- cache-target: release
- bins: cargo-nextest
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- - name: Install Foundry (anvil)
- if: env.SELF_HOSTED_RUNNERS == 'false'
- uses: foundry-rs/foundry-toolchain@v1
- with:
- version: nightly-ca67d15f4abd46394b324c50e21e66f306a1162d
- - name: Install make
- if: env.SELF_HOSTED_RUNNERS == 'false'
- run: choco install -y make
- - name: Set LIBCLANG_PATH
- run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV
- - name: Run tests in release
- run: make test-release
- - name: Show cache stats
- if: env.SELF_HOSTED_RUNNERS == 'true'
- continue-on-error: true
- run: sccache --show-stats
beacon-chain-tests:
name: beacon-chain-tests
needs: [check-labels]
if: needs.check-labels.outputs.skip_ci != 'true'
- # Use self-hosted runners only on the sigp repo.
- runs-on: ${{ github.repository == 'sigp/lighthouse' && fromJson('["self-hosted", "linux", "CI", "large"]') || 'ubuntu-latest' }}
+ runs-on: ${{ github.repository == 'sigp/lighthouse' && 'warp-ubuntu-latest-x64-8x' || 'ubuntu-latest' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v5
- name: Get latest version of stable Rust
- if: env.SELF_HOSTED_RUNNERS == 'false'
uses: moonrepo/setup-rust@v1
with:
channel: stable
@@ -158,22 +119,16 @@ jobs:
bins: cargo-nextest
- name: Run beacon_chain tests for all known forks
run: make test-beacon-chain
- - name: Show cache stats
- if: env.SELF_HOSTED_RUNNERS == 'true'
- continue-on-error: true
- run: sccache --show-stats
http-api-tests:
name: http-api-tests
needs: [check-labels]
if: needs.check-labels.outputs.skip_ci != 'true'
- # Use self-hosted runners only on the sigp repo.
- runs-on: ${{ github.repository == 'sigp/lighthouse' && fromJson('["self-hosted", "linux", "CI", "large"]') || 'ubuntu-latest' }}
+ runs-on: ${{ github.repository == 'sigp/lighthouse' && 'warp-ubuntu-latest-x64-8x' || 'ubuntu-latest' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v5
- name: Get latest version of stable Rust
- if: env.SELF_HOSTED_RUNNERS == 'false'
uses: moonrepo/setup-rust@v1
with:
channel: stable
@@ -181,10 +136,6 @@ jobs:
bins: cargo-nextest
- name: Run http_api tests for all recent forks
run: make test-http-api
- - name: Show cache stats
- if: env.SELF_HOSTED_RUNNERS == 'true'
- continue-on-error: true
- run: sccache --show-stats
op-pool-tests:
name: op-pool-tests
needs: [check-labels]
@@ -252,29 +203,22 @@ jobs:
name: debug-tests-ubuntu
needs: [check-labels]
if: needs.check-labels.outputs.skip_ci != 'true'
- # Use self-hosted runners only on the sigp repo.
- runs-on: ${{ github.repository == 'sigp/lighthouse' && fromJson('["self-hosted", "linux", "CI", "large"]') || 'ubuntu-latest' }}
+ runs-on: ${{ github.repository == 'sigp/lighthouse' && 'warp-ubuntu-latest-x64-8x' || 'ubuntu-latest' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v5
- name: Get latest version of stable Rust
- if: env.SELF_HOSTED_RUNNERS == 'false'
uses: moonrepo/setup-rust@v1
with:
channel: stable
bins: cargo-nextest
- name: Install Foundry (anvil)
- if: env.SELF_HOSTED_RUNNERS == 'false'
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly-ca67d15f4abd46394b324c50e21e66f306a1162d
- name: Run tests in debug
run: make test-debug
- - name: Show cache stats
- if: env.SELF_HOSTED_RUNNERS == 'true'
- continue-on-error: true
- run: sccache --show-stats
state-transition-vectors-ubuntu:
name: state-transition-vectors-ubuntu
needs: [check-labels]
@@ -293,14 +237,12 @@ jobs:
name: ef-tests-ubuntu
needs: [check-labels]
if: needs.check-labels.outputs.skip_ci != 'true'
- # Use self-hosted runners only on the sigp repo.
- runs-on: ${{ github.repository == 'sigp/lighthouse' && fromJson('["self-hosted", "linux", "CI", "small"]') || 'ubuntu-latest' }}
+ runs-on: ${{ github.repository == 'sigp/lighthouse' && 'warp-ubuntu-latest-x64-8x' || 'ubuntu-latest' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v5
- name: Get latest version of stable Rust
- if: env.SELF_HOSTED_RUNNERS == 'false'
uses: moonrepo/setup-rust@v1
with:
channel: stable
@@ -308,10 +250,6 @@ jobs:
bins: cargo-nextest
- name: Run consensus-spec-tests with blst and fake_crypto
run: make test-ef
- - name: Show cache stats
- if: env.SELF_HOSTED_RUNNERS == 'true'
- continue-on-error: true
- run: sccache --show-stats
basic-simulator-ubuntu:
name: basic-simulator-ubuntu
needs: [check-labels]
@@ -360,11 +298,10 @@ jobs:
name: execution-engine-integration-ubuntu
needs: [check-labels]
if: needs.check-labels.outputs.skip_ci != 'true'
- runs-on: ${{ github.repository == 'sigp/lighthouse' && fromJson('["self-hosted", "linux", "CI", "small"]') || 'ubuntu-latest' }}
+ runs-on: ${{ github.repository == 'sigp/lighthouse' && 'warp-ubuntu-latest-x64-8x' || 'ubuntu-latest' }}
steps:
- uses: actions/checkout@v5
- name: Get latest version of stable Rust
- if: env.SELF_HOSTED_RUNNERS == 'false'
uses: moonrepo/setup-rust@v1
with:
channel: stable
@@ -372,9 +309,6 @@ jobs:
cache: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- - name: Add go compiler to $PATH
- if: env.SELF_HOSTED_RUNNERS == 'true'
- run: echo "/usr/local/go/bin" >> $GITHUB_PATH
- name: Run exec engine integration tests in release
run: make test-exec-engine
check-code:
@@ -501,7 +435,6 @@ jobs:
'check-labels',
'target-branch-check',
'release-tests-ubuntu',
- 'release-tests-windows',
'beacon-chain-tests',
'op-pool-tests',
'network-tests',
diff --git a/Cargo.lock b/Cargo.lock
index 516d0df358..e045c8697f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,7 +4,7 @@ version = 4
[[package]]
name = "account_manager"
-version = "0.3.5"
+version = "8.0.0"
dependencies = [
"account_utils",
"bls",
@@ -862,7 +862,7 @@ dependencies = [
"bitvec 1.0.1",
"bls",
"criterion",
- "derivative",
+ "educe",
"eth2",
"eth2_network_config",
"ethereum_hashing",
@@ -918,7 +918,7 @@ dependencies = [
[[package]]
name = "beacon_node"
-version = "8.0.0-rc.1"
+version = "8.0.0"
dependencies = [
"account_utils",
"beacon_chain",
@@ -1193,7 +1193,7 @@ dependencies = [
[[package]]
name = "boot_node"
-version = "8.0.0-rc.1"
+version = "8.0.0"
dependencies = [
"beacon_node",
"bytes",
@@ -1658,15 +1658,19 @@ dependencies = [
[[package]]
name = "compare_fields"
-version = "0.2.0"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05162add7c8618791829528194a271dca93f69194d35b19db1ca7fbfb8275278"
dependencies = [
"compare_fields_derive",
- "itertools 0.10.5",
+ "itertools 0.14.0",
]
[[package]]
name = "compare_fields_derive"
-version = "0.2.0"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f5ee468b2e568b668e2a686112935e7bbe9a81bf4fa6b9f6fc3410ea45fb7ce"
dependencies = [
"quote",
"syn 1.0.109",
@@ -2232,7 +2236,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
dependencies = [
"const-oid",
- "pem-rfc7468",
"zeroize",
]
@@ -2539,9 +2542,8 @@ dependencies = [
"beacon_chain",
"bls",
"compare_fields",
- "compare_fields_derive",
"context_deserialize",
- "derivative",
+ "educe",
"eth2_network_config",
"ethereum_ssz",
"ethereum_ssz_derive",
@@ -2719,7 +2721,6 @@ dependencies = [
"ff 0.13.1",
"generic-array 0.14.7",
"group 0.13.0",
- "pem-rfc7468",
"pkcs8 0.10.2",
"rand_core 0.6.4",
"sec1 0.7.3",
@@ -2861,7 +2862,7 @@ dependencies = [
name = "eth2"
version = "0.1.0"
dependencies = [
- "derivative",
+ "educe",
"eip_3076",
"either",
"enr",
@@ -3857,26 +3858,6 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
-[[package]]
-name = "git-version"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ad568aa3db0fcbc81f2f116137f263d7304f512a1209b35b85150d3ef88ad19"
-dependencies = [
- "git-version-macro",
-]
-
-[[package]]
-name = "git-version-macro"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.100",
-]
-
[[package]]
name = "glob"
version = "0.3.2"
@@ -5033,7 +5014,7 @@ dependencies = [
"arbitrary",
"c-kzg",
"criterion",
- "derivative",
+ "educe",
"ethereum_hashing",
"ethereum_serde_utils",
"ethereum_ssz",
@@ -5064,7 +5045,7 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "lcli"
-version = "8.0.0-rc.1"
+version = "8.0.0"
dependencies = [
"account_utils",
"beacon_chain",
@@ -5317,10 +5298,8 @@ dependencies = [
"hkdf",
"k256 0.13.4",
"multihash",
- "p256",
"quick-protobuf",
"rand 0.8.5",
- "sec1 0.7.3",
"sha2 0.10.8",
"thiserror 2.0.12",
"tracing",
@@ -5574,7 +5553,7 @@ dependencies = [
[[package]]
name = "lighthouse"
-version = "8.0.0-rc.1"
+version = "8.0.0"
dependencies = [
"account_manager",
"account_utils",
@@ -5708,9 +5687,8 @@ dependencies = [
[[package]]
name = "lighthouse_version"
-version = "0.1.0"
+version = "8.0.0"
dependencies = [
- "git-version",
"regex",
]
@@ -6331,7 +6309,7 @@ dependencies = [
"beacon_processor",
"bls",
"delay_map",
- "derivative",
+ "educe",
"eth2",
"eth2_network_config",
"ethereum_ssz",
@@ -6767,7 +6745,7 @@ version = "0.2.0"
dependencies = [
"beacon_chain",
"bitvec 1.0.1",
- "derivative",
+ "educe",
"ethereum_ssz",
"ethereum_ssz_derive",
"itertools 0.10.5",
@@ -6792,18 +6770,6 @@ dependencies = [
"num-traits",
]
-[[package]]
-name = "p256"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
-dependencies = [
- "ecdsa 0.16.9",
- "elliptic-curve 0.13.8",
- "primeorder",
- "sha2 0.10.8",
-]
-
[[package]]
name = "pairing"
version = "0.23.0"
@@ -6969,15 +6935,6 @@ dependencies = [
"serde",
]
-[[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.1"
@@ -7200,15 +7157,6 @@ dependencies = [
"syn 2.0.100",
]
-[[package]]
-name = "primeorder"
-version = "0.13.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
-dependencies = [
- "elliptic-curve 0.13.8",
-]
-
[[package]]
name = "primitive-types"
version = "0.10.1"
@@ -8501,6 +8449,8 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
[[package]]
name = "sensitive_url"
version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb7b0221fa9905eec4163dbf7660b1876cc95663af1deddc3e19ebe49167c58c"
dependencies = [
"serde",
"url",
@@ -8798,7 +8748,7 @@ version = "0.1.0"
dependencies = [
"bincode",
"byteorder",
- "derivative",
+ "educe",
"ethereum_ssz",
"ethereum_ssz_derive",
"filesystem",
@@ -8939,14 +8889,14 @@ dependencies = [
[[package]]
name = "ssz_types"
-version = "0.11.0"
+version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75b55bedc9a18ed2860a46d6beb4f4082416ee1d60be0cc364cebdcdddc7afd4"
+checksum = "704671195db617afa3d919da8f220f2535f20d0fa8dad96a1c27a38a5f8f6e9c"
dependencies = [
"arbitrary",
"ethereum_serde_utils",
"ethereum_ssz",
- "itertools 0.13.0",
+ "itertools 0.14.0",
"serde",
"serde_derive",
"smallvec",
@@ -8967,7 +8917,7 @@ dependencies = [
"arbitrary",
"beacon_chain",
"bls",
- "derivative",
+ "educe",
"ethereum_hashing",
"ethereum_ssz",
"ethereum_ssz_derive",
@@ -9903,10 +9853,9 @@ dependencies = [
"beacon_chain",
"bls",
"compare_fields",
- "compare_fields_derive",
"context_deserialize",
"criterion",
- "derivative",
+ "educe",
"eth2_interop_keypairs",
"ethereum_hashing",
"ethereum_serde_utils",
@@ -10110,7 +10059,7 @@ dependencies = [
[[package]]
name = "validator_client"
-version = "0.3.5"
+version = "8.0.0"
dependencies = [
"account_utils",
"beacon_node_fallback",
@@ -10150,7 +10099,7 @@ version = "0.1.0"
dependencies = [
"bls",
"deposit_contract",
- "derivative",
+ "educe",
"eth2_keystore",
"filesystem",
"hex",
@@ -10238,7 +10187,7 @@ dependencies = [
"beacon_chain",
"clap",
"clap_utils",
- "derivative",
+ "educe",
"environment",
"eth2",
"eth2_network_config",
diff --git a/Cargo.toml b/Cargo.toml
index ae84d645bb..15fea466f0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,8 +19,6 @@ members = [
"boot_node",
"common/account_utils",
"common/clap_utils",
- "common/compare_fields",
- "common/compare_fields_derive",
"common/deposit_contract",
"common/directory",
"common/eip_3076",
@@ -41,7 +39,6 @@ members = [
"common/network_utils",
"common/oneshot_broadcast",
"common/pretty_reqwest_error",
- "common/sensitive_url",
"common/slot_clock",
"common/system_health",
"common/target_check",
@@ -96,6 +93,7 @@ resolver = "2"
[workspace.package]
edition = "2024"
+version = "8.0.0"
[workspace.dependencies]
account_utils = { path = "common/account_utils" }
@@ -121,8 +119,7 @@ c-kzg = { version = "2.1", default-features = false }
cargo_metadata = "0.19"
clap = { version = "4.5.4", features = ["derive", "cargo", "wrap_help"] }
clap_utils = { path = "common/clap_utils" }
-compare_fields = { path = "common/compare_fields" }
-compare_fields_derive = { path = "common/compare_fields_derive" }
+compare_fields = "0.1"
console-subscriber = "0.4"
context_deserialize = { path = "consensus/context_deserialize/context_deserialize", features = [
"all",
@@ -131,11 +128,11 @@ context_deserialize_derive = { path = "consensus/context_deserialize/context_des
criterion = "0.5"
delay_map = "0.4"
deposit_contract = { path = "common/deposit_contract" }
-derivative = "2"
directory = { path = "common/directory" }
dirs = "3"
discv5 = { version = "0.10", features = ["libp2p"] }
doppelganger_service = { path = "validator_client/doppelganger_service" }
+educe = "0.6"
eip_3076 = { path = "common/eip_3076" }
either = "1.9"
environment = { path = "lighthouse/environment" }
@@ -227,7 +224,7 @@ rpds = "0.11"
rusqlite = { version = "0.28", features = ["bundled"] }
rust_eth_kzg = "0.9"
safe_arith = "0.1"
-sensitive_url = { path = "common/sensitive_url" }
+sensitive_url = { version = "0.1", features = ["serde"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_repr = "0.1"
@@ -239,7 +236,7 @@ slashing_protection = { path = "validator_client/slashing_protection" }
slot_clock = { path = "common/slot_clock" }
smallvec = { version = "1.11.2", features = ["arbitrary"] }
snap = "1"
-ssz_types = "0.11.0"
+ssz_types = "0.12.2"
state_processing = { path = "consensus/state_processing" }
store = { path = "beacon_node/store" }
strum = { version = "0.24", features = ["derive"] }
diff --git a/Makefile b/Makefile
index e1e8a2db1f..b6ff73690e 100644
--- a/Makefile
+++ b/Makefile
@@ -30,17 +30,13 @@ TEST_FEATURES ?=
# Cargo profile for regular builds.
PROFILE ?= release
-# List of all hard forks. This list is used to set env variables for several tests so that
-# they run for different forks.
-FORKS=phase0 altair bellatrix capella deneb electra fulu gloas
-
# List of all hard forks up to gloas. This list is used to set env variables for several tests so that
# they run for different forks.
-# TODO(EIP-7732) Remove this once we extend network tests to support gloas
-FORKS_BEFORE_GLOAS=phase0 altair bellatrix capella deneb electra fulu
+# TODO(EIP-7732) Remove this once we extend network tests to support gloas and use RECENT_FORKS instead
+RECENT_FORKS_BEFORE_GLOAS=electra fulu
# List of all recent hard forks. This list is used to set env variables for http_api tests
-RECENT_FORKS=electra fulu
+RECENT_FORKS=electra fulu gloas
# Extra flags for Cargo
CARGO_INSTALL_EXTRA_FLAGS?=
@@ -176,21 +172,21 @@ run-ef-tests:
./$(EF_TESTS)/check_all_files_accessed.py $(EF_TESTS)/.accessed_file_log.txt $(EF_TESTS)/consensus-spec-tests
# Run the tests in the `beacon_chain` crate for all known forks.
-# TODO(EIP-7732) Extend to support gloas
-test-beacon-chain: $(patsubst %,test-beacon-chain-%,$(FORKS_BEFORE_GLOAS))
+# TODO(EIP-7732) Extend to support gloas by using RECENT_FORKS instead
+test-beacon-chain: $(patsubst %,test-beacon-chain-%,$(RECENT_FORKS_BEFORE_GLOAS))
test-beacon-chain-%:
env FORK_NAME=$* cargo nextest run --release --features "fork_from_env,slasher/lmdb,$(TEST_FEATURES)" -p beacon_chain
# Run the tests in the `http_api` crate for recent forks.
-test-http-api: $(patsubst %,test-http-api-%,$(RECENT_FORKS))
+test-http-api: $(patsubst %,test-http-api-%,$(RECENT_FORKS_BEFORE_GLOAS))
test-http-api-%:
env FORK_NAME=$* cargo nextest run --release --features "beacon_chain/fork_from_env" -p http_api
# Run the tests in the `operation_pool` crate for all known forks.
-test-op-pool: $(patsubst %,test-op-pool-%,$(FORKS))
+test-op-pool: $(patsubst %,test-op-pool-%,$(RECENT_FORKS_BEFORE_GLOAS))
test-op-pool-%:
env FORK_NAME=$* cargo nextest run --release \
@@ -198,8 +194,8 @@ test-op-pool-%:
-p operation_pool
# Run the tests in the `network` crate for all known forks.
-# TODO(EIP-7732) Extend to support gloas
-test-network: $(patsubst %,test-network-%,$(FORKS_BEFORE_GLOAS))
+# TODO(EIP-7732) Extend to support gloas by using RECENT_FORKS instead
+test-network: $(patsubst %,test-network-%,$(RECENT_FORKS_BEFORE_GLOAS))
test-network-%:
env FORK_NAME=$* cargo nextest run --release \
diff --git a/account_manager/Cargo.toml b/account_manager/Cargo.toml
index 071e2681dd..8dd50cbc6e 100644
--- a/account_manager/Cargo.toml
+++ b/account_manager/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "account_manager"
-version = "0.3.5"
+version = { workspace = true }
authors = [
"Paul Hauner ",
"Luke Anderson ",
diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml
index 985f4c1752..fd01355978 100644
--- a/beacon_node/Cargo.toml
+++ b/beacon_node/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "beacon_node"
-version = "8.0.0-rc.1"
+version = { workspace = true }
authors = [
"Paul Hauner ",
"Age Manning (
let blobs = (0..num_of_blobs)
.map(|_| Blob::::default())
.collect::>()
- .into();
- let proofs = vec![KzgProof::empty(); num_of_blobs * E::number_of_columns()].into();
+ .try_into()
+ .unwrap();
+ let proofs = vec![KzgProof::empty(); num_of_blobs * E::number_of_columns()]
+ .try_into()
+ .unwrap();
(signed_block, blobs, proofs)
}
diff --git a/beacon_node/beacon_chain/src/attestation_verification.rs b/beacon_node/beacon_chain/src/attestation_verification.rs
index 470664d442..faa396966f 100644
--- a/beacon_node/beacon_chain/src/attestation_verification.rs
+++ b/beacon_node/beacon_chain/src/attestation_verification.rs
@@ -57,7 +57,7 @@ use state_processing::{
};
use std::borrow::Cow;
use strum::AsRefStr;
-use tracing::debug;
+use tracing::{debug, error};
use tree_hash::TreeHash;
use types::{
Attestation, AttestationData, AttestationRef, BeaconCommittee,
@@ -267,6 +267,14 @@ pub enum Error {
/// We were unable to process this attestation due to an internal error. It's unclear if the
/// attestation is valid.
BeaconChainError(Box),
+ /// A critical error occurred while converting SSZ types.
+ /// This can only occur when a VariableList was not able to be constructed from a single
+ /// attestation.
+ ///
+ /// ## Peer scoring
+ ///
+ /// The peer has sent an invalid message.
+ SszTypesError(ssz_types::Error),
}
impl From for Error {
@@ -275,6 +283,12 @@ impl From for Error {
}
}
+impl From for Error {
+ fn from(e: ssz_types::Error) -> Self {
+ Self::SszTypesError(e)
+ }
+}
+
/// Used to avoid double-checking signatures.
#[derive(Copy, Clone)]
enum CheckAttestationSignature {
@@ -442,7 +456,18 @@ fn process_slash_info(
.spec
.fork_name_at_slot::(attestation.data.slot);
- let indexed_attestation = attestation.to_indexed(fork_name);
+ let indexed_attestation = match attestation.to_indexed(fork_name) {
+ Ok(indexed) => indexed,
+ Err(e) => {
+ error!(
+ attestation_root = ?attestation.data.tree_hash_root(),
+ error = ?e,
+ "Unable to construct VariableList from a single attestation. \
+ This indicates a serious bug in SSZ handling"
+ );
+ return Error::SszTypesError(e);
+ }
+ };
(indexed_attestation, true, err)
}
SignatureNotCheckedIndexed(indexed, err) => (indexed, true, err),
@@ -932,7 +957,9 @@ impl<'a, T: BeaconChainTypes> IndexedUnaggregatedAttestation<'a, T> {
.spec
.fork_name_at_slot::(attestation.data.slot);
- let indexed_attestation = attestation.to_indexed(fork_name);
+ let indexed_attestation = attestation
+ .to_indexed(fork_name)
+ .map_err(|e| SignatureNotCheckedSingle(attestation, Error::SszTypesError(e)))?;
let validator_index = match Self::verify_middle_checks(attestation, chain) {
Ok(t) => t,
@@ -1344,7 +1371,7 @@ pub fn verify_signed_aggregate_signatures(
.spec
.fork_at_epoch(indexed_attestation.data().target.epoch);
- let signature_sets = vec![
+ let signature_sets = [
signed_aggregate_selection_proof_signature_set(
|validator_index| pubkey_cache.get(validator_index).map(Cow::Borrowed),
signed_aggregate,
diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs
index 853b8def76..73a02114ea 100644
--- a/beacon_node/beacon_chain/src/beacon_chain.rs
+++ b/beacon_node/beacon_chain/src/beacon_chain.rs
@@ -883,6 +883,12 @@ impl BeaconChain {
return Ok(None);
}
+ // Fast-path for the split slot (which usually corresponds to the finalized slot).
+ let split = self.store.get_split_info();
+ if request_slot == split.slot {
+ return Ok(Some(split.state_root));
+ }
+
// Try an optimized path of reading the root directly from the head state.
let fast_lookup: Option = self.with_head(|head| {
if head.beacon_block.slot() <= request_slot {
@@ -5500,11 +5506,21 @@ impl BeaconChain {
randao_reveal,
eth1_data,
graffiti,
- proposer_slashings: proposer_slashings.into(),
- attester_slashings: attester_slashings_base.into(),
- attestations: attestations_base.into(),
- deposits: deposits.into(),
- voluntary_exits: voluntary_exits.into(),
+ proposer_slashings: proposer_slashings
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ attester_slashings: attester_slashings_base
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ attestations: attestations_base
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ deposits: deposits
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ voluntary_exits: voluntary_exits
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
_phantom: PhantomData,
},
}),
@@ -5521,11 +5537,21 @@ impl BeaconChain {
randao_reveal,
eth1_data,
graffiti,
- proposer_slashings: proposer_slashings.into(),
- attester_slashings: attester_slashings_base.into(),
- attestations: attestations_base.into(),
- deposits: deposits.into(),
- voluntary_exits: voluntary_exits.into(),
+ proposer_slashings: proposer_slashings
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ attester_slashings: attester_slashings_base
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ attestations: attestations_base
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ deposits: deposits
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ voluntary_exits: voluntary_exits
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
sync_aggregate: sync_aggregate
.ok_or(BlockProductionError::MissingSyncAggregate)?,
_phantom: PhantomData,
@@ -5548,11 +5574,21 @@ impl BeaconChain {
randao_reveal,
eth1_data,
graffiti,
- proposer_slashings: proposer_slashings.into(),
- attester_slashings: attester_slashings_base.into(),
- attestations: attestations_base.into(),
- deposits: deposits.into(),
- voluntary_exits: voluntary_exits.into(),
+ proposer_slashings: proposer_slashings
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ attester_slashings: attester_slashings_base
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ attestations: attestations_base
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ deposits: deposits
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ voluntary_exits: voluntary_exits
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
sync_aggregate: sync_aggregate
.ok_or(BlockProductionError::MissingSyncAggregate)?,
execution_payload: block_proposal_contents
@@ -5580,18 +5616,30 @@ impl BeaconChain {
randao_reveal,
eth1_data,
graffiti,
- proposer_slashings: proposer_slashings.into(),
- attester_slashings: attester_slashings_base.into(),
- attestations: attestations_base.into(),
- deposits: deposits.into(),
- voluntary_exits: voluntary_exits.into(),
+ proposer_slashings: proposer_slashings
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ attester_slashings: attester_slashings_base
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ attestations: attestations_base
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ deposits: deposits
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ voluntary_exits: voluntary_exits
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
sync_aggregate: sync_aggregate
.ok_or(BlockProductionError::MissingSyncAggregate)?,
execution_payload: block_proposal_contents
.to_payload()
.try_into()
.map_err(|_| BlockProductionError::InvalidPayloadFork)?,
- bls_to_execution_changes: bls_to_execution_changes.into(),
+ bls_to_execution_changes: bls_to_execution_changes
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
},
}),
None,
@@ -5619,17 +5667,29 @@ impl BeaconChain {
randao_reveal,
eth1_data,
graffiti,
- proposer_slashings: proposer_slashings.into(),
- attester_slashings: attester_slashings_base.into(),
- attestations: attestations_base.into(),
- deposits: deposits.into(),
- voluntary_exits: voluntary_exits.into(),
+ proposer_slashings: proposer_slashings
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ attester_slashings: attester_slashings_base
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ attestations: attestations_base
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ deposits: deposits
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ voluntary_exits: voluntary_exits
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
sync_aggregate: sync_aggregate
.ok_or(BlockProductionError::MissingSyncAggregate)?,
execution_payload: payload
.try_into()
.map_err(|_| BlockProductionError::InvalidPayloadFork)?,
- bls_to_execution_changes: bls_to_execution_changes.into(),
+ bls_to_execution_changes: bls_to_execution_changes
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
blob_kzg_commitments: kzg_commitments.ok_or(
BlockProductionError::MissingKzgCommitment(
"Kzg commitments missing from block contents".to_string(),
@@ -5662,17 +5722,29 @@ impl BeaconChain {
randao_reveal,
eth1_data,
graffiti,
- proposer_slashings: proposer_slashings.into(),
- attester_slashings: attester_slashings_electra.into(),
- attestations: attestations_electra.into(),
- deposits: deposits.into(),
- voluntary_exits: voluntary_exits.into(),
+ proposer_slashings: proposer_slashings
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ attester_slashings: attester_slashings_electra
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ attestations: attestations_electra
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ deposits: deposits
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ voluntary_exits: voluntary_exits
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
sync_aggregate: sync_aggregate
.ok_or(BlockProductionError::MissingSyncAggregate)?,
execution_payload: payload
.try_into()
.map_err(|_| BlockProductionError::InvalidPayloadFork)?,
- bls_to_execution_changes: bls_to_execution_changes.into(),
+ bls_to_execution_changes: bls_to_execution_changes
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
blob_kzg_commitments: kzg_commitments
.ok_or(BlockProductionError::InvalidPayloadFork)?,
execution_requests: maybe_requests
@@ -5704,17 +5776,29 @@ impl BeaconChain {
randao_reveal,
eth1_data,
graffiti,
- proposer_slashings: proposer_slashings.into(),
- attester_slashings: attester_slashings_electra.into(),
- attestations: attestations_electra.into(),
- deposits: deposits.into(),
- voluntary_exits: voluntary_exits.into(),
+ proposer_slashings: proposer_slashings
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ attester_slashings: attester_slashings_electra
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ attestations: attestations_electra
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ deposits: deposits
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
+ voluntary_exits: voluntary_exits
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
sync_aggregate: sync_aggregate
.ok_or(BlockProductionError::MissingSyncAggregate)?,
execution_payload: payload
.try_into()
.map_err(|_| BlockProductionError::InvalidPayloadFork)?,
- bls_to_execution_changes: bls_to_execution_changes.into(),
+ bls_to_execution_changes: bls_to_execution_changes
+ .try_into()
+ .map_err(BlockProductionError::SszTypesError)?,
blob_kzg_commitments: kzg_commitments
.ok_or(BlockProductionError::InvalidPayloadFork)?,
execution_requests: maybe_requests
diff --git a/beacon_node/beacon_chain/src/beacon_fork_choice_store.rs b/beacon_node/beacon_chain/src/beacon_fork_choice_store.rs
index 440388661c..0c203009bb 100644
--- a/beacon_node/beacon_chain/src/beacon_fork_choice_store.rs
+++ b/beacon_node/beacon_chain/src/beacon_fork_choice_store.rs
@@ -5,7 +5,7 @@
//! reads when fork choice requires the validator balances of the justified state.
use crate::{BeaconSnapshot, metrics};
-use derivative::Derivative;
+use educe::Educe;
use fork_choice::ForkChoiceStore;
use proto_array::JustifiedBalances;
use safe_arith::ArithError;
@@ -127,10 +127,10 @@ impl BalancesCache {
/// Implements `fork_choice::ForkChoiceStore` in order to provide a persistent backing to the
/// `fork_choice::ForkChoice` struct.
-#[derive(Debug, Derivative)]
-#[derivative(PartialEq(bound = "E: EthSpec, Hot: ItemStore, Cold: ItemStore"))]
+#[derive(Debug, Educe)]
+#[educe(PartialEq(bound(E: EthSpec, Hot: ItemStore, Cold: ItemStore)))]
pub struct BeaconForkChoiceStore, Cold: ItemStore> {
- #[derivative(PartialEq = "ignore")]
+ #[educe(PartialEq(ignore))]
store: Arc>,
balances_cache: BalancesCache,
time: Slot,
diff --git a/beacon_node/beacon_chain/src/beacon_proposer_cache.rs b/beacon_node/beacon_chain/src/beacon_proposer_cache.rs
index 6effce49f8..bd6460eba7 100644
--- a/beacon_node/beacon_chain/src/beacon_proposer_cache.rs
+++ b/beacon_node/beacon_chain/src/beacon_proposer_cache.rs
@@ -166,10 +166,17 @@ impl BeaconProposerCache {
}
/// Compute the proposer duties using the head state without cache.
+///
+/// Return:
+/// - Proposer indices.
+/// - True dependent root.
+/// - Legacy dependent root (last block of epoch `N - 1`).
+/// - Head execution status.
+/// - Fork at `request_epoch`.
pub fn compute_proposer_duties_from_head(
request_epoch: Epoch,
chain: &BeaconChain,
-) -> Result<(Vec, Hash256, ExecutionStatus, Fork), BeaconChainError> {
+) -> Result<(Vec, Hash256, Hash256, ExecutionStatus, Fork), BeaconChainError> {
// Atomically collect information about the head whilst holding the canonical head `Arc` as
// short as possible.
let (mut state, head_state_root, head_block_root) = {
@@ -203,11 +210,23 @@ pub fn compute_proposer_duties_from_head(
.proposer_shuffling_decision_root_at_epoch(request_epoch, head_block_root, &chain.spec)
.map_err(BeaconChainError::from)?;
+ // This is only required because the V1 proposer duties endpoint spec wasn't updated for Fulu. We
+ // can delete this once the V1 endpoint is deprecated at the Glamsterdam fork.
+ let legacy_dependent_root = state
+ .legacy_proposer_shuffling_decision_root_at_epoch(request_epoch, head_block_root)
+ .map_err(BeaconChainError::from)?;
+
// Use fork_at_epoch rather than the state's fork, because post-Fulu we may not have advanced
// the state completely into the new epoch.
let fork = chain.spec.fork_at_epoch(request_epoch);
- Ok((indices, dependent_root, execution_status, fork))
+ Ok((
+ indices,
+ dependent_root,
+ legacy_dependent_root,
+ execution_status,
+ fork,
+ ))
}
/// If required, advance `state` to the epoch required to determine proposer indices in `target_epoch`.
diff --git a/beacon_node/beacon_chain/src/blob_verification.rs b/beacon_node/beacon_chain/src/blob_verification.rs
index 53f2eff0ca..874673b52e 100644
--- a/beacon_node/beacon_chain/src/blob_verification.rs
+++ b/beacon_node/beacon_chain/src/blob_verification.rs
@@ -1,4 +1,4 @@
-use derivative::Derivative;
+use educe::Educe;
use slot_clock::SlotClock;
use std::marker::PhantomData;
use std::sync::Arc;
@@ -245,8 +245,8 @@ impl GossipVerifiedBlob {
/// Wrapper over a `BlobSidecar` for which we have completed kzg verification.
/// i.e. `verify_blob_kzg_proof(blob, commitment, proof) == true`.
-#[derive(Debug, Derivative, Clone, Encode, Decode)]
-#[derivative(PartialEq, Eq)]
+#[derive(Debug, Educe, Clone, Encode, Decode)]
+#[educe(PartialEq, Eq)]
#[ssz(struct_behaviour = "transparent")]
pub struct KzgVerifiedBlob {
blob: Arc>,
diff --git a/beacon_node/beacon_chain/src/block_verification.rs b/beacon_node/beacon_chain/src/block_verification.rs
index e4f523c8dd..f27f281ac8 100644
--- a/beacon_node/beacon_chain/src/block_verification.rs
+++ b/beacon_node/beacon_chain/src/block_verification.rs
@@ -66,7 +66,7 @@ use crate::{
beacon_chain::{BeaconForkChoice, ForkChoiceError},
metrics,
};
-use derivative::Derivative;
+use educe::Educe;
use eth2::types::{BlockGossip, EventKind};
use execution_layer::PayloadStatus;
pub use fork_choice::{AttestationFromBlock, PayloadVerificationStatus};
@@ -689,8 +689,8 @@ pub fn signature_verify_chain_segment(
/// A wrapper around a `SignedBeaconBlock` that indicates it has been approved for re-gossiping on
/// the p2p network.
-#[derive(Derivative)]
-#[derivative(Debug(bound = "T: BeaconChainTypes"))]
+#[derive(Educe)]
+#[educe(Debug(bound(T: BeaconChainTypes)))]
pub struct GossipVerifiedBlock {
pub block: Arc>,
pub block_root: Hash256,
diff --git a/beacon_node/beacon_chain/src/block_verification_types.rs b/beacon_node/beacon_chain/src/block_verification_types.rs
index 1a0b188fdc..5978e97c4d 100644
--- a/beacon_node/beacon_chain/src/block_verification_types.rs
+++ b/beacon_node/beacon_chain/src/block_verification_types.rs
@@ -2,7 +2,7 @@ use crate::data_availability_checker::AvailabilityCheckError;
pub use crate::data_availability_checker::{AvailableBlock, MaybeAvailableBlock};
use crate::data_column_verification::{CustodyDataColumn, CustodyDataColumnList};
use crate::{PayloadVerificationOutcome, get_block_root};
-use derivative::Derivative;
+use educe::Educe;
use ssz_types::VariableList;
use state_processing::ConsensusContext;
use std::fmt::{Debug, Formatter};
@@ -26,8 +26,8 @@ use types::{
/// Note: We make a distinction over blocks received over gossip because
/// in a post-deneb world, the blobs corresponding to a given block that are received
/// over rpc do not contain the proposer signature for dos resistance.
-#[derive(Clone, Derivative)]
-#[derivative(Hash(bound = "E: EthSpec"))]
+#[derive(Clone, Educe)]
+#[educe(Hash(bound(E: EthSpec)))]
pub struct RpcBlock {
block_root: Hash256,
block: RpcBlockInner,
@@ -80,8 +80,8 @@ impl RpcBlock {
/// Note: This variant is intentionally private because we want to safely construct the
/// internal variants after applying consistency checks to ensure that the block and blobs
/// are consistent with respect to each other.
-#[derive(Debug, Clone, Derivative)]
-#[derivative(Hash(bound = "E: EthSpec"))]
+#[derive(Debug, Clone, Educe)]
+#[educe(Hash(bound(E: EthSpec)))]
enum RpcBlockInner {
/// Single block lookup response. This should potentially hit the data availability cache.
Block(Arc>),
diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs
index 750cde14ca..719c24b956 100644
--- a/beacon_node/beacon_chain/src/builder.rs
+++ b/beacon_node/beacon_chain/src/builder.rs
@@ -931,18 +931,26 @@ where
// Load the persisted custody context from the db and initialize
// the context for this run
- let custody_context = if let Some(custody) =
+ let (custody_context, cgc_changed_opt) = if let Some(custody) =
load_custody_context::(store.clone())
{
- Arc::new(CustodyContext::new_from_persisted_custody_context(
+ let head_epoch = canonical_head
+ .cached_head()
+ .head_slot()
+ .epoch(E::slots_per_epoch());
+ CustodyContext::new_from_persisted_custody_context(
custody,
self.node_custody_type,
+ head_epoch,
&self.spec,
- ))
+ )
} else {
- Arc::new(CustodyContext::new(self.node_custody_type, &self.spec))
+ (
+ CustodyContext::new(self.node_custody_type, &self.spec),
+ None,
+ )
};
- debug!(?custody_context, "Loading persisted custody context");
+ debug!(?custody_context, "Loaded persisted custody context");
let beacon_chain = BeaconChain {
spec: self.spec.clone(),
@@ -1019,7 +1027,7 @@ where
slot_clock,
self.kzg.clone(),
store,
- custody_context,
+ Arc::new(custody_context),
self.spec,
)
.map_err(|e| format!("Error initializing DataAvailabilityChecker: {:?}", e))?,
@@ -1062,6 +1070,14 @@ where
return Err(format!("Weak subjectivity verification failed: {:?}", e));
}
+ if let Some(cgc_changed) = cgc_changed_opt {
+ // Update data column custody info if there's a CGC change from CLI flags.
+ // This will trigger column backfill.
+ let cgc_change_effective_slot =
+ cgc_changed.effective_epoch.start_slot(E::slots_per_epoch());
+ beacon_chain.update_data_column_custody_info(Some(cgc_change_effective_slot));
+ }
+
info!(
head_state = %head.beacon_state_root(),
head_block = %head.beacon_block_root,
diff --git a/beacon_node/beacon_chain/src/custody_context.rs b/beacon_node/beacon_chain/src/custody_context.rs
index 7ec13a8b51..a5ef3ed2f6 100644
--- a/beacon_node/beacon_chain/src/custody_context.rs
+++ b/beacon_node/beacon_chain/src/custody_context.rs
@@ -7,7 +7,7 @@ use std::{
collections::{BTreeMap, HashMap},
sync::atomic::{AtomicU64, Ordering},
};
-use tracing::warn;
+use tracing::{debug, warn};
use types::data_column_custody_group::{CustodyIndex, compute_columns_for_custody_group};
use types::{ChainSpec, ColumnIndex, Epoch, EthSpec, Slot};
@@ -49,6 +49,10 @@ impl ValidatorRegistrations {
///
/// If a `cgc_override` value is specified, the cgc value is inserted into the registration map
/// and is equivalent to registering validator(s) with the same custody requirement.
+ ///
+ /// The node will backfill all the way back to either data_availability_boundary or fulu epoch,
+ /// and because this is a fresh node, setting the epoch to 0 is fine, as backfill will be done via
+ /// backfill sync instead of column backfill.
fn new(cgc_override: Option) -> Self {
let mut registrations = ValidatorRegistrations {
validators: Default::default(),
@@ -100,10 +104,9 @@ impl ValidatorRegistrations {
let validator_custody_requirement =
get_validators_custody_requirement(validator_custody_units, spec);
- tracing::debug!(
+ debug!(
validator_custody_units,
- validator_custody_requirement,
- "Registered validators"
+ validator_custody_requirement, "Registered validators"
);
// If registering the new validator increased the total validator "units", then
@@ -117,19 +120,29 @@ impl ValidatorRegistrations {
let effective_epoch =
(current_slot + effective_delay_slots).epoch(E::slots_per_epoch()) + 1;
self.epoch_validator_custody_requirements
- .entry(effective_epoch)
- .and_modify(|old_custody| *old_custody = validator_custody_requirement)
- .or_insert(validator_custody_requirement);
+ .insert(effective_epoch, validator_custody_requirement);
Some((effective_epoch, validator_custody_requirement))
} else {
None
}
}
- /// Updates the `epoch_validator_custody_requirements` map by pruning all values on/after `effective_epoch`
- /// and updating the map to store the latest validator custody requirements for the `effective_epoch`.
- pub fn backfill_validator_custody_requirements(&mut self, effective_epoch: Epoch) {
+ /// Updates the `epoch -> cgc` map after custody backfill has been completed for
+ /// the specified epoch.
+ ///
+ /// This is done by pruning all values on/after `effective_epoch` and updating the map to store
+ /// the latest validator custody requirements for the `effective_epoch`.
+ pub fn backfill_validator_custody_requirements(
+ &mut self,
+ effective_epoch: Epoch,
+ expected_cgc: u64,
+ ) {
if let Some(latest_validator_custody) = self.latest_validator_custody_requirement() {
+ // If the expected cgc isn't equal to the latest validator custody a very recent cgc change may have occurred.
+ // We should not update the mapping.
+ if expected_cgc != latest_validator_custody {
+ return;
+ }
// Delete records if
// 1. The epoch is greater than or equal than `effective_epoch`
// 2. the cgc requirements match the latest validator custody requirements
@@ -139,11 +152,25 @@ impl ValidatorRegistrations {
});
self.epoch_validator_custody_requirements
- .entry(effective_epoch)
- .and_modify(|old_custody| *old_custody = latest_validator_custody)
- .or_insert(latest_validator_custody);
+ .insert(effective_epoch, latest_validator_custody);
}
}
+
+ /// Updates the `epoch -> cgc` map by pruning records before `effective_epoch`
+ /// while setting the `cgc` at `effective_epoch` to the latest validator custody requirement.
+ ///
+ /// This is used to restart custody backfill sync at `effective_epoch`
+ pub fn reset_validator_custody_requirements(&mut self, effective_epoch: Epoch) {
+ if let Some(latest_validator_custody_requirements) =
+ self.latest_validator_custody_requirement()
+ {
+ self.epoch_validator_custody_requirements
+ .retain(|&epoch, _| epoch >= effective_epoch);
+
+ self.epoch_validator_custody_requirements
+ .insert(effective_epoch, latest_validator_custody_requirements);
+ };
+ }
}
/// Given the `validator_custody_units`, return the custody requirement based on
@@ -247,39 +274,92 @@ impl CustodyContext {
/// Restore the custody context from disk.
///
- /// * If NodeCustodyType::custody_count < validator_custody_at_head, it means the attached
- /// validate stake has increased the node's CGC. We ignore the CLI input.
- /// * If NodeCustodyType::custody_count > validator_custody_at_head, it means the user has
- /// changed the node's custody type via either the --supernode or --semi-supernode flags,
- /// and will require a resync until we implement column backfill for this scenario.
+ /// # Behavior
+ /// * If [`NodeCustodyType::get_custody_count_override`] < validator_custody_at_head, it means
+ /// validators have increased the CGC beyond the derived CGC from cli flags. We ignore the CLI input.
+ /// * If [`NodeCustodyType::get_custody_count_override`] > validator_custody_at_head, it means the user has
+ /// changed the node's custody type via either the --supernode or --semi-supernode flags which
+ /// has resulted in a CGC increase. **The new CGC will be made effective from the next epoch**.
+ ///
+ /// # Returns
+ /// A tuple containing:
+ /// * `Self` - The restored custody context with updated CGC at head
+ /// * `Option` - `Some` if the CLI flag caused a CGC increase (triggering backfill),
+ /// `None` if no CGC change occurred or reduction was prevented
pub fn new_from_persisted_custody_context(
ssz_context: CustodyContextSsz,
node_custody_type: NodeCustodyType,
+ head_epoch: Epoch,
spec: &ChainSpec,
- ) -> Self {
- let cgc_override = node_custody_type.get_custody_count_override(spec);
- if let Some(cgc_from_cli) = cgc_override
- && cgc_from_cli > ssz_context.validator_custody_at_head
- {
- warn!(
- info = "node will continue to run with the current custody count",
- current_custody_count = ssz_context.validator_custody_at_head,
- node_custody_type = ?node_custody_type,
- "Changing node type is currently not supported without a resync and will have no effect",
+ ) -> (Self, Option) {
+ let CustodyContextSsz {
+ mut validator_custody_at_head,
+ mut epoch_validator_custody_requirements,
+ persisted_is_supernode: _,
+ } = ssz_context;
+
+ let mut custody_count_changed = None;
+
+ if let Some(cgc_from_cli) = node_custody_type.get_custody_count_override(spec) {
+ debug!(
+ ?node_custody_type,
+ persisted_custody_count = validator_custody_at_head,
+ "Initialising from persisted custody context"
);
+
+ if cgc_from_cli > validator_custody_at_head {
+ // Make the CGC from CLI effective from the next epoch
+ let effective_epoch = head_epoch + 1;
+ let old_custody_group_count = validator_custody_at_head;
+ validator_custody_at_head = cgc_from_cli;
+
+ let sampling_count = spec
+ .sampling_size_custody_groups(cgc_from_cli)
+ .expect("should compute node sampling size from valid chain spec");
+
+ epoch_validator_custody_requirements.push((effective_epoch, cgc_from_cli));
+
+ custody_count_changed = Some(CustodyCountChanged {
+ new_custody_group_count: validator_custody_at_head,
+ old_custody_group_count,
+ sampling_count,
+ effective_epoch,
+ });
+
+ debug!(
+ info = "new CGC will be effective from the next epoch",
+ ?node_custody_type,
+ old_cgc = old_custody_group_count,
+ new_cgc = validator_custody_at_head,
+ effective_epoch = %effective_epoch,
+ "Node custody type change caused a custody count increase",
+ );
+ } else if cgc_from_cli < validator_custody_at_head {
+ // We don't currently support reducing CGC for simplicity.
+ // A common scenario is that user may restart with a CLI flag, but the validators
+ // are only attached later, and we end up having CGC inconsistency.
+ warn!(
+ info = "node will continue to run with the current custody count",
+ current_custody_count = validator_custody_at_head,
+ node_custody_type = ?node_custody_type,
+ "Reducing CGC is currently not supported without a resync and will have no effect",
+ );
+ }
}
- CustodyContext {
- validator_custody_count: AtomicU64::new(ssz_context.validator_custody_at_head),
+
+ let custody_context = CustodyContext {
+ validator_custody_count: AtomicU64::new(validator_custody_at_head),
validator_registrations: RwLock::new(ValidatorRegistrations {
validators: Default::default(),
- epoch_validator_custody_requirements: ssz_context
- .epoch_validator_custody_requirements
+ epoch_validator_custody_requirements: epoch_validator_custody_requirements
.into_iter()
.collect(),
}),
all_custody_columns_ordered: OnceLock::new(),
_phantom_data: PhantomData,
- }
+ };
+
+ (custody_context, custody_count_changed)
}
/// Initializes an ordered list of data columns based on provided custody groups.
@@ -331,7 +411,7 @@ impl CustodyContext {
let current_cgc = self.validator_custody_count.load(Ordering::Relaxed);
if new_validator_custody != current_cgc {
- tracing::debug!(
+ debug!(
old_count = current_cgc,
new_count = new_validator_custody,
"Validator count at head updated"
@@ -342,10 +422,9 @@ impl CustodyContext {
let updated_cgc = self.custody_group_count_at_head(spec);
// Send the message to network only if there are more columns subnets to subscribe to
if updated_cgc > current_cgc {
- tracing::debug!(
+ debug!(
old_cgc = current_cgc,
- updated_cgc,
- "Custody group count updated"
+ updated_cgc, "Custody group count updated"
);
return Some(CustodyCountChanged {
new_custody_group_count: updated_cgc,
@@ -457,15 +536,34 @@ impl CustodyContext {
&all_columns_ordered[..custody_group_count]
}
- pub fn update_and_backfill_custody_count_at_epoch(&self, effective_epoch: Epoch) {
+ /// The node has completed backfill for this epoch. Update the internal records so the function
+ /// [`Self::custody_columns_for_epoch()`] returns up-to-date results.
+ pub fn update_and_backfill_custody_count_at_epoch(
+ &self,
+ effective_epoch: Epoch,
+ expected_cgc: u64,
+ ) {
self.validator_registrations
.write()
- .backfill_validator_custody_requirements(effective_epoch);
+ .backfill_validator_custody_requirements(effective_epoch, expected_cgc);
+ }
+
+ /// The node is attempting to restart custody backfill. Update the internal records so that
+ /// custody backfill can start backfilling at `effective_epoch`.
+ pub fn reset_validator_custody_requirements(&self, effective_epoch: Epoch) {
+ self.validator_registrations
+ .write()
+ .reset_validator_custody_requirements(effective_epoch);
}
}
-/// The custody count changed because of a change in the
-/// number of validators being managed.
+/// Indicates that the custody group count (CGC) has increased.
+///
+/// CGC increases can occur due to:
+/// 1. Validator registrations increasing effective balance beyond current CGC
+/// 2. CLI flag changes (e.g., switching to --supernode or --semi-supernode)
+///
+/// This struct is used to trigger column backfill and network subnet subscription updates.
pub struct CustodyCountChanged {
pub new_custody_group_count: u64,
pub old_custody_group_count: u64,
@@ -509,6 +607,155 @@ mod tests {
type E = MainnetEthSpec;
+ fn setup_custody_context(
+ spec: &ChainSpec,
+ head_epoch: Epoch,
+ epoch_and_cgc_tuples: Vec<(Epoch, u64)>,
+ ) -> CustodyContext {
+ let cgc_at_head = epoch_and_cgc_tuples.last().unwrap().1;
+ let ssz_context = CustodyContextSsz {
+ validator_custody_at_head: cgc_at_head,
+ persisted_is_supernode: false,
+ epoch_validator_custody_requirements: epoch_and_cgc_tuples,
+ };
+
+ let (custody_context, _) = CustodyContext::::new_from_persisted_custody_context(
+ ssz_context,
+ NodeCustodyType::Fullnode,
+ head_epoch,
+ spec,
+ );
+
+ let all_custody_groups_ordered = (0..spec.number_of_custody_groups).collect::>();
+ custody_context
+ .init_ordered_data_columns_from_custody_groups(all_custody_groups_ordered, spec)
+ .expect("should initialise ordered data columns");
+ custody_context
+ }
+
+ fn complete_backfill_for_epochs(
+ custody_context: &CustodyContext,
+ start_epoch: Epoch,
+ end_epoch: Epoch,
+ expected_cgc: u64,
+ ) {
+ assert!(start_epoch >= end_epoch);
+ // Call from end_epoch down to start_epoch (inclusive), simulating backfill
+ for epoch in (end_epoch.as_u64()..=start_epoch.as_u64()).rev() {
+ custody_context
+ .update_and_backfill_custody_count_at_epoch(Epoch::new(epoch), expected_cgc);
+ }
+ }
+
+ /// Helper function to test CGC increases when switching node custody types.
+ /// Verifies that CustodyCountChanged is returned with correct values and
+ /// that custody_group_count_at_epoch returns appropriate values for current and next epoch.
+ fn assert_custody_type_switch_increases_cgc(
+ persisted_cgc: u64,
+ target_node_custody_type: NodeCustodyType,
+ expected_new_cgc: u64,
+ head_epoch: Epoch,
+ spec: &ChainSpec,
+ ) {
+ let ssz_context = CustodyContextSsz {
+ validator_custody_at_head: persisted_cgc,
+ persisted_is_supernode: false,
+ epoch_validator_custody_requirements: vec![(Epoch::new(0), persisted_cgc)],
+ };
+
+ let (custody_context, custody_count_changed) =
+ CustodyContext::::new_from_persisted_custody_context(
+ ssz_context,
+ target_node_custody_type,
+ head_epoch,
+ spec,
+ );
+
+ // Verify CGC increased
+ assert_eq!(
+ custody_context.custody_group_count_at_head(spec),
+ expected_new_cgc,
+ "cgc should increase from {} to {}",
+ persisted_cgc,
+ expected_new_cgc
+ );
+
+ // Verify CustodyCountChanged is returned with correct values
+ let cgc_changed = custody_count_changed.expect("CustodyCountChanged should be returned");
+ assert_eq!(
+ cgc_changed.new_custody_group_count, expected_new_cgc,
+ "new_custody_group_count should be {}",
+ expected_new_cgc
+ );
+ assert_eq!(
+ cgc_changed.old_custody_group_count, persisted_cgc,
+ "old_custody_group_count should be {}",
+ persisted_cgc
+ );
+ assert_eq!(
+ cgc_changed.effective_epoch,
+ head_epoch + 1,
+ "effective epoch should be head_epoch + 1"
+ );
+ assert_eq!(
+ cgc_changed.sampling_count,
+ spec.sampling_size_custody_groups(expected_new_cgc)
+ .expect("should compute sampling size"),
+ "sampling_count should match expected value"
+ );
+
+ // Verify custody_group_count_at_epoch returns correct values
+ assert_eq!(
+ custody_context.custody_group_count_at_epoch(head_epoch, spec),
+ persisted_cgc,
+ "current epoch should still use old cgc ({})",
+ persisted_cgc
+ );
+ assert_eq!(
+ custody_context.custody_group_count_at_epoch(head_epoch + 1, spec),
+ expected_new_cgc,
+ "next epoch should use new cgc ({})",
+ expected_new_cgc
+ );
+ }
+
+ /// Helper function to test CGC reduction prevention when switching node custody types.
+ /// Verifies that CGC stays at the persisted value and CustodyCountChanged is not returned.
+ fn assert_custody_type_switch_unchanged_cgc(
+ persisted_cgc: u64,
+ target_node_custody_type: NodeCustodyType,
+ head_epoch: Epoch,
+ spec: &ChainSpec,
+ ) {
+ let ssz_context = CustodyContextSsz {
+ validator_custody_at_head: persisted_cgc,
+ persisted_is_supernode: false,
+ epoch_validator_custody_requirements: vec![(Epoch::new(0), persisted_cgc)],
+ };
+
+ let (custody_context, custody_count_changed) =
+ CustodyContext::::new_from_persisted_custody_context(
+ ssz_context,
+ target_node_custody_type,
+ head_epoch,
+ spec,
+ );
+
+ // Verify CGC stays at persisted value (no reduction)
+ assert_eq!(
+ custody_context.custody_group_count_at_head(spec),
+ persisted_cgc,
+ "cgc should remain at {} (reduction not supported)",
+ persisted_cgc
+ );
+
+ // Verify no CustodyCountChanged is returned (no change occurred)
+ assert!(
+ custody_count_changed.is_none(),
+ "CustodyCountChanged should not be returned when CGC doesn't change"
+ );
+ }
+
#[test]
fn no_validators_supernode_default() {
let spec = E::default_spec();
@@ -914,9 +1161,10 @@ mod tests {
epoch_validator_custody_requirements: vec![],
};
- let custody_context = CustodyContext::::new_from_persisted_custody_context(
+ let (custody_context, _) = CustodyContext::::new_from_persisted_custody_context(
ssz_context,
NodeCustodyType::Fullnode,
+ Epoch::new(0),
&spec,
);
@@ -927,51 +1175,155 @@ mod tests {
);
}
+ /// Tests CLI flag change: Fullnode (CGC=0) → Supernode (CGC=128)
+ /// CGC should increase and trigger backfill via CustodyCountChanged.
#[test]
- fn restore_fullnode_then_switch_to_supernode_has_no_effect() {
+ fn restore_fullnode_then_switch_to_supernode_increases_cgc() {
let spec = E::default_spec();
- let ssz_context = CustodyContextSsz {
- validator_custody_at_head: 0, // no validators
- persisted_is_supernode: false,
- epoch_validator_custody_requirements: vec![],
- };
+ let head_epoch = Epoch::new(10);
+ let supernode_cgc = spec.number_of_custody_groups;
- // Attempt to restore as supernode (wants 128), but should use original persisted value
- let custody_context = CustodyContext::::new_from_persisted_custody_context(
- ssz_context,
+ assert_custody_type_switch_increases_cgc(
+ 0,
NodeCustodyType::Supernode,
+ supernode_cgc,
+ head_epoch,
&spec,
);
-
- assert_eq!(
- custody_context.custody_group_count_at_head(&spec),
- spec.custody_requirement,
- "should use original fullnode cgc, not supernode cgc"
- );
}
+ /// Tests validator-driven CGC increase: Semi-supernode (CGC=64) → CGC=70
+ /// Semi-supernode can exceed 64 when validator effective balance increases CGC.
#[test]
- fn restore_supernode_then_switch_to_fullnode_uses_persisted() {
+ fn restore_semi_supernode_with_validators_can_exceed_64() {
let spec = E::default_spec();
- let supernode_cgc = spec.number_of_custody_groups; // supernode cgc
+ let semi_supernode_cgc = spec.number_of_custody_groups / 2; // 64
+ let custody_context = CustodyContext::::new(NodeCustodyType::SemiSupernode, &spec);
- let ssz_context = CustodyContextSsz {
- validator_custody_at_head: supernode_cgc,
- persisted_is_supernode: false,
- epoch_validator_custody_requirements: vec![(Epoch::new(0), supernode_cgc)],
- };
+ // Verify initial CGC is 64 (semi-supernode)
+ assert_eq!(
+ custody_context.custody_group_count_at_head(&spec),
+ semi_supernode_cgc,
+ "initial cgc should be 64"
+ );
- // Attempt to restore as fullnode (wants 8), but should keep persisted value (128)
- let custody_context = CustodyContext::::new_from_persisted_custody_context(
- ssz_context,
- NodeCustodyType::Fullnode,
+ // Register validators with 70 custody units (exceeding semi-supernode default)
+ let validator_custody_units = 70;
+ let current_slot = Slot::new(10);
+ let cgc_changed = custody_context.register_validators(
+ vec![(
+ 0,
+ validator_custody_units * spec.balance_per_additional_custody_group,
+ )],
+ current_slot,
&spec,
);
+ // Verify CGC increased from 64 to 70
+ assert!(
+ cgc_changed.is_some(),
+ "CustodyCountChanged should be returned"
+ );
+ let cgc_changed = cgc_changed.unwrap();
+ assert_eq!(
+ cgc_changed.new_custody_group_count, validator_custody_units,
+ "cgc should increase to 70"
+ );
+ assert_eq!(
+ cgc_changed.old_custody_group_count, semi_supernode_cgc,
+ "old cgc should be 64"
+ );
+
+ // Verify the custody context reflects the new CGC
assert_eq!(
custody_context.custody_group_count_at_head(&spec),
+ validator_custody_units,
+ "custody_group_count_at_head should be 70"
+ );
+ }
+
+ /// Tests CLI flag change prevention: Supernode (CGC=128) → Fullnode (CGC stays 128)
+ /// CGC reduction is not supported - persisted value is retained.
+ #[test]
+ fn restore_supernode_then_switch_to_fullnode_uses_persisted() {
+ let spec = E::default_spec();
+ let supernode_cgc = spec.number_of_custody_groups;
+
+ assert_custody_type_switch_unchanged_cgc(
supernode_cgc,
- "should use persisted supernode cgc, not fullnode cgc"
+ NodeCustodyType::Fullnode,
+ Epoch::new(0),
+ &spec,
+ );
+ }
+
+ /// Tests CLI flag change prevention: Supernode (CGC=128) → Semi-supernode (CGC stays 128)
+ /// CGC reduction is not supported - persisted value is retained.
+ #[test]
+ fn restore_supernode_then_switch_to_semi_supernode_keeps_supernode_cgc() {
+ let spec = E::default_spec();
+ let supernode_cgc = spec.number_of_custody_groups;
+ let head_epoch = Epoch::new(10);
+
+ assert_custody_type_switch_unchanged_cgc(
+ supernode_cgc,
+ NodeCustodyType::SemiSupernode,
+ head_epoch,
+ &spec,
+ );
+ }
+
+ /// Tests CLI flag change: Fullnode with validators (CGC=32) → Semi-supernode (CGC=64)
+ /// CGC should increase and trigger backfill via CustodyCountChanged.
+ #[test]
+ fn restore_fullnode_with_validators_then_switch_to_semi_supernode() {
+ let spec = E::default_spec();
+ let persisted_cgc = 32u64;
+ let semi_supernode_cgc = spec.number_of_custody_groups / 2;
+ let head_epoch = Epoch::new(10);
+
+ assert_custody_type_switch_increases_cgc(
+ persisted_cgc,
+ NodeCustodyType::SemiSupernode,
+ semi_supernode_cgc,
+ head_epoch,
+ &spec,
+ );
+ }
+
+ /// Tests CLI flag change: Semi-supernode (CGC=64) → Supernode (CGC=128)
+ /// CGC should increase and trigger backfill via CustodyCountChanged.
+ #[test]
+ fn restore_semi_supernode_then_switch_to_supernode() {
+ let spec = E::default_spec();
+ let semi_supernode_cgc = spec.number_of_custody_groups / 2;
+ let supernode_cgc = spec.number_of_custody_groups;
+ let head_epoch = Epoch::new(10);
+
+ assert_custody_type_switch_increases_cgc(
+ semi_supernode_cgc,
+ NodeCustodyType::Supernode,
+ supernode_cgc,
+ head_epoch,
+ &spec,
+ );
+ }
+
+ /// Tests CLI flag change: Fullnode with validators (CGC=32) → Supernode (CGC=128)
+ /// CGC should increase and trigger backfill via CustodyCountChanged.
+ #[test]
+ fn restore_with_cli_flag_increases_cgc_from_nonzero() {
+ let spec = E::default_spec();
+ let persisted_cgc = 32u64;
+ let supernode_cgc = spec.number_of_custody_groups;
+ let head_epoch = Epoch::new(10);
+
+ assert_custody_type_switch_increases_cgc(
+ persisted_cgc,
+ NodeCustodyType::Supernode,
+ supernode_cgc,
+ head_epoch,
+ &spec,
);
}
@@ -992,9 +1344,10 @@ mod tests {
],
};
- let custody_context = CustodyContext::::new_from_persisted_custody_context(
+ let (custody_context, _) = CustodyContext::::new_from_persisted_custody_context(
ssz_context,
NodeCustodyType::Fullnode,
+ Epoch::new(20),
&spec,
);
@@ -1033,4 +1386,178 @@ mod tests {
"sampling at epoch 25 should match final cgc"
);
}
+
+ #[test]
+ fn backfill_single_cgc_increase_updates_past_epochs() {
+ let spec = E::default_spec();
+ let final_cgc = 32u64;
+ let default_cgc = spec.custody_requirement;
+
+ // Setup: Node restart after validators were registered, causing CGC increase to 32 at epoch 20
+ let head_epoch = Epoch::new(20);
+ let epoch_and_cgc_tuples = vec![(head_epoch, final_cgc)];
+ let custody_context = setup_custody_context(&spec, head_epoch, epoch_and_cgc_tuples);
+ assert_eq!(
+ custody_context.custody_group_count_at_epoch(Epoch::new(15), &spec),
+ default_cgc,
+ );
+
+ // Backfill from epoch 20 down to 15 (simulating backfill)
+ complete_backfill_for_epochs(&custody_context, head_epoch, Epoch::new(15), final_cgc);
+
+ // After backfilling to epoch 15, it should use latest CGC (32)
+ assert_eq!(
+ custody_context.custody_group_count_at_epoch(Epoch::new(15), &spec),
+ final_cgc,
+ );
+ assert_eq!(
+ custody_context
+ .custody_columns_for_epoch(Some(Epoch::new(15)), &spec)
+ .len(),
+ final_cgc as usize,
+ );
+
+ // Prior epoch should still return the original CGC
+ assert_eq!(
+ custody_context.custody_group_count_at_epoch(Epoch::new(14), &spec),
+ default_cgc,
+ );
+ }
+
+ #[test]
+ fn backfill_with_multiple_cgc_increases_prunes_map_correctly() {
+ let spec = E::default_spec();
+ let initial_cgc = 8u64;
+ let mid_cgc = 16u64;
+ let final_cgc = 32u64;
+
+ // Setup: Node restart after multiple validator registrations causing CGC increases
+ let head_epoch = Epoch::new(20);
+ let epoch_and_cgc_tuples = vec![
+ (Epoch::new(0), initial_cgc),
+ (Epoch::new(10), mid_cgc),
+ (head_epoch, final_cgc),
+ ];
+ let custody_context = setup_custody_context(&spec, head_epoch, epoch_and_cgc_tuples);
+
+ // Backfill to epoch 15 (between the two CGC increases)
+ complete_backfill_for_epochs(&custody_context, Epoch::new(20), Epoch::new(15), final_cgc);
+
+ // Verify epochs 15 - 20 return latest CGC (32)
+ for epoch in 15..=20 {
+ assert_eq!(
+ custody_context.custody_group_count_at_epoch(Epoch::new(epoch), &spec),
+ final_cgc,
+ );
+ }
+
+ // Verify epochs 10-14 still return mid_cgc (16)
+ for epoch in 10..14 {
+ assert_eq!(
+ custody_context.custody_group_count_at_epoch(Epoch::new(epoch), &spec),
+ mid_cgc,
+ );
+ }
+ }
+
+ #[test]
+ fn attempt_backfill_with_invalid_cgc() {
+ let spec = E::default_spec();
+ let initial_cgc = 8u64;
+ let mid_cgc = 16u64;
+ let final_cgc = 32u64;
+
+ // Setup: Node restart after multiple validator registrations causing CGC increases
+ let head_epoch = Epoch::new(20);
+ let epoch_and_cgc_tuples = vec![
+ (Epoch::new(0), initial_cgc),
+ (Epoch::new(10), mid_cgc),
+ (head_epoch, final_cgc),
+ ];
+ let custody_context = setup_custody_context(&spec, head_epoch, epoch_and_cgc_tuples);
+
+ // Backfill to epoch 15 (between the two CGC increases)
+ complete_backfill_for_epochs(&custody_context, Epoch::new(20), Epoch::new(15), final_cgc);
+
+ // Verify epochs 15 - 20 return latest CGC (32)
+ for epoch in 15..=20 {
+ assert_eq!(
+ custody_context.custody_group_count_at_epoch(Epoch::new(epoch), &spec),
+ final_cgc,
+ );
+ }
+
+ // Attempt backfill with an incorrect cgc value
+ complete_backfill_for_epochs(
+ &custody_context,
+ Epoch::new(20),
+ Epoch::new(15),
+ initial_cgc,
+ );
+
+ // Verify epochs 15 - 20 still return latest CGC (32)
+ for epoch in 15..=20 {
+ assert_eq!(
+ custody_context.custody_group_count_at_epoch(Epoch::new(epoch), &spec),
+ final_cgc,
+ );
+ }
+
+ // Verify epochs 10-14 still return mid_cgc (16)
+ for epoch in 10..14 {
+ assert_eq!(
+ custody_context.custody_group_count_at_epoch(Epoch::new(epoch), &spec),
+ mid_cgc,
+ );
+ }
+ }
+
+ #[test]
+ fn reset_validator_custody_requirements() {
+ let spec = E::default_spec();
+ let minimum_cgc = 4u64;
+ let initial_cgc = 8u64;
+ let mid_cgc = 16u64;
+ let final_cgc = 32u64;
+
+ // Setup: Node restart after multiple validator registrations causing CGC increases
+ let head_epoch = Epoch::new(20);
+ let epoch_and_cgc_tuples = vec![
+ (Epoch::new(0), initial_cgc),
+ (Epoch::new(10), mid_cgc),
+ (head_epoch, final_cgc),
+ ];
+ let custody_context = setup_custody_context(&spec, head_epoch, epoch_and_cgc_tuples);
+
+ // Backfill from epoch 20 to 9
+ complete_backfill_for_epochs(&custody_context, Epoch::new(20), Epoch::new(9), final_cgc);
+
+ // Reset validator custody requirements to the latest cgc requirements at `head_epoch` up to the boundary epoch
+ custody_context.reset_validator_custody_requirements(head_epoch);
+
+ // Verify epochs 0 - 19 return the minimum cgc requirement because of the validator custody requirement reset
+ for epoch in 0..=19 {
+ assert_eq!(
+ custody_context.custody_group_count_at_epoch(Epoch::new(epoch), &spec),
+ minimum_cgc,
+ );
+ }
+
+ // Verify epoch 20 returns a CGC of 32
+ assert_eq!(
+ custody_context.custody_group_count_at_epoch(head_epoch, &spec),
+ final_cgc
+ );
+
+ // Rerun Backfill to epoch 20
+ complete_backfill_for_epochs(&custody_context, Epoch::new(20), Epoch::new(0), final_cgc);
+
+ // Verify epochs 0 - 20 return the final cgc requirements
+ for epoch in 0..=20 {
+ assert_eq!(
+ custody_context.custody_group_count_at_epoch(Epoch::new(epoch), &spec),
+ final_cgc,
+ );
+ }
+ }
}
diff --git a/beacon_node/beacon_chain/src/data_availability_checker.rs b/beacon_node/beacon_chain/src/data_availability_checker.rs
index d6cc8d8947..644c471698 100644
--- a/beacon_node/beacon_chain/src/data_availability_checker.rs
+++ b/beacon_node/beacon_chain/src/data_availability_checker.rs
@@ -52,9 +52,8 @@ use types::non_zero_usize::new_non_zero_usize;
///
/// `PendingComponents` are now never removed from the cache manually are only removed via LRU
/// eviction to prevent race conditions (#7961), so we expect this cache to be full all the time.
-pub const OVERFLOW_LRU_CAPACITY: NonZeroUsize = new_non_zero_usize(32);
-pub const STATE_LRU_CAPACITY_NON_ZERO: NonZeroUsize = new_non_zero_usize(32);
-pub const STATE_LRU_CAPACITY: usize = STATE_LRU_CAPACITY_NON_ZERO.get();
+const OVERFLOW_LRU_CAPACITY_NON_ZERO: NonZeroUsize = new_non_zero_usize(32);
+const STATE_LRU_CAPACITY_NON_ZERO: NonZeroUsize = new_non_zero_usize(32);
/// Cache to hold fully valid data that can't be imported to fork-choice yet. After Dencun hard-fork
/// blocks have a sidecar of data that is received separately from the network. We call the concept
@@ -128,7 +127,7 @@ impl DataAvailabilityChecker {
spec: Arc,
) -> Result {
let inner = DataAvailabilityCheckerInner::new(
- OVERFLOW_LRU_CAPACITY,
+ OVERFLOW_LRU_CAPACITY_NON_ZERO,
store,
custody_context.clone(),
spec.clone(),
@@ -617,48 +616,45 @@ impl DataAvailabilityChecker {
));
};
- let data_columns_to_publish = all_data_columns
- .into_iter()
- .filter(|d| !existing_column_indices.contains(&d.index()))
- .collect::>();
-
- let Some(slot) = data_columns_to_publish
- .first()
- .map(|d| d.as_data_column().slot())
- else {
+ let Some(slot) = all_data_columns.first().map(|d| d.as_data_column().slot()) else {
return Ok(DataColumnReconstructionResult::RecoveredColumnsNotImported(
"No new columns to import and publish",
));
};
+ let columns_to_sample = self
+ .custody_context()
+ .sampling_columns_for_epoch(slot.epoch(T::EthSpec::slots_per_epoch()), &self.spec);
+
+ // We only need to import and publish columns that we need to sample
+ // and columns that we haven't already received
+ let data_columns_to_import_and_publish = all_data_columns
+ .into_iter()
+ .filter(|d| {
+ columns_to_sample.contains(&d.index())
+ && !existing_column_indices.contains(&d.index())
+ })
+ .collect::>();
+
metrics::stop_timer(timer);
metrics::inc_counter_by(
&metrics::DATA_AVAILABILITY_RECONSTRUCTED_COLUMNS,
- data_columns_to_publish.len() as u64,
+ data_columns_to_import_and_publish.len() as u64,
);
debug!(
- count = data_columns_to_publish.len(),
+ count = data_columns_to_import_and_publish.len(),
?block_root,
%slot,
"Reconstructed columns"
);
- let columns_to_sample = self
- .custody_context()
- .sampling_columns_for_epoch(slot.epoch(T::EthSpec::slots_per_epoch()), &self.spec);
- let data_columns_to_import: Vec<_> = data_columns_to_publish
- .iter()
- .filter(|column| columns_to_sample.contains(&column.index()))
- .cloned()
- .collect();
-
self.availability_cache
- .put_kzg_verified_data_columns(*block_root, data_columns_to_import)
+ .put_kzg_verified_data_columns(*block_root, data_columns_to_import_and_publish.clone())
.map(|availability| {
DataColumnReconstructionResult::Success((
availability,
- data_columns_to_publish
+ data_columns_to_import_and_publish
.into_iter()
.map(|d| d.clone_arc())
.collect::>(),
@@ -1163,8 +1159,8 @@ mod test {
// Remaining 64 columns should be reconstructed
assert_eq!(
reconstructed_columns.len(),
- 64,
- "should reconstruct the remaining 64 columns"
+ sampling_requirement - spec.number_of_custody_groups as usize / 2,
+ "should reconstruct the remaining 1 columns"
);
// Only the columns required for custody (65) should be imported into the cache
diff --git a/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs b/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs
index b842a1a3f9..5e6322ae95 100644
--- a/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs
+++ b/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs
@@ -828,7 +828,7 @@ mod test {
block_verification::PayloadVerificationOutcome,
block_verification_types::{AsBlock, BlockImportData},
custody_context::NodeCustodyType,
- data_availability_checker::STATE_LRU_CAPACITY,
+ data_availability_checker::STATE_LRU_CAPACITY_NON_ZERO,
test_utils::{BaseHarnessType, BeaconChainHarness, DiskHarnessType},
};
use fork_choice::PayloadVerificationStatus;
@@ -842,6 +842,7 @@ mod test {
use types::{ExecPayload, MinimalEthSpec};
const LOW_VALIDATOR_COUNT: usize = 32;
+ const STATE_LRU_CAPACITY: usize = STATE_LRU_CAPACITY_NON_ZERO.get();
fn get_store_with_spec(
db_path: &TempDir,
@@ -1278,7 +1279,7 @@ mod pending_components_tests {
let mut rng = StdRng::seed_from_u64(0xDEADBEEF0BAD5EEDu64);
let spec = test_spec::();
let (block, blobs_vec) =
- generate_rand_block_and_blobs::(ForkName::Deneb, NumBlobs::Random, &mut rng, &spec);
+ generate_rand_block_and_blobs::(ForkName::Deneb, NumBlobs::Random, &mut rng);
let max_len = spec.max_blobs_per_block(block.epoch()) as usize;
let mut blobs: RuntimeFixedVector