diff --git a/.fork_version b/.fork_version
index b89946e8e..c70bc15b6 100644
--- a/.fork_version
+++ b/.fork_version
@@ -1 +1 @@
-deneb
+electra
diff --git a/.github/config/assertoor/cl-stability-check.yml b/.github/config/assertoor/cl-stability-check.yml
index 9f9338705..998a27341 100644
--- a/.github/config/assertoor/cl-stability-check.yml
+++ b/.github/config/assertoor/cl-stability-check.yml
@@ -12,38 +12,39 @@ tasks:
title: "Check if all clients are ready"
timeout: 1m
-- name: run_tasks_concurrent
- title: "Check if all EL & CL clients are synced and the tx spammer is working"
- timeout: 5m
- config:
- tasks:
- - name: check_consensus_sync_status
- title: "Check if CL clients are synced"
- - name: check_execution_sync_status
- title: "Check if EL clients are synced"
+# TODO(#1413) The following tasks are not working yet, this will be part of the 3rd electra phase
+# - name: run_tasks_concurrent
+# title: "Check if all EL & CL clients are synced and the tx spammer is working"
+# timeout: 5m
+# config:
+# tasks:
+# - name: check_consensus_sync_status
+# title: "Check if CL clients are synced"
+# - name: check_execution_sync_status
+# title: "Check if EL clients are synced"
-- name: run_task_matrix
- title: "Check block proposals from all client pairs"
- timeout: 6m
- configVars:
- matrixValues: "validatorPairNames"
- config:
- runConcurrent: true
- matrixVar: "validatorPairName"
- task:
- name: check_consensus_block_proposals
- title: "Wait for block proposal from ${validatorPairName}"
- config:
- minTransactionCount: 80 # For some reason the tx fuzz is working different than the old spammer, we need to check it
- configVars:
- validatorNamePattern: "validatorPairName"
+# - name: run_task_matrix
+# title: "Check block proposals from all client pairs"
+# timeout: 6m
+# configVars:
+# matrixValues: "validatorPairNames"
+# config:
+# runConcurrent: true
+# matrixVar: "validatorPairName"
+# task:
+# name: check_consensus_block_proposals
+# title: "Wait for block proposal from ${validatorPairName}"
+# config:
+# minTransactionCount: 80 # For some reason the tx fuzz is working different than the old spammer, we need to check it
+# configVars:
+# validatorNamePattern: "validatorPairName"
-- name: run_tasks_concurrent
- title: "Check chain stability (reorgs and forks)"
- timeout: 7m
- config:
- tasks:
- - name: check_consensus_reorgs
- title: "Check consensus reorgs"
- - name: check_consensus_forks
- title: "Check consensus forks"
+# - name: run_tasks_concurrent
+# title: "Check chain stability (reorgs and forks)"
+# timeout: 7m
+# config:
+# tasks:
+# - name: check_consensus_reorgs
+# title: "Check consensus reorgs"
+# - name: check_consensus_forks
+# title: "Check consensus forks"
diff --git a/.github/config/assertoor/network-params.yml b/.github/config/assertoor/network-params.yml
index aa9f92454..f72e10580 100644
--- a/.github/config/assertoor/network-params.yml
+++ b/.github/config/assertoor/network-params.yml
@@ -4,11 +4,7 @@ participants:
cl_type: lighthouse
cl_image: sigp/lighthouse:v7.0.0-beta.5
validator_count: 32
- - el_type: geth
- el_image: ethereum/client-go:v1.15.6
- cl_type: lighthouse
- cl_image: sigp/lighthouse:v7.0.0-beta.5
- validator_count: 32
+ count: 2
- el_type: geth
el_image: ethereum/client-go:v1.15.6
cl_type: lambda
@@ -19,6 +15,9 @@ participants:
cl_max_mem: 4096
keymanager_enabled: true
+network_params:
+ electra_fork_epoch: 0
+
additional_services:
- assertoor
- tx_fuzz
@@ -28,7 +27,7 @@ assertoor_params:
run_stability_check: false
run_block_proposal_check: false
tests:
- - https://raw.githubusercontent.com/lambdaclass/lambda_ethereum_consensus/refs/heads/main/.github/config/assertoor/cl-stability-check.yml
+ - https://raw.githubusercontent.com/lambdaclass/lambda_ethereum_consensus/refs/heads/electra-support/.github/config/assertoor/cl-stability-check.yml
tx_fuzz_params:
tx_fuzz_extra_args: ["--txcount=3", "--accounts=80"]
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4a67e74cc..b480abe9b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -30,14 +30,14 @@ permissions:
jobs:
compile-native:
name: Build native libraries
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Set up Go
# NOTE: this action comes with caching by default
uses: actions/setup-go@v5
with:
- go-version: "1.21"
+ go-version: "1.24"
cache-dependency-path: |
native/libp2p_port/go.sum
- name: Cache output artifacts
@@ -58,7 +58,7 @@ jobs:
download-beacon-node-oapi:
name: Download Beacon Node OAPI
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Cache Beacon Node OAPI
@@ -75,7 +75,7 @@ jobs:
build:
name: Build project
needs: [compile-native, download-beacon-node-oapi]
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Set up Elixir
@@ -135,7 +135,7 @@ jobs:
docker-build:
name: Build Docker image
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
@@ -150,7 +150,7 @@ jobs:
smoke:
name: Start and stop the node
needs: [compile-native, download-beacon-node-oapi]
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Set up Elixir
@@ -195,12 +195,12 @@ jobs:
run: mix compile --warnings-as-errors
- name: Run the node
# NOTE: this starts and then stops the application. It should catch simple runtime errors
- run: mix run -- --checkpoint-sync-url https://mainnet-checkpoint-sync.stakely.io/
+ run: mix run -- --checkpoint-sync-url https://sepolia.beaconstate.info --network sepolia
test:
name: Test
needs: [compile-native, download-beacon-node-oapi]
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Set up Elixir
@@ -245,7 +245,7 @@ jobs:
lint:
name: Lint
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Set up Elixir
@@ -273,7 +273,7 @@ jobs:
download-spectests:
name: Download spectests
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Cache compressed spectests
@@ -293,9 +293,9 @@ jobs:
strategy:
fail-fast: false
matrix:
- fork: ["deneb"]
+ fork: ["electra"]
config: ["minimal", "general", "mainnet"]
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Set up Elixir
@@ -357,7 +357,7 @@ jobs:
spectests-success:
name: All spec-tests passed
needs: spectests-matrix
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
if: always()
steps:
- if: needs.spectests-matrix.result == 'success'
diff --git a/.github/workflows/ci_skipped.yml b/.github/workflows/ci_skipped.yml
index e2d1c9e50..3dc4b3232 100644
--- a/.github/workflows/ci_skipped.yml
+++ b/.github/workflows/ci_skipped.yml
@@ -14,36 +14,36 @@ on:
jobs:
compile-native:
name: Build native libraries
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
if: false
steps: [run: true]
build:
name: Build project
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
if: false
steps: [run: true]
smoke:
name: Start and stop the node
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
if: false
steps: [run: true]
test:
name: Test
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
if: false
steps: [run: true]
lint:
name: Lint
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
if: false
steps: [run: true]
spectests-success:
name: All spec-tests passed
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
if: false
steps: [run: true]
diff --git a/.spectest_version b/.spectest_version
index 0d0c52f84..e099f3d06 100644
--- a/.spectest_version
+++ b/.spectest_version
@@ -1 +1 @@
-v1.4.0
+v1.5.0-beta.3
diff --git a/.tool-versions b/.tool-versions
index 3b0b441c2..329ddbcef 100644
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,5 +1,5 @@
erlang 26.2
elixir 1.16.2-otp-26
-golang 1.22.12
+golang 1.24.2
rust 1.81.0
protoc 30.2
diff --git a/Dockerfile b/Dockerfile
index 603376fd5..808e28f65 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# libp2p port
-FROM golang:1.22 AS libp2p_builder
+FROM golang:1.24 AS libp2p_builder
LABEL stage=builder
# Install dependencies
diff --git a/README.md b/README.md
index bbcc81097..715c15806 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,9 @@ We take security seriously. If you discover a vulnerability in this project, ple
For more details, please refer to our [Security Policy](./.github/SECURITY.md).
+## Electra Support
+
+We are working on the electra support in [this branch](https://github.com/lambdaclass/lambda_ethereum_consensus/tree/electra-support) and the current progress is updated in [this document](https://github.com/lambdaclass/lambda_ethereum_consensus/tree/electra-support/electra-gap.md)
## Prerequisites
diff --git a/config/config.exs b/config/config.exs
index 9478220ac..32984f0aa 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -10,6 +10,7 @@ fork_raw = File.read!(".fork_version") |> String.trim()
fork =
case fork_raw do
"deneb" -> :deneb
+ "electra" -> :electra
v -> raise "Invalid fork specified: #{v}"
end
diff --git a/config/networks/mainnet/config.yaml b/config/networks/mainnet/config.yaml
index 47dc09509..6db16ed68 100644
--- a/config/networks/mainnet/config.yaml
+++ b/config/networks/mainnet/config.yaml
@@ -135,12 +135,14 @@ ATTESTATION_SUBNET_PREFIX_BITS: 6
# Deneb
# `2**7` (=128)
MAX_REQUEST_BLOCKS_DENEB: 128
-# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK
-MAX_REQUEST_BLOB_SIDECARS: 768
# `2**12` (= 4096 epochs, ~18 days)
MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096
# `6`
BLOB_SIDECAR_SUBNET_COUNT: 6
+## 6 blobs
+MAX_BLOBS_PER_BLOCK: 6
+# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK (= 128 * 6) sidecars
+MAX_REQUEST_BLOB_SIDECARS: 768
# Whisk
# `Epoch(2**8)`
@@ -151,3 +153,11 @@ WHISK_PROPOSER_SELECTION_GAP: 2
# EIP7594
EIP7594_FORK_VERSION: 0x06000001
EIP7594_FORK_EPOCH: 18446744073709551615
+
+# Electra
+# 2**7 * 10**9 (= 128,000,000,000) Gwei
+MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000
+# 2**8 * 10**9) (= 256,000,000,000) Gwei
+MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000
+# 9
+MAX_BLOBS_PER_BLOCK_ELECTRA: 9
diff --git a/config/networks/minimal/config.yaml b/config/networks/minimal/config.yaml
index 72cbd013c..ed4f79b87 100644
--- a/config/networks/minimal/config.yaml
+++ b/config/networks/minimal/config.yaml
@@ -135,12 +135,14 @@ ATTESTATION_SUBNET_PREFIX_BITS: 6
# Deneb
# `2**7` (=128)
MAX_REQUEST_BLOCKS_DENEB: 128
-# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK
-MAX_REQUEST_BLOB_SIDECARS: 768
# `2**12` (= 4096 epochs, ~18 days)
MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096
# `6`
BLOB_SIDECAR_SUBNET_COUNT: 6
+## 6 blobs
+MAX_BLOBS_PER_BLOCK: 6
+# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK (= 128 * 6) sidecars
+MAX_REQUEST_BLOB_SIDECARS: 768
# Whisk
WHISK_EPOCHS_PER_SHUFFLING_PHASE: 4
@@ -149,3 +151,11 @@ WHISK_PROPOSER_SELECTION_GAP: 1
# EIP7594
EIP7594_FORK_VERSION: 0x06000001
EIP7594_FORK_EPOCH: 18446744073709551615
+
+# Electra
+# [customized] 2**7 * 10**9 (= 128,000,000,000) Gwei
+MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000
+# [customized] 2**8 * 10**9) (= 256,000,000,000) Gwei
+MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 128000000000
+# 9
+MAX_BLOBS_PER_BLOCK_ELECTRA: 9
diff --git a/config/presets/gnosis/electra.yaml b/config/presets/gnosis/electra.yaml
new file mode 100644
index 000000000..0d70433f3
--- /dev/null
+++ b/config/presets/gnosis/electra.yaml
@@ -0,0 +1,50 @@
+# Mainnet preset - Electra
+
+# Gwei values
+# ---------------------------------------------------------------
+# 2**5 * 10**9 (= 32,000,000,000) Gwei
+MIN_ACTIVATION_BALANCE: 32000000000
+# 2**11 * 10**9 (= 2,048,000,000,000) Gwei
+MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000
+
+# State list lengths
+# ---------------------------------------------------------------
+# `uint64(2**27)` (= 134,217,728)
+PENDING_DEPOSITS_LIMIT: 134217728
+# `uint64(2**27)` (= 134,217,728)
+PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728
+# `uint64(2**18)` (= 262,144)
+PENDING_CONSOLIDATIONS_LIMIT: 262144
+
+# Reward and penalty quotients
+# ---------------------------------------------------------------
+# `uint64(2**12)` (= 4,096)
+MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096
+# `uint64(2**12)` (= 4,096)
+WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096
+
+# # Max operations per block
+# ---------------------------------------------------------------
+# `uint64(2**0)` (= 1)
+MAX_ATTESTER_SLASHINGS_ELECTRA: 1
+# `uint64(2**3)` (= 8)
+MAX_ATTESTATIONS_ELECTRA: 8
+# `uint64(2**1)` (= 2)
+MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 2
+
+# Execution
+# ---------------------------------------------------------------
+# 2**13 (= 8192) deposit requests
+MAX_DEPOSIT_REQUESTS_PER_PAYLOAD: 8192
+# 2**4 (= 16) withdrawal requests
+MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 16
+
+# Withdrawals processing
+# ---------------------------------------------------------------
+# 2**3 ( = 8) pending withdrawals
+MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 8
+
+# Pending deposits processing
+# ---------------------------------------------------------------
+# 2**4 ( = 4) pending deposits
+MAX_PENDING_DEPOSITS_PER_EPOCH: 16
\ No newline at end of file
diff --git a/config/presets/mainnet/electra.yaml b/config/presets/mainnet/electra.yaml
new file mode 100644
index 000000000..42afbb233
--- /dev/null
+++ b/config/presets/mainnet/electra.yaml
@@ -0,0 +1,50 @@
+# Mainnet preset - Electra
+
+# Gwei values
+# ---------------------------------------------------------------
+# 2**5 * 10**9 (= 32,000,000,000) Gwei
+MIN_ACTIVATION_BALANCE: 32000000000
+# 2**11 * 10**9 (= 2,048,000,000,000) Gwei
+MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000
+
+# State list lengths
+# ---------------------------------------------------------------
+# `uint64(2**27)` (= 134,217,728)
+PENDING_DEPOSITS_LIMIT: 134217728
+# `uint64(2**27)` (= 134,217,728)
+PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728
+# `uint64(2**18)` (= 262,144)
+PENDING_CONSOLIDATIONS_LIMIT: 262144
+
+# Reward and penalty quotients
+# ---------------------------------------------------------------
+# `uint64(2**12)` (= 4,096)
+MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096
+# `uint64(2**12)` (= 4,096)
+WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096
+
+# # Max operations per block
+# ---------------------------------------------------------------
+# `uint64(2**0)` (= 1)
+MAX_ATTESTER_SLASHINGS_ELECTRA: 1
+# `uint64(2**3)` (= 8)
+MAX_ATTESTATIONS_ELECTRA: 8
+# `uint64(2**1)` (= 2)
+MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 2
+
+# Execution
+# ---------------------------------------------------------------
+# 2**13 (= 8192) deposit requests
+MAX_DEPOSIT_REQUESTS_PER_PAYLOAD: 8192
+# 2**4 (= 16) withdrawal requests
+MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 16
+
+# Withdrawals processing
+# ---------------------------------------------------------------
+# 2**3 ( = 8) pending withdrawals
+MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 8
+
+# Pending deposits processing
+# ---------------------------------------------------------------
+# 2**4 ( = 4) pending deposits
+MAX_PENDING_DEPOSITS_PER_EPOCH: 16
diff --git a/config/presets/minimal/deneb.yaml b/config/presets/minimal/deneb.yaml
index bc4fe4369..8dde60e86 100644
--- a/config/presets/minimal/deneb.yaml
+++ b/config/presets/minimal/deneb.yaml
@@ -5,8 +5,6 @@
# `uint64(4096)`
FIELD_ELEMENTS_PER_BLOB: 4096
# [customized]
-MAX_BLOB_COMMITMENTS_PER_BLOCK: 16
-# `uint64(6)`
-MAX_BLOBS_PER_BLOCK: 6
-# [customized] `floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK)` = 4 + 1 + 4 = 9
-KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: 9
+MAX_BLOB_COMMITMENTS_PER_BLOCK: 32
+# [customized] floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK) (= 4 + 1 + 5 = 10)
+KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: 10
diff --git a/config/presets/minimal/electra.yaml b/config/presets/minimal/electra.yaml
new file mode 100644
index 000000000..44e476975
--- /dev/null
+++ b/config/presets/minimal/electra.yaml
@@ -0,0 +1,50 @@
+# Minimal preset - Electra
+
+# Gwei values
+# ---------------------------------------------------------------
+# 2**5 * 10**9 (= 32,000,000,000) Gwei
+MIN_ACTIVATION_BALANCE: 32000000000
+# 2**11 * 10**9 (= 2,048,000,000,000) Gwei
+MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000
+
+# State list lengths
+# ---------------------------------------------------------------
+# `uint64(2**27)` (= 134,217,728)
+PENDING_DEPOSITS_LIMIT: 134217728
+# [customized] `uint64(2**6)` (= 64)
+PENDING_PARTIAL_WITHDRAWALS_LIMIT: 64
+# [customized] `uint64(2**6)` (= 64)
+PENDING_CONSOLIDATIONS_LIMIT: 64
+
+# Reward and penalty quotients
+# ---------------------------------------------------------------
+# `uint64(2**12)` (= 4,096)
+MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096
+# `uint64(2**12)` (= 4,096)
+WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096
+
+# # Max operations per block
+# ---------------------------------------------------------------
+# `uint64(2**0)` (= 1)
+MAX_ATTESTER_SLASHINGS_ELECTRA: 1
+# `uint64(2**3)` (= 8)
+MAX_ATTESTATIONS_ELECTRA: 8
+# `uint64(2**1)` (= 2)
+MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 2
+
+# Execution
+# ---------------------------------------------------------------
+# [customized]
+MAX_DEPOSIT_REQUESTS_PER_PAYLOAD: 4
+# [customized] 2**1 (= 2) withdrawal requests
+MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 2
+
+# Withdrawals processing
+# ---------------------------------------------------------------
+# 2**1 ( = 2) pending withdrawals
+MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 2
+
+# Pending deposits processing
+# ---------------------------------------------------------------
+# 2**4 ( = 4) pending deposits
+MAX_PENDING_DEPOSITS_PER_EPOCH: 16
diff --git a/docs/specs/electra/beacon-chain.md b/docs/specs/electra/beacon-chain.md
new file mode 100644
index 000000000..8f0bc9d51
--- /dev/null
+++ b/docs/specs/electra/beacon-chain.md
@@ -0,0 +1,1707 @@
+# Electra -- The Beacon Chain
+
+*Note*: This document is a work-in-progress for researchers and implementers.
+
+## Table of contents
+
+
+
+
+
+- [Introduction](#introduction)
+- [Constants](#constants)
+ - [Misc](#misc)
+ - [Withdrawal prefixes](#withdrawal-prefixes)
+ - [Execution layer triggered requests](#execution-layer-triggered-requests)
+- [Preset](#preset)
+ - [Gwei values](#gwei-values)
+ - [Rewards and penalties](#rewards-and-penalties)
+ - [State list lengths](#state-list-lengths)
+ - [Max operations per block](#max-operations-per-block)
+ - [Execution](#execution)
+ - [Withdrawals processing](#withdrawals-processing)
+ - [Pending deposits processing](#pending-deposits-processing)
+- [Configuration](#configuration)
+ - [Execution](#execution-1)
+ - [Validator cycle](#validator-cycle)
+- [Containers](#containers)
+ - [New containers](#new-containers)
+ - [`PendingDeposit`](#pendingdeposit)
+ - [`PendingPartialWithdrawal`](#pendingpartialwithdrawal)
+ - [`PendingConsolidation`](#pendingconsolidation)
+ - [`DepositRequest`](#depositrequest)
+ - [`WithdrawalRequest`](#withdrawalrequest)
+ - [`ConsolidationRequest`](#consolidationrequest)
+ - [`ExecutionRequests`](#executionrequests)
+ - [`SingleAttestation`](#singleattestation)
+ - [Modified containers](#modified-containers)
+ - [`AttesterSlashing`](#attesterslashing)
+ - [`BeaconBlockBody`](#beaconblockbody)
+ - [Modified containers](#modified-containers-1)
+ - [`Attestation`](#attestation)
+ - [`IndexedAttestation`](#indexedattestation)
+ - [`BeaconState`](#beaconstate)
+- [Helper functions](#helper-functions)
+ - [Predicates](#predicates)
+ - [Modified `compute_proposer_index`](#modified-compute_proposer_index)
+ - [Modified `is_eligible_for_activation_queue`](#modified-is_eligible_for_activation_queue)
+ - [New `is_compounding_withdrawal_credential`](#new-is_compounding_withdrawal_credential)
+ - [New `has_compounding_withdrawal_credential`](#new-has_compounding_withdrawal_credential)
+ - [New `has_execution_withdrawal_credential`](#new-has_execution_withdrawal_credential)
+ - [Modified `is_fully_withdrawable_validator`](#modified-is_fully_withdrawable_validator)
+ - [Modified `is_partially_withdrawable_validator`](#modified-is_partially_withdrawable_validator)
+ - [Misc](#misc-1)
+ - [New `get_committee_indices`](#new-get_committee_indices)
+ - [New `get_max_effective_balance`](#new-get_max_effective_balance)
+ - [Beacon state accessors](#beacon-state-accessors)
+ - [New `get_balance_churn_limit`](#new-get_balance_churn_limit)
+ - [New `get_activation_exit_churn_limit`](#new-get_activation_exit_churn_limit)
+ - [New `get_consolidation_churn_limit`](#new-get_consolidation_churn_limit)
+ - [New `get_pending_balance_to_withdraw`](#new-get_pending_balance_to_withdraw)
+ - [Modified `get_attesting_indices`](#modified-get_attesting_indices)
+ - [Modified `get_next_sync_committee_indices`](#modified-get_next_sync_committee_indices)
+ - [Beacon state mutators](#beacon-state-mutators)
+ - [Modified `initiate_validator_exit`](#modified-initiate_validator_exit)
+ - [New `switch_to_compounding_validator`](#new-switch_to_compounding_validator)
+ - [New `queue_excess_active_balance`](#new-queue_excess_active_balance)
+ - [New `compute_exit_epoch_and_update_churn`](#new-compute_exit_epoch_and_update_churn)
+ - [New `compute_consolidation_epoch_and_update_churn`](#new-compute_consolidation_epoch_and_update_churn)
+ - [Modified `slash_validator`](#modified-slash_validator)
+- [Beacon chain state transition function](#beacon-chain-state-transition-function)
+ - [Epoch processing](#epoch-processing)
+ - [Modified `process_epoch`](#modified-process_epoch)
+ - [Modified `process_registry_updates`](#modified-process_registry_updates)
+ - [Modified `process_slashings`](#modified-process_slashings)
+ - [New `apply_pending_deposit`](#new-apply_pending_deposit)
+ - [New `process_pending_deposits`](#new-process_pending_deposits)
+ - [New `process_pending_consolidations`](#new-process_pending_consolidations)
+ - [Modified `process_effective_balance_updates`](#modified-process_effective_balance_updates)
+ - [Execution engine](#execution-engine)
+ - [Request data](#request-data)
+ - [Modified `NewPayloadRequest`](#modified-newpayloadrequest)
+ - [Engine APIs](#engine-apis)
+ - [Modified `is_valid_block_hash`](#modified-is_valid_block_hash)
+ - [Modified `notify_new_payload`](#modified-notify_new_payload)
+ - [Modified `verify_and_notify_new_payload`](#modified-verify_and_notify_new_payload)
+ - [Block processing](#block-processing)
+ - [Withdrawals](#withdrawals)
+ - [Modified `get_expected_withdrawals`](#modified-get_expected_withdrawals)
+ - [Modified `process_withdrawals`](#modified-process_withdrawals)
+ - [Execution payload](#execution-payload)
+ - [New `get_execution_requests_list`](#new-get_execution_requests_list)
+ - [Modified `process_execution_payload`](#modified-process_execution_payload)
+ - [Operations](#operations)
+ - [Modified `process_operations`](#modified-process_operations)
+ - [Attestations](#attestations)
+ - [Modified `process_attestation`](#modified-process_attestation)
+ - [Deposits](#deposits)
+ - [Modified `get_validator_from_deposit`](#modified-get_validator_from_deposit)
+ - [Modified `add_validator_to_registry`](#modified-add_validator_to_registry)
+ - [Modified `apply_deposit`](#modified-apply_deposit)
+ - [New `is_valid_deposit_signature`](#new-is_valid_deposit_signature)
+ - [Modified `process_deposit`](#modified-process_deposit)
+ - [Voluntary exits](#voluntary-exits)
+ - [Modified `process_voluntary_exit`](#modified-process_voluntary_exit)
+ - [Execution layer withdrawal requests](#execution-layer-withdrawal-requests)
+ - [New `process_withdrawal_request`](#new-process_withdrawal_request)
+ - [Deposit requests](#deposit-requests)
+ - [New `process_deposit_request`](#new-process_deposit_request)
+ - [Execution layer consolidation requests](#execution-layer-consolidation-requests)
+ - [New `is_valid_switch_to_compounding_request`](#new-is_valid_switch_to_compounding_request)
+ - [New `process_consolidation_request`](#new-process_consolidation_request)
+
+
+
+
+## Introduction
+
+Electra is a consensus-layer upgrade containing a number of features. Including:
+
+* [EIP-6110](https://eips.ethereum.org/EIPS/eip-6110): Supply validator deposits on chain
+* [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002): Execution layer triggerable exits
+* [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251): Increase the MAX_EFFECTIVE_BALANCE
+* [EIP-7549](https://eips.ethereum.org/EIPS/eip-7549): Move committee index outside Attestation
+* [EIP-7691](https://eips.ethereum.org/EIPS/eip-7691): Blob throughput increase
+
+*Note*: This specification is built upon [Deneb](../deneb/beacon-chain.md) and is under active development.
+
+## Constants
+
+The following values are (non-configurable) constants used throughout the specification.
+
+### Misc
+
+| Name | Value | Description |
+| - | - | - |
+| `UNSET_DEPOSIT_REQUESTS_START_INDEX` | `uint64(2**64 - 1)` | *[New in Electra:EIP6110]* Value which indicates no start index has been assigned |
+| `FULL_EXIT_REQUEST_AMOUNT` | `uint64(0)` | *[New in Electra:EIP7002]* Withdrawal amount used to signal a full validator exit |
+
+### Withdrawal prefixes
+
+| Name | Value | Description |
+| - | - | - |
+| `COMPOUNDING_WITHDRAWAL_PREFIX` | `Bytes1('0x02')` | *[New in Electra:EIP7251]* Withdrawal credential prefix for a compounding validator |
+
+### Execution layer triggered requests
+
+| Name | Value |
+| - | - |
+| `DEPOSIT_REQUEST_TYPE` | `Bytes1('0x00')` |
+| `WITHDRAWAL_REQUEST_TYPE` | `Bytes1('0x01')` |
+| `CONSOLIDATION_REQUEST_TYPE` | `Bytes1('0x02')` |
+
+## Preset
+
+### Gwei values
+
+| Name | Value | Description |
+| - | - | - |
+| `MIN_ACTIVATION_BALANCE` | `Gwei(2**5 * 10**9)` (= 32,000,000,000) | *[New in Electra:EIP7251]* Minimum balance for a validator to become active |
+| `MAX_EFFECTIVE_BALANCE_ELECTRA` | `Gwei(2**11 * 10**9)` (= 2048,000,000,000) | *[New in Electra:EIP7251]* Maximum effective balance for a compounding validator |
+
+### Rewards and penalties
+
+| Name | Value |
+| - | - |
+| `MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA` | `uint64(2**12)` (= 4,096) |
+| `WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA` | `uint64(2**12)` (= 4,096) |
+
+### State list lengths
+
+| Name | Value | Unit |
+| - | - | - |
+| `PENDING_DEPOSITS_LIMIT` | `uint64(2**27)` (= 134,217,728) | pending deposits |
+| `PENDING_PARTIAL_WITHDRAWALS_LIMIT` | `uint64(2**27)` (= 134,217,728) | pending partial withdrawals |
+| `PENDING_CONSOLIDATIONS_LIMIT` | `uint64(2**18)` (= 262,144) | pending consolidations |
+
+### Max operations per block
+
+| Name | Value |
+| - | - |
+| `MAX_ATTESTER_SLASHINGS_ELECTRA` | `2**0` (= 1) |
+| `MAX_ATTESTATIONS_ELECTRA` | `2**3` (= 8) |
+
+### Execution
+
+| Name | Value | Description |
+| - | - | - |
+| `MAX_DEPOSIT_REQUESTS_PER_PAYLOAD` | `uint64(2**13)` (= 8,192) | *[New in Electra:EIP6110]* Maximum number of execution layer deposit requests in each payload |
+| `MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD` | `uint64(2**4)` (= 16)| *[New in Electra:EIP7002]* Maximum number of execution layer withdrawal requests in each payload |
+| `MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD` | `uint64(2**1)` (= 2) | *[New in Electra:EIP7251]* Maximum number of execution layer consolidation requests in each payload |
+
+### Withdrawals processing
+
+| Name | Value | Description |
+| - | - | - |
+| `MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP` | `uint64(2**3)` (= 8)| *[New in Electra:EIP7002]* Maximum number of pending partial withdrawals to process per payload |
+
+### Pending deposits processing
+
+| Name | Value | Description |
+| - | - | - |
+| `MAX_PENDING_DEPOSITS_PER_EPOCH` | `uint64(2**4)` (= 16)| *[New in Electra:EIP6110]* Maximum number of pending deposits to process per epoch |
+
+## Configuration
+
+### Execution
+
+| Name | Value | Description |
+| - | - | - |
+| `MAX_BLOBS_PER_BLOCK_ELECTRA` | `uint64(9)` | *[New in Electra:EIP7691]* Maximum number of blobs in a single block limited by `MAX_BLOB_COMMITMENTS_PER_BLOCK` |
+
+### Validator cycle
+
+| Name | Value |
+| - | - |
+| `MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA` | `Gwei(2**7 * 10**9)` (= 128,000,000,000) | # Equivalent to 4 32 ETH validators
+| `MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT` | `Gwei(2**8 * 10**9)` (= 256,000,000,000) |
+
+## Containers
+
+### New containers
+
+#### `PendingDeposit`
+
+*Note*: The container is new in EIP7251.
+
+```python
+class PendingDeposit(Container):
+ pubkey: BLSPubkey
+ withdrawal_credentials: Bytes32
+ amount: Gwei
+ signature: BLSSignature
+ slot: Slot
+```
+
+#### `PendingPartialWithdrawal`
+
+*Note*: The container is new in EIP7251.
+
+```python
+class PendingPartialWithdrawal(Container):
+ validator_index: ValidatorIndex
+ amount: Gwei
+ withdrawable_epoch: Epoch
+```
+
+#### `PendingConsolidation`
+
+*Note*: The container is new in EIP7251.
+
+```python
+class PendingConsolidation(Container):
+ source_index: ValidatorIndex
+ target_index: ValidatorIndex
+```
+
+#### `DepositRequest`
+
+*Note*: The container is new in EIP6110.
+
+```python
+class DepositRequest(Container):
+ pubkey: BLSPubkey
+ withdrawal_credentials: Bytes32
+ amount: Gwei
+ signature: BLSSignature
+ index: uint64
+```
+
+#### `WithdrawalRequest`
+
+*Note*: The container is new in EIP7251:EIP7002.
+
+```python
+class WithdrawalRequest(Container):
+ source_address: ExecutionAddress
+ validator_pubkey: BLSPubkey
+ amount: Gwei
+```
+
+#### `ConsolidationRequest`
+
+*Note*: The container is new in EIP7251.
+
+```python
+class ConsolidationRequest(Container):
+ source_address: ExecutionAddress
+ source_pubkey: BLSPubkey
+ target_pubkey: BLSPubkey
+```
+
+#### `ExecutionRequests`
+
+```python
+class ExecutionRequests(Container):
+ deposits: List[DepositRequest, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD] # [New in Electra:EIP6110]
+ withdrawals: List[WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD] # [New in Electra:EIP7002:EIP7251]
+ consolidations: List[ConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD] # [New in Electra:EIP7251]
+```
+
+#### `SingleAttestation`
+
+```python
+class SingleAttestation(Container):
+ committee_index: CommitteeIndex
+ attester_index: ValidatorIndex
+ data: AttestationData
+ signature: BLSSignature
+```
+
+### Modified containers
+
+#### `AttesterSlashing`
+
+```python
+class AttesterSlashing(Container):
+ attestation_1: IndexedAttestation # [Modified in Electra:EIP7549]
+ attestation_2: IndexedAttestation # [Modified in Electra:EIP7549]
+```
+
+#### `BeaconBlockBody`
+
+```python
+class BeaconBlockBody(Container):
+ randao_reveal: BLSSignature
+ eth1_data: Eth1Data # Eth1 data vote
+ graffiti: Bytes32 # Arbitrary data
+ # Operations
+ proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS]
+ attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS_ELECTRA] # [Modified in Electra:EIP7549]
+ attestations: List[Attestation, MAX_ATTESTATIONS_ELECTRA] # [Modified in Electra:EIP7549]
+ deposits: List[Deposit, MAX_DEPOSITS]
+ voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS]
+ sync_aggregate: SyncAggregate
+ # Execution
+ execution_payload: ExecutionPayload
+ bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES]
+ blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]
+ execution_requests: ExecutionRequests # [New in Electra]
+```
+
+### Modified containers
+
+#### `Attestation`
+
+```python
+class Attestation(Container):
+ aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT] # [Modified in Electra:EIP7549]
+ data: AttestationData
+ signature: BLSSignature
+ committee_bits: Bitvector[MAX_COMMITTEES_PER_SLOT] # [New in Electra:EIP7549]
+```
+
+#### `IndexedAttestation`
+
+```python
+class IndexedAttestation(Container):
+ # [Modified in Electra:EIP7549]
+ attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]
+ data: AttestationData
+ signature: BLSSignature
+```
+
+#### `BeaconState`
+
+```python
+class BeaconState(Container):
+ # Versioning
+ genesis_time: uint64
+ genesis_validators_root: Root
+ slot: Slot
+ fork: Fork
+ # History
+ latest_block_header: BeaconBlockHeader
+ block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
+ state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
+ historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT]
+ # Eth1
+ eth1_data: Eth1Data
+ eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH]
+ eth1_deposit_index: uint64
+ # Registry
+ validators: List[Validator, VALIDATOR_REGISTRY_LIMIT]
+ balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT]
+ # Randomness
+ randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR]
+ # Slashings
+ slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances
+ # Participation
+ previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT]
+ current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT]
+ # Finality
+ justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch
+ previous_justified_checkpoint: Checkpoint
+ current_justified_checkpoint: Checkpoint
+ finalized_checkpoint: Checkpoint
+ # Inactivity
+ inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT]
+ # Sync
+ current_sync_committee: SyncCommittee
+ next_sync_committee: SyncCommittee
+ # Execution
+ latest_execution_payload_header: ExecutionPayloadHeader
+ # Withdrawals
+ next_withdrawal_index: WithdrawalIndex
+ next_withdrawal_validator_index: ValidatorIndex
+ # Deep history valid from Capella onwards
+ historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT]
+ deposit_requests_start_index: uint64 # [New in Electra:EIP6110]
+ deposit_balance_to_consume: Gwei # [New in Electra:EIP7251]
+ exit_balance_to_consume: Gwei # [New in Electra:EIP7251]
+ earliest_exit_epoch: Epoch # [New in Electra:EIP7251]
+ consolidation_balance_to_consume: Gwei # [New in Electra:EIP7251]
+ earliest_consolidation_epoch: Epoch # [New in Electra:EIP7251]
+ pending_deposits: List[PendingDeposit, PENDING_DEPOSITS_LIMIT] # [New in Electra:EIP7251]
+ # [New in Electra:EIP7251]
+ pending_partial_withdrawals: List[PendingPartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT]
+ pending_consolidations: List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT] # [New in Electra:EIP7251]
+```
+
+## Helper functions
+
+### Predicates
+
+#### Modified `compute_proposer_index`
+
+*Note*: The function `compute_proposer_index` is modified to use `MAX_EFFECTIVE_BALANCE_ELECTRA` and to use a 16-bit random value instead of an 8-bit random byte in the effective balance filter.
+
+```python
+def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex], seed: Bytes32) -> ValidatorIndex:
+ """
+ Return from ``indices`` a random index sampled by effective balance.
+ """
+ assert len(indices) > 0
+ MAX_RANDOM_VALUE = 2**16 - 1 # [Modified in Electra]
+ i = uint64(0)
+ total = uint64(len(indices))
+ while True:
+ candidate_index = indices[compute_shuffled_index(i % total, total, seed)]
+ # [Modified in Electra]
+ random_bytes = hash(seed + uint_to_bytes(i // 16))
+ offset = i % 16 * 2
+ random_value = bytes_to_uint64(random_bytes[offset:offset + 2])
+ effective_balance = state.validators[candidate_index].effective_balance
+ # [Modified in Electra:EIP7251]
+ if effective_balance * MAX_RANDOM_VALUE >= MAX_EFFECTIVE_BALANCE_ELECTRA * random_value:
+ return candidate_index
+ i += 1
+```
+
+#### Modified `is_eligible_for_activation_queue`
+
+*Note*: The function `is_eligible_for_activation_queue` is modified to use `MIN_ACTIVATION_BALANCE` instead of `MAX_EFFECTIVE_BALANCE`.
+
+```python
+def is_eligible_for_activation_queue(validator: Validator) -> bool:
+ """
+ Check if ``validator`` is eligible to be placed into the activation queue.
+ """
+ return (
+ validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH
+ and validator.effective_balance >= MIN_ACTIVATION_BALANCE # [Modified in Electra:EIP7251]
+ )
+```
+
+#### New `is_compounding_withdrawal_credential`
+
+```python
+def is_compounding_withdrawal_credential(withdrawal_credentials: Bytes32) -> bool:
+ return withdrawal_credentials[:1] == COMPOUNDING_WITHDRAWAL_PREFIX
+```
+
+#### New `has_compounding_withdrawal_credential`
+
+```python
+def has_compounding_withdrawal_credential(validator: Validator) -> bool:
+ """
+ Check if ``validator`` has an 0x02 prefixed "compounding" withdrawal credential.
+ """
+ return is_compounding_withdrawal_credential(validator.withdrawal_credentials)
+```
+
+#### New `has_execution_withdrawal_credential`
+
+```python
+def has_execution_withdrawal_credential(validator: Validator) -> bool:
+ """
+ Check if ``validator`` has a 0x01 or 0x02 prefixed withdrawal credential.
+ """
+ return has_compounding_withdrawal_credential(validator) or has_eth1_withdrawal_credential(validator)
+```
+
+#### Modified `is_fully_withdrawable_validator`
+
+*Note*: The function `is_fully_withdrawable_validator` is modified to use `has_execution_withdrawal_credential` instead of `has_eth1_withdrawal_credential`.
+
+```python
+def is_fully_withdrawable_validator(validator: Validator, balance: Gwei, epoch: Epoch) -> bool:
+ """
+ Check if ``validator`` is fully withdrawable.
+ """
+ return (
+ has_execution_withdrawal_credential(validator) # [Modified in Electra:EIP7251]
+ and validator.withdrawable_epoch <= epoch
+ and balance > 0
+ )
+```
+
+#### Modified `is_partially_withdrawable_validator`
+
+*Note*: The function `is_partially_withdrawable_validator` is modified to use `get_max_effective_balance` instead of `MAX_EFFECTIVE_BALANCE` and `has_execution_withdrawal_credential` instead of `has_eth1_withdrawal_credential`.
+
+```python
+def is_partially_withdrawable_validator(validator: Validator, balance: Gwei) -> bool:
+ """
+ Check if ``validator`` is partially withdrawable.
+ """
+ max_effective_balance = get_max_effective_balance(validator)
+ has_max_effective_balance = validator.effective_balance == max_effective_balance # [Modified in Electra:EIP7251]
+ has_excess_balance = balance > max_effective_balance # [Modified in Electra:EIP7251]
+ return (
+ has_execution_withdrawal_credential(validator) # [Modified in Electra:EIP7251]
+ and has_max_effective_balance
+ and has_excess_balance
+ )
+```
+
+### Misc
+
+#### New `get_committee_indices`
+
+```python
+def get_committee_indices(committee_bits: Bitvector) -> Sequence[CommitteeIndex]:
+ return [CommitteeIndex(index) for index, bit in enumerate(committee_bits) if bit]
+```
+
+#### New `get_max_effective_balance`
+
+```python
+def get_max_effective_balance(validator: Validator) -> Gwei:
+ """
+ Get max effective balance for ``validator``.
+ """
+ if has_compounding_withdrawal_credential(validator):
+ return MAX_EFFECTIVE_BALANCE_ELECTRA
+ else:
+ return MIN_ACTIVATION_BALANCE
+```
+
+### Beacon state accessors
+
+#### New `get_balance_churn_limit`
+
+```python
+def get_balance_churn_limit(state: BeaconState) -> Gwei:
+ """
+ Return the churn limit for the current epoch.
+ """
+ churn = max(
+ MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA,
+ get_total_active_balance(state) // CHURN_LIMIT_QUOTIENT
+ )
+ return churn - churn % EFFECTIVE_BALANCE_INCREMENT
+```
+
+#### New `get_activation_exit_churn_limit`
+
+```python
+def get_activation_exit_churn_limit(state: BeaconState) -> Gwei:
+ """
+ Return the churn limit for the current epoch dedicated to activations and exits.
+ """
+ return min(MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT, get_balance_churn_limit(state))
+```
+
+#### New `get_consolidation_churn_limit`
+
+```python
+def get_consolidation_churn_limit(state: BeaconState) -> Gwei:
+ return get_balance_churn_limit(state) - get_activation_exit_churn_limit(state)
+```
+
+#### New `get_pending_balance_to_withdraw`
+
+```python
+def get_pending_balance_to_withdraw(state: BeaconState, validator_index: ValidatorIndex) -> Gwei:
+ return sum(
+ withdrawal.amount for withdrawal in state.pending_partial_withdrawals
+ if withdrawal.validator_index == validator_index
+ )
+```
+
+#### Modified `get_attesting_indices`
+
+*Note*: The function `get_attesting_indices` is modified to support EIP7549.
+
+```python
+def get_attesting_indices(state: BeaconState, attestation: Attestation) -> Set[ValidatorIndex]:
+ """
+ Return the set of attesting indices corresponding to ``aggregation_bits`` and ``committee_bits``.
+ """
+ output: Set[ValidatorIndex] = set()
+ committee_indices = get_committee_indices(attestation.committee_bits)
+ committee_offset = 0
+ for committee_index in committee_indices:
+ committee = get_beacon_committee(state, attestation.data.slot, committee_index)
+ committee_attesters = set(
+ attester_index for i, attester_index in enumerate(committee)
+ if attestation.aggregation_bits[committee_offset + i]
+ )
+ output = output.union(committee_attesters)
+
+ committee_offset += len(committee)
+
+ return output
+```
+
+#### Modified `get_next_sync_committee_indices`
+
+*Note*: The function `get_next_sync_committee_indices` is modified to use `MAX_EFFECTIVE_BALANCE_ELECTRA` and to use a 16-bit random value instead of an 8-bit random byte in the effective balance filter.
+
+```python
+def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorIndex]:
+ """
+ Return the sync committee indices, with possible duplicates, for the next sync committee.
+ """
+ epoch = Epoch(get_current_epoch(state) + 1)
+
+ MAX_RANDOM_VALUE = 2**16 - 1 # [Modified in Electra]
+ active_validator_indices = get_active_validator_indices(state, epoch)
+ active_validator_count = uint64(len(active_validator_indices))
+ seed = get_seed(state, epoch, DOMAIN_SYNC_COMMITTEE)
+ i = uint64(0)
+ sync_committee_indices: List[ValidatorIndex] = []
+ while len(sync_committee_indices) < SYNC_COMMITTEE_SIZE:
+ shuffled_index = compute_shuffled_index(uint64(i % active_validator_count), active_validator_count, seed)
+ candidate_index = active_validator_indices[shuffled_index]
+ # [Modified in Electra]
+ random_bytes = hash(seed + uint_to_bytes(i // 16))
+ offset = i % 16 * 2
+ random_value = bytes_to_uint64(random_bytes[offset:offset + 2])
+ effective_balance = state.validators[candidate_index].effective_balance
+ # [Modified in Electra:EIP7251]
+ if effective_balance * MAX_RANDOM_VALUE >= MAX_EFFECTIVE_BALANCE_ELECTRA * random_value:
+ sync_committee_indices.append(candidate_index)
+ i += 1
+ return sync_committee_indices
+```
+
+### Beacon state mutators
+
+#### Modified `initiate_validator_exit`
+
+*Note*: The function `initiate_validator_exit` is modified to use the new `compute_exit_epoch_and_update_churn` function.
+
+```python
+def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None:
+ """
+ Initiate the exit of the validator with index ``index``.
+ """
+ # Return if validator already initiated exit
+ validator = state.validators[index]
+ if validator.exit_epoch != FAR_FUTURE_EPOCH:
+ return
+
+ # Compute exit queue epoch [Modified in Electra:EIP7251]
+ exit_queue_epoch = compute_exit_epoch_and_update_churn(state, validator.effective_balance)
+
+ # Set validator exit epoch and withdrawable epoch
+ validator.exit_epoch = exit_queue_epoch
+ validator.withdrawable_epoch = Epoch(validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY)
+```
+
+#### New `switch_to_compounding_validator`
+
+```python
+def switch_to_compounding_validator(state: BeaconState, index: ValidatorIndex) -> None:
+ validator = state.validators[index]
+ validator.withdrawal_credentials = COMPOUNDING_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:]
+ queue_excess_active_balance(state, index)
+```
+
+#### New `queue_excess_active_balance`
+
+```python
+def queue_excess_active_balance(state: BeaconState, index: ValidatorIndex) -> None:
+ balance = state.balances[index]
+ if balance > MIN_ACTIVATION_BALANCE:
+ excess_balance = balance - MIN_ACTIVATION_BALANCE
+ state.balances[index] = MIN_ACTIVATION_BALANCE
+ validator = state.validators[index]
+ # Use bls.G2_POINT_AT_INFINITY as a signature field placeholder
+ # and GENESIS_SLOT to distinguish from a pending deposit request
+ state.pending_deposits.append(PendingDeposit(
+ pubkey=validator.pubkey,
+ withdrawal_credentials=validator.withdrawal_credentials,
+ amount=excess_balance,
+ signature=bls.G2_POINT_AT_INFINITY,
+ slot=GENESIS_SLOT,
+ ))
+```
+
+#### New `compute_exit_epoch_and_update_churn`
+
+```python
+def compute_exit_epoch_and_update_churn(state: BeaconState, exit_balance: Gwei) -> Epoch:
+ earliest_exit_epoch = max(state.earliest_exit_epoch, compute_activation_exit_epoch(get_current_epoch(state)))
+ per_epoch_churn = get_activation_exit_churn_limit(state)
+ # New epoch for exits.
+ if state.earliest_exit_epoch < earliest_exit_epoch:
+ exit_balance_to_consume = per_epoch_churn
+ else:
+ exit_balance_to_consume = state.exit_balance_to_consume
+
+ # Exit doesn't fit in the current earliest epoch.
+ if exit_balance > exit_balance_to_consume:
+ balance_to_process = exit_balance - exit_balance_to_consume
+ additional_epochs = (balance_to_process - 1) // per_epoch_churn + 1
+ earliest_exit_epoch += additional_epochs
+ exit_balance_to_consume += additional_epochs * per_epoch_churn
+
+ # Consume the balance and update state variables.
+ state.exit_balance_to_consume = exit_balance_to_consume - exit_balance
+ state.earliest_exit_epoch = earliest_exit_epoch
+
+ return state.earliest_exit_epoch
+```
+
+#### New `compute_consolidation_epoch_and_update_churn`
+
+```python
+def compute_consolidation_epoch_and_update_churn(state: BeaconState, consolidation_balance: Gwei) -> Epoch:
+ earliest_consolidation_epoch = max(
+ state.earliest_consolidation_epoch, compute_activation_exit_epoch(get_current_epoch(state)))
+ per_epoch_consolidation_churn = get_consolidation_churn_limit(state)
+ # New epoch for consolidations.
+ if state.earliest_consolidation_epoch < earliest_consolidation_epoch:
+ consolidation_balance_to_consume = per_epoch_consolidation_churn
+ else:
+ consolidation_balance_to_consume = state.consolidation_balance_to_consume
+
+ # Consolidation doesn't fit in the current earliest epoch.
+ if consolidation_balance > consolidation_balance_to_consume:
+ balance_to_process = consolidation_balance - consolidation_balance_to_consume
+ additional_epochs = (balance_to_process - 1) // per_epoch_consolidation_churn + 1
+ earliest_consolidation_epoch += additional_epochs
+ consolidation_balance_to_consume += additional_epochs * per_epoch_consolidation_churn
+
+ # Consume the balance and update state variables.
+ state.consolidation_balance_to_consume = consolidation_balance_to_consume - consolidation_balance
+ state.earliest_consolidation_epoch = earliest_consolidation_epoch
+
+ return state.earliest_consolidation_epoch
+```
+
+#### Modified `slash_validator`
+
+*Note*: The function `slash_validator` is modified to change how the slashing penalty and proposer/whistleblower rewards are calculated in accordance with EIP7251.
+
+```python
+def slash_validator(state: BeaconState,
+ slashed_index: ValidatorIndex,
+ whistleblower_index: ValidatorIndex=None) -> None:
+ """
+ Slash the validator with index ``slashed_index``.
+ """
+ epoch = get_current_epoch(state)
+ initiate_validator_exit(state, slashed_index)
+ validator = state.validators[slashed_index]
+ validator.slashed = True
+ validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR))
+ state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance
+ # [Modified in Electra:EIP7251]
+ slashing_penalty = validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA
+ decrease_balance(state, slashed_index, slashing_penalty)
+
+ # Apply proposer and whistleblower rewards
+ proposer_index = get_beacon_proposer_index(state)
+ if whistleblower_index is None:
+ whistleblower_index = proposer_index
+ whistleblower_reward = Gwei(
+ validator.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA) # [Modified in Electra:EIP7251]
+ proposer_reward = Gwei(whistleblower_reward * PROPOSER_WEIGHT // WEIGHT_DENOMINATOR)
+ increase_balance(state, proposer_index, proposer_reward)
+ increase_balance(state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward))
+```
+
+## Beacon chain state transition function
+
+### Epoch processing
+
+#### Modified `process_epoch`
+
+*Note*: The function `process_epoch` is modified to call updated functions and to process pending balance deposits and pending consolidations which are new in Electra.
+
+```python
+def process_epoch(state: BeaconState) -> None:
+ process_justification_and_finalization(state)
+ process_inactivity_updates(state)
+ process_rewards_and_penalties(state)
+ process_registry_updates(state) # [Modified in Electra:EIP7251]
+ process_slashings(state) # [Modified in Electra:EIP7251]
+ process_eth1_data_reset(state)
+ process_pending_deposits(state) # [New in Electra:EIP7251]
+ process_pending_consolidations(state) # [New in Electra:EIP7251]
+ process_effective_balance_updates(state) # [Modified in Electra:EIP7251]
+ process_slashings_reset(state)
+ process_randao_mixes_reset(state)
+ process_historical_summaries_update(state)
+ process_participation_flag_updates(state)
+ process_sync_committee_updates(state)
+```
+
+#### Modified `process_registry_updates`
+
+*Note*: The function `process_registry_updates` is modified to use the updated definitions of
+`initiate_validator_exit` and `is_eligible_for_activation_queue`, changes how the activation epochs
+are computed for eligible validators, and processes activations in the same loop as activation
+eligibility updates and ejections.
+
+```python
+def process_registry_updates(state: BeaconState) -> None:
+ current_epoch = get_current_epoch(state)
+ activation_epoch = compute_activation_exit_epoch(current_epoch)
+
+ # Process activation eligibility, ejections, and activations
+ for index, validator in enumerate(state.validators):
+ if is_eligible_for_activation_queue(validator): # [Modified in Electra:EIP7251]
+ validator.activation_eligibility_epoch = current_epoch + 1
+ elif is_active_validator(validator, current_epoch) and validator.effective_balance <= EJECTION_BALANCE:
+ initiate_validator_exit(state, ValidatorIndex(index)) # [Modified in Electra:EIP7251]
+ elif is_eligible_for_activation(state, validator):
+ validator.activation_epoch = activation_epoch
+```
+
+#### Modified `process_slashings`
+
+*Note*: The function `process_slashings` is modified to use a new algorithm to compute correlation penalty.
+
+```python
+def process_slashings(state: BeaconState) -> None:
+ epoch = get_current_epoch(state)
+ total_balance = get_total_active_balance(state)
+ adjusted_total_slashing_balance = min(
+ sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX,
+ total_balance
+ )
+ increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from total balance to avoid uint64 overflow
+ penalty_per_effective_balance_increment = adjusted_total_slashing_balance // (total_balance // increment)
+ for index, validator in enumerate(state.validators):
+ if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch:
+ effective_balance_increments = validator.effective_balance // increment
+ # [Modified in Electra:EIP7251]
+ penalty = penalty_per_effective_balance_increment * effective_balance_increments
+ decrease_balance(state, ValidatorIndex(index), penalty)
+```
+
+#### New `apply_pending_deposit`
+
+```python
+def apply_pending_deposit(state: BeaconState, deposit: PendingDeposit) -> None:
+ """
+ Applies ``deposit`` to the ``state``.
+ """
+ validator_pubkeys = [v.pubkey for v in state.validators]
+ if deposit.pubkey not in validator_pubkeys:
+ # Verify the deposit signature (proof of possession) which is not checked by the deposit contract
+ if is_valid_deposit_signature(
+ deposit.pubkey,
+ deposit.withdrawal_credentials,
+ deposit.amount,
+ deposit.signature
+ ):
+ add_validator_to_registry(state, deposit.pubkey, deposit.withdrawal_credentials, deposit.amount)
+ else:
+ validator_index = ValidatorIndex(validator_pubkeys.index(deposit.pubkey))
+ increase_balance(state, validator_index, deposit.amount)
+```
+
+#### New `process_pending_deposits`
+
+Iterating over `pending_deposits` queue this function runs the following checks before applying pending deposit:
+1. All Eth1 bridge deposits are processed before the first deposit request gets processed.
+2. Deposit position in the queue is finalized.
+3. Deposit does not exceed the `MAX_PENDING_DEPOSITS_PER_EPOCH` limit.
+4. Deposit does not exceed the activation churn limit.
+
+```python
+def process_pending_deposits(state: BeaconState) -> None:
+ next_epoch = Epoch(get_current_epoch(state) + 1)
+ available_for_processing = state.deposit_balance_to_consume + get_activation_exit_churn_limit(state)
+ processed_amount = 0
+ next_deposit_index = 0
+ deposits_to_postpone = []
+ is_churn_limit_reached = False
+ finalized_slot = compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)
+
+ for deposit in state.pending_deposits:
+ # Do not process deposit requests if Eth1 bridge deposits are not yet applied.
+ if (
+ # Is deposit request
+ deposit.slot > GENESIS_SLOT and
+ # There are pending Eth1 bridge deposits
+ state.eth1_deposit_index < state.deposit_requests_start_index
+ ):
+ break
+
+ # Check if deposit has been finalized, otherwise, stop processing.
+ if deposit.slot > finalized_slot:
+ break
+
+ # Check if number of processed deposits has not reached the limit, otherwise, stop processing.
+ if next_deposit_index >= MAX_PENDING_DEPOSITS_PER_EPOCH:
+ break
+
+ # Read validator state
+ is_validator_exited = False
+ is_validator_withdrawn = False
+ validator_pubkeys = [v.pubkey for v in state.validators]
+ if deposit.pubkey in validator_pubkeys:
+ validator = state.validators[ValidatorIndex(validator_pubkeys.index(deposit.pubkey))]
+ is_validator_exited = validator.exit_epoch < FAR_FUTURE_EPOCH
+ is_validator_withdrawn = validator.withdrawable_epoch < next_epoch
+
+ if is_validator_withdrawn:
+ # Deposited balance will never become active. Increase balance but do not consume churn
+ apply_pending_deposit(state, deposit)
+ elif is_validator_exited:
+ # Validator is exiting, postpone the deposit until after withdrawable epoch
+ deposits_to_postpone.append(deposit)
+ else:
+ # Check if deposit fits in the churn, otherwise, do no more deposit processing in this epoch.
+ is_churn_limit_reached = processed_amount + deposit.amount > available_for_processing
+ if is_churn_limit_reached:
+ break
+
+ # Consume churn and apply deposit.
+ processed_amount += deposit.amount
+ apply_pending_deposit(state, deposit)
+
+ # Regardless of how the deposit was handled, we move on in the queue.
+ next_deposit_index += 1
+
+ state.pending_deposits = state.pending_deposits[next_deposit_index:] + deposits_to_postpone
+
+ # Accumulate churn only if the churn limit has been hit.
+ if is_churn_limit_reached:
+ state.deposit_balance_to_consume = available_for_processing - processed_amount
+ else:
+ state.deposit_balance_to_consume = Gwei(0)
+```
+
+#### New `process_pending_consolidations`
+
+```python
+def process_pending_consolidations(state: BeaconState) -> None:
+ next_epoch = Epoch(get_current_epoch(state) + 1)
+ next_pending_consolidation = 0
+ for pending_consolidation in state.pending_consolidations:
+ source_validator = state.validators[pending_consolidation.source_index]
+ if source_validator.slashed:
+ next_pending_consolidation += 1
+ continue
+ if source_validator.withdrawable_epoch > next_epoch:
+ break
+
+ # Calculate the consolidated balance
+ source_effective_balance = min(
+ state.balances[pending_consolidation.source_index], source_validator.effective_balance)
+
+ # Move active balance to target. Excess balance is withdrawable.
+ decrease_balance(state, pending_consolidation.source_index, source_effective_balance)
+ increase_balance(state, pending_consolidation.target_index, source_effective_balance)
+ next_pending_consolidation += 1
+
+ state.pending_consolidations = state.pending_consolidations[next_pending_consolidation:]
+```
+
+#### Modified `process_effective_balance_updates`
+
+*Note*: The function `process_effective_balance_updates` is modified to use the new limit for the maximum effective balance.
+
+```python
+def process_effective_balance_updates(state: BeaconState) -> None:
+ # Update effective balances with hysteresis
+ for index, validator in enumerate(state.validators):
+ balance = state.balances[index]
+ HYSTERESIS_INCREMENT = uint64(EFFECTIVE_BALANCE_INCREMENT // HYSTERESIS_QUOTIENT)
+ DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER
+ UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER
+ # [Modified in Electra:EIP7251]
+ max_effective_balance = get_max_effective_balance(validator)
+
+ if (
+ balance + DOWNWARD_THRESHOLD < validator.effective_balance
+ or validator.effective_balance + UPWARD_THRESHOLD < balance
+ ):
+ validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, max_effective_balance)
+```
+
+### Execution engine
+
+#### Request data
+
+##### Modified `NewPayloadRequest`
+
+```python
+@dataclass
+class NewPayloadRequest(object):
+ execution_payload: ExecutionPayload
+ versioned_hashes: Sequence[VersionedHash]
+ parent_beacon_block_root: Root
+ execution_requests: ExecutionRequests # [New in Electra]
+```
+
+#### Engine APIs
+
+##### Modified `is_valid_block_hash`
+
+*Note*: The function `is_valid_block_hash` is modified to include the additional `execution_requests_list`.
+
+```python
+def is_valid_block_hash(self: ExecutionEngine,
+ execution_payload: ExecutionPayload,
+ parent_beacon_block_root: Root,
+ execution_requests_list: Sequence[bytes]) -> bool:
+ """
+ Return ``True`` if and only if ``execution_payload.block_hash`` is computed correctly.
+ """
+ ...
+```
+
+##### Modified `notify_new_payload`
+
+*Note*: The function `notify_new_payload` is modified to include the additional `execution_requests_list`.
+
+```python
+def notify_new_payload(self: ExecutionEngine,
+ execution_payload: ExecutionPayload,
+ parent_beacon_block_root: Root,
+ execution_requests_list: Sequence[bytes]) -> bool:
+ """
+ Return ``True`` if and only if ``execution_payload`` and ``execution_requests_list``
+ are valid with respect to ``self.execution_state``.
+ """
+ ...
+```
+
+##### Modified `verify_and_notify_new_payload`
+
+*Note*: The function `verify_and_notify_new_payload` is modified to pass the additional parameter
+`execution_requests_list` when calling `is_valid_block_hash` and `notify_new_payload` in Electra.
+
+```python
+def verify_and_notify_new_payload(self: ExecutionEngine,
+ new_payload_request: NewPayloadRequest) -> bool:
+ """
+ Return ``True`` if and only if ``new_payload_request`` is valid with respect to ``self.execution_state``.
+ """
+ execution_payload = new_payload_request.execution_payload
+ parent_beacon_block_root = new_payload_request.parent_beacon_block_root
+ execution_requests_list = get_execution_requests_list(new_payload_request.execution_requests) # [New in Electra]
+
+ if b'' in execution_payload.transactions:
+ return False
+
+ # [Modified in Electra]
+ if not self.is_valid_block_hash(
+ execution_payload,
+ parent_beacon_block_root,
+ execution_requests_list):
+ return False
+
+ if not self.is_valid_versioned_hashes(new_payload_request):
+ return False
+
+ # [Modified in Electra]
+ if not self.notify_new_payload(
+ execution_payload,
+ parent_beacon_block_root,
+ execution_requests_list):
+ return False
+
+ return True
+```
+
+### Block processing
+
+```python
+def process_block(state: BeaconState, block: BeaconBlock) -> None:
+ process_block_header(state, block)
+ process_withdrawals(state, block.body.execution_payload) # [Modified in Electra:EIP7251]
+ process_execution_payload(state, block.body, EXECUTION_ENGINE) # [Modified in Electra:EIP6110]
+ process_randao(state, block.body)
+ process_eth1_data(state, block.body)
+ process_operations(state, block.body) # [Modified in Electra:EIP6110:EIP7002:EIP7549:EIP7251]
+ process_sync_aggregate(state, block.body.sync_aggregate)
+```
+
+#### Withdrawals
+
+##### Modified `get_expected_withdrawals`
+
+*Note*: The function `get_expected_withdrawals` is modified to support EIP7251.
+
+```python
+def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal], uint64]:
+ epoch = get_current_epoch(state)
+ withdrawal_index = state.next_withdrawal_index
+ validator_index = state.next_withdrawal_validator_index
+ withdrawals: List[Withdrawal] = []
+ processed_partial_withdrawals_count = 0
+
+ # [New in Electra:EIP7251] Consume pending partial withdrawals
+ for withdrawal in state.pending_partial_withdrawals:
+ if withdrawal.withdrawable_epoch > epoch or len(withdrawals) == MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP:
+ break
+
+ validator = state.validators[withdrawal.validator_index]
+ has_sufficient_effective_balance = validator.effective_balance >= MIN_ACTIVATION_BALANCE
+ has_excess_balance = state.balances[withdrawal.validator_index] > MIN_ACTIVATION_BALANCE
+ if validator.exit_epoch == FAR_FUTURE_EPOCH and has_sufficient_effective_balance and has_excess_balance:
+ withdrawable_balance = min(
+ state.balances[withdrawal.validator_index] - MIN_ACTIVATION_BALANCE,
+ withdrawal.amount)
+ withdrawals.append(Withdrawal(
+ index=withdrawal_index,
+ validator_index=withdrawal.validator_index,
+ address=ExecutionAddress(validator.withdrawal_credentials[12:]),
+ amount=withdrawable_balance,
+ ))
+ withdrawal_index += WithdrawalIndex(1)
+
+ processed_partial_withdrawals_count += 1
+
+ # Sweep for remaining.
+ bound = min(len(state.validators), MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP)
+ for _ in range(bound):
+ validator = state.validators[validator_index]
+ # [Modified in Electra:EIP7251]
+ partially_withdrawn_balance = sum(
+ withdrawal.amount for withdrawal in withdrawals if withdrawal.validator_index == validator_index)
+ balance = state.balances[validator_index] - partially_withdrawn_balance
+ if is_fully_withdrawable_validator(validator, balance, epoch):
+ withdrawals.append(Withdrawal(
+ index=withdrawal_index,
+ validator_index=validator_index,
+ address=ExecutionAddress(validator.withdrawal_credentials[12:]),
+ amount=balance,
+ ))
+ withdrawal_index += WithdrawalIndex(1)
+ elif is_partially_withdrawable_validator(validator, balance):
+ withdrawals.append(Withdrawal(
+ index=withdrawal_index,
+ validator_index=validator_index,
+ address=ExecutionAddress(validator.withdrawal_credentials[12:]),
+ amount=balance - get_max_effective_balance(validator), # [Modified in Electra:EIP7251]
+ ))
+ withdrawal_index += WithdrawalIndex(1)
+ if len(withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD:
+ break
+ validator_index = ValidatorIndex((validator_index + 1) % len(state.validators))
+ return withdrawals, processed_partial_withdrawals_count
+```
+
+##### Modified `process_withdrawals`
+
+*Note*: The function `process_withdrawals` is modified to support EIP7251.
+
+```python
+def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None:
+ # [Modified in Electra:EIP7251]
+ expected_withdrawals, processed_partial_withdrawals_count = get_expected_withdrawals(state)
+
+ assert payload.withdrawals == expected_withdrawals
+
+ for withdrawal in expected_withdrawals:
+ decrease_balance(state, withdrawal.validator_index, withdrawal.amount)
+
+ # Update pending partial withdrawals [New in Electra:EIP7251]
+ state.pending_partial_withdrawals = state.pending_partial_withdrawals[processed_partial_withdrawals_count:]
+
+ # Update the next withdrawal index if this block contained withdrawals
+ if len(expected_withdrawals) != 0:
+ latest_withdrawal = expected_withdrawals[-1]
+ state.next_withdrawal_index = WithdrawalIndex(latest_withdrawal.index + 1)
+
+ # Update the next validator index to start the next withdrawal sweep
+ if len(expected_withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD:
+ # Next sweep starts after the latest withdrawal's validator index
+ next_validator_index = ValidatorIndex((expected_withdrawals[-1].validator_index + 1) % len(state.validators))
+ state.next_withdrawal_validator_index = next_validator_index
+ else:
+ # Advance sweep by the max length of the sweep if there was not a full set of withdrawals
+ next_index = state.next_withdrawal_validator_index + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP
+ next_validator_index = ValidatorIndex(next_index % len(state.validators))
+ state.next_withdrawal_validator_index = next_validator_index
+```
+
+#### Execution payload
+
+##### New `get_execution_requests_list`
+
+*Note*: Encodes execution requests as defined by [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685).
+
+```python
+def get_execution_requests_list(execution_requests: ExecutionRequests) -> Sequence[bytes]:
+ requests = [
+ (DEPOSIT_REQUEST_TYPE, execution_requests.deposits),
+ (WITHDRAWAL_REQUEST_TYPE, execution_requests.withdrawals),
+ (CONSOLIDATION_REQUEST_TYPE, execution_requests.consolidations),
+ ]
+
+ return [
+ request_type + ssz_serialize(request_data)
+ for request_type, request_data in requests
+ if len(request_data) != 0
+ ]
+```
+
+##### Modified `process_execution_payload`
+
+*Note*: The function `process_execution_payload` is modified to pass `execution_requests` into `execution_engine.verify_and_notify_new_payload` (via the updated `NewPayloadRequest`).
+
+```python
+def process_execution_payload(state: BeaconState, body: BeaconBlockBody, execution_engine: ExecutionEngine) -> None:
+ payload = body.execution_payload
+
+ # Verify consistency of the parent hash with respect to the previous execution payload header
+ assert payload.parent_hash == state.latest_execution_payload_header.block_hash
+ # Verify prev_randao
+ assert payload.prev_randao == get_randao_mix(state, get_current_epoch(state))
+ # Verify timestamp
+ assert payload.timestamp == compute_timestamp_at_slot(state, state.slot)
+ # Verify commitments are under limit
+ assert len(body.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK_ELECTRA # [Modified in Electra:EIP7691]
+ # Verify the execution payload is valid
+ versioned_hashes = [kzg_commitment_to_versioned_hash(commitment) for commitment in body.blob_kzg_commitments]
+ assert execution_engine.verify_and_notify_new_payload(
+ NewPayloadRequest(
+ execution_payload=payload,
+ versioned_hashes=versioned_hashes,
+ parent_beacon_block_root=state.latest_block_header.parent_root,
+ execution_requests=body.execution_requests, # [New in Electra]
+ )
+ )
+ # Cache execution payload header
+ state.latest_execution_payload_header = ExecutionPayloadHeader(
+ parent_hash=payload.parent_hash,
+ fee_recipient=payload.fee_recipient,
+ state_root=payload.state_root,
+ receipts_root=payload.receipts_root,
+ logs_bloom=payload.logs_bloom,
+ prev_randao=payload.prev_randao,
+ block_number=payload.block_number,
+ gas_limit=payload.gas_limit,
+ gas_used=payload.gas_used,
+ timestamp=payload.timestamp,
+ extra_data=payload.extra_data,
+ base_fee_per_gas=payload.base_fee_per_gas,
+ block_hash=payload.block_hash,
+ transactions_root=hash_tree_root(payload.transactions),
+ withdrawals_root=hash_tree_root(payload.withdrawals),
+ blob_gas_used=payload.blob_gas_used,
+ excess_blob_gas=payload.excess_blob_gas,
+ )
+```
+
+#### Operations
+
+##### Modified `process_operations`
+
+*Note*: The function `process_operations` is modified to support all of the new functionality in Electra.
+
+```python
+def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
+ # [Modified in Electra:EIP6110]
+ # Disable former deposit mechanism once all prior deposits are processed
+ eth1_deposit_index_limit = min(state.eth1_data.deposit_count, state.deposit_requests_start_index)
+ if state.eth1_deposit_index < eth1_deposit_index_limit:
+ assert len(body.deposits) == min(MAX_DEPOSITS, eth1_deposit_index_limit - state.eth1_deposit_index)
+ else:
+ assert len(body.deposits) == 0
+
+ def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None:
+ for operation in operations:
+ fn(state, operation)
+
+ for_ops(body.proposer_slashings, process_proposer_slashing)
+ for_ops(body.attester_slashings, process_attester_slashing)
+ for_ops(body.attestations, process_attestation) # [Modified in Electra:EIP7549]
+ for_ops(body.deposits, process_deposit)
+ for_ops(body.voluntary_exits, process_voluntary_exit) # [Modified in Electra:EIP7251]
+ for_ops(body.bls_to_execution_changes, process_bls_to_execution_change)
+ for_ops(body.execution_requests.deposits, process_deposit_request) # [New in Electra:EIP6110]
+ for_ops(body.execution_requests.withdrawals, process_withdrawal_request) # [New in Electra:EIP7002:EIP7251]
+ for_ops(body.execution_requests.consolidations, process_consolidation_request) # [New in Electra:EIP7251]
+```
+
+##### Attestations
+
+###### Modified `process_attestation`
+
+*Note*: The function is modified to support EIP7549.
+
+```python
+def process_attestation(state: BeaconState, attestation: Attestation) -> None:
+ data = attestation.data
+ assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state))
+ assert data.target.epoch == compute_epoch_at_slot(data.slot)
+ assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot
+
+ # [Modified in Electra:EIP7549]
+ assert data.index == 0
+ committee_indices = get_committee_indices(attestation.committee_bits)
+ committee_offset = 0
+ for committee_index in committee_indices:
+ assert committee_index < get_committee_count_per_slot(state, data.target.epoch)
+ committee = get_beacon_committee(state, data.slot, committee_index)
+ committee_attesters = set(
+ attester_index for i, attester_index in enumerate(committee)
+ if attestation.aggregation_bits[committee_offset + i]
+ )
+ assert len(committee_attesters) > 0
+ committee_offset += len(committee)
+
+ # Bitfield length matches total number of participants
+ assert len(attestation.aggregation_bits) == committee_offset
+
+ # Participation flag indices
+ participation_flag_indices = get_attestation_participation_flag_indices(state, data, state.slot - data.slot)
+
+ # Verify signature
+ assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation))
+
+ # Update epoch participation flags
+ if data.target.epoch == get_current_epoch(state):
+ epoch_participation = state.current_epoch_participation
+ else:
+ epoch_participation = state.previous_epoch_participation
+
+ proposer_reward_numerator = 0
+ for index in get_attesting_indices(state, attestation):
+ for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS):
+ if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index):
+ epoch_participation[index] = add_flag(epoch_participation[index], flag_index)
+ proposer_reward_numerator += get_base_reward(state, index) * weight
+
+ # Reward proposer
+ proposer_reward_denominator = (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT
+ proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator)
+ increase_balance(state, get_beacon_proposer_index(state), proposer_reward)
+```
+
+##### Deposits
+
+###### Modified `get_validator_from_deposit`
+
+*Note*: The function is modified to use `MAX_EFFECTIVE_BALANCE_ELECTRA` for compounding withdrawal credential.
+
+```python
+def get_validator_from_deposit(pubkey: BLSPubkey, withdrawal_credentials: Bytes32, amount: uint64) -> Validator:
+ validator = Validator(
+ pubkey=pubkey,
+ withdrawal_credentials=withdrawal_credentials,
+ effective_balance=Gwei(0),
+ slashed=False,
+ activation_eligibility_epoch=FAR_FUTURE_EPOCH,
+ activation_epoch=FAR_FUTURE_EPOCH,
+ exit_epoch=FAR_FUTURE_EPOCH,
+ withdrawable_epoch=FAR_FUTURE_EPOCH,
+ )
+
+ # [Modified in Electra:EIP7251]
+ max_effective_balance = get_max_effective_balance(validator)
+ validator.effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, max_effective_balance)
+
+ return validator
+```
+
+###### Modified `add_validator_to_registry`
+
+*Note*: The function `add_validator_to_registry` is modified to use the modified `get_validator_from_deposit`.
+
+```python
+def add_validator_to_registry(state: BeaconState,
+ pubkey: BLSPubkey,
+ withdrawal_credentials: Bytes32,
+ amount: uint64) -> None:
+ index = get_index_for_new_validator(state)
+ validator = get_validator_from_deposit(pubkey, withdrawal_credentials, amount) # [Modified in Electra:EIP7251]
+ set_or_append_list(state.validators, index, validator)
+ set_or_append_list(state.balances, index, amount)
+ set_or_append_list(state.previous_epoch_participation, index, ParticipationFlags(0b0000_0000))
+ set_or_append_list(state.current_epoch_participation, index, ParticipationFlags(0b0000_0000))
+ set_or_append_list(state.inactivity_scores, index, uint64(0))
+```
+
+###### Modified `apply_deposit`
+
+*Note*: The function `apply_deposit` is modified to support EIP7251.
+
+```python
+def apply_deposit(state: BeaconState,
+ pubkey: BLSPubkey,
+ withdrawal_credentials: Bytes32,
+ amount: uint64,
+ signature: BLSSignature) -> None:
+ validator_pubkeys = [v.pubkey for v in state.validators]
+ if pubkey not in validator_pubkeys:
+ # Verify the deposit signature (proof of possession) which is not checked by the deposit contract
+ if is_valid_deposit_signature(pubkey, withdrawal_credentials, amount, signature):
+ add_validator_to_registry(state, pubkey, withdrawal_credentials, Gwei(0)) # [Modified in Electra:EIP7251]
+ else:
+ return
+
+ # Increase balance by deposit amount
+ # [Modified in Electra:EIP7251]
+ state.pending_deposits.append(PendingDeposit(
+ pubkey=pubkey,
+ withdrawal_credentials=withdrawal_credentials,
+ amount=amount,
+ signature=signature,
+ slot=GENESIS_SLOT # Use GENESIS_SLOT to distinguish from a pending deposit request
+ ))
+```
+
+###### New `is_valid_deposit_signature`
+
+```python
+def is_valid_deposit_signature(pubkey: BLSPubkey,
+ withdrawal_credentials: Bytes32,
+ amount: uint64,
+ signature: BLSSignature) -> bool:
+ deposit_message = DepositMessage(
+ pubkey=pubkey,
+ withdrawal_credentials=withdrawal_credentials,
+ amount=amount,
+ )
+ domain = compute_domain(DOMAIN_DEPOSIT) # Fork-agnostic domain since deposits are valid across forks
+ signing_root = compute_signing_root(deposit_message, domain)
+ return bls.Verify(pubkey, signing_root, signature)
+```
+
+###### Modified `process_deposit`
+
+*Note*: The function `process_deposit` is modified to to use the modified `apply_deposit`.
+
+```python
+def process_deposit(state: BeaconState, deposit: Deposit) -> None:
+ # Verify the Merkle branch
+ assert is_valid_merkle_branch(
+ leaf=hash_tree_root(deposit.data),
+ branch=deposit.proof,
+ depth=DEPOSIT_CONTRACT_TREE_DEPTH + 1, # Add 1 for the List length mix-in
+ index=state.eth1_deposit_index,
+ root=state.eth1_data.deposit_root,
+ )
+
+ # Deposits must be processed in order
+ state.eth1_deposit_index += 1
+
+ # [Modified in Electra:EIP7251]
+ apply_deposit(
+ state=state,
+ pubkey=deposit.data.pubkey,
+ withdrawal_credentials=deposit.data.withdrawal_credentials,
+ amount=deposit.data.amount,
+ signature=deposit.data.signature,
+ )
+```
+
+##### Voluntary exits
+
+###### Modified `process_voluntary_exit`
+
+*Note*: The function `process_voluntary_exit` is modified to ensure the validator has no pending withdrawals in the queue.
+
+```python
+def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVoluntaryExit) -> None:
+ voluntary_exit = signed_voluntary_exit.message
+ validator = state.validators[voluntary_exit.validator_index]
+ # Verify the validator is active
+ assert is_active_validator(validator, get_current_epoch(state))
+ # Verify exit has not been initiated
+ assert validator.exit_epoch == FAR_FUTURE_EPOCH
+ # Exits must specify an epoch when they become valid; they are not valid before then
+ assert get_current_epoch(state) >= voluntary_exit.epoch
+ # Verify the validator has been active long enough
+ assert get_current_epoch(state) >= validator.activation_epoch + SHARD_COMMITTEE_PERIOD
+ # Only exit validator if it has no pending withdrawals in the queue
+ assert get_pending_balance_to_withdraw(state, voluntary_exit.validator_index) == 0 # [New in Electra:EIP7251]
+ # Verify signature
+ domain = compute_domain(DOMAIN_VOLUNTARY_EXIT, CAPELLA_FORK_VERSION, state.genesis_validators_root)
+ signing_root = compute_signing_root(voluntary_exit, domain)
+ assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature)
+ # Initiate exit
+ initiate_validator_exit(state, voluntary_exit.validator_index)
+```
+
+##### Execution layer withdrawal requests
+
+###### New `process_withdrawal_request`
+
+```python
+def process_withdrawal_request(
+ state: BeaconState,
+ withdrawal_request: WithdrawalRequest
+) -> None:
+ amount = withdrawal_request.amount
+ is_full_exit_request = amount == FULL_EXIT_REQUEST_AMOUNT
+
+ # If partial withdrawal queue is full, only full exits are processed
+ if len(state.pending_partial_withdrawals) == PENDING_PARTIAL_WITHDRAWALS_LIMIT and not is_full_exit_request:
+ return
+
+ validator_pubkeys = [v.pubkey for v in state.validators]
+ # Verify pubkey exists
+ request_pubkey = withdrawal_request.validator_pubkey
+ if request_pubkey not in validator_pubkeys:
+ return
+ index = ValidatorIndex(validator_pubkeys.index(request_pubkey))
+ validator = state.validators[index]
+
+ # Verify withdrawal credentials
+ has_correct_credential = has_execution_withdrawal_credential(validator)
+ is_correct_source_address = (
+ validator.withdrawal_credentials[12:] == withdrawal_request.source_address
+ )
+ if not (has_correct_credential and is_correct_source_address):
+ return
+ # Verify the validator is active
+ if not is_active_validator(validator, get_current_epoch(state)):
+ return
+ # Verify exit has not been initiated
+ if validator.exit_epoch != FAR_FUTURE_EPOCH:
+ return
+ # Verify the validator has been active long enough
+ if get_current_epoch(state) < validator.activation_epoch + SHARD_COMMITTEE_PERIOD:
+ return
+
+ pending_balance_to_withdraw = get_pending_balance_to_withdraw(state, index)
+
+ if is_full_exit_request:
+ # Only exit validator if it has no pending withdrawals in the queue
+ if pending_balance_to_withdraw == 0:
+ initiate_validator_exit(state, index)
+ return
+
+ has_sufficient_effective_balance = validator.effective_balance >= MIN_ACTIVATION_BALANCE
+ has_excess_balance = state.balances[index] > MIN_ACTIVATION_BALANCE + pending_balance_to_withdraw
+
+ # Only allow partial withdrawals with compounding withdrawal credentials
+ if has_compounding_withdrawal_credential(validator) and has_sufficient_effective_balance and has_excess_balance:
+ to_withdraw = min(
+ state.balances[index] - MIN_ACTIVATION_BALANCE - pending_balance_to_withdraw,
+ amount
+ )
+ exit_queue_epoch = compute_exit_epoch_and_update_churn(state, to_withdraw)
+ withdrawable_epoch = Epoch(exit_queue_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY)
+ state.pending_partial_withdrawals.append(PendingPartialWithdrawal(
+ validator_index=index,
+ amount=to_withdraw,
+ withdrawable_epoch=withdrawable_epoch,
+ ))
+```
+
+##### Deposit requests
+
+###### New `process_deposit_request`
+
+```python
+def process_deposit_request(state: BeaconState, deposit_request: DepositRequest) -> None:
+ # Set deposit request start index
+ if state.deposit_requests_start_index == UNSET_DEPOSIT_REQUESTS_START_INDEX:
+ state.deposit_requests_start_index = deposit_request.index
+
+ # Create pending deposit
+ state.pending_deposits.append(PendingDeposit(
+ pubkey=deposit_request.pubkey,
+ withdrawal_credentials=deposit_request.withdrawal_credentials,
+ amount=deposit_request.amount,
+ signature=deposit_request.signature,
+ slot=state.slot,
+ ))
+```
+
+##### Execution layer consolidation requests
+
+###### New `is_valid_switch_to_compounding_request`
+
+```python
+def is_valid_switch_to_compounding_request(
+ state: BeaconState,
+ consolidation_request: ConsolidationRequest
+) -> bool:
+ # Switch to compounding requires source and target be equal
+ if consolidation_request.source_pubkey != consolidation_request.target_pubkey:
+ return False
+
+ # Verify pubkey exists
+ source_pubkey = consolidation_request.source_pubkey
+ validator_pubkeys = [v.pubkey for v in state.validators]
+ if source_pubkey not in validator_pubkeys:
+ return False
+
+ source_validator = state.validators[ValidatorIndex(validator_pubkeys.index(source_pubkey))]
+
+ # Verify request has been authorized
+ if source_validator.withdrawal_credentials[12:] != consolidation_request.source_address:
+ return False
+
+ # Verify source withdrawal credentials
+ if not has_eth1_withdrawal_credential(source_validator):
+ return False
+
+ # Verify the source is active
+ current_epoch = get_current_epoch(state)
+ if not is_active_validator(source_validator, current_epoch):
+ return False
+
+ # Verify exit for source has not been initiated
+ if source_validator.exit_epoch != FAR_FUTURE_EPOCH:
+ return False
+
+ return True
+```
+
+###### New `process_consolidation_request`
+
+```python
+def process_consolidation_request(
+ state: BeaconState,
+ consolidation_request: ConsolidationRequest
+) -> None:
+ if is_valid_switch_to_compounding_request(state, consolidation_request):
+ validator_pubkeys = [v.pubkey for v in state.validators]
+ request_source_pubkey = consolidation_request.source_pubkey
+ source_index = ValidatorIndex(validator_pubkeys.index(request_source_pubkey))
+ switch_to_compounding_validator(state, source_index)
+ return
+
+ # Verify that source != target, so a consolidation cannot be used as an exit
+ if consolidation_request.source_pubkey == consolidation_request.target_pubkey:
+ return
+ # If the pending consolidations queue is full, consolidation requests are ignored
+ if len(state.pending_consolidations) == PENDING_CONSOLIDATIONS_LIMIT:
+ return
+ # If there is too little available consolidation churn limit, consolidation requests are ignored
+ if get_consolidation_churn_limit(state) <= MIN_ACTIVATION_BALANCE:
+ return
+
+ validator_pubkeys = [v.pubkey for v in state.validators]
+ # Verify pubkeys exists
+ request_source_pubkey = consolidation_request.source_pubkey
+ request_target_pubkey = consolidation_request.target_pubkey
+ if request_source_pubkey not in validator_pubkeys:
+ return
+ if request_target_pubkey not in validator_pubkeys:
+ return
+ source_index = ValidatorIndex(validator_pubkeys.index(request_source_pubkey))
+ target_index = ValidatorIndex(validator_pubkeys.index(request_target_pubkey))
+ source_validator = state.validators[source_index]
+ target_validator = state.validators[target_index]
+
+ # Verify source withdrawal credentials
+ has_correct_credential = has_execution_withdrawal_credential(source_validator)
+ is_correct_source_address = (
+ source_validator.withdrawal_credentials[12:] == consolidation_request.source_address
+ )
+ if not (has_correct_credential and is_correct_source_address):
+ return
+
+ # Verify that target has compounding withdrawal credentials
+ if not has_compounding_withdrawal_credential(target_validator):
+ return
+
+ # Verify the source and the target are active
+ current_epoch = get_current_epoch(state)
+ if not is_active_validator(source_validator, current_epoch):
+ return
+ if not is_active_validator(target_validator, current_epoch):
+ return
+ # Verify exits for source and target have not been initiated
+ if source_validator.exit_epoch != FAR_FUTURE_EPOCH:
+ return
+ if target_validator.exit_epoch != FAR_FUTURE_EPOCH:
+ return
+ # Verify the source has been active long enough
+ if current_epoch < source_validator.activation_epoch + SHARD_COMMITTEE_PERIOD:
+ return
+ # Verify the source has no pending withdrawals in the queue
+ if get_pending_balance_to_withdraw(state, source_index) > 0:
+ return
+
+ # Initiate source validator exit and append pending consolidation
+ source_validator.exit_epoch = compute_consolidation_epoch_and_update_churn(
+ state, source_validator.effective_balance
+ )
+ source_validator.withdrawable_epoch = Epoch(
+ source_validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY
+ )
+ state.pending_consolidations.append(PendingConsolidation(
+ source_index=source_index,
+ target_index=target_index
+ ))
+```
\ No newline at end of file
diff --git a/docs/specs/electra/fork.md b/docs/specs/electra/fork.md
new file mode 100644
index 000000000..64bbd4e8a
--- /dev/null
+++ b/docs/specs/electra/fork.md
@@ -0,0 +1,178 @@
+# Electra -- Fork Logic
+
+*Note*: This document is a work-in-progress for researchers and implementers.
+
+## Table of contents
+
+
+
+
+
+- [Introduction](#introduction)
+- [Configuration](#configuration)
+- [Helper functions](#helper-functions)
+ - [Misc](#misc)
+ - [Modified `compute_fork_version`](#modified-compute_fork_version)
+- [Fork to Electra](#fork-to-electra)
+ - [Fork trigger](#fork-trigger)
+ - [Upgrading the state](#upgrading-the-state)
+
+
+
+
+## Introduction
+
+This document describes the process of the Electra upgrade.
+
+## Configuration
+
+Warning: this configuration is not definitive.
+
+| Name | Value |
+| - | - |
+| `ELECTRA_FORK_VERSION` | `Version('0x05000000')` |
+| `ELECTRA_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** |
+
+## Helper functions
+
+### Misc
+
+#### Modified `compute_fork_version`
+
+```python
+def compute_fork_version(epoch: Epoch) -> Version:
+ """
+ Return the fork version at the given ``epoch``.
+ """
+ if epoch >= ELECTRA_FORK_EPOCH:
+ return ELECTRA_FORK_VERSION
+ if epoch >= DENEB_FORK_EPOCH:
+ return DENEB_FORK_VERSION
+ if epoch >= CAPELLA_FORK_EPOCH:
+ return CAPELLA_FORK_VERSION
+ if epoch >= BELLATRIX_FORK_EPOCH:
+ return BELLATRIX_FORK_VERSION
+ if epoch >= ALTAIR_FORK_EPOCH:
+ return ALTAIR_FORK_VERSION
+ return GENESIS_FORK_VERSION
+```
+
+## Fork to Electra
+
+### Fork trigger
+
+The fork is triggered at epoch `ELECTRA_FORK_EPOCH`.
+
+Note that for the pure Electra networks, we don't apply `upgrade_to_electra` since it starts with Electra version logic.
+
+### Upgrading the state
+
+If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == ELECTRA_FORK_EPOCH`,
+an irregular state change is made to upgrade to Electra.
+
+```python
+def upgrade_to_electra(pre: deneb.BeaconState) -> BeaconState:
+ epoch = deneb.get_current_epoch(pre)
+
+ earliest_exit_epoch = compute_activation_exit_epoch(get_current_epoch(pre))
+ for validator in pre.validators:
+ if validator.exit_epoch != FAR_FUTURE_EPOCH:
+ if validator.exit_epoch > earliest_exit_epoch:
+ earliest_exit_epoch = validator.exit_epoch
+ earliest_exit_epoch += Epoch(1)
+
+ post = BeaconState(
+ # Versioning
+ genesis_time=pre.genesis_time,
+ genesis_validators_root=pre.genesis_validators_root,
+ slot=pre.slot,
+ fork=Fork(
+ previous_version=pre.fork.current_version,
+ current_version=ELECTRA_FORK_VERSION, # [Modified in Electra:EIP6110]
+ epoch=epoch,
+ ),
+ # History
+ latest_block_header=pre.latest_block_header,
+ block_roots=pre.block_roots,
+ state_roots=pre.state_roots,
+ historical_roots=pre.historical_roots,
+ # Eth1
+ eth1_data=pre.eth1_data,
+ eth1_data_votes=pre.eth1_data_votes,
+ eth1_deposit_index=pre.eth1_deposit_index,
+ # Registry
+ validators=pre.validators,
+ balances=pre.balances,
+ # Randomness
+ randao_mixes=pre.randao_mixes,
+ # Slashings
+ slashings=pre.slashings,
+ # Participation
+ previous_epoch_participation=pre.previous_epoch_participation,
+ current_epoch_participation=pre.current_epoch_participation,
+ # Finality
+ justification_bits=pre.justification_bits,
+ previous_justified_checkpoint=pre.previous_justified_checkpoint,
+ current_justified_checkpoint=pre.current_justified_checkpoint,
+ finalized_checkpoint=pre.finalized_checkpoint,
+ # Inactivity
+ inactivity_scores=pre.inactivity_scores,
+ # Sync
+ current_sync_committee=pre.current_sync_committee,
+ next_sync_committee=pre.next_sync_committee,
+ # Execution-layer
+ latest_execution_payload_header=pre.latest_execution_payload_header,
+ # Withdrawals
+ next_withdrawal_index=pre.next_withdrawal_index,
+ next_withdrawal_validator_index=pre.next_withdrawal_validator_index,
+ # Deep history valid from Capella onwards
+ historical_summaries=pre.historical_summaries,
+ # [New in Electra:EIP6110]
+ deposit_requests_start_index=UNSET_DEPOSIT_REQUESTS_START_INDEX,
+ # [New in Electra:EIP7251]
+ deposit_balance_to_consume=0,
+ exit_balance_to_consume=0,
+ earliest_exit_epoch=earliest_exit_epoch,
+ consolidation_balance_to_consume=0,
+ earliest_consolidation_epoch=compute_activation_exit_epoch(get_current_epoch(pre)),
+ pending_deposits=[],
+ pending_partial_withdrawals=[],
+ pending_consolidations=[],
+ )
+
+ post.exit_balance_to_consume = get_activation_exit_churn_limit(post)
+ post.consolidation_balance_to_consume = get_consolidation_churn_limit(post)
+
+ # [New in Electra:EIP7251]
+ # add validators that are not yet active to pending balance deposits
+ pre_activation = sorted([
+ index for index, validator in enumerate(post.validators)
+ if validator.activation_epoch == FAR_FUTURE_EPOCH
+ ], key=lambda index: (
+ post.validators[index].activation_eligibility_epoch,
+ index
+ ))
+
+ for index in pre_activation:
+ balance = post.balances[index]
+ post.balances[index] = 0
+ validator = post.validators[index]
+ validator.effective_balance = 0
+ validator.activation_eligibility_epoch = FAR_FUTURE_EPOCH
+ # Use bls.G2_POINT_AT_INFINITY as a signature field placeholder
+ # and GENESIS_SLOT to distinguish from a pending deposit request
+ post.pending_deposits.append(PendingDeposit(
+ pubkey=validator.pubkey,
+ withdrawal_credentials=validator.withdrawal_credentials,
+ amount=balance,
+ signature=bls.G2_POINT_AT_INFINITY,
+ slot=GENESIS_SLOT,
+ ))
+
+ # Ensure early adopters of compounding credentials go through the activation churn
+ for index, validator in enumerate(post.validators):
+ if has_compounding_withdrawal_credential(validator):
+ queue_excess_active_balance(post, ValidatorIndex(index))
+
+ return post
+```
\ No newline at end of file
diff --git a/docs/specs/electra/p2p-interface.md b/docs/specs/electra/p2p-interface.md
new file mode 100644
index 000000000..bec604aa1
--- /dev/null
+++ b/docs/specs/electra/p2p-interface.md
@@ -0,0 +1,209 @@
+# Electra -- Networking
+
+*Note*: This document is a work-in-progress for researchers and implementers.
+
+## Table of contents
+
+
+
+
+
+- [Introduction](#introduction)
+- [Modifications in Electra](#modifications-in-electra)
+ - [Configuration](#configuration)
+ - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub)
+ - [Topics and messages](#topics-and-messages)
+ - [Global topics](#global-topics)
+ - [`beacon_block`](#beacon_block)
+ - [`beacon_aggregate_and_proof`](#beacon_aggregate_and_proof)
+ - [`blob_sidecar_{subnet_id}`](#blob_sidecar_subnet_id)
+ - [Attestation subnets](#attestation-subnets)
+ - [`beacon_attestation_{subnet_id}`](#beacon_attestation_subnet_id)
+ - [The Req/Resp domain](#the-reqresp-domain)
+ - [Messages](#messages)
+ - [BeaconBlocksByRange v2](#beaconblocksbyrange-v2)
+ - [BeaconBlocksByRoot v2](#beaconblocksbyroot-v2)
+ - [BlobSidecarsByRange v1](#blobsidecarsbyrange-v1)
+ - [BlobSidecarsByRoot v1](#blobsidecarsbyroot-v1)
+
+
+
+
+## Introduction
+
+This document contains the consensus-layer networking specification for Electra.
+
+The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite.
+
+## Modifications in Electra
+
+### Configuration
+
+*[New in Electra:EIP7691]*
+
+| Name | Value | Description |
+|-------------------------------------|----------------------------------------------------------|-------------------------------------------------------------------|
+| `MAX_REQUEST_BLOB_SIDECARS_ELECTRA` | `MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA` | Maximum number of blob sidecars in a single request |
+| `BLOB_SIDECAR_SUBNET_COUNT_ELECTRA` | `9` | The number of blob sidecar subnets used in the gossipsub protocol |
+
+### The gossip domain: gossipsub
+
+Some gossip meshes are upgraded in the fork of Electra to support upgraded types.
+
+#### Topics and messages
+
+Topics follow the same specification as in prior upgrades.
+
+The `beacon_block` topic is modified to also support Electra blocks.
+
+The `beacon_aggregate_and_proof` and `beacon_attestation_{subnet_id}` topics are modified to support the gossip of the new attestation type.
+
+The `attester_slashing` topic is modified to support the gossip of the new `AttesterSlashing` type.
+
+The specification around the creation, validation, and dissemination of messages has not changed from the Capella document unless explicitly noted here.
+
+The derivation of the `message-id` remains stable.
+
+##### Global topics
+
+###### `beacon_block`
+
+*Updated validation*
+
+- _[REJECT]_ The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer --
+ i.e. validate that `len(signed_beacon_block.message.body.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK_ELECTRA`
+
+###### `beacon_aggregate_and_proof`
+
+The following convenience variables are re-defined
+
+- `index = get_committee_indices(aggregate.committee_bits)[0]`
+
+The following validations are added:
+
+* [REJECT] `len(committee_indices) == 1`, where `committee_indices = get_committee_indices(aggregate)`.
+* [REJECT] `aggregate.data.index == 0`
+
+###### `blob_sidecar_{subnet_id}`
+
+*[Modified in Electra:EIP7691]*
+
+The existing validations all apply as given from previous forks, with the following exceptions:
+
+* Uses of `MAX_BLOBS_PER_BLOCK` in existing validations are replaced with `MAX_BLOBS_PER_BLOCK_ELECTRA`.
+
+##### Attestation subnets
+
+###### `beacon_attestation_{subnet_id}`
+
+The topic is updated to propagate `SingleAttestation` objects.
+
+The following convenience variables are re-defined:
+
+- `index = attestation.committee_index`
+
+The following validations are added:
+
+- _[REJECT]_ `attestation.data.index == 0`
+- _[REJECT]_ The attester is a member of the committee -- i.e.
+ `attestation.attester_index in get_beacon_committee(state, attestation.data.slot, index)`.
+
+The following validations are removed:
+
+- _[REJECT]_ The attestation is unaggregated --
+ that is, it has exactly one participating validator (`len([bit for bit in aggregation_bits if bit]) == 1`, i.e. exactly 1 bit is set).
+- _[REJECT]_ The number of aggregation bits matches the committee size -- i.e.
+ `len(aggregation_bits) == len(get_beacon_committee(state, attestation.data.slot, index))`.
+
+### The Req/Resp domain
+
+#### Messages
+
+##### BeaconBlocksByRange v2
+
+**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_range/2/`
+
+The Electra fork-digest is introduced to the `context` enum to specify Electra beacon block type.
+
+Per `context = compute_fork_digest(fork_version, genesis_validators_root)`:
+
+[0]: # (eth2spec: skip)
+
+| `fork_version` | Chunk SSZ type |
+|--------------------------|-------------------------------|
+| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` |
+| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` |
+| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` |
+| `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` |
+| `DENEB_FORK_VERSION` | `deneb.SignedBeaconBlock` |
+| `ELECTRA_FORK_VERSION` | `electra.SignedBeaconBlock` |
+
+##### BeaconBlocksByRoot v2
+
+**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/2/`
+
+Per `context = compute_fork_digest(fork_version, genesis_validators_root)`:
+
+[0]: # (eth2spec: skip)
+
+| `fork_version` | Chunk SSZ type |
+|--------------------------|-------------------------------|
+| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` |
+| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` |
+| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` |
+| `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` |
+| `DENEB_FORK_VERSION` | `deneb.SignedBeaconBlock` |
+| `ELECTRA_FORK_VERSION` | `electra.SignedBeaconBlock` |
+
+##### BlobSidecarsByRange v1
+
+**Protocol ID:** `/eth2/beacon_chain/req/blob_sidecars_by_range/1/`
+
+*[Modified in Electra:EIP7691]*
+
+Request Content:
+
+```
+(
+ start_slot: Slot
+ count: uint64
+)
+```
+
+Response Content:
+
+```
+(
+ List[BlobSidecar, MAX_REQUEST_BLOB_SIDECARS_ELECTRA]
+)
+```
+
+*Updated validation*
+
+Clients MUST respond with at least the blob sidecars of the first blob-carrying block that exists in the range, if they have it, and no more than `MAX_REQUEST_BLOB_SIDECARS_ELECTRA` sidecars.
+
+##### BlobSidecarsByRoot v1
+
+**Protocol ID:** `/eth2/beacon_chain/req/blob_sidecars_by_root/1/`
+
+*[Modified in Electra:EIP7691]*
+
+Request Content:
+
+```
+(
+ List[BlobIdentifier, MAX_REQUEST_BLOB_SIDECARS_ELECTRA]
+)
+```
+
+Response Content:
+
+```
+(
+ List[BlobSidecar, MAX_REQUEST_BLOB_SIDECARS_ELECTRA]
+)
+```
+
+*Updated validation*
+
+No more than `MAX_REQUEST_BLOB_SIDECARS_ELECTRA` may be requested at a time.
\ No newline at end of file
diff --git a/docs/specs/electra/validator.md b/docs/specs/electra/validator.md
new file mode 100644
index 000000000..750eb1dea
--- /dev/null
+++ b/docs/specs/electra/validator.md
@@ -0,0 +1,309 @@
+# Electra -- Honest Validator
+
+*Note*: This document is a work-in-progress for researchers and implementers.
+
+## Table of contents
+
+
+
+
+
+- [Introduction](#introduction)
+- [Prerequisites](#prerequisites)
+- [Helpers](#helpers)
+ - [Modified `GetPayloadResponse`](#modified-getpayloadresponse)
+- [Containers](#containers)
+ - [Modified containers](#modified-containers)
+ - [`AggregateAndProof`](#aggregateandproof)
+ - [`SignedAggregateAndProof`](#signedaggregateandproof)
+- [Protocol](#protocol)
+ - [`ExecutionEngine`](#executionengine)
+ - [Modified `get_payload`](#modified-get_payload)
+- [Block proposal](#block-proposal)
+ - [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
+ - [Attester slashings](#attester-slashings)
+ - [Attestations](#attestations)
+ - [Deposits](#deposits)
+ - [Execution payload](#execution-payload)
+ - [Execution Requests](#execution-requests)
+ - [Constructing the `BlobSidecar`s](#constructing-the-blobsidecars)
+ - [Sidecar](#sidecar)
+- [Attesting](#attesting)
+ - [Construct attestation](#construct-attestation)
+- [Attestation aggregation](#attestation-aggregation)
+ - [Construct aggregate](#construct-aggregate)
+
+
+
+
+## Introduction
+
+This document represents the changes to be made in the code of an "honest validator" to implement Electra.
+
+## Prerequisites
+
+This document is an extension of the [Deneb -- Honest Validator](../deneb/validator.md) guide.
+All behaviors and definitions defined in this document, and documents it extends, carry over unless explicitly noted or overridden.
+
+All terminology, constants, functions, and protocol mechanics defined in the updated Beacon Chain doc of [Electra](./beacon-chain.md) are requisite for this document and used throughout.
+Please see related Beacon Chain doc before continuing and use them as a reference throughout.
+
+## Helpers
+
+### Modified `GetPayloadResponse`
+
+```python
+@dataclass
+class GetPayloadResponse(object):
+ execution_payload: ExecutionPayload
+ block_value: uint256
+ blobs_bundle: BlobsBundle
+ execution_requests: Sequence[bytes] # [New in Electra]
+```
+
+## Containers
+
+### Modified containers
+
+#### `AggregateAndProof`
+
+```python
+class AggregateAndProof(Container):
+ aggregator_index: ValidatorIndex
+ aggregate: Attestation # [Modified in Electra:EIP7549]
+ selection_proof: BLSSignature
+```
+
+#### `SignedAggregateAndProof`
+
+```python
+class SignedAggregateAndProof(Container):
+ message: AggregateAndProof # [Modified in Electra:EIP7549]
+ signature: BLSSignature
+```
+
+## Protocol
+
+### `ExecutionEngine`
+
+#### Modified `get_payload`
+
+Given the `payload_id`, `get_payload` returns the most recent version of the execution payload that
+has been built since the corresponding call to `notify_forkchoice_updated` method.
+
+```python
+def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> GetPayloadResponse:
+ """
+ Return ExecutionPayload, uint256, BlobsBundle and execution requests (as Sequence[bytes]) objects.
+ """
+ # pylint: disable=unused-argument
+ ...
+```
+
+## Block proposal
+
+### Constructing the `BeaconBlockBody`
+
+#### Attester slashings
+
+Changed the max attester slashings size to `MAX_ATTESTER_SLASHINGS_ELECTRA`.
+
+#### Attestations
+
+Changed the max attestations size to `MAX_ATTESTATIONS_ELECTRA`.
+
+The network attestation aggregates contain only the assigned committee attestations.
+Attestation aggregates received by the block proposer from the committee aggregators with disjoint `committee_bits` sets and equal `AttestationData` SHOULD be consolidated into a single `Attestation` object.
+The proposer should run the following function to construct an on chain final aggregate from a list of network aggregates with equal `AttestationData`:
+
+```python
+def compute_on_chain_aggregate(network_aggregates: Sequence[Attestation]) -> Attestation:
+ aggregates = sorted(network_aggregates, key=lambda a: get_committee_indices(a.committee_bits)[0])
+
+ data = aggregates[0].data
+ aggregation_bits = Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]()
+ for a in aggregates:
+ for b in a.aggregation_bits:
+ aggregation_bits.append(b)
+
+ signature = bls.Aggregate([a.signature for a in aggregates])
+
+ committee_indices = [get_committee_indices(a.committee_bits)[0] for a in aggregates]
+ committee_flags = [(index in committee_indices) for index in range(0, MAX_COMMITTEES_PER_SLOT)]
+ committee_bits = Bitvector[MAX_COMMITTEES_PER_SLOT](committee_flags)
+
+ return Attestation(
+ aggregation_bits=aggregation_bits,
+ data=data,
+ committee_bits=committee_bits,
+ signature=signature,
+ )
+```
+
+#### Deposits
+
+*[New in Electra:EIP6110]* The expected number of deposits MUST be changed from `min(MAX_DEPOSITS, eth1_data.deposit_count - state.eth1_deposit_index)` to the result of the following function:
+
+```python
+def get_eth1_pending_deposit_count(state: BeaconState) -> uint64:
+ eth1_deposit_index_limit = min(state.eth1_data.deposit_count, state.deposit_requests_start_index)
+ if state.eth1_deposit_index < eth1_deposit_index_limit:
+ return min(MAX_DEPOSITS, eth1_deposit_index_limit - state.eth1_deposit_index)
+ else:
+ return uint64(0)
+```
+
+*Note*: Clients will be able to remove the `Eth1Data` polling mechanism in an uncoordinated fashion once the transition period is finished. The transition period is considered finished when a network reaches the point where `state.eth1_deposit_index == state.deposit_requests_start_index`.
+
+```python
+def get_eth1_vote(state: BeaconState, eth1_chain: Sequence[Eth1Block]) -> Eth1Data:
+ # [New in Electra:EIP6110]
+ if state.eth1_deposit_index == state.deposit_requests_start_index:
+ return state.eth1_data
+
+ period_start = voting_period_start_time(state)
+ # `eth1_chain` abstractly represents all blocks in the eth1 chain sorted by ascending block height
+ votes_to_consider = [
+ get_eth1_data(block) for block in eth1_chain
+ if (
+ is_candidate_block(block, period_start)
+ # Ensure cannot move back to earlier deposit contract states
+ and get_eth1_data(block).deposit_count >= state.eth1_data.deposit_count
+ )
+ ]
+
+ # Valid votes already cast during this period
+ valid_votes = [vote for vote in state.eth1_data_votes if vote in votes_to_consider]
+
+ # Default vote on latest eth1 block data in the period range unless eth1 chain is not live
+ # Non-substantive casting for linter
+ state_eth1_data: Eth1Data = state.eth1_data
+ default_vote = votes_to_consider[len(votes_to_consider) - 1] if any(votes_to_consider) else state_eth1_data
+
+ return max(
+ valid_votes,
+ key=lambda v: (valid_votes.count(v), -valid_votes.index(v)), # Tiebreak by smallest distance
+ default=default_vote
+ )
+```
+
+#### Execution payload
+
+`prepare_execution_payload` is updated from the Deneb specs.
+
+*Note*: In this section, `state` is the state of the slot for the block proposal _without_ the block yet applied.
+That is, `state` is the `previous_state` processed through any empty slots up to the assigned slot using `process_slots(previous_state, slot)`.
+
+*Note*: The only change to `prepare_execution_payload` is the new definition of `get_expected_withdrawals`.
+
+```python
+def prepare_execution_payload(state: BeaconState,
+ safe_block_hash: Hash32,
+ finalized_block_hash: Hash32,
+ suggested_fee_recipient: ExecutionAddress,
+ execution_engine: ExecutionEngine) -> Optional[PayloadId]:
+ # Verify consistency of the parent hash with respect to the previous execution payload header
+ parent_hash = state.latest_execution_payload_header.block_hash
+
+ # Set the forkchoice head and initiate the payload build process
+ withdrawals, _ = get_expected_withdrawals(state) # [Modified in EIP-7251]
+
+ payload_attributes = PayloadAttributes(
+ timestamp=compute_timestamp_at_slot(state, state.slot),
+ prev_randao=get_randao_mix(state, get_current_epoch(state)),
+ suggested_fee_recipient=suggested_fee_recipient,
+ withdrawals=withdrawals,
+ parent_beacon_block_root=hash_tree_root(state.latest_block_header),
+ )
+ return execution_engine.notify_forkchoice_updated(
+ head_block_hash=parent_hash,
+ safe_block_hash=safe_block_hash,
+ finalized_block_hash=finalized_block_hash,
+ payload_attributes=payload_attributes,
+ )
+```
+
+#### Execution Requests
+
+*[New in Electra]*
+
+1. The execution payload is obtained from the execution engine as defined above using `payload_id`. The response also includes a `execution_requests` entry containing a list of bytes. Each element on the list corresponds to one SSZ list of requests as defined in [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685). The first byte of each request is used to determine the request type. Requests must be ordered by request type in ascending order. As a result, there can only be at most one instance of each request type.
+2. Set `block.body.execution_requests = get_execution_requests(execution_requests)`, where:
+
+```python
+def get_execution_requests(execution_requests_list: Sequence[bytes]) -> ExecutionRequests:
+ deposits = []
+ withdrawals = []
+ consolidations = []
+
+ request_types = [
+ DEPOSIT_REQUEST_TYPE,
+ WITHDRAWAL_REQUEST_TYPE,
+ CONSOLIDATION_REQUEST_TYPE,
+ ]
+
+ prev_request_type = None
+ for request in execution_requests_list:
+ request_type, request_data = request[0:1], request[1:]
+
+ # Check that the request type is valid
+ assert request_type in request_types
+ # Check that the request data is not empty
+ assert len(request_data) != 0
+ # Check that requests are in strictly ascending order
+ # Each successive type must be greater than the last with no duplicates
+ assert prev_request_type is None or prev_request_type < request_type
+ prev_request_type = request_type
+
+ if request_type == DEPOSIT_REQUEST_TYPE:
+ deposits = ssz_deserialize(
+ List[DepositRequest, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD],
+ request_data
+ )
+ elif request_type == WITHDRAWAL_REQUEST_TYPE:
+ withdrawals = ssz_deserialize(
+ List[WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD],
+ request_data
+ )
+ elif request_type == CONSOLIDATION_REQUEST_TYPE:
+ consolidations = ssz_deserialize(
+ List[ConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD],
+ request_data
+ )
+
+ return ExecutionRequests(
+ deposits=deposits,
+ withdrawals=withdrawals,
+ consolidations=consolidations,
+ )
+```
+
+### Constructing the `BlobSidecar`s
+
+#### Sidecar
+
+*[Modified in Electra:EIP7691]*
+
+```python
+def compute_subnet_for_blob_sidecar(blob_index: BlobIndex) -> SubnetID:
+ return SubnetID(blob_index % BLOB_SIDECAR_SUBNET_COUNT_ELECTRA)
+```
+
+## Attesting
+
+### Construct attestation
+
+The validator creates `attestation` as a `SingleAttestation` container
+with updated field assignments:
+
+- Set `attestation_data.index = 0`.
+- Set `attestation.committee_index` to the index associated with the validator's committee.
+- Set `attestation.attester_index` to the index of the validator.
+
+## Attestation aggregation
+
+### Construct aggregate
+
+- Set `attestation_data.index = 0`.
+- Let `aggregation_bits` be a `Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]` of length `len(committee)`, where each bit set from each individual attestation is set to `0b1`.
+- Set `attestation.committee_bits = committee_bits`, where `committee_bits` has the bit set corresponding to `committee_index` in each individual attestation.
\ No newline at end of file
diff --git a/electra-gap.md b/electra-gap.md
new file mode 100644
index 000000000..d89f5e6c1
--- /dev/null
+++ b/electra-gap.md
@@ -0,0 +1,173 @@
+# Implementation Gaps for electra Upgrade
+
+This document will guide you through our step-by-step plan for the implementation of the new electra fork. We’ve broken the work into three clear phases to make our goals and priorities easy to follow. This is a living document, so we will update it as we progress through the implementation.
+
+## Roadmap
+
+| Status | Phase | What & Why | Key Steps | Testing |
+|:----:|:------------------------------------------|:------------------------------------------------------|:---------------------------------------------------------------------------|:---------------------------------------------------|
+| 🏗️ | [Phase 1: Beacon Chain Implementation](#phase-1-beacon-chain-implementation) | Build the electra-upgraded beacon chain core | • Apply electra-specific changes
• Run & pass full spec tests | Run spec suite (`make spec-test`), aim for 0 failures |
+| ⌛ | [Phase 2: P2P & Sepolia Long-Running Sessions](#phase-2-p2p--sepolia-long-running-sessions) | Ensure stability on Sepolia | • Implement the P2P changes
• Deploy the node on our server pointing to Sepolia
• Fix every issue we found that interrupts the node execution | Continuous uptime checks & up-to-date block processing for 72+ hrs in Sepolia|
+| ⌛ | [Phase 3: Validator Upgrades](#phase-3-validator-upgrades) | Ensure validators duties on devnets |• Implement the honest validator changes
• Make assertoor work
• Test via Kurtosis & Assertoor | Execute Kurtosis scenarios & Assertoor with continuous uptime checks and up-to-date validation duties for 72+ hrs on kurtosis |
+
+### Why this Order
+
+We kick off with the beacon chain implementation because passing the full spec test suite is critical for protocol correctness and a solid foundation. Once all tests are green, we move to Phase 2 for prolonged Sepolia sessions and the implementation of the p2p interface changes, ensuring real-world testnet stability before mainnet moves to electra which would limit our network options before we upgrade. This will allow us to continue running long session on our servers and monitor the node execution given that just the block/epoch processing and state transitions are needed for this. With a stable node confirmed, Phase 3 begins, upgrading the validator logic, tested through Kurtosis and Assertoor to finalize the electra upgrade roadmap.
+
+### Next Steps
+
+Once we finish the whole electra upgrade we have a clear path to follow for the next steps:
+- **Hooli long running sessions:** Right now Holesky was not an option for us because of performance issues, we need to test on Hooli and see if we can run the node on it on acceptable performance. This effort will be in parallel to the performance optimization one.
+- **Performance optimization:** We need to run the node on Hooli and mainnet to identify and fix the current bottlenecks, specially on block and epoch processing.
+- **Electra code enhancements:** During the implementation, some complex functions were identified that could be simplified. They are mostly related to how to manage early returns in already large python reference functions and port the logic to elixir. We will work on those to improve the code quality and make it easier to maintain in the future.
+
+## Current Status
+
+Right now we are at the [Beacon Chain Implementation](#phase-1-beacon-chain-implementation) phase, our current spec test results for the past weeks are:
+
+- **April 15th, 2025:** `11370 tests, 2003 failures, 784 skipped`
+- **April 22th, 2025:** `11370 tests, 165 failures, 784 skipped`
+
+**Note:** The aim is to reach `0` failures before next week, so we can start the long running sessions on Sepolia. Also, we want to validate the 784 test skipped on the second phase.
+
+## Implementation Gap
+
+Here we will detail the current implementation gaps with the specs and the way to test every phase. The initial one is the larger to implement but it has the most clear way to validate it (spec-tests). The next phases are smaller but they need to be validated on long running sessions both in devnets and testnets.
+
+### Phase 1: Beacon Chain Implementation
+
+We are at `54/58` (91%) of the [beacon chain changes](docs/specs/electra/beacon-chain.md), and most of the remaining functions are already in progress. We have fixed all `11370` spec tests. The skipped tests were there previous to the electra upgrade, so we will work on them if needed after we finish the first phase.
+
+The current status of the implementation in the [electra-support](https://github.com/lambdaclass/lambda_ethereum_consensus/tree/electra-support) branch is as follows:
+
+#### Containers (13/13 - 100%)
+
+- [x] New `PendingDeposit` ([Spec](docs/specs/electra/beacon-chain.md#pendingdeposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400))
+- [x] New `PendingPartialWithdrawal` ([Spec](docs/specs/electra/beacon-chain.md#pendingpartialwithdrawal), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400))
+- [x] New `PendingConsolidation` ([Spec](docs/specs/electra/beacon-chain.md#pendingconsolidation), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400))
+- [x] New `DepositRequest` ([Spec](docs/specs/electra/beacon-chain.md#depositrequest), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400))
+- [x] New `WithdrawalRequest` ([Spec](docs/specs/electra/beacon-chain.md#withdrawalrequest), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400))
+- [x] New `ConsolidationRequest` ([Spec](docs/specs/electra/beacon-chain.md#consolidationrequest), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400))
+- [x] New `ExecutionRequests` ([Spec](docs/specs/electra/beacon-chain.md#executionrequests), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400))
+- [x] New `SingleAttestation` ([Spec](docs/specs/electra/beacon-chain.md#singleattestation), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400))
+- [x] Modified `AttesterSlashing` ([Spec](docs/specs/electra/beacon-chain.md#attesterslashing), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400))
+- [x] Modified `BeaconBlockBody` ([Spec](docs/specs/electra/beacon-chain.md#beaconblockbody), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400))
+- [x] Modified `Attestation` ([Spec](docs/specs/electra/beacon-chain.md#attestation), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400))
+- [x] Modified `IndexedAttestation` ([Spec](docs/specs/electra/beacon-chain.md#indexedattestation), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400))
+- [x] Modified `BeaconState` ([Spec](docs/specs/electra/beacon-chain.md#beaconstate), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1400))
+
+#### Predicates (6/6 - 100%)
+
+- [x] Modified `is_eligible_for_activation_queue` ([Spec](docs/specs/electra/beacon-chain.md#modified-is_eligible_for_activation_queue), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419))
+- [x] New `is_compounding_withdrawal_credential` ([Spec](docs/specs/electra/beacon-chain.md#new-is_compounding_withdrawal_credential), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419))
+- [x] New `has_compounding_withdrawal_credential` ([Spec](docs/specs/electra/beacon-chain.md#new-has_compounding_withdrawal_credential), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419))
+- [x] New `has_execution_withdrawal_credential` ([Spec](docs/specs/electra/beacon-chain.md#new-has_execution_withdrawal_credential), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419))
+- [x] Modified `is_fully_withdrawable_validator` ([Spec](docs/specs/electra/beacon-chain.md#modified-is_fully_withdrawable_validator), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419))
+- [x] Modified `is_partially_withdrawable_validator` ([Spec](docs/specs/electra/beacon-chain.md#modified-is_partially_withdrawable_validator), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419))
+
+
+#### Beacon State Accessors (6/6 - 100%)
+
+- [x] Modified `get_attesting_indices` ([Spec](docs/specs/electra/beacon-chain.md#modified-get_attesting_indices), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419))
+- [x] Modified `get_next_sync_committee_indices` ([Spec](docs/specs/electra/beacon-chain.md#modified-get_next_sync_committee_indices), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1417))
+- [x] New `get_balance_churn_limit` ([Spec](docs/specs/electra/beacon-chain.md#new-get_balance_churn_limit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420))
+- [x] New `get_activation_exit_churn_limit` ([Spec](docs/specs/electra/beacon-chain.md#new-get_activation_exit_churn_limit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420))
+- [x] New `get_consolidation_churn_limit` ([Spec](docs/specs/electra/beacon-chain.md#new-get_consolidation_churn_limit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426))
+- [x] New `get_pending_balance_to_withdraw` ([Spec](docs/specs/electra/beacon-chain.md#new-get_pending_balance_to_withdraw), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426))
+
+#### Beacon State Mutators (6/6 - 100%)
+
+- [x] Modified `initiate_validator_exit` ([Spec](docs/specs/electra/beacon-chain.md#modified-initiate_validator_exit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420))
+- [x] New `switch_to_compounding_validator` ([Spec](docs/specs/electra/beacon-chain.md#new-switch_to_compounding_validator), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426))
+- [x] New `queue_excess_active_balance` ([Spec](docs/specs/electra/beacon-chain.md#new-queue_excess_active_balance), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426))
+- [x] New `compute_exit_epoch_and_update_churn` ([Spec](docs/specs/electra/beacon-chain.md#new-compute_exit_epoch_and_update_churn), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420))
+- [x] New `compute_consolidation_epoch_and_update_churn` ([Spec](docs/specs/electra/beacon-chain.md#new-compute_consolidation_epoch_and_update_churn), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426))
+- [x] Modified `slash_validator` ([Spec](docs/specs/electra/beacon-chain.md#modified-slash_validator), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420))
+
+#### Miscellaneous (3/3 - 100%)
+
+- [x] New `get_committee_indices` ([Spec](docs/specs/electra/beacon-chain.md#new-get_committee_indices), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419))
+- [x] Modified `compute_proposer_index` ([Spec](docs/specs/electra/beacon-chain.md#modified-compute_proposer_index), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1417))
+- [x] New `get_max_effective_balance` ([Spec](docs/specs/electra/beacon-chain.md#new-get_max_effective_balance), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1419))
+
+#### Epoch Processing (8/8 - 100%)
+
+- [x] Modified `process_epoch` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_epoch), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1428))
+- [x] Modified `process_registry_updates` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_registry_updates), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420))
+- [x] Modified `process_slashings` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_slashings), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1417))
+- [x] New `apply_pending_deposit` ([Spec](docs/specs/electra/beacon-chain.md#new-apply_pending_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))
+- [x] New `process_pending_deposits` ([Spec](docs/specs/electra/beacon-chain.md#new-process_pending_deposits), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))
+- [x] New `process_pending_consolidations` ([Spec](docs/specs/electra/beacon-chain.md#new-process_pending_consolidations), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1428))
+- [x] Modified `process_effective_balance_updates` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_effective_balance_updates), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1428))
+- [x] Modified `get_validator_from_deposit` ([Spec](docs/specs/electra/beacon-chains.md#modified-get_validator_from_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))
+
+#### Block Processing (12/13 - 92%)
+
+- [x] Modified `process_withdrawals` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_withdrawals), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1431))
+- [ ] Modified `process_execution_payload` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_execution_payload))
+- [x] Modified `process_operations` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_operations), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))
+- [x] Modified `process_attestation` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_attestation), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426))
+- [x] Modified `process_deposit` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))
+- [x] Modified `process_voluntary_exit` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_voluntary_exit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426))
+- [x] New `process_withdrawal_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_withdrawal_request), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426))
+- [x] New `process_deposit_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_deposit_request), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))
+- [x] New `process_consolidation_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_consolidation_request), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1426))
+- [x] New `is_valid_deposit_signature` ([Spec](docs/specs/electra/beacon-chain.md#new-is_valid_deposit_signature), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))
+- [x] Modified `add_validator_to_registry` ([Spec](docs/specs/electra/beacon-chain.md#modified-add_validator_to_registry), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))
+- [x] Modified `apply_deposit` ([Spec](docs/specs/electra/beacon-chain.md#modified-apply_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))
+- [x] Modified `get_expected_withdrawals` ([Spec](docs/specs/electra/beacon-chain.md#modified-get_expected_withdrawals), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1431))
+
+## Execution Engine
+
+#### Execution Engine (0/3 - 0%)
+
+- [ ] Modified `is_valid_block_hash` ([Spec](docs/specs/electra/beacon-chain.md#modified-is_valid_block_hash))
+- [ ] Modified `notify_new_payload` ([Spec](docs/specs/electra/beacon-chain.md#modified-notify_new_payload))
+- [ ] Modified `verify_and_notify_new_payload` ([Spec](docs/specs/electra/beacon-chain.md#modified-verify_and_notify_new_payload))
+
+## Phase 2: P2P & Sepolia Long-Running Sessions
+
+We didn't start this phase yet, its goals are:
+
+- [P2P electra changes](docs/specs/electra/p2p-interface.md) implementated.
+- To have a stable node processing state transitions in testnets, especially Sepolia.
+- Validate the remaining 784 skipped tests to make sure they are not masking any issues.
+
+The aim is to have the node running on Sepolia uninterrupted for 72+ hrs. The following is the implementation gap for this phase:
+
+### Networking (0/8 - 0% Complete)
+
+- [ ] Updated `beacon_block` topic validation ([Spec](docs/specs/electra/p2p-interface.md#beacon_block))
+- [ ] Updated `beacon_aggregate_and_proof` topic validation ([Spec](docs/specs/electra/p2p-interface.md#beacon_aggregate_and_proof))
+- [ ] Updated `blob_sidecar_{subnet_id}` topic validation ([Spec](docs/specs/electra/p2p-interface.md#blob_sidecar_subnet_id))
+- [ ] Updated `beacon_attestation_{subnet_id}` topic validation ([Spec](docs/specs/electra/p2p-interface.md#beacon_attestation_subnet_id))
+- [ ] Updated `BeaconBlocksByRange v2` ([Spec](docs/specs/electra/p2p-interface.md#beaconblocksbyrange-v2))
+- [ ] Updated `BeaconBlocksByRoot v2` ([Spec](docs/specs/electra/p2p-interface.md#beaconblocksbyroot-v2))
+- [ ] Updated `BlobSidecarsByRange v1` ([Spec](docs/specs/electra/p2p-interface.md#blobsidecarsbyrange-v1))
+- [ ] Updated `BlobSidecarsByRoot v1` ([Spec](docs/specs/electra/p2p-interface.md#blobsidecarsbyroot-v1))
+
+## Phase 3: Validator Upgrades
+
+We didn't start this phase yet, its goals are:
+- [Honest validator changes](docs/specs/electra/validator.md) implemented.
+- To have a stable node processing validator duties in a kurtosis devnet.
+- Have assertoor working with automatic test for the validator duties.
+
+The aim is to have the node running on kurtosis uninterrupted for 72+ hrs. The following is the implementation gap for this phase:
+
+### Honest Validator (0/9 - 0% Complete)
+
+- [ ] Modified `GetPayloadResponse` ([Spec](docs/specs/electra/validator.md#modified-getpayloadresponse))
+- [ ] Modified `AggregateAndProof` ([Spec](docs/specs/electra/validator.md#aggregateandproof))
+- [ ] Modified `SignedAggregateAndProof` ([Spec](docs/specs/electra/validator.md#signedaggregateandproof))
+- [ ] Modified `get_payload` ([Spec](docs/specs/electra/validator.md#modified-get_payload))
+- [ ] Updated `prepare_execution_payload` ([Spec](docs/specs/electra/validator.md#execution-payload))
+- [ ] New `get_execution_requests` ([Spec](docs/specs/electra/validator.md#execution-requests))
+- [ ] Updated `compute_subnet_for_blob_sidecar` ([Spec](docs/specs/electra/validator.md#sidecar))
+- [ ] Updated `construct attestation` ([Spec](docs/specs/electra/validator.md#construct-attestation))
+- [ ] Updated `construct aggregate` ([Spec](docs/specs/electra/validator.md#construct-aggregate))
+
+
+## Changelog
+- **April 10th, 2025:** Created the document with the implementation gap.
+- **April 22th, 2025:** Updated the document with a clear roadmap, next steps and detailed current status.
diff --git a/lib/chain_spec/utils.ex b/lib/chain_spec/utils.ex
index 16b1a9fde..54ab7a358 100644
--- a/lib/chain_spec/utils.ex
+++ b/lib/chain_spec/utils.ex
@@ -2,7 +2,7 @@ defmodule ConfigUtils do
@moduledoc """
Utilities for parsing configs and presets.
"""
- @forks ["phase0", "altair", "bellatrix", "capella", "deneb"]
+ @forks ["phase0", "altair", "bellatrix", "capella", "deneb", "electra"]
def load_config_from_file!(path) do
path
diff --git a/lib/constants.ex b/lib/constants.ex
index 81bffa8e2..03a6039d5 100644
--- a/lib/constants.ex
+++ b/lib/constants.ex
@@ -132,4 +132,26 @@ defmodule Constants do
@spec versioned_hash_version_kzg() :: <<_::8>>
def versioned_hash_version_kzg(), do: <<1>>
+
+ # New in Electra
+ @spec unset_deposit_requests_start_index() :: Types.uint64()
+ def unset_deposit_requests_start_index(), do: 2 ** 64 - 1
+
+ @spec full_exit_request_amount() :: Types.uint64()
+ def full_exit_request_amount(), do: 0
+
+ @spec compounding_withdrawal_prefix() :: Types.bytes1()
+ def compounding_withdrawal_prefix(), do: <<2>>
+
+ @spec deposit_request_type() :: Types.bytes1()
+ def deposit_request_type(), do: <<0>>
+
+ @spec withdrawal_request_type() :: Types.bytes1()
+ def withdrawal_request_type(), do: <<1>>
+
+ @spec consolidation_request_type() :: Types.bytes1()
+ def consolidation_request_type(), do: <<2>>
+
+ @spec g2_point_at_infinity() :: Types.bls_signature()
+ def g2_point_at_infinity(), do: <<0xC0, 0::8*95>>
end
diff --git a/lib/lambda_ethereum_consensus/state_transition/accessors.ex b/lib/lambda_ethereum_consensus/state_transition/accessors.ex
index a2b041aec..a6d0a6dcd 100644
--- a/lib/lambda_ethereum_consensus/state_transition/accessors.ex
+++ b/lib/lambda_ethereum_consensus/state_transition/accessors.ex
@@ -17,7 +17,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do
alias Types.SyncCommittee
alias Types.Validator
- @max_random_byte 2 ** 8 - 1
+ @max_random_byte 2 ** 16 - 1
@doc """
Compute the correct sync committee for a given `epoch`.
@@ -118,13 +118,16 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do
|> Misc.compute_shuffled_index(active_validator_count, seed) do
candidate_index = Aja.Vector.at!(active_validator_indices, shuffled_index)
- <<_::binary-size(rem(index, 32)), random_byte, _::binary>> =
- SszEx.hash(seed <> Misc.uint64_to_bytes(div(index, 32)))
+ random_bytes = SszEx.hash(seed <> Misc.uint_to_bytes(div(index, 16), 64))
+ offset = rem(index, 16) * 2
- max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE")
+ bytes = binary_part(random_bytes, offset, 2) <> <<0::48>>
+ random_value = Misc.bytes_to_uint64(bytes)
+
+ max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE_ELECTRA")
effective_balance = Aja.Vector.at!(validators, candidate_index).effective_balance
- if effective_balance * @max_random_byte >= max_effective_balance * random_byte do
+ if effective_balance * @max_random_byte >= max_effective_balance * random_value do
{:ok, sync_committee_indices |> List.insert_at(0, candidate_index)}
else
{:ok, sync_committee_indices}
@@ -553,7 +556,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do
{:ok, IndexedAttestation.t()} | {:error, String.t()}
def get_indexed_attestation(%BeaconState{} = state, attestation) do
with {:ok, indices} <-
- get_attesting_indices(state, attestation.data, attestation.aggregation_bits) do
+ get_attesting_indices(state, attestation) do
%IndexedAttestation{
attesting_indices: Enum.sort(indices),
data: attestation.data,
@@ -582,19 +585,40 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do
that slot) and then filters the ones that actually participated. It returns an unordered MapSet,
which is useful for checking inclusion, but should be ordered if used to validate an attestation.
"""
- @spec get_attesting_indices(BeaconState.t(), Types.AttestationData.t(), Types.bitlist()) ::
+ @spec get_attesting_indices(BeaconState.t(), Types.Attestation.t()) ::
{:ok, MapSet.t()} | {:error, String.t()}
- def get_attesting_indices(%BeaconState{} = state, data, bits) do
- with {:ok, committee} <- get_beacon_committee(state, data.slot, data.index) do
- committee
- |> Stream.with_index()
- |> Stream.filter(fn {_value, index} -> participated?(bits, index) end)
- |> Stream.map(fn {value, _index} -> value end)
- |> MapSet.new()
- |> then(&{:ok, &1})
+ def get_attesting_indices(%BeaconState{} = state, %Attestation{
+ data: data,
+ aggregation_bits: aggregation_bits,
+ committee_bits: committee_bits
+ }) do
+ committee_bits
+ |> get_committee_indices()
+ |> Enum.reduce_while({MapSet.new(), 0}, fn committee_index, {attesters, offset} ->
+ case get_beacon_committee(state, data.slot, committee_index) do
+ {:ok, committee} ->
+ committee_attesters = compute_committee_attesters(committee, aggregation_bits, offset)
+
+ {:cont, {MapSet.union(attesters, committee_attesters), offset + length(committee)}}
+
+ error ->
+ {:halt, error}
+ end
+ end)
+ |> case do
+ {:error, error} -> {:error, error}
+ {attesters, _offset} -> {:ok, attesters}
end
end
+ defp compute_committee_attesters(committee, aggregation_bits, offset) do
+ committee
+ |> Stream.with_index(offset)
+ |> Stream.filter(fn {_validator, pos} -> participated?(aggregation_bits, pos) end)
+ |> Stream.map(fn {validator, _} -> validator end)
+ |> MapSet.new()
+ end
+
@spec get_committee_attesting_indices([Types.validator_index()], Types.bitlist()) ::
[Types.validator_index()]
def get_committee_attesting_indices(committee, bits) do
@@ -626,4 +650,55 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do
max(ChainSpec.get("EFFECTIVE_BALANCE_INCREMENT"), total_balance)
end
+
+ @spec get_committee_indices(Types.bitvector()) :: Enumerable.t(Types.commitee_index())
+ def get_committee_indices(committee_bits) do
+ bitlist =
+ for <> do
+ bit
+ end
+ |> Enum.reverse()
+
+ for {bit, index} <- Enum.with_index(bitlist), bit == 1, do: index
+ end
+
+ @doc """
+ Return the churn limit for the current epoch.
+ """
+ @spec get_balance_churn_limit(Types.BeaconState.t()) :: Types.gwei()
+ def get_balance_churn_limit(state) do
+ churn =
+ max(
+ ChainSpec.get("MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA"),
+ div(get_total_active_balance(state), ChainSpec.get("CHURN_LIMIT_QUOTIENT"))
+ )
+
+ churn - rem(churn, ChainSpec.get("EFFECTIVE_BALANCE_INCREMENT"))
+ end
+
+ @doc """
+ Return the churn limit for the current epoch dedicated to activations and exits.
+ """
+ @spec get_activation_exit_churn_limit(Types.BeaconState.t()) :: Types.gwei()
+ def get_activation_exit_churn_limit(state) do
+ min(
+ ChainSpec.get("MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT"),
+ get_balance_churn_limit(state)
+ )
+ end
+
+ @spec get_pending_balance_to_withdraw(BeaconState.t(), Types.validator_index()) :: Types.gwei()
+ def get_pending_balance_to_withdraw(state, validator_index) do
+ for(
+ withdrawal <- state.pending_partial_withdrawals,
+ withdrawal.validator_index == validator_index,
+ do: withdrawal.amount
+ )
+ |> Enum.sum()
+ end
+
+ @spec get_consolidation_churn_limit(BeaconState.t()) :: Types.gwei()
+ def get_consolidation_churn_limit(state) do
+ get_balance_churn_limit(state) - get_activation_exit_churn_limit(state)
+ end
end
diff --git a/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex b/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex
index 1685e0b09..1139e254f 100644
--- a/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex
+++ b/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex
@@ -10,6 +10,7 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do
alias LambdaEthereumConsensus.Utils.BitVector
alias LambdaEthereumConsensus.Utils.Randao
alias Types.BeaconState
+ alias Types.DepositMessage
alias Types.HistoricalSummary
alias Types.Validator
@@ -43,7 +44,6 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do
hysteresis_quotient = ChainSpec.get("HYSTERESIS_QUOTIENT")
hysteresis_downward_multiplier = ChainSpec.get("HYSTERESIS_DOWNWARD_MULTIPLIER")
hysteresis_upward_multiplier = ChainSpec.get("HYSTERESIS_UPWARD_MULTIPLIER")
- max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE")
hysteresis_increment = div(effective_balance_increment, hysteresis_quotient)
downward_threshold = hysteresis_increment * hysteresis_downward_multiplier
@@ -54,7 +54,10 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do
|> Aja.Vector.zip_with(balances, fn %Validator{} = validator, balance ->
if balance + downward_threshold < validator.effective_balance or
validator.effective_balance + upward_threshold < balance do
- min(balance - rem(balance, effective_balance_increment), max_effective_balance)
+ min(
+ balance - rem(balance, effective_balance_increment),
+ Validator.get_max_effective_balance(validator)
+ )
|> then(&%{validator | effective_balance: &1})
else
validator
@@ -117,17 +120,17 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do
adjusted_total_slashing_balance =
min(slashed_sum * proportional_slashing_multiplier, total_balance)
+ penalty_per_effective_balance_increment =
+ div(adjusted_total_slashing_balance, div(total_balance, increment))
+
new_state =
validators
|> Stream.with_index()
|> Enum.reduce(state, fn {validator, index}, acc ->
if validator.slashed and
epoch + div(epochs_per_slashings_vector, 2) == validator.withdrawable_epoch do
- # increment factored out from penalty numerator to avoid uint64 overflow
- penalty_numerator =
- div(validator.effective_balance, increment) * adjusted_total_slashing_balance
-
- penalty = div(penalty_numerator, total_balance) * increment
+ effective_balance_increments = div(validator.effective_balance, increment)
+ penalty = penalty_per_effective_balance_increment * effective_balance_increments
BeaconState.decrease_balance(acc, index, penalty)
else
@@ -144,49 +147,75 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do
current_epoch = Accessors.get_current_epoch(state)
activation_exit_epoch = Misc.compute_activation_exit_epoch(current_epoch)
- churn_limit = Accessors.get_validator_activation_churn_limit(state)
+ validators
+ |> Enum.with_index()
+ |> Enum.reduce_while(state, fn {validator, idx}, state ->
+ handle_validator_registry_update(
+ state,
+ validator,
+ idx,
+ current_epoch,
+ activation_exit_epoch,
+ ejection_balance
+ )
+ end)
+ |> then(fn
+ %BeaconState{} = state -> {:ok, state}
+ {:error, reason} -> {:error, reason}
+ end)
+ end
- result =
- validators
- |> Stream.with_index()
- |> Stream.map(fn {v, i} ->
- {{v, i}, Predicates.eligible_for_activation_queue?(v),
- Predicates.active_validator?(v, current_epoch) and
- v.effective_balance <= ejection_balance}
- end)
- |> Stream.filter(&(elem(&1, 1) or elem(&1, 2)))
- |> Stream.map(fn
- {{v, i}, true, b} -> {{%{v | activation_eligibility_epoch: current_epoch + 1}, i}, b}
- {{v, i}, false = _is_eligible, b} -> {{v, i}, b}
- end)
- |> Enum.reduce({:ok, state}, fn
- _, {:error, _} = err -> err
- {{v, i}, should_be_ejected}, {:ok, st} -> eject_validator(st, v, i, should_be_ejected)
- {err, _}, _ -> err
- end)
+ defp handle_validator_registry_update(
+ state,
+ validator,
+ idx,
+ current_epoch,
+ activation_exit_epoch,
+ ejection_balance
+ ) do
+ cond do
+ Predicates.eligible_for_activation_queue?(validator) ->
+ updated_validator = %Validator{
+ validator
+ | activation_eligibility_epoch: current_epoch + 1
+ }
- with {:ok, new_state} <- result do
- new_state.validators
- |> Stream.with_index()
- |> Stream.filter(fn {v, _} -> Predicates.eligible_for_activation?(state, v) end)
- |> Enum.sort_by(fn {%{activation_eligibility_epoch: ep}, i} -> {ep, i} end)
- |> Enum.take(churn_limit)
- |> Enum.reduce(new_state.validators, fn {v, i}, acc ->
- %{v | activation_epoch: activation_exit_epoch}
- |> then(&Aja.Vector.replace_at!(acc, i, &1))
- end)
- |> then(&{:ok, %BeaconState{new_state | validators: &1}})
- end
- end
+ {:cont,
+ %BeaconState{
+ state
+ | validators: Aja.Vector.replace_at!(state.validators, idx, updated_validator)
+ }}
- defp eject_validator(state, validator, index, false) do
- {:ok, %{state | validators: Aja.Vector.replace_at!(state.validators, index, validator)}}
- end
+ Predicates.active_validator?(validator, current_epoch) &&
+ validator.effective_balance <= ejection_balance ->
+ case Mutators.initiate_validator_exit(state, validator) do
+ {:ok, {state, ejected_validator}} ->
+ updated_state = %{
+ state
+ | validators: Aja.Vector.replace_at!(state.validators, idx, ejected_validator)
+ }
+
+ {:cont, updated_state}
- defp eject_validator(state, validator, index, true) do
- with {:ok, ejected_validator} <- Mutators.initiate_validator_exit(state, validator) do
- {:ok,
- %{state | validators: Aja.Vector.replace_at!(state.validators, index, ejected_validator)}}
+ {:error, msg} ->
+ {:halt, {:error, msg}}
+ end
+
+ Predicates.eligible_for_activation?(state, validator) ->
+ updated_validator = %Validator{
+ validator
+ | activation_epoch: activation_exit_epoch
+ }
+
+ updated_state = %BeaconState{
+ state
+ | validators: Aja.Vector.replace_at!(state.validators, idx, updated_validator)
+ }
+
+ {:cont, updated_state}
+
+ true ->
+ {:cont, state}
end
end
@@ -428,4 +457,191 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do
max(balance + delta, 0)
end)
end
+
+ @spec process_pending_deposits(BeaconState.t()) :: {:ok, BeaconState.t()}
+ def process_pending_deposits(%BeaconState{} = state) do
+ available_for_processing =
+ state.deposit_balance_to_consume + Accessors.get_activation_exit_churn_limit(state)
+
+ finalized_slot = Misc.compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)
+
+ {state, churn_limit_reached, processed_amount, deposits_to_postpone, last_processed_index} =
+ state.pending_deposits
+ |> Enum.with_index()
+ |> Enum.reduce_while({state, false, 0, [], 0}, fn {deposit, index},
+ {state, churn_limit_reached,
+ processed_amount, deposits_to_postpone,
+ _last_processed_index} ->
+ cond do
+ # Do not process deposit requests if Eth1 bridge deposits are not yet applied.
+ deposit.slot > Constants.genesis_slot() &&
+ state.eth1_deposit_index < state.deposit_requests_start_index ->
+ {:halt,
+ {state, churn_limit_reached, processed_amount, deposits_to_postpone, index - 1}}
+
+ # Check if deposit has been finalized, otherwise, stop processing.
+ deposit.slot > finalized_slot ->
+ {:halt,
+ {state, churn_limit_reached, processed_amount, deposits_to_postpone, index - 1}}
+
+ # Check if number of processed deposits has not reached the limit, otherwise, stop processing.
+ index >= ChainSpec.get("MAX_PENDING_DEPOSITS_PER_EPOCH") ->
+ {:halt,
+ {state, churn_limit_reached, processed_amount, deposits_to_postpone, index - 1}}
+
+ true ->
+ handle_pending_deposit(
+ deposit,
+ state,
+ churn_limit_reached,
+ processed_amount,
+ deposits_to_postpone,
+ index,
+ available_for_processing
+ )
+ end
+ end)
+
+ deposit_balance_to_consume =
+ if churn_limit_reached do
+ available_for_processing - processed_amount
+ else
+ 0
+ end
+
+ {:ok,
+ %BeaconState{
+ state
+ | pending_deposits:
+ Enum.drop(state.pending_deposits, last_processed_index + 1)
+ |> Enum.concat(deposits_to_postpone),
+ deposit_balance_to_consume: deposit_balance_to_consume
+ }}
+ end
+
+ defp handle_pending_deposit(
+ deposit,
+ state,
+ churn_limit_reached,
+ processed_amount,
+ deposits_to_postpone,
+ index,
+ available_for_processing
+ ) do
+ far_future_epoch = Constants.far_future_epoch()
+ next_epoch = Accessors.get_current_epoch(state)
+
+ {is_validator_exited, is_validator_withdrawn} =
+ case Enum.find(state.validators, fn v -> v.pubkey == deposit.pubkey end) do
+ %Validator{} = validator ->
+ {validator.exit_epoch < far_future_epoch, validator.withdrawable_epoch < next_epoch}
+
+ _ ->
+ {false, false}
+ end
+
+ cond do
+ # Deposited balance will never become active. Increase balance but do not consume churn
+ is_validator_withdrawn ->
+ {:ok, state} = apply_pending_deposit(state, deposit)
+
+ {:cont, {state, churn_limit_reached, processed_amount, deposits_to_postpone, index}}
+
+ # Validator is exiting, postpone the deposit until after withdrawable epoch
+ is_validator_exited ->
+ deposits_to_postpone = Enum.concat(deposits_to_postpone, [deposit])
+
+ {:cont, {state, churn_limit_reached, processed_amount, deposits_to_postpone, index}}
+
+ true ->
+ # Check if deposit fits in the churn, otherwise, do no more deposit processing in this epoch.
+ is_churn_limit_reached =
+ processed_amount + deposit.amount > available_for_processing
+
+ if is_churn_limit_reached do
+ {:halt, {state, true, processed_amount, deposits_to_postpone, index - 1}}
+ else
+ # Consume churn and apply deposit.
+ processed_amount = processed_amount + deposit.amount
+ {:ok, state} = apply_pending_deposit(state, deposit)
+ {:cont, {state, false, processed_amount, deposits_to_postpone, index}}
+ end
+ end
+ end
+
+ @spec process_pending_consolidations(BeaconState.t()) :: {:ok, BeaconState.t()}
+ def process_pending_consolidations(%BeaconState{} = state) do
+ next_epoch = Accessors.get_current_epoch(state) + 1
+
+ {next_pending_consolidation, state} =
+ state.pending_consolidations
+ |> Enum.reduce_while({0, state}, fn pending_consolidation,
+ {next_pending_consolidation, state} ->
+ source_index = pending_consolidation.source_index
+ target_index = pending_consolidation.target_index
+ source_validator = state.validators |> Aja.Vector.at(source_index)
+
+ cond do
+ source_validator.slashed ->
+ {:cont, {next_pending_consolidation + 1, state}}
+
+ source_validator.withdrawable_epoch > next_epoch ->
+ {:halt, {next_pending_consolidation, state}}
+
+ true ->
+ source_effective_balance =
+ min(
+ Aja.Vector.at(state.balances, source_index),
+ source_validator.effective_balance
+ )
+
+ updated_state =
+ state
+ |> BeaconState.decrease_balance(source_index, source_effective_balance)
+ |> BeaconState.increase_balance(target_index, source_effective_balance)
+
+ {:cont, {next_pending_consolidation + 1, updated_state}}
+ end
+ end)
+
+ {:ok,
+ %BeaconState{
+ state
+ | pending_consolidations:
+ Enum.drop(state.pending_consolidations, next_pending_consolidation)
+ }}
+ end
+
+ defp apply_pending_deposit(state, deposit) do
+ index =
+ Enum.find_index(state.validators, fn validator -> validator.pubkey == deposit.pubkey end)
+
+ current_validator? = is_number(index)
+
+ valid_signature? =
+ current_validator? ||
+ DepositMessage.valid_deposit_signature?(
+ deposit.pubkey,
+ deposit.withdrawal_credentials,
+ deposit.amount,
+ deposit.signature
+ )
+
+ cond do
+ current_validator? ->
+ {:ok, BeaconState.increase_balance(state, index, deposit.amount)}
+
+ !current_validator? && valid_signature? ->
+ Mutators.add_validator_to_registry(
+ state,
+ deposit.pubkey,
+ deposit.withdrawal_credentials,
+ deposit.amount
+ )
+
+ true ->
+ # Neither a validator nor have a valid signature, we do not apply the deposit
+ {:ok, state}
+ end
+ end
end
diff --git a/lib/lambda_ethereum_consensus/state_transition/misc.ex b/lib/lambda_ethereum_consensus/state_transition/misc.ex
index 375988237..3ec3e1dd3 100644
--- a/lib/lambda_ethereum_consensus/state_transition/misc.ex
+++ b/lib/lambda_ethereum_consensus/state_transition/misc.ex
@@ -12,7 +12,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Misc do
alias LambdaEthereumConsensus.Utils
alias Types.BeaconState
- @max_random_byte 2 ** 8 - 1
+ @max_random_byte 2 ** 16 - 1
@doc """
Returns the Unix timestamp at the start of the given slot
@@ -130,7 +130,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Misc do
) ::
{:ok, Types.validator_index()}
def compute_proposer_index(state, indices, seed) do
- max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE")
+ max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE_ELECTRA")
total = Aja.Vector.size(indices)
Stream.iterate(0, &(&1 + 1))
@@ -138,15 +138,18 @@ defmodule LambdaEthereumConsensus.StateTransition.Misc do
{:ok, index} = compute_shuffled_index(rem(i, total), total, seed)
candidate_index = Aja.Vector.at!(indices, index)
- <<_::binary-size(rem(i, 32)), random_byte, _::binary>> =
- SszEx.hash(seed <> uint_to_bytes(div(i, 32), 64))
+ random_bytes = SszEx.hash(seed <> uint_to_bytes(div(i, 16), 64))
+ offset = rem(i, 16) * 2
+
+ bytes = binary_part(random_bytes, offset, 2) <> <<0::48>>
+ random_value = bytes_to_uint64(bytes)
effective_balance = Aja.Vector.at(state.validators, candidate_index).effective_balance
- {effective_balance, random_byte, candidate_index}
+ {effective_balance, random_value, candidate_index}
end)
- |> Stream.filter(fn {effective_balance, random_byte, _} ->
- effective_balance * @max_random_byte >= max_effective_balance * random_byte
+ |> Stream.filter(fn {effective_balance, random_value, _} ->
+ effective_balance * @max_random_byte >= max_effective_balance * random_value
end)
|> Enum.take(1)
|> then(fn [{_, _, candidate_index}] -> {:ok, candidate_index} end)
diff --git a/lib/lambda_ethereum_consensus/state_transition/mutators.ex b/lib/lambda_ethereum_consensus/state_transition/mutators.ex
index 1deb14701..60d26f8e0 100644
--- a/lib/lambda_ethereum_consensus/state_transition/mutators.ex
+++ b/lib/lambda_ethereum_consensus/state_transition/mutators.ex
@@ -5,65 +5,41 @@ defmodule LambdaEthereumConsensus.StateTransition.Mutators do
alias LambdaEthereumConsensus.StateTransition.Accessors
alias LambdaEthereumConsensus.StateTransition.Misc
alias Types.BeaconState
+ alias Types.DepositMessage
+ alias Types.PendingDeposit
alias Types.Validator
@doc """
Initiate the exit of the validator with index ``index``.
"""
@spec initiate_validator_exit(BeaconState.t(), integer()) ::
- {:ok, Validator.t()} | {:error, String.t()}
+ {:ok, {BeaconState.t(), Validator.t()}} | {:error, String.t()}
def initiate_validator_exit(%BeaconState{} = state, index) when is_integer(index) do
initiate_validator_exit(state, Aja.Vector.at!(state.validators, index))
end
@spec initiate_validator_exit(BeaconState.t(), Validator.t()) ::
- {:ok, Validator.t()} | {:error, String.t()}
+ {:ok, {BeaconState.t(), Validator.t()}} | {:error, String.t()}
def initiate_validator_exit(%BeaconState{} = state, %Validator{} = validator) do
far_future_epoch = Constants.far_future_epoch()
min_validator_withdrawability_delay = ChainSpec.get("MIN_VALIDATOR_WITHDRAWABILITY_DELAY")
if validator.exit_epoch != far_future_epoch do
- {:ok, validator}
+ {:ok, {state, validator}}
else
- exit_epochs =
- state.validators
- |> Stream.filter(fn validator ->
- validator.exit_epoch != far_future_epoch
- end)
- |> Stream.map(fn validator -> validator.exit_epoch end)
- |> Enum.to_list()
-
- exit_queue_epoch =
- Enum.max(
- exit_epochs ++ [Misc.compute_activation_exit_epoch(Accessors.get_current_epoch(state))]
- )
-
- exit_queue_churn =
- state.validators
- |> Stream.filter(fn validator ->
- validator.exit_epoch == exit_queue_epoch
- end)
- |> Enum.to_list()
- |> length()
-
- exit_queue_epoch =
- if exit_queue_churn >= Accessors.get_validator_churn_limit(state) do
- exit_queue_epoch + 1
- else
- exit_queue_epoch
- end
+ state = compute_exit_epoch_and_update_churn(state, validator.effective_balance)
+ exit_queue_epoch = state.earliest_exit_epoch
- next_withdrawable_epoch = exit_queue_epoch + min_validator_withdrawability_delay
-
- if next_withdrawable_epoch > Constants.far_future_epoch() do
- {:error, "withdrawable_epoch_too_large"}
+ if exit_queue_epoch + min_validator_withdrawability_delay > 2 ** 64 do
+ {:error, "withdrawable_epoch overflow"}
else
{:ok,
- %{
- validator
- | exit_epoch: exit_queue_epoch,
- withdrawable_epoch: next_withdrawable_epoch
- }}
+ {state,
+ %{
+ validator
+ | exit_epoch: exit_queue_epoch,
+ withdrawable_epoch: exit_queue_epoch + min_validator_withdrawability_delay
+ }}}
end
end
end
@@ -78,17 +54,17 @@ defmodule LambdaEthereumConsensus.StateTransition.Mutators do
) ::
{:ok, BeaconState.t()} | {:error, String.t()}
def slash_validator(state, slashed_index, whistleblower_index \\ nil) do
- with {:ok, validator} <- initiate_validator_exit(state, slashed_index),
+ with {:ok, {state, validator}} <- initiate_validator_exit(state, slashed_index),
state = add_slashing(state, validator, slashed_index),
{:ok, proposer_index} <- Accessors.get_beacon_proposer_index(state) do
slashing_penalty =
validator.effective_balance
- |> div(ChainSpec.get("MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX"))
+ |> div(ChainSpec.get("MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA"))
whistleblower_index = whistleblower_index(whistleblower_index, proposer_index)
whistleblower_reward =
- div(validator.effective_balance, ChainSpec.get("WHISTLEBLOWER_REWARD_QUOTIENT"))
+ div(validator.effective_balance, ChainSpec.get("WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA"))
proposer_reward =
(whistleblower_reward * Constants.proposer_weight())
@@ -148,31 +124,49 @@ defmodule LambdaEthereumConsensus.StateTransition.Mutators do
Types.uint64(),
Types.bls_signature()
) :: {:ok, BeaconState.t()} | {:error, String.t()}
- def apply_deposit(state, pubkey, withdrawal_credentials, amount, signature) do
- case Enum.find_index(state.validators, fn validator -> validator.pubkey == pubkey end) do
- index when is_number(index) ->
- {:ok, BeaconState.increase_balance(state, index, amount)}
-
- _ ->
- deposit_message = %Types.DepositMessage{
- pubkey: pubkey,
- withdrawal_credentials: withdrawal_credentials,
- amount: amount
- }
+ def apply_deposit(state, pubkey, withdrawal_credentials, amount, sig) do
+ current_validator? = Enum.any?(state.validators, &(&1.pubkey == pubkey))
- domain = Misc.compute_domain(Constants.domain_deposit())
+ valid_signature? =
+ current_validator? ||
+ DepositMessage.valid_deposit_signature?(pubkey, withdrawal_credentials, amount, sig)
- signing_root = Misc.compute_signing_root(deposit_message, domain)
-
- if Bls.valid?(pubkey, signing_root, signature) do
- apply_initial_deposit(state, pubkey, withdrawal_credentials, amount)
+ if !current_validator? && !valid_signature? do
+ # Neither a validator nor have a valid signature, we do not apply the deposit
+ {:ok, state}
+ else
+ updated_state =
+ if current_validator? do
+ state
else
- {:ok, state}
+ {:ok, state} = add_validator_to_registry(state, pubkey, withdrawal_credentials, 0)
+ state
end
+
+ deposit = %PendingDeposit{
+ pubkey: pubkey,
+ withdrawal_credentials: withdrawal_credentials,
+ amount: amount,
+ signature: sig,
+ slot: Constants.genesis_slot()
+ }
+
+ {:ok,
+ %BeaconState{
+ updated_state
+ | pending_deposits: updated_state.pending_deposits ++ [deposit]
+ }}
end
end
- defp apply_initial_deposit(%BeaconState{} = state, pubkey, withdrawal_credentials, amount) do
+ @spec add_validator_to_registry(
+ BeaconState.t(),
+ Types.bls_pubkey(),
+ Types.bytes32(),
+ Types.gwei()
+ ) ::
+ {:ok, BeaconState.t()}
+ def add_validator_to_registry(%BeaconState{} = state, pubkey, withdrawal_credentials, amount) do
Types.Deposit.get_validator_from_deposit(pubkey, withdrawal_credentials, amount)
|> then(&Aja.Vector.append(state.validators, &1))
|> then(
@@ -187,4 +181,132 @@ defmodule LambdaEthereumConsensus.StateTransition.Mutators do
)
|> then(&{:ok, &1})
end
+
+ @spec compute_exit_epoch_and_update_churn(Types.BeaconState.t(), Types.gwei()) ::
+ Types.BeaconState.t()
+ def compute_exit_epoch_and_update_churn(state, exit_balance) do
+ current_epoch = Accessors.get_current_epoch(state)
+
+ earliest_exit_epoch =
+ max(state.earliest_exit_epoch, Misc.compute_activation_exit_epoch(current_epoch))
+
+ per_epoch_churn = Accessors.get_activation_exit_churn_limit(state)
+
+ exit_balance_to_consume =
+ if state.earliest_exit_epoch < earliest_exit_epoch do
+ per_epoch_churn
+ else
+ state.exit_balance_to_consume
+ end
+
+ {earliest_exit_epoch, exit_balance_to_consume} =
+ if exit_balance > exit_balance_to_consume do
+ balance_to_process = exit_balance - exit_balance_to_consume
+ additional_epochs = div(balance_to_process - 1, per_epoch_churn) + 1
+
+ {
+ earliest_exit_epoch + additional_epochs,
+ exit_balance_to_consume + additional_epochs * per_epoch_churn
+ }
+ else
+ {earliest_exit_epoch, exit_balance_to_consume}
+ end
+
+ %BeaconState{
+ state
+ | exit_balance_to_consume: exit_balance_to_consume - exit_balance,
+ earliest_exit_epoch: earliest_exit_epoch
+ }
+ end
+
+ @spec compute_consolidation_epoch_and_update_churn(Types.BeaconState.t(), Types.gwei()) ::
+ Types.BeaconState.t()
+ def compute_consolidation_epoch_and_update_churn(state, consolidation_balance) do
+ current_epoch = Accessors.get_current_epoch(state)
+
+ earliest_consolidation_epoch =
+ max(state.earliest_consolidation_epoch, Misc.compute_activation_exit_epoch(current_epoch))
+
+ per_epoch_consolidation_churn = Accessors.get_consolidation_churn_limit(state)
+
+ consolidation_balance_to_consume =
+ if state.earliest_consolidation_epoch < earliest_consolidation_epoch do
+ per_epoch_consolidation_churn
+ else
+ state.consolidation_balance_to_consume
+ end
+
+ {earliest_consolidation_epoch, consolidation_balance_to_consume} =
+ if consolidation_balance > consolidation_balance_to_consume do
+ balance_to_process = consolidation_balance - consolidation_balance_to_consume
+ additional_epochs = div(balance_to_process - 1, per_epoch_consolidation_churn) + 1
+
+ {
+ earliest_consolidation_epoch + additional_epochs,
+ consolidation_balance_to_consume + additional_epochs * per_epoch_consolidation_churn
+ }
+ else
+ {earliest_consolidation_epoch, consolidation_balance_to_consume}
+ end
+
+ %BeaconState{
+ state
+ | consolidation_balance_to_consume:
+ consolidation_balance_to_consume - consolidation_balance,
+ earliest_consolidation_epoch: earliest_consolidation_epoch
+ }
+ end
+
+ @spec switch_to_compounding_validator(BeaconState.t(), Types.validator_index()) ::
+ BeaconState.t()
+ def switch_to_compounding_validator(state, index) do
+ validator = Aja.Enum.at(state.validators, index)
+ <<_first_byte::binary-size(1), rest::binary>> = validator.withdrawal_credentials
+
+ withdrawal_credentials =
+ Constants.compounding_withdrawal_prefix() <> rest
+
+ updated_validator = %Validator{
+ validator
+ | withdrawal_credentials: withdrawal_credentials
+ }
+
+ state = %BeaconState{
+ state
+ | validators: Aja.Vector.replace_at(state.validators, index, updated_validator)
+ }
+
+ queue_excess_active_balance(state, index)
+ end
+
+ @spec queue_excess_active_balance(BeaconState.t(), Types.validator_index()) ::
+ BeaconState.t()
+ def queue_excess_active_balance(state, index) do
+ min_activation_balance = ChainSpec.get("MIN_ACTIVATION_BALANCE")
+ balance = Aja.Vector.at(state.balances, index)
+
+ if balance > min_activation_balance do
+ excess_balance = balance - min_activation_balance
+ validator = Aja.Vector.at(state.validators, index)
+
+ updated_balances = Aja.Vector.replace_at(state.balances, index, min_activation_balance)
+ # Use bls.G2_POINT_AT_INFINITY as a signature field placeholder
+ # and GENESIS_SLOT to distinguish from a pending deposit request
+ pending_deposit = %PendingDeposit{
+ pubkey: validator.pubkey,
+ withdrawal_credentials: validator.withdrawal_credentials,
+ amount: excess_balance,
+ signature: Constants.g2_point_at_infinity(),
+ slot: Constants.genesis_slot()
+ }
+
+ %BeaconState{
+ state
+ | balances: updated_balances,
+ pending_deposits: state.pending_deposits ++ [pending_deposit]
+ }
+ else
+ state
+ end
+ end
end
diff --git a/lib/lambda_ethereum_consensus/state_transition/operations.ex b/lib/lambda_ethereum_consensus/state_transition/operations.ex
index 89447ff14..7625fdb77 100644
--- a/lib/lambda_ethereum_consensus/state_transition/operations.ex
+++ b/lib/lambda_ethereum_consensus/state_transition/operations.ex
@@ -13,17 +13,23 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
alias LambdaEthereumConsensus.Utils.BitList
alias LambdaEthereumConsensus.Utils.BitVector
alias LambdaEthereumConsensus.Utils.Randao
+ alias Types.PendingConsolidation
+ alias Types.PendingDeposit
+ alias Types.PendingPartialWithdrawal
alias Types.Attestation
alias Types.BeaconBlock
alias Types.BeaconBlockBody
alias Types.BeaconBlockHeader
alias Types.BeaconState
+ alias Types.ConsolidationRequest
+ alias Types.DepositRequest
alias Types.ExecutionPayload
alias Types.ExecutionPayloadHeader
alias Types.SyncAggregate
alias Types.Validator
alias Types.Withdrawal
+ alias Types.WithdrawalRequest
@spec process_block_header(BeaconState.t(), BeaconBlock.t()) ::
{:ok, BeaconState.t()} | {:error, String.t()}
@@ -235,7 +241,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
payload.timestamp != Misc.compute_timestamp_at_slot(state, state.slot) ->
{:error, "Timestamp verification failed"}
- body.blob_kzg_commitments |> length() > ChainSpec.get("MAX_BLOBS_PER_BLOCK") ->
+ body.blob_kzg_commitments |> length() > ChainSpec.get("MAX_BLOBS_PER_BLOCK_ELECTRA") ->
{:error, "Too many commitments"}
# Cache execution payload header
@@ -288,11 +294,18 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
%BeaconState{validators: validators} = state,
%ExecutionPayload{withdrawals: withdrawals}
) do
- expected_withdrawals = get_expected_withdrawals(state)
+ {expected_withdrawals, processed_partial_withdrawals_count} = get_expected_withdrawals(state)
with :ok <- check_withdrawals(withdrawals, expected_withdrawals) do
state
|> Map.update!(:balances, &decrease_balances(&1, withdrawals))
+ |> then(
+ &%BeaconState{
+ &1
+ | pending_partial_withdrawals:
+ Enum.drop(&1.pending_partial_withdrawals, processed_partial_withdrawals_count)
+ }
+ )
|> update_next_withdrawal_index(withdrawals)
|> update_next_withdrawal_validator_index(withdrawals, Aja.Vector.size(validators))
|> then(&{:ok, &1})
@@ -349,49 +362,147 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
end)
end
- @spec get_expected_withdrawals(BeaconState.t()) :: list(Withdrawal.t())
+ @spec get_expected_withdrawals(BeaconState.t()) ::
+ {list(Withdrawal.t()), non_neg_integer()}
def get_expected_withdrawals(%BeaconState{} = state) do
# Compute the next batch of withdrawals which should be included in a block.
epoch = Accessors.get_current_epoch(state)
max_validators_per_withdrawals_sweep = ChainSpec.get("MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP")
max_withdrawals_per_payload = ChainSpec.get("MAX_WITHDRAWALS_PER_PAYLOAD")
- max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE")
+ # Consume pending partial withdrawals
+ {processed_partial_withdrawals_count, withdrawal_index, pending_partial_withdrawals} =
+ state.pending_partial_withdrawals
+ |> Enum.reduce_while({0, state.next_withdrawal_index, []}, fn withdrawal,
+ {processed_partial_withdrawals_count,
+ withdrawal_index,
+ withdrawals} ->
+ process_partial_withdrawal(
+ state,
+ withdrawal,
+ processed_partial_withdrawals_count,
+ withdrawal_index,
+ withdrawals
+ )
+ end)
bound = state.validators |> Aja.Vector.size() |> min(max_validators_per_withdrawals_sweep)
+ # Sweep for remaining.
+ non_partial_withdrawals =
+ Stream.zip([state.validators, state.balances])
+ |> Stream.with_index()
+ |> Stream.cycle()
+ |> Stream.drop(state.next_withdrawal_validator_index)
+ |> Stream.take(bound)
+ |> Stream.map(fn {{validator, balance}, index} ->
+ partially_withdrawn_balance =
+ Enum.sum(
+ for withdrawal <- pending_partial_withdrawals,
+ withdrawal.validator_index == index,
+ do: withdrawal.amount
+ )
+
+ balance = balance - partially_withdrawn_balance
+
+ cond do
+ Validator.fully_withdrawable_validator?(validator, balance, epoch) ->
+ {validator, balance, index}
+
+ Validator.partially_withdrawable_validator?(validator, balance) ->
+ {validator, balance - Validator.get_max_effective_balance(validator), index}
+
+ true ->
+ nil
+ end
+ end)
+ |> Stream.reject(&is_nil/1)
+ |> Stream.with_index()
+ |> Stream.map(fn {{validator, balance, validator_index}, index} ->
+ %Validator{withdrawal_credentials: withdrawal_credentials} = validator
- Stream.zip([state.validators, state.balances])
- |> Stream.with_index()
- |> Stream.cycle()
- |> Stream.drop(state.next_withdrawal_validator_index)
- |> Stream.take(bound)
- |> Stream.map(fn {{validator, balance}, index} ->
- cond do
- Validator.fully_withdrawable_validator?(validator, balance, epoch) ->
- {validator, balance, index}
-
- Validator.partially_withdrawable_validator?(validator, balance) ->
- {validator, balance - max_effective_balance, index}
-
- true ->
- nil
- end
- end)
- |> Stream.reject(&is_nil/1)
- |> Stream.with_index()
- |> Stream.map(fn {{validator, balance, validator_index}, index} ->
- %Validator{withdrawal_credentials: withdrawal_credentials} = validator
+ <<_::binary-size(12), execution_address::binary>> = withdrawal_credentials
- <<_::binary-size(12), execution_address::binary>> = withdrawal_credentials
+ %Withdrawal{
+ index: index + withdrawal_index,
+ validator_index: validator_index,
+ address: execution_address,
+ amount: balance
+ }
+ end)
- %Withdrawal{
- index: index + state.next_withdrawal_index,
- validator_index: validator_index,
- address: execution_address,
- amount: balance
+ complete_withdrawals =
+ (pending_partial_withdrawals ++ Enum.to_list(non_partial_withdrawals))
+ |> Enum.take(max_withdrawals_per_payload)
+
+ {complete_withdrawals, processed_partial_withdrawals_count}
+ end
+
+ defp process_partial_withdrawal(
+ state,
+ withdrawal,
+ processed_partial_withdrawals_count,
+ withdrawal_index,
+ withdrawals
+ ) do
+ epoch = Accessors.get_current_epoch(state)
+
+ max_pending_partials_per_withdrawals_sweep =
+ ChainSpec.get("MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP")
+
+ # We expect partial withdrawals to be ordered by withdrawable epoch
+ if withdrawal.withdrawable_epoch > epoch ||
+ processed_partial_withdrawals_count == max_pending_partials_per_withdrawals_sweep do
+ {:halt, {processed_partial_withdrawals_count, withdrawal_index, withdrawals}}
+ else
+ do_process_partial_withdrawal(
+ state,
+ withdrawal,
+ processed_partial_withdrawals_count,
+ withdrawal_index,
+ withdrawals
+ )
+ end
+ end
+
+ defp do_process_partial_withdrawal(
+ state,
+ withdrawal,
+ processed_partial_withdrawals_count,
+ withdrawal_index,
+ withdrawals
+ ) do
+ far_future_epoch = Constants.far_future_epoch()
+ min_activation_balance = ChainSpec.get("MIN_ACTIVATION_BALANCE")
+ validator = Aja.Vector.at(state.validators, withdrawal.validator_index)
+ has_sufficient_effective_balance = validator.effective_balance >= min_activation_balance
+
+ has_excess_balance =
+ Aja.Vector.at(state.balances, withdrawal.validator_index) > min_activation_balance
+
+ if validator.exit_epoch == far_future_epoch && has_sufficient_effective_balance &&
+ has_excess_balance do
+ withdrawable_balance =
+ min(
+ Aja.Vector.at(state.balances, withdrawal.validator_index) -
+ min_activation_balance,
+ withdrawal.amount
+ )
+
+ <<_::binary-size(12), address::binary>> = validator.withdrawal_credentials
+
+ withdrawal = %Withdrawal{
+ index: withdrawal_index,
+ validator_index: withdrawal.validator_index,
+ address: address,
+ amount: withdrawable_balance
}
- end)
- |> Enum.take(max_withdrawals_per_payload)
+
+ {:cont,
+ {processed_partial_withdrawals_count + 1, withdrawal_index + 1,
+ withdrawals ++ [withdrawal]}}
+ else
+ {:cont, {processed_partial_withdrawals_count + 1, withdrawal_index, withdrawals}}
+ end
end
@spec process_proposer_slashing(BeaconState.t(), Types.ProposerSlashing.t()) ::
@@ -561,6 +672,9 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
current_epoch < validator.activation_epoch + ChainSpec.get("SHARD_COMMITTEE_PERIOD") ->
{:error, "validator cannot exit yet"}
+ Accessors.get_pending_balance_to_withdraw(state, voluntary_exit.validator_index) != 0 ->
+ {:error, "validator has pending withdrawals in the queue"}
+
not (Misc.compute_domain(
Constants.domain_voluntary_exit(),
fork_version: ChainSpec.get("CAPELLA_FORK_VERSION"),
@@ -571,7 +685,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
{:error, "invalid signature"}
true ->
- with {:ok, validator} <- Mutators.initiate_validator_exit(state, validator_index) do
+ with {:ok, {state, validator}} <- Mutators.initiate_validator_exit(state, validator_index) do
Aja.Vector.replace_at!(state.validators, validator_index, validator)
|> then(&{:ok, %BeaconState{state | validators: &1}})
end
@@ -588,16 +702,18 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
end
@spec validate_attestation(BeaconState.t(), Attestation.t()) :: :ok | {:error, String.t()}
- def validate_attestation(state, %Attestation{data: data} = attestation) do
+ def validate_attestation(
+ state,
+ %Attestation{data: data, aggregation_bits: aggregation_bits} = attestation
+ ) do
with :ok <- check_valid_target_epoch(data, state),
:ok <- check_epoch_matches(data),
:ok <- check_valid_slot_range(data, state),
- :ok <- check_committee_count(data, state),
- {:ok, beacon_committee} <- Accessors.get_beacon_committee(state, data.slot, data.index),
- :ok <- check_matching_aggregation_bits_length(attestation, beacon_committee) do
- beacon_committee
- |> Accessors.get_committee_indexed_attestation(attestation)
- |> then(&check_valid_indexed_attestation(state, &1))
+ :ok <- check_data_index_zero(data),
+ {:ok, committee_offset} <- check_committee_indices(attestation, state),
+ :ok <- check_matching_aggregation_bits_length(aggregation_bits, committee_offset),
+ {:ok, indexed_attestation} <- Accessors.get_indexed_attestation(state, attestation) do
+ check_valid_indexed_attestation(state, indexed_attestation)
end
end
@@ -695,7 +811,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
def fast_process_attestation(
state,
- %Attestation{data: data, aggregation_bits: aggregation_bits} = att,
+ %Attestation{data: data} = att,
previous_epoch_updates,
current_epoch_updates,
attestation_index
@@ -704,10 +820,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
slot = state.slot - data.slot,
{:ok, flag_indices} <-
Accessors.get_attestation_participation_flag_indices(state, data, slot),
- {:ok, committee} <- Accessors.get_beacon_committee(state, data.slot, data.index) do
- attesting_indices =
- Accessors.get_committee_attesting_indices(committee, aggregation_bits)
-
+ {:ok, attesting_indices} <- Accessors.get_attesting_indices(state, att) do
is_current_epoch = data.target.epoch == Accessors.get_current_epoch(state)
epoch_updates = if is_current_epoch, do: current_epoch_updates, else: previous_epoch_updates
@@ -722,6 +835,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
new_epoch_updates =
attesting_indices
+ |> Enum.to_list()
|> Enum.reduce(epoch_updates, fn i, epoch_updates ->
Map.update(epoch_updates, i, [v], &merge_masks(&1, v))
end)
@@ -838,23 +952,22 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
end
end
- defp check_committee_count(data, state) do
- if data.index >= Accessors.get_committee_count_per_slot(state, data.target.epoch) do
- {:error, "Index exceeds committee count"}
+ defp check_committee_count(comittee_index, data, state) do
+ if comittee_index >= Accessors.get_committee_count_per_slot(state, data.target.epoch) do
+ {:error, "Comitee index exceeds committee count"}
else
:ok
end
end
- defp check_matching_aggregation_bits_length(attestation, beacon_committee) do
- aggregation_bits_length = BitList.length(attestation.aggregation_bits)
- beacon_committee_length = length(beacon_committee)
+ defp check_matching_aggregation_bits_length(aggregation_bits, committe_offset) do
+ aggregation_bits_length = BitList.length(aggregation_bits)
- if aggregation_bits_length == beacon_committee_length do
+ if aggregation_bits_length == committe_offset do
:ok
else
{:error,
- "Mismatched length. aggregation_bits: #{aggregation_bits_length}. beacon_committee: #{beacon_committee_length}"}
+ "Mismatched length. aggregation_bits: #{aggregation_bits_length}. committee_offset: #{committe_offset}"}
end
end
@@ -866,6 +979,36 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
end
end
+ defp check_data_index_zero(%{index: 0}), do: :ok
+ defp check_data_index_zero(_data), do: {:error, "Data index should be zero"}
+
+ defp check_committee_attesters_exists(committee, aggregation_bits, committee_offset) do
+ committee
+ |> Enum.with_index()
+ |> Enum.any?(&BitList.set?(aggregation_bits, elem(&1, 1) + committee_offset))
+ |> case do
+ true -> :ok
+ false -> {:error, "No committee attesters exist"}
+ end
+ end
+
+ defp check_committee_indices(attestation, state) do
+ %Attestation{data: data, aggregation_bits: aggregation_bits, committee_bits: committee_bits} =
+ attestation
+
+ committee_bits
+ |> Accessors.get_committee_indices()
+ |> Enum.reduce_while({:ok, 0}, fn committee_index, {:ok, committee_offset} ->
+ with :ok <- check_committee_count(committee_index, data, state),
+ {:ok, committee} <- Accessors.get_beacon_committee(state, data.slot, committee_index),
+ :ok <- check_committee_attesters_exists(committee, aggregation_bits, committee_offset) do
+ {:cont, {:ok, committee_offset + length(committee)}}
+ else
+ error -> {:halt, error}
+ end
+ end)
+ end
+
def process_bls_to_execution_change(state, signed_address_change) do
address_change = signed_address_change.message
@@ -918,6 +1061,326 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
end
end
+ @spec process_deposit_request(BeaconState.t(), DepositRequest.t()) :: {:ok, BeaconState.t()}
+ def process_deposit_request(state, deposit_request) do
+ start_index =
+ if state.deposit_requests_start_index == Constants.unset_deposit_requests_start_index(),
+ do: deposit_request.index,
+ else: state.deposit_requests_start_index
+
+ deposit = %PendingDeposit{
+ pubkey: deposit_request.pubkey,
+ withdrawal_credentials: deposit_request.withdrawal_credentials,
+ amount: deposit_request.amount,
+ signature: deposit_request.signature,
+ slot: state.slot
+ }
+
+ {:ok,
+ %BeaconState{
+ state
+ | deposit_requests_start_index: start_index,
+ pending_deposits: state.pending_deposits ++ [deposit]
+ }}
+ end
+
+ @spec process_withdrawal_request(BeaconState.t(), WithdrawalRequest.t()) ::
+ {:ok, BeaconState.t()} | {:error, String.t()}
+ def process_withdrawal_request(state, withdrawal_request) do
+ amount = withdrawal_request.amount
+ is_full_exit_request = amount == Constants.full_exit_request_amount()
+ request_pubkey = withdrawal_request.validator_pubkey
+ current_epoch = Accessors.get_current_epoch(state)
+ far_future_epoch = Constants.far_future_epoch()
+
+ with false <- partial_withdrawal_on_full_queue?(state, is_full_exit_request),
+ {validator, validator_index} <- find_validator(state, request_pubkey),
+ true <-
+ not invalid_withdrawal_credentials?(validator, withdrawal_request.source_address),
+ true <- Predicates.active_validator?(validator, current_epoch),
+ true <- validator.exit_epoch == far_future_epoch,
+ true <-
+ current_epoch >= validator.activation_epoch + ChainSpec.get("SHARD_COMMITTEE_PERIOD") do
+ pending_balance_to_withdraw =
+ Accessors.get_pending_balance_to_withdraw(state, validator_index)
+
+ withdrawal_request_type =
+ cond do
+ is_full_exit_request and pending_balance_to_withdraw == 0 -> :full_exit
+ is_full_exit_request -> :full_exit_with_pending_balance
+ true -> :partial_exit
+ end
+
+ handle_valid_withdrawal_request(
+ state,
+ validator,
+ validator_index,
+ amount,
+ pending_balance_to_withdraw,
+ withdrawal_request_type
+ )
+ else
+ _ ->
+ {:ok, state}
+ end
+ end
+
+ defp partial_withdrawal_on_full_queue?(state, is_full_exit_request) do
+ length(state.pending_partial_withdrawals) ==
+ ChainSpec.get("PENDING_PARTIAL_WITHDRAWALS_LIMIT") && !is_full_exit_request
+ end
+
+ defp invalid_withdrawal_credentials?(validator, address) do
+ has_correct_credential = Validator.has_execution_withdrawal_credential(validator)
+
+ is_correct_source_address =
+ case validator.withdrawal_credentials do
+ <<_::binary-size(12), validator_address::binary>> -> validator_address == address
+ _ -> false
+ end
+
+ !(has_correct_credential && is_correct_source_address)
+ end
+
+ @spec find_validator(Types.BeaconState.t(), Types.bls_pubkey()) ::
+ {Types.Validator.t(), non_neg_integer()} | nil
+ defp find_validator(state, request_pubkey) do
+ state.validators
+ |> Aja.Enum.find_index(fn validator -> validator.pubkey == request_pubkey end)
+ |> then(fn
+ nil -> nil
+ index -> {Aja.Vector.at(state.validators, index), index}
+ end)
+ end
+
+ defp handle_valid_withdrawal_request(state, _, validator_index, _, _, :full_exit) do
+ with {:ok, {state, validator}} <- Mutators.initiate_validator_exit(state, validator_index) do
+ {:ok,
+ %Types.BeaconState{
+ state
+ | validators: Aja.Vector.replace_at(state.validators, validator_index, validator)
+ }}
+ end
+ end
+
+ defp handle_valid_withdrawal_request(state, _, _, _, _, :full_exit_with_pending_balance),
+ do: {:ok, state}
+
+ defp handle_valid_withdrawal_request(
+ state,
+ validator,
+ validator_index,
+ amount,
+ pending_balance_to_withdraw,
+ :partial_exit
+ ) do
+ min_activation_balance = ChainSpec.get("MIN_ACTIVATION_BALANCE")
+
+ has_sufficient_effective_balance =
+ validator.effective_balance >= min_activation_balance
+
+ has_excess_balance =
+ Aja.Vector.at(state.balances, validator_index) >
+ min_activation_balance + pending_balance_to_withdraw
+
+ if Validator.has_compounding_withdrawal_credential(validator) &&
+ has_sufficient_effective_balance && has_excess_balance do
+ to_withdraw =
+ min(
+ Aja.Vector.at(state.balances, validator_index) - min_activation_balance -
+ pending_balance_to_withdraw,
+ amount
+ )
+
+ state = Mutators.compute_exit_epoch_and_update_churn(state, to_withdraw)
+ exit_queue_epoch = state.earliest_exit_epoch
+
+ withdrawable_epoch =
+ exit_queue_epoch + ChainSpec.get("MIN_VALIDATOR_WITHDRAWABILITY_DELAY")
+
+ pending_partial_withdrawal = %PendingPartialWithdrawal{
+ validator_index: validator_index,
+ amount: to_withdraw,
+ withdrawable_epoch: withdrawable_epoch
+ }
+
+ {:ok,
+ %BeaconState{
+ state
+ | # We should make sure that partial withdrawals are ordered by withdrawable epoch
+ pending_partial_withdrawals:
+ state.pending_partial_withdrawals ++ [pending_partial_withdrawal]
+ }}
+ else
+ {:ok, state}
+ end
+ end
+
+ @spec process_consolidation_request(BeaconState.t(), ConsolidationRequest.t()) ::
+ {:ok, BeaconState.t()}
+ def process_consolidation_request(state, consolidation_request) do
+ request_type =
+ if valid_switch_to_compounding_request?(state, consolidation_request),
+ do: :compounding,
+ else: :consolidation
+
+ do_process_consolidation_request(state, consolidation_request, request_type)
+ end
+
+ defp do_process_consolidation_request(state, consolidation_request, :compounding) do
+ case find_validator(state, consolidation_request.source_pubkey) do
+ {_validator, validator_index} ->
+ {:ok, Mutators.switch_to_compounding_validator(state, validator_index)}
+
+ nil ->
+ {:ok, state}
+ end
+ end
+
+ defp do_process_consolidation_request(state, consolidation_request, :consolidation) do
+ with :ok <- verify_consolidation_request(state, consolidation_request),
+ {source_validator, source_index} <-
+ find_validator(state, consolidation_request.source_pubkey),
+ {_target_validator, target_index} <-
+ find_validator(state, consolidation_request.target_pubkey),
+ :ok <-
+ verify_consolidation_validators(
+ state,
+ source_index,
+ target_index,
+ consolidation_request
+ ) do
+ state =
+ Mutators.compute_consolidation_epoch_and_update_churn(
+ state,
+ source_validator.effective_balance
+ )
+
+ consolidation_epoch = state.earliest_consolidation_epoch
+
+ withdrawable_epoch =
+ consolidation_epoch + ChainSpec.get("MIN_VALIDATOR_WITHDRAWABILITY_DELAY")
+
+ updated_source_validator = %Validator{
+ source_validator
+ | exit_epoch: consolidation_epoch,
+ withdrawable_epoch: withdrawable_epoch
+ }
+
+ pending_consolidation = %PendingConsolidation{
+ source_index: source_index,
+ target_index: target_index
+ }
+
+ updated_state = %BeaconState{
+ state
+ | validators:
+ Aja.Vector.replace_at(state.validators, source_index, updated_source_validator),
+ pending_consolidations: state.pending_consolidations ++ [pending_consolidation]
+ }
+
+ {:ok, updated_state}
+ else
+ _error -> {:ok, state}
+ end
+ end
+
+ defp verify_consolidation_request(state, consolidation_request) do
+ cond do
+ consolidation_request.source_pubkey == consolidation_request.target_pubkey ->
+ {:error, :source_target_same}
+
+ # If the pending consolidations queue is full, consolidation requests are ignored
+ length(state.pending_consolidations) >= ChainSpec.get("PENDING_CONSOLIDATIONS_LIMIT") ->
+ {:error, :queue_full}
+
+ # If there is too little available consolidation churn limit, consolidation requests are ignored
+ Accessors.get_consolidation_churn_limit(state) <= ChainSpec.get("MIN_ACTIVATION_BALANCE") ->
+ {:error, :churn_limit_not_met}
+
+ true ->
+ :ok
+ end
+ end
+
+ defp verify_consolidation_validators(state, source_index, target_index, consolidation_request) do
+ source_validator = Aja.Vector.at(state.validators, source_index)
+ target_validator = Aja.Vector.at(state.validators, target_index)
+ current_epoch = Accessors.get_current_epoch(state)
+ far_future_epoch = Constants.far_future_epoch()
+
+ cond do
+ invalid_consolidation_request_credentials?(
+ source_validator,
+ target_validator,
+ consolidation_request
+ ) ->
+ {:error, :invalid_credentials}
+
+ # Verify the source and the target are active
+ !Predicates.active_validator?(source_validator, current_epoch) ||
+ !Predicates.active_validator?(target_validator, current_epoch) ->
+ {:error, :validator_not_active}
+
+ # Verify exits for source and target have not been initiated
+ source_validator.exit_epoch != far_future_epoch ||
+ target_validator.exit_epoch != far_future_epoch ->
+ {:error, :validator_exiting}
+
+ # Verify the source has been active long enough
+ current_epoch <
+ source_validator.activation_epoch + ChainSpec.get("SHARD_COMMITTEE_PERIOD") ->
+ {:error, :validator_not_active_long_enough}
+
+ # Verify the source has no pending withdrawals in the queue
+ Accessors.get_pending_balance_to_withdraw(state, source_index) > 0 ->
+ {:error, :validator_has_pending_balance}
+
+ # Initiate source validator exit and append pending consolidation
+ true ->
+ :ok
+ end
+ end
+
+ defp invalid_consolidation_request_credentials?(
+ source_validator,
+ target_validator,
+ consolidation_request
+ ) do
+ invalid_withdrawal_credentials?(source_validator, consolidation_request.source_address) ||
+ not Validator.has_compounding_withdrawal_credential(target_validator)
+ end
+
+ @spec valid_switch_to_compounding_request?(BeaconState.t(), ConsolidationRequest.t()) ::
+ boolean()
+ def valid_switch_to_compounding_request?(state, consolidation_request) do
+ current_epoch = Accessors.get_current_epoch(state)
+ far_future_epoch = Constants.far_future_epoch()
+
+ # Verify pubkey exists
+ with {source_validator, _source_index} <-
+ find_validator(state, consolidation_request.source_pubkey),
+ # Switch to compounding requires source and target be equal
+ true <- consolidation_request.source_pubkey == consolidation_request.target_pubkey,
+ # Verify request has been authorized
+ true <-
+ not invalid_withdrawal_credentials?(
+ source_validator,
+ consolidation_request.source_address
+ ),
+ # Verify source withdrawal credentials
+ true <- Validator.has_eth1_withdrawal_credential(source_validator),
+ # Verify the source is active
+ true <- Predicates.active_validator?(source_validator, current_epoch),
+ # Verify exit for source has not been initiated
+ true <- source_validator.exit_epoch == far_future_epoch do
+ true
+ else
+ _ ->
+ false
+ end
+ end
+
@spec process_operations(BeaconState.t(), BeaconBlockBody.t()) ::
{:ok, BeaconState.t()} | {:error, String.t()}
def process_operations(state, body) do
@@ -934,6 +1397,17 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
body.bls_to_execution_changes,
&process_bls_to_execution_change/2
)
+ |> for_ops(:deposit_request, body.execution_requests.deposits, &process_deposit_request/2)
+ |> for_ops(
+ :withdrawal_request,
+ body.execution_requests.withdrawals,
+ &process_withdrawal_request/2
+ )
+ |> for_ops(
+ :consolidation_request,
+ body.execution_requests.consolidations,
+ &process_consolidation_request/2
+ )
end
end
@@ -954,13 +1428,22 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
@spec verify_deposits(BeaconState.t(), BeaconBlockBody.t()) :: :ok | {:error, String.t()}
defp verify_deposits(state, body) do
- deposit_count = state.eth1_data.deposit_count - state.eth1_deposit_index
- deposit_limit = min(ChainSpec.get("MAX_DEPOSITS"), deposit_count)
+ eth1_deposit_index_limit =
+ min(state.eth1_data.deposit_count, state.deposit_requests_start_index)
- if length(body.deposits) == deposit_limit do
- :ok
- else
- {:error, "deposits length mismatch"}
+ max_deposits = ChainSpec.get("MAX_DEPOSITS")
+
+ cond do
+ state.eth1_deposit_index < eth1_deposit_index_limit and
+ length(body.deposits) ==
+ min(max_deposits, eth1_deposit_index_limit - state.eth1_deposit_index) ->
+ :ok
+
+ state.eth1_deposit_index >= eth1_deposit_index_limit and Enum.empty?(body.deposits) ->
+ :ok
+
+ true ->
+ {:error, "Invalid deposits"}
end
end
end
diff --git a/lib/lambda_ethereum_consensus/state_transition/predicates.ex b/lib/lambda_ethereum_consensus/state_transition/predicates.ex
index eb7ce9850..6246237ab 100644
--- a/lib/lambda_ethereum_consensus/state_transition/predicates.ex
+++ b/lib/lambda_ethereum_consensus/state_transition/predicates.ex
@@ -37,10 +37,10 @@ defmodule LambdaEthereumConsensus.StateTransition.Predicates do
@spec eligible_for_activation_queue?(Validator.t()) :: boolean
def eligible_for_activation_queue?(%Validator{} = validator) do
far_future_epoch = Constants.far_future_epoch()
- max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE")
+ min_effective_balance = ChainSpec.get("MIN_ACTIVATION_BALANCE")
validator.activation_eligibility_epoch == far_future_epoch &&
- validator.effective_balance == max_effective_balance
+ validator.effective_balance >= min_effective_balance
end
@doc """
diff --git a/lib/lambda_ethereum_consensus/state_transition/state_transition.ex b/lib/lambda_ethereum_consensus/state_transition/state_transition.ex
index c20fdf891..9217e15bc 100644
--- a/lib/lambda_ethereum_consensus/state_transition/state_transition.ex
+++ b/lib/lambda_ethereum_consensus/state_transition/state_transition.ex
@@ -156,6 +156,8 @@ defmodule LambdaEthereumConsensus.StateTransition do
|> epoch_op(:registry_updates, &EpochProcessing.process_registry_updates/1)
|> epoch_op(:slashings, &EpochProcessing.process_slashings/1)
|> epoch_op(:eth1_data_reset, &EpochProcessing.process_eth1_data_reset/1)
+ |> epoch_op(:pending_deposits, &EpochProcessing.process_pending_deposits/1)
+ |> epoch_op(:pending_consolidations, &EpochProcessing.process_pending_consolidations/1)
|> epoch_op(:effective_balance_updates, &EpochProcessing.process_effective_balance_updates/1)
|> epoch_op(:slashings_reset, &EpochProcessing.process_slashings_reset/1)
|> epoch_op(:randao_mixes_reset, &EpochProcessing.process_randao_mixes_reset/1)
diff --git a/lib/lambda_ethereum_consensus/validator/block_builder.ex b/lib/lambda_ethereum_consensus/validator/block_builder.ex
index aa6aa07d4..7ad0a20a0 100644
--- a/lib/lambda_ethereum_consensus/validator/block_builder.ex
+++ b/lib/lambda_ethereum_consensus/validator/block_builder.ex
@@ -95,7 +95,12 @@ defmodule LambdaEthereumConsensus.Validator.BlockBuilder do
block_request.slot,
block_request.parent_root
),
- execution_payload: execution_payload
+ execution_payload: execution_payload,
+ execution_requests: %Types.ExecutionRequests{
+ deposits: [],
+ withdrawals: [],
+ consolidations: []
+ }
}
}}
end
@@ -132,7 +137,7 @@ defmodule LambdaEthereumConsensus.Validator.BlockBuilder do
prev_randao: Randao.get_randao_mix(mid_state.randao_mixes, current_epoch),
# TODO: add suggested fee recipient
suggested_fee_recipient: <<0::160>>,
- withdrawals: Operations.get_expected_withdrawals(mid_state),
+ withdrawals: elem(Operations.get_expected_withdrawals(mid_state), 0),
parent_beacon_block_root: head_root
}
diff --git a/lib/lambda_ethereum_consensus/validator/validator.ex b/lib/lambda_ethereum_consensus/validator/validator.ex
index eb2c5a67a..9c6252454 100644
--- a/lib/lambda_ethereum_consensus/validator/validator.ex
+++ b/lib/lambda_ethereum_consensus/validator/validator.ex
@@ -175,7 +175,9 @@ defmodule LambdaEthereumConsensus.Validator do
%Attestation{
data: attestation_data,
aggregation_bits: bits,
- signature: signature
+ signature: signature,
+ # Not implemented yet, part of EIP7549
+ committee_bits: BitList.zero(committee_length)
}
end
diff --git a/lib/types/beacon_chain/attestation.ex b/lib/types/beacon_chain/attestation.ex
index 0e5588b93..d623bb5d8 100644
--- a/lib/types/beacon_chain/attestation.ex
+++ b/lib/types/beacon_chain/attestation.ex
@@ -1,45 +1,58 @@
defmodule Types.Attestation do
@moduledoc """
- Struct definition for `AttestationMainnet`.
+ Struct definition for `Attestation`.
Related definitions in `native/ssz_nif/src/types/`.
aggregation_bits is a bit list that has the size of a committee. Each individual bit is set if
the validator corresponding to that bit participated in attesting.
"""
alias LambdaEthereumConsensus.Utils.BitList
+ alias LambdaEthereumConsensus.Utils.BitVector
use LambdaEthereumConsensus.Container
fields = [
:aggregation_bits,
:data,
- :signature
+ :signature,
+ :committee_bits
]
@enforce_keys fields
defstruct fields
@type t :: %__MODULE__{
- # MAX_VALIDATORS_PER_COMMITTEE
+ # [Modified in Electra:EIP7549]
aggregation_bits: Types.bitlist(),
data: Types.AttestationData.t(),
- signature: Types.bls_signature()
+ signature: Types.bls_signature(),
+ # [New in Electra:EIP7549]
+ committee_bits: BitVector.t()
}
@impl LambdaEthereumConsensus.Container
def schema() do
[
- {:aggregation_bits, {:bitlist, ChainSpec.get("MAX_VALIDATORS_PER_COMMITTEE")}},
+ {:aggregation_bits,
+ {:bitlist,
+ ChainSpec.get("MAX_VALIDATORS_PER_COMMITTEE") * ChainSpec.get("MAX_COMMITTEES_PER_SLOT")}},
{:data, Types.AttestationData},
- {:signature, TypeAliases.bls_signature()}
+ {:signature, TypeAliases.bls_signature()},
+ {:committee_bits, {:bitvector, ChainSpec.get("MAX_COMMITTEES_PER_SLOT")}}
]
end
def encode(%__MODULE__{} = map) do
- Map.update!(map, :aggregation_bits, &BitList.to_bytes/1)
+ map
+ |> Map.update!(:aggregation_bits, &BitList.to_bytes/1)
+ |> Map.update!(:committee_bits, &BitVector.to_bytes/1)
end
def decode(%__MODULE__{} = map) do
- Map.update!(map, :aggregation_bits, &BitList.new/1)
+ map
+ |> Map.update!(:aggregation_bits, &BitList.new/1)
+ |> Map.update!(:committee_bits, fn bits ->
+ BitVector.new(bits, ChainSpec.get("MAX_COMMITTEES_PER_SLOT"))
+ end)
end
end
diff --git a/lib/types/beacon_chain/beacon_block_body.ex b/lib/types/beacon_chain/beacon_block_body.ex
index fa4456aca..332f9dab8 100644
--- a/lib/types/beacon_chain/beacon_block_body.ex
+++ b/lib/types/beacon_chain/beacon_block_body.ex
@@ -17,7 +17,9 @@ defmodule Types.BeaconBlockBody do
:sync_aggregate,
:execution_payload,
:bls_to_execution_changes,
- :blob_kzg_commitments
+ :blob_kzg_commitments,
+ # New Electra fields
+ :execution_requests
]
@enforce_keys fields
@@ -36,13 +38,15 @@ defmodule Types.BeaconBlockBody do
# max MAX_DEPOSITS
deposits: list(Types.Deposit.t()),
# max MAX_VOLUNTARY_EXITS
- voluntary_exits: list(Types.VoluntaryExit.t()),
+ voluntary_exits: list(Types.SignedVoluntaryExit.t()),
sync_aggregate: Types.SyncAggregate.t(),
execution_payload: Types.ExecutionPayload.t(),
# max MAX_BLS_TO_EXECUTION_CHANGES
bls_to_execution_changes: list(Types.SignedBLSToExecutionChange.t()),
# max MAX_BLOB_COMMITMENTS_PER_BLOCK
- blob_kzg_commitments: list(Types.kzg_commitment())
+ blob_kzg_commitments: list(Types.kzg_commitment()),
+ # New in Electra
+ execution_requests: Types.ExecutionRequests.t()
}
@impl LambdaEthereumConsensus.Container
@@ -53,9 +57,11 @@ defmodule Types.BeaconBlockBody do
graffiti: TypeAliases.bytes32(),
proposer_slashings:
{:list, Types.ProposerSlashing, ChainSpec.get("MAX_PROPOSER_SLASHINGS")},
+ # [Modified in Electra:EIP7549]
attester_slashings:
- {:list, Types.AttesterSlashing, ChainSpec.get("MAX_ATTESTER_SLASHINGS")},
- attestations: {:list, Types.Attestation, ChainSpec.get("MAX_ATTESTATIONS")},
+ {:list, Types.AttesterSlashing, ChainSpec.get("MAX_ATTESTER_SLASHINGS_ELECTRA")},
+ # [Modified in Electra:EIP7549]
+ attestations: {:list, Types.Attestation, ChainSpec.get("MAX_ATTESTATIONS_ELECTRA")},
deposits: {:list, Types.Deposit, ChainSpec.get("MAX_DEPOSITS")},
voluntary_exits: {:list, Types.SignedVoluntaryExit, ChainSpec.get("MAX_VOLUNTARY_EXITS")},
sync_aggregate: Types.SyncAggregate,
@@ -63,7 +69,9 @@ defmodule Types.BeaconBlockBody do
bls_to_execution_changes:
{:list, Types.SignedBLSToExecutionChange, ChainSpec.get("MAX_BLS_TO_EXECUTION_CHANGES")},
blob_kzg_commitments:
- {:list, TypeAliases.kzg_commitment(), ChainSpec.get("MAX_BLOB_COMMITMENTS_PER_BLOCK")}
+ {:list, TypeAliases.kzg_commitment(), ChainSpec.get("MAX_BLOB_COMMITMENTS_PER_BLOCK")},
+ # New in Electra
+ execution_requests: Types.ExecutionRequests
]
end
end
diff --git a/lib/types/beacon_chain/beacon_state.ex b/lib/types/beacon_chain/beacon_state.ex
index c540b3f52..1aecfc197 100644
--- a/lib/types/beacon_chain/beacon_state.ex
+++ b/lib/types/beacon_chain/beacon_state.ex
@@ -39,7 +39,17 @@ defmodule Types.BeaconState do
:latest_execution_payload_header,
:next_withdrawal_index,
:next_withdrawal_validator_index,
- :historical_summaries
+ :historical_summaries,
+ # New Electra fields
+ :deposit_requests_start_index,
+ :deposit_balance_to_consume,
+ :exit_balance_to_consume,
+ :earliest_exit_epoch,
+ :consolidation_balance_to_consume,
+ :earliest_consolidation_epoch,
+ :pending_deposits,
+ :pending_partial_withdrawals,
+ :pending_consolidations
]
@enforce_keys fields
@@ -105,7 +115,25 @@ defmodule Types.BeaconState do
# Deep history valid from Capella onwards
# [New in Capella]
# HISTORICAL_ROOTS_LIMIT
- historical_summaries: list(Types.HistoricalSummary.t())
+ historical_summaries: list(Types.HistoricalSummary.t()),
+ # [New in Electra:EIP6110]
+ deposit_requests_start_index: Types.uint64(),
+ # [New in Electra:EIP7251]
+ deposit_balance_to_consume: Types.gwei(),
+ # [New in Electra:EIP7251]
+ exit_balance_to_consume: Types.gwei(),
+ # [New in Electra:EIP7251]
+ earliest_exit_epoch: Types.epoch(),
+ # [New in Electra:EIP7251]
+ consolidation_balance_to_consume: Types.gwei(),
+ # [New in Electra:EIP7251]
+ earliest_consolidation_epoch: Types.epoch(),
+ # [New in Electra:EIP7251]
+ pending_deposits: list(Types.PendingDeposit.t()),
+ # [New in Electra:EIP7251]
+ pending_partial_withdrawals: list(Types.PendingPartialWithdrawal.t()),
+ # [New in Electra:EIP7251]
+ pending_consolidations: list(Types.PendingConsolidation.t())
}
@impl LambdaEthereumConsensus.Container
@@ -145,7 +173,19 @@ defmodule Types.BeaconState do
{:next_withdrawal_index, TypeAliases.withdrawal_index()},
{:next_withdrawal_validator_index, TypeAliases.validator_index()},
{:historical_summaries,
- {:list, Types.HistoricalSummary, ChainSpec.get("HISTORICAL_ROOTS_LIMIT")}}
+ {:list, Types.HistoricalSummary, ChainSpec.get("HISTORICAL_ROOTS_LIMIT")}},
+ # New Electra fields
+ {:deposit_requests_start_index, TypeAliases.uint64()},
+ {:deposit_balance_to_consume, TypeAliases.gwei()},
+ {:exit_balance_to_consume, TypeAliases.gwei()},
+ {:earliest_exit_epoch, TypeAliases.epoch()},
+ {:consolidation_balance_to_consume, TypeAliases.gwei()},
+ {:earliest_consolidation_epoch, TypeAliases.epoch()},
+ {:pending_deposits, {:list, Types.PendingDeposit, ChainSpec.get("PENDING_DEPOSITS_LIMIT")}},
+ {:pending_partial_withdrawals,
+ {:list, Types.PendingPartialWithdrawal, ChainSpec.get("PENDING_PARTIAL_WITHDRAWALS_LIMIT")}},
+ {:pending_consolidations,
+ {:list, Types.PendingConsolidation, ChainSpec.get("PENDING_CONSOLIDATIONS_LIMIT")}}
]
end
diff --git a/lib/types/beacon_chain/consolidation_request.ex b/lib/types/beacon_chain/consolidation_request.ex
new file mode 100644
index 000000000..8e8492081
--- /dev/null
+++ b/lib/types/beacon_chain/consolidation_request.ex
@@ -0,0 +1,27 @@
+defmodule Types.ConsolidationRequest do
+ @moduledoc """
+ Struct definition for `ConsolidationRequest`.
+ Added in Electra fork (EIP7251).
+ """
+
+ use LambdaEthereumConsensus.Container
+
+ fields = [:source_address, :source_pubkey, :target_pubkey]
+ @enforce_keys fields
+ defstruct fields
+
+ @type t :: %__MODULE__{
+ source_address: Types.execution_address(),
+ source_pubkey: Types.bls_pubkey(),
+ target_pubkey: Types.bls_pubkey()
+ }
+
+ @impl LambdaEthereumConsensus.Container
+ def schema() do
+ [
+ {:source_address, TypeAliases.execution_address()},
+ {:source_pubkey, TypeAliases.bls_pubkey()},
+ {:target_pubkey, TypeAliases.bls_pubkey()}
+ ]
+ end
+end
diff --git a/lib/types/beacon_chain/deposit.ex b/lib/types/beacon_chain/deposit.ex
index 22808ac15..e7842095e 100644
--- a/lib/types/beacon_chain/deposit.ex
+++ b/lib/types/beacon_chain/deposit.ex
@@ -22,24 +22,29 @@ defmodule Types.Deposit do
@spec get_validator_from_deposit(Types.bls_pubkey(), Types.bytes32(), Types.uint64()) ::
Types.Validator.t()
def get_validator_from_deposit(pubkey, withdrawal_credentials, amount) do
- effective_balance =
- min(
- amount - rem(amount, ChainSpec.get("EFFECTIVE_BALANCE_INCREMENT")),
- ChainSpec.get("MAX_EFFECTIVE_BALANCE")
- )
-
far_future_epoch = Constants.far_future_epoch()
- %Types.Validator{
+ validator = %Types.Validator{
pubkey: pubkey,
withdrawal_credentials: withdrawal_credentials,
activation_eligibility_epoch: far_future_epoch,
activation_epoch: far_future_epoch,
exit_epoch: far_future_epoch,
withdrawable_epoch: far_future_epoch,
- effective_balance: effective_balance,
+ effective_balance: 0,
slashed: false
}
+
+ effective_balance =
+ min(
+ amount - rem(amount, ChainSpec.get("EFFECTIVE_BALANCE_INCREMENT")),
+ Types.Validator.get_max_effective_balance(validator)
+ )
+
+ %Types.Validator{
+ validator
+ | effective_balance: effective_balance
+ }
end
@impl LambdaEthereumConsensus.Container
diff --git a/lib/types/beacon_chain/deposit_message.ex b/lib/types/beacon_chain/deposit_message.ex
index 3ec9b904d..74bbd621d 100644
--- a/lib/types/beacon_chain/deposit_message.ex
+++ b/lib/types/beacon_chain/deposit_message.ex
@@ -3,6 +3,7 @@ defmodule Types.DepositMessage do
Struct definition for `DepositMessage`.
Related definitions in `native/ssz_nif/src/types/`.
"""
+ alias LambdaEthereumConsensus.StateTransition.Misc
use LambdaEthereumConsensus.Container
fields = [
@@ -28,4 +29,23 @@ defmodule Types.DepositMessage do
{:amount, TypeAliases.gwei()}
]
end
+
+ @spec valid_deposit_signature?(
+ Types.bls_pubkey(),
+ Types.bytes32(),
+ Types.gwei(),
+ Types.bls_signature()
+ ) :: boolean()
+ def valid_deposit_signature?(pubkey, withdrawal_credentials, amount, signature) do
+ deposit_message = %__MODULE__{
+ pubkey: pubkey,
+ withdrawal_credentials: withdrawal_credentials,
+ amount: amount
+ }
+
+ domain = Misc.compute_domain(Constants.domain_deposit())
+ signing_root = Misc.compute_signing_root(deposit_message, domain)
+
+ Bls.valid?(pubkey, signing_root, signature)
+ end
end
diff --git a/lib/types/beacon_chain/deposit_request.ex b/lib/types/beacon_chain/deposit_request.ex
new file mode 100644
index 000000000..64fb83292
--- /dev/null
+++ b/lib/types/beacon_chain/deposit_request.ex
@@ -0,0 +1,31 @@
+defmodule Types.DepositRequest do
+ @moduledoc """
+ Struct definition for `DepositRequest`.
+ Added in Electra fork (EIP6110).
+ """
+
+ use LambdaEthereumConsensus.Container
+
+ fields = [:pubkey, :withdrawal_credentials, :amount, :signature, :index]
+ @enforce_keys fields
+ defstruct fields
+
+ @type t :: %__MODULE__{
+ pubkey: Types.bls_pubkey(),
+ withdrawal_credentials: Types.bytes32(),
+ amount: Types.gwei(),
+ signature: Types.bls_signature(),
+ index: Types.uint64()
+ }
+
+ @impl LambdaEthereumConsensus.Container
+ def schema() do
+ [
+ {:pubkey, TypeAliases.bls_pubkey()},
+ {:withdrawal_credentials, TypeAliases.bytes32()},
+ {:amount, TypeAliases.gwei()},
+ {:signature, TypeAliases.bls_signature()},
+ {:index, TypeAliases.uint64()}
+ ]
+ end
+end
diff --git a/lib/types/beacon_chain/execution_requests.ex b/lib/types/beacon_chain/execution_requests.ex
new file mode 100644
index 000000000..e5d7d07ef
--- /dev/null
+++ b/lib/types/beacon_chain/execution_requests.ex
@@ -0,0 +1,31 @@
+defmodule Types.ExecutionRequests do
+ @moduledoc """
+ Struct definition for `ExecutionRequests`.
+ Added in Electra fork.
+ """
+
+ use LambdaEthereumConsensus.Container
+
+ fields = [:deposits, :withdrawals, :consolidations]
+ @enforce_keys fields
+ defstruct fields
+
+ @type t :: %__MODULE__{
+ deposits: list(Types.DepositRequest.t()),
+ withdrawals: list(Types.WithdrawalRequest.t()),
+ consolidations: list(Types.ConsolidationRequest.t())
+ }
+
+ @impl LambdaEthereumConsensus.Container
+ def schema() do
+ [
+ {:deposits,
+ {:list, Types.DepositRequest, ChainSpec.get("MAX_DEPOSIT_REQUESTS_PER_PAYLOAD")}},
+ {:withdrawals,
+ {:list, Types.WithdrawalRequest, ChainSpec.get("MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD")}},
+ {:consolidations,
+ {:list, Types.ConsolidationRequest,
+ ChainSpec.get("MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD")}}
+ ]
+ end
+end
diff --git a/lib/types/beacon_chain/indexed_attestation.ex b/lib/types/beacon_chain/indexed_attestation.ex
index 2fa258522..04016e393 100644
--- a/lib/types/beacon_chain/indexed_attestation.ex
+++ b/lib/types/beacon_chain/indexed_attestation.ex
@@ -23,7 +23,7 @@ defmodule Types.IndexedAttestation do
defstruct fields
@type t :: %__MODULE__{
- # max size is MAX_VALIDATORS_PER_COMMITTEE
+ # [Modified in Electra:EIP7549]
attesting_indices: list(Types.validator_index()),
data: Types.AttestationData.t(),
signature: Types.bls_signature()
@@ -33,7 +33,8 @@ defmodule Types.IndexedAttestation do
def schema() do
[
{:attesting_indices,
- {:list, TypeAliases.validator_index(), ChainSpec.get("MAX_VALIDATORS_PER_COMMITTEE")}},
+ {:list, TypeAliases.validator_index(),
+ ChainSpec.get("MAX_VALIDATORS_PER_COMMITTEE") * ChainSpec.get("MAX_COMMITTEES_PER_SLOT")}},
{:data, Types.AttestationData},
{:signature, TypeAliases.bls_signature()}
]
diff --git a/lib/types/beacon_chain/pending_consolidation.ex b/lib/types/beacon_chain/pending_consolidation.ex
new file mode 100644
index 000000000..2258a60c3
--- /dev/null
+++ b/lib/types/beacon_chain/pending_consolidation.ex
@@ -0,0 +1,25 @@
+defmodule Types.PendingConsolidation do
+ @moduledoc """
+ Struct definition for `PendingConsolidation`.
+ Added in Electra fork (EIP7251).
+ """
+
+ use LambdaEthereumConsensus.Container
+
+ fields = [:source_index, :target_index]
+ @enforce_keys fields
+ defstruct fields
+
+ @type t :: %__MODULE__{
+ source_index: Types.validator_index(),
+ target_index: Types.validator_index()
+ }
+
+ @impl LambdaEthereumConsensus.Container
+ def schema() do
+ [
+ {:source_index, TypeAliases.validator_index()},
+ {:target_index, TypeAliases.validator_index()}
+ ]
+ end
+end
diff --git a/lib/types/beacon_chain/pending_deposit.ex b/lib/types/beacon_chain/pending_deposit.ex
new file mode 100644
index 000000000..6c7a8d885
--- /dev/null
+++ b/lib/types/beacon_chain/pending_deposit.ex
@@ -0,0 +1,31 @@
+defmodule Types.PendingDeposit do
+ @moduledoc """
+ Struct definition for `PendingDeposit`.
+ Added in Electra fork (EIP7251).
+ """
+
+ use LambdaEthereumConsensus.Container
+
+ fields = [:pubkey, :withdrawal_credentials, :amount, :signature, :slot]
+ @enforce_keys fields
+ defstruct fields
+
+ @type t :: %__MODULE__{
+ pubkey: Types.bls_pubkey(),
+ withdrawal_credentials: Types.bytes32(),
+ amount: Types.gwei(),
+ signature: Types.bls_signature(),
+ slot: Types.slot()
+ }
+
+ @impl LambdaEthereumConsensus.Container
+ def schema() do
+ [
+ {:pubkey, TypeAliases.bls_pubkey()},
+ {:withdrawal_credentials, TypeAliases.bytes32()},
+ {:amount, TypeAliases.gwei()},
+ {:signature, TypeAliases.bls_signature()},
+ {:slot, TypeAliases.slot()}
+ ]
+ end
+end
diff --git a/lib/types/beacon_chain/pending_partial_withdrawal.ex b/lib/types/beacon_chain/pending_partial_withdrawal.ex
new file mode 100644
index 000000000..0647b2d72
--- /dev/null
+++ b/lib/types/beacon_chain/pending_partial_withdrawal.ex
@@ -0,0 +1,27 @@
+defmodule Types.PendingPartialWithdrawal do
+ @moduledoc """
+ Struct definition for `PendingPartialWithdrawal`.
+ Added in Electra fork (EIP7251).
+ """
+
+ use LambdaEthereumConsensus.Container
+
+ fields = [:validator_index, :amount, :withdrawable_epoch]
+ @enforce_keys fields
+ defstruct fields
+
+ @type t :: %__MODULE__{
+ validator_index: Types.validator_index(),
+ amount: Types.gwei(),
+ withdrawable_epoch: Types.epoch()
+ }
+
+ @impl LambdaEthereumConsensus.Container
+ def schema() do
+ [
+ {:validator_index, TypeAliases.validator_index()},
+ {:amount, TypeAliases.gwei()},
+ {:withdrawable_epoch, TypeAliases.epoch()}
+ ]
+ end
+end
diff --git a/lib/types/beacon_chain/single_attestation.ex b/lib/types/beacon_chain/single_attestation.ex
new file mode 100644
index 000000000..5d15619d3
--- /dev/null
+++ b/lib/types/beacon_chain/single_attestation.ex
@@ -0,0 +1,35 @@
+defmodule Types.SingleAttestation do
+ @moduledoc """
+ Struct definition for `SingleAttestation`. Added in Electra.
+ Related definitions in `native/ssz_nif/src/types/`.
+ """
+
+ use LambdaEthereumConsensus.Container
+
+ fields = [
+ :committee_index,
+ :attester_index,
+ :data,
+ :signature
+ ]
+
+ @enforce_keys fields
+ defstruct fields
+
+ @type t :: %__MODULE__{
+ committee_index: Types.commitee_index(),
+ attester_index: Types.validator_index(),
+ data: Types.AttestationData.t(),
+ signature: Types.bls_signature()
+ }
+
+ @impl LambdaEthereumConsensus.Container
+ def schema() do
+ [
+ {:committee_index, TypeAliases.commitee_index()},
+ {:attester_index, TypeAliases.validator_index()},
+ {:data, Types.AttestationData},
+ {:signature, TypeAliases.bls_signature()}
+ ]
+ end
+end
diff --git a/lib/types/beacon_chain/validator.ex b/lib/types/beacon_chain/validator.ex
index 96e536020..f477c572e 100644
--- a/lib/types/beacon_chain/validator.ex
+++ b/lib/types/beacon_chain/validator.ex
@@ -5,8 +5,6 @@ defmodule Types.Validator do
"""
use LambdaEthereumConsensus.Container
- @eth1_address_withdrawal_prefix <<0x01>>
-
fields = [
:pubkey,
:withdrawal_credentials,
@@ -38,7 +36,7 @@ defmodule Types.Validator do
@spec has_eth1_withdrawal_credential(t()) :: boolean
def has_eth1_withdrawal_credential(%{withdrawal_credentials: withdrawal_credentials}) do
<> = withdrawal_credentials
- first_byte_of_withdrawal_credentials == @eth1_address_withdrawal_prefix
+ first_byte_of_withdrawal_credentials == Constants.eth1_address_withdrawal_prefix()
end
@doc """
@@ -51,7 +49,7 @@ defmodule Types.Validator do
balance,
epoch
) do
- has_eth1_withdrawal_credential(validator) && withdrawable_epoch <= epoch && balance > 0
+ has_execution_withdrawal_credential(validator) && withdrawable_epoch <= epoch && balance > 0
end
@doc """
@@ -62,10 +60,12 @@ defmodule Types.Validator do
%{effective_balance: effective_balance} = validator,
balance
) do
- max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE")
+ max_effective_balance = get_max_effective_balance(validator)
has_max_effective_balance = effective_balance == max_effective_balance
has_excess_balance = balance > max_effective_balance
- has_eth1_withdrawal_credential(validator) && has_max_effective_balance && has_excess_balance
+
+ has_execution_withdrawal_credential(validator) && has_max_effective_balance &&
+ has_excess_balance
end
@impl LambdaEthereumConsensus.Container
@@ -81,4 +81,38 @@ defmodule Types.Validator do
{:withdrawable_epoch, TypeAliases.epoch()}
]
end
+
+ @spec compounding_withdrawal_credential?(Types.bytes32()) :: boolean()
+ def compounding_withdrawal_credential?(withdrawal_credentials) do
+ <> = withdrawal_credentials
+ first_byte == Constants.compounding_withdrawal_prefix()
+ end
+
+ @doc """
+ Check if ``validator`` has an 0x02 prefixed "compounding" withdrawal credential.
+ """
+ @spec has_compounding_withdrawal_credential(t()) :: boolean()
+ def has_compounding_withdrawal_credential(validator) do
+ compounding_withdrawal_credential?(validator.withdrawal_credentials)
+ end
+
+ @doc """
+ Check if ``validator`` has a 0x01 or 0x02 prefixed withdrawal credential.
+ """
+ @spec has_execution_withdrawal_credential(t()) :: boolean()
+ def has_execution_withdrawal_credential(validator) do
+ has_compounding_withdrawal_credential(validator) || has_eth1_withdrawal_credential(validator)
+ end
+
+ @doc """
+ Get max effective balance for ``validator``.
+ """
+ @spec get_max_effective_balance(t()) :: Types.gwei()
+ def get_max_effective_balance(validator) do
+ if has_compounding_withdrawal_credential(validator) do
+ ChainSpec.get("MAX_EFFECTIVE_BALANCE_ELECTRA")
+ else
+ ChainSpec.get("MIN_ACTIVATION_BALANCE")
+ end
+ end
end
diff --git a/lib/types/beacon_chain/withdrawal_request.ex b/lib/types/beacon_chain/withdrawal_request.ex
new file mode 100644
index 000000000..ac9b2bae8
--- /dev/null
+++ b/lib/types/beacon_chain/withdrawal_request.ex
@@ -0,0 +1,27 @@
+defmodule Types.WithdrawalRequest do
+ @moduledoc """
+ Struct definition for `WithdrawalRequest`.
+ Added in Electra fork (EIP7251:EIP7002).
+ """
+
+ use LambdaEthereumConsensus.Container
+
+ fields = [:source_address, :validator_pubkey, :amount]
+ @enforce_keys fields
+ defstruct fields
+
+ @type t :: %__MODULE__{
+ source_address: Types.execution_address(),
+ validator_pubkey: Types.bls_pubkey(),
+ amount: Types.gwei()
+ }
+
+ @impl LambdaEthereumConsensus.Container
+ def schema() do
+ [
+ {:source_address, TypeAliases.execution_address()},
+ {:validator_pubkey, TypeAliases.bls_pubkey()},
+ {:amount, TypeAliases.gwei()}
+ ]
+ end
+end
diff --git a/native/ssz_nif/Cargo.lock b/native/ssz_nif/Cargo.lock
index ed31578be..82696c4c2 100644
--- a/native/ssz_nif/Cargo.lock
+++ b/native/ssz_nif/Cargo.lock
@@ -11,12 +11,223 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "alloy-primitives"
+version = "0.8.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eacedba97e65cdc7ab592f2b22ef5d3ab8d60b2056bc3a6e6363577e8270ec6f"
+dependencies = [
+ "alloy-rlp",
+ "bytes",
+ "cfg-if",
+ "const-hex",
+ "derive_more",
+ "foldhash",
+ "hashbrown",
+ "indexmap",
+ "itoa",
+ "k256",
+ "keccak-asm",
+ "paste",
+ "proptest",
+ "rand",
+ "ruint",
+ "rustc-hash",
+ "serde",
+ "sha3",
+ "tiny-keccak",
+]
+
+[[package]]
+name = "alloy-rlp"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6c1d995bff8d011f7cd6c81820d51825e6e06d6db73914c1630ecf544d83d6"
+dependencies = [
+ "arrayvec",
+ "bytes",
+]
+
+[[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",
+ "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",
+ "num-traits",
+ "paste",
+ "rustc_version 0.4.1",
+ "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-macros"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20"
+dependencies = [
+ "num-bigint",
+ "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",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[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",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c"
+dependencies = [
+ "num-traits",
+ "rand",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185"
+dependencies = [
+ "num-traits",
+ "rand",
+]
+
[[package]]
name = "arrayvec"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
+[[package]]
+name = "auto_impl"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e12882f59de5360c748c4cbf569a042d5fb0eb515f7bea9c1f470b47f6ffbd73"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+
+[[package]]
+name = "base16ct"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
+
+[[package]]
+name = "base64ct"
+version = "1.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3"
+
+[[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 = "bitflags"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
+
[[package]]
name = "bitvec"
version = "1.0.1"
@@ -38,12 +249,6 @@ dependencies = [
"generic-array",
]
-[[package]]
-name = "bumpalo"
-version = "3.15.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
-
[[package]]
name = "byte-slice-cast"
version = "1.2.2"
@@ -61,12 +266,18 @@ name = "bytes"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
+dependencies = [
+ "serde",
+]
[[package]]
name = "cc"
-version = "1.0.90"
+version = "1.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
+checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
+dependencies = [
+ "shlex",
+]
[[package]]
name = "cfg-if"
@@ -74,6 +285,25 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+[[package]]
+name = "const-hex"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "hex",
+ "proptest",
+ "serde",
+]
+
+[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
[[package]]
name = "cpufeatures"
version = "0.2.12"
@@ -89,6 +319,18 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+[[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",
+ "subtle",
+ "zeroize",
+]
+
[[package]]
name = "crypto-common"
version = "0.1.6"
@@ -101,9 +343,9 @@ dependencies = [
[[package]]
name = "darling"
-version = "0.13.4"
+version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
+checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
dependencies = [
"darling_core",
"darling_macro",
@@ -111,27 +353,37 @@ dependencies = [
[[package]]
name = "darling_core"
-version = "0.13.4"
+version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
+checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
- "syn 1.0.109",
+ "syn 2.0.100",
]
[[package]]
name = "darling_macro"
-version = "0.13.4"
+version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
+checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
- "syn 1.0.109",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "der"
+version = "0.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
+dependencies = [
+ "const-oid",
+ "zeroize",
]
[[package]]
@@ -145,6 +397,36 @@ dependencies = [
"syn 1.0.109",
]
+[[package]]
+name = "derive_more"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+ "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"
@@ -152,7 +434,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
+ "const-oid",
"crypto-common",
+ "subtle",
+]
+
+[[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",
+ "signature",
+ "spki",
]
[[package]]
@@ -162,57 +460,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]]
-name = "equivalent"
-version = "1.0.1"
+name = "elliptic-curve"
+version = "0.13.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
+dependencies = [
+ "base16ct",
+ "crypto-bigint",
+ "digest 0.10.7",
+ "ff",
+ "generic-array",
+ "group",
+ "pkcs8",
+ "rand_core",
+ "sec1",
+ "subtle",
+ "zeroize",
+]
[[package]]
-name = "ethbloom"
-version = "0.13.0"
+name = "equivalent"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60"
-dependencies = [
- "crunchy",
- "fixed-hash",
- "impl-rlp",
- "impl-serde",
- "tiny-keccak",
-]
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
-name = "ethereum-types"
-version = "0.14.1"
+name = "errno"
+version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee"
+checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
- "ethbloom",
- "fixed-hash",
- "impl-rlp",
- "impl-serde",
- "primitive-types",
- "uint",
+ "libc",
+ "windows-sys 0.59.0",
]
[[package]]
name = "ethereum_hashing"
-version = "1.0.0-beta.2"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "233dc6f434ce680dbabf4451ee3380cec46cb3c45d66660445a435619710dd35"
+checksum = "c853bd72c9e5787f8aafc3df2907c2ed03cff3150c3acd94e2e53a98ab70a8ab"
dependencies = [
"cpufeatures",
- "lazy_static",
"ring",
"sha2",
]
[[package]]
name = "ethereum_serde_utils"
-version = "0.5.2"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de4d5951468846963c24e8744c133d44f39dff2cd3a233f6be22b370d08a524f"
+checksum = "70cbccfccf81d67bff0ab36e591fa536c8a935b078a7b0e58c1d00d418332fc9"
dependencies = [
- "ethereum-types",
+ "alloy-primitives",
"hex",
"serde",
"serde_derive",
@@ -221,25 +520,67 @@ dependencies = [
[[package]]
name = "ethereum_ssz"
-version = "0.5.3"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e61ffea29f26e8249d35128a82ec8d3bd4fbc80179ea5f5e5e3daafef6a80fcb"
+checksum = "86da3096d1304f5f28476ce383005385459afeaf0eea08592b65ddbc9b258d16"
dependencies = [
- "ethereum-types",
- "itertools",
+ "alloy-primitives",
+ "ethereum_serde_utils",
+ "itertools 0.13.0",
+ "serde",
+ "serde_derive",
"smallvec",
+ "typenum",
]
[[package]]
name = "ethereum_ssz_derive"
-version = "0.5.3"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6085d7fd3cf84bd2b8fec150d54c8467fb491d8db9c460607c5534f653a0ee38"
+checksum = "d832a5c38eba0e7ad92592f7a22d693954637fbb332b4f669590d66a5c3183e5"
dependencies = [
"darling",
"proc-macro2",
"quote",
- "syn 1.0.109",
+ "syn 2.0.100",
+]
+
+[[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.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
+dependencies = [
+ "rand_core",
+ "subtle",
]
[[package]]
@@ -260,6 +601,12 @@ 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 = "funty"
version = "2.0.0"
@@ -274,6 +621,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
+ "zeroize",
]
[[package]]
@@ -284,14 +632,41 @@ checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
dependencies = [
"cfg-if",
"libc",
- "wasi",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasi 0.14.2+wasi-0.2.4",
+]
+
+[[package]]
+name = "group"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
+dependencies = [
+ "ff",
+ "rand_core",
+ "subtle",
]
[[package]]
name = "hashbrown"
-version = "0.14.3"
+version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
+dependencies = [
+ "foldhash",
+ "serde",
+]
[[package]]
name = "heck"
@@ -304,6 +679,18 @@ name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+dependencies = [
+ "serde",
+]
+
+[[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 = "ident_case"
@@ -320,24 +707,6 @@ dependencies = [
"parity-scale-codec",
]
-[[package]]
-name = "impl-rlp"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808"
-dependencies = [
- "rlp",
-]
-
-[[package]]
-name = "impl-serde"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd"
-dependencies = [
- "serde",
-]
-
[[package]]
name = "impl-trait-for-tuples"
version = "0.2.2"
@@ -351,19 +720,29 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "2.2.6"
+version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
+checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
dependencies = [
"equivalent",
"hashbrown",
+ "serde",
+]
+
+[[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.10.5"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
@@ -375,12 +754,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
-name = "js-sys"
-version = "0.3.69"
+name = "k256"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
+dependencies = [
+ "cfg-if",
+ "ecdsa",
+ "elliptic-curve",
+ "once_cell",
+ "sha2",
+]
+
+[[package]]
+name = "keccak"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654"
+dependencies = [
+ "cpufeatures",
+]
+
+[[package]]
+name = "keccak-asm"
+version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6"
dependencies = [
- "wasm-bindgen",
+ "digest 0.10.7",
+ "sha3-asm",
]
[[package]]
@@ -391,15 +793,21 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
-version = "0.2.153"
+version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
-name = "log"
-version = "0.4.21"
+name = "libm"
+version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
[[package]]
name = "memchr"
@@ -407,6 +815,35 @@ version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+[[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-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+ "libm",
+]
+
[[package]]
name = "once_cell"
version = "1.19.0"
@@ -439,6 +876,33 @@ dependencies = [
"syn 1.0.109",
]
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "pest"
+version = "2.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc"
+dependencies = [
+ "memchr",
+ "thiserror",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pkcs8"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der",
+ "spki",
+]
+
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@@ -453,8 +917,6 @@ checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2"
dependencies = [
"fixed-hash",
"impl-codec",
- "impl-rlp",
- "impl-serde",
"uint",
]
@@ -470,13 +932,39 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.79"
+version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
+checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
+[[package]]
+name = "proptest"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50"
+dependencies = [
+ "bit-set",
+ "bit-vec",
+ "bitflags",
+ "lazy_static",
+ "num-traits",
+ "rand",
+ "rand_chacha",
+ "rand_xorshift",
+ "regex-syntax",
+ "rusty-fork",
+ "tempfile",
+ "unarray",
+]
+
+[[package]]
+name = "quick-error"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+
[[package]]
name = "quote"
version = "1.0.35"
@@ -486,6 +974,12 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "r-efi"
+version = "5.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
+
[[package]]
name = "radium"
version = "0.7.0"
@@ -501,6 +995,7 @@ dependencies = [
"libc",
"rand_chacha",
"rand_core",
+ "serde",
]
[[package]]
@@ -519,7 +1014,16 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
- "getrandom",
+ "getrandom 0.2.12",
+]
+
+[[package]]
+name = "rand_xorshift"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
+dependencies = [
+ "rand_core",
]
[[package]]
@@ -551,19 +1055,28 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+[[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.16.20"
+version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
+ "cfg-if",
+ "getrandom 0.2.12",
"libc",
- "once_cell",
- "spin",
"untrusted",
- "web-sys",
- "winapi",
+ "windows-sys 0.52.0",
]
[[package]]
@@ -576,12 +1089,81 @@ dependencies = [
"rustc-hex",
]
+[[package]]
+name = "ruint"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "825df406ec217a8116bd7b06897c6cc8f65ffefc15d030ae2c9540acc9ed50b6"
+dependencies = [
+ "alloy-rlp",
+ "ark-ff 0.3.0",
+ "ark-ff 0.4.2",
+ "bytes",
+ "fastrlp 0.3.1",
+ "fastrlp 0.4.0",
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+ "parity-scale-codec",
+ "primitive-types",
+ "proptest",
+ "rand",
+ "rlp",
+ "ruint-macro",
+ "serde",
+ "valuable",
+ "zeroize",
+]
+
+[[package]]
+name = "ruint-macro"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18"
+
+[[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.26",
+]
+
+[[package]]
+name = "rustix"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.59.0",
+]
+
[[package]]
name = "rustler"
version = "0.32.1"
@@ -602,7 +1184,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn 2.0.55",
+ "syn 2.0.100",
]
[[package]]
@@ -615,12 +1197,62 @@ dependencies = [
"unreachable",
]
+[[package]]
+name = "rusty-fork"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f"
+dependencies = [
+ "fnv",
+ "quick-error",
+ "tempfile",
+ "wait-timeout",
+]
+
[[package]]
name = "ryu"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
+[[package]]
+name = "sec1"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
+dependencies = [
+ "base16ct",
+ "der",
+ "generic-array",
+ "pkcs8",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "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.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
+
+[[package]]
+name = "semver-parser"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2"
+dependencies = [
+ "pest",
+]
+
[[package]]
name = "serde"
version = "1.0.197"
@@ -638,7 +1270,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.55",
+ "syn 2.0.100",
]
[[package]]
@@ -660,7 +1292,43 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
- "digest",
+ "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.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46"
+dependencies = [
+ "cc",
+ "cfg-if",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[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",
]
[[package]]
@@ -670,10 +1338,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
-name = "spin"
-version = "0.5.2"
+name = "spki"
+version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+dependencies = [
+ "base64ct",
+ "der",
+]
[[package]]
name = "ssz_nif"
@@ -689,14 +1361,13 @@ dependencies = [
[[package]]
name = "ssz_types"
-version = "0.5.4"
+version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "382939886cb24ee8ac885d09116a60f6262d827c7a9e36012b4f6d3d0116d0b3"
+checksum = "dad0fa7e9a85c06d0a6ba5100d733fff72e231eb6db2d86078225cf716fd2d95"
dependencies = [
- "derivative",
"ethereum_serde_utils",
"ethereum_ssz",
- "itertools",
+ "itertools 0.13.0",
"serde",
"serde_derive",
"smallvec",
@@ -712,9 +1383,15 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
-version = "0.10.0"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
@@ -729,9 +1406,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.55"
+version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0"
+checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
@@ -744,6 +1421,39 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
+[[package]]
+name = "tempfile"
+version = "3.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600"
+dependencies = [
+ "fastrand",
+ "getrandom 0.3.2",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
[[package]]
name = "tiny-keccak"
version = "2.0.2"
@@ -772,24 +1482,27 @@ dependencies = [
[[package]]
name = "tree_hash"
-version = "0.5.2"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c998ac5fe2b07c025444bdd522e6258110b63861c6698eedc610c071980238d"
+checksum = "6c58eb0f518840670270d90d97ffee702d8662d9c5494870c9e1e9e0fa00f668"
dependencies = [
- "ethereum-types",
+ "alloy-primitives",
"ethereum_hashing",
+ "ethereum_ssz",
"smallvec",
+ "typenum",
]
[[package]]
name = "tree_hash_derive"
-version = "0.5.2"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84303a9c7cda5f085a3ed9cd241d1e95e04d88aab1d679b02f212e653537ba86"
+checksum = "699e7fb6b3fdfe0c809916f251cf5132d64966858601695c3736630a87e7166a"
dependencies = [
"darling",
+ "proc-macro2",
"quote",
- "syn 1.0.109",
+ "syn 2.0.100",
]
[[package]]
@@ -798,6 +1511,12 @@ version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+[[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"
@@ -810,12 +1529,24 @@ dependencies = [
"static_assertions",
]
+[[package]]
+name = "unarray"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
+
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+[[package]]
+name = "unicode-xid"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+
[[package]]
name = "unreachable"
version = "1.0.0"
@@ -827,9 +1558,15 @@ dependencies = [
[[package]]
name = "untrusted"
-version = "0.7.1"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "valuable"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "version_check"
@@ -843,6 +1580,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
+[[package]]
+name = "wait-timeout"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@@ -850,90 +1596,95 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
-name = "wasm-bindgen"
-version = "0.2.92"
+name = "wasi"
+version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
- "cfg-if",
- "wasm-bindgen-macro",
+ "wit-bindgen-rt",
]
[[package]]
-name = "wasm-bindgen-backend"
-version = "0.2.92"
+name = "windows-sys"
+version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
- "bumpalo",
- "log",
- "once_cell",
- "proc-macro2",
- "quote",
- "syn 2.0.55",
- "wasm-bindgen-shared",
+ "windows-targets",
]
[[package]]
-name = "wasm-bindgen-macro"
-version = "0.2.92"
+name = "windows-sys"
+version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
- "quote",
- "wasm-bindgen-macro-support",
+ "windows-targets",
]
[[package]]
-name = "wasm-bindgen-macro-support"
-version = "0.2.92"
+name = "windows-targets"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.55",
- "wasm-bindgen-backend",
- "wasm-bindgen-shared",
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
]
[[package]]
-name = "wasm-bindgen-shared"
-version = "0.2.92"
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
-name = "web-sys"
-version = "0.3.69"
+name = "windows_aarch64_msvc"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
-dependencies = [
- "js-sys",
- "wasm-bindgen",
-]
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
-name = "winapi"
-version = "0.3.9"
+name = "windows_i686_gnu"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
-dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
-]
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
+name = "windows_i686_gnullvm"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[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_gnullvm"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
@@ -944,6 +1695,15 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "wit-bindgen-rt"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
+dependencies = [
+ "bitflags",
+]
+
[[package]]
name = "wyz"
version = "0.5.1"
@@ -952,3 +1712,23 @@ checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]
+
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
diff --git a/native/ssz_nif/Cargo.toml b/native/ssz_nif/Cargo.toml
index d322084fa..afd7bd560 100644
--- a/native/ssz_nif/Cargo.toml
+++ b/native/ssz_nif/Cargo.toml
@@ -11,8 +11,8 @@ crate-type = ["cdylib"]
[dependencies]
rustler = "0.32.1"
-ethereum_ssz_derive = "0.5.0"
-ethereum_ssz = "0.5.0"
-ssz_types = "0.5.4"
-tree_hash = "0.5.2"
-tree_hash_derive = "0.5.2"
+ethereum_ssz_derive = "0.8.3"
+ethereum_ssz = "0.8.3"
+ssz_types = "0.10.1"
+tree_hash = "0.9.1"
+tree_hash_derive = "0.9.1"
diff --git a/native/ssz_nif/src/elx_types/beacon_chain.rs b/native/ssz_nif/src/elx_types/beacon_chain.rs
index cb49fb913..695c3b884 100644
--- a/native/ssz_nif/src/elx_types/beacon_chain.rs
+++ b/native/ssz_nif/src/elx_types/beacon_chain.rs
@@ -152,6 +152,7 @@ gen_struct_with_config!(
aggregation_bits: Binary<'a>,
data: AttestationData<'a>,
signature: BLSSignature<'a>,
+ committee_bits: Binary<'a>,
}
);
@@ -329,6 +330,90 @@ gen_struct_with_config!(
}
);
+gen_struct!(
+ #[derive(NifStruct)]
+ #[module = "Types.PendingDeposit"]
+ pub(crate) struct PendingDeposit<'a> {
+ pubkey: BLSPubkey<'a>,
+ withdrawal_credentials: Bytes32<'a>,
+ amount: Gwei,
+ signature: BLSSignature<'a>,
+ slot: Slot,
+ }
+);
+
+gen_struct!(
+ #[derive(NifStruct)]
+ #[module = "Types.PendingPartialWithdrawal"]
+ pub(crate) struct PendingPartialWithdrawal {
+ validator_index: ValidatorIndex,
+ amount: Gwei,
+ withdrawable_epoch: Epoch,
+ }
+);
+
+gen_struct!(
+ #[derive(NifStruct)]
+ #[module = "Types.PendingConsolidation"]
+ pub(crate) struct PendingConsolidation {
+ source_index: ValidatorIndex,
+ target_index: ValidatorIndex,
+ }
+);
+
+gen_struct!(
+ #[derive(NifStruct)]
+ #[module = "Types.DepositRequest"]
+ pub(crate) struct DepositRequest<'a> {
+ pubkey: BLSPubkey<'a>,
+ withdrawal_credentials: Bytes32<'a>,
+ amount: Gwei,
+ signature: BLSSignature<'a>,
+ index: u64,
+ }
+);
+
+gen_struct!(
+ #[derive(NifStruct)]
+ #[module = "Types.WithdrawalRequest"]
+ pub(crate) struct WithdrawalRequest<'a> {
+ source_address: ExecutionAddress<'a>,
+ validator_pubkey: BLSPubkey<'a>,
+ amount: Gwei,
+ }
+);
+
+gen_struct!(
+ #[derive(NifStruct)]
+ #[module = "Types.ConsolidationRequest"]
+ pub(crate) struct ConsolidationRequest<'a> {
+ source_address: ExecutionAddress<'a>,
+ source_pubkey: BLSPubkey<'a>,
+ target_pubkey: BLSPubkey<'a>,
+ }
+);
+
+gen_struct_with_config!(
+ #[derive(NifStruct)]
+ #[module = "Types.ExecutionRequests"]
+ pub(crate) struct ExecutionRequests<'a> {
+ deposits: Vec>,
+ withdrawals: Vec>,
+ consolidations: Vec>,
+ }
+);
+
+gen_struct!(
+ #[derive(NifStruct)]
+ #[module = "Types.SingleAttestation"]
+ pub(crate) struct SingleAttestation<'a> {
+ committee_index: CommitteeIndex,
+ attester_index: ValidatorIndex,
+ data: AttestationData<'a>,
+ signature: BLSSignature<'a>,
+ }
+);
+
gen_struct_with_config!(
#[derive(NifStruct)]
#[module = "Types.BeaconState"]
@@ -374,6 +459,16 @@ gen_struct_with_config!(
next_withdrawal_validator_index: ValidatorIndex, // [New in Capella]
// Deep history valid from Capella onwards
historical_summaries: Vec>, // [New in Capella]
+ // Electra fields
+ deposit_requests_start_index: u64, // [New in Electra:EIP6110]
+ deposit_balance_to_consume: Gwei, // [New in Electra:EIP7251]
+ exit_balance_to_consume: Gwei, // [New in Electra:EIP7251]
+ earliest_exit_epoch: Epoch, // [New in Electra:EIP7251]
+ consolidation_balance_to_consume: Gwei, // [New in Electra:EIP7251]
+ earliest_consolidation_epoch: Epoch, // [New in Electra:EIP7251]
+ pending_deposits: Vec>, // [New in Electra:EIP7251]
+ pending_partial_withdrawals: Vec, // [New in Electra:EIP7251]
+ pending_consolidations: Vec, // [New in Electra:EIP7251]
}
);
@@ -393,5 +488,6 @@ gen_struct_with_config!(
execution_payload: ExecutionPayload<'a>,
bls_to_execution_changes: Vec>,
blob_kzg_commitments: Vec>,
+ execution_requests: ExecutionRequests<'a>, // [New in Electra]
}
);
diff --git a/native/ssz_nif/src/ssz_types/beacon_chain.rs b/native/ssz_nif/src/ssz_types/beacon_chain.rs
index eaecfb695..7786b68d4 100644
--- a/native/ssz_nif/src/ssz_types/beacon_chain.rs
+++ b/native/ssz_nif/src/ssz_types/beacon_chain.rs
@@ -46,7 +46,7 @@ pub(crate) struct AttestationData {
#[derive(Encode, Decode, TreeHash)]
pub(crate) struct IndexedAttestation {
- pub(crate) attesting_indices: VariableList,
+ pub(crate) attesting_indices: VariableList,
pub(crate) data: AttestationData,
pub(crate) signature: BLSSignature,
}
@@ -107,9 +107,10 @@ pub(crate) struct VoluntaryExit {
#[derive(Encode, Decode, TreeHash)]
pub(crate) struct Attestation {
- pub(crate) aggregation_bits: BitList,
+ pub(crate) aggregation_bits: BitList,
pub(crate) data: AttestationData,
pub(crate) signature: BLSSignature,
+ pub(crate) committee_bits: BitVector,
}
#[derive(Encode, Decode, TreeHash)]
@@ -241,14 +242,85 @@ pub(crate) struct SyncCommittee {
pub(crate) aggregate_pubkey: BLSPubkey,
}
+// New Electra container types
+
+// For EIP7251
+#[derive(Encode, Decode, TreeHash)]
+pub(crate) struct PendingDeposit {
+ pub(crate) pubkey: BLSPubkey,
+ pub(crate) withdrawal_credentials: Bytes32,
+ pub(crate) amount: Gwei,
+ pub(crate) signature: BLSSignature,
+ pub(crate) slot: Slot,
+}
+
+// For EIP7251
+#[derive(Encode, Decode, TreeHash)]
+pub(crate) struct PendingPartialWithdrawal {
+ pub(crate) validator_index: ValidatorIndex,
+ pub(crate) amount: Gwei,
+ pub(crate) withdrawable_epoch: Epoch,
+}
+
+// For EIP7251
+#[derive(Encode, Decode, TreeHash)]
+pub(crate) struct PendingConsolidation {
+ pub(crate) source_index: ValidatorIndex,
+ pub(crate) target_index: ValidatorIndex,
+}
+
+// For EIP6110
+#[derive(Encode, Decode, TreeHash)]
+pub(crate) struct DepositRequest {
+ pub(crate) pubkey: BLSPubkey,
+ pub(crate) withdrawal_credentials: Bytes32,
+ pub(crate) amount: Gwei,
+ pub(crate) signature: BLSSignature,
+ pub(crate) index: u64,
+}
+
+// For EIP7251:EIP7002
+#[derive(Encode, Decode, TreeHash)]
+pub(crate) struct WithdrawalRequest {
+ pub(crate) source_address: ExecutionAddress,
+ pub(crate) validator_pubkey: BLSPubkey,
+ pub(crate) amount: Gwei,
+}
+
+// For EIP7251
+#[derive(Encode, Decode, TreeHash)]
+pub(crate) struct ConsolidationRequest {
+ pub(crate) source_address: ExecutionAddress,
+ pub(crate) source_pubkey: BLSPubkey,
+ pub(crate) target_pubkey: BLSPubkey,
+}
+
+// For Electra
+#[derive(Encode, Decode, TreeHash)]
+pub(crate) struct ExecutionRequests {
+ pub(crate) deposits: VariableList,
+ pub(crate) withdrawals: VariableList,
+ pub(crate) consolidations:
+ VariableList,
+}
+// For Electra
+#[derive(Encode, Decode, TreeHash)]
+pub(crate) struct SingleAttestation {
+ pub(crate) committee_index: CommitteeIndex,
+ pub(crate) attester_index: ValidatorIndex,
+ pub(crate) data: AttestationData,
+ pub(crate) signature: BLSSignature,
+}
+
#[derive(Encode, Decode, TreeHash)]
pub(crate) struct BeaconBlockBody {
pub(crate) randao_reveal: BLSSignature,
pub(crate) eth1_data: Eth1Data,
pub(crate) graffiti: Bytes32,
pub(crate) proposer_slashings: VariableList,
- pub(crate) attester_slashings: VariableList, C::MaxAttesterSlashings>,
- pub(crate) attestations: VariableList, C::MaxAttestations>,
+ pub(crate) attester_slashings:
+ VariableList, C::MaxAttesterSlashingsElectra>,
+ pub(crate) attestations: VariableList, C::MaxAttestationsElectra>,
pub(crate) deposits: VariableList,
pub(crate) voluntary_exits: VariableList,
pub(crate) sync_aggregate: SyncAggregate,
@@ -256,6 +328,7 @@ pub(crate) struct BeaconBlockBody {
pub(crate) bls_to_execution_changes:
VariableList,
pub(crate) blob_kzg_commitments: VariableList,
+ pub(crate) execution_requests: ExecutionRequests,
}
#[derive(Encode, Decode, TreeHash)]
@@ -303,4 +376,16 @@ pub(crate) struct BeaconState {
pub(crate) next_withdrawal_validator_index: ValidatorIndex, // [New in Capella]
// Deep history valid from Capella onwards
pub(crate) historical_summaries: VariableList, // [New in Capella]
+ // Electra fields
+ pub(crate) deposit_requests_start_index: u64, // [New in Electra:EIP6110]
+ pub(crate) deposit_balance_to_consume: Gwei, // [New in Electra:EIP7251]
+ pub(crate) exit_balance_to_consume: Gwei, // [New in Electra:EIP7251]
+ pub(crate) earliest_exit_epoch: Epoch, // [New in Electra:EIP7251]
+ pub(crate) consolidation_balance_to_consume: Gwei, // [New in Electra:EIP7251]
+ pub(crate) earliest_consolidation_epoch: Epoch, // [New in Electra:EIP7251]
+ pub(crate) pending_deposits: VariableList, // [New in Electra:EIP7251]
+ pub(crate) pending_partial_withdrawals:
+ VariableList, // [New in Electra:EIP7251]
+ pub(crate) pending_consolidations:
+ VariableList, // [New in Electra:EIP7251]
}
diff --git a/native/ssz_nif/src/ssz_types/config.rs b/native/ssz_nif/src/ssz_types/config.rs
index 9793a0e81..e3f8728f6 100644
--- a/native/ssz_nif/src/ssz_types/config.rs
+++ b/native/ssz_nif/src/ssz_types/config.rs
@@ -45,6 +45,17 @@ pub(crate) trait Config {
type FieldElementsPerBlob: Unsigned;
type BytesPerFieldElement: Unsigned;
type KzgCommitmentInclusionProofDepth: Unsigned;
+ type MaxCommitteesPerSlot: Unsigned;
+ // Electra added fields
+ type MaxConsolidationRequestsPerPayload: Unsigned;
+ type MaxDepositRequestsPerPayload: Unsigned;
+ type MaxWithdrawalRequestsPerPayload: Unsigned;
+ type PendingDepositsLimit: Unsigned;
+ type PendingPartialWithdrawalsLimit: Unsigned;
+ type PendingConsolidationsLimit: Unsigned;
+ type MaxAttesterSlashingsElectra: Unsigned;
+ type MaxAttestationsElectra: Unsigned;
+ type MaxValidatorsPerSlot: Unsigned;
// Derived constants. Ideally, this would be trait defaults.
type SyncSubcommitteeSize: Unsigned; // SYNC_COMMITTEE_SIZE / SYNC_COMMITTEE_SUBNET_COUNT
@@ -88,6 +99,17 @@ impl Config for Mainnet {
type FieldElementsPerBlob = U4096;
type BytesPerFieldElement = U32;
type KzgCommitmentInclusionProofDepth = U17;
+ type MaxCommitteesPerSlot = U64;
+ // Electra added fields
+ type MaxConsolidationRequestsPerPayload = U2;
+ type MaxDepositRequestsPerPayload = U8192;
+ type MaxWithdrawalRequestsPerPayload = U16;
+ type PendingDepositsLimit = U134217728;
+ type PendingPartialWithdrawalsLimit = U134217728;
+ type PendingConsolidationsLimit = U262144;
+ type MaxAttesterSlashingsElectra = U1;
+ type MaxAttestationsElectra = U8;
+ type MaxValidatorsPerSlot = U131072; // MaxValidatorsPerCommittee * MaxCommitteesPerSlot - 2048 * 64, this as the rest is fixed and we need to be really carefull about any change
// Derived constants. Ideally, this would be trait defaults.
type SyncSubcommitteeSize =
@@ -109,8 +131,15 @@ impl Config for Minimal {
type SyncCommitteeSize = U32;
type MaxWithdrawalsPerPayload = U4;
type FieldElementsPerBlob = U4096;
- type MaxBlobCommitmentsPerBlock = U16;
- type KzgCommitmentInclusionProofDepth = U9;
+ type MaxBlobCommitmentsPerBlock = U32;
+ type KzgCommitmentInclusionProofDepth = U10;
+ type MaxCommitteesPerSlot = U4;
+ // Electra added fields
+ type MaxDepositRequestsPerPayload = U4;
+ type MaxWithdrawalRequestsPerPayload = U2;
+ type PendingPartialWithdrawalsLimit = U64;
+ type PendingConsolidationsLimit = U64;
+ type MaxValidatorsPerSlot = U8192; // MaxValidatorsPerCommittee * MaxCommitteesPerSlot - 2048 * 4, this as the rest is fixed and we need to be really carefull about any change
// Derived constants. Ideally, this would be trait defaults.
type SyncSubcommitteeSize =
@@ -142,7 +171,11 @@ impl Config for Minimal {
MaxExtraDataBytes,
MaxBlsToExecutionChanges,
MaxBlobsPerBlock,
- BytesPerFieldElement
+ BytesPerFieldElement,
+ MaxConsolidationRequestsPerPayload,
+ PendingDepositsLimit,
+ MaxAttesterSlashingsElectra,
+ MaxAttestationsElectra
});
}
@@ -181,6 +214,17 @@ impl Config for Gnosis {
type FieldElementsPerBlob = U4096;
type BytesPerFieldElement = U32;
type KzgCommitmentInclusionProofDepth = U17;
+ type MaxCommitteesPerSlot = U64;
+ // Electra added fields
+ type MaxConsolidationRequestsPerPayload = U2;
+ type MaxDepositRequestsPerPayload = U8192;
+ type MaxWithdrawalRequestsPerPayload = U16;
+ type PendingDepositsLimit = U134217728;
+ type PendingPartialWithdrawalsLimit = U134217728;
+ type PendingConsolidationsLimit = U262144;
+ type MaxAttesterSlashingsElectra = U1;
+ type MaxAttestationsElectra = U8;
+ type MaxValidatorsPerSlot = U131072; // MaxValidatorsPerCommittee * MaxCommitteesPerSlot - 2048 * 64, this as the rest is fixed and we need to be really carefull about any change
// Derived constants. Ideally, this would be trait defaults.
type SyncSubcommitteeSize =
diff --git a/native/ssz_nif/src/utils/helpers.rs b/native/ssz_nif/src/utils/helpers.rs
index 24fd300ae..637c406bc 100644
--- a/native/ssz_nif/src/utils/helpers.rs
+++ b/native/ssz_nif/src/utils/helpers.rs
@@ -122,7 +122,7 @@ where
for item in vec {
hasher
- .write(item.tree_hash_root().as_bytes())
+ .write(item.tree_hash_root().as_slice())
.expect("ssz_types vec should not contain more elements than max");
}
diff --git a/native/ssz_nif/src/utils/mod.rs b/native/ssz_nif/src/utils/mod.rs
index 3adaec86a..5e54bfa4c 100644
--- a/native/ssz_nif/src/utils/mod.rs
+++ b/native/ssz_nif/src/utils/mod.rs
@@ -53,6 +53,14 @@ macro_rules! schema_match {
Epoch,
BlobSidecar,
BlobIdentifier,
+ PendingDeposit,
+ PendingPartialWithdrawal,
+ PendingConsolidation,
+ DepositRequest,
+ WithdrawalRequest,
+ ConsolidationRequest,
+ ExecutionRequests,
+ SingleAttestation,
}
)
};
diff --git a/network_params.yaml b/network_params.yaml
index 416d0c293..d6a60a49b 100644
--- a/network_params.yaml
+++ b/network_params.yaml
@@ -14,9 +14,12 @@ participants:
validator_count: 32
cl_max_mem: 4096
keymanager_enabled: true
-# Uncomment the following lines to run the the network with the minimal preset (which is various times faster)
-# network_params:
-# preset: minimal
+
+network_params:
+ electra_fork_epoch: 0
+# Uncomment the following line to run the the network with the minimal preset (which is various times faster)
+ # preset: minimal
+
additional_services:
- tx_fuzz
- dora
diff --git a/test/fixtures/block.ex b/test/fixtures/block.ex
index 074d54ee5..cc72e99bb 100644
--- a/test/fixtures/block.ex
+++ b/test/fixtures/block.ex
@@ -53,7 +53,8 @@ defmodule Fixtures.Block do
sync_aggregate: sync_aggregate(),
execution_payload: execution_payload(),
bls_to_execution_changes: [],
- blob_kzg_commitments: []
+ blob_kzg_commitments: [],
+ execution_requests: execution_requests()
]
struct!(BeaconBlockBody, fields)
@@ -101,6 +102,15 @@ defmodule Fixtures.Block do
struct!(ExecutionPayload, fields)
end
+ @spec execution_requests :: Types.ExecutionRequests.t()
+ def execution_requests() do
+ %Types.ExecutionRequests{
+ deposits: [],
+ withdrawals: [],
+ consolidations: []
+ }
+ end
+
@spec fork :: Types.Fork.t()
def fork() do
%Types.Fork{
@@ -200,7 +210,17 @@ defmodule Fixtures.Block do
latest_execution_payload_header: execution_payload_header(),
next_withdrawal_index: Random.uint64(),
next_withdrawal_validator_index: Random.uint64(),
- historical_summaries: []
+ historical_summaries: [],
+ # New Electra fields
+ deposit_requests_start_index: Random.uint64(),
+ deposit_balance_to_consume: Random.uint64(),
+ exit_balance_to_consume: Random.uint64(),
+ earliest_exit_epoch: Random.uint64(),
+ consolidation_balance_to_consume: Random.uint64(),
+ earliest_consolidation_epoch: Random.uint64(),
+ pending_deposits: [],
+ pending_partial_withdrawals: [],
+ pending_consolidations: []
}
end
diff --git a/test/fixtures/validator/proposer/beacon_state.ssz_snappy b/test/fixtures/validator/proposer/beacon_state.ssz_snappy
index 0fc4c91bf..1f6f1917a 100644
Binary files a/test/fixtures/validator/proposer/beacon_state.ssz_snappy and b/test/fixtures/validator/proposer/beacon_state.ssz_snappy differ
diff --git a/test/fixtures/validator/proposer/empty_block.ssz_snappy b/test/fixtures/validator/proposer/empty_block.ssz_snappy
deleted file mode 100644
index 4a4bd26d9..000000000
Binary files a/test/fixtures/validator/proposer/empty_block.ssz_snappy and /dev/null differ
diff --git a/test/fixtures/validator/proposer/empty_signed_beacon_block.ssz_snappy b/test/fixtures/validator/proposer/empty_signed_beacon_block.ssz_snappy
new file mode 100644
index 000000000..a99659b03
Binary files /dev/null and b/test/fixtures/validator/proposer/empty_signed_beacon_block.ssz_snappy differ
diff --git a/test/spec/runners/bls.ex b/test/spec/runners/bls.ex
index 935fdc876..ec148603d 100644
--- a/test/spec/runners/bls.ex
+++ b/test/spec/runners/bls.ex
@@ -6,22 +6,6 @@ defmodule BlsTestRunner do
use ExUnit.CaseTemplate
use TestRunner
- # Remove handler from here once you implement the corresponding functions
- @disabled_handlers [
- # "sign",
- # "verify",
- # "aggregate",
- # "fast_aggregate_verify",
- # "aggregate_verify",
- # "eth_aggregate_pubkeys"
- # "eth_fast_aggregate_verify"
- ]
-
- @impl TestRunner
- def skip?(%SpecTestCase{} = testcase) do
- Enum.member?(@disabled_handlers, testcase.handler)
- end
-
@impl TestRunner
def run_test_case(%SpecTestCase{} = testcase) do
case_dir = SpecTestCase.dir(testcase)
diff --git a/test/spec/runners/epoch_processing.ex b/test/spec/runners/epoch_processing.ex
index 84ef4f83f..1e8891d59 100644
--- a/test/spec/runners/epoch_processing.ex
+++ b/test/spec/runners/epoch_processing.ex
@@ -9,22 +9,9 @@ defmodule EpochProcessingTestRunner do
use ExUnit.CaseTemplate
use TestRunner
- # Remove handler from here once you implement the corresponding functions
+ # TODO: We need to make sure this 2 are still needed to be here
@disabled_handlers [
- # "justification_and_finalization",
- # "inactivity_updates",
- # "rewards_and_penalties",
- # "registry_updates",
- # "slashings",
- # "effective_balance_updates",
- # "eth1_data_reset",
- # "slashings_reset",
- # "randao_mixes_reset",
- # "historical_summaries_update",
"participation_record_updates"
-
- # "participation_flag_updates",
- # "sync_committee_updates"
]
@deprecated_handlers [
@@ -36,11 +23,7 @@ defmodule EpochProcessingTestRunner do
Enum.member?(@disabled_handlers ++ @deprecated_handlers, handler)
end
- @impl TestRunner
- def skip?(%SpecTestCase{fork: "deneb"}), do: false
-
- @impl TestRunner
- def skip?(_), do: true
+ def skip?(_), do: false
@impl TestRunner
def run_test_case(%SpecTestCase{} = testcase) do
diff --git a/test/spec/runners/finality.ex b/test/spec/runners/finality.ex
index 99f7a3cc3..e569f0b59 100644
--- a/test/spec/runners/finality.ex
+++ b/test/spec/runners/finality.ex
@@ -6,27 +6,6 @@ defmodule FinalityTestRunner do
use ExUnit.CaseTemplate
use TestRunner
- @disabled_cases [
- # "finality_no_updates_at_genesis",
- # "finality_rule_1",
- # "finality_rule_2",
- # "finality_rule_3",
- # "finality_rule_4"
- ]
-
- @impl TestRunner
- def skip?(%SpecTestCase{fork: "capella", case: testcase}) do
- Enum.member?(@disabled_cases, testcase)
- end
-
- @impl TestRunner
- def skip?(%SpecTestCase{fork: "deneb", case: testcase}) do
- Enum.member?(@disabled_cases, testcase)
- end
-
- @impl TestRunner
- def skip?(_), do: true
-
@impl TestRunner
def run_test_case(testcase) do
Helpers.ProcessBlocks.process_blocks(testcase)
diff --git a/test/spec/runners/fork_choice.ex b/test/spec/runners/fork_choice.ex
index 9a60e38c4..aa7ed969a 100644
--- a/test/spec/runners/fork_choice.ex
+++ b/test/spec/runners/fork_choice.ex
@@ -23,11 +23,6 @@ defmodule ForkChoiceTestRunner do
:ok
end
- @impl TestRunner
- def skip?(%SpecTestCase{fork: "capella"}), do: false
- def skip?(%SpecTestCase{fork: "deneb"}), do: false
- def skip?(_testcase), do: true
-
@impl TestRunner
def run_test_case(testcase) do
case_dir = SpecTestCase.dir(testcase)
@@ -177,7 +172,7 @@ defmodule ForkChoiceTestRunner do
# TODO: validate the filename's hash
defp load_blob_data(case_dir, block, %{blobs: "blobs_0x" <> _hash = blobs_file, proofs: proofs}) do
- schema = {:list, TypeAliases.blob(), ChainSpec.get("MAX_BLOBS_PER_BLOCK")}
+ schema = {:list, TypeAliases.blob(), ChainSpec.get("MAX_BLOBS_PER_BLOCK_ELECTRA")}
blobs = SpecTestUtils.read_ssz_ex_from_file!(case_dir <> "/#{blobs_file}.ssz_snappy", schema)
diff --git a/test/spec/runners/kzg.ex b/test/spec/runners/kzg.ex
index f5ca1f471..7ffac9b33 100644
--- a/test/spec/runners/kzg.ex
+++ b/test/spec/runners/kzg.ex
@@ -6,21 +6,6 @@ defmodule KzgTestRunner do
use ExUnit.CaseTemplate
use TestRunner
- # Remove handler from here once you implement the corresponding functions
- @disabled_handlers [
- # "blob_to_kzg_commitment"
- # "compute_kzg_proof",
- # "verify_kzg_proof",
- # "compute_blob_kzg_proof",
- # "verify_blob_kzg_proof",
- # "verify_blob_kzg_proof_batch"
- ]
-
- @impl TestRunner
- def skip?(%SpecTestCase{} = testcase) do
- Enum.member?(@disabled_handlers, testcase.handler)
- end
-
@impl TestRunner
def run_test_case(%SpecTestCase{} = testcase) do
case_dir = SpecTestCase.dir(testcase)
diff --git a/test/spec/runners/light_client.ex b/test/spec/runners/light_client.ex
index 01620af0d..fec092b09 100644
--- a/test/spec/runners/light_client.ex
+++ b/test/spec/runners/light_client.ex
@@ -19,15 +19,8 @@ defmodule LightClientTestRunner do
Enum.member?(@disabled_handlers, testcase.handler)
end
- def skip?(%SpecTestCase{fork: "deneb"} = _testcase) do
- # TODO: all of them fail
- true
- end
-
- @impl TestRunner
- def skip?(_testcase) do
- true
- end
+ # TODO: We didn't implement lightclient functions yet
+ def skip?(%SpecTestCase{fork: fork}) when fork in ["deneb", "electra"], do: true
@impl TestRunner
def run_test_case(%SpecTestCase{} = testcase) do
diff --git a/test/spec/runners/operations.ex b/test/spec/runners/operations.ex
index 62fa019e5..12aedc666 100644
--- a/test/spec/runners/operations.ex
+++ b/test/spec/runners/operations.ex
@@ -5,6 +5,9 @@ defmodule OperationsTestRunner do
alias LambdaEthereumConsensus.StateTransition.Operations
alias LambdaEthereumConsensus.Utils.Diff
+ alias Types.ConsolidationRequest
+ alias Types.DepositRequest
+ alias Types.WithdrawalRequest
alias Types.Attestation
alias Types.AttesterSlashing
@@ -32,7 +35,10 @@ defmodule OperationsTestRunner do
"sync_aggregate" => SyncAggregate,
"execution_payload" => BeaconBlockBody,
"withdrawals" => ExecutionPayload,
- "bls_to_execution_change" => SignedBLSToExecutionChange
+ "bls_to_execution_change" => SignedBLSToExecutionChange,
+ "consolidation_request" => ConsolidationRequest,
+ "withdrawal_request" => WithdrawalRequest,
+ "deposit_request" => DepositRequest
# "deposit_receipt" => "DepositReceipt" Not yet implemented
}
@@ -48,54 +54,14 @@ defmodule OperationsTestRunner do
"sync_aggregate" => "sync_aggregate",
"execution_payload" => "body",
"withdrawals" => "execution_payload",
- "bls_to_execution_change" => "address_change"
+ "bls_to_execution_change" => "address_change",
+ "consolidation_request" => "consolidation_request",
+ "withdrawal_request" => "withdrawal_request",
+ "deposit_request" => "deposit_request"
# "deposit_receipt" => "deposit_receipt" Not yet implemented
}
- # Remove handler from here once you implement the corresponding functions
- # "deposit_receipt" handler is not yet implemented
- @disabled_handlers [
- # "attester_slashing",
- # "attestation",
- # "block_header",
- # "deposit",
- # "proposer_slashing",
- # "voluntary_exit",
- # "sync_aggregate",
- # "execution_payload",
- # "withdrawals",
- # "bls_to_execution_change"
- ]
-
- @disabled_handlers_deneb [
- # "attester_slashing",
- # "attestation",
- # "block_header",
- # "deposit",
- # "proposer_slashing",
- # "voluntary_exit"
- # "sync_aggregate",
- # "execution_payload",
- # "withdrawals",
- # "bls_to_execution_change"
- ]
-
- @impl TestRunner
- def skip?(%SpecTestCase{fork: "capella", handler: handler}) do
- Enum.member?(@disabled_handlers, handler)
- end
-
- @impl TestRunner
- def skip?(%SpecTestCase{fork: "deneb", handler: handler}) do
- Enum.member?(@disabled_handlers_deneb, handler)
- end
-
- @impl TestRunner
- def skip?(_testcase) do
- true
- end
-
@impl TestRunner
def run_test_case(%SpecTestCase{handler: handler} = testcase) do
case_dir = SpecTestCase.dir(testcase) <> "/"
diff --git a/test/spec/runners/random.ex b/test/spec/runners/random.ex
index 5fd9a957b..e8dfb9f7d 100644
--- a/test/spec/runners/random.ex
+++ b/test/spec/runners/random.ex
@@ -6,11 +6,6 @@ defmodule RandomTestRunner do
use ExUnit.CaseTemplate
use TestRunner
- @impl TestRunner
- def skip?(%SpecTestCase{fork: "capella"}), do: false
- def skip?(%SpecTestCase{fork: "deneb"}), do: false
- def skip?(_), do: true
-
@impl TestRunner
def run_test_case(testcase) do
Helpers.ProcessBlocks.process_blocks(testcase)
diff --git a/test/spec/runners/rewards.ex b/test/spec/runners/rewards.ex
index df90674c9..afbde0465 100644
--- a/test/spec/runners/rewards.ex
+++ b/test/spec/runners/rewards.ex
@@ -6,20 +6,6 @@ defmodule RewardsTestRunner do
use TestRunner
alias Types.BeaconState
- @disabled [
- # "basic",
- # "leak",
- # "random"
- ]
-
- @impl TestRunner
- def skip?(%SpecTestCase{fork: "capella", handler: handler}) do
- Enum.member?(@disabled, handler)
- end
-
- def skip?(%SpecTestCase{fork: "deneb"}), do: false
- def skip?(_), do: true
-
@impl TestRunner
def run_test_case(%SpecTestCase{} = testcase) do
case_dir = SpecTestCase.dir(testcase)
diff --git a/test/spec/runners/sanity.ex b/test/spec/runners/sanity.ex
index 3cfa468e2..13be8a2e9 100644
--- a/test/spec/runners/sanity.ex
+++ b/test/spec/runners/sanity.ex
@@ -10,109 +10,17 @@ defmodule SanityTestRunner do
alias LambdaEthereumConsensus.Utils.Diff
alias Types.BeaconState
- @disabled_block_cases [
- # "activate_and_partial_withdrawal_max_effective_balance",
- # "activate_and_partial_withdrawal_overdeposit",
- # "attestation",
- # "attester_slashing",
- # "balance_driven_status_transitions",
- # "bls_change",
- # "deposit_and_bls_change",
- # "deposit_in_block",
- # "deposit_top_up",
- # "duplicate_attestation_same_block",
- # "invalid_is_execution_enabled_false",
- # "empty_block_transition",
- # "empty_block_transition_large_validator_set",
- # "empty_block_transition_no_tx",
- # "empty_block_transition_randomized_payload",
- # "empty_epoch_transition",
- # "empty_epoch_transition_large_validator_set",
- # "empty_epoch_transition_not_finalizing",
- # "eth1_data_votes_consensus",
- # "eth1_data_votes_no_consensus",
- # "exit_and_bls_change",
- # "full_random_operations_0",
- # "full_random_operations_1",
- # "full_random_operations_2",
- # "full_random_operations_3",
- # "full_withdrawal_in_epoch_transition",
- # "high_proposer_index",
- # "historical_batch",
- # "inactivity_scores_full_participation_leaking",
- # "inactivity_scores_leaking",
- # "invalid_all_zeroed_sig",
- # "invalid_duplicate_attester_slashing_same_block",
- # "invalid_duplicate_bls_changes_same_block",
- # "invalid_duplicate_deposit_same_block",
- # "invalid_duplicate_proposer_slashings_same_block",
- # "invalid_duplicate_validator_exit_same_block",
- # "invalid_incorrect_block_sig",
- # "invalid_incorrect_proposer_index_sig_from_expected_proposer",
- # "invalid_incorrect_proposer_index_sig_from_proposer_index",
- # "invalid_incorrect_state_root",
- # "invalid_only_increase_deposit_count",
- # "invalid_parent_from_same_slot",
- # "invalid_prev_slot_block_transition",
- # "invalid_same_slot_block_transition",
- # "invalid_similar_proposer_slashings_same_block",
- # "invalid_two_bls_changes_of_different_addresses_same_validator_same_block",
- # "invalid_withdrawal_fail_second_block_payload_isnt_compatible",
- # "is_execution_enabled_false",
- # "many_partial_withdrawals_in_epoch_transition",
- # "multiple_attester_slashings_no_overlap",
- # "multiple_attester_slashings_partial_overlap",
- # "multiple_different_proposer_slashings_same_block",
- # "multiple_different_validator_exits_same_block",
- # "partial_withdrawal_in_epoch_transition",
- # "proposer_after_inactive_index",
- # "proposer_self_slashing",
- # "proposer_slashing",
- # "skipped_slots",
- # "slash_and_exit_diff_index",
- # "slash_and_exit_same_index"
- # "sync_committee_committee__empty",
- # "sync_committee_committee__full",
- # "sync_committee_committee__half",
- # "sync_committee_committee_genesis__empty",
- # "sync_committee_committee_genesis__full",
- # "sync_committee_committee_genesis__half",
- # "top_up_and_partial_withdrawable_validator",
- # "top_up_to_fully_withdrawn_validator",
- # "voluntary_exit",
- # "withdrawal_success_two_blocks"
- ]
-
+ # TODO: We need to make sure this is still needed to be here
@disabled_slot_cases [
- # "empty_epoch",
- # "slots_1",
- # "slots_2",
- # "over_epoch_boundary",
- # NOTE: too long to run in CI
- # TODO: optimize
"historical_accumulator"
-
- # "double_empty_epoch"
]
@impl TestRunner
- def skip?(%SpecTestCase{fork: "capella", handler: "blocks", case: testcase}) do
- Enum.member?(@disabled_block_cases, testcase)
- end
-
- def skip?(%SpecTestCase{fork: "capella", handler: "slots", case: testcase}) do
- Enum.member?(@disabled_slot_cases, testcase)
- end
-
- def skip?(%SpecTestCase{fork: "deneb", handler: "blocks", case: testcase}) do
- Enum.member?(@disabled_block_cases, testcase)
- end
-
- def skip?(%SpecTestCase{fork: "deneb", handler: "slots", case: testcase}) do
+ def skip?(%SpecTestCase{handler: "slots", case: testcase}) do
Enum.member?(@disabled_slot_cases, testcase)
end
- def skip?(_), do: true
+ def skip?(_), do: false
@impl TestRunner
def run_test_case(%SpecTestCase{handler: "slots"} = testcase) do
diff --git a/test/spec/runners/shuffling.ex b/test/spec/runners/shuffling.ex
index 805c823d2..4f8bf669e 100644
--- a/test/spec/runners/shuffling.ex
+++ b/test/spec/runners/shuffling.ex
@@ -9,16 +9,6 @@ defmodule ShufflingTestRunner do
alias LambdaEthereumConsensus.StateTransition.Misc
alias LambdaEthereumConsensus.StateTransition.Shuffling
- # Remove handler from here once you implement the corresponding functions
- @disabled_handlers [
- # "core"
- ]
-
- @impl TestRunner
- def skip?(%SpecTestCase{} = testcase) do
- Enum.member?(@disabled_handlers, testcase.handler)
- end
-
@impl TestRunner
def run_test_case(%SpecTestCase{} = testcase) do
case_dir = SpecTestCase.dir(testcase)
diff --git a/test/spec/runners/ssz_generic.ex b/test/spec/runners/ssz_generic.ex
index 2b9ef8ffc..61d4ded71 100644
--- a/test/spec/runners/ssz_generic.ex
+++ b/test/spec/runners/ssz_generic.ex
@@ -5,30 +5,6 @@ defmodule SszGenericTestRunner do
use ExUnit.CaseTemplate
use TestRunner
- @disabled_handlers [
- # "basic_vector",
- # "bitlist",
- # "bitvector"
- # "boolean",
- # "containers"
- # "uints"
- ]
-
- @disabled_containers [
- # "SingleFieldTestStruct",
- # "SmallTestStruct",
- # "FixedTestStruct",
- # "VarTestStruct",
- # "ComplexTestStruct"
- # "BitsStruct"
- ]
-
- @impl TestRunner
- def skip?(%SpecTestCase{fork: fork, handler: handler, case: cse}) do
- skip_container? = Enum.any?(@disabled_containers, &String.contains?(cse, &1))
- fork != "phase0" or Enum.member?(@disabled_handlers, handler) or skip_container?
- end
-
@impl TestRunner
def run_test_case(%SpecTestCase{} = testcase) do
case_dir = SpecTestCase.dir(testcase)
diff --git a/test/spec/runners/ssz_static.ex b/test/spec/runners/ssz_static.ex
index f2c5a2dfb..d4c380c17 100644
--- a/test/spec/runners/ssz_static.ex
+++ b/test/spec/runners/ssz_static.ex
@@ -17,47 +17,6 @@ defmodule SszStaticTestRunner do
@only_ssz_ex [Types.Eth1Block, Types.SyncAggregatorSelectionData]
@disabled [
- # "DepositData",
- # "DepositMessage",
- # "Eth1Data",
- # "ProposerSlashing",
- # "SignedBeaconBlockHeader",
- # "SignedVoluntaryExit",
- # "Validator",
- # "VoluntaryExit",
- # "Attestation",
- # "AttestationData",
- # "BLSToExecutionChange",
- # "BeaconBlockHeader",
- # "Checkpoint",
- # "Deposit",
- # "SignedBLSToExecutionChange",
- # "SigningData",
- # "SyncCommittee",
- # "SyncCommitteeMessage",
- # "Withdrawal",
- # "AttesterSlashing",
- # "HistoricalSummary",
- # "PendingAttestation",
- # "Fork",
- # "ForkData",
- # "HistoricalBatch",
- # "IndexedAttestation",
- # "ExecutionPayload",
- # "ExecutionPayloadHeader",
- # "SignedBeaconBlock",
- # "SyncAggregate",
- # "AggregateAndProof",
- # "BeaconBlock",
- # "BeaconBlockBody",
- # "BeaconState",
- # "SignedAggregateAndProof",
- # "Eth1Block",
- # "SyncAggregatorSelectionData",
- # "SignedContributionAndProof",
- # "SyncCommitteeContribution",
- # "ContributionAndProof",
- # -- not defined yet
"LightClientBootstrap",
"LightClientOptimisticUpdate",
"LightClientUpdate",
@@ -76,15 +35,8 @@ defmodule SszStaticTestRunner do
}
@impl TestRunner
- def skip?(%SpecTestCase{fork: "capella", handler: handler}) do
- Enum.member?(@disabled, handler)
- end
-
- def skip?(%SpecTestCase{fork: "deneb", handler: handler}) do
- Enum.member?(@disabled, handler)
- end
-
- def skip?(_), do: true
+ def skip?(%SpecTestCase{handler: handler}) when handler in @disabled, do: true
+ def skip?(_), do: false
@impl TestRunner
def run_test_case(%SpecTestCase{} = testcase) do
diff --git a/test/spec/runners/sync.ex b/test/spec/runners/sync.ex
index 9e25ed781..cdce17216 100644
--- a/test/spec/runners/sync.ex
+++ b/test/spec/runners/sync.ex
@@ -8,10 +8,6 @@ defmodule SyncTestRunner do
alias LambdaEthereumConsensus.Execution.EngineApi
- @disabled_cases [
- # "from_syncing_to_invalid"
- ]
-
@impl TestRunner
def setup() do
# Start this supervisor, necessary for post state tasks.
@@ -19,21 +15,18 @@ defmodule SyncTestRunner do
:ok
end
- @impl TestRunner
- def skip?(%SpecTestCase{fork: "capella"} = testcase) do
- Enum.member?(@disabled_cases, testcase.case)
- end
-
- def skip?(%SpecTestCase{fork: "deneb"}) do
- false
- end
-
- def skip?(_testcase), do: true
-
@impl TestRunner
def run_test_case(%SpecTestCase{} = testcase) do
original_engine_api_config = Application.fetch_env!(:lambda_ethereum_consensus, EngineApi)
+ on_exit(fn ->
+ Application.put_env(
+ :lambda_ethereum_consensus,
+ EngineApi,
+ original_engine_api_config
+ )
+ end)
+
Application.put_env(
:lambda_ethereum_consensus,
EngineApi,
@@ -43,13 +36,6 @@ defmodule SyncTestRunner do
{:ok, _pid} = SyncTestRunner.EngineApiMock.start_link([])
ForkChoiceTestRunner.run_test_case(testcase)
-
- # TODO: we should do this cleanup even if the test crashes/fails
- Application.put_env(
- :lambda_ethereum_consensus,
- EngineApi,
- original_engine_api_config
- )
end
end
diff --git a/test/spec/tasks/generate_spec_tests.ex b/test/spec/tasks/generate_spec_tests.ex
index 5224be3c5..5a8100c06 100644
--- a/test/spec/tasks/generate_spec_tests.ex
+++ b/test/spec/tasks/generate_spec_tests.ex
@@ -97,7 +97,7 @@ defmodule Mix.Tasks.GenerateSpecTests do
defp generate_case(runner_module, testcase) do
"""
@tag :tmp_dir
- #{if runner_module.skip?(testcase), do: "\n@tag :skip", else: ""}
+ #{if runner_module.skip?(testcase), do: "@tag :skip", else: ""}
test "#{SpecTestCase.name(testcase)}" do
testcase = #{inspect(testcase)}
#{runner_module}.run_test_case(testcase)
diff --git a/test/spec/utils.ex b/test/spec/utils.ex
index d0a5c6909..318b6f41a 100644
--- a/test/spec/utils.ex
+++ b/test/spec/utils.ex
@@ -35,7 +35,7 @@ defmodule SpecTestUtils do
end
def sanitize_yaml({k, v}), do: {String.to_atom(k), sanitize_yaml(v)}
- def sanitize_yaml("0x"), do: <<0>>
+ def sanitize_yaml("0x"), do: []
def sanitize_yaml("0x" <> hash), do: Base.decode16!(hash, case: :lower)
def sanitize_yaml(x) when is_binary(x) do
diff --git a/test/unit/ssz_ex_test.exs b/test/unit/ssz_ex_test.exs
index cf0e00882..eac6781a5 100644
--- a/test/unit/ssz_ex_test.exs
+++ b/test/unit/ssz_ex_test.exs
@@ -632,7 +632,12 @@ defmodule Unit.SSZExTest do
excess_blob_gas: 0
},
bls_to_execution_changes: [],
- blob_kzg_commitments: []
+ blob_kzg_commitments: [],
+ execution_requests: %Types.ExecutionRequests{
+ deposits: [],
+ withdrawals: [],
+ consolidations: []
+ }
}
}
@@ -889,6 +894,7 @@ defmodule Unit.SSZExTest do
"Invalid binary length while encoding BitVector. \nExpected: 512.\nFound: 2.\nStacktrace: SyncAggregate.sync_committee_bits"
end
+ @tag :skip
test "stacktrace encode nested container" do
attester_slashing = build_broken_attester_slashing()
@@ -898,6 +904,7 @@ defmodule Unit.SSZExTest do
"Invalid binary length while encoding list of {:int, 64}.\nExpected max_size: 2048.\nFound: 3000\nStacktrace: AttesterSlashing.attestation_2.attesting_indices"
end
+ @tag :skip
test "stacktrace hash_tree_root nested container" do
attester_slashing = build_broken_attester_slashing()
{:error, error} = SszEx.hash_tree_root(attester_slashing)
diff --git a/test/unit/state_transition/misc_test.exs b/test/unit/state_transition/misc_test.exs
index 470336c52..db0aba687 100644
--- a/test/unit/state_transition/misc_test.exs
+++ b/test/unit/state_transition/misc_test.exs
@@ -12,6 +12,7 @@ defmodule Unit.StateTransition.MiscTest do
|> then(&Application.put_env(:lambda_ethereum_consensus, ChainSpec, &1))
end
+ @tag :skip
test "Calculating all committees for a single epoch should be the same by any method" do
state = Block.beacon_state_from_file().beacon_state
epoch = Accessors.get_current_epoch(state)
diff --git a/test/unit/validator/block_builder_test.exs b/test/unit/validator/block_builder_test.exs
index 0d32d2644..02a94ea19 100644
--- a/test/unit/validator/block_builder_test.exs
+++ b/test/unit/validator/block_builder_test.exs
@@ -18,6 +18,7 @@ defmodule Unit.Validator.BlockBuilderTest do
|> then(&Application.put_env(:lambda_ethereum_consensus, ChainSpec, &1))
end
+ @tag :skip
test "construct block" do
pre_state =
SpecTestUtils.read_ssz_from_file!(
@@ -27,7 +28,7 @@ defmodule Unit.Validator.BlockBuilderTest do
spec_block =
SpecTestUtils.read_ssz_from_file!(
- "test/fixtures/validator/proposer/empty_block.ssz_snappy",
+ "test/fixtures/validator/proposer/empty_signed_beacon_block.ssz_snappy",
SignedBeaconBlock
)
@@ -69,7 +70,7 @@ defmodule Unit.Validator.BlockBuilderTest do
test "prove commitments" do
spec_block =
SpecTestUtils.read_ssz_from_file!(
- "test/fixtures/validator/proposer/empty_block.ssz_snappy",
+ "test/fixtures/validator/proposer/empty_signed_beacon_block.ssz_snappy",
SignedBeaconBlock
)
@@ -79,12 +80,12 @@ defmodule Unit.Validator.BlockBuilderTest do
[proof] = BlockBuilder.compute_inclusion_proofs(body)
- assert length(proof) == 9
+ assert length(proof) == 10
commitment_root = SszEx.hash_tree_root!(commitment, TypeAliases.kzg_commitment())
# Manually computed generalized index of the commitment in the body
- index = 0b101100000
+ index = 0b1011000000
valid? =
Predicates.valid_merkle_branch?(commitment_root, proof, length(proof), index, body_root)