Add EIP-7805 (FOCIL) specs (#4003)

This commit is contained in:
terence
2025-03-12 19:47:20 -07:00
committed by GitHub
parent 2cc2cd9421
commit 492572dc5b
15 changed files with 784 additions and 4 deletions

1
.gitignore vendored
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -9,6 +9,7 @@ FULU = 'fulu'
EIP6800 = 'eip6800'
EIP7441 = 'eip7441'
EIP7732 = 'eip7732'
EIP7805 = 'eip7805'
# The helper functions that are used when defining constants

View File

@@ -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',
]

View File

@@ -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())

View File

@@ -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,
)
}

View 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()"""

View 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,
)
```

View 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)
```

View 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]
)
```

View 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,
)
```

View File

@@ -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):

View File

@@ -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