Merge branch 'dev' into mkalinin-patch-4

This commit is contained in:
Hsiao-Wei Wang 2022-08-09 21:59:29 +08:00
commit d26232775f
No known key found for this signature in database
GPG Key ID: AE3D6B174F971DE4
7 changed files with 271 additions and 93 deletions

View File

@ -298,8 +298,6 @@ def slash_validator(state: BeaconState,
increase_balance(state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward))
```
## Beacon chain state transition function
### Execution engine

View File

@ -165,7 +165,7 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
assert block.parent_root in store.block_states
# Make a copy of the state to avoid mutability issues
pre_state = copy(store.block_states[block.parent_root])
# Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past.
# Blocks cannot be in the future. If they are, their consideration must be delayed until they are in the past.
assert get_current_slot(store) >= block.slot
# Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor)

View File

@ -269,13 +269,13 @@ class BeaconState(Container):
#### `withdraw`
```python
def withdraw_balance(state: BeaconState, index: ValidatorIndex, amount: Gwei) -> None:
def withdraw_balance(state: BeaconState, validator_index: ValidatorIndex, amount: Gwei) -> None:
# Decrease the validator's balance
decrease_balance(state, index, amount)
decrease_balance(state, validator_index, amount)
# Create a corresponding withdrawal receipt
withdrawal = Withdrawal(
index=state.next_withdrawal_index,
address=ExecutionAddress(state.validators[index].withdrawal_credentials[12:]),
address=ExecutionAddress(state.validators[validator_index].withdrawal_credentials[12:]),
amount=amount,
)
state.next_withdrawal_index = WithdrawalIndex(state.next_withdrawal_index + 1)

View File

@ -406,7 +406,7 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
assert block.parent_root in store.block_states
# Make a copy of the state to avoid mutability issues
pre_state = copy(store.block_states[block.parent_root])
# Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past.
# Blocks cannot be in the future. If they are, their consideration must be delayed until they are in the past.
assert get_current_slot(store) >= block.slot
# Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor)

View File

@ -1,5 +1,37 @@
# Optimistic Sync
## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Introduction](#introduction)
- [Constants](#constants)
- [Helpers](#helpers)
- [Mechanisms](#mechanisms)
- [When to optimistically import blocks](#when-to-optimistically-import-blocks)
- [How to optimistically import blocks](#how-to-optimistically-import-blocks)
- [How to apply `latestValidHash` when payload status is `INVALID`](#how-to-apply-latestvalidhash-when-payload-status-is-invalid)
- [Execution Engine Errors](#execution-engine-errors)
- [Assumptions about Execution Engine Behaviour](#assumptions-about-execution-engine-behaviour)
- [Re-Orgs](#re-orgs)
- [Fork Choice](#fork-choice)
- [Fork Choice Poisoning](#fork-choice-poisoning)
- [Checkpoint Sync (Weak Subjectivity Sync)](#checkpoint-sync-weak-subjectivity-sync)
- [Validator assignments](#validator-assignments)
- [Block Production](#block-production)
- [Attesting](#attesting)
- [Participating in Sync Committees](#participating-in-sync-committees)
- [Ethereum Beacon APIs](#ethereum-beacon-apis)
- [Design Decision Rationale](#design-decision-rationale)
- [Why `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`?](#why-safe_slots_to_import_optimistically)
- [Transitioning from VALID -> INVALIDATED or INVALIDATED -> VALID](#transitioning-from-valid---invalidated-or-invalidated---valid)
- [What about Light Clients?](#what-about-light-clients)
- [What if `TERMINAL_BLOCK_HASH` is used?](#what-if-terminal_block_hash-is-used)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
In order to provide a syncing execution engine with a partial view of the head
@ -163,6 +195,23 @@ 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).
### How to apply `latestValidHash` when payload status is `INVALID`
Processing an `INVALID` payload status depends on the `latestValidHash` parameter.
The general approach is as follows:
1. Consensus engine MUST identify `invalidBlock` as per definition in the table below.
2. `invalidBlock` and all of its descendants MUST be transitioned from `NOT_VALIDATED` to `INVALIDATED`.
| `latestValidHash` | `invalidBlock` |
|:- |:- |
| Execution block hash | The *child* of a block with `body.execution_payload.block_hash == latestValidHash` in the chain containing the block with payload in question |
| `0x00..00` (all zeroes) | The first block with `body.execution_payload != ExecutionPayload()` in the chain containing a block with payload in question |
| `null` | Block with payload in question |
When `latestValidHash` is a meaningful execution block hash but consensus engine
cannot find a block satisfying `body.execution_payload.block_hash == latestValidHash`,
consensus engine SHOULD behave the same as if `latestValidHash` was `null`.
### Execution Engine Errors
When an execution engine returns an error or fails to respond to a payload
@ -350,7 +399,7 @@ specification since it's only possible with a faulty EE.
Such a scenario requires manual intervention.
## What about Light Clients?
### 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
@ -362,7 +411,7 @@ 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?
### 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)

View File

@ -1,3 +1,6 @@
from random import Random
from eth2spec.debug.random_value import get_random_bytes_list
from eth2spec.test.helpers.execution_payload import (
build_empty_execution_payload,
get_execution_payload_header,
@ -46,14 +49,8 @@ def run_execution_payload_processing(spec, state, execution_payload, valid=True,
assert state.latest_execution_payload_header == get_execution_payload_header(spec, execution_payload)
@with_bellatrix_and_later
@spec_state_test
def test_success_first_payload(spec, state):
# pre-state
state = build_state_with_incomplete_transition(spec, state)
def run_success_test(spec, state):
next_slot(spec, state)
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
yield from run_execution_payload_processing(spec, state, execution_payload)
@ -61,12 +58,23 @@ def test_success_first_payload(spec, state):
@with_bellatrix_and_later
@spec_state_test
def test_success_regular_payload(spec, state):
# pre-state
state = build_state_with_complete_transition(spec, state)
next_slot(spec, state)
def test_success_first_payload(spec, state):
state = build_state_with_incomplete_transition(spec, state)
# execution payload
yield from run_success_test(spec, state)
@with_bellatrix_and_later
@spec_state_test
def test_success_regular_payload(spec, state):
state = build_state_with_complete_transition(spec, state)
yield from run_success_test(spec, state)
def run_gap_slot_test(spec, state):
next_slot(spec, state)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
yield from run_execution_payload_processing(spec, state, execution_payload)
@ -75,83 +83,66 @@ def test_success_regular_payload(spec, state):
@with_bellatrix_and_later
@spec_state_test
def test_success_first_payload_with_gap_slot(spec, state):
# pre-state
state = build_state_with_incomplete_transition(spec, state)
next_slot(spec, state)
next_slot(spec, state)
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
yield from run_execution_payload_processing(spec, state, execution_payload)
yield from run_gap_slot_test(spec, state)
@with_bellatrix_and_later
@spec_state_test
def test_success_regular_payload_with_gap_slot(spec, state):
# pre-state
state = build_state_with_complete_transition(spec, state)
next_slot(spec, state)
next_slot(spec, state)
yield from run_gap_slot_test(spec, state)
# execution payload
def run_bad_execution_test(spec, state):
# completely valid payload, but execution itself fails (e.g. block exceeds gas limit)
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
yield from run_execution_payload_processing(spec, state, execution_payload)
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False, execution_valid=False)
@with_bellatrix_and_later
@spec_state_test
def test_bad_execution_first_payload(spec, state):
# completely valid payload, but execution itself fails (e.g. block exceeds gas limit)
# pre-state
state = build_state_with_incomplete_transition(spec, state)
next_slot(spec, state)
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False, execution_valid=False)
yield from run_bad_execution_test(spec, state)
@with_bellatrix_and_later
@spec_state_test
def test_bad_execution_regular_payload(spec, state):
# completely valid payload, but execution itself fails (e.g. block exceeds gas limit)
# pre-state
state = build_state_with_complete_transition(spec, state)
yield from run_bad_execution_test(spec, state)
@with_bellatrix_and_later
@spec_state_test
def test_bad_parent_hash_first_payload(spec, 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.parent_hash = b'\x55' * 32
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False, execution_valid=False)
yield from run_execution_payload_processing(spec, state, execution_payload, valid=True)
@with_bellatrix_and_later
@spec_state_test
def test_bad_parent_hash_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.parent_hash = spec.Hash32()
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
@with_bellatrix_and_later
@spec_state_test
def test_bad_random_first_payload(spec, state):
# pre-state
state = build_state_with_incomplete_transition(spec, state)
def run_bad_prev_randao_test(spec, state):
next_slot(spec, state)
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.prev_randao = b'\x42' * 32
@ -160,26 +151,21 @@ def test_bad_random_first_payload(spec, state):
@with_bellatrix_and_later
@spec_state_test
def test_bad_random_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.prev_randao = b'\x04' * 32
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
def test_bad_prev_randao_first_payload(spec, state):
state = build_state_with_incomplete_transition(spec, state)
yield from run_bad_prev_randao_test(spec, state)
@with_bellatrix_and_later
@spec_state_test
def test_bad_everything_regular_payload(spec, state):
# pre-state
def test_bad_pre_randao_regular_payload(spec, state):
state = build_state_with_complete_transition(spec, state)
yield from run_bad_prev_randao_test(spec, state)
def run_bad_everything_test(spec, state):
next_slot(spec, state)
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.parent_hash = spec.Hash32()
execution_payload.prev_randao = spec.Bytes32()
@ -190,59 +176,198 @@ def test_bad_everything_regular_payload(spec, state):
@with_bellatrix_and_later
@spec_state_test
def test_bad_timestamp_first_payload(spec, state):
# pre-state
def test_bad_everything_first_payload(spec, state):
state = build_state_with_incomplete_transition(spec, state)
yield from run_bad_everything_test(spec, state)
@with_bellatrix_and_later
@spec_state_test
def test_bad_everything_regular_payload(spec, state):
state = build_state_with_complete_transition(spec, state)
yield from run_bad_everything_test(spec, state)
def run_bad_timestamp_test(spec, state, is_future):
next_slot(spec, state)
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.timestamp = execution_payload.timestamp + 1
if is_future:
timestamp = execution_payload.timestamp + 1
else:
timestamp = execution_payload.timestamp - 1
execution_payload.timestamp = timestamp
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
@with_bellatrix_and_later
@spec_state_test
def test_bad_timestamp_regular_payload(spec, state):
# pre-state
def test_future_timestamp_first_payload(spec, state):
state = build_state_with_incomplete_transition(spec, state)
yield from run_bad_timestamp_test(spec, state, is_future=True)
@with_bellatrix_and_later
@spec_state_test
def test_future_timestamp_regular_payload(spec, state):
state = build_state_with_complete_transition(spec, state)
yield from run_bad_timestamp_test(spec, state, is_future=True)
@with_bellatrix_and_later
@spec_state_test
def test_past_timestamp_first_payload(spec, state):
state = build_state_with_incomplete_transition(spec, state)
yield from run_bad_timestamp_test(spec, state, is_future=False)
@with_bellatrix_and_later
@spec_state_test
def test_past_timestamp_regular_payload(spec, state):
state = build_state_with_complete_transition(spec, state)
yield from run_bad_timestamp_test(spec, state, is_future=False)
def run_non_empty_extra_data_test(spec, state):
next_slot(spec, state)
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.timestamp = execution_payload.timestamp + 1
execution_payload.extra_data = b'\x45' * 12
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
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_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
yield from run_non_empty_extra_data_test(spec, state)
@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)
yield from run_non_empty_extra_data_test(spec, state)
def run_non_empty_transactions_test(spec, state):
next_slot(spec, state)
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.extra_data = b'\x45' * 12
num_transactions = 2
execution_payload.transactions = [
spec.Transaction(b'\x99' * 128)
for _ in range(num_transactions)
]
yield from run_execution_payload_processing(spec, state, execution_payload)
assert state.latest_execution_payload_header.transactions_root == execution_payload.transactions.hash_tree_root()
assert state.latest_execution_payload_header.extra_data == execution_payload.extra_data
@with_bellatrix_and_later
@spec_state_test
def test_non_empty_transactions_first_payload(spec, state):
state = build_state_with_incomplete_transition(spec, state)
yield from run_non_empty_extra_data_test(spec, state)
@with_bellatrix_and_later
@spec_state_test
def test_non_empty_transactions_regular_payload(spec, state):
state = build_state_with_complete_transition(spec, state)
yield from run_non_empty_extra_data_test(spec, state)
def run_zero_length_transaction_test(spec, state):
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.transactions = [spec.Transaction(b'')]
assert len(execution_payload.transactions[0]) == 0
yield from run_execution_payload_processing(spec, state, execution_payload)
assert state.latest_execution_payload_header.transactions_root == execution_payload.transactions.hash_tree_root()
@with_bellatrix_and_later
@spec_state_test
def test_zero_length_transaction_first_payload(spec, state):
state = build_state_with_incomplete_transition(spec, state)
yield from run_zero_length_transaction_test(spec, state)
@with_bellatrix_and_later
@spec_state_test
def test_zero_length_transaction_regular_payload(spec, state):
state = build_state_with_complete_transition(spec, state)
yield from run_zero_length_transaction_test(spec, state)
def build_randomized_execution_payload(spec, state, rng):
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.fee_recipient = spec.ExecutionAddress(get_random_bytes_list(rng, 20))
execution_payload.state_root = spec.Bytes32(get_random_bytes_list(rng, 32))
execution_payload.receipts_root = spec.Bytes32(get_random_bytes_list(rng, 32))
execution_payload.logs_bloom = spec.ByteVector[spec.BYTES_PER_LOGS_BLOOM](
get_random_bytes_list(rng, spec.BYTES_PER_LOGS_BLOOM)
)
execution_payload.block_number = rng.randint(0, 10e10)
execution_payload.gas_limit = rng.randint(0, 10e10)
execution_payload.gas_used = rng.randint(0, 10e10)
extra_data_length = rng.randint(0, spec.MAX_EXTRA_DATA_BYTES)
execution_payload.extra_data = spec.ByteList[spec.MAX_EXTRA_DATA_BYTES](
get_random_bytes_list(rng, extra_data_length)
)
execution_payload.base_fee_per_gas = rng.randint(0, 2**256 - 1)
execution_payload.block_hash = spec.Hash32(get_random_bytes_list(rng, 32))
num_transactions = rng.randint(0, 100)
execution_payload.transactions = [
spec.Transaction(get_random_bytes_list(rng, rng.randint(0, 1000)))
for _ in range(num_transactions)
]
return execution_payload
def run_randomized_non_validated_execution_fields_test(spec, state, execution_valid=True, rng=Random(5555)):
next_slot(spec, state)
execution_payload = build_randomized_execution_payload(spec, state, rng)
yield from run_execution_payload_processing(
spec, state,
execution_payload,
valid=execution_valid, execution_valid=execution_valid
)
@with_bellatrix_and_later
@spec_state_test
def test_randomized_non_validated_execution_fields_first_payload__valid(spec, state):
state = build_state_with_incomplete_transition(spec, state)
yield from run_randomized_non_validated_execution_fields_test(spec, state)
@with_bellatrix_and_later
@spec_state_test
def test_randomized_non_validated_execution_fields_regular_payload__valid(spec, state):
state = build_state_with_complete_transition(spec, state)
yield from run_randomized_non_validated_execution_fields_test(spec, state)
@with_bellatrix_and_later
@spec_state_test
def test_randomized_non_validated_execution_fields_first_payload__invalid(spec, state):
state = build_state_with_incomplete_transition(spec, state)
yield from run_randomized_non_validated_execution_fields_test(spec, state, execution_valid=False)
@with_bellatrix_and_later
@spec_state_test
def test_randomized_non_validated_execution_fields_regular_payload__invalid(spec, state):
state = build_state_with_complete_transition(spec, state)
yield from run_randomized_non_validated_execution_fields_test(spec, state, execution_valid=False)

View File

@ -61,14 +61,20 @@ def get_execution_payload_header(spec, execution_payload):
def build_state_with_incomplete_transition(spec, state):
return build_state_with_execution_payload_header(spec, state, spec.ExecutionPayloadHeader())
state = build_state_with_execution_payload_header(spec, state, spec.ExecutionPayloadHeader())
assert not spec.is_merge_transition_complete(state)
return state
def build_state_with_complete_transition(spec, state):
pre_state_payload = build_empty_execution_payload(spec, state)
payload_header = get_execution_payload_header(spec, pre_state_payload)
return build_state_with_execution_payload_header(spec, state, payload_header)
state = build_state_with_execution_payload_header(spec, state, payload_header)
assert spec.is_merge_transition_complete(state)
return state
def build_state_with_execution_payload_header(spec, state, execution_payload_header):