mirror of
https://github.com/ethereum/consensus-specs.git
synced 2026-01-10 08:18:57 -05:00
Add EIP-7805 (FOCIL) specs (#4003)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -27,6 +27,7 @@ tests/core/pyspec/eth2spec/fulu/
|
||||
tests/core/pyspec/eth2spec/eip6800/
|
||||
tests/core/pyspec/eth2spec/eip7441/
|
||||
tests/core/pyspec/eth2spec/eip7732/
|
||||
tests/core/pyspec/eth2spec/eip7805/
|
||||
|
||||
# coverage reports
|
||||
.htmlcov
|
||||
|
||||
5
Makefile
5
Makefile
@@ -12,7 +12,8 @@ ALL_EXECUTABLE_SPEC_NAMES = \
|
||||
fulu \
|
||||
eip6800 \
|
||||
eip7441 \
|
||||
eip7732
|
||||
eip7732 \
|
||||
eip7805
|
||||
|
||||
# A list of fake targets.
|
||||
.PHONY: \
|
||||
@@ -265,4 +266,4 @@ kzg_setups: pyspec
|
||||
|
||||
# Delete all untracked files.
|
||||
clean:
|
||||
@git clean -fdx
|
||||
@git clean -fdx
|
||||
|
||||
@@ -181,3 +181,8 @@ PROPOSER_SELECTION_GAP: 2
|
||||
|
||||
# EIP7732
|
||||
MAX_REQUEST_PAYLOADS: 128
|
||||
|
||||
# EIP7805
|
||||
ATTESTATION_DEADLINE: 4
|
||||
PROPOSER_INCLUSION_LIST_CUT_OFF: 11
|
||||
VIEW_FREEZE_DEADLINE: 9
|
||||
|
||||
@@ -182,3 +182,8 @@ PROPOSER_SELECTION_GAP: 1
|
||||
|
||||
# EIP7732
|
||||
MAX_REQUEST_PAYLOADS: 128
|
||||
|
||||
# EIP7805
|
||||
ATTESTATION_DEADLINE: 2
|
||||
PROPOSER_INCLUSION_LIST_CUT_OFF: 5
|
||||
VIEW_FREEZE_DEADLINE: 3
|
||||
|
||||
@@ -9,6 +9,7 @@ FULU = 'fulu'
|
||||
EIP6800 = 'eip6800'
|
||||
EIP7441 = 'eip7441'
|
||||
EIP7732 = 'eip7732'
|
||||
EIP7805 = 'eip7805'
|
||||
|
||||
|
||||
# The helper functions that are used when defining constants
|
||||
|
||||
@@ -198,7 +198,7 @@ ignored_dependencies = [
|
||||
'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256',
|
||||
'bytes', 'byte', 'ByteList', 'ByteVector',
|
||||
'Dict', 'dict', 'field', 'ceillog2', 'floorlog2', 'Set',
|
||||
'Optional', 'Sequence',
|
||||
'Optional', 'Sequence', 'Tuple',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ from .constants import (
|
||||
EIP6800,
|
||||
EIP7441,
|
||||
EIP7732,
|
||||
EIP7805,
|
||||
)
|
||||
|
||||
|
||||
@@ -25,6 +26,7 @@ PREVIOUS_FORK_OF = {
|
||||
EIP6800: DENEB,
|
||||
EIP7441: CAPELLA,
|
||||
EIP7732: ELECTRA,
|
||||
EIP7805: ELECTRA,
|
||||
}
|
||||
|
||||
ALL_FORKS = list(PREVIOUS_FORK_OF.keys())
|
||||
|
||||
@@ -8,6 +8,7 @@ from .fulu import FuluSpecBuilder
|
||||
from .eip6800 import EIP6800SpecBuilder
|
||||
from .eip7441 import EIP7441SpecBuilder
|
||||
from .eip7732 import EIP7732SpecBuilder
|
||||
from .eip7805 import EIP7805SpecBuilder
|
||||
|
||||
|
||||
spec_builders = {
|
||||
@@ -15,5 +16,6 @@ spec_builders = {
|
||||
for builder in (
|
||||
Phase0SpecBuilder, AltairSpecBuilder, BellatrixSpecBuilder, CapellaSpecBuilder, DenebSpecBuilder,
|
||||
ElectraSpecBuilder, FuluSpecBuilder, EIP6800SpecBuilder, EIP7441SpecBuilder, EIP7732SpecBuilder,
|
||||
EIP7805SpecBuilder,
|
||||
)
|
||||
}
|
||||
|
||||
47
pysetup/spec_builders/eip7805.py
Normal file
47
pysetup/spec_builders/eip7805.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from .base import BaseSpecBuilder
|
||||
from ..constants import EIP7805
|
||||
|
||||
|
||||
class EIP7805SpecBuilder(BaseSpecBuilder):
|
||||
fork: str = EIP7805
|
||||
|
||||
|
||||
@classmethod
|
||||
def execution_engine_cls(cls) -> str:
|
||||
return """
|
||||
class NoopExecutionEngine(ExecutionEngine):
|
||||
|
||||
def notify_new_payload(self: ExecutionEngine,
|
||||
execution_payload: ExecutionPayload,
|
||||
parent_beacon_block_root: Root,
|
||||
execution_requests_list: Sequence[bytes],
|
||||
inclusion_list_transactions: Sequence[Transaction]) -> bool:
|
||||
return True
|
||||
|
||||
def notify_forkchoice_updated(self: ExecutionEngine,
|
||||
head_block_hash: Hash32,
|
||||
safe_block_hash: Hash32,
|
||||
finalized_block_hash: Hash32,
|
||||
payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]:
|
||||
pass
|
||||
|
||||
def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> GetPayloadResponse:
|
||||
# pylint: disable=unused-argument
|
||||
raise NotImplementedError("no default block production")
|
||||
|
||||
def is_valid_block_hash(self: ExecutionEngine,
|
||||
execution_payload: ExecutionPayload,
|
||||
parent_beacon_block_root: Root,
|
||||
execution_requests_list: Sequence[bytes],
|
||||
inclusion_list_transactions: Sequence[Transaction]) -> bool:
|
||||
return True
|
||||
|
||||
def is_valid_versioned_hashes(self: ExecutionEngine, new_payload_request: NewPayloadRequest) -> bool:
|
||||
return True
|
||||
|
||||
def verify_and_notify_new_payload(self: ExecutionEngine,
|
||||
new_payload_request: NewPayloadRequest) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
EXECUTION_ENGINE = NoopExecutionEngine()"""
|
||||
265
specs/_features/eip7805/beacon-chain.md
Normal file
265
specs/_features/eip7805/beacon-chain.md
Normal file
@@ -0,0 +1,265 @@
|
||||
# EIP-7805 -- 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)
|
||||
- [Preset](#preset)
|
||||
- [Domain types](#domain-types)
|
||||
- [Inclusion List Committee](#inclusion-list-committee)
|
||||
- [Execution](#execution)
|
||||
- [Containers](#containers)
|
||||
- [New containers](#new-containers)
|
||||
- [`InclusionList`](#inclusionlist)
|
||||
- [`SignedInclusionList`](#signedinclusionlist)
|
||||
- [Predicates](#predicates)
|
||||
- [New `is_valid_inclusion_list_signature`](#new-is_valid_inclusion_list_signature)
|
||||
- [Beacon State accessors](#beacon-state-accessors)
|
||||
- [New `get_inclusion_list_committee`](#new-get_inclusion_list_committee)
|
||||
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
|
||||
- [Execution engine](#execution-engine)
|
||||
- [Request data](#request-data)
|
||||
- [Modified `NewPayloadRequest`](#modified-newpayloadrequest)
|
||||
- [Engine APIs](#engine-apis)
|
||||
- [Modified `is_valid_block_hash`](#modified-is_valid_block_hash)
|
||||
- [Modified `notify_new_payload`](#modified-notify_new_payload)
|
||||
- [Modified `verify_and_notify_new_payload`](#modified-verify_and_notify_new_payload)
|
||||
- [Modified `process_execution_payload`](#modified-process_execution_payload)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- /TOC -->
|
||||
|
||||
## Introduction
|
||||
|
||||
This is the beacon chain specification to add EIP-7805 / fork-choice enforced, committee-based inclusion list (FOCIL) mechanism to allow forced transaction inclusion. Refers to the following posts:
|
||||
- [Fork-Choice enforced Inclusion Lists (FOCIL): A simple committee-based inclusion list proposal](https://ethresear.ch/t/fork-choice-enforced-inclusion-lists-focil-a-simple-committee-based-inclusion-list-proposal/19870/1)
|
||||
- [FOCIL CL & EL workflow](https://ethresear.ch/t/focil-cl-el-workflow/20526)
|
||||
*Note:* This specification is built upon [Electra](../../electra/beacon_chain.md) and is under active development.
|
||||
|
||||
## Preset
|
||||
|
||||
### Domain types
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `DOMAIN_INCLUSION_LIST_COMMITTEE` | `DomainType('0x0C000000')` |
|
||||
|
||||
### Inclusion List Committee
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `INCLUSION_LIST_COMMITTEE_SIZE` | `uint64(2**4)` (=16) |
|
||||
|
||||
### Execution
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `MAX_TRANSACTIONS_PER_INCLUSION_LIST` | `uint64(1)` **TBD** |
|
||||
|
||||
## Containers
|
||||
|
||||
### New containers
|
||||
|
||||
#### `InclusionList`
|
||||
|
||||
```python
|
||||
class InclusionList(Container):
|
||||
slot: Slot
|
||||
validator_index: ValidatorIndex
|
||||
inclusion_list_committee_root: Root
|
||||
transactions: List[Transaction, MAX_TRANSACTIONS_PER_INCLUSION_LIST]
|
||||
```
|
||||
|
||||
#### `SignedInclusionList`
|
||||
|
||||
```python
|
||||
class SignedInclusionList(Container):
|
||||
message: InclusionList
|
||||
signature: BLSSignature
|
||||
```
|
||||
|
||||
### Predicates
|
||||
|
||||
#### New `is_valid_inclusion_list_signature`
|
||||
|
||||
```python
|
||||
def is_valid_inclusion_list_signature(
|
||||
state: BeaconState,
|
||||
signed_inclusion_list: SignedInclusionList) -> bool:
|
||||
"""
|
||||
Check if ``signed_inclusion_list`` has a valid signature.
|
||||
"""
|
||||
message = signed_inclusion_list.message
|
||||
index = message.validator_index
|
||||
pubkey = state.validators[index].pubkey
|
||||
domain = get_domain(state, DOMAIN_INCLUSION_LIST_COMMITTEE, compute_epoch_at_slot(message.slot))
|
||||
signing_root = compute_signing_root(message, domain)
|
||||
return bls.Verify(pubkey, signing_root, signed_inclusion_list.signature)
|
||||
```
|
||||
|
||||
### Beacon State accessors
|
||||
|
||||
#### New `get_inclusion_list_committee`
|
||||
|
||||
```python
|
||||
def get_inclusion_list_committee(state: BeaconState,
|
||||
slot: Slot) -> Vector[ValidatorIndex, INCLUSION_LIST_COMMITTEE_SIZE]:
|
||||
epoch = compute_epoch_at_slot(slot)
|
||||
seed = get_seed(state, epoch, DOMAIN_INCLUSION_LIST_COMMITTEE)
|
||||
indices = get_active_validator_indices(state, epoch)
|
||||
start = (slot % SLOTS_PER_EPOCH) * INCLUSION_LIST_COMMITTEE_SIZE
|
||||
end = start + INCLUSION_LIST_COMMITTEE_SIZE
|
||||
return [
|
||||
indices[compute_shuffled_index(uint64(i % len(indices)), uint64(len(indices)), seed)]
|
||||
for i in range(start, end)
|
||||
]
|
||||
```
|
||||
|
||||
## Beacon chain state transition function
|
||||
|
||||
### Execution engine
|
||||
|
||||
#### Request data
|
||||
|
||||
##### Modified `NewPayloadRequest`
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class NewPayloadRequest(object):
|
||||
execution_payload: ExecutionPayload
|
||||
versioned_hashes: Sequence[VersionedHash]
|
||||
parent_beacon_block_root: Root
|
||||
execution_requests: ExecutionRequests
|
||||
inclusion_list_transactions: Sequence[Transaction] # [New in EIP-7805]
|
||||
```
|
||||
|
||||
#### Engine APIs
|
||||
|
||||
##### Modified `is_valid_block_hash`
|
||||
|
||||
*Note*: The function `is_valid_block_hash` is modified to include the additional `inclusion_list_transactions`.
|
||||
|
||||
```python
|
||||
def is_valid_block_hash(self: ExecutionEngine,
|
||||
execution_payload: ExecutionPayload,
|
||||
parent_beacon_block_root: Root,
|
||||
execution_requests_list: Sequence[bytes],
|
||||
inclusion_list_transactions: Sequence[Transaction]) -> bool:
|
||||
"""
|
||||
Return ``True`` if and only if ``execution_payload.block_hash`` is computed correctly.
|
||||
"""
|
||||
...
|
||||
```
|
||||
|
||||
##### Modified `notify_new_payload`
|
||||
|
||||
*Note*: The function `notify_new_payload` is modified to include the additional `inclusion_list_transactions`.
|
||||
|
||||
```python
|
||||
def notify_new_payload(self: ExecutionEngine,
|
||||
execution_payload: ExecutionPayload,
|
||||
parent_beacon_block_root: Root,
|
||||
execution_requests_list: Sequence[bytes],
|
||||
inclusion_list_transactions: Sequence[Transaction]) -> bool:
|
||||
"""
|
||||
Return ``True`` if and only if ``execution_payload`` and ``execution_requests_list``
|
||||
are valid with respect to ``self.execution_state``.
|
||||
"""
|
||||
# TODO: move this outside of notify_new_payload.
|
||||
# If execution client returns block does not satisfy inclusion list transactions, cache the block
|
||||
# store.unsatisfied_inclusion_list_blocks.add(execution_payload.block_root)
|
||||
...
|
||||
```
|
||||
|
||||
##### Modified `verify_and_notify_new_payload`
|
||||
|
||||
*Note*: The function `verify_and_notify_new_payload` is modified to pass the additional parameter
|
||||
`inclusion_list_transactions` when calling `notify_new_payload` in EIP-7805.
|
||||
|
||||
```python
|
||||
def verify_and_notify_new_payload(self: ExecutionEngine,
|
||||
new_payload_request: NewPayloadRequest) -> bool:
|
||||
"""
|
||||
Return ``True`` if and only if ``new_payload_request`` is valid with respect to ``self.execution_state``.
|
||||
"""
|
||||
execution_payload = new_payload_request.execution_payload
|
||||
parent_beacon_block_root = new_payload_request.parent_beacon_block_root
|
||||
execution_requests_list = get_execution_requests_list(new_payload_request.execution_requests)
|
||||
inclusion_list_transactions = new_payload_request.inclusion_list_transactions # [New in EIP-7805]
|
||||
|
||||
if b'' in execution_payload.transactions:
|
||||
return False
|
||||
|
||||
if not self.is_valid_block_hash(
|
||||
execution_payload,
|
||||
parent_beacon_block_root,
|
||||
execution_requests_list):
|
||||
return False
|
||||
|
||||
if not self.is_valid_versioned_hashes(new_payload_request):
|
||||
return False
|
||||
|
||||
# [Modified in EIP-7805]
|
||||
if not self.notify_new_payload(
|
||||
execution_payload,
|
||||
parent_beacon_block_root,
|
||||
execution_requests_list,
|
||||
inclusion_list_transactions):
|
||||
return False
|
||||
|
||||
return True
|
||||
```
|
||||
|
||||
##### Modified `process_execution_payload`
|
||||
|
||||
```python
|
||||
def process_execution_payload(state: BeaconState, body: BeaconBlockBody, execution_engine: ExecutionEngine) -> None:
|
||||
payload = body.execution_payload
|
||||
|
||||
# Verify consistency of the parent hash with respect to the previous execution payload header
|
||||
assert payload.parent_hash == state.latest_execution_payload_header.block_hash
|
||||
# Verify prev_randao
|
||||
assert payload.prev_randao == get_randao_mix(state, get_current_epoch(state))
|
||||
# Verify timestamp
|
||||
assert payload.timestamp == compute_timestamp_at_slot(state, state.slot)
|
||||
# Verify commitments are under limit
|
||||
assert len(body.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK_ELECTRA
|
||||
# Verify the execution payload is valid
|
||||
versioned_hashes = [kzg_commitment_to_versioned_hash(commitment) for commitment in body.blob_kzg_commitments]
|
||||
# Verify inclusion list transactions
|
||||
inclusion_list_transactions: Sequence[Transaction] = [] # TODO: where do we get this?
|
||||
assert len(inclusion_list_transactions) <= MAX_TRANSACTIONS_PER_INCLUSION_LIST
|
||||
# Verify the payload with the execution engine
|
||||
assert execution_engine.verify_and_notify_new_payload(
|
||||
NewPayloadRequest(
|
||||
execution_payload=payload,
|
||||
versioned_hashes=versioned_hashes,
|
||||
parent_beacon_block_root=state.latest_block_header.parent_root,
|
||||
execution_requests=body.execution_requests,
|
||||
inclusion_list_transactions=inclusion_list_transactions,
|
||||
)
|
||||
)
|
||||
# Cache execution payload header
|
||||
state.latest_execution_payload_header = ExecutionPayloadHeader(
|
||||
parent_hash=payload.parent_hash,
|
||||
fee_recipient=payload.fee_recipient,
|
||||
state_root=payload.state_root,
|
||||
receipts_root=payload.receipts_root,
|
||||
logs_bloom=payload.logs_bloom,
|
||||
prev_randao=payload.prev_randao,
|
||||
block_number=payload.block_number,
|
||||
gas_limit=payload.gas_limit,
|
||||
gas_used=payload.gas_used,
|
||||
timestamp=payload.timestamp,
|
||||
extra_data=payload.extra_data,
|
||||
base_fee_per_gas=payload.base_fee_per_gas,
|
||||
block_hash=payload.block_hash,
|
||||
transactions_root=hash_tree_root(payload.transactions),
|
||||
withdrawals_root=hash_tree_root(payload.withdrawals),
|
||||
blob_gas_used=payload.blob_gas_used,
|
||||
excess_blob_gas=payload.excess_blob_gas,
|
||||
)
|
||||
```
|
||||
204
specs/_features/eip7805/fork-choice.md
Normal file
204
specs/_features/eip7805/fork-choice.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# EIP-7805 -- 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)
|
||||
- [Configuration](#configuration)
|
||||
- [Time parameters](#time-parameters)
|
||||
- [Fork choice](#fork-choice)
|
||||
- [Helpers](#helpers)
|
||||
- [Modified `Store`](#modified-store)
|
||||
- [New `validate_inclusion_lists`](#new-validate_inclusion_lists)
|
||||
- [New `get_attester_head`](#new-get_attester_head)
|
||||
- [Modified `get_proposer_head`](#modified-get_proposer_head)
|
||||
- [New `on_inclusion_list`](#new-on_inclusion_list)
|
||||
|
||||
<!-- 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-7805 upgrade.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Time parameters
|
||||
|
||||
| Name | Value | Unit | Duration |
|
||||
| - | - | :-: | :-: |
|
||||
| `VIEW_FREEZE_DEADLINE` | `SECONDS_PER_SLOT * 2 // 3 + 1` | seconds | 9 seconds |
|
||||
|
||||
## Fork choice
|
||||
|
||||
### Helpers
|
||||
|
||||
#### Modified `Store`
|
||||
|
||||
**Note:** `Store` is modified to track the seen inclusion lists and inclusion list equivocators.
|
||||
|
||||
```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
|
||||
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)
|
||||
# [New in EIP-7805]
|
||||
inclusion_lists: Dict[Tuple[Slot, Root], List[InclusionList]] = field(default_factory=dict)
|
||||
inclusion_list_equivocators: Dict[Tuple[Slot, Root], Set[ValidatorIndex]] = field(default_factory=dict)
|
||||
unsatisfied_inclusion_list_blocks: Set[Root] = field(default_factory=Set)
|
||||
```
|
||||
|
||||
#### New `validate_inclusion_lists`
|
||||
|
||||
```python
|
||||
def validate_inclusion_lists(store: Store,
|
||||
inclusion_list_transactions: Sequence[Transaction],
|
||||
execution_payload: ExecutionPayload) -> None:
|
||||
"""
|
||||
The ``execution_payload`` satisfies ``inclusion_list_transactions`` validity conditions either
|
||||
when all transactions are present in payload or when any missing transactions are found to be
|
||||
invalid when appended to the end of the payload unless the block is full.
|
||||
"""
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
# Verify inclusion list is a valid length
|
||||
assert len(inclusion_list_transactions) <= MAX_TRANSACTIONS_PER_INCLUSION_LIST * INCLUSION_LIST_COMMITTEE_SIZE
|
||||
|
||||
# Verify inclusion list transactions are present in the execution payload
|
||||
contains_all_txs = all(tx in execution_payload.transactions for tx in inclusion_list_transactions)
|
||||
if contains_all_txs:
|
||||
return
|
||||
|
||||
# TODO: check remaining validity conditions
|
||||
```
|
||||
|
||||
#### New `get_attester_head`
|
||||
|
||||
```python
|
||||
def get_attester_head(store: Store, head_root: Root) -> Root:
|
||||
head_block = store.blocks[head_root]
|
||||
|
||||
if head_root in store.unsatisfied_inclusion_list_blocks:
|
||||
return head_block.parent_root
|
||||
return head_root
|
||||
|
||||
```
|
||||
|
||||
##### Modified `get_proposer_head`
|
||||
|
||||
The implementation of `get_proposer_head` is modified to also account for `store.unsatisfied_inclusion_list_blocks`.
|
||||
|
||||
```python
|
||||
def get_proposer_head(store: Store, head_root: Root, slot: Slot) -> Root:
|
||||
head_block = store.blocks[head_root]
|
||||
parent_root = head_block.parent_root
|
||||
parent_block = store.blocks[parent_root]
|
||||
|
||||
# Only re-org the head block if it arrived later than the attestation deadline.
|
||||
head_late = is_head_late(store, head_root)
|
||||
|
||||
# Do not re-org on an epoch boundary where the proposer shuffling could change.
|
||||
shuffling_stable = is_shuffling_stable(slot)
|
||||
|
||||
# Ensure that the FFG information of the new head will be competitive with the current head.
|
||||
ffg_competitive = is_ffg_competitive(store, head_root, parent_root)
|
||||
|
||||
# Do not re-org if the chain is not finalizing with acceptable frequency.
|
||||
finalization_ok = is_finalization_ok(store, slot)
|
||||
|
||||
# Only re-org if we are proposing on-time.
|
||||
proposing_on_time = is_proposing_on_time(store)
|
||||
|
||||
# Only re-org a single slot at most.
|
||||
parent_slot_ok = parent_block.slot + 1 == head_block.slot
|
||||
current_time_ok = head_block.slot + 1 == slot
|
||||
single_slot_reorg = parent_slot_ok and current_time_ok
|
||||
|
||||
# Check that the head has few enough votes to be overpowered by our proposer boost.
|
||||
assert store.proposer_boost_root != head_root # ensure boost has worn off
|
||||
head_weak = is_head_weak(store, head_root)
|
||||
|
||||
# Check that the missing votes are assigned to the parent and not being hoarded.
|
||||
parent_strong = is_parent_strong(store, parent_root)
|
||||
|
||||
reorg_prerequisites = all([shuffling_stable, ffg_competitive, finalization_ok,
|
||||
proposing_on_time, single_slot_reorg, head_weak, parent_strong])
|
||||
|
||||
# Check that the head block is in the unsatisfied inclusion list blocks
|
||||
inclusion_list_not_satisfied = head_root in store.unsatisfied_inclusion_list_blocks # [New in EIP-7805]
|
||||
|
||||
if reorg_prerequisites and (head_late or inclusion_list_not_satisfied):
|
||||
return parent_root
|
||||
else:
|
||||
return head_root
|
||||
```
|
||||
|
||||
#### New `on_inclusion_list`
|
||||
|
||||
`on_inclusion_list` is called to import `signed_inclusion_list` to the fork choice store.
|
||||
|
||||
```python
|
||||
def on_inclusion_list(
|
||||
store: Store,
|
||||
state: BeaconState,
|
||||
signed_inclusion_list: SignedInclusionList,
|
||||
inclusion_list_committee: Vector[ValidatorIndex, INCLUSION_LIST_COMMITTEE_SIZE]) -> None:
|
||||
"""
|
||||
Verify the inclusion list and import it into the fork choice store. If there exists more than
|
||||
one inclusion list in the store with the same slot and validator index, add the equivocator to
|
||||
the ``inclusion_list_equivocators`` cache. Otherwise, add the inclusion list to the
|
||||
``inclusion_lists` cache.
|
||||
"""
|
||||
message = signed_inclusion_list.message
|
||||
|
||||
# Verify inclusion list slot is either from the current or previous slot
|
||||
assert get_current_slot(store) in [message.slot, message.slot + 1]
|
||||
|
||||
time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT
|
||||
is_before_attesting_interval = time_into_slot < SECONDS_PER_SLOT // INTERVALS_PER_SLOT
|
||||
|
||||
# If the inclusion list is from the previous slot, ignore it if already past the attestation deadline
|
||||
if get_current_slot(store) == message.slot + 1:
|
||||
assert is_before_attesting_interval
|
||||
|
||||
# Sanity check that the given `inclusion_list_committee` matches the root in the inclusion list
|
||||
root = message.inclusion_list_committee_root
|
||||
assert hash_tree_root(inclusion_list_committee) == root
|
||||
|
||||
# Verify inclusion list validator is part of the committee
|
||||
validator_index = message.validator_index
|
||||
assert validator_index in inclusion_list_committee
|
||||
|
||||
# Verify inclusion list signature
|
||||
assert is_valid_inclusion_list_signature(state, signed_inclusion_list)
|
||||
|
||||
is_before_freeze_deadline = get_current_slot(store) == message.slot and time_into_slot < VIEW_FREEZE_DEADLINE
|
||||
|
||||
# Do not process inclusion lists from known equivocators
|
||||
if validator_index not in store.inclusion_list_equivocators[(message.slot, root)]:
|
||||
if validator_index in [il.validator_index for il in store.inclusion_lists[(message.slot, root)]]:
|
||||
validator_inclusion_list = [
|
||||
il for il in store.inclusion_lists[(message.slot, root)]
|
||||
if il.validator_index == validator_index
|
||||
][0]
|
||||
if validator_inclusion_list != message:
|
||||
# We have equivocation evidence for `validator_index`, record it as equivocator
|
||||
store.inclusion_list_equivocators[(message.slot, root)].add(validator_index)
|
||||
# This inclusion list is not an equivocation. Store it if prior to the view freeze deadline
|
||||
elif is_before_freeze_deadline:
|
||||
store.inclusion_lists[(message.slot, root)].append(message)
|
||||
```
|
||||
92
specs/_features/eip7805/p2p-interface.md
Normal file
92
specs/_features/eip7805/p2p-interface.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# EIP-7805 -- Networking
|
||||
|
||||
This document contains the consensus-layer networking specification for EIP-7805.
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
|
||||
- [Modifications in EIP-7805](#modifications-in-eip-7805)
|
||||
- [Configuration](#configuration)
|
||||
- [The gossip domain: gossipsub](#the-gossip-domain-gossipsub)
|
||||
- [Topics and messages](#topics-and-messages)
|
||||
- [Global topics](#global-topics)
|
||||
- [`inclusion_list`](#inclusion_list)
|
||||
- [The Req/Resp domain](#the-reqresp-domain)
|
||||
- [Messages](#messages)
|
||||
- [InclusionListByCommitteeIndices v1](#inclusionlistbycommitteeindices-v1)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
## Modifications in EIP-7805
|
||||
|
||||
### Configuration
|
||||
|
||||
| Name | Value | Unit | Duration |
|
||||
| - | - | :-: | :-: |
|
||||
| `ATTESTATION_DEADLINE` | `SECONDS_PER_SLOT // 3` | seconds | 4 seconds |
|
||||
|
||||
| Name | Value | Description |
|
||||
| - | - | - |
|
||||
| `MAX_REQUEST_INCLUSION_LIST` | `2**4` (= 16) | Maximum number of inclusion list in a single request |
|
||||
| `MAX_BYTES_PER_INCLUSION_LIST` | `2**13` (= 8192) | Maximum size of the inclusion list's transactions in bytes |
|
||||
|
||||
### The gossip domain: gossipsub
|
||||
|
||||
#### Topics and messages
|
||||
|
||||
The new topics along with the type of the `data` field of a gossipsub message are given in this table:
|
||||
|
||||
| Name | Message Type |
|
||||
| - | - |
|
||||
| `inclusion_list` | `SignedInclusionList` |
|
||||
|
||||
##### Global topics
|
||||
|
||||
EIP-7805 introduces a new global topic for inclusion lists.
|
||||
|
||||
###### `inclusion_list`
|
||||
|
||||
This topic is used to propagate signed inclusion list as `SignedInclusionList`.
|
||||
The following validations MUST pass before forwarding the `inclusion_list` on the network, assuming the alias `message = signed_inclusion_list.message`:
|
||||
|
||||
- _[REJECT]_ The size of `message.transactions` is within upperbound `MAX_BYTES_PER_INCLUSION_LIST`.
|
||||
- _[REJECT]_ The slot `message.slot` is equal to the previous or current slot.
|
||||
- _[IGNORE]_ The slot `message.slot` is equal to the current slot, or it is equal to the previous slot and the current time is less than `ATTESTATION_DEADLINE` seconds into the slot.
|
||||
- _[IGNORE]_ The `inclusion_list_committee` for slot `message.slot` on the current branch corresponds to `message.inclusion_list_committee_root`, as determined by `hash_tree_root(inclusion_list_committee) == message.inclusion_list_committee_root`.
|
||||
- _[REJECT]_ The validator index `message.validator_index` is within the `inclusion_list_committee` corresponding to `message.inclusion_list_committee_root`.
|
||||
- _[REJECT]_ The transactions `message.transactions` length is within upperbound `MAX_TRANSACTIONS_PER_INCLUSION_LIST`.
|
||||
- _[IGNORE]_ The `message` is either the first or second valid message received from the validator with index `message.validator_index`.
|
||||
- _[REJECT]_ The signature of `inclusion_list.signature` is valid with respect to the validator index.
|
||||
|
||||
### The Req/Resp domain
|
||||
|
||||
#### Messages
|
||||
|
||||
##### InclusionListByCommitteeIndices v1
|
||||
|
||||
**Protocol ID:** `/eth2/beacon_chain/req/inclusion_list_by_committee_indices/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 |
|
||||
|------------------------|--------------------------------|
|
||||
| `EIP7805_FORK_VERSION` | `EIP-7805.SignedInclusionList` |
|
||||
|
||||
Request Content:
|
||||
|
||||
```
|
||||
(
|
||||
slot: Slot
|
||||
committee_indices: Bitvector[INCLUSION_LIST_COMMITTEE_SIZE]
|
||||
)
|
||||
```
|
||||
|
||||
Response Content:
|
||||
|
||||
```
|
||||
(
|
||||
List[SignedInclusionList, MAX_REQUEST_INCLUSION_LIST]
|
||||
)
|
||||
```
|
||||
150
specs/_features/eip7805/validator.md
Normal file
150
specs/_features/eip7805/validator.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# EIP-7805 -- Honest Validator
|
||||
|
||||
## 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)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Configuration](#configuration)
|
||||
- [Time parameters](#time-parameters)
|
||||
- [Protocol](#protocol)
|
||||
- [`ExecutionEngine`](#executionengine)
|
||||
- [New inclusion list committee assignment](#new-inclusion-list-committee-assignment)
|
||||
- [Lookahead](#lookahead)
|
||||
- [New proposer duty](#new-proposer-duty)
|
||||
- [Block proposal](#block-proposal)
|
||||
- [Update execution client with inclusion lists](#update-execution-client-with-inclusion-lists)
|
||||
- [New inclusion list committee duty](#new-inclusion-list-committee-duty)
|
||||
- [Constructing a signed inclusion list](#constructing-a-signed-inclusion-list)
|
||||
- [Modified attester duty](#modified-attester-duty)
|
||||
- [Modified LMD GHOST vote](#modified-lmd-ghost-vote)
|
||||
- [Modified sync committee duty](#modified-sync-committee-duty)
|
||||
- [Modified beacon block root](#modified-beacon-block-root)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- /TOC -->
|
||||
|
||||
## Introduction
|
||||
|
||||
This document represents the changes to be made in the code of an "honest validator" to implement EIP-7805.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This document is an extension of the [Electra -- Honest Validator](../../electra/validator.md) guide.
|
||||
All behaviors and definitions defined in this document, and documents it extends, carry over unless explicitly noted or overridden.
|
||||
|
||||
All terminology, constants, functions, and protocol mechanics defined in the updated Beacon Chain doc of [EIP-7805](./beacon-chain.md) are requisite for this document and used throughout.
|
||||
Please see related Beacon Chain doc before continuing and use them as a reference throughout.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Time parameters
|
||||
|
||||
| Name | Value | Unit | Duration |
|
||||
| - | - | :-: | :-: |
|
||||
| `PROPOSER_INCLUSION_LIST_CUT_OFF` | `SECONDS_PER_SLOT - 1` | seconds | 11 seconds |
|
||||
|
||||
## Protocol
|
||||
|
||||
### `ExecutionEngine`
|
||||
|
||||
*Note*: `engine_getInclusionListV1` and `engine_updateBlockWithInclusionListV1` functions are added to the `ExecutionEngine` protocol for use as a validator.
|
||||
|
||||
The body of these function is implementation dependent. The Engine API may be used to implement it with an external execution engine.
|
||||
|
||||
## New inclusion list committee assignment
|
||||
|
||||
A validator may be a member of the new Inclusion List Committee (ILC) for a given slot. To check for ILC assignments the validator uses the helper `get_inclusion_committee_assignment(state, epoch, validator_index)` where `epoch <= next_epoch`.
|
||||
|
||||
Inclusion list committee selection is only stable within the context of the current and next epoch.
|
||||
|
||||
```python
|
||||
def get_inclusion_committee_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 ILC. 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_inclusion_list_committee(state, Slot(slot)):
|
||||
return Slot(slot)
|
||||
return None
|
||||
```
|
||||
|
||||
### Lookahead
|
||||
|
||||
`get_inclusion_committee_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 ILC slot.
|
||||
|
||||
## New proposer duty
|
||||
|
||||
### Block proposal
|
||||
|
||||
Proposers 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:
|
||||
|
||||
#### Update execution client with inclusion lists
|
||||
|
||||
The proposer should call `engine_updateInclusionListV1` at `PROPOSER_INCLUSION_LIST_CUT_OFF` into the slot with the list of the inclusion lists that gathered up to `PROPOSER_INCLUSION_LIST_CUT_OFF`.
|
||||
|
||||
## New inclusion list committee duty
|
||||
|
||||
Some validators are selected to submit signed inclusion list. Validators should call `get_inclusion_committee_assignment` at the beginning of an epoch to be prepared to submit their inclusion list during the next epoch.
|
||||
|
||||
A validator should create and broadcast the `signed_inclusion_list` to the global `inclusion_list` subnet by `PROPOSER_INCLUSION_LIST_CUT_OFF` seconds into the slot, unless a block for the current slot has been processed and is the head of the chain and broadcast to the network.
|
||||
|
||||
#### Constructing a signed inclusion list
|
||||
|
||||
The validator creates the `signed_inclusion_list` as follows:
|
||||
- First, the validator creates the `inclusion_list`.
|
||||
- Set `inclusion_list.slot` to the assigned slot returned by `get_inclusion_committee_assignment`.
|
||||
- Set `inclusion_list.validator_index` to the validator's index.
|
||||
- Set `inclusion_list.inclusion_list_committee_root` to the hash tree root of the committee that the validator is a member of.
|
||||
- Set `inclusion_list.transactions` using the response from `engine_getInclusionListV1` from the execution layer client.
|
||||
- Sign the `inclusion_list` using the helper `get_inclusion_list_signature` and obtain the `signature`.
|
||||
- Set `signed_inclusion_list.message` to `inclusion_list`.
|
||||
- Set `signed_inclusion_list.signature` to `signature`.
|
||||
|
||||
```python
|
||||
def get_inclusion_list_signature(
|
||||
state: BeaconState, inclusion_list: InclusionList, privkey: int) -> BLSSignature:
|
||||
domain = get_domain(state, DOMAIN_INCLUSION_LIST_COMMITTEE, compute_epoch_at_slot(inclusion_list.slot))
|
||||
signing_root = compute_signing_root(inclusion_list, domain)
|
||||
return bls.Sign(privkey, signing_root)
|
||||
```
|
||||
|
||||
## Modified attester duty
|
||||
|
||||
#### Modified LMD GHOST vote
|
||||
|
||||
Set `attestation_data.beacon_block_root = get_attester_head(store, head_root)`.
|
||||
|
||||
## Modified sync committee duty
|
||||
|
||||
#### Modified beacon block root
|
||||
|
||||
```python
|
||||
def get_sync_committee_message(state: BeaconState,
|
||||
block_root: Root,
|
||||
validator_index: ValidatorIndex,
|
||||
privkey: int,
|
||||
store: Store) -> SyncCommitteeMessage:
|
||||
epoch = get_current_epoch(state)
|
||||
domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, epoch)
|
||||
signing_root = compute_signing_root(block_root, domain)
|
||||
signature = bls.Sign(privkey, signing_root)
|
||||
|
||||
return SyncCommitteeMessage(
|
||||
slot=state.slot,
|
||||
beacon_block_root=get_attester_head(store, block_root),
|
||||
validator_index=validator_index,
|
||||
signature=signature,
|
||||
)
|
||||
```
|
||||
@@ -11,10 +11,13 @@ from eth2spec.test.context import (
|
||||
spec_state_test,
|
||||
with_altair_and_later,
|
||||
with_presets,
|
||||
with_all_phases_from_except,
|
||||
)
|
||||
from eth2spec.test.helpers.constants import (
|
||||
MAINNET,
|
||||
MINIMAL,
|
||||
ALTAIR,
|
||||
EIP7805,
|
||||
)
|
||||
|
||||
rng = random.Random(1337)
|
||||
@@ -139,7 +142,7 @@ def test_process_sync_committee_contributions(spec, state):
|
||||
spec.process_block(state, block)
|
||||
|
||||
|
||||
@with_altair_and_later
|
||||
@with_all_phases_from_except(ALTAIR, [EIP7805])
|
||||
@spec_state_test
|
||||
@always_bls
|
||||
def test_get_sync_committee_message(spec, state):
|
||||
|
||||
@@ -20,6 +20,7 @@ DAS = SpecForkName('das')
|
||||
FULU = SpecForkName('fulu')
|
||||
EIP7441 = SpecForkName('eip7441')
|
||||
EIP7732 = SpecForkName('eip7732')
|
||||
EIP7805 = SpecForkName('eip7805')
|
||||
|
||||
#
|
||||
# SpecFork settings
|
||||
@@ -58,6 +59,7 @@ PREVIOUS_FORK_OF = {
|
||||
FULU: ELECTRA,
|
||||
EIP7441: CAPELLA,
|
||||
EIP7732: ELECTRA,
|
||||
EIP7805: ELECTRA,
|
||||
}
|
||||
|
||||
# For fork transition tests
|
||||
|
||||
Reference in New Issue
Block a user