diff --git a/presets/mainnet/gloas.yaml b/presets/mainnet/gloas.yaml index 599a426d50..f6becb68db 100644 --- a/presets/mainnet/gloas.yaml +++ b/presets/mainnet/gloas.yaml @@ -10,13 +10,6 @@ PTC_SIZE: 512 # 2**2 (= 4) attestations MAX_PAYLOAD_ATTESTATIONS: 4 -# State list lengths -# --------------------------------------------------------------- -# 2**40 (= 1,099,511,627,776) builder spots -BUILDER_REGISTRY_LIMIT: 1099511627776 -# 2**20 (= 1,048,576) builder pending withdrawals -BUILDER_PENDING_WITHDRAWALS_LIMIT: 1048576 - # Withdrawals processing # --------------------------------------------------------------- # 2**14 (= 16,384) builders diff --git a/presets/minimal/gloas.yaml b/presets/minimal/gloas.yaml index 7ae61ddf97..8e69df72d9 100644 --- a/presets/minimal/gloas.yaml +++ b/presets/minimal/gloas.yaml @@ -10,13 +10,6 @@ PTC_SIZE: 2 # 2**2 (= 4) attestations MAX_PAYLOAD_ATTESTATIONS: 4 -# State list lengths -# --------------------------------------------------------------- -# 2**40 (= 1,099,511,627,776) builder spots -BUILDER_REGISTRY_LIMIT: 1099511627776 -# 2**20 (= 1,048,576) builder pending withdrawals -BUILDER_PENDING_WITHDRAWALS_LIMIT: 1048576 - # Withdrawals processing # --------------------------------------------------------------- # [customized] 2**4 (= 16) builders diff --git a/pysetup/spec_builders/gloas.py b/pysetup/spec_builders/gloas.py index 48cef3b47b..8bc1e8f0bb 100644 --- a/pysetup/spec_builders/gloas.py +++ b/pysetup/spec_builders/gloas.py @@ -8,6 +8,8 @@ class GloasSpecBuilder(BaseSpecBuilder): @classmethod def imports(cls, preset_name: str): return f""" +from eth2spec.utils.ssz.ssz_typing import ProgressiveBitlist, ProgressiveContainer, ProgressiveList + from eth2spec.fulu import {preset_name} as fulu """ diff --git a/specs/electra/beacon-chain.md b/specs/electra/beacon-chain.md index f523d5f8fb..1f9d6805e5 100644 --- a/specs/electra/beacon-chain.md +++ b/specs/electra/beacon-chain.md @@ -3,6 +3,7 @@ - [Introduction](#introduction) +- [Custom types](#custom-types) - [Constants](#constants) - [Misc](#misc) - [Withdrawal prefixes](#withdrawal-prefixes) @@ -129,6 +130,16 @@ Electra is a consensus-layer upgrade containing a number of features. Including: *Note*: This specification is built upon [Deneb](../deneb/beacon-chain.md). +## Custom types + +| Name | SSZ equivalent | Description | +| ----------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------ | +| `AggregationBits` | `Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]` | Combined participation info across all participating subcommittees | +| `AttestingIndices` | `List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]` | List of attesting validator indices | +| `DepositRequests` | `List[DepositRequest, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD]` | List of deposit requests pertaining to an execution payload | +| `WithdrawalRequests` | `List[WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD]` | List of withdrawal requests pertaining to an execution payload | +| `ConsolidationRequests` | `List[ConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD]` | List of withdrawal requests pertaining to an execution payload | + ## Constants The following values are (non-configurable) constants used throughout the @@ -299,11 +310,11 @@ class ConsolidationRequest(Container): ```python class ExecutionRequests(Container): # [New in Electra:EIP6110] - deposits: List[DepositRequest, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD] + deposits: DepositRequests # [New in Electra:EIP7002:EIP7251] - withdrawals: List[WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD] + withdrawals: WithdrawalRequests # [New in Electra:EIP7251] - consolidations: List[ConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD] + consolidations: ConsolidationRequests ``` #### `SingleAttestation` @@ -357,7 +368,7 @@ class BeaconBlockBody(Container): ```python class Attestation(Container): # [Modified in Electra:EIP7549] - aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT] + aggregation_bits: AggregationBits data: AttestationData signature: BLSSignature # [New in Electra:EIP7549] @@ -369,7 +380,7 @@ class Attestation(Container): ```python class IndexedAttestation(Container): # [Modified in Electra:EIP7549] - attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT] + attesting_indices: AttestingIndices data: AttestationData signature: BLSSignature ``` @@ -1397,7 +1408,7 @@ def get_execution_requests_list(execution_requests: ExecutionRequests) -> Sequen return [ request_type + ssz_serialize(request_data) for request_type, request_data in requests - if len(request_data) != 0 + if request_data ] ``` diff --git a/specs/electra/validator.md b/specs/electra/validator.md index 7ad66e6270..17750833f4 100644 --- a/specs/electra/validator.md +++ b/specs/electra/validator.md @@ -127,7 +127,7 @@ def compute_on_chain_aggregate(network_aggregates: Sequence[Attestation]) -> Att ) data = aggregates[0].data - aggregation_bits = Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]() + aggregation_bits = AggregationBits() for a in aggregates: for b in a.aggregation_bits: aggregation_bits.append(b) @@ -249,17 +249,11 @@ def get_execution_requests(execution_requests_list: Sequence[bytes]) -> Executio prev_request_type = request_type if request_type == DEPOSIT_REQUEST_TYPE: - deposits = ssz_deserialize( - List[DepositRequest, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD], request_data - ) + deposits = ssz_deserialize(DepositRequests, request_data) elif request_type == WITHDRAWAL_REQUEST_TYPE: - withdrawals = ssz_deserialize( - List[WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD], request_data - ) + withdrawals = ssz_deserialize(WithdrawalRequests, request_data) elif request_type == CONSOLIDATION_REQUEST_TYPE: - consolidations = ssz_deserialize( - List[ConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD], request_data - ) + consolidations = ssz_deserialize(ConsolidationRequests, request_data) return ExecutionRequests( deposits=deposits, @@ -296,9 +290,9 @@ updated field assignments: ### Construct aggregate - Set `attestation_data.index = 0`. -- Let `aggregation_bits` be a - `Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]` of length - `len(committee)`, where each bit set from each individual attestation is set - to `0b1`. -- Set `attestation.committee_bits = committee_bits`, where `committee_bits` has - the bit set corresponding to `committee_index` in each individual attestation. +- Set `aggregate_attestation.aggregation_bits` to an `AggregationBits` of length + `len(committee)` with the bits corresponding to the `attester_index` of each + `SingleAttestation` inputs set to `0b1`. +- Set `aggregate_attestation.committee_bits` to a + `Bitvector[MAX_COMMITTEES_PER_SLOT]` with the single bit corresponding to the + shared `committee_index` across all `SingleAttestation` inputs set to `0b1`. diff --git a/specs/gloas/beacon-chain.md b/specs/gloas/beacon-chain.md index 5614592b77..567d9f7075 100644 --- a/specs/gloas/beacon-chain.md +++ b/specs/gloas/beacon-chain.md @@ -14,7 +14,6 @@ - [Preset](#preset) - [Misc](#misc-1) - [Max operations per block](#max-operations-per-block) - - [State list lengths](#state-list-lengths) - [Withdrawals processing](#withdrawals-processing) - [Configuration](#configuration) - [Time parameters](#time-parameters) @@ -32,13 +31,18 @@ - [`ExecutionPayloadEnvelope`](#executionpayloadenvelope) - [`SignedExecutionPayloadEnvelope`](#signedexecutionpayloadenvelope) - [Modified containers](#modified-containers) + - [`Attestation`](#attestation) + - [`IndexedAttestation`](#indexedattestation) - [`BeaconBlockBody`](#beaconblockbody) + - [`ExecutionPayload`](#executionpayload) + - [`ExecutionRequests`](#executionrequests) - [`BeaconState`](#beaconstate) - [Dataclasses](#dataclasses) - [Modified dataclasses](#modified-dataclasses) - [`ExpectedWithdrawals`](#expectedwithdrawals) - [Helpers](#helpers) - [Predicates](#predicates) + - [Modified `is_valid_indexed_attestation`](#modified-is_valid_indexed_attestation) - [New `is_builder_index`](#new-is_builder_index) - [New `is_active_builder`](#new-is_active_builder) - [New `is_builder_withdrawal_credential`](#new-is_builder_withdrawal_credential) @@ -112,9 +116,14 @@ Gloas is a consensus-layer upgrade containing a number of features. Including: ## Custom types -| Name | SSZ equivalent | Description | -| -------------- | -------------- | ---------------------- | -| `BuilderIndex` | `uint64` | Builder registry index | +| Name | SSZ equivalent | Description | +| ----------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------- | +| `AggregationBits` | `ProgressiveBitlist` | *[Modified in Gloas:EIP7688]* Combined participation info for all participating subcommittees | +| `AttestingIndices` | `ProgressiveList[ValidatorIndex]` | *[Modified in Gloas:EIP7688]* List of attesting validator indices | +| `DepositRequests` | `ProgressiveList[DepositRequest]` | *[Modified in Gloas:EIP7688]* List of deposit requests pertaining to an execution payload | +| `WithdrawalRequests` | `ProgressiveList[WithdrawalRequest]` | *[Modified in Gloas:EIP7688]* List of withdrawal requests pertaining to an execution payload | +| `ConsolidationRequests` | `ProgressiveList[ConsolidationRequest]` | *[Modified in Gloas:EIP7688]* List of withdrawal requests pertaining to an execution payload | +| `BuilderIndex` | `uint64` | Builder registry index | ## Constants @@ -160,13 +169,6 @@ Gloas is a consensus-layer upgrade containing a number of features. Including: | -------------------------- | ----- | | `MAX_PAYLOAD_ATTESTATIONS` | `4` | -### State list lengths - -| Name | Value | Unit | -| ----------------------------------- | ------------------------------------- | --------------------------- | -| `BUILDER_REGISTRY_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | Builders | -| `BUILDER_PENDING_WITHDRAWALS_LIMIT` | `uint64(2**20)` (= 1,048,576) | Builder pending withdrawals | - ### Withdrawals processing | Name | Value | @@ -285,7 +287,7 @@ class ExecutionPayloadEnvelope(Container): builder_index: BuilderIndex beacon_block_root: Root slot: Slot - blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK] + blob_kzg_commitments: ProgressiveList[KZGCommitment] state_root: Root ``` @@ -299,25 +301,53 @@ class SignedExecutionPayloadEnvelope(Container): ### Modified containers +#### `Attestation` + +```python +# [Modified in Gloas:EIP7688] +class Attestation(ProgressiveContainer(active_fields=[1] * 4)): + aggregation_bits: AggregationBits + data: AttestationData + signature: BLSSignature + committee_bits: Bitvector[MAX_COMMITTEES_PER_SLOT] +``` + +#### `IndexedAttestation` + +```python +# [Modified in Gloas:EIP7688] +class IndexedAttestation(ProgressiveContainer(active_fields=[1] * 3)): + attesting_indices: AttestingIndices + data: AttestationData + signature: BLSSignature +``` + #### `BeaconBlockBody` *Note*: The removed fields (`execution_payload`, `blob_kzg_commitments`, and `execution_requests`) now exist in `ExecutionPayloadEnvelope`. ```python -class BeaconBlockBody(Container): +# [Modified in Gloas:EIP7688] +class BeaconBlockBody(ProgressiveContainer(active_fields=[1] * 12)): randao_reveal: BLSSignature eth1_data: Eth1Data graffiti: Bytes32 - proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] - attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS_ELECTRA] - attestations: List[Attestation, MAX_ATTESTATIONS_ELECTRA] - deposits: List[Deposit, MAX_DEPOSITS] - voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] + # [Modified in Gloas:EIP7688] + proposer_slashings: ProgressiveList[ProposerSlashing] + # [Modified in Gloas:EIP7688] + attester_slashings: ProgressiveList[AttesterSlashing] + # [Modified in Gloas:EIP7688] + attestations: ProgressiveList[Attestation] + # [Modified in Gloas:EIP7688] + deposits: ProgressiveList[Deposit] + # [Modified in Gloas:EIP7688] + voluntary_exits: ProgressiveList[SignedVoluntaryExit] sync_aggregate: SyncAggregate # [Modified in Gloas:EIP7732] # Removed `execution_payload` - bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES] + # [Modified in Gloas:EIP7688] + bls_to_execution_changes: ProgressiveList[SignedBLSToExecutionChange] # [Modified in Gloas:EIP7732] # Removed `blob_kzg_commitments` # [Modified in Gloas:EIP7732] @@ -325,13 +355,50 @@ class BeaconBlockBody(Container): # [New in Gloas:EIP7732] signed_execution_payload_bid: SignedExecutionPayloadBid # [New in Gloas:EIP7732] - payload_attestations: List[PayloadAttestation, MAX_PAYLOAD_ATTESTATIONS] + payload_attestations: ProgressiveList[PayloadAttestation] +``` + +#### `ExecutionPayload` + +```python +# [Modified in Gloas:EIP7688] +class ExecutionPayload(ProgressiveContainer(active_fields=[1] * 17)): + parent_hash: Hash32 + fee_recipient: ExecutionAddress + state_root: Bytes32 + receipts_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + prev_randao: Bytes32 + block_number: uint64 + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + block_hash: Hash32 + # [Modified in Gloas:EIP7688] + transactions: ProgressiveList[Transaction] + # [Modified in Gloas:EIP7688] + withdrawals: ProgressiveList[Withdrawal] + blob_gas_used: uint64 + excess_blob_gas: uint64 +``` + +#### `ExecutionRequests` + +```python +# [Modified in Gloas:EIP7688] +class ExecutionRequests(ProgressiveContainer(active_fields=[1] * 3)): + deposits: DepositRequests + withdrawals: WithdrawalRequests + consolidations: ConsolidationRequests ``` #### `BeaconState` ```python -class BeaconState(Container): +# [Modified in Gloas:EIP7688] +class BeaconState(ProgressiveContainer(active_fields=[1] * 45)): genesis_time: uint64 genesis_validators_root: Root slot: Slot @@ -343,17 +410,22 @@ class BeaconState(Container): eth1_data: Eth1Data eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH] eth1_deposit_index: uint64 - validators: List[Validator, VALIDATOR_REGISTRY_LIMIT] - balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT] + # [Modified in Gloas:EIP7688] + validators: ProgressiveList[Validator] + # [Modified in Gloas:EIP7688] + balances: ProgressiveList[Gwei] randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR] slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] - previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] - current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + # [Modified in Gloas:EIP7688] + previous_epoch_participation: ProgressiveList[ParticipationFlags] + # [Modified in Gloas:EIP7688] + current_epoch_participation: ProgressiveList[ParticipationFlags] justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] previous_justified_checkpoint: Checkpoint current_justified_checkpoint: Checkpoint finalized_checkpoint: Checkpoint - inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] + # [Modified in Gloas:EIP7688] + inactivity_scores: ProgressiveList[uint64] current_sync_committee: SyncCommittee next_sync_committee: SyncCommittee # [Modified in Gloas:EIP7732] @@ -369,12 +441,15 @@ class BeaconState(Container): earliest_exit_epoch: Epoch consolidation_balance_to_consume: Gwei earliest_consolidation_epoch: Epoch - pending_deposits: List[PendingDeposit, PENDING_DEPOSITS_LIMIT] - pending_partial_withdrawals: List[PendingPartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT] - pending_consolidations: List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT] + # [Modified in Gloas:EIP7688] + pending_deposits: ProgressiveList[PendingDeposit] + # [Modified in Gloas:EIP7688] + pending_partial_withdrawals: ProgressiveList[PendingPartialWithdrawal] + # [Modified in Gloas:EIP7688] + pending_consolidations: ProgressiveList[PendingConsolidation] proposer_lookahead: Vector[ValidatorIndex, (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH] # [New in Gloas:EIP7732] - builders: List[Builder, BUILDER_REGISTRY_LIMIT] + builders: ProgressiveList[Builder] # [New in Gloas:EIP7732] next_withdrawal_builder_index: BuilderIndex # [New in Gloas:EIP7732] @@ -382,11 +457,11 @@ class BeaconState(Container): # [New in Gloas:EIP7732] builder_pending_payments: Vector[BuilderPendingPayment, 2 * SLOTS_PER_EPOCH] # [New in Gloas:EIP7732] - builder_pending_withdrawals: List[BuilderPendingWithdrawal, BUILDER_PENDING_WITHDRAWALS_LIMIT] + builder_pending_withdrawals: ProgressiveList[BuilderPendingWithdrawal] # [New in Gloas:EIP7732] latest_block_hash: Hash32 # [New in Gloas:EIP7732] - payload_expected_withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] + payload_expected_withdrawals: ProgressiveList[Withdrawal] ``` ## Dataclasses @@ -411,6 +486,31 @@ class ExpectedWithdrawals(object): ### Predicates +#### Modified `is_valid_indexed_attestation` + +```python +def is_valid_indexed_attestation( + state: BeaconState, indexed_attestation: IndexedAttestation +) -> bool: + """ + Check if ``indexed_attestation`` is not empty, has sorted and unique indices and has a valid aggregate signature. + """ + # [Modified in Gloas:EIP7688] + # Verify indices are sorted and unique + indices = indexed_attestation.attesting_indices + if ( + len(indices) == 0 + or len(indices) > MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT + or not indices == sorted(set(indices)) + ): + return False + # Verify aggregate signature + pubkeys = [state.validators[i].pubkey for i in indices] + domain = get_domain(state, DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch) + signing_root = compute_signing_root(indexed_attestation.data, domain) + return bls.FastAggregateVerify(pubkeys, signing_root, indexed_attestation.signature) +``` + #### New `is_builder_index` ```python @@ -989,7 +1089,7 @@ def apply_withdrawals(state: BeaconState, withdrawals: Sequence[Withdrawal]) -> def update_payload_expected_withdrawals( state: BeaconState, withdrawals: Sequence[Withdrawal] ) -> None: - state.payload_expected_withdrawals = List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD](withdrawals) + state.payload_expected_withdrawals = ProgressiveList[Withdrawal](withdrawals) ``` ##### New `update_builder_pending_withdrawals` @@ -1140,6 +1240,13 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: for operation in operations: fn(state, operation) + # [Modified in Gloas:EIP7688] + assert len(body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS + assert len(body.attester_slashings) <= MAX_ATTESTER_SLASHINGS_ELECTRA + assert len(body.attestations) <= MAX_ATTESTATIONS_ELECTRA + assert len(body.voluntary_exits) <= MAX_VOLUNTARY_EXITS + assert len(body.bls_to_execution_changes) <= MAX_BLS_TO_EXECUTION_CHANGES + assert len(body.payload_attestations) <= MAX_PAYLOAD_ATTESTATIONS # [Modified in Gloas:EIP7732] for_ops(body.proposer_slashings, process_proposer_slashing) for_ops(body.attester_slashings, process_attester_slashing) @@ -1553,6 +1660,9 @@ def process_execution_payload( for operation in operations: fn(state, operation) + assert len(requests.deposits) <= MAX_DEPOSIT_REQUESTS_PER_PAYLOAD + assert len(requests.withdrawals) <= MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD + assert len(requests.consolidations) <= MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD for_ops(requests.deposits, process_deposit_request) for_ops(requests.withdrawals, process_withdrawal_request) for_ops(requests.consolidations, process_consolidation_request) diff --git a/specs/gloas/builder.md b/specs/gloas/builder.md index c0c4f1906e..5840e7870e 100644 --- a/specs/gloas/builder.md +++ b/specs/gloas/builder.md @@ -153,7 +153,7 @@ def get_data_column_sidecars( beacon_block_root: Root, # [New in Gloas:EIP7732] slot: Slot, - kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK], + kzg_commitments: ProgressiveList[KZGCommitment], # [Modified in Gloas:EIP7732] # Removed `kzg_commitments_inclusion_proof` cells_and_kzg_proofs: Sequence[ @@ -195,7 +195,7 @@ of header and inclusion proof computations. def get_data_column_sidecars_from_block( signed_block: SignedBeaconBlock, # [New in Gloas:EIP7732] - blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK], + blob_kzg_commitments: ProgressiveList[KZGCommitment], cells_and_kzg_proofs: Sequence[ Tuple[Vector[Cell, CELLS_PER_EXT_BLOB], Vector[KZGProof, CELLS_PER_EXT_BLOB]] ], diff --git a/specs/gloas/fork.md b/specs/gloas/fork.md index 3840846222..9374a4f797 100644 --- a/specs/gloas/fork.md +++ b/specs/gloas/fork.md @@ -9,6 +9,7 @@ - [Fork to Gloas](#fork-to-gloas) - [Fork trigger](#fork-trigger) - [Upgrading the state](#upgrading-the-state) + - [Upgrading attestations and attester slashings](#upgrading-attestations-and-attester-slashings) @@ -58,17 +59,21 @@ def upgrade_to_gloas(pre: fulu.BeaconState) -> BeaconState: eth1_data=pre.eth1_data, eth1_data_votes=pre.eth1_data_votes, eth1_deposit_index=pre.eth1_deposit_index, - validators=pre.validators, - balances=pre.balances, + validators=ProgressiveList[Validator](list(pre.validators)), + balances=ProgressiveList[Gwei](list(pre.balances)), randao_mixes=pre.randao_mixes, slashings=pre.slashings, - previous_epoch_participation=pre.previous_epoch_participation, - current_epoch_participation=pre.current_epoch_participation, + previous_epoch_participation=ProgressiveList[ParticipationFlags]( + list(pre.previous_epoch_participation) + ), + current_epoch_participation=ProgressiveList[ParticipationFlags]( + list(pre.current_epoch_participation) + ), justification_bits=pre.justification_bits, previous_justified_checkpoint=pre.previous_justified_checkpoint, current_justified_checkpoint=pre.current_justified_checkpoint, finalized_checkpoint=pre.finalized_checkpoint, - inactivity_scores=pre.inactivity_scores, + inactivity_scores=ProgressiveList[uint64](list(pre.inactivity_scores)), current_sync_committee=pre.current_sync_committee, next_sync_committee=pre.next_sync_committee, # [Modified in Gloas:EIP7732] @@ -86,9 +91,13 @@ def upgrade_to_gloas(pre: fulu.BeaconState) -> BeaconState: earliest_exit_epoch=pre.earliest_exit_epoch, consolidation_balance_to_consume=pre.consolidation_balance_to_consume, earliest_consolidation_epoch=pre.earliest_consolidation_epoch, - pending_deposits=pre.pending_deposits, - pending_partial_withdrawals=pre.pending_partial_withdrawals, - pending_consolidations=pre.pending_consolidations, + pending_deposits=ProgressiveList[PendingDeposit](list(pre.pending_deposits)), + pending_partial_withdrawals=ProgressiveList[PendingPartialWithdrawal]( + list(pre.pending_partial_withdrawals) + ), + pending_consolidations=ProgressiveList[PendingConsolidation]( + list(pre.pending_consolidations) + ), proposer_lookahead=pre.proposer_lookahead, # [New in Gloas:EIP7732] builders=[], @@ -108,3 +117,36 @@ def upgrade_to_gloas(pre: fulu.BeaconState) -> BeaconState: return post ``` + +### Upgrading attestations and attester slashings + +A Gloas `BeaconBlockBody` can still contain earlier attestations and attester +slashings. In order to pack them, the pre-Gloas data needs to be locally +upgraded to Gloas before creating the block. + +```python +def upgrade_attestation_to_gloas(pre: fulu.Attestation) -> Attestation: + return Attestation( + aggregation_bits=AggregationBits(list(pre.aggregation_bits)), + data=pre.data, + signature=pre.signature, + committee_bits=pre.committee_bits, + ) +``` + +```python +def upgrade_indexed_attestation_to_gloas(pre: fulu.IndexedAttestation) -> IndexedAttestation: + return IndexedAttestation( + attesting_indices=AttestingIndices(list(pre.attesting_indices)), + data=pre.data, + signature=pre.signature, + ) +``` + +```python +def upgrade_attester_slashing_to_gloas(pre: fulu.AttesterSlashing) -> AttesterSlashing: + return AttesterSlashing( + attestation_1=upgrade_indexed_attestation_to_gloas(pre.attestation_1), + attestation_2=upgrade_indexed_attestation_to_gloas(pre.attestation_2), + ) +``` diff --git a/specs/gloas/light-client/sync-protocol.md b/specs/gloas/light-client/sync-protocol.md new file mode 100644 index 0000000000..04e5960101 --- /dev/null +++ b/specs/gloas/light-client/sync-protocol.md @@ -0,0 +1,32 @@ +# Gloas Light Client -- Sync Protocol + + + +- [Introduction](#introduction) +- [Constants](#constants) + - [Frozen constants](#frozen-constants) +- [Note](#note) + + + +## Introduction + +This upgrade updates light client data to include the Gloas changes to the +generalized indices of [`BeaconState`](../beacon-chain.md). + +## Constants + +### Frozen constants + +Existing `GeneralizedIndex` constants are frozen at their +[Electra](../../electra/light-client/sync-protocol.md#constants) values. + +| Name | Value | +| --------------------------------------- | ------------------------------------------------------------------------------------ | +| `FINALIZED_ROOT_GINDEX_ELECTRA` | `get_generalized_index(electra.BeaconState, 'finalized_checkpoint', 'root')` (= 169) | +| `CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA` | `get_generalized_index(electra.BeaconState, 'current_sync_committee')` (= 86) | +| `NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA` | `get_generalized_index(electra.BeaconState, 'next_sync_committee')` (= 87) | + +## Note + +The light client specs for Gloas are currently incomplete. diff --git a/specs/gloas/p2p-interface.md b/specs/gloas/p2p-interface.md index 91b9a58fb7..dcc216460c 100644 --- a/specs/gloas/p2p-interface.md +++ b/specs/gloas/p2p-interface.md @@ -66,9 +66,12 @@ committed in the builder's bid for the corresponding `beacon_block_root`. ```python class DataColumnSidecar(Container): index: ColumnIndex - column: List[Cell, MAX_BLOB_COMMITMENTS_PER_BLOCK] - kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK] - kzg_proofs: List[KZGProof, MAX_BLOB_COMMITMENTS_PER_BLOCK] + # [Modified in Gloas:7688] + column: ProgressiveList[Cell] + # [Modified in Gloas:7688] + kzg_commitments: ProgressiveList[KZGCommitment] + # [Modified in Gloas:7688] + kzg_proofs: ProgressiveList[KZGProof] # [Modified in Gloas:EIP7732] # Removed `signed_block_header` # [Modified in Gloas:EIP7732] @@ -210,6 +213,14 @@ The *type* of the payload of this topic changes to the (modified) There are no new validations for this topic. However, all validations with regards to the `ExecutionPayload` are removed: +- _[REJECT]_ The number of operations is within limits -- i.e. validate that + `len(body.deposits) <= MAX_DEPOSITS` and + `len(body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS` and + `len(body.attester_slashings) <= MAX_ATTESTER_SLASHINGS_ELECTRA` and + `len(body.attestations) <= MAX_ATTESTATIONS_ELECTRA` and + `len(body.voluntary_exits) <= MAX_VOLUNTARY_EXITS` and + `len(body.bls_to_execution_changes) <= MAX_BLS_TO_EXECUTION_CHANGES` and + `len(body.payload_attestations) <= MAX_PAYLOAD_ATTESTATIONS` - _[REJECT]_ The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer -- i.e. validate that `len(signed_beacon_block.message.body.blob_kzg_commitments) <= get_blob_parameters(get_current_epoch(state)).max_blobs_per_block` @@ -244,8 +255,15 @@ This topic is used to propagate execution payload messages as The following validations MUST pass before forwarding the `signed_execution_payload_envelope` on the network, assuming the alias `envelope = signed_execution_payload_envelope.message`, -`payload = envelope.payload`: - +`payload = envelope.payload`, +`execution_requests = envelope.execution_requests`: + +- _[REJECT]_ The number of execution requests is within limits -- i.e. validate + that `len(execution_requests.deposits) <= MAX_DEPOSIT_REQUESTS_PER_PAYLOAD` + and + `len(execution_requests.withdrawals) <= MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD` + and + `len(execution_requests.consolidations) <= MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD` - _[IGNORE]_ The envelope's block root `envelope.block_root` has been seen (via gossip or non-gossip sources) (a client MAY queue payload for processing once the block is retrieved). diff --git a/ssz/simple-serialize.md b/ssz/simple-serialize.md index 4fdd9dfd7b..d88cb387fa 100644 --- a/ssz/simple-serialize.md +++ b/ssz/simple-serialize.md @@ -55,8 +55,8 @@ foo: uint64 bar: boolean ``` -- **progressive container** _[EIP-7495, currently unused]_: ordered - heterogeneous collection of values with stable Merkleization +- **progressive container** _[EIP-7495]_: ordered heterogeneous collection of + values with stable Merkleization - python dataclass notation with key-type pairs, e.g. ```python class Square(ProgressiveContainer(active_fields=[1, 0, 1])): @@ -73,8 +73,8 @@ - **list**: ordered variable-length homogeneous collection, limited to `N` values - notation `List[type, N]`, e.g. `List[uint64, N]` -- **progressive list** _[EIP-7916, currently unused]_: ordered variable-length - homogeneous collection, without limit +- **progressive list** _[EIP-7916]_: ordered variable-length homogeneous + collection, without limit - notation `ProgressiveList[type]`, e.g. `ProgressiveList[uint64]` - **bitvector**: ordered fixed-length collection of `boolean` values, with `N` bits @@ -82,8 +82,8 @@ - **bitlist**: ordered variable-length collection of `boolean` values, limited to `N` bits - notation `Bitlist[N]` -- **progressive bitlist** _[EIP-7916, currently unused]_: ordered - variable-length collection of `boolean` values, without limit +- **progressive bitlist** _[EIP-7916]_: ordered variable-length collection of + `boolean` values, without limit - notation `ProgressiveBitlist` - **union**: union type containing one of the given subtypes - notation `Union[type_0, type_1, ...]`, e.g. `union[None, uint64, uint32]` diff --git a/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py index 2e97482b76..eda57e8560 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py @@ -38,9 +38,7 @@ def run_execution_payload_processing( slot=state.slot, builder_index=spec.BUILDER_INDEX_SELF_BUILD, ) - kzg_list = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]( - blob_kzg_commitments - ) + kzg_list = spec.ProgressiveList[spec.KZGCommitment](blob_kzg_commitments) # In Gloas, blob_kzg_commitments_root is stored in latest_execution_payload_bid, not latest_execution_payload_header state.latest_execution_payload_bid.blob_kzg_commitments_root = kzg_list.hash_tree_root() state.latest_execution_payload_bid.builder_index = envelope.builder_index diff --git a/tests/core/pyspec/eth2spec/test/deneb/transition/test_operations.py b/tests/core/pyspec/eth2spec/test/deneb/transition/test_operations.py index 3904b1877d..31380443d5 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/transition/test_operations.py +++ b/tests/core/pyspec/eth2spec/test/deneb/transition/test_operations.py @@ -5,6 +5,7 @@ ) from eth2spec.test.helpers.attestations import ( get_valid_attestation, + upgrade_attestation_to_new_spec, ) from eth2spec.test.helpers.block import ( build_empty_block_for_next_slot, @@ -112,6 +113,7 @@ def test_transition_attestation_from_previous_fork_with_new_range( # Transition to the fork epoch with a block transition_until_fork(spec, state, fork_epoch) state, fork_block = do_fork(state, spec, post_spec, fork_epoch) + attestation = upgrade_attestation_to_new_spec(spec, post_spec, attestation) current_epoch = spec.get_current_epoch(state) assert current_epoch == fork_epoch # Transition to second to last slot in `fork_epoch` diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_networking.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_networking.py index 0aa2ef0824..40b20014af 100644 --- a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_networking.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_networking.py @@ -41,9 +41,9 @@ def compute_data_column_sidecar(spec, state): cells_and_kzg_proofs = [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs] if is_post_gloas(spec): - block.body.signed_execution_payload_bid.message.blob_kzg_commitments_root = spec.List[ - spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK - ](blob_kzg_commitments).hash_tree_root() + block.body.signed_execution_payload_bid.message.blob_kzg_commitments_root = ( + spec.ProgressiveList[spec.KZGCommitment](blob_kzg_commitments).hash_tree_root() + ) signed_block = sign_block(spec, state, block, proposer_index=0) return spec.get_data_column_sidecars_from_block( signed_block, blob_kzg_commitments, cells_and_kzg_proofs diff --git a/tests/core/pyspec/eth2spec/test/gloas/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/gloas/block_processing/test_process_execution_payload.py index ea93640583..4f5062d21b 100644 --- a/tests/core/pyspec/eth2spec/test/gloas/block_processing/test_process_execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/gloas/block_processing/test_process_execution_payload.py @@ -1,3 +1,5 @@ +from random import Random + from eth2spec.test.context import ( always_bls, spec_state_test, @@ -7,6 +9,11 @@ build_empty_execution_payload, ) from eth2spec.test.helpers.keys import builder_privkeys, privkeys +from eth2spec.test.helpers.multi_operations import ( + get_random_consolidation_requests, + get_random_deposit_requests, + get_random_withdrawal_requests, +) def run_execution_payload_processing( @@ -89,18 +96,10 @@ def prepare_execution_payload_envelope( execution_payload = build_empty_execution_payload(spec, state) if execution_requests is None: - execution_requests = spec.ExecutionRequests( - deposits=spec.List[spec.DepositRequest, spec.MAX_DEPOSIT_REQUESTS_PER_PAYLOAD](), - withdrawals=spec.List[ - spec.WithdrawalRequest, spec.MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD - ](), - consolidations=spec.List[ - spec.ConsolidationRequest, spec.MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD - ](), - ) + execution_requests = spec.ExecutionRequests() if blob_kzg_commitments is None: - blob_kzg_commitments = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]() + blob_kzg_commitments = spec.ProgressiveList[spec.KZGCommitment]() # Create a copy of state for computing state_root after execution payload processing if state_root is None: @@ -183,7 +182,7 @@ def setup_state_with_payload_bid(spec, state, builder_index=None, value=None, pr prev_randao = spec.get_randao_mix(state, spec.get_current_epoch(state)) # Create and set the latest execution payload bid - kzg_list = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]() + kzg_list = spec.ProgressiveList[spec.KZGCommitment]() bid = spec.ExecutionPayloadBid( parent_block_hash=state.latest_block_hash, parent_block_root=state.latest_block_header.hash_tree_root(), @@ -198,10 +197,8 @@ def setup_state_with_payload_bid(spec, state, builder_index=None, value=None, pr ) state.latest_execution_payload_bid = bid - # Setup withdrawals root - state.payload_expected_withdrawals = spec.List[ - spec.Withdrawal, spec.MAX_WITHDRAWALS_PER_PAYLOAD - ]() + # Setup expected withdrawals + state.payload_expected_withdrawals = spec.ProgressiveList[spec.Withdrawal]() # Add pending payment if value > 0 if value > 0: @@ -379,7 +376,7 @@ def test_process_execution_payload_with_blob_commitments(spec, state): setup_state_with_payload_bid(spec, state, builder_index, spec.Gwei(3000000)) # Create blob commitments - blob_kzg_commitments = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]( + blob_kzg_commitments = spec.ProgressiveList[spec.KZGCommitment]( [ spec.KZGCommitment(b"\x42" * 48), spec.KZGCommitment(b"\x43" * 48), @@ -443,7 +440,7 @@ def test_process_execution_payload_with_execution_requests(spec, state): # Create execution requests execution_requests = spec.ExecutionRequests( - deposits=spec.List[spec.DepositRequest, spec.MAX_DEPOSIT_REQUESTS_PER_PAYLOAD]( + deposits=spec.DepositRequests( [ spec.DepositRequest( pubkey=spec.BLSPubkey(b"\x01" * 48), @@ -454,7 +451,7 @@ def test_process_execution_payload_with_execution_requests(spec, state): ) ] ), - withdrawals=spec.List[spec.WithdrawalRequest, spec.MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD]( + withdrawals=spec.WithdrawalRequests( [ spec.WithdrawalRequest( source_address=spec.ExecutionAddress(b"\x04" * 20), @@ -463,9 +460,7 @@ def test_process_execution_payload_with_execution_requests(spec, state): ) ] ), - consolidations=spec.List[ - spec.ConsolidationRequest, spec.MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD - ]( + consolidations=spec.ConsolidationRequests( [ spec.ConsolidationRequest( source_address=spec.ExecutionAddress(b"\x06" * 20), @@ -523,6 +518,73 @@ def test_process_execution_payload_with_execution_requests(spec, state): assert cleared_payment.withdrawal.builder_index == empty_payment.withdrawal.builder_index +def run_execution_payload_with_invalid_execution_requests_test(spec, state, execution_requests): + """ + Test execution payload processing with invalid execution requests + """ + setup_state_with_payload_bid(spec, state, spec.BUILDER_INDEX_SELF_BUILD, spec.Gwei(0)) + + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.block_hash = state.latest_execution_payload_bid.block_hash + execution_payload.gas_limit = state.latest_execution_payload_bid.gas_limit + execution_payload.parent_hash = state.latest_block_hash + + signed_envelope = prepare_execution_payload_envelope( + spec, + state, + builder_index=spec.BUILDER_INDEX_SELF_BUILD, + execution_payload=execution_payload, + execution_requests=execution_requests, + ) + + yield from run_execution_payload_processing(spec, state, signed_envelope, valid=False) + + +@with_gloas_and_later +@spec_state_test +def test_process_execution_payload_too_many_deposit_requests(spec, state): + rng = Random(3456) + yield from run_execution_payload_with_invalid_execution_requests_test( + spec, + state, + spec.ExecutionRequests( + deposits=get_random_deposit_requests( + spec, state, rng, spec.MAX_DEPOSIT_REQUESTS_PER_PAYLOAD + 1 + ) + ), + ) + + +@with_gloas_and_later +@spec_state_test +def test_process_execution_payload_too_many_withdrawal_requests(spec, state): + rng = Random(3456) + yield from run_execution_payload_with_invalid_execution_requests_test( + spec, + state, + spec.ExecutionRequests( + withdrawals=get_random_withdrawal_requests( + spec, state, rng, spec.MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD + 1 + ) + ), + ) + + +@with_gloas_and_later +@spec_state_test +def test_process_execution_payload_too_many_consolidation_requests(spec, state): + rng = Random(3456) + yield from run_execution_payload_with_invalid_execution_requests_test( + spec, + state, + spec.ExecutionRequests( + consolidations=get_random_consolidation_requests( + spec, state, rng, spec.MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD + 1 + ) + ), + ) + + # # Invalid signature tests # @@ -648,7 +710,7 @@ def test_process_execution_payload_wrong_blob_commitments_root(spec, state): builder_index = 0 setup_state_with_payload_bid(spec, state, builder_index, spec.Gwei(2800000)) - original_blob_commitments = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]( + original_blob_commitments = spec.ProgressiveList[spec.KZGCommitment]( [spec.KZGCommitment(b"\x11" * 48)] ) state.latest_execution_payload_bid.blob_kzg_commitments_root = ( @@ -661,7 +723,7 @@ def test_process_execution_payload_wrong_blob_commitments_root(spec, state): execution_payload.parent_hash = state.latest_block_hash # Use different blob commitments - wrong_blob_commitments = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]( + wrong_blob_commitments = spec.ProgressiveList[spec.KZGCommitment]( [spec.KZGCommitment(b"\x22" * 48)] ) @@ -844,9 +906,7 @@ def test_process_execution_payload_max_blob_commitments_valid(spec, state): max_blob_commitments = [ spec.KZGCommitment(b"\x42" * 48) for _ in range(spec.config.MAX_BLOBS_PER_BLOCK) ] - blob_kzg_commitments = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]( - max_blob_commitments - ) + blob_kzg_commitments = spec.ProgressiveList[spec.KZGCommitment](max_blob_commitments) # Update committed bid to match state.latest_execution_payload_bid.blob_kzg_commitments_root = ( diff --git a/tests/core/pyspec/eth2spec/test/gloas/block_processing/test_process_execution_payload_bid.py b/tests/core/pyspec/eth2spec/test/gloas/block_processing/test_process_execution_payload_bid.py index 1e74795edf..b1f10973e9 100644 --- a/tests/core/pyspec/eth2spec/test/gloas/block_processing/test_process_execution_payload_bid.py +++ b/tests/core/pyspec/eth2spec/test/gloas/block_processing/test_process_execution_payload_bid.py @@ -87,7 +87,7 @@ def prepare_signed_execution_payload_bid( ) if blob_kzg_commitments_root is None: - kzg_list = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]() + kzg_list = spec.ProgressiveList[spec.KZGCommitment]() blob_kzg_commitments_root = kzg_list.hash_tree_root() if prev_randao is None: @@ -326,7 +326,7 @@ def test_process_execution_payload_bid_self_build_non_zero_value(spec, state): Test self-builder with non-zero value fails (builder_index == BUILDER_INDEX_SELF_BUILD but value > 0) """ block = build_empty_block_for_next_slot(spec, state) - kzg_list = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]() + kzg_list = spec.ProgressiveList[spec.KZGCommitment]() blob_kzg_commitments_root = kzg_list.hash_tree_root() bid = spec.ExecutionPayloadBid( diff --git a/tests/core/pyspec/eth2spec/test/gloas/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/gloas/sanity/test_blocks.py new file mode 100644 index 0000000000..974216f631 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/gloas/sanity/test_blocks.py @@ -0,0 +1,164 @@ +from random import Random + +from eth2spec.test.context import ( + spec_state_test, + with_gloas_and_later, +) +from eth2spec.test.helpers.attestations import get_max_attestations +from eth2spec.test.helpers.attester_slashings import ( + get_max_attester_slashings, + get_valid_attester_slashing_by_indices, +) +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.bls_to_execution_changes import get_signed_address_change +from eth2spec.test.helpers.deposits import build_deposit_data, deposit_from_context +from eth2spec.test.helpers.keys import privkeys, pubkeys +from eth2spec.test.helpers.multi_operations import ( + get_random_attestations, +) +from eth2spec.test.helpers.proposer_slashings import ( + get_valid_proposer_slashings, +) +from eth2spec.test.helpers.state import next_epoch, next_slots, state_transition_and_sign_block +from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits + + +@with_gloas_and_later +@spec_state_test +def test_invalid_too_many_proposer_slashings(spec, state): + num_slashings = spec.MAX_PROPOSER_SLASHINGS + 1 + proposer_slashings = get_valid_proposer_slashings(spec, state, num_slashings) + + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + block.body.proposer_slashings = proposer_slashings + signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) + + yield "blocks", [signed_block] + yield "post", None + + +@with_gloas_and_later +@spec_state_test +def test_invalid_too_many_attester_slashings(spec, state): + num_slashings = get_max_attester_slashings(spec) + 1 + full_indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[:8] + per_slashing_length = len(full_indices) // num_slashings + attester_slashings = [ + get_valid_attester_slashing_by_indices( + spec, + state, + full_indices[i * per_slashing_length : (i + 1) * per_slashing_length], + signed_1=True, + signed_2=True, + ) + for i in range(num_slashings) + ] + + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + block.body.attester_slashings = attester_slashings + signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) + + yield "blocks", [signed_block] + yield "post", None + + +@with_gloas_and_later +@spec_state_test +def test_invalid_too_many_attestations(spec, state): + rng = Random(2000) + + next_epoch(spec, state) + num_attestations = get_max_attestations(spec) + 1 + attestations = get_random_attestations(spec, state, rng, num_attestations) + + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + block.body.attestations = attestations + signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) + + yield "blocks", [signed_block] + yield "post", None + + +@with_gloas_and_later +@spec_state_test +def test_invalid_too_many_deposits(spec, state): + num_deposits = spec.MAX_DEPOSITS + 1 + validator_index = len(state.validators) + amount = spec.MIN_ACTIVATION_BALANCE + + deposit_data_list = [spec.DepositData() for _ in range(state.eth1_deposit_index)] + for _ in range(num_deposits): + deposit_data = build_deposit_data( + spec, + pubkeys[validator_index], + privkeys[validator_index], + amount, + withdrawal_credentials=b"\x00" * 32, + signed=True, + ) + deposit_data_list.append(deposit_data) + + deposits = [] + deposit_root = None + for i in range(state.eth1_deposit_index, state.eth1_deposit_index + num_deposits): + deposit, deposit_root, _ = deposit_from_context(spec, deposit_data_list, i) + deposits.append(deposit) + + state.eth1_data.deposit_root = deposit_root + state.eth1_data.deposit_count = len(deposit_data_list) + + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + block.body.deposits = deposits + signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) + + yield "blocks", [signed_block] + yield "post", None + + +@with_gloas_and_later +@spec_state_test +def test_invalid_too_many_voluntary_exits(spec, state): + next_slots(spec, state, spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH) + num_exits = spec.MAX_VOLUNTARY_EXITS + 1 + full_indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[ + :num_exits + ] + signed_exits = prepare_signed_exits(spec, state, full_indices) + + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + block.body.voluntary_exits = signed_exits + signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) + + yield "blocks", [signed_block] + yield "post", None + + +@with_gloas_and_later +@spec_state_test +def test_invalid_too_many_bls_to_execution_changes(spec, state): + num_address_changes = spec.MAX_BLS_TO_EXECUTION_CHANGES + 1 + signed_address_changes = [ + get_signed_address_change(spec, state, validator_index=i) + for i in range(num_address_changes) + ] + + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + block.body.bls_to_execution_changes = signed_address_changes + signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) + + yield "blocks", [signed_block] + yield "post", None diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index a74684c2f5..42c2bbe3ff 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -493,9 +493,7 @@ def get_empty_eip7549_aggregation_bits(spec, state, committee_bits, slot): for index in committee_indices: committee = spec.get_beacon_committee(state, slot, index) participants_count += len(committee) - aggregation_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE * spec.MAX_COMMITTEES_PER_SLOT]( - [False] * participants_count - ) + aggregation_bits = spec.AggregationBits([False] * participants_count) return aggregation_bits @@ -512,3 +510,24 @@ def get_eip7549_aggregation_bits_offset(spec, state, slot, committee_bits, commi committee = spec.get_beacon_committee(state, slot, committee_indices[i]) offset += len(committee) return offset + + +def needs_upgrade_to_gloas(spec, new_spec): + return is_post_gloas(new_spec) and not is_post_gloas(spec) + + +def check_attestation_equal(spec, new_spec, data, upgraded): + assert list(data.aggregation_bits) == list(upgraded.aggregation_bits) + assert data.data == upgraded.data + assert data.signature == upgraded.signature + assert list(data.committee_bits) == list(upgraded.committee_bits) + + +def upgrade_attestation_to_new_spec(spec, new_spec, data): + upgraded = data + + if needs_upgrade_to_gloas(spec, new_spec): + upgraded = new_spec.upgrade_attestation_to_gloas(upgraded) + check_attestation_equal(spec, new_spec, data, upgraded) + + return upgraded diff --git a/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py b/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py index 9c2568a4d1..4a9f349e27 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py @@ -3,7 +3,7 @@ sign_attestation, sign_indexed_attestation, ) -from eth2spec.test.helpers.forks import is_post_electra +from eth2spec.test.helpers.forks import is_post_electra, is_post_gloas def get_valid_attester_slashing( @@ -74,3 +74,38 @@ def get_max_attester_slashings(spec): return spec.MAX_ATTESTER_SLASHINGS_ELECTRA else: return spec.MAX_ATTESTER_SLASHINGS + + +def needs_upgrade_to_gloas(spec, new_spec): + return is_post_gloas(new_spec) and not is_post_gloas(spec) + + +def check_indexed_attestation_equal(spec, new_spec, data, upgraded): + assert list(data.attesting_indices) == list(upgraded.attesting_indices) + assert data.data == upgraded.data + assert data.signature == upgraded.signature + + +def upgrade_indexed_attestation_to_new_spec(spec, new_spec, data): + upgraded = data + + if needs_upgrade_to_gloas(spec, new_spec): + upgraded = new_spec.upgrade_indexed_attestation_to_gloas(upgraded) + check_indexed_attestation_equal(spec, new_spec, data, upgraded) + + return upgraded + + +def check_attester_slashing_equal(spec, new_spec, data, upgraded): + check_indexed_attestation_equal(spec, new_spec, data.attestation_1, upgraded.attestation_1) + check_indexed_attestation_equal(spec, new_spec, data.attestation_2, upgraded.attestation_2) + + +def upgrade_attester_slashing_to_new_spec(spec, new_spec, data): + upgraded = data + + if needs_upgrade_to_gloas(spec, new_spec): + upgraded = new_spec.upgrade_attester_slashing_to_gloas(upgraded) + check_attester_slashing_equal(spec, new_spec, data, upgraded) + + return upgraded diff --git a/tests/core/pyspec/eth2spec/test/helpers/blob.py b/tests/core/pyspec/eth2spec/test/helpers/blob.py index eb77340b29..d193b0cc9d 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/blob.py +++ b/tests/core/pyspec/eth2spec/test/helpers/blob.py @@ -145,9 +145,7 @@ def get_block_with_blob(spec, state, rng: Random | None = None, blob_count=1): spec, blob_count=blob_count, rng=rng or random.Random(5566) ) if is_post_gloas(spec): - blob_kzg_commitments = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]( - blob_kzg_commitments - ) + blob_kzg_commitments = spec.ProgressiveList[spec.KZGCommitment](blob_kzg_commitments) kzg_root = blob_kzg_commitments.hash_tree_root() block.body.signed_execution_payload_bid.message.blob_kzg_commitments_root = kzg_root # For self-builds, use point at infinity signature as per spec diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index 9c0d8e8870..2ae70d7023 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -79,7 +79,7 @@ def get_execution_payload_bid(spec, state, execution_payload): raise ValueError("get_execution_payload_bid only available for gloas and later") parent_block_root = hash_tree_root(state.latest_block_header) - kzg_list = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]() + kzg_list = spec.ProgressiveList[spec.KZGCommitment]() builder_index = spec.get_beacon_proposer_index(state) return spec.ExecutionPayloadBid( @@ -316,7 +316,7 @@ def build_empty_post_gloas_execution_payload_bid(spec, state): if not is_post_gloas(spec): return parent_block_root = hash_tree_root(state.latest_block_header) - kzg_list = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]() + kzg_list = spec.ProgressiveList[spec.KZGCommitment]() # Use self-build: builder_index is the same as the beacon proposer index builder_index = spec.BUILDER_INDEX_SELF_BUILD # Set block_hash to a different value than spec.Hash32(), @@ -436,7 +436,7 @@ def build_randomized_execution_payload(spec, state, rng): def build_state_with_incomplete_transition(spec, state): if is_post_gloas(spec): # In Gloas, we need to set up the execution payload bid instead - kzgs = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]() + kzgs = spec.ProgressiveList[spec.KZGCommitment]() bid = spec.ExecutionPayloadBid( slot=state.slot, value=spec.Gwei(0), diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index a55e0ad3ae..3a6037421a 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -6,6 +6,7 @@ ) from eth2spec.test.helpers.attester_slashings import ( get_valid_attester_slashing_by_indices, + upgrade_attester_slashing_to_new_spec, ) from eth2spec.test.helpers.block import ( build_empty_block, @@ -382,6 +383,10 @@ def run_transition_with_operation( signed_1=True, signed_2=True, ) + if is_at_fork: + attester_slashing = upgrade_attester_slashing_to_new_spec( + target_spec, post_spec, attester_slashing + ) operation_dict = {"attester_slashings": [attester_slashing]} elif operation_type == OperationType.DEPOSIT: # create a new deposit diff --git a/tests/core/pyspec/eth2spec/test/helpers/genesis.py b/tests/core/pyspec/eth2spec/test/helpers/genesis.py index 979fbed579..0537d394dd 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/genesis.py +++ b/tests/core/pyspec/eth2spec/test/helpers/genesis.py @@ -245,9 +245,7 @@ def create_genesis_state(spec, validator_balances, activation_threshold): builder_balance = 2 * spec.MIN_DEPOSIT_AMOUNT state.builders = [build_mock_builder(spec, i, builder_balance) for i in range(8)] state.execution_payload_availability = [0b1 for _ in range(spec.SLOTS_PER_HISTORICAL_ROOT)] - state.payload_expected_withdrawals = spec.List[ - spec.Withdrawal, spec.MAX_WITHDRAWALS_PER_PAYLOAD - ]() + state.payload_expected_withdrawals = spec.ProgressiveList[spec.Withdrawal]() state.builder_pending_payments = [ spec.BuilderPendingPayment() for _ in range(2 * spec.SLOTS_PER_EPOCH) ] diff --git a/tests/core/pyspec/eth2spec/test/helpers/gloas/fork.py b/tests/core/pyspec/eth2spec/test/helpers/gloas/fork.py index fc125aa9e5..50732e3df9 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/gloas/fork.py +++ b/tests/core/pyspec/eth2spec/test/helpers/gloas/fork.py @@ -24,17 +24,12 @@ def run_fork_test(post_spec, pre_state): "eth1_data", "eth1_data_votes", "eth1_deposit_index", - "validators", - "balances", "randao_mixes", "slashings", - "previous_epoch_participation", - "current_epoch_participation", "justification_bits", "previous_justified_checkpoint", "current_justified_checkpoint", "finalized_checkpoint", - "inactivity_scores", "current_sync_committee", "next_sync_committee", "next_withdrawal_index", @@ -46,16 +41,23 @@ def run_fork_test(post_spec, pre_state): "earliest_exit_epoch", "consolidation_balance_to_consume", "earliest_consolidation_epoch", - "pending_deposits", - "pending_partial_withdrawals", - "pending_consolidations", "proposer_lookahead", ] for field in stable_fields: assert getattr(pre_state, field) == getattr(post_state, field) # Modified fields - modified_fields = ["fork"] + modified_fields = [ + "fork", + "validators", + "balances", + "previous_epoch_participation", + "current_epoch_participation", + "inactivity_scores", + "pending_deposits", + "pending_partial_withdrawals", + "pending_consolidations", + ] for field in modified_fields: assert getattr(pre_state, field) != getattr(post_state, field) diff --git a/tests/core/pyspec/eth2spec/test/helpers/keys.py b/tests/core/pyspec/eth2spec/test/helpers/keys.py index e69cfa7b54..bf74c50b54 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/keys.py +++ b/tests/core/pyspec/eth2spec/test/helpers/keys.py @@ -8,7 +8,7 @@ } # Enough keys for 256 validators per slot in worst-case epoch length -privkeys = [i + 1 for i in range(32 * 256)] +privkeys = [i + 1 for i in range(32 * 256 + 1)] pubkeys = [bls.SkToPk(privkey) for privkey in privkeys] pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys, pubkeys)} diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py index 8eed4db020..54f605174d 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -107,8 +107,9 @@ def get_random_attester_slashings(spec, state, rng, slashed_indices=[]): return slashings -def get_random_attestations(spec, state, rng): - num_attestations = rng.randrange(1, get_max_attestations(spec)) +def get_random_attestations(spec, state, rng, num_attestations=None): + if num_attestations is None: + num_attestations = rng.randrange(1, get_max_attestations(spec)) attestations = [ get_valid_attestation( diff --git a/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py b/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py index fa6d4c5248..58470f0ee5 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py +++ b/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py @@ -134,3 +134,16 @@ def get_valid_proposer_slashing( signed_header_1=signed_header_1, signed_header_2=signed_header_2, ) + + +def get_valid_proposer_slashings(spec, state, num_slashings): + proposer_slashings = [] + for i in range(num_slashings): + slashed_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[i] + assert not state.validators[slashed_index].slashed + + proposer_slashing = get_valid_proposer_slashing( + spec, state, slashed_index=slashed_index, signed_1=True, signed_2=True + ) + proposer_slashings.append(proposer_slashing) + return proposer_slashings diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index c3572930e7..09d8f47416 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -2,7 +2,6 @@ from lru import LRU -from eth2spec.phase0.mainnet import VALIDATOR_REGISTRY_LIMIT # equal everywhere, fine to import from eth2spec.test.helpers.attestations import ( cached_prepare_state_with_attestations, ) @@ -16,12 +15,12 @@ from eth2spec.test.helpers.state import ( next_epoch, ) -from eth2spec.utils.ssz.ssz_typing import Container, List, uint64 +from eth2spec.utils.ssz.ssz_typing import Container, ProgressiveList, uint64 class Deltas(Container): - rewards: List[uint64, VALIDATOR_REGISTRY_LIMIT] - penalties: List[uint64, VALIDATOR_REGISTRY_LIMIT] + rewards: ProgressiveList[uint64] + penalties: ProgressiveList[uint64] def get_inactivity_penalty_quotient(spec): diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py index 70799da218..e981c8db45 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py @@ -10,6 +10,8 @@ spec_test, with_all_phases, with_custom_state, + with_gloas_and_later, + with_presets, ) from eth2spec.test.helpers.attestations import sign_indexed_attestation from eth2spec.test.helpers.attester_slashings import ( @@ -19,6 +21,7 @@ get_valid_attester_slashing, get_valid_attester_slashing_by_indices, ) +from eth2spec.test.helpers.constants import MINIMAL from eth2spec.test.helpers.proposer_slashings import ( get_min_slashing_penalty_quotient, get_whistleblower_reward_quotient, @@ -528,3 +531,26 @@ def test_invalid_unsorted_att_2(spec, state): sign_indexed_attestation(spec, state, attester_slashing.attestation_2) yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) + + +@with_gloas_and_later +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) +@spec_test +@with_custom_state( + balances_fn=lambda spec: [spec.MAX_EFFECTIVE_BALANCE] + * (spec.MAX_VALIDATORS_PER_COMMITTEE * spec.MAX_COMMITTEES_PER_SLOT + 1), + threshold_fn=lambda spec: spec.config.EJECTION_BALANCE, +) +@single_phase +def test_invalid_too_many_attesting_indices(spec, state): + indices = [ + spec.ValidatorIndex(i) + for i in range(spec.MAX_VALIDATORS_PER_COMMITTEE * spec.MAX_COMMITTEES_PER_SLOT + 1) + ] + attester_slashing = get_valid_attester_slashing_by_indices( + spec, state, indices, signed_1=True, signed_2=True + ) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py index 2cf383d952..346ba03a9e 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py @@ -50,6 +50,7 @@ from eth2spec.test.helpers.proposer_slashings import ( check_proposer_slashing_effect, get_valid_proposer_slashing, + get_valid_proposer_slashings, ) from eth2spec.test.helpers.state import ( get_balance, @@ -544,15 +545,7 @@ def test_multiple_different_proposer_slashings_same_block(spec, state): pre_state = state.copy() num_slashings = 3 - proposer_slashings = [] - for i in range(num_slashings): - slashed_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[i] - assert not state.validators[slashed_index].slashed - - proposer_slashing = get_valid_proposer_slashing( - spec, state, slashed_index=slashed_index, signed_1=True, signed_2=True - ) - proposer_slashings.append(proposer_slashing) + proposer_slashings = get_valid_proposer_slashings(spec, state, num_slashings) yield "pre", state