Merge branch 'dev' into withdrawals-push
This commit is contained in:
commit
2026103bb9
2
Makefile
2
Makefile
|
@ -130,9 +130,11 @@ codespell:
|
|||
codespell . --skip ./.git -I .codespell-whitelist
|
||||
|
||||
# TODO: add future protocol upgrade patch packages to linting.
|
||||
# NOTE: we use `pylint` just for catching unused arguments in spec code
|
||||
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 -p eth2spec.capella
|
||||
|
||||
lint_generators: pyspec
|
||||
|
|
|
@ -33,7 +33,7 @@ The current features are:
|
|||
* [Honest Validator guide changes](specs/altair/validator.md)
|
||||
* [P2P Networking](specs/altair/p2p-interface.md)
|
||||
|
||||
### Bellatrix (as known as The Merge)
|
||||
### Bellatrix (also known as The Merge)
|
||||
|
||||
The Bellatrix protocol upgrade is still actively in development. The exact specification has not been formally accepted as final and details are still subject to change.
|
||||
|
||||
|
@ -45,7 +45,6 @@ The Bellatrix protocol upgrade is still actively in development. The exact speci
|
|||
* [Bellatrix fork](specs/bellatrix/fork.md)
|
||||
* [Fork Choice changes](specs/bellatrix/fork-choice.md)
|
||||
* [Validator additions](specs/bellatrix/validator.md)
|
||||
* [Client settings](specs/bellatrix/client-settings.md)
|
||||
* [P2P Networking](specs/bellatrix/p2p-interface.md)
|
||||
|
||||
### Sharding
|
||||
|
|
|
@ -9,7 +9,7 @@ Standard configs:
|
|||
- [`minimal.yaml`](./minimal.yaml): Minimal configuration, used in spec-testing along with the [`minimal`](../presets/minimal) preset.
|
||||
|
||||
Not all network configurations are in scope for the specification,
|
||||
see [`github.com/eth2-clients/eth2-networks`](https://github.com/eth2-clients/eth2-networks) for common networks,
|
||||
see [`github.com/eth-clients/eth2-networks`](https://github.com/eth-clients/eth2-networks) for common networks,
|
||||
and additional testnet assets.
|
||||
|
||||
## Forking
|
||||
|
|
|
@ -3,6 +3,13 @@
|
|||
# Extends the mainnet preset
|
||||
PRESET_BASE: 'mainnet'
|
||||
|
||||
# Free-form short name of the network that this configuration applies to - known
|
||||
# canonical network names include:
|
||||
# * 'mainnet' - there can be only one
|
||||
# * 'prater' - testnet
|
||||
# Must match the regex: [a-z0-9\-]
|
||||
CONFIG_NAME: 'mainnet'
|
||||
|
||||
# Transition
|
||||
# ---------------------------------------------------------------
|
||||
# TBD, 2**256-2**10 is a placeholder
|
||||
|
|
|
@ -3,6 +3,13 @@
|
|||
# Extends the minimal preset
|
||||
PRESET_BASE: 'minimal'
|
||||
|
||||
# Free-form short name of the network that this configuration applies to - known
|
||||
# canonical network names include:
|
||||
# * 'mainnet' - there can be only one
|
||||
# * 'prater' - testnet
|
||||
# Must match the regex: [a-z0-9\-]
|
||||
CONFIG_NAME: 'minimal'
|
||||
|
||||
# Transition
|
||||
# ---------------------------------------------------------------
|
||||
# TBD, 2**256-2**10 is a placeholder
|
||||
|
|
9
setup.py
9
setup.py
|
@ -516,7 +516,7 @@ def get_pow_block(hash: Bytes32) -> Optional[PowBlock]:
|
|||
return PowBlock(block_hash=hash, parent_hash=Bytes32(), total_difficulty=uint256(0))
|
||||
|
||||
|
||||
def get_execution_state(execution_state_root: Bytes32) -> ExecutionState:
|
||||
def get_execution_state(_execution_state_root: Bytes32) -> ExecutionState:
|
||||
pass
|
||||
|
||||
|
||||
|
@ -526,7 +526,7 @@ def get_pow_chain_head() -> PowBlock:
|
|||
|
||||
class NoopExecutionEngine(ExecutionEngine):
|
||||
|
||||
def execute_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool:
|
||||
def notify_new_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool:
|
||||
return True
|
||||
|
||||
def notify_forkchoice_updated(self: ExecutionEngine,
|
||||
|
@ -767,7 +767,7 @@ def parse_config_vars(conf: Dict[str, str]) -> Dict[str, str]:
|
|||
"""
|
||||
out: Dict[str, str] = dict()
|
||||
for k, v in conf.items():
|
||||
if isinstance(v, str) and (v.startswith("0x") or k == 'PRESET_BASE'):
|
||||
if isinstance(v, str) and (v.startswith("0x") or k == 'PRESET_BASE' or k == 'CONFIG_NAME'):
|
||||
# Represent byte data with string, to avoid misinterpretation as big-endian int.
|
||||
# Everything is either byte data or an integer, with PRESET_BASE as one exception.
|
||||
out[k] = f"'{v}'"
|
||||
|
@ -883,6 +883,7 @@ class PySpecCommand(Command):
|
|||
specs/bellatrix/fork.md
|
||||
specs/bellatrix/fork-choice.md
|
||||
specs/bellatrix/validator.md
|
||||
sync/optimistic.md
|
||||
"""
|
||||
if self.spec_fork == CAPELLA:
|
||||
self.md_doc_paths += """
|
||||
|
@ -1030,7 +1031,7 @@ setup(
|
|||
python_requires=">=3.8, <4",
|
||||
extras_require={
|
||||
"test": ["pytest>=4.4", "pytest-cov", "pytest-xdist"],
|
||||
"lint": ["flake8==3.7.7", "mypy==0.812"],
|
||||
"lint": ["flake8==3.7.7", "mypy==0.812", "pylint==2.12.2"],
|
||||
"generator": ["python-snappy==0.5.4"],
|
||||
},
|
||||
install_requires=[
|
||||
|
|
|
@ -126,7 +126,7 @@ This patch updates a few configuration values to move penalty parameters closer
|
|||
|
||||
| Name | Value | Unit | Duration |
|
||||
| - | - | - | - |
|
||||
| `SYNC_COMMITTEE_SIZE` | `uint64(2**9)` (= 512) | Validators | |
|
||||
| `SYNC_COMMITTEE_SIZE` | `uint64(2**9)` (= 512) | validators | |
|
||||
| `EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | `uint64(2**8)` (= 256) | epochs | ~27 hours |
|
||||
|
||||
## Configuration
|
||||
|
@ -521,7 +521,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
|
|||
signing_root = compute_signing_root(deposit_message, domain)
|
||||
# Initialize validator if the deposit signature is valid
|
||||
if bls.Verify(pubkey, signing_root, deposit.data.signature):
|
||||
state.validators.append(get_validator_from_deposit(state, deposit))
|
||||
state.validators.append(get_validator_from_deposit(deposit))
|
||||
state.balances.append(amount)
|
||||
state.previous_epoch_participation.append(ParticipationFlags(0b0000_0000))
|
||||
state.current_epoch_participation.append(ParticipationFlags(0b0000_0000))
|
||||
|
|
|
@ -42,17 +42,17 @@ uses sync committees introduced in [this beacon chain extension](./beacon-chain.
|
|||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `FINALIZED_ROOT_INDEX` | `get_generalized_index(BeaconState, 'finalized_checkpoint', 'root')` |
|
||||
| `NEXT_SYNC_COMMITTEE_INDEX` | `get_generalized_index(BeaconState, 'next_sync_committee')` |
|
||||
| `FINALIZED_ROOT_INDEX` | `get_generalized_index(BeaconState, 'finalized_checkpoint', 'root')` (= 105) |
|
||||
| `NEXT_SYNC_COMMITTEE_INDEX` | `get_generalized_index(BeaconState, 'next_sync_committee')` (= 55) |
|
||||
|
||||
## Preset
|
||||
|
||||
### Misc
|
||||
|
||||
| Name | Value | Notes |
|
||||
| - | - | - |
|
||||
| `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | |
|
||||
| `UPDATE_TIMEOUT` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | ~27.3 hours |
|
||||
| Name | Value | Unit | Duration |
|
||||
| - | - | - | - |
|
||||
| `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | validators |
|
||||
| `UPDATE_TIMEOUT` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | epochs | ~27.3 hours |
|
||||
|
||||
## Containers
|
||||
|
||||
|
@ -69,7 +69,7 @@ class LightClientUpdate(Container):
|
|||
finalized_header: BeaconBlockHeader
|
||||
finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX)]
|
||||
# Sync committee aggregate signature
|
||||
sync_committee_aggregate: SyncAggregate
|
||||
sync_aggregate: SyncAggregate
|
||||
# Fork version for the aggregate signature
|
||||
fork_version: Version
|
||||
```
|
||||
|
@ -127,7 +127,7 @@ def get_safety_threshold(store: LightClientStore) -> uint64:
|
|||
|
||||
## Light client state updates
|
||||
|
||||
A light client maintains its state in a `store` object of type `LightClientStore` and receives `update` objects of type `LightClientUpdate`. Every `update` triggers `process_light_client_update(store, update, current_slot)` where `current_slot` is the current slot based on some local clock. `process_slot_for_light_client_store` is processed every time the current slot increments.
|
||||
A light client maintains its state in a `store` object of type `LightClientStore` and receives `update` objects of type `LightClientUpdate`. Every `update` triggers `process_light_client_update(store, update, current_slot, genesis_validators_root)` where `current_slot` is the current slot based on a local clock. `process_slot_for_light_client_store` is triggered every time the current slot increments.
|
||||
|
||||
#### `process_slot_for_light_client_store`
|
||||
|
||||
|
@ -157,8 +157,8 @@ def validate_light_client_update(store: LightClientStore,
|
|||
assert current_slot >= active_header.slot > store.finalized_header.slot
|
||||
|
||||
# Verify update does not skip a sync committee period
|
||||
finalized_period = compute_epoch_at_slot(store.finalized_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD
|
||||
update_period = compute_epoch_at_slot(active_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD
|
||||
finalized_period = compute_sync_committee_period(compute_epoch_at_slot(store.finalized_header.slot))
|
||||
update_period = compute_sync_committee_period(compute_epoch_at_slot(active_header.slot))
|
||||
assert update_period in (finalized_period, finalized_period + 1)
|
||||
|
||||
# Verify that the `finalized_header`, if present, actually is the finalized header saved in the
|
||||
|
@ -187,8 +187,8 @@ def validate_light_client_update(store: LightClientStore,
|
|||
index=get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX),
|
||||
root=active_header.state_root,
|
||||
)
|
||||
|
||||
sync_aggregate = update.sync_committee_aggregate
|
||||
|
||||
sync_aggregate = update.sync_aggregate
|
||||
|
||||
# Verify sync committee has sufficient participants
|
||||
assert sum(sync_aggregate.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS
|
||||
|
@ -208,12 +208,14 @@ def validate_light_client_update(store: LightClientStore,
|
|||
```python
|
||||
def apply_light_client_update(store: LightClientStore, update: LightClientUpdate) -> None:
|
||||
active_header = get_active_header(update)
|
||||
finalized_period = compute_epoch_at_slot(store.finalized_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD
|
||||
update_period = compute_epoch_at_slot(active_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD
|
||||
finalized_period = compute_sync_committee_period(compute_epoch_at_slot(store.finalized_header.slot))
|
||||
update_period = compute_sync_committee_period(compute_epoch_at_slot(active_header.slot))
|
||||
if update_period == finalized_period + 1:
|
||||
store.current_sync_committee = store.next_sync_committee
|
||||
store.next_sync_committee = update.next_sync_committee
|
||||
store.finalized_header = active_header
|
||||
if store.finalized_header.slot > store.optimistic_header.slot:
|
||||
store.optimistic_header = store.finalized_header
|
||||
```
|
||||
|
||||
#### `process_light_client_update`
|
||||
|
@ -225,12 +227,12 @@ def process_light_client_update(store: LightClientStore,
|
|||
genesis_validators_root: Root) -> None:
|
||||
validate_light_client_update(store, update, current_slot, genesis_validators_root)
|
||||
|
||||
sync_committee_bits = update.sync_committee_aggregate.sync_committee_bits
|
||||
sync_committee_bits = update.sync_aggregate.sync_committee_bits
|
||||
|
||||
# Update the best update in case we have to force-update to it if the timeout elapses
|
||||
if (
|
||||
store.best_valid_update is None
|
||||
or sum(sync_committee_bits) > sum(store.best_valid_update.sync_committee_aggregate.sync_committee_bits)
|
||||
or sum(sync_committee_bits) > sum(store.best_valid_update.sync_aggregate.sync_committee_bits)
|
||||
):
|
||||
store.best_valid_update = update
|
||||
|
||||
|
|
|
@ -268,7 +268,7 @@ This process occurs each slot.
|
|||
If a validator is in the current sync committee (i.e. `is_assigned_to_sync_committee()` above returns `True`), then for every `slot` in the current sync committee period, the validator should prepare a `SyncCommitteeMessage` for the previous slot (`slot - 1`) according to the logic in `get_sync_committee_message` as soon as they have determined the head block of `slot - 1`. This means that when assigned to `slot` a `SyncCommitteeMessage` is prepared and broadcast in `slot-1 ` instead of `slot`.
|
||||
|
||||
This logic is triggered upon the same conditions as when producing an attestation.
|
||||
Meaning, a sync committee member should produce and broadcast a `SyncCommitteeMessage` either when (a) the validator has received a valid block from the expected block proposer for the current `slot` or (b) one-third of the slot has transpired (`SECONDS_PER_SLOT / 3` seconds after the start of the slot) -- whichever comes first.
|
||||
Meaning, a sync committee member should produce and broadcast a `SyncCommitteeMessage` either when (a) the validator has received a valid block from the expected block proposer for the current `slot` or (b) one-third of the slot has transpired (`SECONDS_PER_SLOT / INTERVALS_PER_SLOT` seconds after the start of the slot) -- whichever comes first.
|
||||
|
||||
`get_sync_committee_message(state, block_root, validator_index, privkey)` assumes the parameter `state` is the head state corresponding to processing the block up to the current slot as determined by the fork choice (including any empty slots up to the current slot processed with `process_slots` on top of the latest block), `block_root` is the root of the head block, `validator_index` is the index of the validator in the registry `state.validators` controlled by `privkey`, and `privkey` is the BLS private key for the validator.
|
||||
|
||||
|
@ -385,7 +385,7 @@ The collection of input signatures should include one signature per validator wh
|
|||
|
||||
##### Broadcast sync committee contribution
|
||||
|
||||
If the validator is selected to aggregate (`is_sync_committee_aggregator()`), then they broadcast their best aggregate as a `SignedContributionAndProof` to the global aggregate channel (`sync_committee_contribution_and_proof` topic) two-thirds of the way through the `slot`-that is, `SECONDS_PER_SLOT * 2 / 3` seconds after the start of `slot`.
|
||||
If the validator is selected to aggregate (`is_sync_committee_aggregator()`), then they broadcast their best aggregate as a `SignedContributionAndProof` to the global aggregate channel (`sync_committee_contribution_and_proof` topic) two-thirds of the way through the `slot`-that is, `SECONDS_PER_SLOT * 2 / INTERVALS_PER_SLOT` seconds after the start of `slot`.
|
||||
|
||||
Selection proofs are provided in `ContributionAndProof` to prove to the gossip channel that the validator has been selected as an aggregator.
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
- [Modified `slash_validator`](#modified-slash_validator)
|
||||
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
|
||||
- [Execution engine](#execution-engine)
|
||||
- [`execute_payload`](#execute_payload)
|
||||
- [`notify_new_payload`](#notify_new_payload)
|
||||
- [Block processing](#block-processing)
|
||||
- [Execution payload](#execution-payload)
|
||||
- [`process_execution_payload`](#process_execution_payload)
|
||||
|
@ -169,9 +169,9 @@ class ExecutionPayload(Container):
|
|||
parent_hash: Hash32
|
||||
fee_recipient: ExecutionAddress # 'beneficiary' in the yellow paper
|
||||
state_root: Bytes32
|
||||
receipt_root: Bytes32 # 'receipts root' in the yellow paper
|
||||
receipts_root: Bytes32
|
||||
logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM]
|
||||
random: Bytes32 # 'difficulty' in the yellow paper
|
||||
prev_randao: Bytes32 # 'difficulty' in the yellow paper
|
||||
block_number: uint64 # 'number' in the yellow paper
|
||||
gas_limit: uint64
|
||||
gas_used: uint64
|
||||
|
@ -191,9 +191,9 @@ class ExecutionPayloadHeader(Container):
|
|||
parent_hash: Hash32
|
||||
fee_recipient: ExecutionAddress
|
||||
state_root: Bytes32
|
||||
receipt_root: Bytes32
|
||||
receipts_root: Bytes32
|
||||
logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM]
|
||||
random: Bytes32
|
||||
prev_randao: Bytes32
|
||||
block_number: uint64
|
||||
gas_limit: uint64
|
||||
gas_used: uint64
|
||||
|
@ -307,17 +307,17 @@ def slash_validator(state: BeaconState,
|
|||
The implementation-dependent `ExecutionEngine` protocol encapsulates the execution sub-system logic via:
|
||||
|
||||
* a state object `self.execution_state` of type `ExecutionState`
|
||||
* a state transition function `self.execute_payload` which applies changes to the `self.execution_state`
|
||||
* a notification function `self.notify_new_payload` which may apply changes to the `self.execution_state`
|
||||
|
||||
*Note*: `execute_payload` is a function accessed through the `EXECUTION_ENGINE` module which instantiates the `ExecutionEngine` protocol.
|
||||
*Note*: `notify_new_payload` is a function accessed through the `EXECUTION_ENGINE` module which instantiates the `ExecutionEngine` protocol.
|
||||
|
||||
The body of this function is implementation dependent.
|
||||
The Engine API may be used to implement this and similarly defined functions via an external execution engine.
|
||||
|
||||
#### `execute_payload`
|
||||
#### `notify_new_payload`
|
||||
|
||||
```python
|
||||
def execute_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool:
|
||||
def notify_new_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool:
|
||||
"""
|
||||
Return ``True`` if and only if ``execution_payload`` is valid with respect to ``self.execution_state``.
|
||||
"""
|
||||
|
@ -348,20 +348,20 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe
|
|||
# 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 random
|
||||
assert payload.random == get_randao_mix(state, get_current_epoch(state))
|
||||
# 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.execute_payload(payload)
|
||||
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,
|
||||
receipt_root=payload.receipt_root,
|
||||
receipts_root=payload.receipts_root,
|
||||
logs_bloom=payload.logs_bloom,
|
||||
random=payload.random,
|
||||
prev_randao=payload.prev_randao,
|
||||
block_number=payload.block_number,
|
||||
gas_limit=payload.gas_limit,
|
||||
gas_used=payload.gas_used,
|
||||
|
|
|
@ -80,7 +80,7 @@ Used to signal to initiate the payload build process via `notify_forkchoice_upda
|
|||
@dataclass
|
||||
class PayloadAttributes(object):
|
||||
timestamp: uint64
|
||||
random: Bytes32
|
||||
prev_randao: Bytes32
|
||||
suggested_fee_recipient: ExecutionAddress
|
||||
```
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ Readers should understand the Phase 0 and Altair documents and use them as a bas
|
|||
- [Why was the max gossip message size increased at Bellatrix?](#why-was-the-max-gossip-message-size-increased-at-bellatrix)
|
||||
- [Req/Resp](#reqresp)
|
||||
- [Why was the max chunk response size increased at Bellatrix?](#why-was-the-max-chunk-response-size-increased-at-bellatrix)
|
||||
- [Why allow invalid payloads on the P2P network?](#why-allow-invalid-payloads-on-the-p2p-network)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- /TOC -->
|
||||
|
@ -85,13 +86,26 @@ The *type* of the payload of this topic changes to the (modified) `SignedBeaconB
|
|||
Specifically, this type changes with the addition of `execution_payload` to the inner `BeaconBlockBody`.
|
||||
See Bellatrix [state transition document](./beacon-chain.md#beaconblockbody) for further details.
|
||||
|
||||
Blocks with execution enabled will be permitted to propagate regardless of the
|
||||
validity of the execution payload. This prevents network segregation between
|
||||
[optimistic](/sync/optimistic.md) and non-optimistic nodes.
|
||||
|
||||
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`.
|
||||
- If the execution is enabled for the block -- i.e. `is_execution_enabled(state, block.body)`
|
||||
then validate the following:
|
||||
- _[REJECT]_ The block's execution payload timestamp is correct with respect to the slot
|
||||
-- i.e. `execution_payload.timestamp == compute_timestamp_at_slot(state, block.slot)`.
|
||||
- _[REJECT]_ The block's execution payload timestamp is correct with respect to the slot
|
||||
-- i.e. `execution_payload.timestamp == compute_timestamp_at_slot(state, block.slot)`.
|
||||
- If `exection_payload` verification of block's parent by an execution node is *not* complete:
|
||||
- [REJECT] The block's parent (defined by `block.parent_root`) passes all
|
||||
validation (excluding execution node verification of the `block.body.execution_payload`).
|
||||
- otherwise:
|
||||
- [IGNORE] The block's parent (defined by `block.parent_root`) passes all
|
||||
validation (including execution node verification of the `block.body.execution_payload`).
|
||||
|
||||
The following gossip validation from prior specifications MUST NOT be applied if the execution is enabled for the block -- i.e. `is_execution_enabled(state, block.body)`:
|
||||
- [REJECT] The block's parent (defined by `block.parent_root`) passes validation.
|
||||
|
||||
### Transitioning the gossip
|
||||
|
||||
|
@ -100,6 +114,14 @@ details on how to handle transitioning gossip topics for Bellatrix.
|
|||
|
||||
## The Req/Resp domain
|
||||
|
||||
Non-faulty, [optimistic](/sync/optimistic.md) nodes may send blocks which
|
||||
result in an INVALID response from an execution engine. To prevent network
|
||||
segregation between optimistic and non-optimistic nodes, transmission of an
|
||||
INVALID execution payload via the Req/Resp domain SHOULD NOT cause a node to be
|
||||
down-scored or disconnected. Transmission of a block which is invalid due to
|
||||
any consensus layer rules (i.e., *not* execution layer rules) MAY result in
|
||||
down-scoring or disconnection.
|
||||
|
||||
### Messages
|
||||
|
||||
#### BeaconBlocksByRange v2
|
||||
|
@ -181,3 +203,26 @@ valid block sizes in the range of gas limits expected in the medium term.
|
|||
|
||||
As with both gossip and req/rsp maximum values, type-specific limits should
|
||||
always by simultaneously respected.
|
||||
|
||||
### Why allow invalid payloads on the P2P network?
|
||||
|
||||
The specification allows blocks with invalid execution payloads to propagate across
|
||||
gossip and via RPC calls. The reasoning for this is as follows:
|
||||
|
||||
1. Optimistic nodes must listen to block gossip to obtain a view of the head of
|
||||
the chain.
|
||||
2. Therefore, optimistic nodes must propagate gossip blocks. Otherwise, they'd
|
||||
be censoring.
|
||||
3. If optimistic nodes will propagate blocks via gossip, then they must respond
|
||||
to requests for the parent via RPC.
|
||||
4. Therefore, optimistic nodes must send optimistic blocks via RPC.
|
||||
|
||||
So, to prevent network segregation from optimistic nodes inadvertently sending
|
||||
invalid execution payloads, nodes should never downscore/disconnect nodes due to such invalid
|
||||
payloads. This does open the network to some DoS attacks from invalid execution
|
||||
payloads, but the scope of actors is limited to validators who can put those
|
||||
payloads in valid (and slashable) beacon blocks. Therefore, it is argued that
|
||||
the DoS risk introduced in tolerable.
|
||||
|
||||
More complicated schemes are possible that could restrict invalid payloads from
|
||||
RPC. However, it's not clear that complexity is warranted.
|
||||
|
|
|
@ -143,7 +143,7 @@ def prepare_execution_payload(state: BeaconState,
|
|||
# Set the forkchoice head and initiate the payload build process
|
||||
payload_attributes = PayloadAttributes(
|
||||
timestamp=compute_timestamp_at_slot(state, state.slot),
|
||||
random=get_randao_mix(state, get_current_epoch(state)),
|
||||
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)
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
- [Introduction](#introduction)
|
||||
- [Constants](#constants)
|
||||
- [Misc](#misc)
|
||||
- [Configuration](#configuration)
|
||||
- [Domain types](#domain-types)
|
||||
- [Preset](#preset)
|
||||
- [Time parameters](#time-parameters)
|
||||
- [Max operations per block](#max-operations-per-block)
|
||||
- [Size parameters](#size-parameters)
|
||||
|
|
|
@ -196,11 +196,6 @@ This builds on top of the protocol identification and encoding spec which was in
|
|||
|
||||
Note that DAS networking uses a different protocol prefix: `/eth2/das/req`
|
||||
|
||||
The result codes are extended with:
|
||||
- 3: **ResourceUnavailable** -- when the request was valid but cannot be served at this point in time.
|
||||
|
||||
TODO: unify with phase0? Lighthoue already defined this in their response codes enum.
|
||||
|
||||
### Messages
|
||||
|
||||
#### DASQuery
|
||||
|
|
|
@ -1832,7 +1832,7 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
|
|||
##### Deposits
|
||||
|
||||
```python
|
||||
def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator:
|
||||
def get_validator_from_deposit(deposit: Deposit) -> Validator:
|
||||
amount = deposit.data.amount
|
||||
effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)
|
||||
|
||||
|
@ -1877,7 +1877,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
|
|||
return
|
||||
|
||||
# Add validator and balance entries
|
||||
state.validators.append(get_validator_from_deposit(state, deposit))
|
||||
state.validators.append(get_validator_from_deposit(deposit))
|
||||
state.balances.append(amount)
|
||||
else:
|
||||
# Increase balance by deposit amount
|
||||
|
|
|
@ -569,11 +569,11 @@ The response code can have one of the following values, encoded as a single unsi
|
|||
The response payload adheres to the `ErrorMessage` schema (described below).
|
||||
- 3: **ResourceUnavailable** -- the responder does not have requested resource.
|
||||
The response payload adheres to the `ErrorMessage` schema (described below).
|
||||
*Note*: This response code is only valid as a response to `BlocksByRange`.
|
||||
*Note*: This response code is only valid as a response where specified.
|
||||
|
||||
Clients MAY use response codes above `128` to indicate alternative, erroneous request-specific responses.
|
||||
|
||||
The range `[3, 127]` is RESERVED for future usages, and should be treated as error if not recognized expressly.
|
||||
The range `[4, 127]` is RESERVED for future usages, and should be treated as error if not recognized expressly.
|
||||
|
||||
The `ErrorMessage` schema is:
|
||||
|
||||
|
|
|
@ -0,0 +1,376 @@
|
|||
# Optimistic Sync
|
||||
|
||||
## Introduction
|
||||
|
||||
In order to provide a syncing execution engine with a partial view of the head
|
||||
of the chain, it may be desirable for a consensus engine to import beacon
|
||||
blocks without verifying the execution payloads. This partial sync is called an
|
||||
*optimistic sync*.
|
||||
|
||||
Optimistic sync is designed to be opt-in and backwards compatible (i.e.,
|
||||
non-optimistic nodes can tolerate optimistic nodes on the network and vice
|
||||
versa). Optimistic sync is not a fundamental requirement for consensus nodes.
|
||||
Rather, it's a stop-gap measure to allow execution nodes to sync via
|
||||
established methods until future Ethereum roadmap items are implemented (e.g.,
|
||||
statelessness).
|
||||
|
||||
## Constants
|
||||
|
||||
|Name|Value|Unit
|
||||
|---|---|---|
|
||||
|`SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`| `128` | slots
|
||||
|
||||
*Note: the `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` must be user-configurable. See
|
||||
[Fork Choice Poisoning](#fork-choice-poisoning).*
|
||||
|
||||
## Helpers
|
||||
|
||||
For brevity, we define two aliases for values of the `status` field on
|
||||
`PayloadStatusV1`:
|
||||
|
||||
- Alias `NOT_VALIDATED` to:
|
||||
- `SYNCING`
|
||||
- `ACCEPTED`
|
||||
- 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
|
||||
root of that block.
|
||||
|
||||
Let `blocks: Dict[Root, BeaconBlock]` and `block_states: Dict[Root,
|
||||
BeaconState]` be the blocks (and accompanying states) that have been verified
|
||||
either completely or optimistically.
|
||||
|
||||
Let `optimistic_roots: Set[Root]` be the set of `hash_tree_root(block)` for all
|
||||
optimistically imported blocks which have only received a `NOT_VALIDATED` designation
|
||||
from an execution engine (i.e., they are not known to be `INVALIDATED` or `VALID`).
|
||||
|
||||
Let `current_slot: Slot` be `(time - genesis_time) // SECONDS_PER_SLOT` where
|
||||
`time` is the UNIX time according to the local system clock.
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class OptimisticStore(object):
|
||||
optimistic_roots: Set[Root]
|
||||
head_block_root: Root
|
||||
blocks: Dict[Root, BeaconBlock]
|
||||
block_states: Dict[Root, BeaconState]
|
||||
```
|
||||
|
||||
```python
|
||||
def is_optimistic(opt_store: OptimisticStore, block: BeaconBlock) -> bool:
|
||||
return hash_tree_root(block) in opt_store.optimistic_roots
|
||||
```
|
||||
|
||||
```python
|
||||
def latest_verified_ancestor(opt_store: OptimisticStore, block: BeaconBlock) -> BeaconBlock:
|
||||
# It is assumed that the `block` parameter is never an INVALIDATED block.
|
||||
while True:
|
||||
if not is_optimistic(opt_store, block) or block.parent_root == Root():
|
||||
return block
|
||||
block = opt_store.blocks[block.parent_root]
|
||||
```
|
||||
|
||||
```python
|
||||
def is_execution_block(block: BeaconBlock) -> bool:
|
||||
return block.body.execution_payload != ExecutionPayload()
|
||||
```
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Let only a node which returns `is_optimistic(opt_store, head) is True` be an *optimistic
|
||||
node*. Let only a validator on an optimistic node be an *optimistic validator*.
|
||||
|
||||
When this specification only defines behaviour for an optimistic
|
||||
node/validator, but *not* for the non-optimistic case, assume default
|
||||
behaviours without regard for optimistic sync.
|
||||
|
||||
## Mechanisms
|
||||
|
||||
### 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:
|
||||
|
||||
1. The justified checkpoint 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.
|
||||
|
||||
*See [Fork Choice Poisoning](#fork-choice-poisoning) for the motivations behind
|
||||
these conditions.*
|
||||
|
||||
### How to optimistically import blocks
|
||||
|
||||
To optimistically import a block:
|
||||
|
||||
- The [`notify_new_payload`](../specs/bellatrix/beacon-chain.md#notify_new_payload) function MUST return `True` if the execution
|
||||
engine returns `NOT_VALIDATED` or `VALID`. An `INVALIDATED` response MUST return `False`.
|
||||
- The [`validate_merge_block`](../specs/bellatrix/fork-choice.md#validate_merge_block)
|
||||
function MUST NOT raise an assertion if both the
|
||||
`pow_block` and `pow_parent` are unknown to the execution engine.
|
||||
- All other assertions in [`validate_merge_block`](../specs/bellatrix/fork-choice.md#validate_merge_block)
|
||||
(e.g., `TERMINAL_BLOCK_HASH`) MUST prevent an optimistic import.
|
||||
- The parent of the block MUST NOT have an `INVALIDATED` execution payload.
|
||||
|
||||
In addition to this change in validation, the consensus engine MUST track which
|
||||
blocks returned `NOT_VALIDATED` and which returned `VALID` for subsequent processing.
|
||||
|
||||
Optimistically imported blocks MUST pass all verifications included in
|
||||
`process_block` (withstanding the modifications to `notify_new_payload`).
|
||||
|
||||
A consensus engine MUST be able to retrospectively (i.e., after import) modify
|
||||
the status of `NOT_VALIDATED` blocks to be either `VALID` or `INVALIDATED` based upon responses
|
||||
from an execution engine. I.e., perform the following transitions:
|
||||
|
||||
- `NOT_VALIDATED` -> `VALID`
|
||||
- `NOT_VALIDATED` -> `INVALIDATED`
|
||||
|
||||
When a block transitions from `NOT_VALIDATED` -> `VALID`, all *ancestors* of the
|
||||
block MUST also transition from `NOT_VALIDATED` -> `VALID`. Such a block and any previously `NOT_VALIDATED` ancestors are no longer
|
||||
considered "optimistically imported".
|
||||
|
||||
When a block transitions from `NOT_VALIDATED` -> `INVALIDATED`, all *descendants* of the
|
||||
block MUST also transition from `NOT_VALIDATED` -> `INVALIDATED`.
|
||||
|
||||
When a block transitions from the `NOT_VALIDATED` state, it is removed from the set of
|
||||
`opt_store.optimistic_roots`.
|
||||
|
||||
When a "merge block" (i.e. the first block which enables execution in a chain) is declared to be
|
||||
`VALID` by an execution engine (either directly or indirectly), the full
|
||||
[`validate_merge_block`](../specs/bellatrix/fork-choice.md#validate_merge_block)
|
||||
MUST be run against the merge block. If the block
|
||||
fails [`validate_merge_block`](../specs/bellatrix/fork-choice.md#validate_merge_block),
|
||||
the merge block MUST be treated the same as
|
||||
an `INVALIDATED` block (i.e., it and all its descendants are invalidated and
|
||||
removed from the block tree).
|
||||
|
||||
### Execution Engine Errors
|
||||
|
||||
When an execution engine returns an error or fails to respond to a payload
|
||||
validity request for some block, a consensus engine:
|
||||
|
||||
- MUST NOT optimistically import the block.
|
||||
- MUST NOT apply the block to the fork choice store.
|
||||
- MAY queue the block for later processing.
|
||||
|
||||
### Assumptions about Execution Engine Behaviour
|
||||
|
||||
This specification assumes execution engines will only return `NOT_VALIDATED` when
|
||||
there is insufficient information available to make a `VALID` or `INVALIDATED`
|
||||
determination on the given `ExecutionPayload` (e.g., the parent payload is
|
||||
unknown). Specifically, `NOT_VALIDATED` responses should be fork-specific, in that
|
||||
the search for a block on one chain MUST NOT trigger a `NOT_VALIDATED` response for
|
||||
another chain.
|
||||
|
||||
### Re-Orgs
|
||||
|
||||
The consensus engine MUST support any chain reorganisation which does *not*
|
||||
affect the justified checkpoint.
|
||||
|
||||
If the justified checkpoint transitions from `NOT_VALIDATED` -> `INVALIDATED`, a
|
||||
consensus engine MAY choose to alert the user and force the application to
|
||||
exit.
|
||||
|
||||
## Fork Choice
|
||||
|
||||
Consensus engines MUST support removing blocks from fork choice that transition
|
||||
from `NOT_VALIDATED` to `INVALIDATED`. Specifically, a block deemed `INVALIDATED` at any
|
||||
point MUST NOT be included in the canonical chain and the weights from those
|
||||
`INVALIDATED` blocks MUST NOT be applied to any `VALID` or `NOT_VALIDATED` ancestors.
|
||||
|
||||
### Fork Choice Poisoning
|
||||
|
||||
During the merge transition it is possible for an attacker to craft a
|
||||
`BeaconBlock` with an execution payload that references an
|
||||
eternally-unavailable `body.execution_payload.parent_hash` (i.e., the parent
|
||||
hash is random bytes). In rare circumstances, it is possible that an attacker
|
||||
can build atop such a block to trigger justification. If an optimistic node
|
||||
imports this malicious chain, that node will have a "poisoned" fork choice
|
||||
store, such that the node is unable to produce a block that descends from the
|
||||
head (due to the invalid chain of payloads) and the node is unable to produce a
|
||||
block that forks around the head (due to the justification of the malicious
|
||||
chain).
|
||||
|
||||
If an honest chain exists which justifies a higher epoch than the malicious
|
||||
chain, that chain will take precedence and revive any poisoned store. Such a
|
||||
chain, if imported before the malicious chain, will prevent the store from
|
||||
being poisoned. Therefore, the poisoning attack is temporary if >= 2/3rds of
|
||||
the network is honest and non-faulty.
|
||||
|
||||
The `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` parameter assumes that the network
|
||||
will justify a honest chain within some number of slots. With this assumption,
|
||||
it is acceptable to optimistically import transition blocks during the sync
|
||||
process. Since there is an assumption that an honest chain with a higher
|
||||
justified checkpoint exists, any fork choice poisoning will be short-lived and
|
||||
resolved before that node is required to produce a block.
|
||||
|
||||
However, the assumption that the honest, canonical chain will always justify
|
||||
within `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` slots is dubious. Therefore,
|
||||
clients MUST provide the following command line flag to assist with manual
|
||||
disaster recovery:
|
||||
|
||||
- `--safe-slots-to-import-optimistically`: modifies the
|
||||
`SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`.
|
||||
|
||||
## Checkpoint Sync (Weak Subjectivity Sync)
|
||||
|
||||
A consensus engine MAY assume that the `ExecutionPayload` of a block used as an
|
||||
anchor for checkpoint sync is `VALID` without necessarily providing that
|
||||
payload to an execution engine.
|
||||
|
||||
## Validator assignments
|
||||
|
||||
An optimistic node is *not* a full node. It is unable to produce blocks, since
|
||||
an execution engine cannot produce a payload upon an unknown parent. It cannot
|
||||
faithfully attest to the head block of the chain, since it has not fully
|
||||
verified that block.
|
||||
|
||||
### Block Production
|
||||
|
||||
An optimistic validator MUST NOT produce a block (i.e., sign across the
|
||||
`DOMAIN_BEACON_PROPOSER` domain).
|
||||
|
||||
### Attesting
|
||||
|
||||
An optimistic validator MUST NOT participate in attestation (i.e., sign across the
|
||||
`DOMAIN_BEACON_ATTESTER`, `DOMAIN_SELECTION_PROOF` or
|
||||
`DOMAIN_AGGREGATE_AND_PROOF` domains).
|
||||
|
||||
### Participating in Sync Committees
|
||||
|
||||
An optimistic validator MUST NOT participate in sync committees (i.e., sign across the
|
||||
`DOMAIN_SYNC_COMMITTEE`, `DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF` or
|
||||
`DOMAIN_CONTRIBUTION_AND_PROOF` domains).
|
||||
|
||||
## 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/*`
|
||||
|
||||
## Design Decision Rationale
|
||||
|
||||
### Why `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`?
|
||||
|
||||
Nodes can only import an optimistic block if their justified checkpoint is
|
||||
verified or the block is older than `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`.
|
||||
|
||||
These restraints are applied in order to mitigate an attack where a block which
|
||||
enables execution (a *transition block*) can reference a junk parent hash. This
|
||||
makes it impossible for honest nodes to build atop that block. If an attacker
|
||||
exploits a nuance in fork choice `filter_block_tree`, they can, in some rare
|
||||
cases, produce a junk block that out-competes all locally produced blocks for
|
||||
the head. This prevents a node from producing a chain of blocks, therefore
|
||||
breaking liveness.
|
||||
|
||||
Thankfully, if 2/3rds of validators are not poisoned, they can justify an
|
||||
honest chain which will un-poison all other nodes.
|
||||
|
||||
Notably, this attack only exists for optimistic nodes. Nodes which fully verify
|
||||
the transition block will reject a block with a junk parent hash. Therefore,
|
||||
liveness is unaffected if a vast majority of nodes have fully synced execution
|
||||
and consensus clients before and during the transition.
|
||||
|
||||
Given all of this, we can say two things:
|
||||
|
||||
1. **BNs which are following the head during the transition shouldn't
|
||||
optimistically import the transition block.** If 1/3rd of validators
|
||||
optimistically import the poison block, there will be no remaining nodes to
|
||||
justify an honest chain.
|
||||
2. **BNs which are syncing can optimistically import transition blocks.** In
|
||||
this case a justified chain already exists blocks. The poison block would be
|
||||
quickly reverted and would have no affect on liveness.
|
||||
|
||||
Astute readers will notice that (2) contains a glaring assumption about network
|
||||
liveness. This is necessary because a node cannot feasibly ascertain that the
|
||||
transition block is justified without importing that block and risking
|
||||
poisoning. Therefore, we use `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` to say
|
||||
something along the lines of: *"if the transition block is sufficiently old
|
||||
enough, then we can just assume that block is honest or there exists an honest
|
||||
justified chain to out-compete it."*
|
||||
|
||||
Note the use of "feasibly" in the previous paragraph. One can imagine
|
||||
mechanisms to check that a block is justified before importing it. For example,
|
||||
just keep processing blocks without adding them to fork choice. However, there
|
||||
are still edge-cases here (e.g., when to halt and declare there was no
|
||||
justification?) and how to mitigate implementation complexity. At this point,
|
||||
it's important to reflect on the attack and how likely it is to happen. It
|
||||
requires some rather contrived circumstances and it seems very unlikely to
|
||||
occur. Therefore, we need to consider if adding complexity to avoid an
|
||||
unlikely attack increases or decreases our total risk. Presently, it appears
|
||||
that `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` sits in a sweet spot for this
|
||||
trade-off.
|
||||
|
||||
### Transitioning from VALID -> INVALIDATED or INVALIDATED -> VALID
|
||||
|
||||
These operations are purposefully omitted. It is outside of the scope of the
|
||||
specification since it's only possible with a faulty EE.
|
||||
|
||||
Such a scenario requires manual intervention.
|
||||
|
||||
## What about Light Clients?
|
||||
|
||||
An alternative to optimistic sync is to run a light client inside/alongside
|
||||
beacon nodes that mitigates the need for optimistic sync by providing
|
||||
tip-of-chain blocks to the execution engine. However, light clients comes with
|
||||
their own set of complexities. Relying on light clients may also restrict nodes
|
||||
from syncing from genesis, if they so desire.
|
||||
|
||||
A notable thing about optimistic sync is that it's *optional*. Should an
|
||||
implementation decide to go the light-client route, then they can just ignore
|
||||
optimistic sync all together.
|
||||
|
||||
## What if `TERMINAL_BLOCK_HASH` is used?
|
||||
|
||||
If the terminal block hash override is used (i.e., `TERMINAL_BLOCK_HASH !=
|
||||
Hash32()`), the [`validate_merge_block`](../specs/bellatrix/fork-choice.md#validate_merge_block)
|
||||
function will deterministically
|
||||
return `True` or `False`. Whilst it's not *technically* required
|
||||
retrospectively call [`validate_merge_block`](../specs/bellatrix/fork-choice.md#validate_merge_block)
|
||||
on a transition block that
|
||||
matches `TERMINAL_BLOCK_HASH` after an optimistic sync, doing so will have no
|
||||
effect. For simplicity, the optimistic sync specification does not define
|
||||
edge-case behaviour for when `TERMINAL_BLOCK_HASH` is used.
|
|
@ -1 +1 @@
|
|||
1.1.8
|
||||
1.1.9
|
|
@ -14,7 +14,7 @@ def parse_config_vars(conf: Dict[str, Any]) -> Dict[str, Any]:
|
|||
out[k] = [int(item) if item.isdigit() else item for item in v]
|
||||
elif isinstance(v, str) and v.startswith("0x"):
|
||||
out[k] = bytes.fromhex(v[2:])
|
||||
elif k != 'PRESET_BASE':
|
||||
elif k != 'PRESET_BASE' and k != 'CONFIG_NAME':
|
||||
out[k] = int(v)
|
||||
else:
|
||||
out[k] = v
|
||||
|
|
|
@ -24,7 +24,7 @@ def run_sync_committees_progress_test(spec, state):
|
|||
first_sync_committee = state.current_sync_committee.copy()
|
||||
second_sync_committee = state.next_sync_committee.copy()
|
||||
|
||||
current_period = spec.get_current_epoch(state) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD
|
||||
current_period = spec.compute_sync_committee_period(spec.get_current_epoch(state))
|
||||
next_period = current_period + 1
|
||||
next_period_start_epoch = next_period * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD
|
||||
next_period_start_slot = next_period_start_epoch * spec.SLOTS_PER_EPOCH
|
||||
|
|
|
@ -5,38 +5,29 @@ from eth2spec.test.context import (
|
|||
with_presets,
|
||||
with_altair_and_later,
|
||||
)
|
||||
from eth2spec.test.helpers.attestations import next_epoch_with_attestations
|
||||
from eth2spec.test.helpers.attestations import (
|
||||
next_epoch_with_attestations,
|
||||
)
|
||||
from eth2spec.test.helpers.block import (
|
||||
build_empty_block,
|
||||
build_empty_block_for_next_slot,
|
||||
)
|
||||
from eth2spec.test.helpers.constants import MINIMAL
|
||||
from eth2spec.test.helpers.light_client import (
|
||||
get_sync_aggregate,
|
||||
initialize_light_client_store,
|
||||
)
|
||||
from eth2spec.test.helpers.state import (
|
||||
next_slots,
|
||||
state_transition_and_sign_block,
|
||||
)
|
||||
from eth2spec.test.helpers.sync_committee import (
|
||||
compute_aggregate_sync_committee_signature,
|
||||
)
|
||||
from eth2spec.test.helpers.merkle import build_proof
|
||||
|
||||
|
||||
def _initialize_light_client_store(spec, state):
|
||||
return spec.LightClientStore(
|
||||
finalized_header=spec.BeaconBlockHeader(),
|
||||
current_sync_committee=state.current_sync_committee,
|
||||
next_sync_committee=state.next_sync_committee,
|
||||
best_valid_update=None,
|
||||
optimistic_header=spec.BeaconBlockHeader(),
|
||||
previous_max_active_participants=0,
|
||||
current_max_active_participants=0,
|
||||
)
|
||||
|
||||
|
||||
@with_altair_and_later
|
||||
@spec_state_test
|
||||
def test_process_light_client_update_not_timeout(spec, state):
|
||||
store = _initialize_light_client_store(spec, state)
|
||||
store = initialize_light_client_store(spec, state)
|
||||
|
||||
# Block at slot 1 doesn't increase sync committee period, so it won't force update store.finalized_header
|
||||
block = build_empty_block_for_next_slot(spec, state)
|
||||
|
@ -49,19 +40,7 @@ def test_process_light_client_update_not_timeout(spec, state):
|
|||
body_root=signed_block.message.body.hash_tree_root(),
|
||||
)
|
||||
# Sync committee signing the header
|
||||
all_pubkeys = [v.pubkey for v in state.validators]
|
||||
committee = [all_pubkeys.index(pubkey) for pubkey in state.current_sync_committee.pubkeys]
|
||||
sync_committee_bits = [True] * len(committee)
|
||||
sync_committee_signature = compute_aggregate_sync_committee_signature(
|
||||
spec,
|
||||
state,
|
||||
block_header.slot,
|
||||
committee,
|
||||
)
|
||||
sync_committee_aggregate = spec.SyncAggregate(
|
||||
sync_committee_bits=sync_committee_bits,
|
||||
sync_committee_signature=sync_committee_signature,
|
||||
)
|
||||
sync_aggregate = get_sync_aggregate(spec, state, block_header, block_root=None)
|
||||
next_sync_committee_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_INDEX))]
|
||||
|
||||
# Ensure that finality checkpoint is genesis
|
||||
|
@ -76,7 +55,7 @@ def test_process_light_client_update_not_timeout(spec, state):
|
|||
next_sync_committee_branch=next_sync_committee_branch,
|
||||
finalized_header=finality_header,
|
||||
finality_branch=finality_branch,
|
||||
sync_committee_aggregate=sync_committee_aggregate,
|
||||
sync_aggregate=sync_aggregate,
|
||||
fork_version=state.fork.current_version,
|
||||
)
|
||||
|
||||
|
@ -94,12 +73,12 @@ def test_process_light_client_update_not_timeout(spec, state):
|
|||
@spec_state_test
|
||||
@with_presets([MINIMAL], reason="too slow")
|
||||
def test_process_light_client_update_timeout(spec, state):
|
||||
store = _initialize_light_client_store(spec, state)
|
||||
store = initialize_light_client_store(spec, state)
|
||||
|
||||
# Forward to next sync committee period
|
||||
next_slots(spec, state, spec.UPDATE_TIMEOUT)
|
||||
snapshot_period = spec.compute_epoch_at_slot(store.optimistic_header.slot) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD
|
||||
update_period = spec.compute_epoch_at_slot(state.slot) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD
|
||||
snapshot_period = spec.compute_sync_committee_period(spec.compute_epoch_at_slot(store.optimistic_header.slot))
|
||||
update_period = spec.compute_sync_committee_period(spec.compute_epoch_at_slot(state.slot))
|
||||
assert snapshot_period + 1 == update_period
|
||||
|
||||
block = build_empty_block_for_next_slot(spec, state)
|
||||
|
@ -113,20 +92,8 @@ def test_process_light_client_update_timeout(spec, state):
|
|||
)
|
||||
|
||||
# Sync committee signing the finalized_block_header
|
||||
all_pubkeys = [v.pubkey for v in state.validators]
|
||||
committee = [all_pubkeys.index(pubkey) for pubkey in state.current_sync_committee.pubkeys]
|
||||
sync_committee_bits = [True] * len(committee)
|
||||
sync_committee_signature = compute_aggregate_sync_committee_signature(
|
||||
spec,
|
||||
state,
|
||||
block_header.slot,
|
||||
committee,
|
||||
block_root=spec.Root(block_header.hash_tree_root()),
|
||||
)
|
||||
sync_committee_aggregate = spec.SyncAggregate(
|
||||
sync_committee_bits=sync_committee_bits,
|
||||
sync_committee_signature=sync_committee_signature,
|
||||
)
|
||||
sync_aggregate = get_sync_aggregate(
|
||||
spec, state, block_header, block_root=spec.Root(block_header.hash_tree_root()))
|
||||
|
||||
# Sync committee is updated
|
||||
next_sync_committee_branch = build_proof(state.get_backing(), spec.NEXT_SYNC_COMMITTEE_INDEX)
|
||||
|
@ -140,7 +107,7 @@ def test_process_light_client_update_timeout(spec, state):
|
|||
next_sync_committee_branch=next_sync_committee_branch,
|
||||
finalized_header=finality_header,
|
||||
finality_branch=finality_branch,
|
||||
sync_committee_aggregate=sync_committee_aggregate,
|
||||
sync_aggregate=sync_aggregate,
|
||||
fork_version=state.fork.current_version,
|
||||
)
|
||||
|
||||
|
@ -158,7 +125,7 @@ def test_process_light_client_update_timeout(spec, state):
|
|||
@spec_state_test
|
||||
@with_presets([MINIMAL], reason="too slow")
|
||||
def test_process_light_client_update_finality_updated(spec, state):
|
||||
store = _initialize_light_client_store(spec, state)
|
||||
store = initialize_light_client_store(spec, state)
|
||||
|
||||
# Change finality
|
||||
blocks = []
|
||||
|
@ -169,8 +136,8 @@ def test_process_light_client_update_finality_updated(spec, state):
|
|||
# Ensure that finality checkpoint has changed
|
||||
assert state.finalized_checkpoint.epoch == 3
|
||||
# Ensure that it's same period
|
||||
snapshot_period = spec.compute_epoch_at_slot(store.optimistic_header.slot) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD
|
||||
update_period = spec.compute_epoch_at_slot(state.slot) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD
|
||||
snapshot_period = spec.compute_sync_committee_period(spec.compute_epoch_at_slot(store.optimistic_header.slot))
|
||||
update_period = spec.compute_sync_committee_period(spec.compute_epoch_at_slot(state.slot))
|
||||
assert snapshot_period == update_period
|
||||
|
||||
# Updated sync_committee and finality
|
||||
|
@ -191,20 +158,8 @@ def test_process_light_client_update_finality_updated(spec, state):
|
|||
)
|
||||
|
||||
# Sync committee signing the finalized_block_header
|
||||
all_pubkeys = [v.pubkey for v in state.validators]
|
||||
committee = [all_pubkeys.index(pubkey) for pubkey in state.current_sync_committee.pubkeys]
|
||||
sync_committee_bits = [True] * len(committee)
|
||||
sync_committee_signature = compute_aggregate_sync_committee_signature(
|
||||
spec,
|
||||
state,
|
||||
block_header.slot,
|
||||
committee,
|
||||
block_root=spec.Root(block_header.hash_tree_root()),
|
||||
)
|
||||
sync_committee_aggregate = spec.SyncAggregate(
|
||||
sync_committee_bits=sync_committee_bits,
|
||||
sync_committee_signature=sync_committee_signature,
|
||||
)
|
||||
sync_aggregate = get_sync_aggregate(
|
||||
spec, state, block_header, block_root=spec.Root(block_header.hash_tree_root()))
|
||||
|
||||
update = spec.LightClientUpdate(
|
||||
attested_header=block_header,
|
||||
|
@ -212,7 +167,7 @@ def test_process_light_client_update_finality_updated(spec, state):
|
|||
next_sync_committee_branch=next_sync_committee_branch,
|
||||
finalized_header=finalized_block_header,
|
||||
finality_branch=finality_branch,
|
||||
sync_committee_aggregate=sync_committee_aggregate,
|
||||
sync_aggregate=sync_aggregate,
|
||||
fork_version=state.fork.current_version,
|
||||
)
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ def run_execution_payload_processing(spec, state, execution_payload, valid=True,
|
|||
called_new_block = False
|
||||
|
||||
class TestEngine(spec.NoopExecutionEngine):
|
||||
def execute_payload(self, payload) -> bool:
|
||||
def notify_new_payload(self, payload) -> bool:
|
||||
nonlocal called_new_block, execution_valid
|
||||
called_new_block = True
|
||||
assert payload == execution_payload
|
||||
|
@ -153,7 +153,7 @@ def test_bad_random_first_payload(spec, state):
|
|||
|
||||
# execution payload
|
||||
execution_payload = build_empty_execution_payload(spec, state)
|
||||
execution_payload.random = b'\x42' * 32
|
||||
execution_payload.prev_randao = b'\x42' * 32
|
||||
|
||||
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
|
||||
|
||||
|
@ -167,7 +167,7 @@ def test_bad_random_regular_payload(spec, state):
|
|||
|
||||
# execution payload
|
||||
execution_payload = build_empty_execution_payload(spec, state)
|
||||
execution_payload.random = b'\x04' * 32
|
||||
execution_payload.prev_randao = b'\x04' * 32
|
||||
|
||||
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
|
||||
|
||||
|
@ -182,7 +182,7 @@ def test_bad_everything_regular_payload(spec, state):
|
|||
# execution payload
|
||||
execution_payload = build_empty_execution_payload(spec, state)
|
||||
execution_payload.parent_hash = spec.Hash32()
|
||||
execution_payload.random = spec.Bytes32()
|
||||
execution_payload.prev_randao = spec.Bytes32()
|
||||
execution_payload.timestamp = 0
|
||||
|
||||
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
|
||||
|
@ -214,3 +214,35 @@ def test_bad_timestamp_regular_payload(spec, state):
|
|||
execution_payload.timestamp = execution_payload.timestamp + 1
|
||||
|
||||
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
|
||||
|
||||
|
||||
@with_bellatrix_and_later
|
||||
@spec_state_test
|
||||
def test_non_empty_extra_data_first_payload(spec, state):
|
||||
# pre-state
|
||||
state = build_state_with_incomplete_transition(spec, state)
|
||||
next_slot(spec, state)
|
||||
|
||||
# execution payload
|
||||
execution_payload = build_empty_execution_payload(spec, state)
|
||||
execution_payload.extra_data = b'\x45' * 12
|
||||
|
||||
yield from run_execution_payload_processing(spec, state, execution_payload)
|
||||
|
||||
assert state.latest_execution_payload_header.extra_data == execution_payload.extra_data
|
||||
|
||||
|
||||
@with_bellatrix_and_later
|
||||
@spec_state_test
|
||||
def test_non_empty_extra_data_regular_payload(spec, state):
|
||||
# pre-state
|
||||
state = build_state_with_complete_transition(spec, state)
|
||||
next_slot(spec, state)
|
||||
|
||||
# execution payload
|
||||
execution_payload = build_empty_execution_payload(spec, state)
|
||||
execution_payload.extra_data = b'\x45' * 12
|
||||
|
||||
yield from run_execution_payload_processing(spec, state, execution_payload)
|
||||
|
||||
assert state.latest_execution_payload_header.extra_data == execution_payload.extra_data
|
||||
|
|
|
@ -0,0 +1,438 @@
|
|||
"""
|
||||
This module is generated from the ``random`` test generator.
|
||||
Please do not edit this file manually.
|
||||
See the README for that generator for more information.
|
||||
"""
|
||||
|
||||
from eth2spec.test.helpers.constants import BELLATRIX
|
||||
from eth2spec.test.context import (
|
||||
misc_balances_in_default_range_with_many_validators,
|
||||
with_phases,
|
||||
zero_activation_threshold,
|
||||
only_generator,
|
||||
)
|
||||
from eth2spec.test.context import (
|
||||
always_bls,
|
||||
spec_test,
|
||||
with_custom_state,
|
||||
single_phase,
|
||||
)
|
||||
from eth2spec.test.utils.randomized_block_tests import (
|
||||
run_generated_randomized_test,
|
||||
)
|
||||
|
||||
|
||||
@only_generator("randomized test for broad coverage, not point-to-point CI")
|
||||
@with_phases([BELLATRIX])
|
||||
@with_custom_state(
|
||||
balances_fn=misc_balances_in_default_range_with_many_validators,
|
||||
threshold_fn=zero_activation_threshold
|
||||
)
|
||||
@spec_test
|
||||
@single_phase
|
||||
@always_bls
|
||||
def test_randomized_0(spec, state):
|
||||
# scenario as high-level, informal text:
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
# epochs:1,slots:0,with-block:no_block
|
||||
# epochs:0,slots:random_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_bellatrix'} # noqa: E501
|
||||
yield from run_generated_randomized_test(
|
||||
spec,
|
||||
state,
|
||||
scenario,
|
||||
)
|
||||
|
||||
|
||||
@only_generator("randomized test for broad coverage, not point-to-point CI")
|
||||
@with_phases([BELLATRIX])
|
||||
@with_custom_state(
|
||||
balances_fn=misc_balances_in_default_range_with_many_validators,
|
||||
threshold_fn=zero_activation_threshold
|
||||
)
|
||||
@spec_test
|
||||
@single_phase
|
||||
@always_bls
|
||||
def test_randomized_1(spec, state):
|
||||
# scenario as high-level, informal text:
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:1,slots:0,with-block:no_block
|
||||
# epochs:0,slots:random_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_bellatrix'} # noqa: E501
|
||||
yield from run_generated_randomized_test(
|
||||
spec,
|
||||
state,
|
||||
scenario,
|
||||
)
|
||||
|
||||
|
||||
@only_generator("randomized test for broad coverage, not point-to-point CI")
|
||||
@with_phases([BELLATRIX])
|
||||
@with_custom_state(
|
||||
balances_fn=misc_balances_in_default_range_with_many_validators,
|
||||
threshold_fn=zero_activation_threshold
|
||||
)
|
||||
@spec_test
|
||||
@single_phase
|
||||
@always_bls
|
||||
def test_randomized_2(spec, state):
|
||||
# scenario as high-level, informal text:
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:last_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_bellatrix'} # noqa: E501
|
||||
yield from run_generated_randomized_test(
|
||||
spec,
|
||||
state,
|
||||
scenario,
|
||||
)
|
||||
|
||||
|
||||
@only_generator("randomized test for broad coverage, not point-to-point CI")
|
||||
@with_phases([BELLATRIX])
|
||||
@with_custom_state(
|
||||
balances_fn=misc_balances_in_default_range_with_many_validators,
|
||||
threshold_fn=zero_activation_threshold
|
||||
)
|
||||
@spec_test
|
||||
@single_phase
|
||||
@always_bls
|
||||
def test_randomized_3(spec, state):
|
||||
# scenario as high-level, informal text:
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:last_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
# epochs:1,slots:0,with-block:no_block
|
||||
# epochs:0,slots:last_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_bellatrix'} # noqa: E501
|
||||
yield from run_generated_randomized_test(
|
||||
spec,
|
||||
state,
|
||||
scenario,
|
||||
)
|
||||
|
||||
|
||||
@only_generator("randomized test for broad coverage, not point-to-point CI")
|
||||
@with_phases([BELLATRIX])
|
||||
@with_custom_state(
|
||||
balances_fn=misc_balances_in_default_range_with_many_validators,
|
||||
threshold_fn=zero_activation_threshold
|
||||
)
|
||||
@spec_test
|
||||
@single_phase
|
||||
@always_bls
|
||||
def test_randomized_4(spec, state):
|
||||
# scenario as high-level, informal text:
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:1,slots:0,with-block:no_block
|
||||
# epochs:0,slots:last_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
# epochs:1,slots:0,with-block:no_block
|
||||
# epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_bellatrix'} # noqa: E501
|
||||
yield from run_generated_randomized_test(
|
||||
spec,
|
||||
state,
|
||||
scenario,
|
||||
)
|
||||
|
||||
|
||||
@only_generator("randomized test for broad coverage, not point-to-point CI")
|
||||
@with_phases([BELLATRIX])
|
||||
@with_custom_state(
|
||||
balances_fn=misc_balances_in_default_range_with_many_validators,
|
||||
threshold_fn=zero_activation_threshold
|
||||
)
|
||||
@spec_test
|
||||
@single_phase
|
||||
@always_bls
|
||||
def test_randomized_5(spec, state):
|
||||
# scenario as high-level, informal text:
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:random_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:random_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_bellatrix'} # noqa: E501
|
||||
yield from run_generated_randomized_test(
|
||||
spec,
|
||||
state,
|
||||
scenario,
|
||||
)
|
||||
|
||||
|
||||
@only_generator("randomized test for broad coverage, not point-to-point CI")
|
||||
@with_phases([BELLATRIX])
|
||||
@with_custom_state(
|
||||
balances_fn=misc_balances_in_default_range_with_many_validators,
|
||||
threshold_fn=zero_activation_threshold
|
||||
)
|
||||
@spec_test
|
||||
@single_phase
|
||||
@always_bls
|
||||
def test_randomized_6(spec, state):
|
||||
# scenario as high-level, informal text:
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:1,slots:0,with-block:no_block
|
||||
# epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_bellatrix'} # noqa: E501
|
||||
yield from run_generated_randomized_test(
|
||||
spec,
|
||||
state,
|
||||
scenario,
|
||||
)
|
||||
|
||||
|
||||
@only_generator("randomized test for broad coverage, not point-to-point CI")
|
||||
@with_phases([BELLATRIX])
|
||||
@with_custom_state(
|
||||
balances_fn=misc_balances_in_default_range_with_many_validators,
|
||||
threshold_fn=zero_activation_threshold
|
||||
)
|
||||
@spec_test
|
||||
@single_phase
|
||||
@always_bls
|
||||
def test_randomized_7(spec, state):
|
||||
# scenario as high-level, informal text:
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:1,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
# epochs:1,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_bellatrix'} # noqa: E501
|
||||
yield from run_generated_randomized_test(
|
||||
spec,
|
||||
state,
|
||||
scenario,
|
||||
)
|
||||
|
||||
|
||||
@only_generator("randomized test for broad coverage, not point-to-point CI")
|
||||
@with_phases([BELLATRIX])
|
||||
@with_custom_state(
|
||||
balances_fn=misc_balances_in_default_range_with_many_validators,
|
||||
threshold_fn=zero_activation_threshold
|
||||
)
|
||||
@spec_test
|
||||
@single_phase
|
||||
@always_bls
|
||||
def test_randomized_8(spec, state):
|
||||
# scenario as high-level, informal text:
|
||||
# epochs:epochs_until_leak,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
# epochs:1,slots:0,with-block:no_block
|
||||
# epochs:0,slots:random_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_bellatrix'} # noqa: E501
|
||||
yield from run_generated_randomized_test(
|
||||
spec,
|
||||
state,
|
||||
scenario,
|
||||
)
|
||||
|
||||
|
||||
@only_generator("randomized test for broad coverage, not point-to-point CI")
|
||||
@with_phases([BELLATRIX])
|
||||
@with_custom_state(
|
||||
balances_fn=misc_balances_in_default_range_with_many_validators,
|
||||
threshold_fn=zero_activation_threshold
|
||||
)
|
||||
@spec_test
|
||||
@single_phase
|
||||
@always_bls
|
||||
def test_randomized_9(spec, state):
|
||||
# scenario as high-level, informal text:
|
||||
# epochs:epochs_until_leak,slots:0,with-block:no_block
|
||||
# epochs:1,slots:0,with-block:no_block
|
||||
# epochs:0,slots:random_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_bellatrix'} # noqa: E501
|
||||
yield from run_generated_randomized_test(
|
||||
spec,
|
||||
state,
|
||||
scenario,
|
||||
)
|
||||
|
||||
|
||||
@only_generator("randomized test for broad coverage, not point-to-point CI")
|
||||
@with_phases([BELLATRIX])
|
||||
@with_custom_state(
|
||||
balances_fn=misc_balances_in_default_range_with_many_validators,
|
||||
threshold_fn=zero_activation_threshold
|
||||
)
|
||||
@spec_test
|
||||
@single_phase
|
||||
@always_bls
|
||||
def test_randomized_10(spec, state):
|
||||
# scenario as high-level, informal text:
|
||||
# epochs:epochs_until_leak,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:last_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_bellatrix'} # noqa: E501
|
||||
yield from run_generated_randomized_test(
|
||||
spec,
|
||||
state,
|
||||
scenario,
|
||||
)
|
||||
|
||||
|
||||
@only_generator("randomized test for broad coverage, not point-to-point CI")
|
||||
@with_phases([BELLATRIX])
|
||||
@with_custom_state(
|
||||
balances_fn=misc_balances_in_default_range_with_many_validators,
|
||||
threshold_fn=zero_activation_threshold
|
||||
)
|
||||
@spec_test
|
||||
@single_phase
|
||||
@always_bls
|
||||
def test_randomized_11(spec, state):
|
||||
# scenario as high-level, informal text:
|
||||
# epochs:epochs_until_leak,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:last_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
# epochs:1,slots:0,with-block:no_block
|
||||
# epochs:0,slots:last_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_bellatrix'} # noqa: E501
|
||||
yield from run_generated_randomized_test(
|
||||
spec,
|
||||
state,
|
||||
scenario,
|
||||
)
|
||||
|
||||
|
||||
@only_generator("randomized test for broad coverage, not point-to-point CI")
|
||||
@with_phases([BELLATRIX])
|
||||
@with_custom_state(
|
||||
balances_fn=misc_balances_in_default_range_with_many_validators,
|
||||
threshold_fn=zero_activation_threshold
|
||||
)
|
||||
@spec_test
|
||||
@single_phase
|
||||
@always_bls
|
||||
def test_randomized_12(spec, state):
|
||||
# scenario as high-level, informal text:
|
||||
# epochs:epochs_until_leak,slots:0,with-block:no_block
|
||||
# epochs:1,slots:0,with-block:no_block
|
||||
# epochs:0,slots:last_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
# epochs:1,slots:0,with-block:no_block
|
||||
# epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_bellatrix'} # noqa: E501
|
||||
yield from run_generated_randomized_test(
|
||||
spec,
|
||||
state,
|
||||
scenario,
|
||||
)
|
||||
|
||||
|
||||
@only_generator("randomized test for broad coverage, not point-to-point CI")
|
||||
@with_phases([BELLATRIX])
|
||||
@with_custom_state(
|
||||
balances_fn=misc_balances_in_default_range_with_many_validators,
|
||||
threshold_fn=zero_activation_threshold
|
||||
)
|
||||
@spec_test
|
||||
@single_phase
|
||||
@always_bls
|
||||
def test_randomized_13(spec, state):
|
||||
# scenario as high-level, informal text:
|
||||
# epochs:epochs_until_leak,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:random_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:random_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_bellatrix'} # noqa: E501
|
||||
yield from run_generated_randomized_test(
|
||||
spec,
|
||||
state,
|
||||
scenario,
|
||||
)
|
||||
|
||||
|
||||
@only_generator("randomized test for broad coverage, not point-to-point CI")
|
||||
@with_phases([BELLATRIX])
|
||||
@with_custom_state(
|
||||
balances_fn=misc_balances_in_default_range_with_many_validators,
|
||||
threshold_fn=zero_activation_threshold
|
||||
)
|
||||
@spec_test
|
||||
@single_phase
|
||||
@always_bls
|
||||
def test_randomized_14(spec, state):
|
||||
# scenario as high-level, informal text:
|
||||
# epochs:epochs_until_leak,slots:0,with-block:no_block
|
||||
# epochs:1,slots:0,with-block:no_block
|
||||
# epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_bellatrix'} # noqa: E501
|
||||
yield from run_generated_randomized_test(
|
||||
spec,
|
||||
state,
|
||||
scenario,
|
||||
)
|
||||
|
||||
|
||||
@only_generator("randomized test for broad coverage, not point-to-point CI")
|
||||
@with_phases([BELLATRIX])
|
||||
@with_custom_state(
|
||||
balances_fn=misc_balances_in_default_range_with_many_validators,
|
||||
threshold_fn=zero_activation_threshold
|
||||
)
|
||||
@spec_test
|
||||
@single_phase
|
||||
@always_bls
|
||||
def test_randomized_15(spec, state):
|
||||
# scenario as high-level, informal text:
|
||||
# epochs:epochs_until_leak,slots:0,with-block:no_block
|
||||
# epochs:1,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
# epochs:1,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:no_block
|
||||
# epochs:0,slots:0,with-block:random_block_bellatrix
|
||||
scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_bellatrix', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_bellatrix'} # noqa: E501
|
||||
yield from run_generated_randomized_test(
|
||||
spec,
|
||||
state,
|
||||
scenario,
|
||||
)
|
|
@ -103,7 +103,7 @@ def test_prepare_execution_payload(spec, state):
|
|||
|
||||
# 1. Handle `is_merge_complete`
|
||||
if is_merge_complete:
|
||||
state.latest_execution_payload_header = spec.ExecutionPayloadHeader(random=b'\x12' * 32)
|
||||
state.latest_execution_payload_header = spec.ExecutionPayloadHeader(prev_randao=b'\x12' * 32)
|
||||
else:
|
||||
state.latest_execution_payload_header = spec.ExecutionPayloadHeader()
|
||||
|
||||
|
|
|
@ -13,10 +13,10 @@ def build_empty_execution_payload(spec, state, randao_mix=None):
|
|||
parent_hash=latest.block_hash,
|
||||
fee_recipient=spec.ExecutionAddress(),
|
||||
state_root=latest.state_root, # no changes to the state
|
||||
receipt_root=b"no receipts here" + b"\x00" * 16, # TODO: root of empty MPT may be better.
|
||||
receipts_root=b"no receipts here" + b"\x00" * 16, # TODO: root of empty MPT may be better.
|
||||
logs_bloom=spec.ByteVector[spec.BYTES_PER_LOGS_BLOOM](), # TODO: zeroed logs bloom for empty logs ok?
|
||||
block_number=latest.block_number + 1,
|
||||
random=randao_mix,
|
||||
prev_randao=randao_mix,
|
||||
gas_limit=latest.gas_limit, # retain same limit
|
||||
gas_used=0, # empty block, 0 gas
|
||||
timestamp=timestamp,
|
||||
|
@ -36,9 +36,9 @@ def get_execution_payload_header(spec, execution_payload):
|
|||
parent_hash=execution_payload.parent_hash,
|
||||
fee_recipient=execution_payload.fee_recipient,
|
||||
state_root=execution_payload.state_root,
|
||||
receipt_root=execution_payload.receipt_root,
|
||||
receipts_root=execution_payload.receipts_root,
|
||||
logs_bloom=execution_payload.logs_bloom,
|
||||
random=execution_payload.random,
|
||||
prev_randao=execution_payload.prev_randao,
|
||||
block_number=execution_payload.block_number,
|
||||
gas_limit=execution_payload.gas_limit,
|
||||
gas_used=execution_payload.gas_used,
|
||||
|
|
|
@ -33,9 +33,9 @@ def get_sample_genesis_execution_payload_header(spec,
|
|||
parent_hash=b'\x30' * 32,
|
||||
fee_recipient=b'\x42' * 20,
|
||||
state_root=b'\x20' * 32,
|
||||
receipt_root=b'\x20' * 32,
|
||||
receipts_root=b'\x20' * 32,
|
||||
logs_bloom=b'\x35' * spec.BYTES_PER_LOGS_BLOOM,
|
||||
random=eth1_block_hash,
|
||||
prev_randao=eth1_block_hash,
|
||||
block_number=0,
|
||||
gas_limit=30000000,
|
||||
base_fee_per_gas=1000000000,
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
from eth2spec.test.helpers.sync_committee import (
|
||||
compute_aggregate_sync_committee_signature,
|
||||
)
|
||||
|
||||
|
||||
def initialize_light_client_store(spec, state):
|
||||
return spec.LightClientStore(
|
||||
finalized_header=spec.BeaconBlockHeader(),
|
||||
current_sync_committee=state.current_sync_committee,
|
||||
next_sync_committee=state.next_sync_committee,
|
||||
best_valid_update=None,
|
||||
optimistic_header=spec.BeaconBlockHeader(),
|
||||
previous_max_active_participants=0,
|
||||
current_max_active_participants=0,
|
||||
)
|
||||
|
||||
|
||||
def get_sync_aggregate(spec, state, block_header, block_root=None, signature_slot=None):
|
||||
if signature_slot is None:
|
||||
signature_slot = block_header.slot
|
||||
|
||||
all_pubkeys = [v.pubkey for v in state.validators]
|
||||
committee = [all_pubkeys.index(pubkey) for pubkey in state.current_sync_committee.pubkeys]
|
||||
sync_committee_bits = [True] * len(committee)
|
||||
sync_committee_signature = compute_aggregate_sync_committee_signature(
|
||||
spec,
|
||||
state,
|
||||
block_header.slot,
|
||||
committee,
|
||||
block_root=block_root,
|
||||
)
|
||||
return spec.SyncAggregate(
|
||||
sync_committee_bits=sync_committee_bits,
|
||||
sync_committee_signature=sync_committee_signature,
|
||||
)
|
|
@ -55,12 +55,22 @@ def randomize_state(spec, state, stats, exit_fraction=0.1, slash_fraction=0.1):
|
|||
return scenario_state
|
||||
|
||||
|
||||
def randomize_state_altair(spec, state, stats):
|
||||
scenario_state = randomize_state(spec, state, stats, exit_fraction=0.1, slash_fraction=0.1)
|
||||
def randomize_state_altair(spec, state, stats, exit_fraction=0.1, slash_fraction=0.1):
|
||||
scenario_state = randomize_state(spec, state, stats, exit_fraction=exit_fraction, slash_fraction=slash_fraction)
|
||||
randomize_inactivity_scores(spec, state)
|
||||
return scenario_state
|
||||
|
||||
|
||||
def randomize_state_bellatrix(spec, state, stats, exit_fraction=0.1, slash_fraction=0.1):
|
||||
scenario_state = randomize_state_altair(spec,
|
||||
state,
|
||||
stats,
|
||||
exit_fraction=exit_fraction,
|
||||
slash_fraction=slash_fraction)
|
||||
# TODO: randomize execution payload, merge status, etc.
|
||||
return scenario_state
|
||||
|
||||
|
||||
# epochs
|
||||
|
||||
def epochs_until_leak(spec):
|
||||
|
@ -179,6 +189,12 @@ def random_block_altair_with_cycling_sync_committee_participation(spec,
|
|||
return block
|
||||
|
||||
|
||||
def random_block_bellatrix(spec, state, signed_blocks, scenario_state):
|
||||
block = random_block_altair_with_cycling_sync_committee_participation(spec, state, signed_blocks, scenario_state)
|
||||
# TODO: return randomized execution payload
|
||||
return block
|
||||
|
||||
|
||||
# validations
|
||||
|
||||
def no_op_validation(_spec, _state):
|
||||
|
|
|
@ -4,5 +4,7 @@ all:
|
|||
pip3 install -r requirements.txt
|
||||
rm -f ../../core/pyspec/eth2spec/test/phase0/random/test_random.py
|
||||
rm -f ../../core/pyspec/eth2spec/test/altair/random/test_random.py
|
||||
rm -f ../../core/pyspec/eth2spec/test/bellatrix/random/test_random.py
|
||||
python3 generate.py phase0 > ../../core/pyspec/eth2spec/test/phase0/random/test_random.py
|
||||
python3 generate.py altair > ../../core/pyspec/eth2spec/test/altair/random/test_random.py
|
||||
python3 generate.py bellatrix > ../../core/pyspec/eth2spec/test/bellatrix/random/test_random.py
|
||||
|
|
|
@ -19,8 +19,10 @@ from eth2spec.test.utils.randomized_block_tests import (
|
|||
no_op_validation,
|
||||
randomize_state,
|
||||
randomize_state_altair,
|
||||
randomize_state_bellatrix,
|
||||
random_block,
|
||||
random_block_altair_with_cycling_sync_committee_participation,
|
||||
random_block_bellatrix,
|
||||
last_slot_in_epoch,
|
||||
random_slot_in_epoch,
|
||||
penultimate_slot_in_epoch,
|
||||
|
@ -30,7 +32,7 @@ from eth2spec.test.utils.randomized_block_tests import (
|
|||
transition_to_leaking,
|
||||
transition_without_leak,
|
||||
)
|
||||
from eth2spec.test.helpers.constants import PHASE0, ALTAIR
|
||||
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX
|
||||
|
||||
|
||||
# Ensure this many blocks are present in *each* randomized scenario
|
||||
|
@ -254,5 +256,12 @@ if __name__ == "__main__":
|
|||
state_randomizer=randomize_state_altair,
|
||||
block_randomizer=random_block_altair_with_cycling_sync_committee_participation,
|
||||
)
|
||||
if BELLATRIX in sys.argv:
|
||||
did_generate = True
|
||||
run_generate_tests_to_std_out(
|
||||
BELLATRIX,
|
||||
state_randomizer=randomize_state_bellatrix,
|
||||
block_randomizer=random_block_bellatrix,
|
||||
)
|
||||
if not did_generate:
|
||||
warnings.warn("no phase given for test generation")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from eth2spec.test.helpers.constants import PHASE0, ALTAIR
|
||||
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX
|
||||
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators
|
||||
|
||||
|
||||
|
@ -9,10 +9,14 @@ if __name__ == "__main__":
|
|||
altair_mods = {key: 'eth2spec.test.altair.random.test_' + key for key in [
|
||||
'random',
|
||||
]}
|
||||
bellatrix_mods = {key: 'eth2spec.test.bellatrix.random.test_' + key for key in [
|
||||
'random',
|
||||
]}
|
||||
|
||||
all_mods = {
|
||||
PHASE0: phase_0_mods,
|
||||
ALTAIR: altair_mods,
|
||||
BELLATRIX: bellatrix_mods,
|
||||
}
|
||||
|
||||
run_state_test_generators(runner_name="random", all_mods=all_mods)
|
||||
|
|
|
@ -104,17 +104,33 @@ def invalid_cases():
|
|||
RandomizationMode.mode_nil_count,
|
||||
RandomizationMode.mode_one_count,
|
||||
RandomizationMode.mode_max_count]:
|
||||
if len(offsets) != 0:
|
||||
for offset_index in offsets:
|
||||
yield f'{name}_{mode.to_name()}_offset_{offset_index}_plus_one', \
|
||||
invalid_test_case(lambda: mod_offset(
|
||||
b=serialize(container_case_fn(rng, mode, typ)),
|
||||
offset_index=offset_index,
|
||||
change=lambda x: x + 1
|
||||
))
|
||||
yield f'{name}_{mode.to_name()}_offset_{offset_index}_zeroed', \
|
||||
invalid_test_case(lambda: mod_offset(
|
||||
b=serialize(container_case_fn(rng, mode, typ)),
|
||||
offset_index=offset_index,
|
||||
change=lambda x: 0
|
||||
))
|
||||
for index, offset_index in enumerate(offsets):
|
||||
yield f'{name}_{mode.to_name()}_offset_{offset_index}_plus_one', \
|
||||
invalid_test_case(lambda: mod_offset(
|
||||
b=serialize(container_case_fn(rng, mode, typ)),
|
||||
offset_index=offset_index,
|
||||
change=lambda x: x + 1
|
||||
))
|
||||
yield f'{name}_{mode.to_name()}_offset_{offset_index}_zeroed', \
|
||||
invalid_test_case(lambda: mod_offset(
|
||||
b=serialize(container_case_fn(rng, mode, typ)),
|
||||
offset_index=offset_index,
|
||||
change=lambda x: 0
|
||||
))
|
||||
if index == 0:
|
||||
yield f'{name}_{mode.to_name()}_offset_{offset_index}_minus_one', \
|
||||
invalid_test_case(lambda: mod_offset(
|
||||
b=serialize(container_case_fn(rng, mode, typ)),
|
||||
offset_index=offset_index,
|
||||
change=lambda x: x - 1
|
||||
))
|
||||
if mode == RandomizationMode.mode_max_count:
|
||||
serialized = serialize(container_case_fn(rng, mode, typ))
|
||||
serialized = serialized + serialized[:2]
|
||||
yield f'{name}_{mode.to_name()}_last_offset_{offset_index}_overflow', \
|
||||
invalid_test_case(lambda: serialized)
|
||||
if mode == RandomizationMode.mode_one_count:
|
||||
serialized = serialize(container_case_fn(rng, mode, typ))
|
||||
serialized = serialized + serialized[:1]
|
||||
yield f'{name}_{mode.to_name()}_last_offset_{offset_index}_wrong_byte_length', \
|
||||
invalid_test_case(lambda: serialized)
|
||||
|
|
Loading…
Reference in New Issue