diff --git a/specs/_features/eip7805/beacon-chain.md b/specs/_features/eip7805/beacon-chain.md new file mode 100644 index 0000000000..a6b4c3a915 --- /dev/null +++ b/specs/_features/eip7805/beacon-chain.md @@ -0,0 +1,165 @@ +# EIP-7805 -- The Beacon Chain + +## Table of contents + + + + + +- [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) + - [`get_inclusion_list_committee`](#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 `verify_and_notify_new_payload`](#modified-verify_and_notify_new_payload) + + + + +## 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_IL_COMMITTEE` | `DomainType('0x0C000000')` # (New in EIP7805) | + +### Inclusion List Committee + +| Name | Value | +| - | - | +| `IL_COMMITTEE_SIZE` | `uint64(2**4)` (=16) # (New in EIP-7805) | + +### Execution + +| Name | Value | +| - | - | +| `MAX_TRANSACTIONS_PER_INCLUSION_LIST` | `uint64(1)` # (New in EIP-7805) TODO: Placeholder | + +## 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_IL_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 + +#### `get_inclusion_list_committee` + +```python +def get_inclusion_list_committee(state: BeaconState, slot: Slot) -> Vector[ValidatorIndex, IL_COMMITTEE_SIZE]: + epoch = compute_epoch_at_slot(slot) + seed = get_seed(state, epoch, DOMAIN_IL_COMMITTEE) + indices = get_active_validator_indices(state, epoch) + start = (slot % SLOTS_PER_EPOCH) * IL_COMMITTEE_SIZE + end = start + IL_COMMITTEE_SIZE + return [indices[compute_shuffled_index(uint64(i), 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 + il_transactions: List[Transaction, MAX_TRANSACTIONS_PER_INCLUSION_LIST] # [New in EIP-7805] +``` + +#### Engine APIs + +##### Modified `verify_and_notify_new_payload` + +*Note*: The function `verify_and_notify_new_payload` is modified to pass the additional parameter `il_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 + execution_requests = new_payload_request.execution_requests + parent_beacon_block_root = new_payload_request.parent_beacon_block_root + il_transactions = new_payload_request.il_transactions # [New in EIP-7805] + + if not self.is_valid_block_hash(execution_payload, parent_beacon_block_root): + 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, + execution_requests, + parent_beacon_block_root, + il_transactions): + return False + + return True +``` \ No newline at end of file diff --git a/specs/_features/eip7805/fork-choice.md b/specs/_features/eip7805/fork-choice.md new file mode 100644 index 0000000000..f17077dc7f --- /dev/null +++ b/specs/_features/eip7805/fork-choice.md @@ -0,0 +1,152 @@ +# EIP-7805 -- Fork Choice + +## Table of contents + + + + +- [Introduction](#introduction) +- [Configuration](#configuration) +- [Helpers](#helpers) + - [New `validate_inclusion_lists`](#new-validate_inclusion_lists) + - [Modified `Store`](#modified-store) + - [Modified `notify_new_payload`](#modified-notify_new_payload) + - [`get_attester_head`](#get_attester_head) + - [New `on_inclusion_list`](#new-on_inclusion_list) + + + + +## Introduction + +This is the modification of the fork choice accompanying the EIP-7805 upgrade. + +## Configuration + +| Name | Value | Unit | Duration | +| - | - | :-: | :-: | +| `VIEW_FREEZE_DEADLINE` | `uint64(9)` | seconds | 9 seconds # (New in EIP7805) | + +## Helpers + +### New `validate_inclusion_lists` + +```python +def validate_inclusion_lists(store: Store, inclusion_list_transactions: List[Transaction, MAX_TRANSACTIONS_PER_INCLUSION_LIST * IL_COMMITTEE_SIZE], execution_payload: ExecutionPayload) -> bool: + """ + Return ``True`` if and only if the input ``inclusion_list_transactions`` satisfies validation, + that to verify if 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. + """ + ... +``` + +### 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) + inclusion_lists: Dict[Tuple[Slot, Root], List[InclusionList]] = field(default_factory=dict) # [New in EIP-7805] + inclusion_list_equivocators: Dict[Tuple[Slot, Root], Set[ValidatorIndex]] = field(default_factory=dict) # [New in EIP-7805] + unsatisfied_inclusion_list_blocks: Set[Root] # [New in EIP-7805] +``` + +##### Modified `notify_new_payload` + +*Note*: The function `notify_new_payload` is modified to include the additional `il_transactions` parameter in EIP-7805. + +```python +def notify_new_payload(self: ExecutionEngine, + execution_payload: ExecutionPayload, + execution_requests: ExecutionRequests, + parent_beacon_block_root: Root, + il_transactions: List[Transaction, MAX_TRANSACTIONS_PER_INCLUSION_LIST], + store: Store) -> bool: + """ + Return ``True`` if and only if ``execution_payload`` and ``execution_requests`` + are valid with respect to ``self.execution_state``. + """ + + # If execution client returns block does not satisfy inclusion list transactions, cache the block + store.unsatisfied_inclusion_list_blocks.add(execution_payload.block_root) + ... +``` + +##### `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 + +``` + +### 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, + signed_inclusion_list: SignedInclusionList, + inclusion_list_committee: Vector[ValidatorIndex, IL_COMMITTEE_SIZE]]) -> None: + """ + Verify the inclusion list and import it into the fork choice store. + If there exists more than 1 inclusion list in store with the same slot and validator index, add the equivocator to the ``inclusion_list_equivocators`` cache. + Otherwise, add 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 inclusion_list_equivocators[(message.slot, root)]: + if validator_index in [il.validator_index for il in inclusion_lists[(message.slot, root)]]: + il = [il for il in inclusion_lists[(message.slot, root)] if il.validator_index == validator_index][0] + if validator_il != message: + # We have equivocation evidence for `validator_index`, record it as equivocator + 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: + inclusion_lists[(message.slot, root)].append(message) +``` + + diff --git a/specs/_features/eip7805/p2p-interface.md b/specs/_features/eip7805/p2p-interface.md new file mode 100644 index 0000000000..a5c9086a65 --- /dev/null +++ b/specs/_features/eip7805/p2p-interface.md @@ -0,0 +1,87 @@ +# EIP-7805 -- Networking + +This document contains the consensus-layer networking specification for EIP-7805. + + + + +- [Time parameters](#time-parameters) +- [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) + + + +### Time parameters + +| Name | Value | Unit | Duration | +| - | - | :-: | :-: | +| `attestation_deadline` | `uint64(4)` | seconds | 4 seconds | + +### Configuration + +| `MAX_REQUEST_INCLUSION_LIST` | `2**4` (= 16) | Maximum number of inclusion list in a single request | + +### 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` [New in EIP-7805] | + +##### 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 transactions `message.transactions` is not exceeding `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 `` 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[IL_COMMITTEE_SIZE] +) +``` + +Response Content: +``` +( + List[SignedInclusionList, MAX_REQUEST_INCLUSION_LIST] +) +``` diff --git a/specs/_features/eip7805/validator.md b/specs/_features/eip7805/validator.md new file mode 100644 index 0000000000..b71d3e44ee --- /dev/null +++ b/specs/_features/eip7805/validator.md @@ -0,0 +1,147 @@ +# EIP-7805 -- Honest Validator + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Prerequisites](#prerequisites) + - [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) + + + + +## 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. + +### Time parameters + +| Name | Value | Unit | Duration | +| - | - | :-: | :-: | +| `PROPOSER_INCLUSION_LIST_CUT_OFF` | `uint64(11)` | 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 since `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_IL_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) -> 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, + ) +```