Merge pull request #2896 from ethereum/dev

Bellatrix Release Candidate (v1.2.0-rc.1)
This commit is contained in:
Danny Ryan 2022-05-23 05:19:51 -06:00 committed by GitHub
commit b08f6d70f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 2491 additions and 120 deletions

View File

@ -129,6 +129,20 @@ jobs:
command: make citest fork=bellatrix
- store_test_results:
path: tests/core/pyspec/test-reports
test-capella:
docker:
- image: circleci/python:3.8
working_directory: ~/specs-repo
steps:
- restore_cache:
key: v3-specs-repo-{{ .Branch }}-{{ .Revision }}
- restore_pyspec_cached_venv
- run:
name: Run py-tests
command: make citest fork=capella
- store_test_results:
path: tests/core/pyspec/test-reports
table_of_contents:
docker:
- image: circleci/node:10.16.3
@ -243,6 +257,9 @@ workflows:
- test-bellatrix:
requires:
- install_pyspec_test
- test-capella:
requires:
- install_pyspec_test
- table_of_contents
- codespell
- lint:

1
.gitignore vendored
View File

@ -18,6 +18,7 @@ consensus-spec-tests/
tests/core/pyspec/eth2spec/phase0/
tests/core/pyspec/eth2spec/altair/
tests/core/pyspec/eth2spec/bellatrix/
tests/core/pyspec/eth2spec/capella/
# coverage reports
.htmlcov

View File

@ -25,9 +25,11 @@ GENERATOR_VENVS = $(patsubst $(GENERATOR_DIR)/%, $(GENERATOR_DIR)/%venv, $(GENER
MARKDOWN_FILES = $(wildcard $(SPEC_DIR)/phase0/*.md) $(wildcard $(SPEC_DIR)/altair/*.md) $(wildcard $(SSZ_DIR)/*.md) \
$(wildcard $(SPEC_DIR)/bellatrix/*.md) \
$(wildcard $(SPEC_DIR)/capella/*.md) \
$(wildcard $(SPEC_DIR)/custody/*.md) \
$(wildcard $(SPEC_DIR)/das/*.md) \
$(wildcard $(SPEC_DIR)/sharding/*.md)
$(wildcard $(SPEC_DIR)/sharding/*.md) \
$(wildcard $(SPEC_DIR)/eip4844/*.md)
COV_HTML_OUT=.htmlcov
COV_HTML_OUT_DIR=$(PY_SPEC_DIR)/$(COV_HTML_OUT)
@ -67,7 +69,7 @@ partial_clean:
clean: partial_clean
rm -rf venv
# legacy cleanup. The pyspec venv should be located at the repository root
# legacy cleanup. The pyspec venv should be located at the repository root
rm -rf $(PY_SPEC_DIR)/venv
rm -rf $(DEPOSIT_CONTRACT_COMPILER_DIR)/venv
rm -rf $(DEPOSIT_CONTRACT_TESTER_DIR)/venv
@ -97,12 +99,12 @@ install_test:
# Testing against `minimal` config by default
test: pyspec
. venv/bin/activate; cd $(PY_SPEC_DIR); \
python3 -m pytest -n 4 --disable-bls --cov=eth2spec.phase0.minimal --cov=eth2spec.altair.minimal --cov=eth2spec.bellatrix.minimal --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
python3 -m pytest -n 4 --disable-bls --cov=eth2spec.phase0.minimal --cov=eth2spec.altair.minimal --cov=eth2spec.bellatrix.minimal --cov=eth2spec.capella.minimal --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
# Testing against `minimal` config by default
find_test: pyspec
. venv/bin/activate; cd $(PY_SPEC_DIR); \
python3 -m pytest -k=$(K) --disable-bls --cov=eth2spec.phase0.minimal --cov=eth2spec.altair.minimal --cov=eth2spec.bellatrix.minimal --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
python3 -m pytest -k=$(K) --disable-bls --cov=eth2spec.phase0.minimal --cov=eth2spec.altair.minimal --cov=eth2spec.bellatrix.minimal --cov=eth2spec.capella.minimal --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
citest: pyspec
mkdir -p tests/core/pyspec/test-reports/eth2spec;
@ -135,7 +137,7 @@ lint: pyspec
. venv/bin/activate; cd $(PY_SPEC_DIR); \
flake8 --config $(LINTER_CONFIG_FILE) ./eth2spec \
&& pylint --disable=all --enable unused-argument ./eth2spec/phase0 ./eth2spec/altair ./eth2spec/bellatrix \
&& mypy --config-file $(LINTER_CONFIG_FILE) -p eth2spec.phase0 -p eth2spec.altair -p eth2spec.bellatrix
&& mypy --config-file $(LINTER_CONFIG_FILE) -p eth2spec.phase0 -p eth2spec.altair -p eth2spec.bellatrix -p eth2spec.capella
lint_generators: pyspec
. venv/bin/activate; cd $(TEST_GENERATORS_DIR); \

View File

@ -44,11 +44,16 @@ ALTAIR_FORK_EPOCH: 74240 # Oct 27, 2021, 10:56:23am UTC
# Bellatrix
BELLATRIX_FORK_VERSION: 0x02000000
BELLATRIX_FORK_EPOCH: 18446744073709551615
# Capella
CAPELLA_FORK_VERSION: 0x03000000
CAPELLA_FORK_EPOCH: 18446744073709551615
# Sharding
SHARDING_FORK_VERSION: 0x03000000
SHARDING_FORK_VERSION: 0x04000000
SHARDING_FORK_EPOCH: 18446744073709551615
# Time parameters
# ---------------------------------------------------------------
# 12 seconds
@ -79,8 +84,8 @@ CHURN_LIMIT_QUOTIENT: 65536
# Fork choice
# ---------------------------------------------------------------
# 70%
PROPOSER_SCORE_BOOST: 70
# 40%
PROPOSER_SCORE_BOOST: 40
# Deposit contract
# ---------------------------------------------------------------

View File

@ -43,8 +43,11 @@ ALTAIR_FORK_EPOCH: 18446744073709551615
# Bellatrix
BELLATRIX_FORK_VERSION: 0x02000001
BELLATRIX_FORK_EPOCH: 18446744073709551615
# Capella
CAPELLA_FORK_VERSION: 0x03000001
CAPELLA_FORK_EPOCH: 18446744073709551615
# Sharding
SHARDING_FORK_VERSION: 0x03000001
SHARDING_FORK_VERSION: 0x04000001
SHARDING_FORK_EPOCH: 18446744073709551615
@ -78,8 +81,8 @@ CHURN_LIMIT_QUOTIENT: 32
# Fork choice
# ---------------------------------------------------------------
# 70%
PROPOSER_SCORE_BOOST: 70
# 40%
PROPOSER_SCORE_BOOST: 40
# Deposit contract

48
fork_choice/safe-block.md Normal file
View File

@ -0,0 +1,48 @@
# Fork Choice -- Safe Block
## 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)
- [`get_safe_beacon_block_root`](#get_safe_beacon_block_root)
- [`get_safe_execution_payload_hash`](#get_safe_execution_payload_hash)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
Under honest majority and certain network synchronicity assumptions
there exist a block that is safe from re-orgs. Normally this block is
pretty close to the head of canonical chain which makes it valuable
to expose a safe block to users.
This section describes an algorithm to find a safe block.
## `get_safe_beacon_block_root`
```python
def get_safe_beacon_block_root(store: Store) -> Root:
# Use most recent justified block as a stopgap
return store.justified_checkpoint.root
```
*Note*: Currently safe block algorithm simply returns `store.justified_checkpoint.root`
and is meant to be improved in the future.
## `get_safe_execution_payload_hash`
```python
def get_safe_execution_payload_hash(store: Store) -> Hash32:
safe_block_root = get_safe_beacon_block_root(store)
safe_block = store.blocks[safe_block_root]
# Return Hash32() if no payload is yet justified
if compute_epoch_at_slot(safe_block.slot) >= BELLATRIX_FORK_EPOCH:
return safe_block.body.execution_payload.block_hash
else:
return Hash32()
```
*Note*: This helper uses beacon block container extended in [Bellatrix](../specs/bellatrix/beacon-chain.md).

View File

@ -0,0 +1 @@
# Minimal preset - Capella

View File

@ -0,0 +1 @@
# Minimal preset - Capella

View File

@ -12,6 +12,9 @@ from abc import ABC, abstractmethod
import ast
import subprocess
import sys
import copy
from collections import OrderedDict
# NOTE: have to programmatically include third-party dependencies in `setup.py`.
def installPackage(package: str):
@ -41,6 +44,8 @@ from marko.ext.gfm.elements import Table
PHASE0 = 'phase0'
ALTAIR = 'altair'
BELLATRIX = 'bellatrix'
CAPELLA = 'capella'
# The helper functions that are used when defining constants
CONSTANT_DEP_SUNDRY_CONSTANTS_FUNCTIONS = '''
@ -529,6 +534,7 @@ class NoopExecutionEngine(ExecutionEngine):
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
@ -548,9 +554,22 @@ EXECUTION_ENGINE = NoopExecutionEngine()"""
return {**super().hardcoded_custom_type_dep_constants(), **constants}
#
# CapellaSpecBuilder
#
class CapellaSpecBuilder(BellatrixSpecBuilder):
fork: str = CAPELLA
@classmethod
def imports(cls, preset_name: str):
return super().imports(preset_name) + f'''
from eth2spec.bellatrix import {preset_name} as bellatrix
'''
spec_builders = {
builder.fork: builder
for builder in (Phase0SpecBuilder, AltairSpecBuilder, BellatrixSpecBuilder)
for builder in (Phase0SpecBuilder, AltairSpecBuilder, BellatrixSpecBuilder, CapellaSpecBuilder)
}
@ -683,7 +702,7 @@ ignored_dependencies = [
'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256',
'bytes', 'byte', 'ByteList', 'ByteVector',
'Dict', 'dict', 'field', 'ceillog2', 'floorlog2', 'Set',
'Optional',
'Optional', 'Sequence',
]
@ -709,7 +728,6 @@ def dependency_order_class_objects(objects: Dict[str, str], custom_types: Dict[s
for item in [dep, key] + key_list[key_list.index(dep)+1:]:
objects[item] = objects.pop(item)
def combine_ssz_objects(old_objects: Dict[str, str], new_objects: Dict[str, str], custom_types) -> Dict[str, str]:
"""
Takes in old spec and new spec ssz objects, combines them,
@ -799,7 +817,12 @@ def _build_spec(preset_name: str, fork: str,
spec_object = combine_spec_objects(spec_object, value)
class_objects = {**spec_object.ssz_objects, **spec_object.dataclasses}
dependency_order_class_objects(class_objects, spec_object.custom_types)
# Ensure it's ordered after multiple forks
new_objects = {}
while OrderedDict(new_objects) != OrderedDict(class_objects):
new_objects = copy.deepcopy(class_objects)
dependency_order_class_objects(class_objects, spec_object.custom_types)
return objects_to_spec(preset_name, spec_object, spec_builders[fork], class_objects)
@ -846,14 +869,14 @@ class PySpecCommand(Command):
if len(self.md_doc_paths) == 0:
print("no paths were specified, using default markdown file paths for pyspec"
" build (spec fork: %s)" % self.spec_fork)
if self.spec_fork in (PHASE0, ALTAIR, BELLATRIX):
if self.spec_fork in (PHASE0, ALTAIR, BELLATRIX, CAPELLA):
self.md_doc_paths = """
specs/phase0/beacon-chain.md
specs/phase0/fork-choice.md
specs/phase0/validator.md
specs/phase0/weak-subjectivity.md
"""
if self.spec_fork in (ALTAIR, BELLATRIX):
if self.spec_fork in (ALTAIR, BELLATRIX, CAPELLA):
self.md_doc_paths += """
specs/altair/beacon-chain.md
specs/altair/bls.md
@ -862,7 +885,7 @@ class PySpecCommand(Command):
specs/altair/p2p-interface.md
specs/altair/sync-protocol.md
"""
if self.spec_fork == BELLATRIX:
if self.spec_fork in (BELLATRIX, CAPELLA):
self.md_doc_paths += """
specs/bellatrix/beacon-chain.md
specs/bellatrix/fork.md
@ -870,6 +893,14 @@ class PySpecCommand(Command):
specs/bellatrix/validator.md
sync/optimistic.md
"""
if self.spec_fork == CAPELLA:
self.md_doc_paths += """
specs/capella/beacon-chain.md
specs/capella/fork.md
specs/capella/fork-choice.md
specs/capella/validator.md
specs/capella/p2p-interface.md
"""
if len(self.md_doc_paths) == 0:
raise Exception('no markdown files specified, and spec fork "%s" is unknown', self.spec_fork)
@ -1017,7 +1048,7 @@ setup(
"eth-typing>=2.1.0,<3.0.0",
"pycryptodome==3.9.4",
"py_ecc==5.2.0",
"milagro_bls_binding==1.6.3",
"milagro_bls_binding==1.9.0",
"dataclasses==0.6",
"remerkleable==0.1.24",
RUAMEL_YAML_VERSION,

View File

@ -144,6 +144,7 @@ def get_sync_subcommittee_pubkeys(state: BeaconState, subcommittee_index: uint64
- _[REJECT]_ `contribution_and_proof.selection_proof` selects the validator as an aggregator for the slot -- i.e. `is_sync_committee_aggregator(contribution_and_proof.selection_proof)` returns `True`.
- _[REJECT]_ The aggregator's validator index is in the declared subcommittee of the current sync committee --
i.e. `state.validators[contribution_and_proof.aggregator_index].pubkey in get_sync_subcommittee_pubkeys(state, contribution.subcommittee_index)`.
- _[IGNORE]_ A valid sync committee contribution with equal `slot`, `beacon_block_root` and `subcommittee_index` whose `aggregation_bits` is non-strict superset has _not_ already been seen.
- _[IGNORE]_ The sync committee contribution is the first valid contribution received for the aggregator with index `contribution_and_proof.aggregator_index`
for the slot `contribution.slot` and subcommittee index `contribution.subcommittee_index`
(this requires maintaining a cache of size `SYNC_COMMITTEE_SIZE` for this topic that can be flushed after each slot).

View File

@ -53,7 +53,7 @@ uses sync committees introduced in [this beacon chain extension](./beacon-chain.
| Name | Value | Unit | Duration |
| - | - | - | - |
| `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | validators |
| `UPDATE_TIMEOUT` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | epochs | ~27.3 hours |
| `UPDATE_TIMEOUT` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | slots | ~27.3 hours |
## Containers

View File

@ -12,6 +12,7 @@
- [Protocols](#protocols)
- [`ExecutionEngine`](#executionengine)
- [`notify_forkchoice_updated`](#notify_forkchoice_updated)
- [`safe_block_hash`](#safe_block_hash)
- [Helpers](#helpers)
- [`PayloadAttributes`](#payloadattributes)
- [`PowBlock`](#powblock)
@ -47,8 +48,9 @@ The Engine API may be used to implement it with an external execution engine.
#### `notify_forkchoice_updated`
This function performs two actions *atomically*:
This function performs three actions *atomically*:
* Re-organizes the execution payload chain and corresponding state to make `head_block_hash` the head.
* Updates safe block hash with the value provided by `safe_block_hash` parameter.
* Applies finality to the execution state: it irreversibly persists the chain of all execution payloads
and corresponding state, up to and including `finalized_block_hash`.
@ -58,18 +60,24 @@ Additionally, if `payload_attributes` is provided, this function sets in motion
```python
def notify_forkchoice_updated(self: ExecutionEngine,
head_block_hash: Hash32,
safe_block_hash: Hash32,
finalized_block_hash: Hash32,
payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]:
...
```
*Note*: The call of the `notify_forkchoice_updated` function maps on the `POS_FORKCHOICE_UPDATED` event defined in the [EIP-3675](https://eips.ethereum.org/EIPS/eip-3675#definitions).
*Note*: The `(head_block_hash, finalized_block_hash)` values of the `notify_forkchoice_updated` function call maps on the `POS_FORKCHOICE_UPDATED` event defined in the [EIP-3675](https://eips.ethereum.org/EIPS/eip-3675#definitions).
As per EIP-3675, before a post-transition block is finalized, `notify_forkchoice_updated` MUST be called with `finalized_block_hash = Hash32()`.
*Note*: Client software MUST NOT call this function until the transition conditions are met on the PoW network, i.e. there exists a block for which `is_valid_terminal_pow_block` function returns `True`.
*Note*: Client software MUST call this function to initiate the payload build process to produce the merge transition block; the `head_block_hash` parameter MUST be set to the hash of a terminal PoW block in this case.
##### `safe_block_hash`
The `safe_block_hash` parameter MUST be set to return value of
[`get_safe_execution_payload_hash(store: Store)`](../../fork_choice/safe-block.md#get_safe_execution_payload_hash) function.
## Helpers
### `PayloadAttributes`

View File

@ -110,7 +110,7 @@ The following gossip validation from prior specifications MUST NOT be applied if
### Transitioning the gossip
See gossip transition details found in the [Altair document](../altair/p2p-interface.md#transitioning-the-gossip) for
details on how to handle transitioning gossip topics for Bellatrix.
details on how to handle transitioning gossip topics for EIP-4844.
## The Req/Resp domain
@ -170,7 +170,7 @@ Per `context = compute_fork_digest(fork_version, genesis_validators_root)`:
### Why was the max gossip message size increased at Bellatrix?
With the addition of `ExecutionPayload` to `BeaconBlock`s, there is a dynamic
field -- `transactions` -- which can validly exceed the `GOSSIP_MAX_SIZE` limit (1 MiB) put in place in
field -- `transactions` -- which can validly exceed the `GOSSIP_MAX_SIZE` limit (1 MiB) put in
place at Phase 0. At the `GAS_LIMIT` (~30M) currently seen on mainnet in 2021, a single transaction
filled entirely with data at a cost of 16 gas per byte can create a valid
`ExecutionPayload` of ~2 MiB. Thus we need a size limit to at least account for

View File

@ -110,9 +110,10 @@ All validator responsibilities remain unchanged other than those noted below. Na
To obtain an execution payload, a block proposer building a block on top of a `state` must take the following actions:
1. Set `payload_id = prepare_execution_payload(state, pow_chain, finalized_block_hash, suggested_fee_recipient, execution_engine)`, where:
1. Set `payload_id = prepare_execution_payload(state, pow_chain, safe_block_hash, finalized_block_hash, suggested_fee_recipient, execution_engine)`, where:
* `state` is the state object after applying `process_slots(state, slot)` transition to the resulting state of the parent block processing
* `pow_chain` is a `Dict[Hash32, PowBlock]` dictionary that abstractly represents all blocks in the PoW chain with block hash as the dictionary key
* `safe_block_hash` is the return value of the `get_safe_execution_payload_hash(store: Store)` function call
* `finalized_block_hash` is the hash of the latest finalized execution payload (`Hash32()` if none yet finalized)
* `suggested_fee_recipient` is the value suggested to be used for the `fee_recipient` field of the execution payload
@ -120,6 +121,7 @@ To obtain an execution payload, a block proposer building a block on top of a `s
```python
def prepare_execution_payload(state: BeaconState,
pow_chain: Dict[Hash32, PowBlock],
safe_block_hash: Hash32,
finalized_block_hash: Hash32,
suggested_fee_recipient: ExecutionAddress,
execution_engine: ExecutionEngine) -> Optional[PayloadId]:
@ -146,7 +148,12 @@ def prepare_execution_payload(state: BeaconState,
prev_randao=get_randao_mix(state, get_current_epoch(state)),
suggested_fee_recipient=suggested_fee_recipient,
)
return execution_engine.notify_forkchoice_updated(parent_hash, finalized_block_hash, payload_attributes)
return execution_engine.notify_forkchoice_updated(
head_block_hash=parent_hash,
safe_block_hash=safe_block_hash,
finalized_block_hash=finalized_block_hash,
payload_attributes=payload_attributes,
)
```
2. Set `block.body.execution_payload = get_execution_payload(payload_id, execution_engine)`, where:

View File

@ -0,0 +1,428 @@
# Capella -- 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)
- [Custom types](#custom-types)
- [Constants](#constants)
- [Domain types](#domain-types)
- [Preset](#preset)
- [State list lengths](#state-list-lengths)
- [Max operations per block](#max-operations-per-block)
- [Execution](#execution)
- [Configuration](#configuration)
- [Containers](#containers)
- [New containers](#new-containers)
- [`Withdrawal`](#withdrawal)
- [`BLSToExecutionChange`](#blstoexecutionchange)
- [`SignedBLSToExecutionChange`](#signedblstoexecutionchange)
- [Extended Containers](#extended-containers)
- [`ExecutionPayload`](#executionpayload)
- [`ExecutionPayloadHeader`](#executionpayloadheader)
- [`Validator`](#validator)
- [`BeaconBlockBody`](#beaconblockbody)
- [`BeaconState`](#beaconstate)
- [Helpers](#helpers)
- [Beacon state mutators](#beacon-state-mutators)
- [`withdraw`](#withdraw)
- [Predicates](#predicates)
- [`is_fully_withdrawable_validator`](#is_fully_withdrawable_validator)
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
- [Epoch processing](#epoch-processing)
- [Withdrawals](#withdrawals)
- [Block processing](#block-processing)
- [New `process_withdrawals`](#new-process_withdrawals)
- [Modified `process_execution_payload`](#modified-process_execution_payload)
- [Modified `process_operations`](#modified-process_operations)
- [New `process_bls_to_execution_change`](#new-process_bls_to_execution_change)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
Capella is a consensus-layer upgrade containing a number of features related
to validator withdrawals. Including:
* Automatic withdrawals of `withdrawable` validators
* Partial withdrawals during block proposal
* Operation to change from `BLS_WITHDRAWAL_PREFIX` to
`ETH1_ADDRESS_WITHDRAWAL_PREFIX` versioned withdrawal credentials to enable withdrawals for a validator
## Custom types
We define the following Python custom types for type hinting and readability:
| Name | SSZ equivalent | Description |
| - | - | - |
| `WithdrawalIndex` | `uint64` | an index of a `Withdrawal`|
## Constants
### Domain types
| Name | Value |
| - | - |
| `DOMAIN_BLS_TO_EXECUTION_CHANGE` | `DomainType('0x0A000000')` |
## Preset
### State list lengths
| Name | Value | Unit | Duration |
| - | - | :-: | :-: |
| `WITHDRAWALS_QUEUE_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | withdrawals enqueued in state|
### Max operations per block
| Name | Value |
| - | - |
| `MAX_BLS_TO_EXECUTION_CHANGES` | `2**4` (= 16) |
### Execution
| Name | Value | Description |
| - | - | - |
| `MAX_WITHDRAWALS_PER_PAYLOAD` | `uint64(2**4)` (= 16) | Maximum amount of withdrawals allowed in each payload |
## Configuration
## Containers
### New containers
#### `Withdrawal`
```python
class Withdrawal(Container):
index: WithdrawalIndex
address: ExecutionAddress
amount: Gwei
```
#### `BLSToExecutionChange`
```python
class BLSToExecutionChange(Container):
validator_index: ValidatorIndex
from_bls_pubkey: BLSPubkey
to_execution_address: ExecutionAddress
```
#### `SignedBLSToExecutionChange`
```python
class SignedBLSToExecutionChange(Container):
message: BLSToExecutionChange
signature: BLSSignature
```
### Extended Containers
#### `ExecutionPayload`
```python
class ExecutionPayload(Container):
# Execution block header fields
parent_hash: Hash32
fee_recipient: ExecutionAddress # 'beneficiary' in the yellow paper
state_root: Bytes32
receipts_root: Bytes32
logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM]
prev_randao: Bytes32 # 'difficulty' in the yellow paper
block_number: uint64 # 'number' in the yellow paper
gas_limit: uint64
gas_used: uint64
timestamp: uint64
extra_data: ByteList[MAX_EXTRA_DATA_BYTES]
base_fee_per_gas: uint256
# Extra payload fields
block_hash: Hash32 # Hash of execution block
transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD]
withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] # [New in Capella]
```
#### `ExecutionPayloadHeader`
```python
class ExecutionPayloadHeader(Container):
# Execution block header fields
parent_hash: Hash32
fee_recipient: ExecutionAddress
state_root: Bytes32
receipts_root: Bytes32
logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM]
prev_randao: Bytes32
block_number: uint64
gas_limit: uint64
gas_used: uint64
timestamp: uint64
extra_data: ByteList[MAX_EXTRA_DATA_BYTES]
base_fee_per_gas: uint256
# Extra payload fields
block_hash: Hash32 # Hash of execution block
transactions_root: Root
withdrawals_root: Root # [New in Capella]
```
#### `Validator`
```python
class Validator(Container):
pubkey: BLSPubkey
withdrawal_credentials: Bytes32 # Commitment to pubkey for withdrawals
effective_balance: Gwei # Balance at stake
slashed: boolean
# Status epochs
activation_eligibility_epoch: Epoch # When criteria for activation were met
activation_epoch: Epoch
exit_epoch: Epoch
withdrawable_epoch: Epoch # When validator can withdraw funds
fully_withdrawn_epoch: Epoch # [New in Capella]
```
#### `BeaconBlockBody`
```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
execution_payload: ExecutionPayload
# Capella operations
bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES] # [New in Capella]
```
#### `BeaconState`
```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]
# 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
withdrawal_index: WithdrawalIndex
withdrawals_queue: List[Withdrawal, WITHDRAWALS_QUEUE_LIMIT] # [New in Capella]
```
## Helpers
### Beacon state mutators
#### `withdraw`
```python
def withdraw_balance(state: BeaconState, index: ValidatorIndex, amount: Gwei) -> None:
# Decrease the validator's balance
decrease_balance(state, index, amount)
# Create a corresponding withdrawal receipt
withdrawal = Withdrawal(
index=state.withdrawal_index,
address=state.validators[index].withdrawal_credentials[12:],
amount=amount,
)
state.withdrawal_index = WithdrawalIndex(state.withdrawal_index + 1)
state.withdrawals_queue.append(withdrawal)
```
### Predicates
#### `is_fully_withdrawable_validator`
```python
def is_fully_withdrawable_validator(validator: Validator, epoch: Epoch) -> bool:
"""
Check if ``validator`` is fully withdrawable.
"""
is_eth1_withdrawal_prefix = validator.withdrawal_credentials[:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX
return is_eth1_withdrawal_prefix and validator.withdrawable_epoch <= epoch < validator.fully_withdrawn_epoch
```
## Beacon chain state transition function
### Epoch processing
```python
def process_epoch(state: BeaconState) -> None:
process_justification_and_finalization(state)
process_inactivity_updates(state)
process_rewards_and_penalties(state)
process_registry_updates(state)
process_slashings(state)
process_eth1_data_reset(state)
process_effective_balance_updates(state)
process_slashings_reset(state)
process_randao_mixes_reset(state)
process_historical_roots_update(state)
process_participation_flag_updates(state)
process_sync_committee_updates(state)
process_full_withdrawals(state) # [New in Capella]
```
#### Withdrawals
*Note*: The function `process_full_withdrawals` is new.
```python
def process_full_withdrawals(state: BeaconState) -> None:
current_epoch = get_current_epoch(state)
for index, validator in enumerate(state.validators):
if is_fully_withdrawable_validator(validator, current_epoch):
# TODO, consider the zero-balance case
withdraw_balance(state, ValidatorIndex(index), state.balances[index])
validator.fully_withdrawn_epoch = current_epoch
```
### Block processing
```python
def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_block_header(state, block)
if is_execution_enabled(state, block.body):
process_withdrawals(state, block.body.execution_payload) # [New in Capella]
process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [Modified in Capella]
process_randao(state, block.body)
process_eth1_data(state, block.body)
process_operations(state, block.body)
process_sync_aggregate(state, block.body.sync_aggregate)
```
#### New `process_withdrawals`
```python
def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None:
num_withdrawals = min(MAX_WITHDRAWALS_PER_PAYLOAD, len(state.withdrawals_queue))
dequeued_withdrawals = state.withdrawals_queue[:num_withdrawals]
assert len(dequeued_withdrawals) == len(payload.withdrawals)
for dequeued_withdrawal, withdrawal in zip(dequeued_withdrawals, payload.withdrawals):
assert dequeued_withdrawal == withdrawal
# Remove dequeued withdrawals from state
state.withdrawals_queue = state.withdrawals_queue[num_withdrawals:]
```
#### Modified `process_execution_payload`
*Note*: The function `process_execution_payload` is modified to use the new `ExecutionPayloadHeader` type.
```python
def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None:
# Verify consistency of the parent hash with respect to the previous execution payload header
if is_merge_transition_complete(state):
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 the execution payload is valid
assert execution_engine.notify_new_payload(payload)
# 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), # [New in Capella]
)
```
#### Modified `process_operations`
*Note*: The function `process_operations` is modified to process `BLSToExecutionChange` operations included in the block.
```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) # [New in Capella]
```
#### New `process_bls_to_execution_change`
```python
def process_bls_to_execution_change(state: BeaconState,
signed_address_change: SignedBLSToExecutionChange) -> None:
address_change = signed_address_change.message
assert address_change.validator_index < len(state.validators)
validator = state.validators[address_change.validator_index]
assert validator.withdrawal_credentials[:1] == BLS_WITHDRAWAL_PREFIX
assert validator.withdrawal_credentials[1:] == hash(address_change.from_bls_pubkey)[1:]
domain = get_domain(state, DOMAIN_BLS_TO_EXECUTION_CHANGE)
signing_root = compute_signing_root(address_change, domain)
assert bls.Verify(address_change.from_bls_pubkey, signing_root, signed_address_change.signature)
validator.withdrawal_credentials = (
ETH1_ADDRESS_WITHDRAWAL_PREFIX
+ b'\x00' * 11
+ address_change.to_execution_address
)
```

View File

@ -0,0 +1,62 @@
# Capella -- Fork Choice
**Notice**: This document is a work-in-progress for researchers and implementers.
## 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)
- [Custom types](#custom-types)
- [Protocols](#protocols)
- [`ExecutionEngine`](#executionengine)
- [`notify_forkchoice_updated`](#notify_forkchoice_updated)
- [Helpers](#helpers)
- [Extended `PayloadAttributes`](#extended-payloadattributes)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
This is the modification of the fork choice according to the Capella upgrade.
Unless stated explicitly, all prior functionality from [Bellatrix](../bellatrix/fork-choice.md) is inherited.
## Custom types
## Protocols
### `ExecutionEngine`
*Note*: The `notify_forkchoice_updated` function is modified in the `ExecutionEngine` protocol at the Capella upgrade.
#### `notify_forkchoice_updated`
The only change made is to the `PayloadAttributes` container through the addition of `withdrawals`.
Otherwise, `notify_forkchoice_updated` inherits all prior functionality.
```python
def notify_forkchoice_updated(self: ExecutionEngine,
head_block_hash: Hash32,
safe_block_hash: Hash32,
finalized_block_hash: Hash32,
payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]:
...
```
## Helpers
### Extended `PayloadAttributes`
`PayloadAttributes` is extended with the `withdrawals` field.
```python
@dataclass
class PayloadAttributes(object):
timestamp: uint64
prev_randao: Bytes32
suggested_fee_recipient: ExecutionAddress
withdrawals: Sequence[Withdrawal] # new in Capella
```

111
specs/capella/fork.md Normal file
View File

@ -0,0 +1,111 @@
# Capella -- Fork Logic
## 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)
- [Fork to Capella](#fork-to-capella)
- [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 Capella upgrade.
## Configuration
Warning: this configuration is not definitive.
| Name | Value |
| - | - |
| `CAPELLA_FORK_VERSION` | `Version('0x03000000')` |
| `CAPELLA_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** |
## Fork to Capella
### Fork trigger
The fork is triggered at epoch `CAPELLA_FORK_EPOCH`.
Note that for the pure Capella networks, we don't apply `upgrade_to_capella` since it starts with Capella version logic.
### Upgrading the state
If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == CAPELLA_FORK_EPOCH`,
an irregular state change is made to upgrade to Capella.
The upgrade occurs after the completion of the inner loop of `process_slots` that sets `state.slot` equal to `CAPELLA_FORK_EPOCH * SLOTS_PER_EPOCH`.
Care must be taken when transitioning through the fork boundary as implementations will need a modified [state transition function](../phase0/beacon-chain.md#beacon-chain-state-transition-function) that deviates from the Phase 0 document.
In particular, the outer `state_transition` function defined in the Phase 0 document will not expose the precise fork slot to execute the upgrade in the presence of skipped slots at the fork boundary. Instead the logic must be within `process_slots`.
```python
def upgrade_to_capella(pre: bellatrix.BeaconState) -> BeaconState:
epoch = bellatrix.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=CAPELLA_FORK_VERSION,
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=[],
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=pre.latest_execution_payload_header,
# Withdrawals
withdrawal_index=WithdrawalIndex(0),
withdrawals_queue=[],
)
for pre_validator in pre.validators:
post_validator = Validator(
pubkey=pre_validator.pubkey,
withdrawal_credentials=pre_validator.withdrawal_credentials,
effective_balance=pre_validator.effective_balance,
slashed=pre_validator.slashed,
activation_eligibility_epoch=pre_validator.activation_eligibility_epoch,
activation_epoch=pre_validator.activation_epoch,
exit_epoch=pre_validator.exit_epoch,
withdrawable_epoch=pre_validator.withdrawable_epoch,
fully_withdrawn_epoch=FAR_FUTURE_EPOCH,
)
post.validators.append(post_validator)
return post
```

View File

108
specs/capella/validator.md Normal file
View File

@ -0,0 +1,108 @@
# Capella -- Honest Validator
**Notice**: This document is a work-in-progress for researchers and implementers.
## 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)
- [Helpers](#helpers)
- [Protocols](#protocols)
- [`ExecutionEngine`](#executionengine)
- [`get_payload`](#get_payload)
- [Beacon chain responsibilities](#beacon-chain-responsibilities)
- [Block proposal](#block-proposal)
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
- [ExecutionPayload](#executionpayload)
<!-- 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 the Capella upgrade.
## Prerequisites
This document is an extension of the [Bellatrix -- Honest Validator](../bellatrix/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 [Capella](./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.
## Helpers
## Protocols
### `ExecutionEngine`
#### `get_payload`
`get_payload` returns the upgraded Capella `ExecutionPayload` type.
## Beacon chain responsibilities
All validator responsibilities remain unchanged other than those noted below.
### Block proposal
#### Constructing the `BeaconBlockBody`
##### ExecutionPayload
`ExecutionPayload`s are constructed as they were in Bellatrix, except that the
expected withdrawals for the slot must be gathered from the `state` (utilizing the
helper `get_expected_withdrawals`) and passed into the `ExecutionEngine` within `prepare_execution_payload`.
```python
def get_expected_withdrawals(state: BeaconState) -> Sequence[Withdrawal]:
num_withdrawals = min(MAX_WITHDRAWALS_PER_PAYLOAD, len(state.withdrawals_queue))
return state.withdrawals_queue[:num_withdrawals]
```
*Note*: The only change made to `prepare_execution_payload` is to call
`get_expected_withdrawals()` to set the new `withdrawals` field of `PayloadAttributes`.
```python
def prepare_execution_payload(state: BeaconState,
pow_chain: Dict[Hash32, PowBlock],
safe_block_hash: Hash32,
finalized_block_hash: Hash32,
suggested_fee_recipient: ExecutionAddress,
execution_engine: ExecutionEngine) -> Optional[PayloadId]:
if not is_merge_transition_complete(state):
is_terminal_block_hash_set = TERMINAL_BLOCK_HASH != Hash32()
is_activation_epoch_reached = get_current_epoch(state) >= TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH
if is_terminal_block_hash_set and not is_activation_epoch_reached:
# Terminal block hash is set but activation epoch is not yet reached, no prepare payload call is needed
return None
terminal_pow_block = get_terminal_pow_block(pow_chain)
if terminal_pow_block is None:
# Pre-merge, no prepare payload call is needed
return None
# Signify merge via producing on top of the terminal PoW block
parent_hash = terminal_pow_block.block_hash
else:
# Post-merge, normal payload
parent_hash = state.latest_execution_payload_header.block_hash
# Set the forkchoice head and initiate the payload build process
payload_attributes = PayloadAttributes(
timestamp=compute_timestamp_at_slot(state, state.slot),
prev_randao=get_randao_mix(state, get_current_epoch(state)),
suggested_fee_recipient=suggested_fee_recipient,
withdrawals=get_expected_withdrawals(state), # [New in Capella]
)
return execution_engine.notify_forkchoice_updated(
head_block_hash=parent_hash,
safe_block_hash=safe_block_hash,
finalized_block_hash=finalized_block_hash,
payload_attributes=payload_attributes,
)
```

View File

@ -0,0 +1,190 @@
# EIP-4844 -- The Beacon Chain
**Notice**: This document is a work-in-progress for researchers and implementers.
## 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)
- [Custom types](#custom-types)
- [Constants](#constants)
- [Domain types](#domain-types)
- [Preset](#preset)
- [Trusted setup](#trusted-setup)
- [Configuration](#configuration)
- [Containers](#containers)
- [Extended containers](#extended-containers)
- [`BeaconBlockBody`](#beaconblockbody)
- [Helper functions](#helper-functions)
- [KZG core](#kzg-core)
- [`blob_to_kzg`](#blob_to_kzg)
- [`kzg_to_versioned_hash`](#kzg_to_versioned_hash)
- [Misc](#misc)
- [`tx_peek_blob_versioned_hashes`](#tx_peek_blob_versioned_hashes)
- [`verify_kzgs_against_transactions`](#verify_kzgs_against_transactions)
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
- [Block processing](#block-processing)
- [Blob KZGs](#blob-kzgs)
- [Testing](#testing)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
This upgrade adds blobs to the beacon chain as part of EIP-4844.
## Custom types
| Name | SSZ equivalent | Description |
| - | - | - |
| `BLSFieldElement` | `uint256` | `x < BLS_MODULUS` |
| `Blob` | `Vector[BLSFieldElement, FIELD_ELEMENTS_PER_BLOB]` | |
| `VersionedHash` | `Bytes32` | |
| `KZGCommitment` | `Bytes48` | Same as BLS standard "is valid pubkey" check but also allows `0x00..00` for point-at-infinity |
## Constants
| Name | Value |
| - | - |
| `BLOB_TX_TYPE` | `uint8(0x05)` |
| `FIELD_ELEMENTS_PER_BLOB` | `4096` |
| `BLS_MODULUS` | `52435875175126190479447740508185965837690552500527637822603658699938581184513` |
### Domain types
| Name | Value |
| - | - |
| `DOMAIN_BLOBS_SIDECAR` | `DomainType('0x0a000000')` |
## Preset
### Trusted setup
The trusted setup is part of the preset: during testing a `minimal` insecure variant may be used,
but reusing the `mainnet` settings in public networks is a critical security requirement.
| Name | Value |
| - | - |
| `KZG_SETUP_G2` | `Vector[G2Point, FIELD_ELEMENTS_PER_BLOB]`, contents TBD |
| `KZG_SETUP_LAGRANGE` | `Vector[KZGCommitment, FIELD_ELEMENTS_PER_BLOB]`, contents TBD |
## Configuration
## Containers
### Extended containers
#### `BeaconBlockBody`
Note: `BeaconBlock` and `SignedBeaconBlock` types are updated 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
execution_payload: ExecutionPayload
blob_kzgs: List[KZGCommitment, MAX_BLOBS_PER_BLOCK] # [New in EIP-4844]
```
## Helper functions
### KZG core
KZG core functions. These are also defined in EIP-4844 execution specs.
#### `blob_to_kzg`
```python
def blob_to_kzg(blob: Blob) -> KZGCommitment:
computed_kzg = bls.Z1
for value, point_kzg in zip(blob, KZG_SETUP_LAGRANGE):
assert value < BLS_MODULUS
computed_kzg = bls.add(
computed_kzg,
bls.multiply(point_kzg, value)
)
return computed_kzg
```
#### `kzg_to_versioned_hash`
```python
def kzg_to_versioned_hash(kzg: KZGCommitment) -> VersionedHash:
return BLOB_COMMITMENT_VERSION_KZG + hash(kzg)[1:]
```
### Misc
#### `tx_peek_blob_versioned_hashes`
This function retrieves the hashes from the `SignedBlobTransaction` as defined in EIP-4844, using SSZ offsets.
Offsets are little-endian `uint32` values, as defined in the [SSZ specification](../../ssz/simple-serialize.md).
```python
def tx_peek_blob_versioned_hashes(opaque_tx: Transaction) -> Sequence[VersionedHash]:
assert opaque_tx[0] == BLOB_TX_TYPE
message_offset = 1 + uint32.decode_bytes(opaque_tx[1:5])
# field offset: 32 + 8 + 32 + 32 + 8 + 4 + 32 + 4 + 4 = 156
blob_versioned_hashes_offset = uint32.decode_bytes(opaque_tx[message_offset+156:message_offset+160])
return [VersionedHash(opaque_tx[x:x+32]) for x in range(blob_versioned_hashes_offset, len(opaque_tx), 32)]
```
#### `verify_kzgs_against_transactions`
```python
def verify_kzgs_against_transactions(transactions: Sequence[Transaction], blob_kzgs: Sequence[KZGCommitment]) -> bool:
all_versioned_hashes = []
for tx in transactions:
if tx[0] == BLOB_TX_TYPE:
all_versioned_hashes.extend(tx_peek_blob_versioned_hashes(tx))
return all_versioned_hashes == [kzg_to_versioned_hash(kzg) for kzg in blob_kzgs]
```
## Beacon chain state transition function
### Block processing
```python
def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_block_header(state, block)
if is_execution_enabled(state, block.body):
process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE)
process_randao(state, block.body)
process_eth1_data(state, block.body)
process_operations(state, block.body)
process_sync_aggregate(state, block.body.sync_aggregate)
process_blob_kzgs(state, block.body) # [New in EIP-4844]
```
#### Blob KZGs
```python
def process_blob_kzgs(state: BeaconState, body: BeaconBlockBody):
assert verify_kzgs_against_transactions(body.execution_payload.transactions, body.blob_kzgs)
```
## Testing
*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure EIP-4844 testing only.
The `BeaconState` initialization is unchanged, except for the use of the updated `eip4844.BeaconBlockBody` type
when initializing the first body-root:
```python
state.latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())),
```

43
specs/eip4844/fork.md Normal file
View File

@ -0,0 +1,43 @@
# EIP-4844 -- 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)
- [Fork to EIP-4844](#fork-to-eip-4844)
- [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 EIP-4844 upgrade.
## Configuration
Warning: this configuration is not definitive.
| Name | Value |
| - | - |
| `EIP4844_FORK_VERSION` | `Version('0x03000000')` |
| `EIP4844_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** |
## Fork to EIP-4844
### 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 `EIP4844_FORK_EPOCH`.
Note that for the pure EIP-4844 networks, we don't apply `upgrade_to_eip4844` since it starts with EIP-4844 version logic.
### Upgrading the state
The `eip4844.BeaconState` format is equal to the `bellatrix.BeaconState` format, no upgrade has to be performed.

View File

@ -0,0 +1,260 @@
# EIP-4844 -- Networking
This document contains the consensus-layer networking specification for EIP-4844.
The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite.
## 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 -->
- [Preset](#preset)
- [Configuration](#configuration)
- [Containers](#containers)
- [`BlobsSidecar`](#blobssidecar)
- [`SignedBlobsSidecar`](#signedblobssidecar)
- [The gossip domain: gossipsub](#the-gossip-domain-gossipsub)
- [Topics and messages](#topics-and-messages)
- [Global topics](#global-topics)
- [`beacon_block`](#beacon_block)
- [`blobs_sidecar`](#blobs_sidecar)
- [Transitioning the gossip](#transitioning-the-gossip)
- [The Req/Resp domain](#the-reqresp-domain)
- [Messages](#messages)
- [BeaconBlocksByRange v2](#beaconblocksbyrange-v2)
- [BeaconBlocksByRoot v2](#beaconblocksbyroot-v2)
- [BlobsSidecarsByRange v1](#blobssidecarsbyrange-v1)
- [Design decision rationale](#design-decision-rationale)
- [Why are blobs relayed as a sidecar, separate from beacon blocks?](#why-are-blobs-relayed-as-a-sidecar-separate-from-beacon-blocks)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Preset
| Name | Value |
| - | - |
| `MAX_BLOBS_PER_BLOCK` | `uint64(2**4)` (= 16) |
## Configuration
| Name | Value | Description |
|------------------------------------------|-------------------------------|---------------------------------------------------------------------|
| `MAX_REQUEST_BLOBS_SIDECARS` | `2**7` (= 128) | Maximum number of blobs sidecars in a single request |
| `MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS` | `2**13` (= 8192, ~1.2 months) | The minimum epoch range over which a node must serve blobs sidecars |
## Containers
### `BlobsSidecar`
```python
class BlobsSidecar(Container):
beacon_block_root: Root
beacon_block_slot: Slot
blobs: List[Blob, MAX_BLOBS_PER_BLOCK]
```
### `SignedBlobsSidecar`
```python
class SignedBlobsSidecar(Container):
message: BlobsSidecar
signature: BLSSignature
```
## The gossip domain: gossipsub
Some gossip meshes are upgraded in the fork of EIP4844 to support upgraded types.
### Topics and messages
Topics follow the same specification as in prior upgrades.
All topics remain stable except the beacon block topic which is updated with the modified type.
The specification around the creation, validation, and dissemination of messages has not changed from the Bellatrix document unless explicitly noted here.
The derivation of the `message-id` remains stable.
The new topics along with the type of the `data` field of a gossipsub message are given in this table:
| Name | Message Type |
| - | - |
| `beacon_block` | `SignedBeaconBlock` (modified) |
| `blobs_sidecar` | `SignedBlobsSidecar` (new) |
Note that the `ForkDigestValue` path segment of the topic separates the old and the new `beacon_block` topics.
#### Global topics
EIP4844 changes the type of the global beacon block topic and introduces a new global topic for blobs-sidecars.
##### `beacon_block`
The *type* of the payload of this topic changes to the (modified) `SignedBeaconBlock` found in EIP4844.
In addition to the gossip validations for this topic from prior specifications,
the following validations MUST pass before forwarding the `signed_beacon_block` on the network.
Alias `block = signed_beacon_block.message`, `execution_payload = block.body.execution_payload`.
- _[REJECT]_ The KZG commitments of the blobs are all correctly encoded compressed BLS G1 Points.
-- i.e. `all(bls.KeyValidate(commitment) for commitment in block.body.blob_kzgs)`
- _[REJECT]_ The KZG commitments correspond to the versioned hashes in the transactions list.
-- i.e. `verify_kzgs_against_transactions(block.body.execution_payload.transactions, block.body.blob_kzgs)`
##### `blobs_sidecar`
This topic is used to propagate data blobs included in any given beacon block.
The following validations MUST pass before forwarding the `signed_blobs_sidecar` on the network;
Alias `sidecar = signed_blobs_sidecar.message`.
- _[IGNORE]_ the `sidecar.beacon_block_slot` is for the current slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `blobs_sidecar.beacon_block_slot == current_slot`.
- _[REJECT]_ the `sidecar.blobs` are all well formatted, i.e. the `BLSFieldElement` in valid range (`x < BLS_MODULUS`).
- _[REJECT]_ the beacon proposer signature, `signed_blobs_sidecar.signature`, is valid -- i.e.
```python
domain = get_domain(state, DOMAIN_BLOBS_SIDECAR, blobs_sidecar.beacon_block_slot // SLOTS_PER_EPOCH)
signing_root = compute_signing_root(blobs_sidecar, domain)
assert bls.Verify(proposer_pubkey, signing_root, signed_blob_header.signature)
```
where `proposer_pubkey` is the pubkey of the beacon block proposer of `blobs_sidecar.beacon_block_slot`
- _[IGNORE]_ The sidecar is the first sidecar with valid signature received for the `(proposer_index, sidecar.beacon_block_slot)` combination,
where `proposer_index` is the validator index of the beacon block proposer of `blobs_sidecar.beacon_block_slot`
Note that a sidecar may be propagated before or after the corresponding beacon block.
Once both sidecar and beacon block are received, `verify_blobs_sidecar` can unlock the data-availability fork-choice dependency.
### Transitioning the gossip
See gossip transition details found in the [Altair document](../altair/p2p-interface.md#transitioning-the-gossip) for
details on how to handle transitioning gossip topics for this upgrade.
## The Req/Resp domain
### Messages
#### BeaconBlocksByRange v2
**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_range/2/`
The EIP-4844 fork-digest is introduced to the `context` enum to specify EIP-4844 beacon block type.
Per `context = compute_fork_digest(fork_version, genesis_validators_root)`:
[0]: # (eth2spec: skip)
| `fork_version` | Chunk SSZ type |
|--------------------------|-------------------------------|
| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` |
| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` |
| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` |
| `EIP4844_FORK_VERSION` | `eip4844.SignedBeaconBlock` |
#### BeaconBlocksByRoot v2
**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/2/`
The EIP-4844 fork-digest is introduced to the `context` enum to specify EIP-4844 beacon block type.
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` |
| `EIP4844_FORK_VERSION` | `eip4844.SignedBeaconBlock` |
#### BlobsSidecarsByRange v1
**Protocol ID:** `/eth2/beacon_chain/req/blobs_sidecars_by_range/1/`
Request Content:
```
(
start_slot: Slot
count: uint64
)
```
Response Content:
```
(
List[BlobsSidecar, MAX_REQUEST_BLOBS_SIDECARS]
)
```
Requests blobs sidecars in the slot range `[start_slot, start_slot + count)`,
leading up to the current head block as selected by fork choice.
The response is unsigned, i.e. `BlobsSidecarsByRange`, as the signature of the beacon block proposer
may not be available beyond the initial distribution via gossip.
Before consuming the next response chunk, the response reader SHOULD verify the blobs sidecar is well-formatted and
correct w.r.t. the expected KZG commitments through `verify_blobs_sidecar`.
`BlobsSidecarsByRange` is primarily used to sync blobs that may have been missed on gossip.
The request MUST be encoded as an SSZ-container.
The response MUST consist of zero or more `response_chunk`.
Each _successful_ `response_chunk` MUST contain a single `SignedBlobsSidecar` payload.
Clients MUST keep a record of signed blobs sidecars seen on the epoch range
`[max(GENESIS_EPOCH, current_epoch - MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS), current_epoch]`
where `current_epoch` is defined by the current wall-clock time,
and clients MUST support serving requests of blocks on this range.
Peers that are unable to reply to block requests within the `MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS`
epoch range SHOULD respond with error code `3: ResourceUnavailable`.
Such peers that are unable to successfully reply to this range of requests MAY get descored
or disconnected at any time.
*Note*: The above requirement implies that nodes that start from a recent weak subjectivity checkpoint
MUST backfill the local blobs database to at least epoch `current_epoch - MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS`
to be fully compliant with `BlobsSidecarsByRange` requests. To safely perform such a
backfill of blocks to the recent state, the node MUST validate both (1) the
proposer signatures and (2) that the blocks form a valid chain up to the most
recent block referenced in the weak subjectivity state.
*Note*: Although clients that bootstrap from a weak subjectivity checkpoint can begin
participating in the networking immediately, other peers MAY
disconnect and/or temporarily ban such an un-synced or semi-synced client.
Clients MUST respond with at least the first blobs sidecar that exists in the range, if they have it,
and no more than `MAX_REQUEST_BLOBS_SIDECARS` sidecars.
The following blobs sidecars, where they exist, MUST be sent in consecutive order.
Clients MAY limit the number of blobs sidecars in the response.
The response MUST contain no more than `count` blobs sidecars.
Clients MUST respond with blobs sidecars from their view of the current fork choice
-- that is, blobs sidecars as included by blocks from the single chain defined by the current head.
Of note, blocks from slots before the finalization MUST lead to the finalized block reported in the `Status` handshake.
Clients MUST respond with blobs sidecars that are consistent from a single chain within the context of the request.
After the initial blobs sidecar, clients MAY stop in the process of responding
if their fork choice changes the view of the chain in the context of the request.
# Design decision rationale
## Why are blobs relayed as a sidecar, separate from beacon blocks?
This "sidecar" design provides forward compatibility for further data increases by black-boxing `is_data_available()`:
with full sharding `is_data_available()` can be replaced by data-availability-sampling (DAS)
thus avoiding all blobs being downloaded by all beacon nodes on the network.
Such sharding design may introduce an updated `BlobsSidecar` to identify the shard,
but does not affect the `BeaconBlock` structure.

134
specs/eip4844/validator.md Normal file
View File

@ -0,0 +1,134 @@
# EIP-4844 -- Honest Validator
**Notice**: This document is a work-in-progress for researchers and implementers.
## 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)
- [Helpers](#helpers)
- [`is_data_available`](#is_data_available)
- [`verify_blobs_sidecar`](#verify_blobs_sidecar)
- [Beacon chain responsibilities](#beacon-chain-responsibilities)
- [Block proposal](#block-proposal)
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
- [Blob commitments](#blob-commitments)
- [Beacon Block publishing time](#beacon-block-publishing-time)
<!-- 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-4844.
## Prerequisites
This document is an extension of the [Bellatrix -- Honest Validator](../bellatrix/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 EIP4844](./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.
## Helpers
### `is_data_available`
The implementation of `is_data_available` is meant to change with later sharding upgrades.
Initially, it requires every verifying actor to retrieve the matching `BlobsSidecar`,
and verify the sidecar with `verify_blobs`.
Without the sidecar the block may be processed further optimistically,
but MUST NOT be considered valid until a valid `BlobsSidecar` has been downloaded.
```python
def is_data_available(slot: Slot, beacon_block_root: Root, kzgs: Sequence[KZGCommitment]):
sidecar = retrieve_blobs_sidecar(slot, beacon_block_root) # implementation dependent, raises an exception if not available
verify_blobs_sidecar(slot, beacon_block_root, kzgs, sidecar)
```
### `verify_blobs_sidecar`
```python
def verify_blobs_sidecar(slot: Slot, beacon_block_root: Root,
expected_kzgs: Sequence[KZGCommitment], blobs_sidecar: BlobsSidecar):
assert slot == blobs_sidecar.beacon_block_slot
assert beacon_block_root == blobs_sidecar.beacon_block_root
blobs = blobs_sidecar.blobs
assert len(expected_kzgs) == len(blobs)
for kzg, blob in zip(expected_kzgs, blobs):
assert blob_to_kzg(blob) == kzg
```
## Beacon chain responsibilities
All validator responsibilities remain unchanged other than those noted below.
Namely, the blob handling and the addition of `BlobsSidecar`.
### Block proposal
#### Constructing the `BeaconBlockBody`
##### Blob commitments
After retrieving the execution payload from the execution engine as specified in Bellatrix,
the blobs are retrieved and processed:
```python
# execution_payload = execution_engine.get_payload(payload_id)
# block.body.execution_payload = execution_payload
# ...
kzgs, blobs = get_blobs(payload_id)
# Optionally sanity-check that the KZG commitments match the versioned hashes in the transactions
assert verify_kzgs_against_transactions(execution_payload.transactions, kzgs)
# Optionally sanity-check that the KZG commitments match the blobs (as produced by the execution engine)
assert len(kzgs) == len(blobs) and [blob_to_kzg(blob) == kzg for blob, kzg in zip(blobs, kzgs)]
# Update the block body
block.body.blob_kzgs = kzgs
```
The `blobs` should be held with the block in preparation of publishing.
Without the `blobs`, the published block will effectively be ignored by honest validators.
Note: This API is *unstable*. `get_blobs` and `get_payload` may be unified.
Implementers may also retrieve blobs individually per transaction.
### Beacon Block publishing time
Before publishing a prepared beacon block proposal, the corresponding blobs are packaged into a sidecar object for distribution to the network:
```python
blobs_sidecar = BlobsSidecar(
beacon_block_root=hash_tree_root(beacon_block)
beacon_block_slot=beacon_block.slot
shard=0,
blobs=blobs,
)
```
And then signed:
```python
domain = get_domain(state, DOMAIN_BLOBS_SIDECAR, blobs_sidecar.beacon_block_slot / SLOTS_PER_EPOCH)
signing_root = compute_signing_root(blobs_sidecar, domain)
signature = bls.Sign(privkey, signing_root)
signed_blobs_sidecar = SignedBlobsSidecar(message=blobs_sidecar, signature=signature)
```
This `signed_blobs_sidecar` is then published to the global `blobs_sidecar` topic as soon as the `beacon_block` is published.
After publishing the sidecar peers on the network may request the sidecar through sync-requests, or a local user may be interested.
The validator MUST hold on to blobs for `MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS` epochs and serve when capable,
to ensure the data-availability of these blobs throughout the network.
After `MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS` nodes MAY prune the blobs and/or stop serving them.

View File

@ -203,6 +203,9 @@ The following values are (non-configurable) constants used throughout the specif
| `DOMAIN_VOLUNTARY_EXIT` | `DomainType('0x04000000')` |
| `DOMAIN_SELECTION_PROOF` | `DomainType('0x05000000')` |
| `DOMAIN_AGGREGATE_AND_PROOF` | `DomainType('0x06000000')` |
| `DOMAIN_APPLICATION_MASK` | `DomainType('0x00000001')` |
*Note*: `DOMAIN_APPLICATION_MASK` reserves the rest of the bitspace in `DomainType` for application usage. This means for some `DomainType` `DOMAIN_SOME_APPLICATION`, `DOMAIN_SOME_APPLICATION & DOMAIN_APPLICATION_MASK` **MUST** be non-zero. This expression for any other `DomainType` in the consensus specs **MUST** be zero.
## Preset

View File

@ -32,6 +32,7 @@
- [`on_tick`](#on_tick)
- [`on_block`](#on_block)
- [`on_attestation`](#on_attestation)
- [`on_attester_slashing`](#on_attester_slashing)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
@ -61,21 +62,21 @@ Any of the above handlers that trigger an unhandled exception (e.g. a failed ass
### Constant
| Name | Value |
| - | - |
| Name | Value |
| -------------------- | ----------- |
| `INTERVALS_PER_SLOT` | `uint64(3)` |
### Preset
| Name | Value | Unit | Duration |
| - | - | :-: | :-: |
| Name | Value | Unit | Duration |
| -------------------------------- | ------------ | :---: | :--------: |
| `SAFE_SLOTS_TO_UPDATE_JUSTIFIED` | `2**3` (= 8) | slots | 96 seconds |
### Configuration
| Name | Value |
| - | - |
| `PROPOSER_SCORE_BOOST` | `uint64(70)` |
| Name | Value |
| ---------------------- | ------------ |
| `PROPOSER_SCORE_BOOST` | `uint64(40)` |
- The proposer score boost is worth `PROPOSER_SCORE_BOOST` percentage of the committee's weight, i.e., for slot with committee weight `committee_weight` the boost weight is equal to `(committee_weight * PROPOSER_SCORE_BOOST) // 100`.
@ -101,6 +102,7 @@ class Store(object):
finalized_checkpoint: Checkpoint
best_justified_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)
checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict)
@ -129,6 +131,7 @@ def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -
finalized_checkpoint=finalized_checkpoint,
best_justified_checkpoint=justified_checkpoint,
proposer_boost_root=proposer_boost_root,
equivocating_indices=set(),
blocks={anchor_root: copy(anchor_block)},
block_states={anchor_root: copy(anchor_state)},
checkpoint_states={justified_checkpoint: copy(anchor_state)},
@ -179,6 +182,7 @@ def get_latest_attesting_balance(store: Store, root: Root) -> Gwei:
attestation_score = Gwei(sum(
state.validators[i].effective_balance for i in active_indices
if (i in store.latest_messages
and i not in store.equivocating_indices
and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root)
))
if store.proposer_boost_root == Root():
@ -357,7 +361,8 @@ def store_target_checkpoint_state(store: Store, target: Checkpoint) -> None:
def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None:
target = attestation.data.target
beacon_block_root = attestation.data.beacon_block_root
for i in attesting_indices:
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 target.epoch > store.latest_messages[i].epoch:
store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root)
```
@ -459,3 +464,25 @@ def on_attestation(store: Store, attestation: Attestation, is_from_block: bool=F
# Update latest messages for attesting indices
update_latest_messages(store, indexed_attestation.attesting_indices, attestation)
```
#### `on_attester_slashing`
*Note*: `on_attester_slashing` should be called while syncing and a client MUST maintain the equivocation set of `AttesterSlashing`s from at least the latest finalized checkpoint.
```python
def on_attester_slashing(store: Store, attester_slashing: AttesterSlashing) -> None:
"""
Run ``on_attester_slashing`` immediately upon receiving a new ``AttesterSlashing``
from either within a block or directly on the wire.
"""
attestation_1 = attester_slashing.attestation_1
attestation_2 = attester_slashing.attestation_2
assert is_slashable_attestation_data(attestation_1.data, attestation_2.data)
state = store.block_states[store.justified_checkpoint.root]
assert is_valid_indexed_attestation(state, attestation_1)
assert is_valid_indexed_attestation(state, attestation_2)
indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices)
for index in indices:
store.equivocating_indices.add(index)
```

View File

@ -252,7 +252,7 @@ Likewise, clients MUST NOT emit or propagate messages larger than this limit.
The optional `from` (1), `seqno` (3), `signature` (5) and `key` (6) protobuf fields are omitted from the message,
since messages are identified by content, anonymous, and signed where necessary in the application layer.
Starting from Gossipsub v1.1, clients MUST enforce this by applying the `StrictNoSign`
[signature policy](https://github.com/libp2p/specs/blob/master/pubsub/README.md#signature-policy-options).
[signature policy](https://github.com/libp2p/specs/blob/master/pubsub/README.md#signature-policy-options).
The `message-id` of a gossipsub message MUST be the following 20 byte value computed from the message data:
* If `message.data` has a valid snappy decompression, set `message-id` to the first 20 bytes of the `SHA256` hash of
@ -337,7 +337,7 @@ The following validations MUST pass before forwarding the `signed_aggregate_and_
(a client MAY queue future aggregates for processing at the appropriate slot).
- _[REJECT]_ The aggregate attestation's epoch matches its target -- i.e. `aggregate.data.target.epoch ==
compute_epoch_at_slot(aggregate.data.slot)`
- _[IGNORE]_ The valid aggregate attestation defined by `hash_tree_root(aggregate)` has _not_ already been seen
- _[IGNORE]_ A valid aggregate attestation defined by `hash_tree_root(aggregate.data)` whose `aggregation_bits` is a non-strict superset has _not_ already been seen.
(via aggregate gossip, within a verified block, or through the creation of an equivalent aggregate locally).
- _[IGNORE]_ The `aggregate` is the first valid aggregate received for the aggregator
with index `aggregate_and_proof.aggregator_index` for the epoch `aggregate.data.target.epoch`.
@ -355,7 +355,7 @@ The following validations MUST pass before forwarding the `signed_aggregate_and_
(via both gossip and non-gossip sources)
(a client MAY queue aggregates for processing once block is retrieved).
- _[REJECT]_ The block being voted for (`aggregate.data.beacon_block_root`) passes validation.
- _[REJECT]_ The current `finalized_checkpoint` is an ancestor of the `block` defined by `aggregate.data.beacon_block_root` -- i.e.
- _[IGNORE]_ The current `finalized_checkpoint` is an ancestor of the `block` defined by `aggregate.data.beacon_block_root` -- i.e.
`get_ancestor(store, aggregate.data.beacon_block_root, compute_start_slot_at_epoch(store.finalized_checkpoint.epoch))
== store.finalized_checkpoint.root`
@ -727,7 +727,7 @@ Request Content:
(
start_slot: Slot
count: uint64
step: uint64
step: uint64 # Deprecated, must be set to 1
)
```
@ -738,12 +738,12 @@ Response Content:
)
```
Requests beacon blocks in the slot range `[start_slot, start_slot + count * step)`, leading up to the current head block as selected by fork choice.
`step` defines the slot increment between blocks.
For example, requesting blocks starting at `start_slot` 2 with a step value of 2 would return the blocks at slots [2, 4, 6, …].
Requests beacon blocks in the slot range `[start_slot, start_slot + count)`, leading up to the current head block as selected by fork choice.
For example, requesting blocks starting at `start_slot=2` and `count=4` would return the blocks at slots `[2, 3, 4, 5]`.
In cases where a slot is empty for a given slot number, no block is returned.
For example, if slot 4 were empty in the previous example, the returned array would contain [2, 6, …].
A request MUST NOT have a 0 slot increment, i.e. `step >= 1`.
For example, if slot 4 were empty in the previous example, the returned array would contain `[2, 3, 5]`.
`step` is deprecated and must be set to 1. Clients may respond with a single block if a larger step is returned during the deprecation transition period.
`BeaconBlocksByRange` is primarily used to sync historical blocks.

View File

@ -163,10 +163,7 @@ The `withdrawal_credentials` field must be such that:
* `withdrawal_credentials[12:] == eth1_withdrawal_address`
After the merge of the current Ethereum application layer into the Beacon Chain,
withdrawals to `eth1_withdrawal_address` will be normal ETH transfers (with no payload other than the validator's ETH)
triggered by a user transaction that will set the gas price and gas limit as well pay fees.
As long as the account or contract with address `eth1_withdrawal_address` can receive ETH transfers,
the future withdrawal protocol is agnostic to all other implementation details.
withdrawals to `eth1_withdrawal_address` will simply be increases to the account's ETH balance that do **NOT** trigger any EVM execution.
### Submit deposit
@ -279,9 +276,15 @@ A validator has two primary responsibilities to the beacon chain: [proposing blo
### Block proposal
A validator is expected to propose a [`SignedBeaconBlock`](./beacon-chain.md#signedbeaconblock) at
the beginning of any slot during which `is_proposer(state, validator_index)` returns `True`.
To propose, the validator selects the `BeaconBlock`, `parent`,
that in their view of the fork choice is the head of the chain during `slot - 1`.
the beginning of any `slot` during which `is_proposer(state, validator_index)` returns `True`.
To propose, the validator selects the `BeaconBlock`, `parent` which:
1. In their view of fork choice is the head of the chain at the start of
`slot`, after running `on_tick` and applying any queued attestations from `slot - 1`.
2. Is from a slot strictly less than the slot of the block about to be proposed,
i.e. `parent.slot < slot`.
The validator creates, signs, and broadcasts a `block` that is a child of `parent`
that satisfies a valid [beacon chain state transition](./beacon-chain.md#beacon-chain-state-transition-function).

View File

@ -34,7 +34,6 @@ For brevity, we define two aliases for values of the `status` field on
- Alias `INVALIDATED` to:
- `INVALID`
- `INVALID_BLOCK_HASH`
- `INVALID_TERMINAL_BLOCK`
Let `head: BeaconBlock` be the result of calling of the fork choice
algorithm at the time of block production. Let `head_block_root: Root` be the
@ -81,10 +80,13 @@ def is_execution_block(block: BeaconBlock) -> bool:
```python
def is_optimistic_candidate_block(opt_store: OptimisticStore, current_slot: Slot, block: BeaconBlock) -> bool:
justified_root = opt_store.block_states[opt_store.head_block_root].current_justified_checkpoint.root
justified_is_execution_block = is_execution_block(opt_store.blocks[justified_root])
block_is_deep = block.slot + SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY <= current_slot
return justified_is_execution_block or block_is_deep
if is_execution_block(opt_store.blocks[block.parent_root]):
return True
if block.slot + SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY <= current_slot:
return True
return False
```
Let only a node which returns `is_optimistic(opt_store, head) is True` be an *optimistic
@ -99,14 +101,20 @@ behaviours without regard for optimistic sync.
### When to optimistically import blocks
A block MAY be optimistically imported when
`is_optimistic_candidate_block(opt_store, current_slot, block)` returns
`True`. This ensures that blocks are only optimistically imported if either:
`is_optimistic_candidate_block(opt_store, current_slot, block)` returns `True`.
This ensures that blocks are only optimistically imported if one or more of the
following are true:
1. The justified checkpoint has execution enabled.
1. The parent of the block has execution enabled.
1. The current slot (as per the system clock) is at least
`SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` ahead of the slot of the block being
imported.
In effect, there are restrictions on when a *merge block* can be optimistically
imported. The merge block is the first block in any chain where
`is_execution_block(block) == True`. Any descendant of a merge block may be
imported optimistically at any time.
*See [Fork Choice Poisoning](#fork-choice-poisoning) for the motivations behind
these conditions.*
@ -256,40 +264,9 @@ An optimistic validator MUST NOT participate in sync committees (i.e., sign acro
## Ethereum Beacon APIs
Consensus engines which provide an implementation of the [Ethereum Beacon
APIs](https://github.com/ethereum/beacon-APIs) must take care to avoid
presenting optimistic blocks as fully-verified blocks.
### Helpers
Let the following response types be defined as any response with the
corresponding HTTP status code:
- "Success" Response: Status Codes 200-299.
- "Not Found" Response: Status Code 404.
- "Syncing" Response: Status Code 503.
### Requests for Optimistic Blocks
When information about an optimistic block is requested, the consensus engine:
- MUST NOT respond with success.
- MAY respond with not found.
- MAY respond with syncing.
### Requests for an Optimistic Head
When `is_optimistic(opt_store, head) is True`, the consensus engine:
- MUST NOT return an optimistic `head`.
- MAY substitute the head block with `latest_verified_ancestor(block)`.
- MAY return syncing.
### Requests to Validators Endpoints
When `is_optimistic(opt_store, head) is True`, the consensus engine MUST return syncing to
all endpoints which match the following pattern:
- `eth/*/validator/*`
APIs](https://github.com/ethereum/beacon-APIs) must take care to ensure the
`execution_optimistic` value is set to `True` whenever the request references
optimistic blocks (and vice-versa).
## Design Decision Rationale

View File

@ -281,7 +281,7 @@ def test_transition_with_random_three_quarters_participation(state, fork_epoch,
assert committee_len >= 4
filter_len = committee_len // 4
participant_count = committee_len - filter_len
return rng.sample(indices, participant_count)
return rng.sample(sorted(indices), participant_count)
yield from _run_transition_test_with_attestations(
state,
@ -304,7 +304,7 @@ def test_transition_with_random_half_participation(state, fork_epoch, spec, post
assert committee_len >= 2
filter_len = committee_len // 2
participant_count = committee_len - filter_len
return rng.sample(indices, participant_count)
return rng.sample(sorted(indices), participant_count)
yield from _run_transition_test_with_attestations(
state,

View File

@ -59,7 +59,7 @@ def test_is_assigned_to_sync_committee(spec, state):
if disqualified_pubkeys:
sample_size = 3
assert validator_count >= sample_size
some_pubkeys = rng.sample(disqualified_pubkeys, sample_size)
some_pubkeys = rng.sample(sorted(disqualified_pubkeys), sample_size)
for pubkey in some_pubkeys:
validator_index = active_pubkeys.index(pubkey)
is_current = spec.is_assigned_to_sync_committee(

View File

@ -1,4 +1,5 @@
from copy import deepcopy
from typing import Optional
from eth2spec.test.helpers.pow_block import (
prepare_random_pow_chain,
@ -142,16 +143,22 @@ def test_prepare_execution_payload(spec, state):
# Dummy arguments
finalized_block_hash = b'\x56' * 32
safe_block_hash = b'\x58' * 32
suggested_fee_recipient = b'\x78' * 20
# Mock execution_engine
class TestEngine(spec.NoopExecutionEngine):
def notify_forkchoice_updated(self, parent_hash, finalized_block_hash, payload_attributes) -> bool:
def notify_forkchoice_updated(self,
head_block_hash,
safe_block_hash,
finalized_block_hash,
payload_attributes) -> Optional[spec.PayloadId]:
return SAMPLE_PAYLOAD_ID
payload_id = spec.prepare_execution_payload(
state=state,
pow_chain=pow_chain.to_dict(),
safe_block_hash=safe_block_hash,
finalized_block_hash=finalized_block_hash,
suggested_fee_recipient=suggested_fee_recipient,
execution_engine=TestEngine(),

View File

@ -0,0 +1,191 @@
from eth2spec.utils import bls
from eth2spec.test.helpers.keys import pubkeys, privkeys, pubkey_to_privkey
from eth2spec.test.context import spec_state_test, expect_assertion_error, with_capella_and_later, always_bls
def run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=True):
"""
Run ``process_bls_to_execution_change``, yielding:
- pre-state ('pre')
- address-change ('address_change')
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
# yield pre-state
yield 'pre', state
yield 'address_change', signed_address_change
# If the address_change is invalid, processing is aborted, and there is no post-state.
if not valid:
expect_assertion_error(lambda: spec.process_bls_to_execution_change(state, signed_address_change))
yield 'post', None
return
# process address change
spec.process_bls_to_execution_change(state, signed_address_change)
# Make sure the address change has been processed
validator_index = signed_address_change.message.validator_index
validator = state.validators[validator_index]
assert validator.withdrawal_credentials[:1] == spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX
assert validator.withdrawal_credentials[1:12] == b'\x00' * 11
assert validator.withdrawal_credentials[12:] == signed_address_change.message.to_execution_address
# yield post-state
yield 'post', state
def get_signed_address_change(spec, state, validator_index=None, withdrawal_pubkey=None):
if validator_index is None:
validator_index = 0
if withdrawal_pubkey is None:
key_index = -1 - validator_index
withdrawal_pubkey = pubkeys[key_index]
withdrawal_privkey = privkeys[key_index]
else:
withdrawal_privkey = pubkey_to_privkey[withdrawal_pubkey]
domain = spec.get_domain(state, spec.DOMAIN_BLS_TO_EXECUTION_CHANGE)
address_change = spec.BLSToExecutionChange(
validator_index=validator_index,
from_bls_pubkey=withdrawal_pubkey,
to_execution_address=b'\x42' * 20,
)
signing_root = spec.compute_signing_root(address_change, domain)
return spec.SignedBLSToExecutionChange(
message=address_change,
signature=bls.Sign(withdrawal_privkey, signing_root),
)
@with_capella_and_later
@spec_state_test
def test_success(spec, state):
signed_address_change = get_signed_address_change(spec, state)
yield from run_bls_to_execution_change_processing(spec, state, signed_address_change)
@with_capella_and_later
@spec_state_test
def test_success_not_activated(spec, state):
validator_index = 3
validator = state.validators[validator_index]
validator.activation_eligibility_epoch += 4
validator.activation_epoch = spec.FAR_FUTURE_EPOCH
assert not spec.is_active_validator(validator, spec.get_current_epoch(state))
signed_address_change = get_signed_address_change(spec, state)
yield from run_bls_to_execution_change_processing(spec, state, signed_address_change)
assert not spec.is_fully_withdrawable_validator(state.validators[validator_index], spec.get_current_epoch(state))
@with_capella_and_later
@spec_state_test
def test_success_in_activation_queue(spec, state):
validator_index = 3
validator = state.validators[validator_index]
validator.activation_eligibility_epoch = spec.get_current_epoch(state)
validator.activation_epoch += 4
assert not spec.is_active_validator(validator, spec.get_current_epoch(state))
signed_address_change = get_signed_address_change(spec, state)
yield from run_bls_to_execution_change_processing(spec, state, signed_address_change)
assert not spec.is_fully_withdrawable_validator(state.validators[validator_index], spec.get_current_epoch(state))
@with_capella_and_later
@spec_state_test
def test_success_in_exit_queue(spec, state):
validator_index = 3
spec.initiate_validator_exit(state, validator_index)
assert spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state))
assert spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch
signed_address_change = get_signed_address_change(spec, state, validator_index=validator_index)
yield from run_bls_to_execution_change_processing(spec, state, signed_address_change)
@with_capella_and_later
@spec_state_test
def test_success_exited(spec, state):
validator_index = 4
validator = state.validators[validator_index]
validator.exit_epoch = spec.get_current_epoch(state)
assert not spec.is_active_validator(validator, spec.get_current_epoch(state))
signed_address_change = get_signed_address_change(spec, state, validator_index=validator_index)
yield from run_bls_to_execution_change_processing(spec, state, signed_address_change)
assert not spec.is_fully_withdrawable_validator(state.validators[validator_index], spec.get_current_epoch(state))
@with_capella_and_later
@spec_state_test
def test_success_withdrawable(spec, state):
validator_index = 4
validator = state.validators[validator_index]
validator.exit_epoch = spec.get_current_epoch(state)
validator.withdrawable_epoch = spec.get_current_epoch(state)
assert not spec.is_active_validator(validator, spec.get_current_epoch(state))
signed_address_change = get_signed_address_change(spec, state, validator_index=validator_index)
yield from run_bls_to_execution_change_processing(spec, state, signed_address_change)
assert spec.is_fully_withdrawable_validator(state.validators[validator_index], spec.get_current_epoch(state))
@with_capella_and_later
@spec_state_test
def test_fail_val_index_out_of_range(spec, state):
# Create for one validator beyond the validator list length
signed_address_change = get_signed_address_change(spec, state, validator_index=len(state.validators))
yield from run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=False)
@with_capella_and_later
@spec_state_test
def test_fail_already_0x01(spec, state):
# Create for one validator beyond the validator list length
validator_index = len(state.validators) // 2
validator = state.validators[validator_index]
validator.withdrawal_credentials = b'\x01' + b'\x00' * 11 + b'\x23' * 20
signed_address_change = get_signed_address_change(spec, state, validator_index=validator_index)
yield from run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=False)
@with_capella_and_later
@spec_state_test
def test_fail_incorrect_from_bls_pubkey(spec, state):
# Create for one validator beyond the validator list length
validator_index = 2
signed_address_change = get_signed_address_change(
spec, state,
validator_index=validator_index,
withdrawal_pubkey=pubkeys[0],
)
yield from run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=False)
@with_capella_and_later
@spec_state_test
@always_bls
def test_fail_bad_signature(spec, state):
signed_address_change = get_signed_address_change(spec, state)
# Mutate sigature
signed_address_change.signature = spec.BLSSignature(b'\x42' * 96)
yield from run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=False)

View File

@ -0,0 +1,242 @@
from eth2spec.test.helpers.execution_payload import (
build_empty_execution_payload,
)
from eth2spec.test.context import spec_state_test, expect_assertion_error, with_capella_and_later
from eth2spec.test.helpers.state import next_slot
def prepare_withdrawals_queue(spec, state, num_withdrawals):
pre_queue_len = len(state.withdrawals_queue)
for i in range(num_withdrawals):
withdrawal = spec.Withdrawal(
index=i + 5,
address=b'\x42' * 20,
amount=200000 + i,
)
state.withdrawals_queue.append(withdrawal)
assert len(state.withdrawals_queue) == num_withdrawals + pre_queue_len
def run_withdrawals_processing(spec, state, execution_payload, valid=True):
"""
Run ``process_execution_payload``, yielding:
- pre-state ('pre')
- execution payload ('execution_payload')
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
pre_withdrawals_queue = state.withdrawals_queue.copy()
num_withdrawals = min(spec.MAX_WITHDRAWALS_PER_PAYLOAD, len(pre_withdrawals_queue))
yield 'pre', state
yield 'execution_payload', execution_payload
if not valid:
expect_assertion_error(lambda: spec.process_withdrawals(state, execution_payload))
yield 'post', None
return
spec.process_withdrawals(state, execution_payload)
yield 'post', state
if len(pre_withdrawals_queue) == 0:
assert len(state.withdrawals_queue) == 0
elif len(pre_withdrawals_queue) <= num_withdrawals:
assert len(state.withdrawals_queue) == 0
else:
assert state.withdrawals_queue == pre_withdrawals_queue[num_withdrawals:]
@with_capella_and_later
@spec_state_test
def test_success_empty_queue(spec, state):
assert len(state.withdrawals_queue) == 0
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload)
@with_capella_and_later
@spec_state_test
def test_success_one_in_queue(spec, state):
prepare_withdrawals_queue(spec, state, 1)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload)
@with_capella_and_later
@spec_state_test
def test_success_max_per_slot_in_queue(spec, state):
prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload)
@with_capella_and_later
@spec_state_test
def test_success_a_lot_in_queue(spec, state):
prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload)
#
# Failure cases in which the number of withdrawals in the execution_payload is incorrect
#
@with_capella_and_later
@spec_state_test
def test_fail_empty_queue_non_empty_withdrawals(spec, state):
assert len(state.withdrawals_queue) == 0
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
withdrawal = spec.Withdrawal(
index=0,
address=b'\x30' * 20,
amount=420,
)
execution_payload.withdrawals.append(withdrawal)
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_capella_and_later
@spec_state_test
def test_fail_one_in_queue_none_in_withdrawals(spec, state):
prepare_withdrawals_queue(spec, state, 1)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.withdrawals = []
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_capella_and_later
@spec_state_test
def test_fail_one_in_queue_two_in_withdrawals(spec, state):
prepare_withdrawals_queue(spec, state, 1)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.withdrawals.append(execution_payload.withdrawals[0].copy())
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_capella_and_later
@spec_state_test
def test_fail_max_per_slot_in_queue_one_less_in_withdrawals(spec, state):
prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.withdrawals = execution_payload.withdrawals[:-1]
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_capella_and_later
@spec_state_test
def test_fail_a_lot_in_queue_too_few_in_withdrawals(spec, state):
prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.withdrawals = execution_payload.withdrawals[:-1]
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
#
# Failure cases in which the withdrawals in the execution_payload are incorrect
#
@with_capella_and_later
@spec_state_test
def test_fail_incorrect_dequeue_index(spec, state):
prepare_withdrawals_queue(spec, state, 1)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.withdrawals[0].index += 1
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_capella_and_later
@spec_state_test
def test_fail_incorrect_dequeue_address(spec, state):
prepare_withdrawals_queue(spec, state, 1)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.withdrawals[0].address = b'\xff' * 20
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_capella_and_later
@spec_state_test
def test_fail_incorrect_dequeue_amount(spec, state):
prepare_withdrawals_queue(spec, state, 1)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.withdrawals[0].amount += 1
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_capella_and_later
@spec_state_test
def test_fail_one_of_many_dequeued_incorrectly(spec, state):
prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
num_withdrawals = len(execution_payload.withdrawals)
# Pick withdrawal in middle of list and mutate
withdrawal = execution_payload.withdrawals[num_withdrawals // 2]
withdrawal.index += 1
withdrawal.address = b'\x99' * 20
withdrawal.amount += 4000000
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_capella_and_later
@spec_state_test
def test_fail_many_dequeued_incorrectly(spec, state):
prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
for i, withdrawal in enumerate(execution_payload.withdrawals):
if i % 3 == 0:
withdrawal.index += 1
elif i % 3 == 1:
withdrawal.address = (i).to_bytes(20, 'big')
else:
withdrawal.amount += 1
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)

View File

@ -0,0 +1,91 @@
from eth2spec.test.context import (
with_capella_and_later,
spec_state_test,
)
from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with
def set_validator_withdrawable(spec, state, index, withdrawable_epoch=None):
if withdrawable_epoch is None:
withdrawable_epoch = spec.get_current_epoch(state)
validator = state.validators[index]
validator.withdrawable_epoch = withdrawable_epoch
validator.withdrawal_credentials = spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:]
assert spec.is_fully_withdrawable_validator(validator, withdrawable_epoch)
def run_process_full_withdrawals(spec, state, num_expected_withdrawals=None):
pre_withdrawal_index = state.withdrawal_index
pre_withdrawals_queue = state.withdrawals_queue
to_be_withdrawn_indices = [
index for index, validator in enumerate(state.validators)
if spec.is_fully_withdrawable_validator(validator, spec.get_current_epoch(state))
]
if num_expected_withdrawals is not None:
assert len(to_be_withdrawn_indices) == num_expected_withdrawals
yield from run_epoch_processing_with(spec, state, 'process_full_withdrawals')
for index in to_be_withdrawn_indices:
validator = state.validators[index]
assert validator.fully_withdrawn_epoch == spec.get_current_epoch(state)
assert state.balances[index] == 0
assert len(state.withdrawals_queue) == len(pre_withdrawals_queue) + num_expected_withdrawals
assert state.withdrawal_index == pre_withdrawal_index + num_expected_withdrawals
@with_capella_and_later
@spec_state_test
def test_no_withdrawals(spec, state):
pre_validators = state.validators.copy()
yield from run_process_full_withdrawals(spec, state, 0)
assert pre_validators == state.validators
@with_capella_and_later
@spec_state_test
def test_no_withdrawals_but_some_next_epoch(spec, state):
current_epoch = spec.get_current_epoch(state)
# Make a few validators withdrawable at the *next* epoch
for index in range(3):
set_validator_withdrawable(spec, state, index, current_epoch + 1)
yield from run_process_full_withdrawals(spec, state, 0)
@with_capella_and_later
@spec_state_test
def test_single_withdrawal(spec, state):
# Make one validator withdrawable
set_validator_withdrawable(spec, state, 0)
assert state.withdrawal_index == 0
yield from run_process_full_withdrawals(spec, state, 1)
assert state.withdrawal_index == 1
@with_capella_and_later
@spec_state_test
def test_multi_withdrawal(spec, state):
# Make a few validators withdrawable
for index in range(3):
set_validator_withdrawable(spec, state, index)
yield from run_process_full_withdrawals(spec, state, 3)
@with_capella_and_later
@spec_state_test
def test_all_withdrawal(spec, state):
# Make all validators withdrawable
for index in range(len(state.validators)):
set_validator_withdrawable(spec, state, index)
yield from run_process_full_withdrawals(spec, state, len(state.validators))

View File

@ -0,0 +1,82 @@
from eth2spec.test.context import (
with_phases,
with_custom_state,
with_presets,
spec_test, with_state,
low_balances, misc_balances, large_validator_set,
)
from eth2spec.test.utils import with_meta_tags
from eth2spec.test.helpers.constants import (
BELLATRIX, CAPELLA,
MINIMAL,
)
from eth2spec.test.helpers.state import (
next_epoch,
next_epoch_via_block,
)
from eth2spec.test.helpers.capella.fork import (
CAPELLA_FORK_TEST_META_TAGS,
run_fork_test,
)
@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA])
@spec_test
@with_state
@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS)
def test_fork_base_state(spec, phases, state):
yield from run_fork_test(phases[CAPELLA], state)
@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA])
@spec_test
@with_state
@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS)
def test_fork_next_epoch(spec, phases, state):
next_epoch(spec, state)
yield from run_fork_test(phases[CAPELLA], state)
@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA])
@spec_test
@with_state
@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS)
def test_fork_next_epoch_with_block(spec, phases, state):
next_epoch_via_block(spec, state)
yield from run_fork_test(phases[CAPELLA], state)
@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA])
@spec_test
@with_state
@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS)
def test_fork_many_next_epoch(spec, phases, state):
for _ in range(3):
next_epoch(spec, state)
yield from run_fork_test(phases[CAPELLA], state)
@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA])
@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@spec_test
@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS)
def test_fork_random_low_balances(spec, phases, state):
yield from run_fork_test(phases[CAPELLA], state)
@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA])
@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@spec_test
@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS)
def test_fork_random_misc_balances(spec, phases, state):
yield from run_fork_test(phases[CAPELLA], state)
@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA])
@with_presets([MINIMAL],
reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated")
@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@spec_test
@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS)
def test_fork_random_large_validator_set(spec, phases, state):
yield from run_fork_test(phases[CAPELLA], state)

View File

@ -6,12 +6,14 @@ from eth_utils import encode_hex
from eth2spec.phase0 import mainnet as spec_phase0_mainnet, minimal as spec_phase0_minimal
from eth2spec.altair import mainnet as spec_altair_mainnet, minimal as spec_altair_minimal
from eth2spec.bellatrix import mainnet as spec_bellatrix_mainnet, minimal as spec_bellatrix_minimal
from eth2spec.capella import mainnet as spec_capella_mainnet, minimal as spec_capella_minimal
from eth2spec.utils import bls
from .exceptions import SkippedTest
from .helpers.constants import (
PHASE0, ALTAIR, BELLATRIX, MINIMAL, MAINNET,
ALL_PHASES, FORKS_BEFORE_ALTAIR, FORKS_BEFORE_BELLATRIX,
PHASE0, ALTAIR, BELLATRIX, CAPELLA,
MINIMAL, MAINNET,
ALL_PHASES, FORKS_BEFORE_ALTAIR, FORKS_BEFORE_BELLATRIX, FORKS_BEFORE_CAPELLA,
ALL_FORK_UPGRADES,
)
from .helpers.typing import SpecForkName, PresetBaseName
@ -57,6 +59,10 @@ class SpecBellatrix(Spec):
...
class SpecCapella(Spec):
...
@dataclass(frozen=True)
class ForkMeta:
pre_fork_name: str
@ -69,11 +75,13 @@ spec_targets: Dict[PresetBaseName, Dict[SpecForkName, Spec]] = {
PHASE0: spec_phase0_minimal,
ALTAIR: spec_altair_minimal,
BELLATRIX: spec_bellatrix_minimal,
CAPELLA: spec_capella_minimal,
},
MAINNET: {
PHASE0: spec_phase0_mainnet,
ALTAIR: spec_altair_mainnet,
BELLATRIX: spec_bellatrix_mainnet,
CAPELLA: spec_capella_mainnet,
},
}
@ -82,6 +90,7 @@ class SpecForks(TypedDict, total=False):
PHASE0: SpecPhase0
ALTAIR: SpecAltair
BELLATRIX: SpecBellatrix
CAPELLA: SpecCapella
def _prepare_state(balances_fn: Callable[[Any], Sequence[int]], threshold_fn: Callable[[Any], int],
@ -533,8 +542,13 @@ def is_post_bellatrix(spec):
return spec.fork not in FORKS_BEFORE_BELLATRIX
def is_post_capella(spec):
return spec.fork not in FORKS_BEFORE_CAPELLA
with_altair_and_later = with_all_phases_except([PHASE0])
with_bellatrix_and_later = with_all_phases_except([PHASE0, ALTAIR])
with_capella_and_later = with_all_phases_except([PHASE0, ALTAIR, BELLATRIX])
def only_generator(reason):

View File

@ -0,0 +1,59 @@
CAPELLA_FORK_TEST_META_TAGS = {
'fork': 'capella',
}
def run_fork_test(post_spec, pre_state):
yield 'pre', pre_state
post_state = post_spec.upgrade_to_capella(pre_state)
# Stable fields
stable_fields = [
'genesis_time', 'genesis_validators_root', 'slot',
# History
'latest_block_header', 'block_roots', 'state_roots', 'historical_roots',
# Eth1
'eth1_data', 'eth1_data_votes', 'eth1_deposit_index',
# Registry
'balances',
# Randomness
'randao_mixes',
# Slashings
'slashings',
# Participation
'previous_epoch_participation', 'current_epoch_participation',
# Finality
'justification_bits', 'previous_justified_checkpoint', 'current_justified_checkpoint', 'finalized_checkpoint',
# Inactivity
'inactivity_scores',
# Sync
'current_sync_committee', 'next_sync_committee',
# Execution
'latest_execution_payload_header',
]
for field in stable_fields:
assert getattr(pre_state, field) == getattr(post_state, field)
# Modified fields
modified_fields = ['fork', 'validators']
for field in modified_fields:
assert getattr(pre_state, field) != getattr(post_state, field)
assert len(pre_state.validators) == len(post_state.validators)
for pre_validator, post_validator in zip(pre_state.validators, post_state.validators):
stable_validator_fields = [
'pubkey', 'withdrawal_credentials',
'effective_balance',
'slashed',
'activation_eligibility_epoch', 'activation_epoch', 'exit_epoch', 'withdrawable_epoch',
]
for field in stable_validator_fields:
assert getattr(pre_validator, field) == getattr(post_validator, field)
assert post_validator.fully_withdrawn_epoch == post_spec.FAR_FUTURE_EPOCH
assert pre_state.fork.current_version == post_state.fork.previous_version
assert post_state.fork.current_version == post_spec.config.CAPELLA_FORK_VERSION
assert post_state.fork.epoch == post_spec.get_current_epoch(post_state)
yield 'post', post_state

View File

@ -8,6 +8,7 @@ from .typing import SpecForkName, PresetBaseName
PHASE0 = SpecForkName('phase0')
ALTAIR = SpecForkName('altair')
BELLATRIX = SpecForkName('bellatrix')
CAPELLA = SpecForkName('capella')
# Experimental phases (not included in default "ALL_PHASES"):
SHARDING = SpecForkName('sharding')
@ -15,16 +16,18 @@ CUSTODY_GAME = SpecForkName('custody_game')
DAS = SpecForkName('das')
# The forks that pytest runs with.
ALL_PHASES = (PHASE0, ALTAIR, BELLATRIX)
ALL_PHASES = (PHASE0, ALTAIR, BELLATRIX, CAPELLA)
# The forks that output to the test vectors.
TESTGEN_FORKS = (PHASE0, ALTAIR, BELLATRIX)
FORKS_BEFORE_ALTAIR = (PHASE0,)
FORKS_BEFORE_BELLATRIX = (PHASE0, ALTAIR)
FORKS_BEFORE_CAPELLA = (PHASE0, ALTAIR, BELLATRIX)
ALL_FORK_UPGRADES = {
# pre_fork_name: post_fork_name
PHASE0: ALTAIR,
ALTAIR: BELLATRIX,
BELLATRIX: CAPELLA,
}
ALL_PRE_POST_FORKS = ALL_FORK_UPGRADES.items()
AFTER_BELLATRIX_UPGRADES = {key: value for key, value in ALL_FORK_UPGRADES.items() if key not in FORKS_BEFORE_ALTAIR}

View File

@ -28,6 +28,7 @@ def get_process_calls(spec):
'process_participation_record_updates'
),
'process_sync_committee_updates', # altair
'process_full_withdrawals', # capella
# TODO: add sharding processing functions when spec stabilizes.
]

View File

@ -1,3 +1,6 @@
from eth2spec.test.helpers.constants import FORKS_BEFORE_CAPELLA
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.
@ -25,6 +28,10 @@ def build_empty_execution_payload(spec, state, randao_mix=None):
block_hash=spec.Hash32(),
transactions=empty_txs,
)
if spec.fork not in FORKS_BEFORE_CAPELLA:
num_withdrawals = min(spec.MAX_WITHDRAWALS_PER_PAYLOAD, len(state.withdrawals_queue))
payload.withdrawals = state.withdrawals_queue[:num_withdrawals]
# TODO: real RLP + block hash logic would be nice, requires RLP and keccak256 dependency however.
payload.block_hash = spec.Hash32(spec.hash(payload.hash_tree_root() + b"FAKE RLP HASH"))
@ -32,7 +39,7 @@ def build_empty_execution_payload(spec, state, randao_mix=None):
def get_execution_payload_header(spec, execution_payload):
return spec.ExecutionPayloadHeader(
payload_header = spec.ExecutionPayloadHeader(
parent_hash=execution_payload.parent_hash,
fee_recipient=execution_payload.fee_recipient,
state_root=execution_payload.state_root,
@ -48,6 +55,9 @@ def get_execution_payload_header(spec, execution_payload):
block_hash=execution_payload.block_hash,
transactions_root=spec.hash_tree_root(execution_payload.transactions)
)
if spec.fork not in FORKS_BEFORE_CAPELLA:
payload_header.withdrawals_root = spec.hash_tree_root(execution_payload.withdrawals)
return payload_header
def build_state_with_incomplete_transition(spec, state):

View File

@ -92,6 +92,10 @@ def get_attestation_file_name(attestation):
return f"attestation_{encode_hex(attestation.hash_tree_root())}"
def get_attester_slashing_file_name(attester_slashing):
return f"attester_slashing_{encode_hex(attester_slashing.hash_tree_root())}"
def on_tick_and_append_step(spec, store, time, test_steps):
spec.on_tick(store, time)
test_steps.append({'tick': int(time)})
@ -142,6 +146,10 @@ def add_block(spec,
for attestation in signed_block.message.body.attestations:
run_on_attestation(spec, store, attestation, is_from_block=True, valid=True)
# An on_block step implies receiving block's attester slashings
for attester_slashing in signed_block.message.body.attester_slashings:
run_on_attester_slashing(spec, store, attester_slashing, valid=True)
block_root = signed_block.message.hash_tree_root()
assert store.blocks[block_root] == signed_block.message
assert store.block_states[block_root].hash_tree_root() == signed_block.message.state_root
@ -168,6 +176,38 @@ def add_block(spec,
return store.block_states[signed_block.message.hash_tree_root()]
def run_on_attester_slashing(spec, store, attester_slashing, valid=True):
if not valid:
try:
spec.on_attester_slashing(store, attester_slashing)
except AssertionError:
return
else:
assert False
spec.on_attester_slashing(store, attester_slashing)
def add_attester_slashing(spec, store, attester_slashing, test_steps, valid=True):
slashing_file_name = get_attester_slashing_file_name(attester_slashing)
yield get_attester_slashing_file_name(attester_slashing), attester_slashing
if not valid:
try:
run_on_attester_slashing(spec, store, attester_slashing)
except AssertionError:
test_steps.append({
'attester_slashing': slashing_file_name,
'valid': False,
})
return
else:
assert False
run_on_attester_slashing(spec, store, attester_slashing)
test_steps.append({'attester_slashing': slashing_file_name})
def get_formatted_head_output(spec, store):
head = spec.get_head(store)
slot = store.blocks[head].slot

View File

@ -12,6 +12,7 @@ from eth2spec.test.helpers.block import (
from eth2spec.test.helpers.constants import (
ALTAIR,
BELLATRIX,
CAPELLA,
)
from eth2spec.test.helpers.deposits import (
prepare_state_and_deposit,
@ -147,6 +148,8 @@ def do_fork(state, spec, post_spec, fork_epoch, with_block=True, operation_dict=
state = post_spec.upgrade_to_altair(state)
elif post_spec.fork == BELLATRIX:
state = post_spec.upgrade_to_bellatrix(state)
elif post_spec.fork == CAPELLA:
state = post_spec.upgrade_to_capella(state)
assert state.fork.epoch == fork_epoch
@ -156,6 +159,9 @@ def do_fork(state, spec, post_spec, fork_epoch, with_block=True, operation_dict=
elif post_spec.fork == BELLATRIX:
assert state.fork.previous_version == post_spec.config.ALTAIR_FORK_VERSION
assert state.fork.current_version == post_spec.config.BELLATRIX_FORK_VERSION
elif post_spec.fork == CAPELLA:
assert state.fork.previous_version == post_spec.config.BELLATRIX_FORK_VERSION
assert state.fork.current_version == post_spec.config.CAPELLA_FORK_VERSION
if with_block:
return state, _state_transition_and_sign_block_at_slot(post_spec, state, operation_dict=operation_dict)

View File

@ -1,16 +1,17 @@
from eth2spec.test.helpers.constants import (
ALTAIR, BELLATRIX,
FORKS_BEFORE_ALTAIR, FORKS_BEFORE_BELLATRIX,
FORKS_BEFORE_ALTAIR, FORKS_BEFORE_BELLATRIX, FORKS_BEFORE_CAPELLA,
)
from eth2spec.test.helpers.keys import pubkeys
def build_mock_validator(spec, i: int, balance: int):
pubkey = pubkeys[i]
active_pubkey = pubkeys[i]
withdrawal_pubkey = pubkeys[-1 - i]
# insecurely use pubkey as withdrawal key as well
withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:]
return spec.Validator(
pubkey=pubkeys[i],
withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(withdrawal_pubkey)[1:]
validator = spec.Validator(
pubkey=active_pubkey,
withdrawal_credentials=withdrawal_credentials,
activation_eligibility_epoch=spec.FAR_FUTURE_EPOCH,
activation_epoch=spec.FAR_FUTURE_EPOCH,
@ -19,6 +20,11 @@ def build_mock_validator(spec, i: int, balance: int):
effective_balance=min(balance - balance % spec.EFFECTIVE_BALANCE_INCREMENT, spec.MAX_EFFECTIVE_BALANCE)
)
if spec.fork not in FORKS_BEFORE_CAPELLA:
validator.fully_withdrawn_epoch = spec.FAR_FUTURE_EPOCH
return validator
def get_sample_genesis_execution_payload_header(spec,
eth1_block_hash=None):

View File

@ -342,3 +342,31 @@ def test_activation_queue_activation_and_ejection__exceed_scaled_churn_limit(spe
churn_limit = spec.get_validator_churn_limit(state)
assert churn_limit > spec.config.MIN_PER_EPOCH_CHURN_LIMIT
yield from run_test_activation_queue_activation_and_ejection(spec, state, churn_limit * 2)
@with_all_phases
@spec_state_test
def test_invalid_large_withdrawable_epoch(spec, state):
"""
This test forces a validator into a withdrawable epoch that overflows the
epoch (uint64) type. To do this we need two validators, one validator that
already has an exit epoch and another with a low effective balance. When
calculating the withdrawable epoch for the second validator, it will
use the greatest exit epoch of all of the validators. If the first
validator is given an exit epoch between
(FAR_FUTURE_EPOCH-MIN_VALIDATOR_WITHDRAWABILITY_DELAY+1) and
(FAR_FUTURE_EPOCH-1), it will cause an overflow.
"""
assert spec.is_active_validator(state.validators[0], spec.get_current_epoch(state))
assert spec.is_active_validator(state.validators[1], spec.get_current_epoch(state))
state.validators[0].exit_epoch = spec.FAR_FUTURE_EPOCH - 1
state.validators[1].effective_balance = spec.config.EJECTION_BALANCE
try:
yield from run_process_registry_updates(spec, state)
except ValueError:
yield 'post', None
return
raise AssertionError('expected ValueError')

View File

@ -200,7 +200,10 @@ def run_with_participation(spec, state, participation_fn):
@spec_state_test
def test_almost_empty_attestations(spec, state):
rng = Random(1234)
yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, 1))
def participation_fn(slot, comm_index, comm):
return rng.sample(sorted(comm), 1)
yield from run_with_participation(spec, state, participation_fn)
@with_all_phases
@ -208,14 +211,20 @@ def test_almost_empty_attestations(spec, state):
@leaking()
def test_almost_empty_attestations_with_leak(spec, state):
rng = Random(1234)
yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, 1))
def participation_fn(slot, comm_index, comm):
return rng.sample(sorted(comm), 1)
yield from run_with_participation(spec, state, participation_fn)
@with_all_phases
@spec_state_test
def test_random_fill_attestations(spec, state):
rng = Random(4567)
yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, len(comm) // 3))
def participation_fn(slot, comm_index, comm):
return rng.sample(sorted(comm), len(comm) // 3)
yield from run_with_participation(spec, state, participation_fn)
@with_all_phases
@ -223,14 +232,20 @@ def test_random_fill_attestations(spec, state):
@leaking()
def test_random_fill_attestations_with_leak(spec, state):
rng = Random(4567)
yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, len(comm) // 3))
def participation_fn(slot, comm_index, comm):
return rng.sample(sorted(comm), len(comm) // 3)
yield from run_with_participation(spec, state, participation_fn)
@with_all_phases
@spec_state_test
def test_almost_full_attestations(spec, state):
rng = Random(8901)
yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, len(comm) - 1))
def participation_fn(slot, comm_index, comm):
return rng.sample(sorted(comm), len(comm) - 1)
yield from run_with_participation(spec, state, participation_fn)
@with_all_phases
@ -238,7 +253,10 @@ def test_almost_full_attestations(spec, state):
@leaking()
def test_almost_full_attestations_with_leak(spec, state):
rng = Random(8901)
yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, len(comm) - 1))
def participation_fn(slot, comm_index, comm):
return rng.sample(sorted(comm), len(comm) - 1)
yield from run_with_participation(spec, state, participation_fn)
@with_all_phases

View File

@ -8,16 +8,21 @@ from eth2spec.test.context import (
with_presets,
)
from eth2spec.test.helpers.attestations import get_valid_attestation, next_epoch_with_attestations
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
from eth2spec.test.helpers.block import (
apply_empty_block,
build_empty_block_for_next_slot,
)
from eth2spec.test.helpers.constants import MINIMAL
from eth2spec.test.helpers.fork_choice import (
tick_and_run_on_attestation,
tick_and_add_block,
add_attester_slashing,
add_block,
get_anchor_root,
get_genesis_forkchoice_store_and_block,
get_formatted_head_output,
on_tick_and_append_step,
add_block,
add_attestation,
tick_and_run_on_attestation,
tick_and_add_block,
)
from eth2spec.test.helpers.state import (
next_slots,
@ -338,3 +343,84 @@ def test_proposer_boost_correct_head(spec, state):
})
yield 'steps', test_steps
@with_all_phases
@spec_state_test
def test_discard_equivocations(spec, state):
test_steps = []
genesis_state = state.copy()
# Initialization
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
yield 'anchor_state', state
yield 'anchor_block', anchor_block
anchor_root = get_anchor_root(spec, state)
assert spec.get_head(store) == anchor_root
test_steps.append({
'checks': {
'head': get_formatted_head_output(spec, store),
}
})
# Build block that serves as head before discarding equivocations
state_1 = genesis_state.copy()
next_slots(spec, state_1, 3)
block_1 = build_empty_block_for_next_slot(spec, state_1)
signed_block_1 = state_transition_and_sign_block(spec, state_1, block_1)
# Build equivocating attestations to feed to store
state_eqv = state_1.copy()
block_eqv = apply_empty_block(spec, state_eqv, state_eqv.slot + 1)
attestation_eqv = get_valid_attestation(spec, state_eqv, slot=block_eqv.slot, signed=True)
next_slots(spec, state_1, 1)
attestation = get_valid_attestation(spec, state_1, slot=block_eqv.slot, signed=True)
assert spec.is_slashable_attestation_data(attestation.data, attestation_eqv.data)
indexed_attestation = spec.get_indexed_attestation(state_1, attestation)
indexed_attestation_eqv = spec.get_indexed_attestation(state_eqv, attestation_eqv)
attester_slashing = spec.AttesterSlashing(attestation_1=indexed_attestation, attestation_2=indexed_attestation_eqv)
# Build block that serves as head after discarding equivocations
state_2 = genesis_state.copy()
next_slots(spec, state_2, 2)
block_2 = build_empty_block_for_next_slot(spec, state_2)
signed_block_2 = state_transition_and_sign_block(spec, state_2.copy(), block_2)
while spec.hash_tree_root(block_1) >= spec.hash_tree_root(block_2):
block_2.body.graffiti = spec.Bytes32(hex(rng.getrandbits(8 * 32))[2:].zfill(64))
signed_block_2 = state_transition_and_sign_block(spec, state_2.copy(), block_2)
assert spec.hash_tree_root(block_1) < spec.hash_tree_root(block_2)
# Tick to (block_eqv.slot + 2) slot time
time = store.genesis_time + (block_eqv.slot + 2) * spec.config.SECONDS_PER_SLOT
on_tick_and_append_step(spec, store, time, test_steps)
# Process block_2
yield from add_block(spec, store, signed_block_2, test_steps)
assert store.proposer_boost_root == spec.Root()
assert spec.get_head(store) == spec.hash_tree_root(block_2)
# Process block_1
# The head should remain block_2
yield from add_block(spec, store, signed_block_1, test_steps)
assert store.proposer_boost_root == spec.Root()
assert spec.get_head(store) == spec.hash_tree_root(block_2)
# Process attestation
# The head should change to block_1
yield from add_attestation(spec, store, attestation, test_steps)
assert spec.get_head(store) == spec.hash_tree_root(block_1)
# Process attester_slashing
# The head should revert to block_2
yield from add_attester_slashing(spec, store, attester_slashing, test_steps)
assert spec.get_head(store) == spec.hash_tree_root(block_2)
test_steps.append({
'checks': {
'head': get_formatted_head_output(spec, store),
}
})
yield 'steps', test_steps

View File

@ -38,7 +38,7 @@ def _drop_random_one_third(_slot, _index, indices):
assert committee_len >= 3
filter_len = committee_len // 3
participant_count = committee_len - filter_len
return rng.sample(indices, participant_count)
return rng.sample(sorted(indices), participant_count)
@with_all_phases

View File

@ -1,7 +1,7 @@
from eth2spec.test.context import with_all_phases, spec_state_test
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA
from eth2spec.test.helpers.state import transition_to, state_transition_and_sign_block, next_epoch, next_slot
from eth2spec.test.helpers.fork_choice import get_genesis_forkchoice_store
@ -19,7 +19,7 @@ def run_on_attestation(spec, state, store, attestation, valid=True):
spec.on_attestation(store, attestation)
sample_index = indexed_attestation.attesting_indices[0]
if spec.fork in (PHASE0, ALTAIR, BELLATRIX):
if spec.fork in (PHASE0, ALTAIR, BELLATRIX, CAPELLA):
latest_message = spec.LatestMessage(
epoch=attestation.data.target.epoch,
root=attestation.data.beacon_block_root,

View File

@ -21,7 +21,7 @@ An SSZ-snappy encoded `BeaconState`, the state before running the epoch sub-tran
### `post.ssz_snappy`
An SSZ-snappy encoded `BeaconState`, the state after applying the epoch sub-transition.
An SSZ-snappy encoded `BeaconState`, the state after applying the epoch sub-transition. No value if the sub-transition processing is aborted.
## Condition

View File

@ -76,11 +76,27 @@ Adds `PowBlock` data which is required for executing `on_block(store, block)`.
{
pow_block: string -- the name of the `pow_block_<32-byte-root>.ssz_snappy` file.
To be used in `get_pow_block` lookup
}
}
```
The file is located in the same folder (see below).
PowBlocks should be used as return values for `get_pow_block(hash: Hash32) -> PowBlock` function if hashes match.
#### `on_attester_slashing` execution step
The parameter that is required for executing `on_attester_slashing(store, attester_slashing)`.
```yaml
{
attester_slashing: string -- the name of the `attester_slashing_<32-byte-root>.ssz_snappy` file.
To execute `on_attester_slashing(store, attester_slashing)` with the given attester slashing.
valid: bool -- optional, default to `true`.
If it's `false`, this execution step is expected to be invalid.
}
```
The file is located in the same folder (see below).
After this step, the `store` object may have been updated.
#### Checks step
The checks to verify the current status of `store`.