diff --git a/packages/beacon-node/test/e2e/api/impl/config.test.ts b/packages/beacon-node/test/e2e/api/impl/config.test.ts
index 454cdaf8d068..369f3eb138f5 100644
--- a/packages/beacon-node/test/e2e/api/impl/config.test.ts
+++ b/packages/beacon-node/test/e2e/api/impl/config.test.ts
@@ -13,6 +13,10 @@ const CONSTANT_NAMES_SKIP_LIST = new Set([
"PAYLOAD_STATUS_VALID",
"PAYLOAD_STATUS_INVALIDATED",
"PAYLOAD_STATUS_NOT_VALIDATED",
+ // TODO: decide whether to expose EMPTY_BLOCK_HASH in the config API. New bellatrix constant in
+ // v1.7.0-alpha.9; a zero Hash32 only used in the terminal-block fork-choice check, so skipped for
+ // now like the PAYLOAD_STATUS_* constants above.
+ "EMPTY_BLOCK_HASH",
]);
describe("api / impl / config", () => {
diff --git a/packages/beacon-node/test/spec/presets/fork_choice.test.ts b/packages/beacon-node/test/spec/presets/fork_choice.test.ts
index 8651ef3ed396..728e5044ef76 100644
--- a/packages/beacon-node/test/spec/presets/fork_choice.test.ts
+++ b/packages/beacon-node/test/spec/presets/fork_choice.test.ts
@@ -630,9 +630,17 @@ const forkChoiceTest =
name.includes("include_votes_another_empty_chain_with_enough_ffg_votes_current_epoch") ||
name.includes("include_votes_another_empty_chain_with_enough_ffg_votes_previous_epoch") ||
name.includes("include_votes_another_empty_chain_without_enough_ffg_votes_current_epoch"))) ||
- // TODO GLOAS: Spec test fixture bug in v1.7.0-alpha.5: wrong_withdrawals envelope SSZ data is
- // byte-for-byte identical to the valid envelope, making it impossible to reject
- name.endsWith("on_execution_payload_envelope__wrong_withdrawals"),
+ // TODO: re-enable after "apply proposer boost if dependent roots match" (consensus-specs #5306)
+ // is implemented. New behavior in v1.7.0-alpha.9; Lodestar still applies the boost
+ // unconditionally. Only the altair vectors exercise the changed condition (other forks pass).
+ name.endsWith("altair/fork_choice/on_block/pyspec_tests/justified_update_always_if_better") ||
+ name.endsWith("altair/fork_choice/on_block/pyspec_tests/justified_update_not_realized_finality") ||
+ name.endsWith("altair/fork_choice/get_head/pyspec_tests/voting_source_beyond_two_epoch") ||
+ // TODO GLOAS: re-enable after the validate_on_attestation payload-status updates
+ // (consensus-specs #5275) are implemented. New gloas on_attestation vectors in v1.7.0-alpha.9.
+ name.endsWith("validate_on_attestation_beacon_root_payload_check") ||
+ name.endsWith("validate_on_attestation_payload_invalid_index") ||
+ name.endsWith("validate_on_attestation_same_slot_full_vote_rejected"),
},
};
};
diff --git a/packages/beacon-node/test/spec/utils/specTestIterator.ts b/packages/beacon-node/test/spec/utils/specTestIterator.ts
index c0ee63af2bcb..50d1a69c30c4 100644
--- a/packages/beacon-node/test/spec/utils/specTestIterator.ts
+++ b/packages/beacon-node/test/spec/utils/specTestIterator.ts
@@ -72,7 +72,6 @@ export const defaultSkipOpts: SkipOpts = {
/^electra\/light_client\/single_merkle_proof\/BeaconBlockBody.*/,
/^fulu\/light_client\/single_merkle_proof\/BeaconBlockBody.*/,
/^.+\/light_client\/data_collection\/.*/,
- /^gloas\/ssz_static\/ForkChoiceNode.*$/,
// Ignore the partial data column container additions for now. Unskip them when
// cell level DAS is ready
/^fulu\/ssz_static\/PartialDataColumn(Header|PartsMetadata|Sidecar)\/.*$/,
@@ -84,6 +83,11 @@ export const defaultSkipOpts: SkipOpts = {
// New test suite added in v1.7.0-alpha.8 (consensus-specs #5206); gloas PTC fork choice
// handling is not yet implemented in Lodestar.
/^gloas\/fork_choice\/on_payload_attestation_message\/.*$/,
+ // TODO-FULU: re-enable after the Fulu deposit-mechanism removal (consensus-specs #4704) is
+ // implemented. v1.7.0-alpha.9 regenerated these transition vectors so eth1-bridge deposits are
+ // no longer processed across the Electra to Fulu boundary; Lodestar still applies the Electra
+ // deposit flow, diverging the post-fork state root.
+ /^fulu\/transition\/.*/,
],
skippedTests: [
// TODO-GLOAS: re-enable after gloas light client is implemented
diff --git a/packages/state-transition/src/epoch/processProposerLookahead.ts b/packages/state-transition/src/epoch/processProposerLookahead.ts
index 4d92afcf45cc..6ab87c190405 100644
--- a/packages/state-transition/src/epoch/processProposerLookahead.ts
+++ b/packages/state-transition/src/epoch/processProposerLookahead.ts
@@ -1,6 +1,7 @@
import {ForkSeq, MIN_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params";
import {ssz} from "@lodestar/types";
import {CachedBeaconStateFulu, EpochTransitionCache} from "../types.js";
+import {FLAG_UNSLASHED} from "../util/attesterStatus.js";
import {computeEpochShuffling} from "../util/epochShuffling.js";
import {computeProposerIndices} from "../util/seed.js";
@@ -26,7 +27,13 @@ export function processProposerLookahead(
// Save shuffling to cache so afterProcessEpoch can reuse it instead of recomputing
cache.nextShuffling = shuffling;
- const lastEpochProposerLookahead = computeProposerIndices(fork, state, shuffling, epoch);
+ const activeIndices =
+ fork >= ForkSeq.gloas
+ ? // Exclude slashed validators from proposing (EIP-8045)
+ shuffling.activeIndices.filter((index) => (cache.flags[index] & FLAG_UNSLASHED) !== 0)
+ : shuffling.activeIndices;
+
+ const lastEpochProposerLookahead = computeProposerIndices(fork, state, {activeIndices}, epoch);
state.proposerLookahead = ssz.fulu.ProposerLookahead.toViewDU([
...remainingProposerLookahead,
diff --git a/spec-tests-version.json b/spec-tests-version.json
index 2e5a2ffdfe92..306c3252b52c 100644
--- a/spec-tests-version.json
+++ b/spec-tests-version.json
@@ -1,6 +1,6 @@
{
"ethereumConsensusSpecsTests": {
- "specVersion": "v1.7.0-alpha.8",
+ "specVersion": "v1.7.0-alpha.9",
"specTestsRepoUrl": "https://github.com/ethereum/consensus-specs",
"outputDirBase": "spec-tests",
"testsToDownload": [
diff --git a/specrefs/.ethspecify.yml b/specrefs/.ethspecify.yml
index 1dcdf1a78e23..2dd0246922d9 100644
--- a/specrefs/.ethspecify.yml
+++ b/specrefs/.ethspecify.yml
@@ -1,4 +1,4 @@
-version: v1.7.0-alpha.8
+version: v1.7.0-alpha.9
style: full
specrefs:
@@ -25,6 +25,7 @@ exceptions:
- UINT64_MAX_SQRT#phase0
# bellatrix
+ - EMPTY_BLOCK_HASH#bellatrix
- PAYLOAD_STATUS_INVALIDATED#bellatrix
- PAYLOAD_STATUS_NOT_VALIDATED#bellatrix
- PAYLOAD_STATUS_VALID#bellatrix
@@ -61,7 +62,6 @@ exceptions:
containers:
# gloas
- - ForkChoiceNode#gloas
- LightClientHeader#gloas
- PartialDataColumnGroupID#gloas
- PartialDataColumnHeader#gloas
@@ -81,6 +81,7 @@ exceptions:
dataclasses:
# phase0
+ - ForkChoiceNode#phase0
- LatestMessage#phase0
- Seen#phase0
@@ -99,6 +100,9 @@ exceptions:
# electra
- Seen#electra
+ # fulu
+ - Seen#fulu
+
# capella
- ExpectedWithdrawals#capella
@@ -107,7 +111,9 @@ exceptions:
# gloas
- ExpectedWithdrawals#gloas
+ - ForkChoiceNode#gloas
- LatestMessage#gloas
+ - Seen#gloas
- Store#gloas
# phase0 fast confirmation / not implemented
@@ -146,10 +152,14 @@ exceptions:
- get_base_reward#phase0
- get_checkpoint_block#phase0
- get_current_store_epoch#phase0
+ - get_dependent_root#phase0
- get_eligible_validator_indices#phase0
- get_eth1_vote#phase0
- get_filtered_block_tree#phase0
- get_forkchoice_store#phase0
+ - get_node_children#phase0
+ - get_node_for_root#phase0
+ - get_supported_node#phase0
- get_head_deltas#phase0
- get_inactivity_penalty_deltas#phase0
- get_inclusion_delay_deltas#phase0
@@ -210,6 +220,7 @@ exceptions:
- compute_fork_version#bellatrix
- get_execution_payload#bellatrix
- get_pow_block_at_terminal_total_difficulty#bellatrix
+ - get_safe_execution_block_hash#bellatrix
- get_terminal_pow_block#bellatrix
- is_execution_block#bellatrix
- is_merge_transition_block#bellatrix
@@ -319,6 +330,9 @@ exceptions:
- verify_cell_kzg_proof_batch_impl#fulu
- verify_partial_data_column_header_inclusion_proof#fulu
- verify_partial_data_column_sidecar_kzg_proofs#fulu
+ - validate_beacon_block_gossip#fulu
+ - validate_data_column_sidecar_gossip#fulu
+ - validate_partial_data_column_sidecar_gossip#fulu
# gloas
- add_builder_to_registry#gloas
@@ -329,9 +343,11 @@ exceptions:
- get_ancestor#gloas
- get_attestation_participation_flag_indices#gloas
- get_attestation_score#gloas
+ - get_beacon_proposer_indices#gloas
- get_builder_withdrawals#gloas
- get_builders_sweep_withdrawals#gloas
- get_checkpoint_block#gloas
+ - get_dependent_root#gloas
- get_execution_payload_bid_signature#gloas
- get_execution_payload_envelope_signature#gloas
- get_forkchoice_store#gloas
@@ -339,6 +355,7 @@ exceptions:
- get_index_for_new_builder#gloas
- get_next_sync_committee_indices#gloas
- get_node_children#gloas
+ - get_node_for_root#gloas
- get_parent_payload_status#gloas
- get_payload_attestation_due_ms#gloas
- get_payload_attestation_message_signature#gloas
@@ -346,14 +363,17 @@ exceptions:
- get_pending_balance_to_withdraw#gloas
- get_proposer_preferences_signature#gloas
- get_ptc_assignment#gloas
+ - get_safe_execution_block_hash#gloas
+ - get_supported_node#gloas
- get_upcoming_proposal_slots#gloas
- get_weight#gloas
- has_compounding_withdrawal_credential#gloas
+ - is_ancestor#gloas
- is_head_late#gloas
- is_head_weak#gloas
- is_parent_node_full#gloas
- is_parent_strong#gloas
- - is_supporting_vote#gloas
+ - is_previous_slot_payload_decision#gloas
- is_valid_proposal_slot#gloas
- notify_ptc_messages#gloas
- on_block#gloas
diff --git a/specrefs/constants.yml b/specrefs/constants.yml
index 0dd86d9bc4e9..fc08509f0329 100644
--- a/specrefs/constants.yml
+++ b/specrefs/constants.yml
@@ -292,6 +292,13 @@
DOMAIN_VOLUNTARY_EXIT: DomainType = '0x04000000'
+- name: EMPTY_BLOCK_HASH#bellatrix
+ sources: []
+ spec: |
+
+ EMPTY_BLOCK_HASH: Hash32 =
+
+
- name: ENDIANNESS#phase0
sources: []
spec: |
diff --git a/specrefs/containers.yml b/specrefs/containers.yml
index 79ec57900654..c1ab68597329 100644
--- a/specrefs/containers.yml
+++ b/specrefs/containers.yml
@@ -1175,15 +1175,6 @@
epoch: Epoch
-- name: ForkChoiceNode#gloas
- sources: []
- spec: |
-
- class ForkChoiceNode(Container):
- root: Root
- payload_status: PayloadStatus # One of PAYLOAD_STATUS_* values
-
-
- name: ForkData#phase0
sources:
- file: packages/types/src/phase0/sszTypes.ts
diff --git a/specrefs/dataclasses.yml b/specrefs/dataclasses.yml
index 2e91e918d831..7916aeeea911 100644
--- a/specrefs/dataclasses.yml
+++ b/specrefs/dataclasses.yml
@@ -14,8 +14,8 @@
- file: packages/types/src/deneb/sszTypes.ts
search: export const BlobsBundle =
spec: |
-
- class BlobsBundle(object):
+
+ class BlobsBundle:
commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]
proofs: List[KZGProof, MAX_BLOB_COMMITMENTS_PER_BLOCK]
blobs: List[Blob, MAX_BLOB_COMMITMENTS_PER_BLOCK]
@@ -26,8 +26,8 @@
- file: packages/types/src/fulu/sszTypes.ts
search: export const BlobsBundle =
spec: |
-
- class BlobsBundle(object):
+
+ class BlobsBundle:
commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]
# [Modified in Fulu:EIP7594]
proofs: List[KZGProof, FIELD_ELEMENTS_PER_EXT_BLOB * MAX_BLOB_COMMITMENTS_PER_BLOCK]
@@ -37,8 +37,8 @@
- name: ExpectedWithdrawals#capella
sources: []
spec: |
-
- class ExpectedWithdrawals(object):
+
+ class ExpectedWithdrawals:
withdrawals: Sequence[Withdrawal]
processed_sweep_withdrawals_count: uint64
@@ -46,8 +46,8 @@
- name: ExpectedWithdrawals#electra
sources: []
spec: |
-
- class ExpectedWithdrawals(object):
+
+ class ExpectedWithdrawals:
withdrawals: Sequence[Withdrawal]
# [New in Electra:EIP7251]
processed_partial_withdrawals_count: uint64
@@ -57,8 +57,8 @@
- name: ExpectedWithdrawals#gloas
sources: []
spec: |
-
- class ExpectedWithdrawals(object):
+
+ class ExpectedWithdrawals:
withdrawals: Sequence[Withdrawal]
# [New in Gloas:EIP7732]
processed_builder_withdrawals_count: uint64
@@ -71,8 +71,8 @@
- name: FastConfirmationStore#phase0
sources: []
spec: |
-
- class FastConfirmationStore(object):
+
+ class FastConfirmationStore:
store: Store
confirmed_root: Root
previous_epoch_observed_justified_checkpoint: Checkpoint
@@ -82,11 +82,31 @@
current_slot_head: Root
+- name: ForkChoiceNode#phase0
+ sources: []
+ spec: |
+
+ @dataclass(eq=True, frozen=True)
+ class ForkChoiceNode:
+ root: Root
+
+
+- name: ForkChoiceNode#gloas
+ sources: []
+ spec: |
+
+ @dataclass(eq=True, frozen=True)
+ class ForkChoiceNode:
+ root: Root
+ # [New in Gloas:EIP7732]
+ payload_status: PayloadStatus # One of PAYLOAD_STATUS_* values
+
+
- name: GetInclusionListResponse#heze
sources: []
spec: |
-
- class GetInclusionListResponse(object):
+
+ class GetInclusionListResponse:
inclusion_list_transactions: Sequence[Transaction]
@@ -97,8 +117,8 @@
- file: packages/beacon-node/src/execution/engine/interface.ts
search: "getPayload("
spec: |
-
- class GetPayloadResponse(object):
+
+ class GetPayloadResponse:
execution_payload: ExecutionPayload
@@ -109,8 +129,8 @@
- file: packages/beacon-node/src/execution/engine/interface.ts
search: "getPayload("
spec: |
-
- class GetPayloadResponse(object):
+
+ class GetPayloadResponse:
execution_payload: ExecutionPayload
block_value: uint256
@@ -122,8 +142,8 @@
- file: packages/beacon-node/src/execution/engine/interface.ts
search: "getPayload("
spec: |
-
- class GetPayloadResponse(object):
+
+ class GetPayloadResponse:
execution_payload: ExecutionPayload
block_value: uint256
# [New in Deneb:EIP4844]
@@ -137,8 +157,8 @@
- file: packages/beacon-node/src/execution/engine/interface.ts
search: "getPayload("
spec: |
-
- class GetPayloadResponse(object):
+
+ class GetPayloadResponse:
execution_payload: ExecutionPayload
block_value: uint256
blobs_bundle: BlobsBundle
@@ -153,8 +173,8 @@
- file: packages/beacon-node/src/execution/engine/interface.ts
search: "getPayload("
spec: |
-
- class GetPayloadResponse(object):
+
+ class GetPayloadResponse:
execution_payload: ExecutionPayload
block_value: uint256
# [Modified in Fulu:EIP7594]
@@ -165,8 +185,8 @@
- name: InclusionListStore#heze
sources: []
spec: |
-
- class InclusionListStore(object):
+
+ class InclusionListStore:
inclusion_lists: DefaultDict[Tuple[Slot, Root], Dict[Root, InclusionList]] = field(
default_factory=lambda: defaultdict(dict)
)
@@ -179,9 +199,9 @@
- name: LatestMessage#phase0
sources: []
spec: |
-
+
@dataclass(eq=True, frozen=True)
- class LatestMessage(object):
+ class LatestMessage:
epoch: Epoch
root: Root
@@ -189,9 +209,9 @@
- name: LatestMessage#gloas
sources: []
spec: |
-
+
@dataclass(eq=True, frozen=True)
- class LatestMessage(object):
+ class LatestMessage:
slot: Slot
root: Root
payload_present: boolean
@@ -202,8 +222,8 @@
- file: packages/types/src/altair/sszTypes.ts
search: export const LightClientStore =
spec: |
-
- class LightClientStore(object):
+
+ class LightClientStore:
# Header that is finalized
finalized_header: LightClientHeader
# Sync committees corresponding to the finalized header
@@ -223,8 +243,8 @@
- file: packages/types/src/capella/sszTypes.ts
search: export const LightClientStore =
spec: |
-
- class LightClientStore(object):
+
+ class LightClientStore:
# [Modified in Capella]
finalized_header: LightClientHeader
current_sync_committee: SyncCommittee
@@ -242,8 +262,8 @@
- file: packages/beacon-node/src/execution/engine/interface.ts
search: "notifyNewPayload("
spec: |
-
- class NewPayloadRequest(object):
+
+ class NewPayloadRequest:
execution_payload: ExecutionPayload
@@ -252,8 +272,8 @@
- file: packages/beacon-node/src/execution/engine/interface.ts
search: "notifyNewPayload("
spec: |
-
- class NewPayloadRequest(object):
+
+ class NewPayloadRequest:
execution_payload: ExecutionPayload
versioned_hashes: Sequence[VersionedHash]
parent_beacon_block_root: Root
@@ -264,8 +284,8 @@
- file: packages/beacon-node/src/execution/engine/interface.ts
search: "notifyNewPayload("
spec: |
-
- class NewPayloadRequest(object):
+
+ class NewPayloadRequest:
execution_payload: ExecutionPayload
versioned_hashes: Sequence[VersionedHash]
parent_beacon_block_root: Root
@@ -289,8 +309,8 @@
- file: packages/types/src/bellatrix/sszTypes.ts
search: export const PayloadAttributes =
spec: |
-
- class PayloadAttributes(object):
+
+ class PayloadAttributes:
timestamp: uint64
prev_randao: Bytes32
suggested_fee_recipient: ExecutionAddress
@@ -301,8 +321,8 @@
- file: packages/types/src/capella/sszTypes.ts
search: export const PayloadAttributes =
spec: |
-
- class PayloadAttributes(object):
+
+ class PayloadAttributes:
timestamp: uint64
prev_randao: Bytes32
suggested_fee_recipient: ExecutionAddress
@@ -315,8 +335,8 @@
- file: packages/types/src/deneb/sszTypes.ts
search: export const PayloadAttributes =
spec: |
-
- class PayloadAttributes(object):
+
+ class PayloadAttributes:
timestamp: uint64
prev_randao: Bytes32
suggested_fee_recipient: ExecutionAddress
@@ -330,8 +350,8 @@
- file: packages/types/src/gloas/sszTypes.ts
search: export const PayloadAttributes =
spec: |
-
- class PayloadAttributes(object):
+
+ class PayloadAttributes:
timestamp: uint64
prev_randao: Bytes32
suggested_fee_recipient: ExecutionAddress
@@ -346,8 +366,8 @@
- name: PayloadAttributes#heze
sources: []
spec: |
-
- class PayloadAttributes(object):
+
+ class PayloadAttributes:
timestamp: uint64
prev_randao: Bytes32
suggested_fee_recipient: ExecutionAddress
@@ -362,8 +382,8 @@
- name: Seen#altair
sources: []
spec: |
-
- class Seen(object):
+
+ class Seen:
proposer_slots: Set[Tuple[ValidatorIndex, Slot]]
aggregator_epochs: Set[Tuple[ValidatorIndex, Epoch]]
aggregate_data_roots: Dict[Root, Set[Tuple[boolean, ...]]]
@@ -382,8 +402,8 @@
- name: Seen#capella
sources: []
spec: |
-
- class Seen(object):
+
+ class Seen:
proposer_slots: Set[Tuple[ValidatorIndex, Slot]]
aggregator_epochs: Set[Tuple[ValidatorIndex, Epoch]]
aggregate_data_roots: Dict[Root, Set[Tuple[boolean, ...]]]
@@ -401,8 +421,8 @@
- name: Seen#deneb
sources: []
spec: |
-
- class Seen(object):
+
+ class Seen:
proposer_slots: Set[Tuple[ValidatorIndex, Slot]]
aggregator_epochs: Set[Tuple[ValidatorIndex, Epoch]]
aggregate_data_roots: Dict[Root, Set[Tuple[boolean, ...]]]
@@ -421,8 +441,8 @@
- name: Seen#electra
sources: []
spec: |
-
- class Seen(object):
+
+ class Seen:
proposer_slots: Set[Tuple[ValidatorIndex, Slot]]
aggregator_epochs: Set[Tuple[ValidatorIndex, Epoch]]
# [Modified in Electra:EIP7549]
@@ -438,13 +458,56 @@
blob_sidecar_tuples: Set[Tuple[Slot, ValidatorIndex, BlobIndex]]
+- name: Seen#fulu
+ sources: []
+ spec: |
+
+ class Seen:
+ proposer_slots: Set[Tuple[ValidatorIndex, Slot]]
+ aggregator_epochs: Set[Tuple[ValidatorIndex, Epoch]]
+ aggregate_data_roots: Dict[Tuple[Root, CommitteeIndex], Set[Tuple[boolean, ...]]]
+ voluntary_exit_indices: Set[ValidatorIndex]
+ proposer_slashing_indices: Set[ValidatorIndex]
+ attester_slashing_indices: Set[ValidatorIndex]
+ attestation_validator_epochs: Set[Tuple[ValidatorIndex, Epoch]]
+ sync_contribution_aggregator_slots: Set[Tuple[ValidatorIndex, Slot, uint64]]
+ sync_contribution_data: Dict[Tuple[Slot, Root, uint64], Set[Tuple[boolean, ...]]]
+ sync_message_validator_slots: Set[Tuple[Slot, ValidatorIndex, uint64]]
+ bls_to_execution_change_indices: Set[ValidatorIndex]
+ # [Modified in Fulu:EIP7594]
+ # Removed `blob_sidecar_tuples`
+ # [New in Fulu:EIP7594]
+ data_column_sidecar_tuples: Set[Tuple[Slot, ValidatorIndex, ColumnIndex]]
+ # [New in Fulu]
+ partial_data_column_headers: Dict[Root, PartialDataColumnHeader]
+
+
+- name: Seen#gloas
+ sources: []
+ spec: |
+
+ class Seen:
+ proposer_slots: Set[Tuple[ValidatorIndex, Slot]]
+ aggregator_epochs: Set[Tuple[ValidatorIndex, Epoch]]
+ aggregate_data_roots: Dict[Tuple[Root, CommitteeIndex], Set[Tuple[boolean, ...]]]
+ voluntary_exit_indices: Set[ValidatorIndex]
+ proposer_slashing_indices: Set[ValidatorIndex]
+ attester_slashing_indices: Set[ValidatorIndex]
+ attestation_validator_epochs: Set[Tuple[ValidatorIndex, Epoch]]
+ sync_contribution_aggregator_slots: Set[Tuple[ValidatorIndex, Slot, uint64]]
+ sync_contribution_data: Dict[Tuple[Slot, Root, uint64], Set[Tuple[boolean, ...]]]
+ sync_message_validator_slots: Set[Tuple[Slot, ValidatorIndex, uint64]]
+ bls_to_execution_change_indices: Set[ValidatorIndex]
+ data_column_sidecar_tuples: Set[Tuple[Slot, ValidatorIndex, ColumnIndex]]
+
+
- name: Store#phase0
sources:
- file: packages/fork-choice/src/forkChoice/store.ts
search: export interface IForkChoiceStore
spec: |
-
- class Store(object):
+
+ class Store:
time: uint64
genesis_time: uint64
justified_checkpoint: Checkpoint
@@ -464,8 +527,8 @@
- name: Store#gloas
sources: []
spec: |
-
- class Store(object):
+
+ class Store:
time: uint64
genesis_time: uint64
justified_checkpoint: Checkpoint
@@ -494,8 +557,8 @@
- name: Store#heze
sources: []
spec: |
-
- class Store(object):
+
+ class Store:
time: uint64
genesis_time: uint64
justified_checkpoint: Checkpoint
diff --git a/specrefs/functions.yml b/specrefs/functions.yml
index ed47d7825511..e63529bfd7a3 100644
--- a/specrefs/functions.yml
+++ b/specrefs/functions.yml
@@ -916,8 +916,8 @@
- name: compute_fork_version#phase0
sources: []
spec: |
-
- def compute_fork_version(epoch: Epoch) -> Version:
+
+ def compute_fork_version(epoch: Epoch) -> Version: # noqa: ARG001
"""
Return the fork version at the given ``epoch``.
"""
@@ -1129,7 +1129,7 @@
- file: packages/beacon-node/src/util/dataColumns.ts
search: export async function getCellsAndProofs(
spec: |
-
+
def compute_matrix(blobs: Sequence[Blob]) -> Sequence[MatrixEntry]:
"""
Return the full, flattened sequence of matrix entries.
@@ -1140,7 +1140,7 @@
matrix = []
for blob_index, blob in enumerate(blobs):
cells, proofs = compute_cells_and_kzg_proofs(blob)
- for cell_index, (cell, proof) in enumerate(zip(cells, proofs)):
+ for cell_index, (cell, proof) in enumerate(zip(cells, proofs, strict=True)):
matrix.append(
MatrixEntry(
cell=cell,
@@ -1232,7 +1232,7 @@
- name: compute_on_chain_aggregate#electra
sources: []
spec: |
-
+
def compute_on_chain_aggregate(network_aggregates: Sequence[Attestation]) -> Attestation:
aggregates = sorted(
network_aggregates, key=lambda a: get_committee_indices(a.committee_bits)[0]
@@ -1247,7 +1247,7 @@
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_flags = [(index in committee_indices) for index in range(MAX_COMMITTEES_PER_SLOT)]
committee_bits = Bitvector[MAX_COMMITTEES_PER_SLOT](committee_flags)
return Attestation(
@@ -1587,7 +1587,7 @@
- name: compute_subnets_for_sync_committee#altair
sources: []
spec: |
-
+
def compute_subnets_for_sync_committee(
state: BeaconState, validator_index: ValidatorIndex
) -> Set[SubnetID]:
@@ -1603,12 +1603,10 @@
sync_committee_indices = [
index for index, pubkey in enumerate(sync_committee.pubkeys) if pubkey == target_pubkey
]
- return set(
- [
- SubnetID(index // (SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT))
- for index in sync_committee_indices
- ]
- )
+ return {
+ SubnetID(index // (SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT))
+ for index in sync_committee_indices
+ }
- name: compute_subscribed_subnet#phase0
@@ -1678,7 +1676,7 @@
- file: packages/state-transition/src/util/weakSubjectivity.ts
search: export function computeWeakSubjectivityPeriod(
spec: |
-
+
def compute_weak_subjectivity_period(state: BeaconState) -> uint64:
"""
Returns the weak subjectivity period for the current ``state``.
@@ -1696,7 +1694,7 @@
Delta = MAX_DEPOSITS * SLOTS_PER_EPOCH
D = SAFETY_DECAY
- if T * (200 + 3 * D) < t * (200 + 12 * D):
+ if t * (200 + 12 * D) > T * (200 + 3 * D):
epochs_for_validator_set_churn = (
N * (t * (200 + 12 * D) - T * (200 + 3 * D)) // (600 * delta * (2 * t + T))
)
@@ -2012,12 +2010,10 @@
search: '^\s+nodeIsViableForHead\(node:'
regex: true
spec: |
-
+
def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconBlock]) -> bool:
block = store.blocks[block_root]
- children = [
- root for root in store.blocks.keys() if store.blocks[root].parent_root == block_root
- ]
+ children = [root for root in store.blocks if store.blocks[root].parent_root == block_root]
# If any children branches contain expected finalized/justified checkpoints,
# add to filtered block-tree and signal viability to parent.
@@ -2083,7 +2079,7 @@
- name: find_latest_confirmed_descendant#phase0
sources: []
spec: |
-
+
def find_latest_confirmed_descendant(
fcr_store: FastConfirmationStore, latest_confirmed_root: Root
) -> Root:
@@ -2092,7 +2088,7 @@
starting from ``latest_confirmed_root``.
"""
store = fcr_store.store
- head = get_head(store)
+ head = get_head(store).root
current_epoch = get_current_store_epoch(store)
confirmed_root = latest_confirmed_root
@@ -2127,7 +2123,11 @@
# The algorithm can only rely on the previous head
# if it is a descendant of the block that is attempted to be confirmed
- if not is_ancestor(store, fcr_store.previous_slot_head, block_root):
+ if not is_ancestor(
+ store,
+ get_node_for_root(fcr_store.previous_slot_head),
+ get_node_for_root(block_root),
+ ):
break
if not is_one_confirmed(store, get_current_balance_source(fcr_store), block_root):
@@ -2309,37 +2309,29 @@
search: '^\s+getAncestor\('
regex: true
spec: |
-
- def get_ancestor(store: Store, root: Root, slot: Slot) -> Root:
- block = store.blocks[root]
+
+ def get_ancestor(store: Store, node: ForkChoiceNode, slot: Slot) -> ForkChoiceNode:
+ block = store.blocks[node.root]
if block.slot > slot:
- return get_ancestor(store, block.parent_root, slot)
- return root
+ parent = ForkChoiceNode(root=block.parent_root)
+ return get_ancestor(store, parent, slot)
+ return node
- name: get_ancestor#gloas
sources: []
spec: |
-
- def get_ancestor(store: Store, root: Root, slot: Slot) -> ForkChoiceNode:
- """
- Returns the beacon block root and the payload status of the ancestor of the beacon block
- with ``root`` at ``slot``. If the beacon block with ``root`` is already at ``slot`` or we are
- requesting an ancestor "in the future", it returns ``PAYLOAD_STATUS_PENDING``.
- """
- block = store.blocks[root]
- if block.slot <= slot:
- return ForkChoiceNode(root=root, payload_status=PAYLOAD_STATUS_PENDING)
-
- parent = store.blocks[block.parent_root]
- while parent.slot > slot:
- block = parent
- parent = store.blocks[block.parent_root]
-
- return ForkChoiceNode(
- root=block.parent_root,
- payload_status=get_parent_payload_status(store, block),
- )
+
+ def get_ancestor(store: Store, node: ForkChoiceNode, slot: Slot) -> ForkChoiceNode:
+ block = store.blocks[node.root]
+ if block.slot > slot:
+ # [Modified in Gloas:EIP7732]
+ parent = ForkChoiceNode(
+ root=block.parent_root,
+ payload_status=get_parent_payload_status(store, block),
+ )
+ return get_ancestor(store, parent, slot)
+ return node
- name: get_ancestor_roots#phase0
@@ -2582,8 +2574,8 @@
- name: get_attestation_score#phase0
sources: []
spec: |
-
- def get_attestation_score(store: Store, root: Root, state: BeaconState) -> Gwei:
+
+ def get_attestation_score(store: Store, node: ForkChoiceNode, state: BeaconState) -> Gwei:
unslashed_and_active_indices = [
i
for i in get_active_validator_indices(state, get_current_epoch(state))
@@ -2596,8 +2588,7 @@
if (
i in store.latest_messages
and i not in store.equivocating_indices
- and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot)
- == root
+ and is_ancestor(store, get_supported_node(store, store.latest_messages[i]), node)
)
)
)
@@ -2606,15 +2597,8 @@
- name: get_attestation_score#gloas
sources: []
spec: |
-
- def get_attestation_score(
- store: Store,
- # [Modified in Gloas:EIP7732]
- # Removed `root`
- # [New in Gloas:EIP7732]
- node: ForkChoiceNode,
- state: BeaconState,
- ) -> Gwei:
+
+ def get_attestation_score(store: Store, node: ForkChoiceNode, state: BeaconState) -> Gwei:
unslashed_and_active_indices = [
i
for i in get_active_validator_indices(state, get_current_epoch(state))
@@ -2627,8 +2611,7 @@
if (
i in store.latest_messages
and i not in store.equivocating_indices
- # [Modified in Gloas:EIP7732]
- and is_supporting_vote(store, node, store.latest_messages[i])
+ and is_ancestor(store, get_supported_node(store, store.latest_messages[i]), node)
)
)
)
@@ -2665,13 +2648,13 @@
- file: packages/state-transition/src/cache/epochCache.ts
search: "getAttestingIndices(fork: ForkSeq, attestation: Attestation)"
spec: |
-
+
def get_attesting_indices(state: BeaconState, attestation: Attestation) -> Set[ValidatorIndex]:
"""
Return the set of attesting indices corresponding to ``data`` and ``bits``.
"""
committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index)
- return set(index for i, index in enumerate(committee) if attestation.aggregation_bits[i])
+ return {index for i, index in enumerate(committee) if attestation.aggregation_bits[i]}
- name: get_attesting_indices#electra
@@ -2679,7 +2662,7 @@
- file: packages/state-transition/src/cache/epochCache.ts
search: "getAttestingIndices(fork: ForkSeq, attestation: Attestation)"
spec: |
-
+
def get_attesting_indices(state: BeaconState, attestation: Attestation) -> Set[ValidatorIndex]:
"""
Return the set of attesting indices corresponding to ``aggregation_bits`` and ``committee_bits``.
@@ -2689,11 +2672,11 @@
committee_offset = 0
for committee_index in committee_indices:
committee = get_beacon_committee(state, attestation.data.slot, committee_index)
- committee_attesters = set(
+ committee_attesters = {
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)
@@ -2841,6 +2824,26 @@
return compute_proposer_indices(state, epoch, seed, indices)
+- name: get_beacon_proposer_indices#gloas
+ sources: []
+ spec: |
+
+ def get_beacon_proposer_indices(
+ state: BeaconState, epoch: Epoch
+ ) -> Vector[ValidatorIndex, SLOTS_PER_EPOCH]:
+ """
+ Return the proposer indices for the given ``epoch``.
+ """
+ # [Modified in Gloas:EIP8045]
+ indices = [
+ index
+ for index in get_active_validator_indices(state, epoch)
+ if not state.validators[index].slashed
+ ]
+ seed = get_seed(state, epoch, DOMAIN_BEACON_PROPOSER)
+ return compute_proposer_indices(state, epoch, seed, indices)
+
+
- name: get_blob_parameters#fulu
sources:
- file: packages/config/src/forkConfig/index.ts
@@ -3084,25 +3087,28 @@
- name: get_checkpoint_block#phase0
sources: []
spec: |
-
+
def get_checkpoint_block(store: Store, root: Root, epoch: Epoch) -> Root:
"""
Compute the checkpoint block for epoch ``epoch`` in the chain of block ``root``
"""
epoch_first_slot = compute_start_slot_at_epoch(epoch)
- return get_ancestor(store, root, epoch_first_slot)
+ node = ForkChoiceNode(root=root)
+ return get_ancestor(store, node, epoch_first_slot).root
- name: get_checkpoint_block#gloas
sources: []
spec: |
-
+
def get_checkpoint_block(store: Store, root: Root, epoch: Epoch) -> Root:
"""
Compute the checkpoint block for epoch ``epoch`` in the chain of block ``root``
"""
epoch_first_slot = compute_start_slot_at_epoch(epoch)
- return get_ancestor(store, root, epoch_first_slot).root
+ # [Modified in Gloas:EIP7732]
+ node = ForkChoiceNode(root=root, payload_status=PAYLOAD_STATUS_PENDING)
+ return get_ancestor(store, node, epoch_first_slot).root
- name: get_checkpoint_for_block#phase0
@@ -3307,12 +3313,12 @@
- name: get_current_target#phase0
sources: []
spec: |
-
+
def get_current_target(store: Store) -> Checkpoint:
"""
Return current epoch target.
"""
- head = get_head(store)
+ head = get_head(store).root
current_epoch = get_current_store_epoch(store)
return get_checkpoint_for_block(store, head, current_epoch)
@@ -3570,6 +3576,40 @@
)
+- name: get_dependent_root#phase0
+ sources: []
+ spec: |
+
+ def get_dependent_root(store: Store, root: Root) -> Root:
+ epoch = get_current_store_epoch(store)
+ if epoch <= MIN_SEED_LOOKAHEAD:
+ # Genesis block parent
+ return Root()
+
+ node = ForkChoiceNode(root=root)
+ dependent_slot = Slot(compute_start_slot_at_epoch(epoch - MIN_SEED_LOOKAHEAD) - 1)
+ return get_ancestor(store, node, dependent_slot).root
+
+
+- name: get_dependent_root#gloas
+ sources: []
+ spec: |
+
+ def get_dependent_root(store: Store, root: Root) -> Root:
+ epoch = get_current_store_epoch(store)
+ if epoch <= MIN_SEED_LOOKAHEAD:
+ # Genesis block parent
+ return Root()
+
+ # [Modified in Gloas:EIP7732]
+ node = ForkChoiceNode(
+ root=root,
+ payload_status=PAYLOAD_STATUS_PENDING,
+ )
+ dependent_slot = Slot(compute_start_slot_at_epoch(epoch - MIN_SEED_LOOKAHEAD) - 1)
+ return get_ancestor(store, node, dependent_slot).root
+
+
- name: get_domain#phase0
sources:
- file: packages/config/src/genesisConfig/index.ts
@@ -4141,31 +4181,32 @@
search: '^\s+getHead\(\):'
regex: true
spec: |
-
- def get_head(store: Store) -> Root:
+
+ def get_head(store: Store) -> ForkChoiceNode:
# Get filtered block tree that only includes viable branches
blocks = get_filtered_block_tree(store)
# Execute the LMD-GHOST fork choice
- head = store.justified_checkpoint.root
+ head = ForkChoiceNode(root=store.justified_checkpoint.root)
while True:
- children = [root for root in blocks.keys() if blocks[root].parent_root == head]
+ children = get_node_children(store, blocks, head)
if len(children) == 0:
return head
# Sort by latest attesting balance with ties broken lexicographically
# Ties broken by favoring block with lexicographically higher root
- head = max(children, key=lambda root: (get_weight(store, root), root))
+ head = max(children, key=lambda child: (get_weight(store, child), child.root))
- name: get_head#gloas
sources: []
spec: |
-
+
def get_head(store: Store) -> ForkChoiceNode:
# Get filtered block tree that only includes viable branches
blocks = get_filtered_block_tree(store)
# Execute the LMD-GHOST fork-choice
head = ForkChoiceNode(
root=store.justified_checkpoint.root,
+ # [New in Gloas:EIP7732]
payload_status=PAYLOAD_STATUS_PENDING,
)
@@ -4179,6 +4220,7 @@
key=lambda child: (
get_weight(store, child),
child.root,
+ # [New in Gloas:EIP7732]
get_payload_status_tiebreaker(store, child),
),
)
@@ -4344,7 +4386,7 @@
- name: get_inclusion_list_committee#heze
sources: []
spec: |
-
+
def get_inclusion_list_committee(
state: BeaconState, slot: Slot
) -> Vector[ValidatorIndex, INCLUSION_LIST_COMMITTEE_SIZE]:
@@ -4358,9 +4400,9 @@
for i in range(committees_per_slot):
committee = get_beacon_committee(state, slot, CommitteeIndex(i))
indices.extend(committee)
- return Vector[ValidatorIndex, INCLUSION_LIST_COMMITTEE_SIZE](
- [indices[i % len(indices)] for i in range(INCLUSION_LIST_COMMITTEE_SIZE)]
- )
+ return Vector[ValidatorIndex, INCLUSION_LIST_COMMITTEE_SIZE]([
+ indices[i % len(indices)] for i in range(INCLUSION_LIST_COMMITTEE_SIZE)
+ ])
- name: get_inclusion_list_committee_assignment#heze
@@ -4515,7 +4557,7 @@
- name: get_latest_confirmed#phase0
sources: []
spec: |
-
+
def get_latest_confirmed(fcr_store: FastConfirmationStore) -> Root:
"""
Return the most recent confirmed block by executing the FCR algorithm.
@@ -4529,10 +4571,10 @@
# 2) the latest confirmed block does not belong to the canonical chain,
# 3) the confirmed chain starting from the current epoch observed justified checkpoint
# cannot be re-confirmed at the start of the current epoch.
- head = get_head(store)
+ head = get_head(store).root
if (
get_block_epoch(store, confirmed_root) + 1 < current_epoch
- or not is_ancestor(store, head, confirmed_root)
+ or not is_ancestor(store, get_node_for_root(head), get_node_for_root(confirmed_root))
or (
is_start_slot_at_epoch(get_current_slot(store))
and not is_confirmed_chain_safe(fcr_store, confirmed_root)
@@ -4867,10 +4909,22 @@
)
+- name: get_node_children#phase0
+ sources: []
+ spec: |
+
+ def get_node_children(
+ store: Store, # noqa: ARG001
+ blocks: Dict[Root, BeaconBlock],
+ node: ForkChoiceNode,
+ ) -> Sequence[ForkChoiceNode]:
+ return [ForkChoiceNode(root=root) for root in blocks if blocks[root].parent_root == node.root]
+
+
- name: get_node_children#gloas
sources: []
spec: |
-
+
def get_node_children(
store: Store, blocks: Dict[Root, BeaconBlock], node: ForkChoiceNode
) -> Sequence[ForkChoiceNode]:
@@ -4882,7 +4936,7 @@
else:
return [
ForkChoiceNode(root=root, payload_status=PAYLOAD_STATUS_PENDING)
- for root in blocks.keys()
+ for root in blocks
if (
blocks[root].parent_root == node.root
and node.payload_status == get_parent_payload_status(store, blocks[root])
@@ -4890,6 +4944,23 @@
]
+- name: get_node_for_root#phase0
+ sources: []
+ spec: |
+
+ def get_node_for_root(block_root: Root) -> ForkChoiceNode:
+ return ForkChoiceNode(root=block_root)
+
+
+- name: get_node_for_root#gloas
+ sources: []
+ spec: |
+
+ def get_node_for_root(block_root: Root) -> ForkChoiceNode:
+ # [Modified in Gloas:EIP7732]
+ return ForkChoiceNode(root=block_root, payload_status=PAYLOAD_STATUS_PENDING)
+
+
- name: get_parent_payload_status#gloas
sources: []
spec: |
@@ -4934,19 +5005,18 @@
- name: get_payload_status_tiebreaker#gloas
sources: []
spec: |
-
+
def get_payload_status_tiebreaker(store: Store, node: ForkChoiceNode) -> uint8:
- if node.payload_status == PAYLOAD_STATUS_PENDING or store.blocks[
- node.root
- ].slot + 1 != get_current_slot(store):
- return node.payload_status
- else:
+ if is_previous_slot_payload_decision(store, node):
# To decide on a payload from the previous slot, choose
# between FULL and EMPTY based on `should_extend_payload`
if node.payload_status == PAYLOAD_STATUS_EMPTY:
return 1
- else:
- return 2 if should_extend_payload(store, node.root) else 0
+ if should_extend_payload(store, node.root):
+ return 2
+ return 0
+ else:
+ return node.payload_status
- name: get_pending_balance_to_withdraw#electra
@@ -5103,7 +5173,7 @@
- file: packages/fork-choice/src/forkChoice/forkChoice.ts
search: "* Same as https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.4/specs/phase0/fork-choice.md#get_proposer_head"
spec: |
-
+
def get_proposer_head(store: Store, head_root: Root, slot: Slot) -> Root:
head_block = store.blocks[head_root]
parent_root = head_block.parent_root
@@ -5139,18 +5209,16 @@
# Re-org more aggressively if there is a proposer equivocation in the previous slot.
proposer_equivocation = is_proposer_equivocation(store, head_root)
- if all(
- [
- head_late,
- shuffling_stable,
- ffg_competitive,
- finalization_ok,
- proposing_on_time,
- single_slot_reorg,
- head_weak,
- parent_strong,
- ]
- ):
+ if all([
+ head_late,
+ shuffling_stable,
+ ffg_competitive,
+ finalization_ok,
+ proposing_on_time,
+ single_slot_reorg,
+ head_weak,
+ parent_strong,
+ ]):
# We can re-org the current head by building upon its parent block.
return parent_root
elif all([head_weak, current_time_ok, proposer_equivocation]):
@@ -5246,12 +5314,12 @@
- name: get_pulled_up_head_state#phase0
sources: []
spec: |
-
+
def get_pulled_up_head_state(store: Store) -> BeaconState:
"""
Return the state of the head pulled up to the current epoch if needed.
"""
- head = get_head(store)
+ head = get_head(store).root
head_state = store.block_states[head]
if get_current_epoch(head_state) < get_current_store_epoch(store):
pulled_up_state = copy(head_state)
@@ -5274,6 +5342,25 @@
return state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR]
+- name: get_safe_execution_block_hash#bellatrix
+ sources: []
+ spec: |
+
+ def get_safe_execution_block_hash(fcr_store: FastConfirmationStore) -> Hash32:
+ safe_block = fcr_store.store.blocks[fcr_store.confirmed_root]
+ return safe_block.body.execution_payload.block_hash
+
+
+- name: get_safe_execution_block_hash#gloas
+ sources: []
+ spec: |
+
+ def get_safe_execution_block_hash(fcr_store: FastConfirmationStore) -> Hash32:
+ safe_block = fcr_store.store.blocks[fcr_store.confirmed_root]
+ # [Modified in Gloas:EIP7732]
+ return safe_block.body.signed_execution_payload_bid.message.parent_block_hash
+
+
- name: get_safety_threshold#altair
sources:
- file: packages/state-transition/src/lightClient/spec/utils.ts
@@ -5309,12 +5396,12 @@
- name: get_slot_committee#phase0
sources: []
spec: |
-
+
def get_slot_committee(store: Store, slot: Slot) -> Set[ValidatorIndex]:
"""
Return participants of all committees in ``slot``.
"""
- head = get_head(store)
+ head = get_head(store).root
shuffling_source = store.block_states[head]
committees_count = get_committee_count_per_slot(shuffling_source, compute_epoch_at_slot(slot))
participants: Set[ValidatorIndex] = set()
@@ -5391,6 +5478,42 @@
return compute_empty_slot_support_discount(store, balance_source, block_root)
+- name: get_supported_node#phase0
+ sources: []
+ spec: |
+
+ def get_supported_node(
+ store: Store, # noqa: ARG001
+ message: LatestMessage,
+ ) -> ForkChoiceNode:
+ """
+ Return a node supported by the ``message``.
+ """
+ return ForkChoiceNode(root=message.root)
+
+
+- name: get_supported_node#gloas
+ sources: []
+ spec: |
+
+ def get_supported_node(store: Store, message: LatestMessage) -> ForkChoiceNode:
+ """
+ Return a node supported by the ``message``.
+ """
+ # [New in Gloas:EIP7732]
+ block = store.blocks[message.root]
+ if block.slot < message.slot:
+ if message.payload_present:
+ payload_status = PAYLOAD_STATUS_FULL
+ else:
+ payload_status = PAYLOAD_STATUS_EMPTY
+ else:
+ payload_status = PAYLOAD_STATUS_PENDING
+
+ # [Modified in Gloas:EIP7732]
+ return ForkChoiceNode(root=message.root, payload_status=payload_status)
+
+
- name: get_sync_committee_message#altair
sources:
- file: packages/validator/src/services/validatorStore.ts
@@ -5492,9 +5615,9 @@
- name: get_terminal_pow_block#bellatrix
sources: []
spec: |
-
+
def get_terminal_pow_block(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlock]:
- if TERMINAL_BLOCK_HASH != Hash32():
+ if TERMINAL_BLOCK_HASH != EMPTY_BLOCK_HASH:
# Terminal block hash override takes precedence over terminal total difficulty
if TERMINAL_BLOCK_HASH in pow_chain:
return pow_chain[TERMINAL_BLOCK_HASH]
@@ -5822,58 +5945,51 @@
- name: get_weight#phase0
sources: []
spec: |
-
- def get_weight(store: Store, root: Root) -> Gwei:
+
+ def get_weight(store: Store, node: ForkChoiceNode) -> Gwei:
state = store.checkpoint_states[store.justified_checkpoint]
- attestation_score = get_attestation_score(store, root, state)
+ attestation_score = get_attestation_score(store, node, state)
if store.proposer_boost_root == Root():
# Return only attestation score if ``proposer_boost_root`` is not set
return attestation_score
# Calculate proposer score if ``proposer_boost_root`` is set
proposer_score = Gwei(0)
- # Boost is applied if ``root`` is an ancestor of ``proposer_boost_root``
- if get_ancestor(store, store.proposer_boost_root, store.blocks[root].slot) == root:
+ proposer_boost_node = ForkChoiceNode(root=store.proposer_boost_root)
+ # Boost is applied if ``node`` is an ancestor of ``proposer_boost_node``
+ if is_ancestor(store, proposer_boost_node, node):
proposer_score = get_proposer_score(store)
+
return attestation_score + proposer_score
- name: get_weight#gloas
sources: []
spec: |
-
- def get_weight(
- store: Store,
+
+ def get_weight(store: Store, node: ForkChoiceNode) -> Gwei:
+ # [New in Gloas:EIP7732]
+ if is_previous_slot_payload_decision(store, node):
+ return Gwei(0)
+
+ state = store.checkpoint_states[store.justified_checkpoint]
+ attestation_score = get_attestation_score(store, node, state)
# [Modified in Gloas:EIP7732]
- node: ForkChoiceNode,
- ) -> Gwei:
- if node.payload_status == PAYLOAD_STATUS_PENDING or store.blocks[
- node.root
- ].slot + 1 != get_current_slot(store):
- state = store.checkpoint_states[store.justified_checkpoint]
- attestation_score = get_attestation_score(store, node, state)
- if not should_apply_proposer_boost(store):
- # Return only attestation score if
- # proposer boost should not apply
- return attestation_score
-
- # Calculate proposer score if `proposer_boost_root` is set
- proposer_score = Gwei(0)
-
- # `proposer_boost_root` is treated as a vote for the
- # proposer's block in the current slot. Proposer boost
- # is applied accordingly to all ancestors
- message = LatestMessage(
- slot=get_current_slot(store),
- root=store.proposer_boost_root,
- payload_present=False,
- )
- if is_supporting_vote(store, node, message):
- proposer_score = get_proposer_score(store)
+ if not should_apply_proposer_boost(store):
+ # Return only attestation score if proposer boost should not apply
+ return attestation_score
- return attestation_score + proposer_score
- else:
- return Gwei(0)
+ # Calculate proposer score if proposer boost should apply
+ proposer_score = Gwei(0)
+ # [Modified in Gloas:EIP7732]
+ proposer_boost_node = ForkChoiceNode(
+ root=store.proposer_boost_root, payload_status=PAYLOAD_STATUS_PENDING
+ )
+ # Boost is applied if ``node`` is an ancestor of ``proposer_boost_node``
+ if is_ancestor(store, proposer_boost_node, node):
+ proposer_score = get_proposer_score(store)
+
+ return attestation_score + proposer_score
- name: has_compounding_withdrawal_credential#electra
@@ -5961,7 +6077,7 @@
- file: packages/state-transition/src/util/genesis.ts
search: export function initializeBeaconStateFromEth1(
spec: |
-
+
def initialize_beacon_state_from_eth1(
eth1_block_hash: Hash32, eth1_timestamp: uint64, deposits: Sequence[Deposit]
) -> BeaconState:
@@ -5980,7 +6096,7 @@
)
# Process deposits
- leaves = list(map(lambda deposit: deposit.data, deposits))
+ leaves = [deposit.data for deposit in deposits]
for index, deposit in enumerate(deposits):
deposit_data_list = List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[: index + 1])
state.eth1_data.deposit_root = hash_tree_root(deposit_data_list)
@@ -6214,12 +6330,25 @@
- name: is_ancestor#phase0
sources: []
spec: |
-
- def is_ancestor(store: Store, block_root: Root, ancestor_root: Root) -> bool:
- """
- Return ``True`` if ``ancestor_root`` is an ancestor of ``block_root``.
- """
- return get_ancestor(store, block_root, store.blocks[ancestor_root].slot) == ancestor_root
+
+ def is_ancestor(store: Store, node: ForkChoiceNode, ancestor: ForkChoiceNode) -> bool:
+ return get_ancestor(store, node, store.blocks[ancestor.root].slot) == ancestor
+
+
+- name: is_ancestor#gloas
+ sources: []
+ spec: |
+
+ def is_ancestor(store: Store, node: ForkChoiceNode, ancestor: ForkChoiceNode) -> bool:
+ node_ancestor = get_ancestor(store, node, store.blocks[ancestor.root].slot)
+ if node_ancestor.root != ancestor.root:
+ return False
+
+ # [New in Gloas:EIP7732]
+ return (
+ node_ancestor.payload_status == ancestor.payload_status
+ or ancestor.payload_status == PAYLOAD_STATUS_PENDING
+ )
- name: is_assigned_to_sync_committee#altair
@@ -6365,16 +6494,16 @@
- name: is_confirmed_chain_safe#phase0
sources: []
spec: |
-
+
def is_confirmed_chain_safe(fcr_store: FastConfirmationStore, confirmed_root: Root) -> bool:
"""
Return ``True`` if and only if all blocks of the confirmed chain
starting from current_epoch_observed_justified_checkpoint are LMD-GHOST safe.
"""
store = fcr_store.store
- # Check if the confirmed_root is descendant of current_epoch_observed_justified_checkpoint
- if not is_ancestor(
- store, confirmed_root, fcr_store.current_epoch_observed_justified_checkpoint.root
+ # Check if the confirmed_root has current_epoch_observed_justified_checkpoint in its chain
+ if fcr_store.current_epoch_observed_justified_checkpoint != get_checkpoint_for_block(
+ store, confirmed_root, fcr_store.current_epoch_observed_justified_checkpoint.epoch
):
return False
@@ -6387,8 +6516,10 @@
# Limit reconfirmation to the first block of the previous epoch
# as if it is successful, reconfirmation of the ancestors is implied.
ancestor_at_previous_epoch_start = get_ancestor(
- store, confirmed_root, compute_start_slot_at_epoch(Epoch(current_epoch - 1))
- )
+ store,
+ get_node_for_root(confirmed_root),
+ compute_start_slot_at_epoch(Epoch(current_epoch - 1)),
+ ).root
if get_block_epoch(store, ancestor_at_previous_epoch_start) + 1 == current_epoch:
# The parent of the first block of the previous epoch
start_root_exclusive = store.blocks[ancestor_at_previous_epoch_start].parent_root
@@ -6691,11 +6822,11 @@
- file: packages/fork-choice/src/forkChoice/forkChoice.ts
search: "// https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.4/specs/phase0/fork-choice.md#is_head_weak"
spec: |
-
+
def is_head_weak(store: Store, head_root: Root) -> bool:
justified_state = store.checkpoint_states[store.justified_checkpoint]
reorg_threshold = calculate_committee_fraction(justified_state, REORG_HEAD_WEIGHT_THRESHOLD)
- head_weight = get_weight(store, head_root)
+ head_weight = get_weight(store, ForkChoiceNode(root=head_root))
return head_weight < reorg_threshold
@@ -6740,7 +6871,7 @@
- name: is_inclusion_list_bits_inclusive#heze
sources: []
spec: |
-
+
def is_inclusion_list_bits_inclusive(
store: InclusionListStore,
state: BeaconState,
@@ -6757,7 +6888,7 @@
return all(
inclusion_bit or not local_inclusion_bit
for inclusion_bit, local_inclusion_bit in zip(
- inclusion_list_bits, local_inclusion_list_bits
+ inclusion_list_bits, local_inclusion_list_bits, strict=True
)
)
@@ -6791,7 +6922,7 @@
- name: is_non_strict_superset#phase0
sources: []
spec: |
-
+
def is_non_strict_superset(
seen_bits_set: Set[Tuple[boolean, ...]],
new_bits: Tuple[boolean, ...],
@@ -6802,7 +6933,7 @@
"""
for prior_bits in seen_bits_set:
is_superset = True
- for prior_bit, new_bit in zip(prior_bits, new_bits):
+ for prior_bit, new_bit in zip(prior_bits, new_bits, strict=True):
if new_bit and not prior_bit:
is_superset = False
break
@@ -6814,12 +6945,12 @@
- name: is_one_confirmed#phase0
sources: []
spec: |
-
+
def is_one_confirmed(store: Store, balance_source: BeaconState, block_root: Root) -> bool:
"""
Return ``True`` if and only if the block is LMD-GHOST safe.
"""
- support = get_attestation_score(store, block_root, balance_source)
+ support = get_attestation_score(store, get_node_for_root(block_root), balance_source)
safety_threshold = compute_safety_threshold(store, block_root, balance_source)
return support > safety_threshold
@@ -6861,12 +6992,12 @@
- file: packages/fork-choice/src/forkChoice/forkChoice.ts
search: "// https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/phase0/fork-choice.md#is_parent_strong"
spec: |
-
+
def is_parent_strong(store: Store, root: Root) -> bool:
justified_state = store.checkpoint_states[store.justified_checkpoint]
parent_threshold = calculate_committee_fraction(justified_state, REORG_PARENT_WEIGHT_THRESHOLD)
parent_root = store.blocks[root].parent_root
- parent_weight = get_weight(store, parent_root)
+ parent_weight = get_weight(store, ForkChoiceNode(root=parent_root))
return parent_weight > parent_threshold
@@ -6978,6 +7109,16 @@
return False
+- name: is_previous_slot_payload_decision#gloas
+ sources: []
+ spec: |
+
+ def is_previous_slot_payload_decision(store: Store, node: ForkChoiceNode) -> bool:
+ is_previous_slot = store.blocks[node.root].slot + 1 == get_current_slot(store)
+ is_payload_decision = node.payload_status in [PAYLOAD_STATUS_EMPTY, PAYLOAD_STATUS_FULL]
+ return is_previous_slot and is_payload_decision
+
+
- name: is_proposer#phase0
sources: []
spec: |
@@ -7071,34 +7212,6 @@
return compute_slots_since_epoch_start(slot) == 0
-- name: is_supporting_vote#gloas
- sources: []
- spec: |
-
- def is_supporting_vote(store: Store, node: ForkChoiceNode, message: LatestMessage) -> bool:
- """
- Returns whether the vote ``message`` supports the chain containing the
- forkchoice node ``node``.
- """
- block = store.blocks[node.root]
- if node.root == message.root:
- if node.payload_status == PAYLOAD_STATUS_PENDING:
- return True
- assert message.slot >= block.slot
- if message.slot == block.slot:
- return False
- if message.payload_present:
- return node.payload_status == PAYLOAD_STATUS_FULL
- else:
- return node.payload_status == PAYLOAD_STATUS_EMPTY
- else:
- ancestor = get_ancestor(store, message.root, block.slot)
- return node.root == ancestor.root and (
- node.payload_status == PAYLOAD_STATUS_PENDING
- or node.payload_status == ancestor.payload_status
- )
-
-
- name: is_sync_committee_aggregator#altair
sources:
- file: packages/state-transition/src/util/aggregator.ts
@@ -7182,7 +7295,7 @@
- file: packages/state-transition/src/block/isValidIndexedAttestation.ts
search: export function isValidIndexedAttestation(
spec: |
-
+
def is_valid_indexed_attestation(
state: BeaconState, indexed_attestation: IndexedAttestation
) -> bool:
@@ -7191,7 +7304,7 @@
"""
# Verify indices are sorted and unique
indices = indexed_attestation.attesting_indices
- if len(indices) == 0 or not indices == sorted(set(indices)):
+ if len(indices) == 0 or indices != sorted(set(indices)):
return False
# Verify aggregate signature
pubkeys = [state.validators[i].pubkey for i in indices]
@@ -7205,7 +7318,7 @@
- file: packages/state-transition/src/block/isValidIndexedPayloadAttestation.ts
search: export function isValidIndexedPayloadAttestation(
spec: |
-
+
def is_valid_indexed_payload_attestation(
state: BeaconState, attestation: IndexedPayloadAttestation
) -> bool:
@@ -7215,7 +7328,7 @@
"""
# Verify indices are non-empty and sorted
indices = attestation.attesting_indices
- if len(indices) == 0 or not indices == sorted(indices):
+ if len(indices) == 0 or indices != sorted(indices):
return False
# Verify aggregate signature
@@ -7630,7 +7743,7 @@
search: '^\s+onBlock\('
regex: true
spec: |
-
+
def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
block = signed_block.message
# Parent block must be known
@@ -7654,14 +7767,17 @@
# Check the block is valid and compute the post-state
state = pre_state.copy()
block_root = hash_tree_root(block)
- state_transition(state, signed_block, True)
+ state_transition(state, signed_block, validate_result=True)
+
+ # Compute head before applying the block
+ head = get_head(store)
# Add new block to the store
store.blocks[block_root] = block
# Add new state for this block to the store
store.block_states[block_root] = state
record_block_timeliness(store, block_root)
- update_proposer_boost_root(store, block_root)
+ update_proposer_boost_root(store, head.root, block_root)
# Update checkpoints in store if necessary
update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint)
@@ -7676,7 +7792,7 @@
search: '^\s+onBlock\('
regex: true
spec: |
-
+
def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
"""
Run ``on_block`` upon receiving a new block.
@@ -7706,19 +7822,21 @@
# Check the block is valid and compute the post-state
state = pre_state.copy()
block_root = hash_tree_root(block)
- state_transition(state, signed_block, True)
+ state_transition(state, signed_block, validate_result=True)
# [New in Bellatrix]
if is_merge_transition_block(pre_state, block.body):
validate_merge_block(block)
+ # Compute head before applying the block
+ head = get_head(store)
# Add new block to the store
store.blocks[block_root] = block
# Add new state for this block to the store
store.block_states[block_root] = state
record_block_timeliness(store, block_root)
- update_proposer_boost_root(store, block_root)
+ update_proposer_boost_root(store, head.root, block_root)
# Update checkpoints in store if necessary
update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint)
@@ -7733,7 +7851,7 @@
search: '^\s+onBlock\('
regex: true
spec: |
-
+
def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
"""
Run ``on_block`` upon receiving a new block.
@@ -7759,15 +7877,17 @@
# Make a copy of the state to avoid mutability issues
state = copy(store.block_states[block.parent_root])
block_root = hash_tree_root(block)
- state_transition(state, signed_block, True)
+ state_transition(state, signed_block, validate_result=True)
+ # Compute head before applying the block
+ head = get_head(store)
# Add new block to the store
store.blocks[block_root] = block
# Add new state for this block to the store
store.block_states[block_root] = state
record_block_timeliness(store, block_root)
- update_proposer_boost_root(store, block_root)
+ update_proposer_boost_root(store, head.root, block_root)
# Update checkpoints in store if necessary
update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint)
@@ -7782,7 +7902,7 @@
search: '^\s+onBlock\('
regex: true
spec: |
-
+
def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
"""
Run ``on_block`` upon receiving a new block.
@@ -7813,15 +7933,17 @@
# Make a copy of the state to avoid mutability issues
state = copy(store.block_states[block.parent_root])
block_root = hash_tree_root(block)
- state_transition(state, signed_block, True)
+ state_transition(state, signed_block, validate_result=True)
+ # Compute head before applying the block
+ head = get_head(store)
# Add new block to the store
store.blocks[block_root] = block
# Add new state for this block to the store
store.block_states[block_root] = state
record_block_timeliness(store, block_root)
- update_proposer_boost_root(store, block_root)
+ update_proposer_boost_root(store, head.root, block_root)
# Update checkpoints in store if necessary
update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint)
@@ -7836,7 +7958,7 @@
search: '^\s+onBlock\('
regex: true
spec: |
-
+
def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
"""
Run ``on_block`` upon receiving a new block.
@@ -7867,15 +7989,17 @@
# Check the block is valid and compute the post-state
block_root = hash_tree_root(block)
- state_transition(state, signed_block, True)
+ state_transition(state, signed_block, validate_result=True)
+ # Compute head before applying the block
+ head = get_head(store)
# Add new block to the store
store.blocks[block_root] = block
# Add new state for this block to the store
store.block_states[block_root] = state
record_block_timeliness(store, block_root)
- update_proposer_boost_root(store, block_root)
+ update_proposer_boost_root(store, head.root, block_root)
# Update checkpoints in store if necessary
update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint)
@@ -7887,7 +8011,7 @@
- name: on_block#gloas
sources: []
spec: |
-
+
def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
"""
Run ``on_block`` upon receiving a new block.
@@ -7921,8 +8045,10 @@
# Check the block is valid and compute the post-state
block_root = hash_tree_root(block)
- state_transition(state, signed_block, True)
+ state_transition(state, signed_block, validate_result=True)
+ # Compute head before applying the block
+ head = get_head(store)
# Add new block to the store
store.blocks[block_root] = block
# Add new state for this block to the store
@@ -7935,7 +8061,7 @@
notify_ptc_messages(store, state, block.body.payload_attestations)
record_block_timeliness(store, block_root)
- update_proposer_boost_root(store, block_root)
+ update_proposer_boost_root(store, head.root, block_root)
# Update checkpoints in store if necessary
update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint)
@@ -8227,7 +8353,7 @@
- file: packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts
search: export async function prepareExecutionPayload(
spec: |
-
+
def prepare_execution_payload(
state: BeaconState,
safe_block_hash: Hash32,
@@ -8238,7 +8364,7 @@
) -> Optional[PayloadId]:
if not is_merge_transition_complete(state):
assert pow_chain is not None
- is_terminal_block_hash_set = TERMINAL_BLOCK_HASH != Hash32()
+ is_terminal_block_hash_set = TERMINAL_BLOCK_HASH != EMPTY_BLOCK_HASH
is_activation_epoch_reached = (
get_current_epoch(state) >= TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH
)
@@ -8607,7 +8733,7 @@
- file: packages/state-transition/src/block/processAttestationsAltair.ts
search: export function processAttestationsAltair(
spec: |
-
+
def process_attestation(state: BeaconState, attestation: Attestation) -> None:
data = attestation.data
assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state))
@@ -8621,11 +8747,11 @@
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(
+ committee_attesters = {
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)
@@ -8666,7 +8792,7 @@
- name: process_attestation#gloas
sources: []
spec: |
-
+
def process_attestation(state: BeaconState, attestation: Attestation) -> None:
data = attestation.data
assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state))
@@ -8680,11 +8806,11 @@
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(
+ committee_attesters = {
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)
@@ -8858,6 +8984,23 @@
process_sync_aggregate(state, block.body.sync_aggregate)
+- name: process_block#fulu
+ sources:
+ - file: packages/state-transition/src/block/index.ts
+ search: export function processBlock(
+ spec: |
+
+ def process_block(state: BeaconState, block: BeaconBlock) -> None:
+ process_block_header(state, block)
+ process_withdrawals(state, block.body.execution_payload)
+ process_execution_payload(state, block.body, EXECUTION_ENGINE)
+ process_randao(state, block.body)
+ process_eth1_data(state, block.body)
+ # [Modified in Fulu]
+ process_operations(state, block.body)
+ process_sync_aggregate(state, block.body.sync_aggregate)
+
+
- name: process_block#gloas
sources: []
spec: |
@@ -9103,13 +9246,30 @@
- file: packages/state-transition/src/block/processDepositRequest.ts
search: export function processDepositRequest(
spec: |
-
+
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,
+ )
+ )
+
+
+- name: process_deposit_request#fulu
+ sources:
+ - file: packages/state-transition/src/block/processDepositRequest.ts
+ search: export function processDepositRequest(
+ spec: |
+
+ def process_deposit_request(state: BeaconState, deposit_request: DepositRequest) -> None:
state.pending_deposits.append(
PendingDeposit(
pubkey=deposit_request.pubkey,
@@ -10114,21 +10274,38 @@
for_ops(body.execution_requests.consolidations, process_consolidation_request)
+- name: process_operations#fulu
+ sources:
+ - file: packages/state-transition/src/block/processOperations.ts
+ search: export function processOperations(
+ spec: |
+
+ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
+ # [Modified in Fulu]
+ 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 Fulu]
+ # Removed `process_deposit`
+ for_ops(body.voluntary_exits, process_voluntary_exit)
+ for_ops(body.bls_to_execution_changes, process_bls_to_execution_change)
+ for_ops(body.execution_requests.deposits, process_deposit_request)
+ for_ops(body.execution_requests.withdrawals, process_withdrawal_request)
+ for_ops(body.execution_requests.consolidations, process_consolidation_request)
+
+
- name: process_operations#gloas
sources: []
spec: |
-
+
def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
- # 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
+ assert len(body.deposits) == 0
def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None:
for operation in operations:
@@ -10139,7 +10316,6 @@
for_ops(body.attester_slashings, process_attester_slashing)
# [Modified in Gloas:EIP7732]
for_ops(body.attestations, process_attestation)
- for_ops(body.deposits, process_deposit)
# [Modified in Gloas:EIP7732]
for_ops(body.voluntary_exits, process_voluntary_exit)
for_ops(body.bls_to_execution_changes, process_bls_to_execution_change)
@@ -10322,17 +10498,17 @@
state.deposit_balance_to_consume = Gwei(0)
-- name: process_pending_deposits#gloas
+- name: process_pending_deposits#fulu
sources:
- file: packages/state-transition/src/epoch/processPendingDeposits.ts
search: export function processPendingDeposits(
spec: |
-
+
def process_pending_deposits(state: BeaconState) -> None:
next_epoch = Epoch(get_current_epoch(state) + 1)
- # [Modified in Gloas:EIP8061]
- # Deposits still consume the activation-only churn budget in Gloas.
- available_for_processing = state.deposit_balance_to_consume + get_activation_churn_limit(state)
+ available_for_processing = state.deposit_balance_to_consume + get_activation_exit_churn_limit(
+ state
+ )
processed_amount = 0
next_deposit_index = 0
deposits_to_postpone = []
@@ -10340,16 +10516,6 @@
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
@@ -10395,11 +10561,74 @@
state.deposit_balance_to_consume = Gwei(0)
-- name: process_proposer_lookahead#fulu
+- name: process_pending_deposits#gloas
sources:
- - file: packages/state-transition/src/epoch/processProposerLookahead.ts
- search: export function processProposerLookahead(
- spec: |
+ - file: packages/state-transition/src/epoch/processPendingDeposits.ts
+ search: export function processPendingDeposits(
+ spec: |
+
+ def process_pending_deposits(state: BeaconState) -> None:
+ next_epoch = Epoch(get_current_epoch(state) + 1)
+ # [Modified in Gloas:EIP8061]
+ # Deposits still consume the activation-only churn budget in Gloas.
+ available_for_processing = state.deposit_balance_to_consume + get_activation_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:
+ # 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)
+
+
+- name: process_proposer_lookahead#fulu
+ sources:
+ - file: packages/state-transition/src/epoch/processProposerLookahead.ts
+ search: export function processProposerLookahead(
+ spec: |
def process_proposer_lookahead(state: BeaconState) -> None:
last_epoch_start = len(state.proposer_lookahead) - SLOTS_PER_EPOCH
@@ -10844,7 +11073,7 @@
- file: packages/state-transition/src/block/processSyncCommittee.ts
search: export function processSyncAggregate(
spec: |
-
+
def process_sync_aggregate(state: BeaconState, sync_aggregate: SyncAggregate) -> None:
# Verify sync committee aggregate signature signing over the previous slot block root
committee_pubkeys = state.current_sync_committee.pubkeys
@@ -10856,7 +11085,7 @@
# More than half participated - subtract non-participant keys.
# First determine nonparticipating members
non_participant_pubkeys = [
- pubkey for pubkey, bit in zip(committee_pubkeys, committee_bits) if not bit
+ pubkey for pubkey, bit in zip(committee_pubkeys, committee_bits, strict=True) if not bit
]
# Compute aggregate of non-participants
non_participant_aggregate = eth_aggregate_pubkeys(non_participant_pubkeys)
@@ -10871,7 +11100,9 @@
# Less than half participated - aggregate participant keys
participant_pubkeys = [
pubkey
- for pubkey, bit in zip(committee_pubkeys, sync_aggregate.sync_committee_bits)
+ for pubkey, bit in zip(
+ committee_pubkeys, sync_aggregate.sync_committee_bits, strict=True
+ )
if bit
]
previous_slot = max(state.slot, Slot(1)) - Slot(1)
@@ -10899,7 +11130,7 @@
ValidatorIndex(all_pubkeys.index(pubkey)) for pubkey in state.current_sync_committee.pubkeys
]
for participant_index, participation_bit in zip(
- committee_indices, sync_aggregate.sync_committee_bits
+ committee_indices, sync_aggregate.sync_committee_bits, strict=True
):
if participation_bit:
increase_balance(state, participant_index, participant_reward)
@@ -11305,7 +11536,7 @@
- file: packages/beacon-node/src/util/blobs.ts
search: export async function dataColumnMatrixRecovery(
spec: |
-
+
def recover_matrix(
partial_matrix: Sequence[MatrixEntry], blob_count: uint64
) -> Sequence[MatrixEntry]:
@@ -11320,7 +11551,9 @@
cell_indices = [e.column_index for e in partial_matrix if e.row_index == blob_index]
cells = [e.cell for e in partial_matrix if e.row_index == blob_index]
recovered_cells, recovered_proofs = recover_cells_and_kzg_proofs(cell_indices, cells)
- for cell_index, (cell, proof) in enumerate(zip(recovered_cells, recovered_proofs)):
+ for cell_index, (cell, proof) in enumerate(
+ zip(recovered_cells, recovered_proofs, strict=True)
+ ):
matrix.append(
MatrixEntry(
cell=cell,
@@ -11411,12 +11644,18 @@
- name: should_build_on_full#gloas
sources: []
spec: |
-
+
def should_build_on_full(store: Store, head: ForkChoiceNode) -> bool:
assert head.payload_status != PAYLOAD_STATUS_PENDING
if head.payload_status == PAYLOAD_STATUS_EMPTY:
return False
- return not payload_data_availability(store, head.root, available=False)
+ if store.blocks[head.root].slot + 1 != get_current_slot(store):
+ return True
+ if payload_data_availability(store, head.root, available=False):
+ return False
+ if payload_timeliness(store, head.root, timely=False):
+ return False
+ return True
- name: should_extend_payload#gloas
@@ -11458,71 +11697,6 @@
)
-- name: should_override_forkchoice_update#bellatrix
- sources:
- - file: packages/fork-choice/src/forkChoice/forkChoice.ts
- search: "// See https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/bellatrix/fork-choice.md#should_override_forkchoice_update"
- spec: |
-
- def should_override_forkchoice_update(store: Store, head_root: Root) -> bool:
- head_block = store.blocks[head_root]
- parent_root = head_block.parent_root
- parent_block = store.blocks[parent_root]
- current_slot = get_current_slot(store)
- proposal_slot = head_block.slot + Slot(1)
-
- # Only re-org the head_block block if it arrived later than the attestation deadline.
- head_late = is_head_late(store, head_root)
-
- # Shuffling stable.
- shuffling_stable = is_shuffling_stable(proposal_slot)
-
- # FFG information of the new head_block will be competitive with the current head.
- ffg_competitive = is_ffg_competitive(store, head_root, parent_root)
-
- # Do not re-org if the chain is not finalizing with acceptable frequency.
- finalization_ok = is_finalization_ok(store, proposal_slot)
-
- # Only suppress the fork choice update if we are confident that we will propose the next block.
- parent_state_advanced = store.block_states[parent_root].copy()
- process_slots(parent_state_advanced, proposal_slot)
- proposer_index = get_beacon_proposer_index(parent_state_advanced)
- proposing_reorg_slot = validator_is_connected(proposer_index)
-
- # Single slot re-org.
- parent_slot_ok = parent_block.slot + 1 == head_block.slot
- proposing_on_time = is_proposing_on_time(store)
-
- # Note that this condition is different from `get_proposer_head`
- current_time_ok = head_block.slot == current_slot or (
- proposal_slot == current_slot and proposing_on_time
- )
- single_slot_reorg = parent_slot_ok and current_time_ok
-
- # Check the head weight only if the attestations from the head slot have already been applied.
- # Implementations may want to do this in different ways, e.g. by advancing
- # `store.time` early, or by counting queued attestations during the head block's slot.
- if current_slot > head_block.slot:
- head_weak = is_head_weak(store, head_root)
- parent_strong = is_parent_strong(store, head_root)
- else:
- head_weak = True
- parent_strong = True
-
- return all(
- [
- head_late,
- shuffling_stable,
- ffg_competitive,
- finalization_ok,
- proposing_reorg_slot,
- single_slot_reorg,
- head_weak,
- parent_strong,
- ]
- )
-
-
- name: slash_validator#phase0
sources:
- file: packages/state-transition/src/block/slashValidator.ts
@@ -11780,12 +11954,12 @@
- name: update_fast_confirmation_variables#phase0
sources: []
spec: |
-
+
def update_fast_confirmation_variables(fcr_store: FastConfirmationStore) -> None:
# Update prev and curr slot head
store = fcr_store.store
fcr_store.previous_slot_head = fcr_store.current_slot_head
- fcr_store.current_slot_head = get_head(store)
+ fcr_store.current_slot_head = get_head(store).root
# Update greatest unrealized justified checkpoint at the last slot of an epoch
if is_start_slot_at_epoch(Slot(get_current_slot(store) + 1)):
@@ -11916,44 +12090,32 @@
- name: update_proposer_boost_root#phase0
sources: []
spec: |
-
- def update_proposer_boost_root(store: Store, root: Root) -> None:
+
+ def update_proposer_boost_root(store: Store, head: Root, root: Root) -> None:
is_first_block = store.proposer_boost_root == Root()
is_timely = store.block_timeliness[root]
+ is_same_dependent_root = get_dependent_root(store, root) == get_dependent_root(store, head)
# Add proposer score boost if the block is timely, not conflicting with an
- # existing block, with the same the proposer as the canonical chain.
- if is_timely and is_first_block:
- head_state = copy(store.block_states[get_head(store)])
- slot = get_current_slot(store)
- if head_state.slot < slot:
- process_slots(head_state, slot)
- block = store.blocks[root]
- # Only update if the proposer is the same as on the canonical chain
- if block.proposer_index == get_beacon_proposer_index(head_state):
- store.proposer_boost_root = root
+ # existing block, with the same dependent root as the canonical chain head.
+ if is_timely and is_first_block and is_same_dependent_root:
+ store.proposer_boost_root = root
- name: update_proposer_boost_root#gloas
sources: []
spec: |
-
- def update_proposer_boost_root(store: Store, root: Root) -> None:
+
+ def update_proposer_boost_root(store: Store, head: Root, root: Root) -> None:
is_first_block = store.proposer_boost_root == Root()
# [Modified in Gloas:EIP7732]
is_timely = store.block_timeliness[root][ATTESTATION_TIMELINESS_INDEX]
+ is_same_dependent_root = get_dependent_root(store, root) == get_dependent_root(store, head)
- # Add proposer score boost if the block is the first timely block
- # for this slot, with the same proposer as the canonical chain.
- if is_timely and is_first_block:
- head_state = copy(store.block_states[get_head(store).root])
- slot = get_current_slot(store)
- if head_state.slot < slot:
- process_slots(head_state, slot)
- block = store.blocks[root]
- # Only update if the proposer is the same as on the canonical chain
- if block.proposer_index == get_beacon_proposer_index(head_state):
- store.proposer_boost_root = root
+ # Add proposer score boost if the block is timely, not conflicting with an
+ # existing block, with the same dependent root as the canonical chain head.
+ if is_timely and is_first_block and is_same_dependent_root:
+ store.proposer_boost_root = root
- name: update_unrealized_checkpoints#phase0
@@ -12682,15 +12844,14 @@
- file: packages/state-transition/src/slot/upgradeStateToElectra.ts
search: export function upgradeStateToElectra(
spec: |
-
+
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 = max(earliest_exit_epoch, validator.exit_epoch)
earliest_exit_epoch += Epoch(1)
post = BeaconState(
@@ -13286,14 +13447,14 @@
- name: validate_beacon_attestation_gossip#deneb
sources: []
spec: |
-
+
def validate_beacon_attestation_gossip(
seen: Seen,
store: Store,
state: BeaconState,
attestation: Attestation,
- subnet_id: uint64,
current_time_ms: uint64,
+ subnet_id: SubnetID,
) -> None:
"""
Validate an Attestation for gossip propagation on a subnet.
@@ -13393,15 +13554,15 @@
- name: validate_beacon_attestation_gossip#electra
sources: []
spec: |
-
+
def validate_beacon_attestation_gossip(
seen: Seen,
store: Store,
state: BeaconState,
# [Modified in Electra:EIP7549]
attestation: SingleAttestation,
- subnet_id: uint64,
current_time_ms: uint64,
+ subnet_id: SubnetID,
) -> None:
"""
Validate a SingleAttestation for gossip propagation on a subnet.
@@ -13504,7 +13665,7 @@
- name: validate_beacon_block_gossip#bellatrix
sources: []
spec: |
-
+
def validate_beacon_block_gossip(
seen: Seen,
store: Store,
@@ -13512,7 +13673,7 @@
signed_beacon_block: SignedBeaconBlock,
current_time_ms: uint64,
# [New in Bellatrix]
- block_payload_statuses: Dict[Root, PayloadValidationStatus] = {},
+ block_payload_statuses: Dict[Root, PayloadValidationStatus],
) -> None:
"""
Validate a SignedBeaconBlock for gossip propagation.
@@ -13574,11 +13735,11 @@
# [IGNORE] The block's parent's execution payload passes validation
if parent_payload_status == PAYLOAD_STATUS_INVALIDATED:
raise GossipIgnore("block's parent is valid and EL result is invalid")
- else:
- # [REJECT] The block's parent passes validation
- if block.parent_root not in store.block_states:
- # [Modified in Bellatrix]
- raise GossipReject("block's parent is invalid and execution is not enabled")
+
+ # [REJECT] The block's parent passes validation
+ elif block.parent_root not in store.block_states:
+ # [Modified in Bellatrix]
+ raise GossipReject("block's parent is invalid and execution is not enabled")
# [REJECT] The block is from a higher slot than its parent
if block.slot <= store.blocks[block.parent_root].slot:
@@ -13606,14 +13767,14 @@
- name: validate_beacon_block_gossip#capella
sources: []
spec: |
-
+
def validate_beacon_block_gossip(
seen: Seen,
store: Store,
state: BeaconState,
signed_beacon_block: SignedBeaconBlock,
current_time_ms: uint64,
- block_payload_statuses: Dict[Root, PayloadValidationStatus] = {},
+ block_payload_statuses: Dict[Root, PayloadValidationStatus],
) -> None:
"""
Validate a SignedBeaconBlock for gossip propagation.
@@ -13700,14 +13861,14 @@
- name: validate_beacon_block_gossip#deneb
sources: []
spec: |
-
+
def validate_beacon_block_gossip(
seen: Seen,
store: Store,
state: BeaconState,
signed_beacon_block: SignedBeaconBlock,
current_time_ms: uint64,
- block_payload_statuses: Dict[Root, PayloadValidationStatus] = {},
+ block_payload_statuses: Dict[Root, PayloadValidationStatus],
) -> None:
"""
Validate a SignedBeaconBlock for gossip propagation.
@@ -13799,14 +13960,14 @@
- name: validate_beacon_block_gossip#electra
sources: []
spec: |
-
+
def validate_beacon_block_gossip(
seen: Seen,
store: Store,
state: BeaconState,
signed_beacon_block: SignedBeaconBlock,
current_time_ms: uint64,
- block_payload_statuses: Dict[Root, PayloadValidationStatus] = {},
+ block_payload_statuses: Dict[Root, PayloadValidationStatus],
) -> None:
"""
Validate a SignedBeaconBlock for gossip propagation.
@@ -13895,17 +14056,119 @@
seen.proposer_slots.add((block.proposer_index, block.slot))
+- name: validate_beacon_block_gossip#fulu
+ sources: []
+ spec: |
+
+ def validate_beacon_block_gossip(
+ seen: Seen,
+ store: Store,
+ state: BeaconState,
+ signed_beacon_block: SignedBeaconBlock,
+ current_time_ms: uint64,
+ block_payload_statuses: Optional[Dict[Root, PayloadValidationStatus]] = None,
+ ) -> None:
+ """
+ Validate a SignedBeaconBlock for gossip propagation.
+ Raises GossipIgnore or GossipReject on validation failure.
+ """
+ if block_payload_statuses is None:
+ block_payload_statuses = {}
+ block = signed_beacon_block.message
+ execution_payload = block.body.execution_payload
+
+ # [IGNORE] The block is not from a future slot
+ # (MAY be queued for processing at the appropriate slot)
+ if not is_not_from_future_slot(state, block.slot, current_time_ms):
+ raise GossipIgnore("block is from a future slot")
+
+ # [IGNORE] The block is from a slot greater than the latest finalized slot
+ # (MAY choose to validate and store such blocks for additional purposes
+ # -- e.g. slashing detection, archive nodes, etc)
+ finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
+ if block.slot <= finalized_slot:
+ raise GossipIgnore("block is not from a slot greater than the latest finalized slot")
+
+ # [IGNORE] The block is the first block with valid signature received for the proposer for the slot
+ if (block.proposer_index, block.slot) in seen.proposer_slots:
+ raise GossipIgnore("block is not the first valid block for this proposer and slot")
+
+ # [REJECT] The proposer index is a valid validator index
+ if block.proposer_index >= len(state.validators):
+ raise GossipReject("proposer index out of range")
+
+ # [REJECT] The proposer signature is valid
+ proposer = state.validators[block.proposer_index]
+ domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(block.slot))
+ signing_root = compute_signing_root(block, domain)
+ if not bls.Verify(proposer.pubkey, signing_root, signed_beacon_block.signature):
+ raise GossipReject("invalid proposer signature")
+
+ # [IGNORE] The block's parent has been seen (via gossip or non-gossip sources)
+ # (MAY be queued until parent is retrieved)
+ if block.parent_root not in store.blocks:
+ raise GossipIgnore("block's parent has not been seen")
+
+ # [REJECT] The block's execution payload timestamp is correct with respect to the slot
+ if execution_payload.timestamp != compute_time_at_slot(state, block.slot):
+ raise GossipReject("incorrect execution payload timestamp")
+
+ parent_payload_status = PAYLOAD_STATUS_NOT_VALIDATED
+ if block.parent_root in block_payload_statuses:
+ parent_payload_status = block_payload_statuses[block.parent_root]
+
+ if block.parent_root not in store.block_states:
+ if parent_payload_status == PAYLOAD_STATUS_NOT_VALIDATED:
+ # [REJECT] The block's parent passes validation
+ raise GossipReject("block's parent is invalid and EL result is unknown")
+
+ # [IGNORE] The block's parent passes validation
+ raise GossipIgnore("block's parent is invalid and EL result is known")
+
+ # [IGNORE] The block's parent's execution payload passes validation
+ if parent_payload_status == PAYLOAD_STATUS_INVALIDATED:
+ raise GossipIgnore("block's parent is valid and EL result is invalid")
+
+ # [REJECT] The block is from a higher slot than its parent
+ if block.slot <= store.blocks[block.parent_root].slot:
+ raise GossipReject("block is not from a higher slot than its parent")
+
+ # [REJECT] The current finalized checkpoint is an ancestor of the block
+ checkpoint_block = get_checkpoint_block(
+ store, block.parent_root, store.finalized_checkpoint.epoch
+ )
+ if checkpoint_block != store.finalized_checkpoint.root:
+ raise GossipReject("finalized checkpoint is not an ancestor of block")
+
+ # [Modified in Fulu:EIP7892]
+ # [REJECT] The length of KZG commitments is less than or equal to the limit
+ max_blobs = get_blob_parameters(get_current_epoch(state)).max_blobs_per_block
+ if len(block.body.blob_kzg_commitments) > max_blobs:
+ raise GossipReject("too many blob kzg commitments")
+
+ # [REJECT] The block is proposed by the expected proposer for the slot
+ # (if shuffling is not available, IGNORE instead and MAY be queued for later)
+ parent_state = store.block_states[block.parent_root].copy()
+ process_slots(parent_state, block.slot)
+ expected_proposer = get_beacon_proposer_index(parent_state)
+ if block.proposer_index != expected_proposer:
+ raise GossipReject("block proposer_index does not match expected proposer")
+
+ # Mark this block as seen
+ seen.proposer_slots.add((block.proposer_index, block.slot))
+
+
- name: validate_blob_sidecar_gossip#deneb
sources: []
spec: |
-
+
def validate_blob_sidecar_gossip(
seen: Seen,
store: Store,
state: BeaconState,
blob_sidecar: BlobSidecar,
- subnet_id: SubnetID,
current_time_ms: uint64,
+ subnet_id: SubnetID,
) -> None:
"""
Validate a BlobSidecar for gossip propagation on a subnet.
@@ -13993,14 +14256,14 @@
- name: validate_blob_sidecar_gossip#electra
sources: []
spec: |
-
+
def validate_blob_sidecar_gossip(
seen: Seen,
store: Store,
state: BeaconState,
blob_sidecar: BlobSidecar,
- subnet_id: SubnetID,
current_time_ms: uint64,
+ subnet_id: SubnetID,
) -> None:
"""
Validate a BlobSidecar for gossip propagation on a subnet.
@@ -14145,12 +14408,105 @@
seen.bls_to_execution_change_indices.add(validator_index)
+- name: validate_data_column_sidecar_gossip#fulu
+ sources: []
+ spec: |
+
+ def validate_data_column_sidecar_gossip(
+ seen: Seen,
+ store: Store,
+ state: BeaconState,
+ sidecar: DataColumnSidecar,
+ current_time_ms: uint64,
+ subnet_id: SubnetID,
+ ) -> None:
+ """
+ Validate a DataColumnSidecar for gossip propagation on a subnet.
+ Raises GossipIgnore or GossipReject on validation failure.
+ """
+ block_header = sidecar.signed_block_header.message
+
+ # [IGNORE] The sidecar is the first sidecar for the tuple
+ # (block_header.slot, block_header.proposer_index, sidecar.index)
+ sidecar_tuple = (block_header.slot, block_header.proposer_index, sidecar.index)
+ if sidecar_tuple in seen.data_column_sidecar_tuples:
+ raise GossipIgnore("already seen sidecar from this proposer for this slot and index")
+
+ # [REJECT] The sidecar is valid as verified by verify_data_column_sidecar
+ if not verify_data_column_sidecar(sidecar):
+ raise GossipReject("invalid sidecar")
+
+ # [REJECT] The sidecar is for the correct subnet
+ if compute_subnet_for_data_column_sidecar(sidecar.index) != subnet_id:
+ raise GossipReject("sidecar is for wrong subnet")
+
+ # [IGNORE] The sidecar is not from a future slot
+ # (MAY be queued for processing at the appropriate slot)
+ if not is_not_from_future_slot(state, block_header.slot, current_time_ms):
+ raise GossipIgnore("sidecar is from a future slot")
+
+ # [IGNORE] The sidecar is from a slot greater than the latest finalized slot
+ finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
+ if block_header.slot <= finalized_slot:
+ raise GossipIgnore("sidecar is not from a slot greater than the latest finalized slot")
+
+ # [REJECT] The proposer index is a valid validator index
+ if block_header.proposer_index >= len(state.validators):
+ raise GossipReject("proposer index out of range")
+
+ # [REJECT] The proposer signature of sidecar.signed_block_header is valid
+ proposer = state.validators[block_header.proposer_index]
+ domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(block_header.slot))
+ signing_root = compute_signing_root(block_header, domain)
+ if not bls.Verify(proposer.pubkey, signing_root, sidecar.signed_block_header.signature):
+ raise GossipReject("invalid proposer signature on sidecar block header")
+
+ # [IGNORE] The sidecar's block's parent has been seen
+ # (MAY be queued for processing once the parent block is retrieved)
+ if block_header.parent_root not in store.blocks:
+ raise GossipIgnore("sidecar's parent has not been seen")
+
+ # [REJECT] The sidecar's block's parent passes validation
+ if block_header.parent_root not in store.block_states:
+ raise GossipReject("sidecar's parent failed validation")
+
+ # [REJECT] The sidecar is from a higher slot than the sidecar's block's parent
+ if block_header.slot <= store.blocks[block_header.parent_root].slot:
+ raise GossipReject("sidecar is not from a higher slot than its parent")
+
+ # [REJECT] The current finalized_checkpoint is an ancestor of the sidecar's block
+ checkpoint_block = get_checkpoint_block(
+ store, block_header.parent_root, store.finalized_checkpoint.epoch
+ )
+ if checkpoint_block != store.finalized_checkpoint.root:
+ raise GossipReject("finalized checkpoint is not an ancestor of sidecar's block")
+
+ # [REJECT] The sidecar is valid as verified by verify_data_column_sidecar_inclusion_proof
+ if not verify_data_column_sidecar_inclusion_proof(sidecar):
+ raise GossipReject("invalid sidecar inclusion proof")
+
+ # [REJECT] The sidecar is valid as verified by verify_data_column_sidecar_kzg_proofs
+ if not verify_data_column_sidecar_kzg_proofs(sidecar):
+ raise GossipReject("invalid sidecar kzg proofs")
+
+ # [REJECT] The sidecar is proposed by the expected proposer_index
+ # (if shuffling is not available, IGNORE instead and MAY be queued for later)
+ parent_state = store.block_states[block_header.parent_root].copy()
+ process_slots(parent_state, block_header.slot)
+ expected_proposer = get_beacon_proposer_index(parent_state)
+ if block_header.proposer_index != expected_proposer:
+ raise GossipReject("sidecar proposer_index does not match expected proposer")
+
+ # Mark this data column sidecar as seen
+ seen.data_column_sidecar_tuples.add(sidecar_tuple)
+
+
- name: validate_light_client_update#altair
sources:
- file: packages/state-transition/src/lightClient/spec/validateLightClientUpdate.ts
search: export function validateLightClientUpdate(
spec: |
-
+
def validate_light_client_update(
store: LightClientStore,
update: LightClientUpdate,
@@ -14222,7 +14578,9 @@
sync_committee = store.next_sync_committee
participant_pubkeys = [
pubkey
- for (bit, pubkey) in zip(sync_aggregate.sync_committee_bits, sync_committee.pubkeys)
+ for (bit, pubkey) in zip(
+ sync_aggregate.sync_committee_bits, sync_committee.pubkeys, strict=True
+ )
if bit
]
fork_version_slot = max(update.signature_slot, Slot(1)) - Slot(1)
@@ -14237,7 +14595,7 @@
- name: validate_merge_block#bellatrix
sources: []
spec: |
-
+
def validate_merge_block(block: BeaconBlock) -> None:
"""
Check the parent PoW block of execution payload is a valid terminal PoW block.
@@ -14246,7 +14604,7 @@
and a client software MAY delay a call to ``validate_merge_block``
until the PoW block(s) become available.
"""
- if TERMINAL_BLOCK_HASH != Hash32():
+ if TERMINAL_BLOCK_HASH != EMPTY_BLOCK_HASH:
# If `TERMINAL_BLOCK_HASH` is used as an override, the activation epoch must be reached.
assert compute_epoch_at_slot(block.slot) >= TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH
assert block.body.execution_payload.parent_hash == TERMINAL_BLOCK_HASH
@@ -14265,7 +14623,7 @@
- name: validate_merge_block#gloas
sources: []
spec: |
-
+
def validate_merge_block(block: BeaconBlock) -> None:
"""
Check the parent PoW block of execution payload is a valid terminal PoW block.
@@ -14274,7 +14632,7 @@
and a client software MAY delay a call to ``validate_merge_block``
until the PoW block(s) become available.
"""
- if TERMINAL_BLOCK_HASH != Hash32():
+ if TERMINAL_BLOCK_HASH != EMPTY_BLOCK_HASH:
# If `TERMINAL_BLOCK_HASH` is used as an override, the activation epoch must be reached.
assert compute_epoch_at_slot(block.slot) >= TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH
assert block.body.execution_payload.parent_hash == TERMINAL_BLOCK_HASH
@@ -14371,6 +14729,143 @@
assert get_current_slot(store) >= attestation.data.slot + 1
+- name: validate_partial_data_column_sidecar_gossip#fulu
+ sources: []
+ spec: |
+
+ def validate_partial_data_column_sidecar_gossip(
+ seen: Seen,
+ store: Store,
+ state: BeaconState,
+ sidecar: PartialDataColumnSidecar,
+ block_root: Root,
+ column_index: ColumnIndex,
+ current_time_ms: uint64,
+ ) -> None:
+ """
+ Validate a PartialDataColumnSidecar for gossip propagation on a subnet.
+ Raises GossipIgnore or GossipReject on validation failure.
+ """
+ has_header = len(sidecar.header) == 1
+ num_cells_present = sum(1 for b in sidecar.cells_present_bitmap if b)
+ has_cells = num_cells_present > 0
+
+ # [REJECT] A header and/or cells are present in the message
+ if not (has_header or has_cells):
+ raise GossipReject("partial message is semantically empty")
+
+ # [REJECT] The cell count equals the number of bits set in cells_present_bitmap
+ if len(sidecar.partial_column) != num_cells_present:
+ raise GossipReject("number of cells does not match number of set bits")
+
+ # [REJECT] The proof count equals the number of bits set in cells_present_bitmap
+ if len(sidecar.kzg_proofs) != num_cells_present:
+ raise GossipReject("number of proofs does not match number of set bits")
+
+ if has_header:
+ header = sidecar.header[0]
+ block_header = header.signed_block_header.message
+
+ # [REJECT] The received header MUST equal any previously validated header for this block
+ prior_header = seen.partial_data_column_headers.get(block_root)
+ if prior_header is not None and prior_header != header:
+ raise GossipReject("header differs from previously validated header")
+
+ # [REJECT] The signed_block_header hash matches the partial message's group id
+ if hash_tree_root(block_header) != block_root:
+ raise GossipReject("header's block root does not match partial message group id")
+
+ # [REJECT] The header's kzg_commitments list is non-empty
+ if len(header.kzg_commitments) == 0:
+ raise GossipReject("header's kzg_commitments is empty")
+
+ # [IGNORE] The header is not from a future slot
+ # (MAY be queued for processing at the appropriate slot)
+ if not is_not_from_future_slot(state, block_header.slot, current_time_ms):
+ raise GossipIgnore("header is from a future slot")
+
+ # [IGNORE] The header is from a slot greater than the latest finalized slot
+ finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
+ if block_header.slot <= finalized_slot:
+ raise GossipIgnore("header is not from a slot greater than the latest finalized slot")
+
+ # [REJECT] The proposer index is a valid validator index
+ if block_header.proposer_index >= len(state.validators):
+ raise GossipReject("proposer index out of range")
+
+ # [REJECT] The proposer signature of signed_block_header is valid
+ proposer = state.validators[block_header.proposer_index]
+ domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(block_header.slot))
+ signing_root = compute_signing_root(block_header, domain)
+ if not bls.Verify(proposer.pubkey, signing_root, header.signed_block_header.signature):
+ raise GossipReject("invalid proposer signature on header")
+
+ # [IGNORE] The header's block's parent has been seen
+ # (MAY be queued for processing once the parent block is retrieved)
+ if block_header.parent_root not in store.blocks:
+ raise GossipIgnore("header's parent has not been seen")
+
+ # [REJECT] The header's block's parent passes validation
+ if block_header.parent_root not in store.block_states:
+ raise GossipReject("header's parent failed validation")
+
+ # [REJECT] The header is from a higher slot than the header's block's parent
+ if block_header.slot <= store.blocks[block_header.parent_root].slot:
+ raise GossipReject("header is not from a higher slot than its parent")
+
+ # [REJECT] The current finalized_checkpoint is an ancestor of the header's block
+ checkpoint_block = get_checkpoint_block(
+ store, block_header.parent_root, store.finalized_checkpoint.epoch
+ )
+ if checkpoint_block != store.finalized_checkpoint.root:
+ raise GossipReject("finalized checkpoint is not an ancestor of header's block")
+
+ # [REJECT] The header's kzg_commitments inclusion proof is valid
+ if not verify_partial_data_column_header_inclusion_proof(header):
+ raise GossipReject("invalid header inclusion proof")
+
+ # [REJECT] The header is proposed by the expected proposer_index
+ # (if shuffling is not available, IGNORE instead and MAY be queued for later)
+ parent_state = store.block_states[block_header.parent_root].copy()
+ process_slots(parent_state, block_header.slot)
+ expected_proposer = get_beacon_proposer_index(parent_state)
+ if block_header.proposer_index != expected_proposer:
+ raise GossipReject("header proposer_index does not match expected proposer")
+
+ # Mark this header as seen
+ seen.partial_data_column_headers[block_root] = header
+
+ if has_cells:
+ # [IGNORE] A valid corresponding PartialDataColumnHeader has been seen
+ header = seen.partial_data_column_headers.get(block_root)
+ if header is None:
+ raise GossipIgnore("valid corresponding header has not been seen")
+
+ block_header = header.signed_block_header.message
+
+ # [IGNORE] The corresponding header is not from a future slot
+ # (MAY be queued for processing at the appropriate slot)
+ if not is_not_from_future_slot(state, block_header.slot, current_time_ms):
+ raise GossipIgnore("corresponding header is from a future slot")
+
+ # [IGNORE] The corresponding header is from a slot greater than the latest finalized slot
+ finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
+ if block_header.slot <= finalized_slot:
+ raise GossipIgnore(
+ "corresponding header is not from a slot greater than the latest finalized slot"
+ )
+
+ # [REJECT] The cells_present_bitmap length equals the number of header kzg_commitments
+ if len(sidecar.cells_present_bitmap) != len(header.kzg_commitments):
+ raise GossipReject("bitmap length does not match commitments length")
+
+ # [REJECT] The sidecar's cell and proof data is valid
+ if not verify_partial_data_column_sidecar_kzg_proofs(
+ sidecar, header.kzg_commitments, column_index
+ ):
+ raise GossipReject("invalid sidecar kzg proofs")
+
+
- name: validate_sync_committee_contribution_and_proof_gossip#altair
sources: []
spec: |
@@ -14482,13 +14977,13 @@
- name: validate_sync_committee_message_gossip#altair
sources: []
spec: |
-
+
def validate_sync_committee_message_gossip(
seen: Seen,
state: BeaconState,
sync_committee_message: SyncCommitteeMessage,
- subnet_id: uint64,
current_time_ms: uint64,
+ subnet_id: SubnetID,
) -> None:
"""
Validate a SyncCommitteeMessage for gossip propagation on a subnet.
@@ -14954,10 +15449,10 @@
- file: packages/utils/src/bytes/browser.ts
search: export function xor(
spec: |
-
+
def xor(bytes_1: Bytes32, bytes_2: Bytes32) -> Bytes32:
"""
Return the exclusive-or of two 32-byte strings.
"""
- return Bytes32(a ^ b for a, b in zip(bytes_1, bytes_2))
+ return Bytes32(a ^ b for a, b in zip(bytes_1, bytes_2, strict=True))