mirror of
https://github.com/status-im/eth2.0-specs.git
synced 2025-02-19 22:08:13 +00:00
Merge pull request #3828 from potuz/epbs_cl_repo
EIP-7732: Enshrined Proposer-Builder Separation
This commit is contained in:
commit
8f8ab03acf
1
.gitignore
vendored
1
.gitignore
vendored
@ -25,6 +25,7 @@ tests/core/pyspec/eth2spec/electra/
|
||||
tests/core/pyspec/eth2spec/whisk/
|
||||
tests/core/pyspec/eth2spec/eip7594/
|
||||
tests/core/pyspec/eth2spec/eip6800/
|
||||
tests/core/pyspec/eth2spec/eip7732/
|
||||
|
||||
# coverage reports
|
||||
.htmlcov
|
||||
|
2
Makefile
2
Makefile
@ -34,7 +34,7 @@ MARKDOWN_FILES = $(wildcard $(SPEC_DIR)/*/*.md) \
|
||||
$(wildcard $(SPEC_DIR)/_features/*/*/*.md) \
|
||||
$(wildcard $(SSZ_DIR)/*.md)
|
||||
|
||||
ALL_EXECUTABLE_SPEC_NAMES = phase0 altair bellatrix capella deneb electra whisk eip6800
|
||||
ALL_EXECUTABLE_SPEC_NAMES = phase0 altair bellatrix capella deneb electra whisk eip6800 eip7732
|
||||
# The parameters for commands. Use `foreach` to avoid listing specs again.
|
||||
COVERAGE_SCOPE := $(foreach S,$(ALL_EXECUTABLE_SPEC_NAMES), --cov=eth2spec.$S.$(TEST_PRESET_TYPE))
|
||||
PYLINT_SCOPE := $(foreach S,$(ALL_EXECUTABLE_SPEC_NAMES), ./eth2spec/$S)
|
||||
|
@ -59,6 +59,9 @@ EIP7594_FORK_EPOCH: 18446744073709551615
|
||||
# WHISK
|
||||
WHISK_FORK_VERSION: 0x08000000 # temporary stub
|
||||
WHISK_FORK_EPOCH: 18446744073709551615
|
||||
# EIP7732
|
||||
EIP7732_FORK_VERSION: 0x09000000 # temporary stub
|
||||
EIP7732_FORK_EPOCH: 18446744073709551615
|
||||
|
||||
# Time parameters
|
||||
# ---------------------------------------------------------------
|
||||
|
@ -58,6 +58,9 @@ EIP7594_FORK_EPOCH: 18446744073709551615
|
||||
# WHISK
|
||||
WHISK_FORK_VERSION: 0x08000001
|
||||
WHISK_FORK_EPOCH: 18446744073709551615
|
||||
# EIP7732
|
||||
EIP7732_FORK_VERSION: 0x09000001
|
||||
EIP7732_FORK_EPOCH: 18446744073709551615
|
||||
|
||||
# Time parameters
|
||||
# ---------------------------------------------------------------
|
||||
|
9
presets/mainnet/eip-7732.yaml
Normal file
9
presets/mainnet/eip-7732.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
# Mainnet preset - EIP7732
|
||||
|
||||
# Execution
|
||||
# ---------------------------------------------------------------
|
||||
# 2**9 (= 512)
|
||||
PTC_SIZE: 512
|
||||
# 2**2 (= 4)
|
||||
MAX_PAYLOAD_ATTESTATIONS: 4
|
||||
KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_EIP7732: 13
|
9
presets/minimal/eip-7732.yaml
Normal file
9
presets/minimal/eip-7732.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
# Minimal preset - EIP7732
|
||||
|
||||
# Execution
|
||||
# ---------------------------------------------------------------
|
||||
# 2**1(= 2)
|
||||
PTC_SIZE: 2
|
||||
# 2**2 (= 4)
|
||||
MAX_PAYLOAD_ATTESTATIONS: 4
|
||||
KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_EIP7732: 13
|
@ -8,6 +8,7 @@ ELECTRA = 'electra'
|
||||
EIP7594 = 'eip7594'
|
||||
EIP6800 = 'eip6800'
|
||||
WHISK = 'whisk'
|
||||
EIP7732 = 'eip7732'
|
||||
|
||||
|
||||
# The helper functions that are used when defining constants
|
||||
|
@ -124,13 +124,22 @@ def objects_to_spec(preset_name: str,
|
||||
# Keep engine from the most recent fork
|
||||
execution_engine_cls = reduce(lambda txt, builder: builder.execution_engine_cls() or txt, builders, "")
|
||||
|
||||
# Remove deprecated constants
|
||||
deprecate_constants = reduce(lambda obj, builder: obj.union(builder.deprecate_constants()), builders, set())
|
||||
# constant_vars = {k: v for k, v in spec_object.constant_vars.items() if k not in deprecate_constants}
|
||||
filtered_ssz_dep_constants = {k: v for k, v in hardcoded_ssz_dep_constants.items() if k not in deprecate_constants}
|
||||
# Remove deprecated presets
|
||||
deprecate_presets = reduce(lambda obj, builder: obj.union(builder.deprecate_presets()), builders, set())
|
||||
# preset_vars = {k: v for k, v in spec_object.constant_vars.items() if k not in deprecate_constants}
|
||||
filtered_hardcoded_func_dep_presets = {k: v for k, v in hardcoded_func_dep_presets.items() if k not in deprecate_presets}
|
||||
|
||||
constant_vars_spec = '# Constant vars\n' + '\n'.join(format_constant(k, v) for k, v in spec_object.constant_vars.items())
|
||||
preset_vars_spec = '# Preset vars\n' + '\n'.join(format_constant(k, v) for k, v in spec_object.preset_vars.items())
|
||||
ordered_class_objects_spec = '\n\n\n'.join(ordered_class_objects.values())
|
||||
ssz_dep_constants = '\n'.join(map(lambda x: '%s = %s' % (x, hardcoded_ssz_dep_constants[x]), hardcoded_ssz_dep_constants))
|
||||
ssz_dep_constants_verification = '\n'.join(map(lambda x: 'assert %s == %s' % (x, spec_object.ssz_dep_constants[x]), hardcoded_ssz_dep_constants))
|
||||
ssz_dep_constants_verification = '\n'.join(map(lambda x: 'assert %s == %s' % (x, spec_object.ssz_dep_constants[x]), filtered_ssz_dep_constants))
|
||||
custom_type_dep_constants = '\n'.join(map(lambda x: '%s = %s' % (x, hardcoded_custom_type_dep_constants[x]), hardcoded_custom_type_dep_constants))
|
||||
func_dep_presets_verification = '\n'.join(map(lambda x: 'assert %s == %s # noqa: E501' % (x, spec_object.func_dep_presets[x]), hardcoded_func_dep_presets))
|
||||
func_dep_presets_verification = '\n'.join(map(lambda x: 'assert %s == %s # noqa: E501' % (x, spec_object.func_dep_presets[x]), filtered_hardcoded_func_dep_presets))
|
||||
spec_strs = [
|
||||
imports,
|
||||
preparations,
|
||||
|
@ -10,6 +10,7 @@ from .constants import (
|
||||
WHISK,
|
||||
EIP7594,
|
||||
EIP6800,
|
||||
EIP7732,
|
||||
)
|
||||
|
||||
|
||||
@ -23,6 +24,7 @@ PREVIOUS_FORK_OF = {
|
||||
WHISK: CAPELLA,
|
||||
EIP7594: DENEB,
|
||||
EIP6800: DENEB,
|
||||
EIP7732: ELECTRA,
|
||||
}
|
||||
|
||||
ALL_FORKS = list(PREVIOUS_FORK_OF.keys())
|
||||
|
@ -7,12 +7,13 @@ from .electra import ElectraSpecBuilder
|
||||
from .whisk import WhiskSpecBuilder
|
||||
from .eip7594 import EIP7594SpecBuilder
|
||||
from .eip6800 import EIP6800SpecBuilder
|
||||
from .eip7732 import EIP7732SpecBuilder
|
||||
|
||||
|
||||
spec_builders = {
|
||||
builder.fork: builder
|
||||
for builder in (
|
||||
Phase0SpecBuilder, AltairSpecBuilder, BellatrixSpecBuilder, CapellaSpecBuilder, DenebSpecBuilder,
|
||||
ElectraSpecBuilder, WhiskSpecBuilder, EIP7594SpecBuilder, EIP6800SpecBuilder,
|
||||
ElectraSpecBuilder, WhiskSpecBuilder, EIP7594SpecBuilder, EIP6800SpecBuilder, EIP7732SpecBuilder,
|
||||
)
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ def get_generalized_index(ssz_class: Any, *path: PyUnion[int, SSZVariableName])
|
||||
|
||||
|
||||
def compute_merkle_proof(object: SSZObject,
|
||||
index: GeneralizedIndex) -> Sequence[Bytes32]:
|
||||
index: GeneralizedIndex) -> list[Bytes32]:
|
||||
return build_proof(object.get_backing(), index)'''
|
||||
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Sequence, Dict
|
||||
from typing import Sequence, Dict, Set
|
||||
from pathlib import Path
|
||||
|
||||
class BaseSpecBuilder(ABC):
|
||||
@ -54,3 +54,11 @@ class BaseSpecBuilder(ABC):
|
||||
@classmethod
|
||||
def implement_optimizations(cls, functions: Dict[str, str]) -> Dict[str, str]:
|
||||
return functions
|
||||
|
||||
@classmethod
|
||||
def deprecate_constants(cls) -> Set[str]:
|
||||
return set()
|
||||
|
||||
@classmethod
|
||||
def deprecate_presets(cls) -> Set[str]:
|
||||
return set()
|
||||
|
45
pysetup/spec_builders/eip7732.py
Normal file
45
pysetup/spec_builders/eip7732.py
Normal file
@ -0,0 +1,45 @@
|
||||
from typing import Dict, Set
|
||||
|
||||
from .base import BaseSpecBuilder
|
||||
from ..constants import EIP7732
|
||||
|
||||
|
||||
class EIP7732SpecBuilder(BaseSpecBuilder):
|
||||
fork: str = EIP7732
|
||||
|
||||
@classmethod
|
||||
def imports(cls, preset_name: str):
|
||||
return f'''
|
||||
from eth2spec.electra import {preset_name} as electra
|
||||
'''
|
||||
|
||||
@classmethod
|
||||
def sundry_functions(cls) -> str:
|
||||
return '''
|
||||
def concat_generalized_indices(*indices: GeneralizedIndex) -> GeneralizedIndex:
|
||||
o = GeneralizedIndex(1)
|
||||
for i in indices:
|
||||
o = GeneralizedIndex(o * bit_floor(i) + (i - bit_floor(i)))
|
||||
return o'''
|
||||
|
||||
|
||||
@classmethod
|
||||
def hardcoded_custom_type_dep_constants(cls, spec_object) -> Dict[str, str]:
|
||||
return {
|
||||
'PTC_SIZE': spec_object.preset_vars['PTC_SIZE'].value,
|
||||
'MAX_PAYLOAD_ATTESTATIONS': spec_object.preset_vars['MAX_PAYLOAD_ATTESTATIONS'].value,
|
||||
'KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_EIP7732':
|
||||
spec_object.preset_vars['KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_EIP7732'].value,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def deprecate_constants(cls) -> Set[str]:
|
||||
return set([
|
||||
'EXECUTION_PAYLOAD_GINDEX',
|
||||
])
|
||||
|
||||
@classmethod
|
||||
def deprecate_presets(cls) -> Set[str]:
|
||||
return set([
|
||||
'KZG_COMMITMENT_INCLUSION_PROOF_DEPTH',
|
||||
])
|
716
specs/_features/eip7732/beacon-chain.md
Normal file
716
specs/_features/eip7732/beacon-chain.md
Normal file
@ -0,0 +1,716 @@
|
||||
# EIP-7732 -- The Beacon Chain
|
||||
|
||||
## Table of contents
|
||||
|
||||
<!-- TOC -->
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Constants](#constants)
|
||||
- [Payload status](#payload-status)
|
||||
- [Preset](#preset)
|
||||
- [Misc](#misc)
|
||||
- [Domain types](#domain-types)
|
||||
- [Max operations per block](#max-operations-per-block)
|
||||
- [Containers](#containers)
|
||||
- [New containers](#new-containers)
|
||||
- [`PayloadAttestationData`](#payloadattestationdata)
|
||||
- [`PayloadAttestation`](#payloadattestation)
|
||||
- [`PayloadAttestationMessage`](#payloadattestationmessage)
|
||||
- [`IndexedPayloadAttestation`](#indexedpayloadattestation)
|
||||
- [`SignedExecutionPayloadHeader`](#signedexecutionpayloadheader)
|
||||
- [`ExecutionPayloadEnvelope`](#executionpayloadenvelope)
|
||||
- [`SignedExecutionPayloadEnvelope`](#signedexecutionpayloadenvelope)
|
||||
- [Modified containers](#modified-containers)
|
||||
- [`BeaconBlockBody`](#beaconblockbody)
|
||||
- [`ExecutionPayloadHeader`](#executionpayloadheader)
|
||||
- [`BeaconState`](#beaconstate)
|
||||
- [Helper functions](#helper-functions)
|
||||
- [Math](#math)
|
||||
- [`bit_floor`](#bit_floor)
|
||||
- [Misc](#misc-1)
|
||||
- [`remove_flag`](#remove_flag)
|
||||
- [Predicates](#predicates)
|
||||
- [`is_valid_indexed_payload_attestation`](#is_valid_indexed_payload_attestation)
|
||||
- [`is_parent_block_full`](#is_parent_block_full)
|
||||
- [Beacon State accessors](#beacon-state-accessors)
|
||||
- [`get_ptc`](#get_ptc)
|
||||
- [Modified `get_attesting_indices`](#modified-get_attesting_indices)
|
||||
- [`get_payload_attesting_indices`](#get_payload_attesting_indices)
|
||||
- [`get_indexed_payload_attestation`](#get_indexed_payload_attestation)
|
||||
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
|
||||
- [Block processing](#block-processing)
|
||||
- [Withdrawals](#withdrawals)
|
||||
- [Modified `process_withdrawals`](#modified-process_withdrawals)
|
||||
- [Execution payload header](#execution-payload-header)
|
||||
- [New `verify_execution_payload_header_signature`](#new-verify_execution_payload_header_signature)
|
||||
- [New `process_execution_payload_header`](#new-process_execution_payload_header)
|
||||
- [Operations](#operations)
|
||||
- [Modified `process_operations`](#modified-process_operations)
|
||||
- [Payload Attestations](#payload-attestations)
|
||||
- [`process_payload_attestation`](#process_payload_attestation)
|
||||
- [Modified `process_execution_payload`](#modified-process_execution_payload)
|
||||
- [New `verify_execution_payload_envelope_signature`](#new-verify_execution_payload_envelope_signature)
|
||||
- [Modified `is_merge_transition_complete`](#modified-is_merge_transition_complete)
|
||||
- [Modified `validate_merge_block`](#modified-validate_merge_block)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- /TOC -->
|
||||
|
||||
## Introduction
|
||||
|
||||
This is the beacon chain specification of the enshrined proposer builder separation feature.
|
||||
|
||||
*Note:* This specification is built upon [Electra](../../electra/beacon-chain.md) and is under active development.
|
||||
|
||||
This feature adds new staked consensus participants called *Builders* and new honest validators duties called *payload timeliness attestations*. The slot is divided in **four** intervals. Honest validators gather *signed bids* (a `SignedExecutionPayloadHeader`) from builders and submit their consensus blocks (a `SignedBeaconBlock`) including these bids at the beginning of the slot. At the start of the second interval, honest validators submit attestations just as they do previous to this feature). At the start of the third interval, aggregators aggregate these attestations and the builder broadcasts either a full payload or a message indicating that they are withholding the payload (a `SignedExecutionPayloadEnvelope`). At the start of the fourth interval, some validators selected to be members of the new **Payload Timeliness Committee** (PTC) attest to the presence and timeliness of the builder's payload.
|
||||
|
||||
At any given slot, the status of the blockchain's head may be either
|
||||
- A block from a previous slot (e.g. the current slot's proposer did not submit its block).
|
||||
- An *empty* block from the current slot (e.g. the proposer submitted a timely block, but the builder did not reveal the payload on time).
|
||||
- A full block for the current slot (both the proposer and the builder revealed on time).
|
||||
|
||||
## Constants
|
||||
|
||||
### Payload status
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `PAYLOAD_ABSENT` | `uint8(0)` |
|
||||
| `PAYLOAD_PRESENT` | `uint8(1)` |
|
||||
| `PAYLOAD_WITHHELD` | `uint8(2)` |
|
||||
| `PAYLOAD_INVALID_STATUS` | `uint8(3)` |
|
||||
|
||||
## Preset
|
||||
|
||||
### Misc
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `PTC_SIZE` | `uint64(2**9)` (=512) # (New in EIP-7732) |
|
||||
|
||||
### Domain types
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `DOMAIN_BEACON_BUILDER` | `DomainType('0x1B000000')` # (New in EIP-7732)|
|
||||
| `DOMAIN_PTC_ATTESTER` | `DomainType('0x0C000000')` # (New in EIP-7732)|
|
||||
|
||||
### Max operations per block
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `MAX_PAYLOAD_ATTESTATIONS` | `2**2` (= 4) # (New in EIP-7732) |
|
||||
|
||||
## Containers
|
||||
|
||||
### New containers
|
||||
|
||||
#### `PayloadAttestationData`
|
||||
|
||||
```python
|
||||
class PayloadAttestationData(Container):
|
||||
beacon_block_root: Root
|
||||
slot: Slot
|
||||
payload_status: uint8
|
||||
```
|
||||
|
||||
#### `PayloadAttestation`
|
||||
|
||||
```python
|
||||
class PayloadAttestation(Container):
|
||||
aggregation_bits: Bitvector[PTC_SIZE]
|
||||
data: PayloadAttestationData
|
||||
signature: BLSSignature
|
||||
```
|
||||
|
||||
#### `PayloadAttestationMessage`
|
||||
|
||||
```python
|
||||
class PayloadAttestationMessage(Container):
|
||||
validator_index: ValidatorIndex
|
||||
data: PayloadAttestationData
|
||||
signature: BLSSignature
|
||||
```
|
||||
|
||||
#### `IndexedPayloadAttestation`
|
||||
|
||||
```python
|
||||
class IndexedPayloadAttestation(Container):
|
||||
attesting_indices: List[ValidatorIndex, PTC_SIZE]
|
||||
data: PayloadAttestationData
|
||||
signature: BLSSignature
|
||||
```
|
||||
|
||||
#### `SignedExecutionPayloadHeader`
|
||||
|
||||
```python
|
||||
class SignedExecutionPayloadHeader(Container):
|
||||
message: ExecutionPayloadHeader
|
||||
signature: BLSSignature
|
||||
```
|
||||
|
||||
#### `ExecutionPayloadEnvelope`
|
||||
|
||||
```python
|
||||
class ExecutionPayloadEnvelope(Container):
|
||||
payload: ExecutionPayload
|
||||
builder_index: ValidatorIndex
|
||||
beacon_block_root: Root
|
||||
blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]
|
||||
payload_withheld: boolean
|
||||
state_root: Root
|
||||
```
|
||||
|
||||
#### `SignedExecutionPayloadEnvelope`
|
||||
|
||||
```python
|
||||
class SignedExecutionPayloadEnvelope(Container):
|
||||
message: ExecutionPayloadEnvelope
|
||||
signature: BLSSignature
|
||||
```
|
||||
|
||||
### Modified containers
|
||||
|
||||
#### `BeaconBlockBody`
|
||||
|
||||
**Note:** The Beacon Block body is modified to contain a `Signed ExecutionPayloadHeader`. The containers `BeaconBlock` and `SignedBeaconBlock` are modified indirectly.
|
||||
|
||||
```python
|
||||
class BeaconBlockBody(Container):
|
||||
randao_reveal: BLSSignature
|
||||
eth1_data: Eth1Data # Eth1 data vote
|
||||
graffiti: Bytes32 # Arbitrary data
|
||||
# Operations
|
||||
proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS]
|
||||
attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS]
|
||||
attestations: List[Attestation, MAX_ATTESTATIONS]
|
||||
deposits: List[Deposit, MAX_DEPOSITS]
|
||||
voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS]
|
||||
sync_aggregate: SyncAggregate
|
||||
# Execution
|
||||
# Removed execution_payload [Removed in EIP-7732]
|
||||
# Removed blob_kzg_commitments [Removed in EIP-7732]
|
||||
bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES]
|
||||
# PBS
|
||||
signed_execution_payload_header: SignedExecutionPayloadHeader # [New in EIP-7732]
|
||||
payload_attestations: List[PayloadAttestation, MAX_PAYLOAD_ATTESTATIONS] # [New in EIP-7732]
|
||||
```
|
||||
|
||||
#### `ExecutionPayloadHeader`
|
||||
|
||||
**Note:** The `ExecutionPayloadHeader` is modified to only contain the block hash of the committed `ExecutionPayload` in addition to the builder's payment information, gas limit and KZG commitments root to verify the inclusion proofs.
|
||||
|
||||
```python
|
||||
class ExecutionPayloadHeader(Container):
|
||||
parent_block_hash: Hash32
|
||||
parent_block_root: Root
|
||||
block_hash: Hash32
|
||||
gas_limit: uint64
|
||||
builder_index: ValidatorIndex
|
||||
slot: Slot
|
||||
value: Gwei
|
||||
blob_kzg_commitments_root: Root
|
||||
```
|
||||
|
||||
#### `BeaconState`
|
||||
|
||||
*Note*: The `BeaconState` is modified to track the last withdrawals honored in the CL. The `latest_execution_payload_header` is modified semantically to refer not to a past committed `ExecutionPayload` but instead it corresponds to the state's slot builder's bid. Another addition is to track the last committed block hash and the last slot that was full, that is in which there were both consensus and execution blocks included.
|
||||
|
||||
```python
|
||||
class BeaconState(Container):
|
||||
# Versioning
|
||||
genesis_time: uint64
|
||||
genesis_validators_root: Root
|
||||
slot: Slot
|
||||
fork: Fork
|
||||
# History
|
||||
latest_block_header: BeaconBlockHeader
|
||||
block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
|
||||
state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
|
||||
historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] # Frozen in Capella, replaced by historical_summaries
|
||||
# Eth1
|
||||
eth1_data: Eth1Data
|
||||
eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH]
|
||||
eth1_deposit_index: uint64
|
||||
# Registry
|
||||
validators: List[Validator, VALIDATOR_REGISTRY_LIMIT]
|
||||
balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT]
|
||||
# Randomness
|
||||
randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR]
|
||||
# Slashings
|
||||
slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances
|
||||
# Participation
|
||||
previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT]
|
||||
current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT]
|
||||
# Finality
|
||||
justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch
|
||||
previous_justified_checkpoint: Checkpoint
|
||||
current_justified_checkpoint: Checkpoint
|
||||
finalized_checkpoint: Checkpoint
|
||||
# Inactivity
|
||||
inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT]
|
||||
# Sync
|
||||
current_sync_committee: SyncCommittee
|
||||
next_sync_committee: SyncCommittee
|
||||
# Execution
|
||||
latest_execution_payload_header: ExecutionPayloadHeader
|
||||
# Withdrawals
|
||||
next_withdrawal_index: WithdrawalIndex
|
||||
next_withdrawal_validator_index: ValidatorIndex
|
||||
# Deep history valid from Capella onwards
|
||||
historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT]
|
||||
deposit_requests_start_index: uint64
|
||||
deposit_balance_to_consume: Gwei
|
||||
exit_balance_to_consume: Gwei
|
||||
earliest_exit_epoch: Epoch
|
||||
consolidation_balance_to_consume: Gwei
|
||||
earliest_consolidation_epoch: Epoch
|
||||
pending_balance_deposits: List[PendingBalanceDeposit, PENDING_BALANCE_DEPOSITS_LIMIT]
|
||||
pending_partial_withdrawals: List[PendingPartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT]
|
||||
pending_consolidations: List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT]
|
||||
# PBS
|
||||
latest_block_hash: Hash32 # [New in EIP-7732]
|
||||
latest_full_slot: Slot # [New in EIP-7732]
|
||||
latest_withdrawals_root: Root # [New in EIP-7732]
|
||||
```
|
||||
|
||||
## Helper functions
|
||||
|
||||
### Math
|
||||
|
||||
#### `bit_floor`
|
||||
|
||||
```python
|
||||
def bit_floor(n: uint64) -> uint64:
|
||||
"""
|
||||
if ``n`` is not zero, returns the largest power of `2` that is not greater than `n`.
|
||||
"""
|
||||
if n == 0:
|
||||
return 0
|
||||
return uint64(1) << (n.bit_length() - 1)
|
||||
```
|
||||
|
||||
### Misc
|
||||
|
||||
#### `remove_flag`
|
||||
|
||||
```python
|
||||
def remove_flag(flags: ParticipationFlags, flag_index: int) -> ParticipationFlags:
|
||||
flag = ParticipationFlags(2**flag_index)
|
||||
return flags & ~flag
|
||||
```
|
||||
|
||||
### Predicates
|
||||
|
||||
#### `is_valid_indexed_payload_attestation`
|
||||
|
||||
```python
|
||||
def is_valid_indexed_payload_attestation(
|
||||
state: BeaconState,
|
||||
indexed_payload_attestation: IndexedPayloadAttestation) -> bool:
|
||||
"""
|
||||
Check if ``indexed_payload_attestation`` is not empty, has sorted and unique indices and has
|
||||
a valid aggregate signature.
|
||||
"""
|
||||
# Verify the data is valid
|
||||
if indexed_payload_attestation.data.payload_status >= PAYLOAD_INVALID_STATUS:
|
||||
return False
|
||||
|
||||
# Verify indices are sorted and unique
|
||||
indices = indexed_payload_attestation.attesting_indices
|
||||
if len(indices) == 0 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_PTC_ATTESTER, None)
|
||||
signing_root = compute_signing_root(indexed_payload_attestation.data, domain)
|
||||
return bls.FastAggregateVerify(pubkeys, signing_root, indexed_payload_attestation.signature)
|
||||
```
|
||||
|
||||
#### `is_parent_block_full`
|
||||
|
||||
This function returns true if the last committed payload header was fulfilled with a payload, this can only happen when both beacon block and payload were present. This function must be called on a beacon state before processing the execution payload header in the block.
|
||||
|
||||
```python
|
||||
def is_parent_block_full(state: BeaconState) -> bool:
|
||||
return state.latest_execution_payload_header.block_hash == state.latest_block_hash
|
||||
```
|
||||
|
||||
### Beacon State accessors
|
||||
|
||||
#### `get_ptc`
|
||||
|
||||
```python
|
||||
def get_ptc(state: BeaconState, slot: Slot) -> Vector[ValidatorIndex, PTC_SIZE]:
|
||||
"""
|
||||
Get the payload timeliness committee for the given ``slot``
|
||||
"""
|
||||
epoch = compute_epoch_at_slot(slot)
|
||||
committees_per_slot = bit_floor(min(get_committee_count_per_slot(state, epoch), PTC_SIZE))
|
||||
members_per_committee = PTC_SIZE // committees_per_slot
|
||||
|
||||
validator_indices: List[ValidatorIndex] = []
|
||||
for idx in range(committees_per_slot):
|
||||
beacon_committee = get_beacon_committee(state, slot, CommitteeIndex(idx))
|
||||
validator_indices += beacon_committee[:members_per_committee]
|
||||
return validator_indices
|
||||
```
|
||||
|
||||
#### Modified `get_attesting_indices`
|
||||
|
||||
`get_attesting_indices` is modified to ignore PTC votes
|
||||
|
||||
```python
|
||||
def get_attesting_indices(state: BeaconState, attestation: Attestation) -> Set[ValidatorIndex]:
|
||||
"""
|
||||
Return the set of attesting indices corresponding to ``aggregation_bits`` and ``committee_bits``.
|
||||
"""
|
||||
output: Set[ValidatorIndex] = set()
|
||||
committee_indices = get_committee_indices(attestation.committee_bits)
|
||||
committee_offset = 0
|
||||
for index in committee_indices:
|
||||
committee = get_beacon_committee(state, attestation.data.slot, index)
|
||||
committee_attesters = set(
|
||||
index for i, index in enumerate(committee) if attestation.aggregation_bits[committee_offset + i])
|
||||
output = output.union(committee_attesters)
|
||||
committee_offset += len(committee)
|
||||
|
||||
if compute_epoch_at_slot(attestation.data.slot) < EIP7732_FORK_EPOCH:
|
||||
return output
|
||||
ptc = get_ptc(state, attestation.data.slot)
|
||||
return set(i for i in output if i not in ptc)
|
||||
```
|
||||
|
||||
#### `get_payload_attesting_indices`
|
||||
|
||||
```python
|
||||
def get_payload_attesting_indices(state: BeaconState, slot: Slot,
|
||||
payload_attestation: PayloadAttestation) -> Set[ValidatorIndex]:
|
||||
"""
|
||||
Return the set of attesting indices corresponding to ``payload_attestation``.
|
||||
"""
|
||||
ptc = get_ptc(state, slot)
|
||||
return set(index for i, index in enumerate(ptc) if payload_attestation.aggregation_bits[i])
|
||||
```
|
||||
|
||||
#### `get_indexed_payload_attestation`
|
||||
|
||||
```python
|
||||
def get_indexed_payload_attestation(state: BeaconState, slot: Slot,
|
||||
payload_attestation: PayloadAttestation) -> IndexedPayloadAttestation:
|
||||
"""
|
||||
Return the indexed payload attestation corresponding to ``payload_attestation``.
|
||||
"""
|
||||
attesting_indices = get_payload_attesting_indices(state, slot, payload_attestation)
|
||||
|
||||
return IndexedPayloadAttestation(
|
||||
attesting_indices=sorted(attesting_indices),
|
||||
data=payload_attestation.data,
|
||||
signature=payload_attestation.signature,
|
||||
)
|
||||
```
|
||||
|
||||
## Beacon chain state transition function
|
||||
|
||||
*Note*: state transition is fundamentally modified in EIP-7732. The full state transition is broken in two parts, first importing a signed block and then importing an execution payload.
|
||||
|
||||
The post-state corresponding to a pre-state `state` and a signed beacon block `signed_block` is defined as `state_transition(state, signed_block)`. State transitions that trigger an unhandled exception (e.g. a failed `assert` or an out-of-range list access) are considered invalid. State transitions that cause a `uint64` overflow or underflow are also considered invalid.
|
||||
|
||||
The post-state corresponding to a pre-state `state` and a signed execution payload envelope `signed_envelope` is defined as `process_execution_payload(state, signed_envelope)`. State transitions that trigger an unhandled exception (e.g. a failed `assert` or an out-of-range list access) are considered invalid. State transitions that cause an `uint64` overflow or underflow are also considered invalid.
|
||||
|
||||
### Block processing
|
||||
|
||||
```python
|
||||
def process_block(state: BeaconState, block: BeaconBlock) -> None:
|
||||
process_block_header(state, block)
|
||||
process_withdrawals(state) # [Modified in EIP-7732]
|
||||
process_execution_payload_header(state, block) # [Modified in EIP-7732, removed process_execution_payload]
|
||||
process_randao(state, block.body)
|
||||
process_eth1_data(state, block.body)
|
||||
process_operations(state, block.body) # [Modified in EIP-7732]
|
||||
process_sync_aggregate(state, block.body.sync_aggregate)
|
||||
```
|
||||
|
||||
#### Withdrawals
|
||||
|
||||
##### Modified `process_withdrawals`
|
||||
|
||||
**Note:** This is modified to take only the `state` as parameter. Withdrawals are deterministic given the beacon state, any execution payload that has the corresponding block as parent beacon block is required to honor these withdrawals in the execution layer. This function must be called before `process_execution_payload_header` as this latter function affects validator balances.
|
||||
|
||||
```python
|
||||
def process_withdrawals(state: BeaconState) -> None:
|
||||
# return early if the parent block was empty
|
||||
if not is_parent_block_full(state):
|
||||
return
|
||||
|
||||
withdrawals, partial_withdrawals_count = get_expected_withdrawals(state)
|
||||
withdrawals_list = List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD](withdrawals)
|
||||
state.latest_withdrawals_root = hash_tree_root(withdrawals_list)
|
||||
for withdrawal in withdrawals:
|
||||
decrease_balance(state, withdrawal.validator_index, withdrawal.amount)
|
||||
|
||||
# Update pending partial withdrawals
|
||||
state.pending_partial_withdrawals = state.pending_partial_withdrawals[partial_withdrawals_count:]
|
||||
|
||||
# Update the next withdrawal index if this block contained withdrawals
|
||||
if len(withdrawals) != 0:
|
||||
latest_withdrawal = withdrawals[-1]
|
||||
state.next_withdrawal_index = WithdrawalIndex(latest_withdrawal.index + 1)
|
||||
|
||||
# Update the next validator index to start the next withdrawal sweep
|
||||
if len(withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD:
|
||||
# Next sweep starts after the latest withdrawal's validator index
|
||||
next_validator_index = ValidatorIndex((withdrawals[-1].validator_index + 1) % len(state.validators))
|
||||
state.next_withdrawal_validator_index = next_validator_index
|
||||
else:
|
||||
# Advance sweep by the max length of the sweep if there was not a full set of withdrawals
|
||||
next_index = state.next_withdrawal_validator_index + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP
|
||||
next_validator_index = ValidatorIndex(next_index % len(state.validators))
|
||||
state.next_withdrawal_validator_index = next_validator_index
|
||||
```
|
||||
|
||||
#### Execution payload header
|
||||
|
||||
##### New `verify_execution_payload_header_signature`
|
||||
|
||||
```python
|
||||
def verify_execution_payload_header_signature(state: BeaconState,
|
||||
signed_header: SignedExecutionPayloadHeader) -> bool:
|
||||
# Check the signature
|
||||
builder = state.validators[signed_header.message.builder_index]
|
||||
signing_root = compute_signing_root(signed_header.message, get_domain(state, DOMAIN_BEACON_BUILDER))
|
||||
return bls.Verify(builder.pubkey, signing_root, signed_header.signature)
|
||||
```
|
||||
|
||||
##### New `process_execution_payload_header`
|
||||
|
||||
```python
|
||||
def process_execution_payload_header(state: BeaconState, block: BeaconBlock) -> None:
|
||||
# Verify the header signature
|
||||
signed_header = block.body.signed_execution_payload_header
|
||||
assert verify_execution_payload_header_signature(state, signed_header)
|
||||
|
||||
# Check that the builder has funds to cover the bid
|
||||
header = signed_header.message
|
||||
builder_index = header.builder_index
|
||||
amount = header.value
|
||||
assert state.balances[builder_index] >= amount
|
||||
|
||||
# Verify that the bid is for the current slot
|
||||
assert header.slot == block.slot
|
||||
# Verify that the bid is for the right parent block
|
||||
assert header.parent_block_hash == state.latest_block_hash
|
||||
assert header.parent_block_root == block.parent_root
|
||||
|
||||
# Transfer the funds from the builder to the proposer
|
||||
decrease_balance(state, builder_index, amount)
|
||||
increase_balance(state, block.proposer_index, amount)
|
||||
|
||||
# Cache the signed execution payload header
|
||||
state.latest_execution_payload_header = header
|
||||
```
|
||||
|
||||
#### Operations
|
||||
|
||||
##### Modified `process_operations`
|
||||
|
||||
**Note:** `process_operations` is modified to process PTC attestations
|
||||
|
||||
```python
|
||||
def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
|
||||
# Verify that outstanding deposits are processed up to the maximum number of deposits
|
||||
assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index)
|
||||
|
||||
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)
|
||||
for_ops(body.deposits, process_deposit)
|
||||
for_ops(body.voluntary_exits, process_voluntary_exit)
|
||||
for_ops(body.bls_to_execution_changes, process_bls_to_execution_change)
|
||||
# Removed `process_deposit_request` in EIP-7732
|
||||
# Removed `process_withdrawal_request` in EIP-7732
|
||||
# Removed `process_consolidation_request` in EIP-7732
|
||||
for_ops(body.payload_attestations, process_payload_attestation) # [New in EIP-7732]
|
||||
```
|
||||
|
||||
##### Payload Attestations
|
||||
|
||||
###### `process_payload_attestation`
|
||||
|
||||
```python
|
||||
def process_payload_attestation(state: BeaconState, payload_attestation: PayloadAttestation) -> None:
|
||||
# Check that the attestation is for the parent beacon block
|
||||
data = payload_attestation.data
|
||||
assert data.beacon_block_root == state.latest_block_header.parent_root
|
||||
# Check that the attestation is for the previous slot
|
||||
assert data.slot + 1 == state.slot
|
||||
|
||||
# Verify signature
|
||||
indexed_payload_attestation = get_indexed_payload_attestation(state, data.slot, payload_attestation)
|
||||
assert is_valid_indexed_payload_attestation(state, indexed_payload_attestation)
|
||||
|
||||
if state.slot % SLOTS_PER_EPOCH == 0:
|
||||
epoch_participation = state.previous_epoch_participation
|
||||
else:
|
||||
epoch_participation = state.current_epoch_participation
|
||||
|
||||
# Return early if the attestation is for the wrong payload status
|
||||
payload_was_present = data.slot == state.latest_full_slot
|
||||
voted_present = data.payload_status == PAYLOAD_PRESENT
|
||||
proposer_reward_denominator = (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT
|
||||
proposer_index = get_beacon_proposer_index(state)
|
||||
if voted_present != payload_was_present:
|
||||
# Unset the flags in case they were set by an equivocating ptc attestation
|
||||
proposer_penalty_numerator = 0
|
||||
for index in indexed_payload_attestation.attesting_indices:
|
||||
for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS):
|
||||
if has_flag(epoch_participation[index], flag_index):
|
||||
epoch_participation[index] = remove_flag(epoch_participation[index], flag_index)
|
||||
proposer_penalty_numerator += get_base_reward(state, index) * weight
|
||||
# Penalize the proposer
|
||||
proposer_penalty = Gwei(2 * proposer_penalty_numerator // proposer_reward_denominator)
|
||||
decrease_balance(state, proposer_index, proposer_penalty)
|
||||
return
|
||||
|
||||
# Reward the proposer and set all the participation flags in case of correct attestations
|
||||
proposer_reward_numerator = 0
|
||||
for index in indexed_payload_attestation.attesting_indices:
|
||||
for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS):
|
||||
if not has_flag(epoch_participation[index], flag_index):
|
||||
epoch_participation[index] = add_flag(epoch_participation[index], flag_index)
|
||||
proposer_reward_numerator += get_base_reward(state, index) * weight
|
||||
|
||||
# Reward proposer
|
||||
proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator)
|
||||
increase_balance(state, proposer_index, proposer_reward)
|
||||
```
|
||||
|
||||
#### Modified `process_execution_payload`
|
||||
|
||||
##### New `verify_execution_payload_envelope_signature`
|
||||
|
||||
```python
|
||||
def verify_execution_payload_envelope_signature(
|
||||
state: BeaconState, signed_envelope: SignedExecutionPayloadEnvelope) -> bool:
|
||||
builder = state.validators[signed_envelope.message.builder_index]
|
||||
signing_root = compute_signing_root(signed_envelope.message, get_domain(state, DOMAIN_BEACON_BUILDER))
|
||||
return bls.Verify(builder.pubkey, signing_root, signed_envelope.signature)
|
||||
```
|
||||
|
||||
*Note*: `process_execution_payload` is now an independent check in state transition. It is called when importing a signed execution payload proposed by the builder of the current slot.
|
||||
|
||||
```python
|
||||
def process_execution_payload(state: BeaconState,
|
||||
signed_envelope: SignedExecutionPayloadEnvelope,
|
||||
execution_engine: ExecutionEngine, verify: bool = True) -> None:
|
||||
# Verify signature
|
||||
if verify:
|
||||
assert verify_execution_payload_envelope_signature(state, signed_envelope)
|
||||
envelope = signed_envelope.message
|
||||
payload = envelope.payload
|
||||
# Cache latest block header state root
|
||||
previous_state_root = hash_tree_root(state)
|
||||
if state.latest_block_header.state_root == Root():
|
||||
state.latest_block_header.state_root = previous_state_root
|
||||
|
||||
# Verify consistency with the beacon block
|
||||
assert envelope.beacon_block_root == hash_tree_root(state.latest_block_header)
|
||||
|
||||
# Verify consistency with the committed header
|
||||
committed_header = state.latest_execution_payload_header
|
||||
assert envelope.builder_index == committed_header.builder_index
|
||||
assert committed_header.blob_kzg_commitments_root == hash_tree_root(envelope.blob_kzg_commitments)
|
||||
|
||||
if not envelope.payload_withheld:
|
||||
# Verify the withdrawals root
|
||||
assert hash_tree_root(payload.withdrawals) == state.latest_withdrawals_root
|
||||
|
||||
# Verify the gas_limit
|
||||
assert committed_header.gas_limit == payload.gas_limit
|
||||
|
||||
assert committed_header.block_hash == payload.block_hash
|
||||
# Verify consistency of the parent hash with respect to the previous execution payload
|
||||
assert payload.parent_hash == state.latest_block_hash
|
||||
# Verify prev_randao
|
||||
assert payload.prev_randao == get_randao_mix(state, get_current_epoch(state))
|
||||
# Verify timestamp
|
||||
assert payload.timestamp == compute_timestamp_at_slot(state, state.slot)
|
||||
# Verify commitments are under limit
|
||||
assert len(envelope.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK
|
||||
# Verify the execution payload is valid
|
||||
versioned_hashes = [kzg_commitment_to_versioned_hash(commitment)
|
||||
for commitment in envelope.blob_kzg_commitments]
|
||||
assert execution_engine.verify_and_notify_new_payload(
|
||||
NewPayloadRequest(
|
||||
execution_payload=payload,
|
||||
versioned_hashes=versioned_hashes,
|
||||
parent_beacon_block_root=state.latest_block_header.parent_root,
|
||||
)
|
||||
)
|
||||
|
||||
# Process Electra operations
|
||||
def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None:
|
||||
for operation in operations:
|
||||
fn(state, operation)
|
||||
|
||||
for_ops(payload.deposit_requests, process_deposit_request)
|
||||
for_ops(payload.withdrawal_requests, process_withdrawal_request)
|
||||
for_ops(payload, process_consolidation_request)
|
||||
|
||||
# Cache the execution payload header and proposer
|
||||
state.latest_block_hash = payload.block_hash
|
||||
state.latest_full_slot = state.slot
|
||||
|
||||
# Verify the state root
|
||||
if verify:
|
||||
assert envelope.state_root == hash_tree_root(state)
|
||||
```
|
||||
|
||||
#### Modified `is_merge_transition_complete`
|
||||
|
||||
`is_merge_transition_complete` is modified only for testing purposes to add the blob kzg commitments root for an empty list
|
||||
|
||||
```python
|
||||
def is_merge_transition_complete(state: BeaconState) -> bool:
|
||||
header = ExecutionPayloadHeader()
|
||||
kzgs = List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]()
|
||||
header.blob_kzg_commitments_root = kzgs.hash_tree_root()
|
||||
|
||||
return state.latest_execution_payload_header != header
|
||||
```
|
||||
|
||||
#### Modified `validate_merge_block`
|
||||
`validate_merge_block` is modified to use the new `signed_execution_payload_header` message in the Beacon Block Body
|
||||
|
||||
```python
|
||||
def validate_merge_block(block: BeaconBlock) -> None:
|
||||
"""
|
||||
Check the parent PoW block of execution payload is a valid terminal PoW block.
|
||||
|
||||
Note: Unavailable PoW block(s) may later become available,
|
||||
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` 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.signed_execution_payload_header.message.parent_block_hash == TERMINAL_BLOCK_HASH
|
||||
return
|
||||
|
||||
# Modified in EIP-7732
|
||||
pow_block = get_pow_block(block.body.signed_execution_payload_header.message.parent_block_hash)
|
||||
# Check if `pow_block` is available
|
||||
assert pow_block is not None
|
||||
pow_parent = get_pow_block(pow_block.parent_hash)
|
||||
# Check if `pow_parent` is available
|
||||
assert pow_parent is not None
|
||||
# Check if `pow_block` is a valid terminal PoW block
|
||||
assert is_valid_terminal_pow_block(pow_block, pow_parent)
|
||||
```
|
128
specs/_features/eip7732/builder.md
Normal file
128
specs/_features/eip7732/builder.md
Normal file
@ -0,0 +1,128 @@
|
||||
# EIP-7732 -- Honest Builder
|
||||
|
||||
This is an accompanying document which describes the expected actions of a "builder" participating in the Ethereum proof-of-stake protocol.
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Builders attributions](#builders-attributions)
|
||||
- [Constructing the payload bid](#constructing-the-payload-bid)
|
||||
- [Constructing the `BlobSidecar`s](#constructing-the-blobsidecars)
|
||||
- [Constructing the execution payload envelope](#constructing-the-execution-payload-envelope)
|
||||
- [Honest payload withheld messages](#honest-payload-withheld-messages)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
## Introduction
|
||||
|
||||
With the EIP-7732 Fork, the protocol includes new staked participants of the protocol called *Builders*. While Builders are a subset of the validator set, they have extra attributions that are optional. Validators may opt to not be builders and as such we collect the set of guidelines for those validators that want to act as builders in this document.
|
||||
|
||||
## Builders attributions
|
||||
|
||||
Builders can submit bids to produce execution payloads. They can broadcast these bids in the form of `SignedExecutionPayloadHeader` objects, these objects encode a commitment to reveal an execution payload in exchange for a payment. When their bids are chosen by the corresponding proposer, builders are expected to broadcast an accompanying `SignedExecutionPayloadEnvelope` object honoring the commitment.
|
||||
|
||||
Thus, builders tasks are divided in two, submitting bids, and submitting payloads.
|
||||
|
||||
### Constructing the payload bid
|
||||
|
||||
Builders can broadcast a payload bid for the current or the next slot's proposer to include. They produce a `SignedExecutionPayloadHeader` as follows.
|
||||
|
||||
1. Set `header.parent_block_hash` to the current head of the execution chain (this can be obtained from the beacon state as `state.last_block_hash`).
|
||||
2. Set `header.parent_block_root` to be the head of the consensus chain (this can be obtained from the beacon state as `hash_tree_root(state.latest_block_header)`. The `parent_block_root` and `parent_block_hash` must be compatible, in the sense that they both should come from the same `state` by the method described in this and the previous point.
|
||||
3. Construct an execution payload. This can be performed with an external execution engine with a call to `engine_getPayloadV4`.
|
||||
4. Set `header.block_hash` to be the block hash of the constructed payload, that is `payload.block_hash`.
|
||||
5. Set `header.gas_limit` to be the gas limit of the constructed payload, that is `payload.gas_limit`.
|
||||
6. Set `header.builder_index` to be the validator index of the builder performing these actions.
|
||||
7. Set `header.slot` to be the slot for which this bid is aimed. This slot **MUST** be either the current slot or the next slot.
|
||||
8. Set `header.value` to be the value that the builder will pay the proposer if the bid is accepted. The builder **MUST** have balance enough to fulfill this bid.
|
||||
9. Set `header.kzg_commitments_root` to be the `hash_tree_root` of the `blobsbundle.commitments` field returned by `engine_getPayloadV4`.
|
||||
|
||||
After building the `header`, the builder obtains a `signature` of the header by using
|
||||
|
||||
```python
|
||||
def get_execution_payload_header_signature(
|
||||
state: BeaconState, header: ExecutionPayloadHeader, privkey: int) -> BLSSignature:
|
||||
domain = get_domain(state, DOMAIN_BEACON_BUILDER, compute_epoch_at_slot(header.slot))
|
||||
signing_root = compute_signing_root(header, domain)
|
||||
return bls.Sign(privkey, signing_root)
|
||||
```
|
||||
|
||||
The builder assembles then `signed_execution_payload_header = SignedExecutionPayloadHeader(message=header, signature=signature)` and broadcasts it on the `execution_payload_header` global gossip topic.
|
||||
|
||||
### Constructing the `BlobSidecar`s
|
||||
|
||||
[Modified in EIP-7732]
|
||||
|
||||
The `BlobSidecar` container is modified indirectly because the constant `KZG_COMMITMENT_INCLUSION_PROOF_DEPTH` is modified. The function `get_blob_sidecars` is modified because the KZG commitments are no longer included in the beacon block but rather in the `ExecutionPayloadEnvelope`, the builder has to send the commitments as parameters to this function.
|
||||
|
||||
```python
|
||||
def get_blob_sidecars(signed_block: SignedBeaconBlock,
|
||||
blobs: Sequence[Blob],
|
||||
blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK],
|
||||
blob_kzg_proofs: Sequence[KZGProof]) -> Sequence[BlobSidecar]:
|
||||
block = signed_block.message
|
||||
block_header = BeaconBlockHeader(
|
||||
slot=block.slot,
|
||||
proposer_index=block.proposer_index,
|
||||
parent_root=block.parent_root,
|
||||
state_root=block.state_root,
|
||||
body_root=hash_tree_root(block.body),
|
||||
)
|
||||
signed_block_header = SignedBeaconBlockHeader(message=block_header, signature=signed_block.signature)
|
||||
sidecars: List[BlobSidecar] = []
|
||||
for index, blob in enumerate(blobs):
|
||||
proof = compute_merkle_proof(
|
||||
blob_kzg_commitments,
|
||||
get_generalized_index(List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK], index),
|
||||
)
|
||||
proof += compute_merkle_proof(
|
||||
block.body,
|
||||
get_generalized_index(
|
||||
BeaconBlockBody,
|
||||
"signed_execution_payload_header",
|
||||
"message",
|
||||
"blob_kzg_commitments_root",
|
||||
),
|
||||
)
|
||||
sidecars.append(
|
||||
BlobSidecar(
|
||||
index=index,
|
||||
blob=blob,
|
||||
kzg_commitment=blob_kzg_commitments[index],
|
||||
kzg_proof=blob_kzg_proofs[index],
|
||||
signed_block_header=signed_block_header,
|
||||
kzg_commitment_inclusion_proof=proof
|
||||
)
|
||||
)
|
||||
return sidecars
|
||||
```
|
||||
|
||||
### Constructing the execution payload envelope
|
||||
|
||||
When the proposer publishes a valid `SignedBeaconBlock` containing a signed commitment by the builder, the builder is later expected to broadcast the corresponding `SignedExecutionPayloadEnvelope` that fulfills this commitment. See below for a special case of an *honestly withheld payload*.
|
||||
|
||||
To construct the `execution_payload_envelope` the builder must perform the following steps, we alias `header` to be the committed `ExecutionPayloadHeader` in the beacon block.
|
||||
|
||||
1. Set the `payload` field to be the `ExecutionPayload` constructed when creating the corresponding bid. This payload **MUST** have the same block hash as `header.block_hash`.
|
||||
2. Set the `builder_index` field to be the validator index of the builder performing these steps. This field **MUST** be `header.builder_index`.
|
||||
3. Set `beacon_block_root` to be the `hash_tree_root` of the corresponding beacon block.
|
||||
4. Set `blob_kzg_commitments` to be the `commitments` field of the blobs bundle constructed when constructing the bid. This field **MUST** have a `hash_tree_root` equal to `header.blob_kzg_commitments_root`.
|
||||
5. Set `payload_witheld` to `False`.
|
||||
|
||||
After setting these parameters, the builder should run `process_execution_payload(state, signed_envelope, verify=False)` and this function should not trigger an exception.
|
||||
|
||||
6. Set `state_root` to `hash_tree_root(state)`.
|
||||
After preparing the `envelope` the builder should sign the envelope using:
|
||||
```python
|
||||
def get_execution_payload_envelope_signature(
|
||||
state: BeaconState, envelope: ExecutionPayloadEnvelope, privkey: int) -> BLSSignature:
|
||||
domain = get_domain(state, DOMAIN_BEACON_BUILDER, compute_epoch_at_slot(state.slot))
|
||||
signing_root = compute_signing_root(envelope, domain)
|
||||
return bls.Sign(privkey, signing_root)
|
||||
```
|
||||
The builder assembles then `signed_execution_payload_envelope = SignedExecutionPayloadEnvelope(message=envelope, signature=signature)` and broadcasts it on the `execution_payload` global gossip topic.
|
||||
|
||||
### Honest payload withheld messages
|
||||
|
||||
An honest builder that has seen a `SignedBeaconBlock` referencing his signed bid, but that block was not timely and thus it is not the head of the builder's chain, may choose to withhold their execution payload. For this the builder should simply act as if it were building an empty payload, without any transactions, withdrawals, etc. The `payload.block_hash` may not be equal to `header.block_hash`. The builder may then sets `payload_withheld` to `True`. If the PTC sees this message and votes for it, validators will attribute a *withholding boost* to the builder, which would increase the forkchoice weight of the parent block, favoring it and preventing the builder from being charged for the bid by not revealing.
|
563
specs/_features/eip7732/fork-choice.md
Normal file
563
specs/_features/eip7732/fork-choice.md
Normal file
@ -0,0 +1,563 @@
|
||||
# EIP-7732 -- Fork Choice
|
||||
|
||||
## Table of contents
|
||||
<!-- TOC -->
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Constants](#constants)
|
||||
- [Containers](#containers)
|
||||
- [New `ChildNode`](#new-childnode)
|
||||
- [Helpers](#helpers)
|
||||
- [Modified `LatestMessage`](#modified-latestmessage)
|
||||
- [Modified `update_latest_messages`](#modified-update_latest_messages)
|
||||
- [Modified `Store`](#modified-store)
|
||||
- [Modified `get_forkchoice_store`](#modified-get_forkchoice_store)
|
||||
- [`notify_ptc_messages`](#notify_ptc_messages)
|
||||
- [`is_payload_present`](#is_payload_present)
|
||||
- [`is_parent_node_full`](#is_parent_node_full)
|
||||
- [Modified `get_ancestor`](#modified-get_ancestor)
|
||||
- [Modified `get_checkpoint_block`](#modified-get_checkpoint_block)
|
||||
- [`is_supporting_vote`](#is_supporting_vote)
|
||||
- [New `compute_proposer_boost`](#new-compute_proposer_boost)
|
||||
- [New `compute_withhold_boost`](#new-compute_withhold_boost)
|
||||
- [New `compute_reveal_boost`](#new-compute_reveal_boost)
|
||||
- [Modified `get_weight`](#modified-get_weight)
|
||||
- [Modified `get_head`](#modified-get_head)
|
||||
- [Updated fork-choice handlers](#updated-fork-choice-handlers)
|
||||
- [Modified `on_block`](#modified-on_block)
|
||||
- [New fork-choice handlers](#new-fork-choice-handlers)
|
||||
- [New `on_execution_payload`](#new-on_execution_payload)
|
||||
- [`seconds_into_slot`](#seconds_into_slot)
|
||||
- [Modified `on_tick_per_slot`](#modified-on_tick_per_slot)
|
||||
- [`on_payload_attestation_message`](#on_payload_attestation_message)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- /TOC -->
|
||||
|
||||
## Introduction
|
||||
|
||||
This is the modification of the fork choice accompanying the EIP-7732 upgrade.
|
||||
|
||||
## Constants
|
||||
|
||||
| Name | Value |
|
||||
| -------------------- | ----------- |
|
||||
| `PAYLOAD_TIMELY_THRESHOLD` | `PTC_SIZE / 2` (=`uint64(256)`) |
|
||||
| `INTERVALS_PER_SLOT` | `4` # [modified in EIP-7732] |
|
||||
| `PROPOSER_SCORE_BOOST` | `20` # [modified in EIP-7732] |
|
||||
| `PAYLOAD_WITHHOLD_BOOST` | `40` |
|
||||
| `PAYLOAD_REVEAL_BOOST` | `40` |
|
||||
|
||||
## Containers
|
||||
|
||||
### New `ChildNode`
|
||||
Auxiliary class to consider `(block, slot, bool)` LMD voting
|
||||
|
||||
```python
|
||||
class ChildNode(Container):
|
||||
root: Root
|
||||
slot: Slot
|
||||
is_payload_present: boolean
|
||||
```
|
||||
|
||||
## Helpers
|
||||
|
||||
### Modified `LatestMessage`
|
||||
**Note:** The class is modified to keep track of the slot instead of the epoch.
|
||||
|
||||
```python
|
||||
@dataclass(eq=True, frozen=True)
|
||||
class LatestMessage(object):
|
||||
slot: Slot
|
||||
root: Root
|
||||
```
|
||||
|
||||
### Modified `update_latest_messages`
|
||||
**Note:** the function `update_latest_messages` is updated to use the attestation slot instead of target. Notice that this function is only called on validated attestations and validators cannot attest twice in the same epoch without equivocating. Notice also that target epoch number and slot number are validated on `validate_on_attestation`.
|
||||
|
||||
```python
|
||||
def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None:
|
||||
slot = attestation.data.slot
|
||||
beacon_block_root = attestation.data.beacon_block_root
|
||||
non_equivocating_attesting_indices = [i for i in attesting_indices if i not in store.equivocating_indices]
|
||||
for i in non_equivocating_attesting_indices:
|
||||
if i not in store.latest_messages or slot > store.latest_messages[i].slot:
|
||||
store.latest_messages[i] = LatestMessage(slot=slot, root=beacon_block_root)
|
||||
```
|
||||
|
||||
### Modified `Store`
|
||||
**Note:** `Store` is modified to track the intermediate states of "empty" consensus blocks, that is, those consensus blocks for which the corresponding execution payload has not been revealed or has not been included on chain.
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class Store(object):
|
||||
time: uint64
|
||||
genesis_time: uint64
|
||||
justified_checkpoint: Checkpoint
|
||||
finalized_checkpoint: Checkpoint
|
||||
unrealized_justified_checkpoint: Checkpoint
|
||||
unrealized_finalized_checkpoint: Checkpoint
|
||||
proposer_boost_root: Root
|
||||
payload_withhold_boost_root: Root # [New in EIP-7732]
|
||||
payload_withhold_boost_full: boolean # [New in EIP-7732]
|
||||
payload_reveal_boost_root: Root # [New in EIP-7732]
|
||||
equivocating_indices: Set[ValidatorIndex]
|
||||
blocks: Dict[Root, BeaconBlock] = field(default_factory=dict)
|
||||
block_states: Dict[Root, BeaconState] = field(default_factory=dict)
|
||||
block_timeliness: Dict[Root, boolean] = field(default_factory=dict)
|
||||
checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict)
|
||||
latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict)
|
||||
unrealized_justifications: Dict[Root, Checkpoint] = field(default_factory=dict)
|
||||
execution_payload_states: Dict[Root, BeaconState] = field(default_factory=dict) # [New in EIP-7732]
|
||||
ptc_vote: Dict[Root, Vector[uint8, PTC_SIZE]] = field(default_factory=dict) # [New in EIP-7732]
|
||||
```
|
||||
|
||||
### Modified `get_forkchoice_store`
|
||||
|
||||
```python
|
||||
def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store:
|
||||
assert anchor_block.state_root == hash_tree_root(anchor_state)
|
||||
anchor_root = hash_tree_root(anchor_block)
|
||||
anchor_epoch = get_current_epoch(anchor_state)
|
||||
justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root)
|
||||
finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root)
|
||||
proposer_boost_root = Root()
|
||||
return Store(
|
||||
time=uint64(anchor_state.genesis_time + SECONDS_PER_SLOT * anchor_state.slot),
|
||||
genesis_time=anchor_state.genesis_time,
|
||||
justified_checkpoint=justified_checkpoint,
|
||||
finalized_checkpoint=finalized_checkpoint,
|
||||
unrealized_justified_checkpoint=justified_checkpoint,
|
||||
unrealized_finalized_checkpoint=finalized_checkpoint,
|
||||
proposer_boost_root=proposer_boost_root,
|
||||
payload_withhold_boost_root=proposer_boost_root, # [New in EIP-7732]
|
||||
payload_withhold_boost_full=True, # [New in EIP-7732]
|
||||
payload_reveal_boost_root=proposer_boost_root, # [New in EIP-7732]
|
||||
equivocating_indices=set(),
|
||||
blocks={anchor_root: copy(anchor_block)},
|
||||
block_states={anchor_root: copy(anchor_state)},
|
||||
checkpoint_states={justified_checkpoint: copy(anchor_state)},
|
||||
unrealized_justifications={anchor_root: justified_checkpoint},
|
||||
execution_payload_states={anchor_root: copy(anchor_state)}, # [New in EIP-7732]
|
||||
ptc_vote={anchor_root: Vector[uint8, PTC_SIZE]()},
|
||||
)
|
||||
```
|
||||
|
||||
### `notify_ptc_messages`
|
||||
|
||||
```python
|
||||
def notify_ptc_messages(store: Store, state: BeaconState, payload_attestations: Sequence[PayloadAttestation]) -> None:
|
||||
"""
|
||||
Extracts a list of ``PayloadAttestationMessage`` from ``payload_attestations`` and updates the store with them
|
||||
These Payload attestations are assumed to be in the beacon block hence signature verification is not needed
|
||||
"""
|
||||
if state.slot == 0:
|
||||
return
|
||||
for payload_attestation in payload_attestations:
|
||||
indexed_payload_attestation = get_indexed_payload_attestation(state, Slot(state.slot - 1), payload_attestation)
|
||||
for idx in indexed_payload_attestation.attesting_indices:
|
||||
on_payload_attestation_message(
|
||||
store,
|
||||
PayloadAttestationMessage(
|
||||
validator_index=idx,
|
||||
data=payload_attestation.data,
|
||||
signature=BLSSignature(),
|
||||
is_from_block=True
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
### `is_payload_present`
|
||||
|
||||
```python
|
||||
def is_payload_present(store: Store, beacon_block_root: Root) -> bool:
|
||||
"""
|
||||
Return whether the execution payload for the beacon block with root ``beacon_block_root`` was voted as present
|
||||
by the PTC
|
||||
"""
|
||||
# The beacon block root must be known
|
||||
assert beacon_block_root in store.ptc_vote
|
||||
return store.ptc_vote[beacon_block_root].count(PAYLOAD_PRESENT) > PAYLOAD_TIMELY_THRESHOLD
|
||||
```
|
||||
|
||||
### `is_parent_node_full`
|
||||
|
||||
```python
|
||||
def is_parent_node_full(store: Store, block: BeaconBlock) -> bool:
|
||||
parent = store.blocks[block.parent_root]
|
||||
parent_block_hash = block.body.signed_execution_payload_header.message.parent_block_hash
|
||||
message_block_hash = parent.body.signed_execution_payload_header.message.block_hash
|
||||
return parent_block_hash == message_block_hash
|
||||
```
|
||||
|
||||
### Modified `get_ancestor`
|
||||
**Note:** `get_ancestor` is modified to return whether the chain is based on an *empty* or *full* block.
|
||||
|
||||
```python
|
||||
def get_ancestor(store: Store, root: Root, slot: Slot) -> ChildNode:
|
||||
"""
|
||||
Returns the beacon block root, the slot 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 its PTC status instead of the actual payload content.
|
||||
"""
|
||||
block = store.blocks[root]
|
||||
if block.slot <= slot:
|
||||
return ChildNode(root=root, slot=slot, is_payload_present=is_payload_present(store, root))
|
||||
|
||||
parent = store.blocks[block.parent_root]
|
||||
if parent.slot > slot:
|
||||
return get_ancestor(store, block.parent_root, slot)
|
||||
return ChildNode(root=block.parent_root, slot=parent.slot, is_payload_present=is_parent_node_full(store, block))
|
||||
```
|
||||
|
||||
### Modified `get_checkpoint_block`
|
||||
**Note:** `get_checkpoint_block` is modified to use the new `get_ancestor`
|
||||
|
||||
```python
|
||||
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
|
||||
```
|
||||
|
||||
|
||||
### `is_supporting_vote`
|
||||
|
||||
```python
|
||||
def is_supporting_vote(store: Store, node: ChildNode, message: LatestMessage) -> bool:
|
||||
"""
|
||||
Returns whether a vote for ``message.root`` supports the chain containing the beacon block ``node.root`` with the
|
||||
payload contents indicated by ``node.is_payload_present`` as head during slot ``node.slot``.
|
||||
"""
|
||||
if node.root == message.root:
|
||||
# an attestation for a given root always counts for that root regardless if full or empty
|
||||
# as long as the attestation happened after the requested slot.
|
||||
return node.slot <= message.slot
|
||||
message_block = store.blocks[message.root]
|
||||
if node.slot >= message_block.slot:
|
||||
return False
|
||||
ancestor = get_ancestor(store, message.root, node.slot)
|
||||
return (node.root == ancestor.root) and (node.is_payload_present == ancestor.is_payload_present)
|
||||
```
|
||||
|
||||
### New `compute_proposer_boost`
|
||||
This is a helper to compute the proposer boost. It applies the proposer boost to any ancestor of the proposer boost root taking into account the payload presence. There is one exception: if the requested node has the same root and slot as the block with the proposer boost root, then the proposer boost is applied to both empty and full versions of the node.
|
||||
```python
|
||||
def compute_proposer_boost(store: Store, state: BeaconState, node: ChildNode) -> Gwei:
|
||||
if store.proposer_boost_root == Root():
|
||||
return Gwei(0)
|
||||
ancestor = get_ancestor(store, store.proposer_boost_root, node.slot)
|
||||
if ancestor.root != node.root:
|
||||
return Gwei(0)
|
||||
proposer_boost_slot = store.blocks[store.proposer_boost_root].slot
|
||||
# Proposer boost is not applied after skipped slots
|
||||
if node.slot > proposer_boost_slot:
|
||||
return Gwei(0)
|
||||
if (node.slot < proposer_boost_slot) and (ancestor.is_payload_present != node.is_payload_present):
|
||||
return Gwei(0)
|
||||
committee_weight = get_total_active_balance(state) // SLOTS_PER_EPOCH
|
||||
return (committee_weight * PROPOSER_SCORE_BOOST) // 100
|
||||
```
|
||||
|
||||
### New `compute_withhold_boost`
|
||||
This is a similar helper that applies for the withhold boost. In this case this always takes into account the reveal status.
|
||||
|
||||
```python
|
||||
def compute_withhold_boost(store: Store, state: BeaconState, node: ChildNode) -> Gwei:
|
||||
if store.payload_withhold_boost_root == Root():
|
||||
return Gwei(0)
|
||||
ancestor = get_ancestor(store, store.payload_withhold_boost_root, node.slot)
|
||||
if ancestor.root != node.root:
|
||||
return Gwei(0)
|
||||
if node.slot >= store.blocks[store.payload_withhold_boost_root].slot:
|
||||
ancestor.is_payload_present = store.payload_withhold_boost_full
|
||||
if ancestor.is_payload_present != node.is_payload_present:
|
||||
return Gwei(0)
|
||||
|
||||
committee_weight = get_total_active_balance(state) // SLOTS_PER_EPOCH
|
||||
return (committee_weight * PAYLOAD_WITHHOLD_BOOST) // 100
|
||||
```
|
||||
|
||||
### New `compute_reveal_boost`
|
||||
This is a similar helper to the last two, the only difference is that the reveal boost is only applied to the full version of the node when querying for the same slot as the revealed payload.
|
||||
|
||||
```python
|
||||
def compute_reveal_boost(store: Store, state: BeaconState, node: ChildNode) -> Gwei:
|
||||
if store.payload_reveal_boost_root == Root():
|
||||
return Gwei(0)
|
||||
ancestor = get_ancestor(store, store.payload_reveal_boost_root, node.slot)
|
||||
if ancestor.root != node.root:
|
||||
return Gwei(0)
|
||||
if node.slot >= store.blocks[store.payload_reveal_boost_root].slot:
|
||||
ancestor.is_payload_present = True
|
||||
if ancestor.is_payload_present != node.is_payload_present:
|
||||
return Gwei(0)
|
||||
committee_weight = get_total_active_balance(state) // SLOTS_PER_EPOCH
|
||||
return (committee_weight * PAYLOAD_REVEAL_BOOST) // 100
|
||||
```
|
||||
|
||||
### Modified `get_weight`
|
||||
|
||||
**Note:** `get_weight` is modified to only count votes for descending chains that support the status of a triple `Root, Slot, bool`, where the `bool` indicates if the block was full or not. `Slot` is needed for a correct implementation of `(Block, Slot)` voting.
|
||||
|
||||
```python
|
||||
def get_weight(store: Store, node: ChildNode) -> Gwei:
|
||||
state = store.checkpoint_states[store.justified_checkpoint]
|
||||
unslashed_and_active_indices = [
|
||||
i for i in get_active_validator_indices(state, get_current_epoch(state))
|
||||
if not state.validators[i].slashed
|
||||
]
|
||||
attestation_score = Gwei(sum(
|
||||
state.validators[i].effective_balance for i in unslashed_and_active_indices
|
||||
if (i in store.latest_messages
|
||||
and i not in store.equivocating_indices
|
||||
and is_supporting_vote(store, node, store.latest_messages[i]))
|
||||
))
|
||||
|
||||
# Compute boosts
|
||||
proposer_score = compute_proposer_boost(store, state, node)
|
||||
builder_reveal_score = compute_reveal_boost(store, state, node)
|
||||
builder_withhold_score = compute_withhold_boost(store, state, node)
|
||||
|
||||
return attestation_score + proposer_score + builder_reveal_score + builder_withhold_score
|
||||
```
|
||||
|
||||
### Modified `get_head`
|
||||
|
||||
**Note:** `get_head` is a modified to use the new `get_weight` function. It returns the `ChildNode` object corresponidng to the head block.
|
||||
|
||||
```python
|
||||
def get_head(store: Store) -> ChildNode:
|
||||
# Get filtered block tree that only includes viable branches
|
||||
blocks = get_filtered_block_tree(store)
|
||||
# Execute the LMD-GHOST fork choice
|
||||
justified_root = store.justified_checkpoint.root
|
||||
justified_block = store.blocks[justified_root]
|
||||
justified_slot = justified_block.slot
|
||||
justified_full = is_payload_present(store, justified_root)
|
||||
best_child = ChildNode(root=justified_root, slot=justified_slot, is_payload_present=justified_full)
|
||||
while True:
|
||||
children = [
|
||||
ChildNode(root=root, slot=block.slot, is_payload_present=present) for (root, block) in blocks.items()
|
||||
if block.parent_root == best_child.root and block.slot > best_child.slot and
|
||||
(best_child.root == justified_root or is_parent_node_full(store, block) == best_child.is_payload_present)
|
||||
for present in (True, False) if root in store.execution_payload_states or not present
|
||||
]
|
||||
if len(children) == 0:
|
||||
return best_child
|
||||
# if we have children we consider the current head advanced as a possible head
|
||||
highest_child_slot = max(child.slot for child in children)
|
||||
children += [
|
||||
ChildNode(root=best_child.root, slot=best_child.slot + 1, is_payload_present=best_child.is_payload_present)
|
||||
]
|
||||
# Sort by latest attesting balance with
|
||||
# Ties broken by the block's slot
|
||||
# Ties are broken by the PTC vote
|
||||
# Ties are then broken by favoring full blocks
|
||||
# Ties then broken by favoring block with lexicographically higher root
|
||||
new_best_child = max(children, key=lambda child: (
|
||||
get_weight(store, child),
|
||||
blocks[child.root].slot,
|
||||
is_payload_present(store, child.root),
|
||||
child.is_payload_present,
|
||||
child.root
|
||||
)
|
||||
)
|
||||
if new_best_child.root == best_child.root and new_best_child.slot >= highest_child_slot:
|
||||
return new_best_child
|
||||
best_child = new_best_child
|
||||
```
|
||||
|
||||
## Updated fork-choice handlers
|
||||
|
||||
### Modified `on_block`
|
||||
|
||||
*Note*: The handler `on_block` is modified to consider the pre `state` of the given consensus beacon block depending not only on the parent block root, but also on the parent blockhash. In addition we delay the checking of blob data availability until the processing of the execution payload.
|
||||
|
||||
```python
|
||||
def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
|
||||
"""
|
||||
Run ``on_block`` upon receiving a new block.
|
||||
"""
|
||||
block = signed_block.message
|
||||
# Parent block must be known
|
||||
assert block.parent_root in store.block_states
|
||||
|
||||
# Check if this blocks builds on empty or full parent block
|
||||
parent_block = store.blocks[block.parent_root]
|
||||
header = block.body.signed_execution_payload_header.message
|
||||
parent_header = parent_block.body.signed_execution_payload_header.message
|
||||
# Make a copy of the state to avoid mutability issues
|
||||
if is_parent_node_full(store, block):
|
||||
assert block.parent_root in store.execution_payload_states
|
||||
state = copy(store.execution_payload_states[block.parent_root])
|
||||
else:
|
||||
assert header.parent_block_hash == parent_header.parent_block_hash
|
||||
state = copy(store.block_states[block.parent_root])
|
||||
|
||||
# Blocks cannot be in the future. If they are, their consideration must be delayed until they are in the past.
|
||||
current_slot = get_current_slot(store)
|
||||
assert current_slot >= block.slot
|
||||
|
||||
# Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor)
|
||||
finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
|
||||
assert block.slot > finalized_slot
|
||||
# Check block is a descendant of the finalized block at the checkpoint finalized slot
|
||||
finalized_checkpoint_block = get_checkpoint_block(
|
||||
store,
|
||||
block.parent_root,
|
||||
store.finalized_checkpoint.epoch,
|
||||
)
|
||||
assert store.finalized_checkpoint.root == finalized_checkpoint_block
|
||||
|
||||
# Check the block is valid and compute the post-state
|
||||
block_root = hash_tree_root(block)
|
||||
state_transition(state, signed_block, True)
|
||||
|
||||
# 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
|
||||
# Add a new PTC voting for this block to the store
|
||||
store.ptc_vote[block_root] = [PAYLOAD_ABSENT] * PTC_SIZE
|
||||
|
||||
# Notify the store about the payload_attestations in the block
|
||||
notify_ptc_messages(store, state, block.body.payload_attestations)
|
||||
# Add proposer score boost if the block is timely
|
||||
time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT
|
||||
is_before_attesting_interval = time_into_slot < SECONDS_PER_SLOT // INTERVALS_PER_SLOT
|
||||
is_timely = get_current_slot(store) == block.slot and is_before_attesting_interval
|
||||
store.block_timeliness[hash_tree_root(block)] = is_timely
|
||||
|
||||
# Add proposer score boost if the block is timely and not conflicting with an existing block
|
||||
is_first_block = store.proposer_boost_root == Root()
|
||||
if is_timely and is_first_block:
|
||||
store.proposer_boost_root = hash_tree_root(block)
|
||||
|
||||
# Update checkpoints in store if necessary
|
||||
update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint)
|
||||
|
||||
# Eagerly compute unrealized justification and finality.
|
||||
compute_pulled_up_tip(store, block_root)
|
||||
```
|
||||
|
||||
## New fork-choice handlers
|
||||
|
||||
### New `on_execution_payload`
|
||||
|
||||
The handler `on_execution_payload` is called when the node receives a `SignedExecutionPayloadEnvelope` to sync.
|
||||
|
||||
```python
|
||||
def on_execution_payload(store: Store, signed_envelope: SignedExecutionPayloadEnvelope) -> None:
|
||||
"""
|
||||
Run ``on_execution_payload`` upon receiving a new execution payload.
|
||||
"""
|
||||
envelope = signed_envelope.message
|
||||
# The corresponding beacon block root needs to be known
|
||||
assert envelope.beacon_block_root in store.block_states
|
||||
|
||||
# Check if blob data is available
|
||||
# If not, this payload MAY be queued and subsequently considered when blob data becomes available
|
||||
assert is_data_available(envelope.beacon_block_root, envelope.blob_kzg_commitments)
|
||||
|
||||
# Make a copy of the state to avoid mutability issues
|
||||
state = copy(store.block_states[envelope.beacon_block_root])
|
||||
|
||||
# Process the execution payload
|
||||
process_execution_payload(state, signed_envelope, EXECUTION_ENGINE)
|
||||
|
||||
# Add new state for this payload to the store
|
||||
store.execution_payload_states[envelope.beacon_block_root] = state
|
||||
```
|
||||
|
||||
### `seconds_into_slot`
|
||||
|
||||
```python
|
||||
def seconds_into_slot(store: Store) -> uint64:
|
||||
return (store.time - store.genesis_time) % SECONDS_PER_SLOT
|
||||
```
|
||||
|
||||
### Modified `on_tick_per_slot`
|
||||
|
||||
Modified to reset the payload boost roots
|
||||
|
||||
```python
|
||||
def on_tick_per_slot(store: Store, time: uint64) -> None:
|
||||
previous_slot = get_current_slot(store)
|
||||
|
||||
# Update store time
|
||||
store.time = time
|
||||
|
||||
current_slot = get_current_slot(store)
|
||||
|
||||
# If this is a new slot, reset store.proposer_boost_root
|
||||
if current_slot > previous_slot:
|
||||
store.proposer_boost_root = Root()
|
||||
else:
|
||||
# Reset the payload boost if this is the attestation time
|
||||
if seconds_into_slot(store) >= SECONDS_PER_SLOT // INTERVALS_PER_SLOT:
|
||||
store.payload_withhold_boost_root = Root()
|
||||
store.payload_withhold_boost_full = False
|
||||
store.payload_reveal_boost_root = Root()
|
||||
|
||||
# If a new epoch, pull-up justification and finalization from previous epoch
|
||||
if current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0:
|
||||
update_checkpoints(store, store.unrealized_justified_checkpoint, store.unrealized_finalized_checkpoint)
|
||||
```
|
||||
|
||||
### `on_payload_attestation_message`
|
||||
|
||||
```python
|
||||
def on_payload_attestation_message(
|
||||
store: Store, ptc_message: PayloadAttestationMessage, is_from_block: bool=False) -> None:
|
||||
"""
|
||||
Run ``on_payload_attestation_message`` upon receiving a new ``ptc_message`` directly on the wire.
|
||||
"""
|
||||
# The beacon block root must be known
|
||||
data = ptc_message.data
|
||||
# PTC attestation must be for a known block. If block is unknown, delay consideration until the block is found
|
||||
state = store.block_states[data.beacon_block_root]
|
||||
ptc = get_ptc(state, data.slot)
|
||||
# PTC votes can only change the vote for their assigned beacon block, return early otherwise
|
||||
if data.slot != state.slot:
|
||||
return
|
||||
# Check that the attester is from the PTC
|
||||
assert ptc_message.validator_index in ptc
|
||||
|
||||
# Verify the signature and check that its for the current slot if it is coming from the wire
|
||||
if not is_from_block:
|
||||
# Check that the attestation is for the current slot
|
||||
assert data.slot == get_current_slot(store)
|
||||
# Verify the signature
|
||||
assert is_valid_indexed_payload_attestation(
|
||||
state,
|
||||
IndexedPayloadAttestation(
|
||||
attesting_indices=[ptc_message.validator_index],
|
||||
data=data,
|
||||
signature=ptc_message.signature
|
||||
)
|
||||
)
|
||||
# Update the ptc vote for the block
|
||||
ptc_index = ptc.index(ptc_message.validator_index)
|
||||
ptc_vote = store.ptc_vote[data.beacon_block_root]
|
||||
ptc_vote[ptc_index] = data.payload_status
|
||||
|
||||
# Only update payload boosts with attestations from a block if the block is for the current slot and it's early
|
||||
if is_from_block and data.slot + 1 != get_current_slot(store):
|
||||
return
|
||||
time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT
|
||||
if is_from_block and time_into_slot >= SECONDS_PER_SLOT // INTERVALS_PER_SLOT:
|
||||
return
|
||||
|
||||
# Update the payload boosts if threshold has been achieved
|
||||
if ptc_vote.count(PAYLOAD_PRESENT) > PAYLOAD_TIMELY_THRESHOLD:
|
||||
store.payload_reveal_boost_root = data.beacon_block_root
|
||||
if ptc_vote.count(PAYLOAD_WITHHELD) > PAYLOAD_TIMELY_THRESHOLD:
|
||||
block = store.blocks[data.beacon_block_root]
|
||||
store.payload_withhold_boost_root = block.parent_root
|
||||
store.payload_withhold_boost_full = is_parent_node_full(store, block)
|
||||
```
|
139
specs/_features/eip7732/fork.md
Normal file
139
specs/_features/eip7732/fork.md
Normal file
@ -0,0 +1,139 @@
|
||||
# EIP-7732 -- Fork Logic
|
||||
|
||||
**Notice**: This document is a work-in-progress for researchers and implementers.
|
||||
|
||||
## Table of contents
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Configuration](#configuration)
|
||||
- [Helper functions](#helper-functions)
|
||||
- [Misc](#misc)
|
||||
- [Modified `compute_fork_version`](#modified-compute_fork_version)
|
||||
- [Fork to EIP-7732](#fork-to-eip-7732)
|
||||
- [Fork trigger](#fork-trigger)
|
||||
- [Upgrading the state](#upgrading-the-state)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
## Introduction
|
||||
|
||||
This document describes the process of the EIP-7732 upgrade.
|
||||
|
||||
## Configuration
|
||||
|
||||
Warning: this configuration is not definitive.
|
||||
|
||||
| Name | Value |
|
||||
|---------------------| - |
|
||||
| `EIP7732_FORK_VERSION` | `Version('0x09000000')` |
|
||||
| `EIP7732_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** |
|
||||
|
||||
## Helper functions
|
||||
|
||||
### Misc
|
||||
|
||||
#### Modified `compute_fork_version`
|
||||
|
||||
```python
|
||||
def compute_fork_version(epoch: Epoch) -> Version:
|
||||
"""
|
||||
Return the fork version at the given ``epoch``.
|
||||
"""
|
||||
if epoch >= EIP7732_FORK_EPOCH:
|
||||
return EIP7732_FORK_VERSION
|
||||
if epoch >= ELECTRA_FORK_EPOCH:
|
||||
return ELECTRA_FORK_VERSION
|
||||
if epoch >= DENEB_FORK_EPOCH:
|
||||
return DENEB_FORK_VERSION
|
||||
if epoch >= CAPELLA_FORK_EPOCH:
|
||||
return CAPELLA_FORK_VERSION
|
||||
if epoch >= BELLATRIX_FORK_EPOCH:
|
||||
return BELLATRIX_FORK_VERSION
|
||||
if epoch >= ALTAIR_FORK_EPOCH:
|
||||
return ALTAIR_FORK_VERSION
|
||||
return GENESIS_FORK_VERSION
|
||||
```
|
||||
|
||||
## Fork to EIP-7732
|
||||
|
||||
### Fork trigger
|
||||
|
||||
TBD. This fork is defined for testing purposes, the EIP may be combined with other
|
||||
consensus-layer upgrade.
|
||||
For now, we assume the condition will be triggered at epoch `EIP7732_FORK_EPOCH`.
|
||||
|
||||
### Upgrading the state
|
||||
|
||||
If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == EIP7732_FORK_EPOCH`,
|
||||
an irregular state change is made to upgrade to EIP-7732.
|
||||
|
||||
```python
|
||||
def upgrade_to_eip7732(pre: electra.BeaconState) -> BeaconState:
|
||||
epoch = electra.get_current_epoch(pre)
|
||||
|
||||
post = BeaconState(
|
||||
# Versioning
|
||||
genesis_time=pre.genesis_time,
|
||||
genesis_validators_root=pre.genesis_validators_root,
|
||||
slot=pre.slot,
|
||||
fork=Fork(
|
||||
previous_version=pre.fork.current_version,
|
||||
current_version=EIP7732_FORK_VERSION, # [Modified in EIP-7732]
|
||||
epoch=epoch,
|
||||
),
|
||||
# History
|
||||
latest_block_header=pre.latest_block_header,
|
||||
block_roots=pre.block_roots,
|
||||
state_roots=pre.state_roots,
|
||||
historical_roots=pre.historical_roots,
|
||||
# Eth1
|
||||
eth1_data=pre.eth1_data,
|
||||
eth1_data_votes=pre.eth1_data_votes,
|
||||
eth1_deposit_index=pre.eth1_deposit_index,
|
||||
# Registry
|
||||
validators=pre.validators,
|
||||
balances=pre.balances,
|
||||
# Randomness
|
||||
randao_mixes=pre.randao_mixes,
|
||||
# Slashings
|
||||
slashings=pre.slashings,
|
||||
# Participation
|
||||
previous_epoch_participation=pre.previous_epoch_participation,
|
||||
current_epoch_participation=pre.current_epoch_participation,
|
||||
# Finality
|
||||
justification_bits=pre.justification_bits,
|
||||
previous_justified_checkpoint=pre.previous_justified_checkpoint,
|
||||
current_justified_checkpoint=pre.current_justified_checkpoint,
|
||||
finalized_checkpoint=pre.finalized_checkpoint,
|
||||
# Inactivity
|
||||
inactivity_scores=pre.inactivity_scores,
|
||||
# Sync
|
||||
current_sync_committee=pre.current_sync_committee,
|
||||
next_sync_committee=pre.next_sync_committee,
|
||||
# Execution-layer
|
||||
latest_execution_payload_header=ExecutionPayloadHeader(), # [Modified in EIP-7732]
|
||||
# Withdrawals
|
||||
next_withdrawal_index=pre.next_withdrawal_index,
|
||||
next_withdrawal_validator_index=pre.next_withdrawal_validator_index,
|
||||
# Deep history valid from Capella onwards
|
||||
historical_summaries=pre.historical_summaries,
|
||||
deposit_requests_start_index=pre.deposit_requests_start_index,
|
||||
deposit_balance_to_consume=pre.deposit_balance_to_consume,
|
||||
exit_balance_to_consume=pre.exit_balance_to_consume,
|
||||
earliest_exit_epoch=pre.earliest_exit_epoch,
|
||||
consolidation_balance_to_consume=pre.consolidation_balance_to_consume,
|
||||
earliest_consolidation_epoch=pre.earliest_consolidation_epoch,
|
||||
pending_balance_deposits=pre.pending_balance_deposits,
|
||||
pending_partial_withdrawals=pre.pending_partial_withdrawals,
|
||||
pending_consolidations=pre.pending_consolidations,
|
||||
# ePBS
|
||||
latest_block_hash=pre.latest_execution_payload_header.block_hash, # [New in EIP-7732]
|
||||
latest_full_slot=pre.slot, # [New in EIP-7732]
|
||||
latest_withdrawals_root=Root(), # [New in EIP-7732]
|
||||
)
|
||||
|
||||
return post
|
||||
```
|
267
specs/_features/eip7732/p2p-interface.md
Normal file
267
specs/_features/eip7732/p2p-interface.md
Normal file
@ -0,0 +1,267 @@
|
||||
# EIP-7732 -- Networking
|
||||
|
||||
This document contains the consensus-layer networking specification for EIP7732.
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
|
||||
- [Modification in EIP-7732](#modification-in-eip-7732)
|
||||
- [Preset](#preset)
|
||||
- [Containers](#containers)
|
||||
- [`BlobSidecar`](#blobsidecar)
|
||||
- [Helpers](#helpers)
|
||||
- [Modified `verify_blob_sidecar_inclusion_proof`](#modified-verify_blob_sidecar_inclusion_proof)
|
||||
- [The gossip domain: gossipsub](#the-gossip-domain-gossipsub)
|
||||
- [Topics and messages](#topics-and-messages)
|
||||
- [Global topics](#global-topics)
|
||||
- [`beacon_block`](#beacon_block)
|
||||
- [`execution_payload`](#execution_payload)
|
||||
- [`payload_attestation_message`](#payload_attestation_message)
|
||||
- [`execution_payload_header`](#execution_payload_header)
|
||||
- [The Req/Resp domain](#the-reqresp-domain)
|
||||
- [Messages](#messages)
|
||||
- [BeaconBlocksByRange v3](#beaconblocksbyrange-v3)
|
||||
- [BeaconBlocksByRoot v3](#beaconblocksbyroot-v3)
|
||||
- [BlobSidecarsByRoot v2](#blobsidecarsbyroot-v2)
|
||||
- [ExecutionPayloadEnvelopeByRoot v1](#executionpayloadenvelopebyroot-v1)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
## Modification in EIP-7732
|
||||
|
||||
### Preset
|
||||
|
||||
*[Modified in EIP-7732]*
|
||||
|
||||
| Name | Value | Description |
|
||||
|------------------------------------------|-----------------------------------|---------------------------------------------------------------------|
|
||||
| `KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_EIP7732` | `13` # TODO: Compute it when the spec stabilizes | Merkle proof depth for the `blob_kzg_commitments` list item |
|
||||
|
||||
|
||||
### Containers
|
||||
|
||||
#### `BlobSidecar`
|
||||
|
||||
The `BlobSidecar` container is modified indirectly because the constant `KZG_COMMITMENT_INCLUSION_PROOF_DEPTH` is modified.
|
||||
|
||||
```python
|
||||
class BlobSidecar(Container):
|
||||
index: BlobIndex # Index of blob in block
|
||||
blob: Blob
|
||||
kzg_commitment: KZGCommitment
|
||||
kzg_proof: KZGProof # Allows for quick verification of kzg_commitment
|
||||
signed_block_header: SignedBeaconBlockHeader
|
||||
kzg_commitment_inclusion_proof: Vector[Bytes32, KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_EIP7732]
|
||||
```
|
||||
|
||||
#### Helpers
|
||||
|
||||
##### Modified `verify_blob_sidecar_inclusion_proof`
|
||||
|
||||
`verify_blob_sidecar_inclusion_proof` is modified in EIP-7732 to account for the fact that the KZG commitments are included in the `ExecutionPayloadEnvelope` and no longer in the beacon block body.
|
||||
|
||||
```python
|
||||
def verify_blob_sidecar_inclusion_proof(blob_sidecar: BlobSidecar) -> bool:
|
||||
inner_gindex = get_generalized_index(
|
||||
List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK],
|
||||
blob_sidecar.index
|
||||
)
|
||||
outer_gindex = get_generalized_index(
|
||||
BeaconBlockBody,
|
||||
"signed_execution_payload_header",
|
||||
"message",
|
||||
"blob_kzg_commitments_root",
|
||||
)
|
||||
gindex = get_subtree_index(concat_generalized_indices(outer_gindex, inner_gindex))
|
||||
|
||||
return is_valid_merkle_branch(
|
||||
leaf=blob_sidecar.kzg_commitment.hash_tree_root(),
|
||||
branch=blob_sidecar.kzg_commitment_inclusion_proof,
|
||||
depth=KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_EIP7732,
|
||||
index=gindex,
|
||||
root=blob_sidecar.signed_block_header.message.body_root,
|
||||
)
|
||||
```
|
||||
|
||||
### The gossip domain: gossipsub
|
||||
|
||||
Some gossip meshes are upgraded in the fork of EIP-7732 to support upgraded types.
|
||||
|
||||
#### Topics and messages
|
||||
|
||||
Topics follow the same specification as in prior upgrades.
|
||||
|
||||
The `beacon_block` topic is updated to support the modified type
|
||||
| Name | Message Type |
|
||||
| --- | --- |
|
||||
| `beacon_block` | `SignedBeaconBlock` [modified in EIP-7732] |
|
||||
|
||||
The new topics along with the type of the `data` field of a gossipsub message are given in this table:
|
||||
|
||||
| Name | Message Type |
|
||||
|-------------------------------|------------------------------------------------------|
|
||||
| `execution_payload_header` | `SignedExecutionPayloadHeader` [New in EIP-7732] |
|
||||
| `execution_payload` | `SignedExecutionPayloadEnvelope` [New in EIP-7732] |
|
||||
| `payload_attestation_message` | `PayloadAttestationMessage` [New in EIP-7732] |
|
||||
|
||||
##### Global topics
|
||||
|
||||
EIP-7732 introduces new global topics for execution header, execution payload and payload attestation.
|
||||
|
||||
###### `beacon_block`
|
||||
|
||||
[Modified in EIP-7732]
|
||||
|
||||
The *type* of the payload of this topic changes to the (modified) `SignedBeaconBlock` found in [the Beacon Chain changes](./beacon-chain.md).
|
||||
|
||||
There are no new validations for this topic. However, all validations with regards to the `ExecutionPayload` are removed:
|
||||
|
||||
- _[REJECT]_ The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer -- i.e. validate that len(body.signed_beacon_block.message.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK
|
||||
- _[REJECT]_ The block's execution payload timestamp is correct with respect to the slot
|
||||
-- i.e. `execution_payload.timestamp == compute_timestamp_at_slot(state, block.slot)`.
|
||||
- If `execution_payload` verification of block's parent by an execution node is *not* complete:
|
||||
- [REJECT] The block's parent (defined by `block.parent_root`) passes all validation (excluding execution node verification of the `block.body.execution_payload`).
|
||||
- otherwise:
|
||||
- [IGNORE] The block's parent (defined by `block.parent_root`) passes all validation (including execution node verification of the `block.body.execution_payload`).
|
||||
- [REJECT] The block's parent (defined by `block.parent_root`) passes validation.
|
||||
|
||||
And instead the following validations are set in place with the alias `header = signed_execution_payload_header.message`:
|
||||
|
||||
- If `execution_payload` verification of block's execution payload parent by an execution node **is complete**:
|
||||
- [REJECT] The block's execution payload parent (defined by `header.parent_block_hash`) passes all validation.
|
||||
- [REJECT] The block's parent (defined by `block.parent_root`) passes validation.
|
||||
|
||||
###### `execution_payload`
|
||||
|
||||
This topic is used to propagate execution payload messages as `SignedExecutionPayloadEnvelope`.
|
||||
|
||||
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 = payload_envelope.payload`:
|
||||
|
||||
- _[IGNORE]_ The envelope's block root `envelope.block_root` has been seen (via both gossip and non-gossip sources) (a client MAY queue payload for processing once the block is retrieved).
|
||||
- _[IGNORE]_ The node has not seen another valid `SignedExecutionPayloadEnvelope` for this block root from this builder.
|
||||
|
||||
Let `block` be the block with `envelope.beacon_block_root`.
|
||||
Let `header` alias `block.body.signed_execution_payload_header.message` (notice that this can be obtained from the `state.signed_execution_payload_header`)
|
||||
- _[REJECT]_ `block` passes validation.
|
||||
- _[REJECT]_ `envelope.builder_index == header.builder_index`
|
||||
- if `envelope.payload_withheld == False` then
|
||||
- _[REJECT]_ `payload.block_hash == header.block_hash`
|
||||
- _[REJECT]_ The builder signature, `signed_execution_payload_envelope.signature`, is valid with respect to the builder's public key.
|
||||
|
||||
###### `payload_attestation_message`
|
||||
|
||||
This topic is used to propagate signed payload attestation message.
|
||||
|
||||
The following validations MUST pass before forwarding the `payload_attestation_message` on the network, assuming the alias `data = payload_attestation_message.data`:
|
||||
|
||||
- _[IGNORE]_ `data.slot` is the current slot.
|
||||
- _[REJECT]_ `data.payload_status < PAYLOAD_INVALID_STATUS`
|
||||
- _[IGNORE]_ the `payload_attestation_message` is the first valid payload attestation message received from the validator index.
|
||||
- _[IGNORE]_ The attestation's `data.beacon_block_root` has been seen (via both gossip and non-gossip sources) (a client MAY queue attestation for processing once the block is retrieved. Note a client might want to request payload after).
|
||||
_ _[REJECT]_ The beacon block with root `data.beacon_block_root` passes validation.
|
||||
- _[REJECT]_ The validator index is within the payload committee in `get_ptc(state, data.slot)`. For the current's slot head state.
|
||||
- _[REJECT]_ The signature of `payload_attestation_message.signature` is valid with respect to the validator index.
|
||||
|
||||
###### `execution_payload_header`
|
||||
|
||||
This topic is used to propagate signed bids as `SignedExecutionPayloadHeader`.
|
||||
|
||||
The following validations MUST pass before forwarding the `signed_execution_payload_header` on the network, assuming the alias `header = signed_execution_payload_header.message`:
|
||||
|
||||
- _[IGNORE]_ this is the first signed bid seen with a valid signature from the given builder for this slot.
|
||||
- _[IGNORE]_ this bid is the highest value bid seen for the pair of the corresponding slot and the given parent block hash.
|
||||
- _[REJECT]_ The signed builder bid, `header.builder_index` is a valid and non-slashed builder index in state.
|
||||
- _[IGNORE]_ The signed builder bid value, `header.value`, is less or equal than the builder's balance in state. i.e. `MIN_BUILDER_BALANCE + header.value < state.builder_balances[header.builder_index]`.
|
||||
- _[IGNORE]_ `header.parent_block_hash` is the block hash of a known execution payload in fork choice.
|
||||
- _[IGNORE]_ `header.slot` is the current slot or the next slot.
|
||||
- _[REJECT]_ The builder signature, `signed_execution_payload_header_envelope.signature`, is valid with respect to the `header_envelope.builder_index`.
|
||||
|
||||
### The Req/Resp domain
|
||||
|
||||
#### Messages
|
||||
|
||||
##### BeaconBlocksByRange v3
|
||||
|
||||
**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_range/3/`
|
||||
|
||||
[0]: # (eth2spec: skip)
|
||||
|
||||
| `fork_version` | Chunk SSZ type |
|
||||
|--------------------------|-------------------------------|
|
||||
| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` |
|
||||
| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` |
|
||||
| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` |
|
||||
| `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` |
|
||||
| `DENEB_FORK_VERSION` | `deneb.SignedBeaconBlock` |
|
||||
| `EIP7732_FORK_VERSION` | `eip7732.SignedBeaconBlock` |
|
||||
|
||||
##### BeaconBlocksByRoot v3
|
||||
|
||||
**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/3/`
|
||||
|
||||
Per `context = compute_fork_digest(fork_version, genesis_validators_root)`:
|
||||
|
||||
[1]: # (eth2spec: skip)
|
||||
|
||||
| `fork_version` | Chunk SSZ type |
|
||||
|--------------------------|-------------------------------|
|
||||
| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` |
|
||||
| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` |
|
||||
| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` |
|
||||
| `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` |
|
||||
| `DENEB_FORK_VERSION` | `deneb.SignedBeaconBlock` |
|
||||
| `EIP7732_FORK_VERSION` | `eip7732.SignedBeaconBlock` |
|
||||
|
||||
|
||||
##### BlobSidecarsByRoot v2
|
||||
|
||||
**Protocol ID:** `/eth2/beacon_chain/req/blob_sidecars_by_root/2/`
|
||||
|
||||
[1]: # (eth2spec: skip)
|
||||
|
||||
| `fork_version` | Chunk SSZ type |
|
||||
|--------------------------|-------------------------------|
|
||||
| `DENEB_FORK_VERSION` | `deneb.BlobSidecar` |
|
||||
| `EIP7732_FORK_VERSION` | `eip7732.BlobSidecar` |
|
||||
|
||||
|
||||
##### ExecutionPayloadEnvelopeByRoot v1
|
||||
|
||||
**Protocol ID:** `/eth2/beacon_chain/req/execution_payload_envelope_by_root/1/`
|
||||
|
||||
The `<context-bytes>` field is calculated as `context = compute_fork_digest(fork_version, genesis_validators_root)`:
|
||||
|
||||
[1]: # (eth2spec: skip)
|
||||
|
||||
| `fork_version` | Chunk SSZ type |
|
||||
|------------------------|------------------------------------------|
|
||||
| `EIP7732_FORK_VERSION` | `eip7732.SignedExecutionPayloadEnvelope` |
|
||||
|
||||
Request Content:
|
||||
|
||||
```
|
||||
(
|
||||
List[Root, MAX_REQUEST_PAYLOAD]
|
||||
)
|
||||
```
|
||||
|
||||
Response Content:
|
||||
|
||||
```
|
||||
(
|
||||
List[SignedExecutionPayloadEnvelope, MAX_REQUEST_PAYLOAD]
|
||||
)
|
||||
```
|
||||
Requests execution payload envelope by `signed_execution_payload_envelope.message.block_root`. The response is a list of `SignedExecutionPayloadEnvelope` whose length is less than or equal to the number of requested execution payload envelopes. It may be less in the case that the responding peer is missing payload envelopes.
|
||||
|
||||
No more than `MAX_REQUEST_PAYLOAD` may be requested at a time.
|
||||
|
||||
ExecutionPayloadEnvelopeByRoot is primarily used to recover recent execution payload envelope (e.g. when receiving a payload attestation with revealed status as true but never received a payload).
|
||||
|
||||
The request MUST be encoded as an SSZ-field.
|
||||
|
||||
The response MUST consist of zero or more `response_chunk`. Each successful `response_chunk` MUST contain a single `SignedExecutionPayloadEnvelope` payload.
|
||||
|
||||
Clients MUST support requesting payload envelopes since the latest finalized epoch.
|
||||
|
||||
Clients MUST respond with at least one payload envelope, if they have it. Clients MAY limit the number of payload envelopes in the response.
|
134
specs/_features/eip7732/validator.md
Normal file
134
specs/_features/eip7732/validator.md
Normal file
@ -0,0 +1,134 @@
|
||||
# EIP-7732 -- Honest Validator
|
||||
|
||||
This document represents the changes and additions to the Honest validator guide included in the EIP-7732 fork.
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
**Table of Contents**
|
||||
|
||||
- [Validator assignment](#validator-assignment)
|
||||
- [Lookahead](#lookahead)
|
||||
- [Beacon chain responsibilities](#beacon-chain-responsibilities)
|
||||
- [Attestation](#attestation)
|
||||
- [Sync Committee participations](#sync-committee-participations)
|
||||
- [Block proposal](#block-proposal)
|
||||
- [Constructing the new `signed_execution_payload_header` field in `BeaconBlockBody`](#constructing-the-new-signed_execution_payload_header-field-in--beaconblockbody)
|
||||
- [Constructing the new `payload_attestations` field in `BeaconBlockBody`](#constructing-the-new-payload_attestations-field-in--beaconblockbody)
|
||||
- [Blob sidecars](#blob-sidecars)
|
||||
- [Payload timeliness attestation](#payload-timeliness-attestation)
|
||||
- [Constructing a payload attestation](#constructing-a-payload-attestation)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
## Validator assignment
|
||||
|
||||
A validator may be a member of the new Payload Timeliness Committee (PTC) for a given slot. To check for PTC assignments the validator uses the helper `get_ptc_assignment(state, epoch, validator_index)` where `epoch <= next_epoch`.
|
||||
|
||||
PTC committee selection is only stable within the context of the current and next epoch.
|
||||
|
||||
```python
|
||||
def get_ptc_assignment(
|
||||
state: BeaconState,
|
||||
epoch: Epoch,
|
||||
validator_index: ValidatorIndex) -> Optional[Slot]:
|
||||
"""
|
||||
Returns the slot during the requested epoch in which the validator with index `validator_index`
|
||||
is a member of the PTC. Returns None if no assignment is found.
|
||||
"""
|
||||
next_epoch = Epoch(get_current_epoch(state) + 1)
|
||||
assert epoch <= next_epoch
|
||||
|
||||
start_slot = compute_start_slot_at_epoch(epoch)
|
||||
for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH):
|
||||
if validator_index in get_ptc(state, Slot(slot)):
|
||||
return Slot(slot)
|
||||
return None
|
||||
```
|
||||
|
||||
### Lookahead
|
||||
|
||||
[New in EIP-7732]
|
||||
|
||||
`get_ptc_assignment` should be called at the start of each epoch to get the assignment for the next epoch (`current_epoch + 1`). A validator should plan for future assignments by noting their assigned PTC slot.
|
||||
|
||||
## Beacon chain responsibilities
|
||||
|
||||
All validator responsibilities remain unchanged other than the following:
|
||||
|
||||
- Proposers are no longer required to broadcast `BlobSidecar` objects, as this becomes a builder's duty.
|
||||
- Some validators are selected per slot to become PTC members, these validators must broadcast `PayloadAttestationMessage` objects during the assigned slot before the deadline of `3 * SECONDS_PER_SLOT // INTERVALS_PER_SLOT` seconds into the slot.
|
||||
|
||||
### Attestation
|
||||
|
||||
Attestation duties are not changed for validators, however the attestation deadline is implicitly changed by the change in `INTERVALS_PER_SLOT`.
|
||||
|
||||
### Sync Committee participations
|
||||
|
||||
Sync committee duties are not changed for validators, however the submission deadline is implicitly changed by the change in `INTERVALS_PER_SLOT`.
|
||||
|
||||
|
||||
### Block proposal
|
||||
|
||||
Validators are still expected to propose `SignedBeaconBlock` at the beginning of any slot during which `is_proposer(state, validator_index)` returns `true`. The mechanism to prepare this beacon block and related sidecars differs from previous forks as follows
|
||||
|
||||
#### Constructing the new `signed_execution_payload_header` field in `BeaconBlockBody`
|
||||
|
||||
To obtain `signed_execution_payload_header`, a block proposer building a block on top of a `state` must take the following actions:
|
||||
* Listen to the `execution_payload_header` gossip global topic and save an accepted `signed_execution_payload_header` from a builder. Proposer MAY obtain these signed messages by other off-protocol means.
|
||||
* The `signed_execution_payload_header` must satisfy the verification conditions found in `process_execution_payload_header`, that is
|
||||
- The header signature must be valid
|
||||
- The builder balance can cover the header value
|
||||
- The header slot is for the proposal block slot
|
||||
- The header parent block hash equals the state's `latest_block_hash`.
|
||||
- The header parent block root equals the current block's `parent_root`.
|
||||
* Select one bid and set `body.signed_execution_payload_header = signed_execution_payload_header`
|
||||
|
||||
#### Constructing the new `payload_attestations` field in `BeaconBlockBody`
|
||||
|
||||
Up to `MAX_PAYLOAD_ATTESTATIONS`, aggregate payload attestations can be included in the block. The validator will have to
|
||||
* Listen to the `payload_attestation_message` gossip global topic
|
||||
* The payload attestations added must satisfy the verification conditions found in payload attestation gossip validation and payload attestation processing. This means
|
||||
- The `data.beacon_block_root` corresponds to `block.parent_root`.
|
||||
- The slot of the parent block is exactly one slot before the proposing slot.
|
||||
- The signature of the payload attestation data message verifies correctly.
|
||||
* The proposer needs to aggregate all payload attestations with the same data into a given `PayloadAttestation` object. For this it needs to fill the `aggregation_bits` field by using the relative position of the validator indices with respect to the PTC that is obtained from `get_ptc(state, block_slot - 1)`.
|
||||
* The proposer should only include payload attestations that are consistent with the current block they are proposing. That is, if the previous block had a payload, they should only include attestations with `payload_status = PAYLOAD_PRESENT`. Proposers are penalized for attestations that are not-consistent with their view.
|
||||
|
||||
#### Blob sidecars
|
||||
The blob sidecars are no longer broadcast by the validator, and thus their construction is not necessary. This deprecates the corresponding sections from the honest validator guide in the Electra fork, moving them, albeit with some modifications, to the [honest Builder guide](./builder.md)
|
||||
|
||||
### Payload timeliness attestation
|
||||
|
||||
Some validators are selected to submit payload timeliness attestations. Validators should call `get_ptc_assignment` at the beginning of an epoch to be prepared to submit their PTC attestations during the next epoch.
|
||||
|
||||
A validator should create and broadcast the `payload_attestation_message` to the global execution attestation subnet not after `SECONDS_PER_SLOT * 3 / INTERVALS_PER_SLOT` seconds since the start of `slot`
|
||||
|
||||
#### Constructing a payload attestation
|
||||
|
||||
If a validator is in the payload attestation committee for the current slot (as obtained from `get_ptc_assignment` above) then the validator should prepare a `PayloadAttestationMessage` for the current slot,
|
||||
according to the logic in `get_payload_attestation_message` below and broadcast it not after `SECONDS_PER_SLOT * 3 / INTERVALS_PER_SLOT` seconds since the start of the slot, to the global `payload_attestation_message` pubsub topic.
|
||||
|
||||
The validator creates `payload_attestation_message` as follows:
|
||||
* If the validator has not seen any beacon block for the assigned slot, do not submit a payload attestation. It will be ignored anyway.
|
||||
* Set `data.beacon_block_root` be the HTR of the beacon block seen for the assigned slot
|
||||
* Set `data.slot` to be the assigned slot.
|
||||
* Set `data.payload_status` as follows
|
||||
- If a `SignedExecutionPayloadEnvelope` has been seen referencing the block `data.beacon_block_root` and the envelope has `payload_withheld = False`, set to `PAYLOAD_PRESENT`.
|
||||
- If a `SignedExecutionPayloadEnvelope` has been seen referencing the block `data.beacon_block_root` and the envelope has `payload_withheld = True`, set to `PAYLOAD_WITHHELD`.
|
||||
- If no `SignedExecutionPayloadEnvelope` has been seen referencing the block `data.beacon_block_root` set to `PAYLOAD_ABSENT`.
|
||||
* Set `payload_attestation_message.validator_index = validator_index` where `validator_index` is the validator chosen to submit. The private key mapping to `state.validators[validator_index].pubkey` is used to sign the payload timeliness attestation.
|
||||
* Sign the `payload_attestation_message.data` using the helper `get_payload_attestation_message_signature`.
|
||||
|
||||
Notice that the attester only signs the `PayloadAttestationData` and not the `validator_index` field in the message. Proposers need to aggregate these attestations as described above.
|
||||
|
||||
```python
|
||||
def get_payload_attestation_message_signature(
|
||||
state: BeaconState, attestation: PayloadAttestationMessage, privkey: int) -> BLSSignature:
|
||||
domain = get_domain(state, DOMAIN_PTC_ATTESTER, compute_epoch_at_slot(attestation.data.slot))
|
||||
signing_root = compute_signing_root(attestation.data, domain)
|
||||
return bls.Sign(privkey, signing_root)
|
||||
```
|
||||
|
||||
**Remark** Validators do not need to check the full validity of the `ExecutionPayload` contained in within the envelope, but the checks in the [P2P guide](./p2p-interface.md) should pass for the `SignedExecutionPayloadEnvelope`.
|
||||
|
||||
|
1
tests/core/pyspec/eth2spec/eip7732/__init__.py
Normal file
1
tests/core/pyspec/eth2spec/eip7732/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import mainnet as spec # noqa:F401
|
@ -1,5 +1,6 @@
|
||||
from eth2spec.test.helpers.execution_payload import build_empty_execution_payload
|
||||
from eth2spec.test.helpers.forks import is_post_whisk, is_post_altair, is_post_bellatrix
|
||||
from eth2spec.test.helpers.execution_payload import build_empty_signed_execution_payload_header
|
||||
from eth2spec.test.helpers.forks import is_post_whisk, is_post_altair, is_post_bellatrix, is_post_eip7732
|
||||
from eth2spec.test.helpers.keys import privkeys, whisk_ks_initial, whisk_ks_final
|
||||
from eth2spec.utils import bls
|
||||
from eth2spec.utils.bls import only_with_bls
|
||||
@ -117,6 +118,11 @@ def build_empty_block(spec, state, slot=None, proposer_index=None):
|
||||
if is_post_altair(spec):
|
||||
empty_block.body.sync_aggregate.sync_committee_signature = spec.G2_POINT_AT_INFINITY
|
||||
|
||||
if is_post_eip7732(spec):
|
||||
signed_header = build_empty_signed_execution_payload_header(spec, state)
|
||||
empty_block.body.signed_execution_payload_header = signed_header
|
||||
return empty_block
|
||||
|
||||
if is_post_bellatrix(spec):
|
||||
empty_block.body.execution_payload = build_empty_execution_payload(spec, state)
|
||||
|
||||
|
@ -20,6 +20,7 @@ DAS = SpecForkName('das')
|
||||
ELECTRA = SpecForkName('electra')
|
||||
WHISK = SpecForkName('whisk')
|
||||
EIP7594 = SpecForkName('eip7594')
|
||||
EIP7732 = SpecForkName('eip7732')
|
||||
|
||||
#
|
||||
# SpecFork settings
|
||||
@ -43,7 +44,7 @@ LIGHT_CLIENT_TESTING_FORKS = (*[item for item in MAINNET_FORKS if item != PHASE0
|
||||
# The forks that output to the test vectors.
|
||||
TESTGEN_FORKS = (*MAINNET_FORKS, ELECTRA, EIP7594, WHISK)
|
||||
# Forks allowed in the test runner `--fork` flag, to fail fast in case of typos
|
||||
ALLOWED_TEST_RUNNER_FORKS = (*ALL_PHASES, WHISK)
|
||||
ALLOWED_TEST_RUNNER_FORKS = (*ALL_PHASES, WHISK, EIP7732)
|
||||
|
||||
# NOTE: the same definition as in `pysetup/md_doc_paths.py`
|
||||
PREVIOUS_FORK_OF = {
|
||||
@ -57,6 +58,7 @@ PREVIOUS_FORK_OF = {
|
||||
# Experimental patches
|
||||
WHISK: CAPELLA,
|
||||
EIP7594: DENEB,
|
||||
EIP7732: ELECTRA,
|
||||
}
|
||||
|
||||
# For fork transition tests
|
||||
|
@ -3,16 +3,31 @@ from trie import HexaryTrie
|
||||
from rlp import encode
|
||||
from rlp.sedes import big_endian_int, Binary, List
|
||||
|
||||
from eth2spec.test.helpers.keys import privkeys
|
||||
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
||||
from eth2spec.debug.random_value import get_random_bytes_list
|
||||
from eth2spec.test.helpers.withdrawals import get_expected_withdrawals
|
||||
from eth2spec.test.helpers.forks import (
|
||||
is_post_capella,
|
||||
is_post_deneb,
|
||||
is_post_electra,
|
||||
is_post_eip7732,
|
||||
)
|
||||
|
||||
|
||||
def get_execution_payload_header(spec, execution_payload):
|
||||
if is_post_eip7732(spec):
|
||||
return spec.ExecutionPayloadHeader(
|
||||
parent_block_hash=execution_payload.parent_hash,
|
||||
parent_block_root=spec.Root(), # TODO: Fix this
|
||||
block_hash=execution_payload.block_hash,
|
||||
gas_limit=execution_payload.gas_limit,
|
||||
builder_index=spec.ValidatorIndex(0), # TODO: Fix this
|
||||
slot=spec.Slot(0), # TODO: Fix this
|
||||
value=spec.Gwei(0), # TODO: Fix this
|
||||
blob_kzg_commitments_root=spec.Root() # TODO: Fix this
|
||||
)
|
||||
|
||||
payload_header = spec.ExecutionPayloadHeader(
|
||||
parent_hash=execution_payload.parent_hash,
|
||||
fee_recipient=execution_payload.fee_recipient,
|
||||
@ -64,6 +79,9 @@ def compute_el_header_block_hash(spec,
|
||||
"""
|
||||
Computes the RLP execution block hash described by an `ExecutionPayloadHeader`.
|
||||
"""
|
||||
if is_post_eip7732(spec):
|
||||
return spec.Hash32()
|
||||
|
||||
execution_payload_header_rlp = [
|
||||
# parent_hash
|
||||
(Binary(32, 32), payload_header.parent_hash),
|
||||
@ -218,6 +236,34 @@ def compute_el_block_hash(spec, payload, pre_state):
|
||||
)
|
||||
|
||||
|
||||
def build_empty_post_eip7732_execution_payload_header(spec, state):
|
||||
if not is_post_eip7732(spec):
|
||||
return
|
||||
parent_block_root = hash_tree_root(state.latest_block_header)
|
||||
return spec.ExecutionPayloadHeader(
|
||||
parent_block_hash=state.latest_block_hash,
|
||||
parent_block_root=parent_block_root,
|
||||
block_hash=spec.Hash32(),
|
||||
gas_limit=spec.uint64(0),
|
||||
builder_index=spec.ValidatorIndex(0),
|
||||
slot=state.slot,
|
||||
value=spec.Gwei(0),
|
||||
blob_kzg_commitments_root=spec.Root()
|
||||
)
|
||||
|
||||
|
||||
def build_empty_signed_execution_payload_header(spec, state):
|
||||
if not is_post_eip7732(spec):
|
||||
return
|
||||
message = build_empty_post_eip7732_execution_payload_header(spec, state)
|
||||
privkey = privkeys[0]
|
||||
signature = spec.get_execution_payload_header_signature(state, message, privkey)
|
||||
return spec.SignedExecutionPayloadHeader(
|
||||
message=message,
|
||||
signature=signature,
|
||||
)
|
||||
|
||||
|
||||
def build_empty_execution_payload(spec, state, randao_mix=None):
|
||||
"""
|
||||
Assuming a pre-state of the same slot, build a valid ExecutionPayload without any transactions.
|
||||
@ -232,18 +278,19 @@ def build_empty_execution_payload(spec, state, randao_mix=None):
|
||||
payload = spec.ExecutionPayload(
|
||||
parent_hash=latest.block_hash,
|
||||
fee_recipient=spec.ExecutionAddress(),
|
||||
state_root=latest.state_root, # no changes to the state
|
||||
receipts_root=spec.Bytes32(bytes.fromhex("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347")),
|
||||
logs_bloom=spec.ByteVector[spec.BYTES_PER_LOGS_BLOOM](), # TODO: zeroed logs bloom for empty logs ok?
|
||||
block_number=latest.block_number + 1,
|
||||
prev_randao=randao_mix,
|
||||
gas_limit=latest.gas_limit, # retain same limit
|
||||
gas_used=0, # empty block, 0 gas
|
||||
timestamp=timestamp,
|
||||
extra_data=spec.ByteList[spec.MAX_EXTRA_DATA_BYTES](),
|
||||
base_fee_per_gas=latest.base_fee_per_gas, # retain same base_fee
|
||||
transactions=empty_txs,
|
||||
)
|
||||
if not is_post_eip7732(spec):
|
||||
payload.state_root = latest.state_root # no changes to the state
|
||||
payload.block_number = latest.block_number + 1
|
||||
payload.gas_limit = latest.gas_limit # retain same limit
|
||||
payload.base_fee_per_gas = latest.base_fee_per_gas # retain same base_fee
|
||||
if is_post_capella(spec):
|
||||
payload.withdrawals = get_expected_withdrawals(spec, state)
|
||||
if is_post_deneb(spec):
|
||||
|
@ -1,6 +1,6 @@
|
||||
from .constants import (
|
||||
PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB,
|
||||
ELECTRA, WHISK,
|
||||
ELECTRA, WHISK, EIP7732,
|
||||
PREVIOUS_FORK_OF,
|
||||
)
|
||||
|
||||
@ -45,6 +45,10 @@ def is_post_whisk(spec):
|
||||
return is_post_fork(spec.fork, WHISK)
|
||||
|
||||
|
||||
def is_post_eip7732(spec):
|
||||
return is_post_fork(spec.fork, EIP7732)
|
||||
|
||||
|
||||
def get_spec_for_fork_version(spec, fork_version, phases):
|
||||
if phases is None:
|
||||
return spec
|
||||
|
Loading…
x
Reference in New Issue
Block a user