Merge branch 'dev'

This commit is contained in:
Danny Ryan 2021-10-15 11:10:40 -06:00
commit 0eb3a865df
No known key found for this signature in database
GPG Key ID: 2765A792E42CE07A
15 changed files with 971 additions and 156 deletions

View File

@ -527,9 +527,6 @@ class NoopExecutionEngine(ExecutionEngine):
def execute_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool:
return True
def notify_consensus_validated(self: ExecutionEngine, block_hash: Hash32, valid: bool) -> None:
pass
def notify_forkchoice_updated(self: ExecutionEngine, head_block_hash: Hash32, finalized_block_hash: Hash32) -> None:
pass

View File

@ -31,7 +31,6 @@
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
- [Execution engine](#execution-engine)
- [`execute_payload`](#execute_payload)
- [`notify_consensus_validated`](#notify_consensus_validated)
- [Block processing](#block-processing)
- [Execution payload processing](#execution-payload-processing)
- [`is_valid_gas_limit`](#is_valid_gas_limit)
@ -145,8 +144,6 @@ class BeaconState(Container):
#### `ExecutionPayload`
*Note*: The `base_fee_per_gas` field is serialized in little-endian.
```python
class ExecutionPayload(Container):
# Execution block header fields
@ -161,7 +158,7 @@ class ExecutionPayload(Container):
gas_used: uint64
timestamp: uint64
extra_data: ByteList[MAX_EXTRA_DATA_BYTES]
base_fee_per_gas: Bytes32 # base fee introduced in EIP-1559, little-endian serialized
base_fee_per_gas: uint256
# Extra payload fields
block_hash: Hash32 # Hash of execution block
transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD]
@ -183,7 +180,7 @@ class ExecutionPayloadHeader(Container):
gas_used: uint64
timestamp: uint64
extra_data: ByteList[MAX_EXTRA_DATA_BYTES]
base_fee_per_gas: Bytes32
base_fee_per_gas: uint256
# Extra payload fields
block_hash: Hash32 # Hash of execution block
transactions_root: Root
@ -234,13 +231,11 @@ The implementation-dependent `ExecutionEngine` protocol encapsulates the executi
* 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 function `self.notify_consensus_validated` which signals that the beacon block containing the execution payload
is valid with respect to the consensus rule set
*Note*: `execute_payload` and `notify_consensus_validated` are functions accessed through the `EXECUTION_ENGINE` module which instantiates the `ExecutionEngine` protocol.
*Note*: `execute_payload` is a function accessed through the `EXECUTION_ENGINE` module which instantiates the `ExecutionEngine` protocol.
The body of each of these functions is implementation dependent.
The Engine API may be used to implement them with an external execution engine.
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`
@ -252,20 +247,6 @@ def execute_payload(self: ExecutionEngine, execution_payload: ExecutionPayload)
...
```
#### `notify_consensus_validated`
```python
def notify_consensus_validated(self: ExecutionEngine, block_hash: Hash32, valid: bool) -> None:
...
```
The inputs to this function depend on the result of the state transition. A call to `notify_consensus_validated` must be made after the [`state_transition`](../phase0/beacon-chain.md#beacon-chain-state-transition-function) function finishes. The value of the `valid` parameter must be set as follows:
* `True` if `state_transition` function call succeeds
* `False` if `state_transition` function call fails
*Note*: The call of the `notify_consensus_validated` function with `valid = True` maps on the `POS_CONSENSUS_VALIDATED` event defined in the [EIP-3675](https://eips.ethereum.org/EIPS/eip-3675#definitions).
### Block processing
*Note*: The call to the `process_execution_payload` must happen before the call to the `process_randao` as the former depends on the `randao_mix` computed with the reveal of the previous block.

View File

@ -16,7 +16,7 @@ This document specifies configurable settings that clients must implement for th
### Override terminal total difficulty
To coordinate manual overrides to [`TERMINAL_TOTAL_DIFFICULTY`](./beacon-chain.md#Transition-settings) parameter, clients must provide `--terminal-total-difficulty-override` as a configurable setting. The value provided by this setting must take precedence over pre-configured `TERMINAL_TOTAL_DIFFICULTY` parameter.
To coordinate manual overrides to [`TERMINAL_TOTAL_DIFFICULTY`](./beacon-chain.md#Transition-settings) parameter, clients must provide `--terminal-total-difficulty-override` as a configurable setting. The value provided by this setting must take precedence over pre-configured `TERMINAL_TOTAL_DIFFICULTY` parameter. Clients should accept the setting as a decimal value (i.e., *not* hexadecimal).
Except under exceptional scenarios, this setting is expected to not be used. Sufficient warning to the user about this exceptional configurable setting should be provided.

View File

@ -1 +1 @@
1.1.2
1.1.3

View File

@ -1,7 +1,10 @@
from random import Random
from eth2spec.test.context import spec_state_test, with_altair_and_later
from eth2spec.test.helpers.inactivity_scores import randomize_inactivity_scores, zero_inactivity_scores
from eth2spec.test.helpers.inactivity_scores import (
randomize_inactivity_scores,
zero_inactivity_scores,
)
from eth2spec.test.helpers.state import (
next_epoch,
next_epoch_via_block,

View File

@ -0,0 +1,206 @@
import random
from eth2spec.test.context import (
MINIMAL,
fork_transition_test,
with_presets,
)
from eth2spec.test.helpers.constants import PHASE0, ALTAIR
from eth2spec.test.helpers.fork_transition import (
do_altair_fork,
transition_until_fork,
transition_to_next_epoch_and_append_blocks,
)
from eth2spec.test.helpers.random import (
exit_random_validators,
set_some_activations,
set_some_new_deposits,
)
#
# Exit
#
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
@with_presets([MINIMAL],
reason="only test with enough validators such that at least one exited index is not in sync committee")
def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag):
"""
1/4 validators initiated voluntary exit before the fork,
and are exiting but still active *after* the fork transition.
"""
exited_indices = exit_random_validators(
spec,
state,
rng=random.Random(5566),
fraction=0.25,
exit_epoch=10,
from_epoch=spec.get_current_epoch(state),
)
transition_until_fork(spec, state, fork_epoch)
# check pre state
assert len(exited_indices) > 0
for index in exited_indices:
validator = state.validators[index]
assert not validator.slashed
assert fork_epoch < validator.exit_epoch < spec.FAR_FUTURE_EPOCH
assert spec.is_active_validator(validator, spec.get_current_epoch(state))
assert not spec.is_in_inactivity_leak(state)
assert spec.get_current_epoch(state) < fork_epoch
yield "pre", state
# irregular state transition to handle fork:
blocks = []
state, block = do_altair_fork(state, spec, post_spec, fork_epoch)
blocks.append(post_tag(block))
# ensure that some of the current sync committee members are exiting
exited_pubkeys = [state.validators[index].pubkey for index in exited_indices]
assert any(set(exited_pubkeys).intersection(list(state.current_sync_committee.pubkeys)))
assert any(set(exited_pubkeys).difference(list(state.current_sync_committee.pubkeys)))
# continue regular state transition with new spec into next epoch
transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True)
# check state
for index in exited_indices:
validator = state.validators[index]
assert not validator.slashed
assert post_spec.is_active_validator(validator, post_spec.get_current_epoch(state))
assert not post_spec.is_in_inactivity_leak(state)
yield "blocks", blocks
yield "post", state
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
def test_transition_with_one_fourth_exiting_validators_exit_at_fork(state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag):
"""
1/4 validators initiated voluntary exit before the fork,
and being exited and inactive *right after* the fork transition.
"""
exited_indices = exit_random_validators(
spec,
state,
rng=random.Random(5566),
fraction=0.25,
exit_epoch=fork_epoch,
from_epoch=spec.get_current_epoch(state),
)
transition_until_fork(spec, state, fork_epoch)
# check pre state
assert len(exited_indices) > 0
for index in exited_indices:
validator = state.validators[index]
assert not validator.slashed
assert fork_epoch == validator.exit_epoch < spec.FAR_FUTURE_EPOCH
assert spec.is_active_validator(validator, spec.get_current_epoch(state))
assert not spec.is_in_inactivity_leak(state)
assert spec.get_current_epoch(state) < fork_epoch
yield "pre", state
# irregular state transition to handle fork:
blocks = []
state, block = do_altair_fork(state, spec, post_spec, fork_epoch)
blocks.append(post_tag(block))
# check post transition state
for index in exited_indices:
validator = state.validators[index]
assert not validator.slashed
assert not post_spec.is_active_validator(validator, post_spec.get_current_epoch(state))
assert not post_spec.is_in_inactivity_leak(state)
# ensure that none of the current sync committee members are exited validators
exited_pubkeys = [state.validators[index].pubkey for index in exited_indices]
assert not any(set(exited_pubkeys).intersection(list(state.current_sync_committee.pubkeys)))
# continue regular state transition with new spec into next epoch
transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True)
yield "blocks", blocks
yield "post", state
#
# Activation
#
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Create some deposits before the transition
"""
transition_until_fork(spec, state, fork_epoch)
deposited_indices = set_some_new_deposits(spec, state, rng=random.Random(5566))
assert spec.get_current_epoch(state) < fork_epoch
assert len(deposited_indices) > 0
for validator_index in deposited_indices:
assert not spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state))
yield "pre", state
# irregular state transition to handle fork:
blocks = []
state, block = do_altair_fork(state, spec, post_spec, fork_epoch)
blocks.append(post_tag(block))
# continue regular state transition with new spec into next epoch
transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True)
yield "blocks", blocks
yield "post", state
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
def test_transition_with_activation_at_fork_epoch(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Create some deposits before the transition
"""
transition_until_fork(spec, state, fork_epoch)
selected_indices = set_some_activations(spec, state, rng=random.Random(5566), activation_epoch=fork_epoch)
assert spec.get_current_epoch(state) < fork_epoch
assert len(selected_indices) > 0
for validator_index in selected_indices:
validator = state.validators[validator_index]
assert not spec.is_active_validator(validator, spec.get_current_epoch(state))
assert validator.activation_epoch == fork_epoch
yield "pre", state
# irregular state transition to handle fork:
blocks = []
state, block = do_altair_fork(state, spec, post_spec, fork_epoch)
blocks.append(post_tag(block))
# continue regular state transition with new spec into next epoch
transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True)
# now they are active
for validator_index in selected_indices:
validator = state.validators[validator_index]
assert post_spec.is_active_validator(validator, post_spec.get_current_epoch(state))
yield "blocks", blocks
yield "post", state

View File

@ -0,0 +1,63 @@
from eth2spec.test.context import fork_transition_test
from eth2spec.test.helpers.constants import PHASE0, ALTAIR
from eth2spec.test.helpers.fork_transition import (
do_altair_fork,
transition_until_fork,
transition_to_next_epoch_and_append_blocks,
)
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=7)
def test_transition_with_leaking_pre_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2).
The leaking starts before the fork transition in this case.
"""
transition_until_fork(spec, state, fork_epoch)
assert spec.is_in_inactivity_leak(state)
assert spec.get_current_epoch(state) < fork_epoch
yield "pre", state
# irregular state transition to handle fork:
blocks = []
state, block = do_altair_fork(state, spec, post_spec, fork_epoch)
blocks.append(post_tag(block))
# check post transition state
assert spec.is_in_inactivity_leak(state)
# continue regular state transition with new spec into next epoch
transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True)
yield "blocks", blocks
yield "post", state
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=6)
def test_transition_with_leaking_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2).
The leaking starts at the fork transition in this case.
"""
transition_until_fork(spec, state, fork_epoch)
assert not spec.is_in_inactivity_leak(state)
assert spec.get_current_epoch(state) < fork_epoch
yield "pre", state
# irregular state transition to handle fork:
blocks = []
state, block = do_altair_fork(state, spec, post_spec, fork_epoch)
blocks.append(post_tag(block))
# check post transition state
assert spec.is_in_inactivity_leak(state)
# continue regular state transition with new spec into next epoch
transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True)
yield "blocks", blocks
yield "post", state

View File

@ -0,0 +1,176 @@
from eth2spec.test.context import (
always_bls,
fork_transition_test,
)
from eth2spec.test.helpers.constants import PHASE0, ALTAIR
from eth2spec.test.helpers.fork_transition import (
OperationType,
run_transition_with_operation,
)
#
# PROPOSER_SLASHING
#
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
@always_bls
def test_transition_with_proposer_slashing_right_after_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Create an attester slashing right *after* the transition
"""
yield from run_transition_with_operation(
state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
operation_type=OperationType.PROPOSER_SLASHING,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH,
)
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
@always_bls
def test_transition_with_proposer_slashing_right_before_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Create an attester slashing right *before* the transition
"""
yield from run_transition_with_operation(
state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
operation_type=OperationType.PROPOSER_SLASHING,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1,
)
#
# ATTESTER_SLASHING
#
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
@always_bls
def test_transition_with_attester_slashing_right_after_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Create an attester slashing right *after* the transition
"""
yield from run_transition_with_operation(
state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
operation_type=OperationType.ATTESTER_SLASHING,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH,
)
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
@always_bls
def test_transition_with_attester_slashing_right_before_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Create an attester slashing right *after* the transition
"""
yield from run_transition_with_operation(
state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
operation_type=OperationType.ATTESTER_SLASHING,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1,
)
#
# DEPOSIT
#
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
def test_transition_with_deposit_right_after_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Create a deposit right *after* the transition
"""
yield from run_transition_with_operation(
state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
operation_type=OperationType.DEPOSIT,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH,
)
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
def test_transition_with_deposit_right_before_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Create a deposit right *before* the transition
"""
yield from run_transition_with_operation(
state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
operation_type=OperationType.DEPOSIT,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1,
)
#
# VOLUNTARY_EXIT
#
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=260)
def test_transition_with_voluntary_exit_right_after_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Create a voluntary exit right *after* the transition.
fork_epoch=260 because mainnet `SHARD_COMMITTEE_PERIOD` is 256 epochs.
"""
# Fast forward to the future epoch so that validator can do voluntary exit
state.slot = spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
yield from run_transition_with_operation(
state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
operation_type=OperationType.VOLUNTARY_EXIT,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH,
)
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=260)
def test_transition_with_voluntary_exit_right_before_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Create a voluntary exit right *before* the transition.
fork_epoch=260 because mainnet `SHARD_COMMITTEE_PERIOD` is 256 epochs.
"""
# Fast forward to the future epoch so that validator can do voluntary exit
state.slot = spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
yield from run_transition_with_operation(
state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
operation_type=OperationType.VOLUNTARY_EXIT,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1,
)

View File

@ -0,0 +1,73 @@
import random
from eth2spec.test.context import (
MINIMAL,
fork_transition_test,
with_presets,
)
from eth2spec.test.helpers.constants import PHASE0, ALTAIR
from eth2spec.test.helpers.fork_transition import (
do_altair_fork,
transition_to_next_epoch_and_append_blocks,
transition_until_fork,
)
from eth2spec.test.helpers.random import (
slash_random_validators,
)
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=1)
@with_presets([MINIMAL],
reason="only test with enough validators such that at least one exited index is not in sync committee")
def test_transition_with_one_fourth_slashed_active_validators_pre_fork(state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag):
"""
1/4 validators are slashed but still active at the fork transition.
"""
# slash 1/4 validators
slashed_indices = slash_random_validators(spec, state, rng=random.Random(5566), fraction=0.25)
assert len(slashed_indices) > 0
# check if some validators are slashed but still active
for validator_index in slashed_indices:
validator = state.validators[validator_index]
assert validator.slashed
assert spec.is_active_validator(validator, spec.get_current_epoch(state))
assert not spec.is_in_inactivity_leak(state)
transition_until_fork(spec, state, fork_epoch)
assert spec.get_current_epoch(state) < fork_epoch
yield "pre", state
# irregular state transition to handle fork:
state, _ = do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False)
# ensure that some of the current sync committee members are slashed
slashed_pubkeys = [state.validators[index].pubkey for index in slashed_indices]
assert any(set(slashed_pubkeys).intersection(list(state.current_sync_committee.pubkeys)))
assert any(set(slashed_pubkeys).difference(list(state.current_sync_committee.pubkeys)))
# continue regular state transition with new spec into next epoch
# since the proposer might have been slashed, here we only create blocks with non-slashed proposers
blocks = []
transition_to_next_epoch_and_append_blocks(
post_spec,
state,
post_tag,
blocks,
only_last_block=True,
ignoring_proposers=slashed_indices,
)
# check post state
for validator in state.validators:
assert post_spec.is_active_validator(validator, post_spec.get_current_epoch(state))
assert not post_spec.is_in_inactivity_leak(state)
yield "blocks", blocks
yield "post", state

View File

@ -1,83 +1,18 @@
import random
from eth2spec.test.context import fork_transition_test
from eth2spec.test.helpers.constants import PHASE0, ALTAIR
from eth2spec.test.helpers.state import state_transition_and_sign_block, next_slot, next_epoch_via_signed_block
from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_empty_block, sign_block
from eth2spec.test.helpers.state import (
next_epoch_via_signed_block,
)
from eth2spec.test.helpers.attestations import next_slots_with_attestations
def _state_transition_and_sign_block_at_slot(spec, state):
"""
Cribbed from ``transition_unsigned_block`` helper
where the early parts of the state transition have already
been applied to ``state``.
Used to produce a block during an irregular state transition.
"""
block = build_empty_block(spec, state)
assert state.latest_block_header.slot < block.slot
assert state.slot == block.slot
spec.process_block(state, block)
block.state_root = state.hash_tree_root()
return sign_block(spec, state, block)
def _all_blocks(_):
return True
def _skip_slots(*slots):
"""
Skip making a block if its slot is
passed as an argument to this filter
"""
def f(state_at_prior_slot):
return state_at_prior_slot.slot + 1 not in slots
return f
def _no_blocks(_):
return False
def _only_at(slot):
"""
Only produce a block if its slot is ``slot``.
"""
def f(state_at_prior_slot):
return state_at_prior_slot.slot + 1 == slot
return f
def _state_transition_across_slots(spec, state, to_slot, block_filter=_all_blocks):
assert state.slot < to_slot
while state.slot < to_slot:
should_make_block = block_filter(state)
if should_make_block:
block = build_empty_block_for_next_slot(spec, state)
signed_block = state_transition_and_sign_block(spec, state, block)
yield signed_block
else:
next_slot(spec, state)
def _do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True):
spec.process_slots(state, state.slot + 1)
assert state.slot % spec.SLOTS_PER_EPOCH == 0
assert spec.get_current_epoch(state) == fork_epoch
state = post_spec.upgrade_to_altair(state)
assert state.fork.epoch == fork_epoch
assert state.fork.previous_version == post_spec.config.GENESIS_FORK_VERSION
assert state.fork.current_version == post_spec.config.ALTAIR_FORK_VERSION
if with_block:
return state, _state_transition_and_sign_block_at_slot(post_spec, state)
else:
return state, None
from eth2spec.test.helpers.fork_transition import (
do_altair_fork,
no_blocks,
only_at,
skip_slots,
state_transition_across_slots,
transition_to_next_epoch_and_append_blocks,
)
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
@ -95,19 +30,15 @@ def test_normal_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag
blocks = []
blocks.extend([
pre_tag(block) for block in
_state_transition_across_slots(spec, state, to_slot)
state_transition_across_slots(spec, state, to_slot)
])
# irregular state transition to handle fork:
state, block = _do_altair_fork(state, spec, post_spec, fork_epoch)
state, block = do_altair_fork(state, spec, post_spec, fork_epoch)
blocks.append(post_tag(block))
# continue regular state transition with new spec into next epoch
to_slot = post_spec.SLOTS_PER_EPOCH + state.slot
blocks.extend([
post_tag(block) for block in
_state_transition_across_slots(post_spec, state, to_slot)
])
transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks)
assert state.slot % post_spec.SLOTS_PER_EPOCH == 0
assert post_spec.get_current_epoch(state) == fork_epoch + 1
@ -136,18 +67,14 @@ def test_transition_missing_first_post_block(state, fork_epoch, spec, post_spec,
blocks = []
blocks.extend([
pre_tag(block) for block in
_state_transition_across_slots(spec, state, to_slot)
state_transition_across_slots(spec, state, to_slot)
])
# irregular state transition to handle fork:
state, _ = _do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False)
state, _ = do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False)
# continue regular state transition with new spec into next epoch
to_slot = post_spec.SLOTS_PER_EPOCH + state.slot
blocks.extend([
post_tag(block) for block in
_state_transition_across_slots(post_spec, state, to_slot)
])
transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks)
assert state.slot % post_spec.SLOTS_PER_EPOCH == 0
assert post_spec.get_current_epoch(state) == fork_epoch + 1
@ -178,19 +105,15 @@ def test_transition_missing_last_pre_fork_block(state, fork_epoch, spec, post_sp
blocks = []
blocks.extend([
pre_tag(block) for block in
_state_transition_across_slots(spec, state, to_slot, block_filter=_skip_slots(last_slot_of_pre_fork))
state_transition_across_slots(spec, state, to_slot, block_filter=skip_slots(last_slot_of_pre_fork))
])
# irregular state transition to handle fork:
state, block = _do_altair_fork(state, spec, post_spec, fork_epoch)
state, block = do_altair_fork(state, spec, post_spec, fork_epoch)
blocks.append(post_tag(block))
# continue regular state transition with new spec into next epoch
to_slot = post_spec.SLOTS_PER_EPOCH + state.slot
blocks.extend([
post_tag(block) for block in
_state_transition_across_slots(post_spec, state, to_slot)
])
transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks)
assert state.slot % post_spec.SLOTS_PER_EPOCH == 0
assert post_spec.get_current_epoch(state) == fork_epoch + 1
@ -221,18 +144,18 @@ def test_transition_only_blocks_post_fork(state, fork_epoch, spec, post_spec, pr
blocks = []
blocks.extend([
pre_tag(block) for block in
_state_transition_across_slots(spec, state, to_slot, block_filter=_no_blocks)
state_transition_across_slots(spec, state, to_slot, block_filter=no_blocks)
])
# irregular state transition to handle fork:
state, _ = _do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False)
state, _ = do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False)
# continue regular state transition with new spec into next epoch
to_slot = post_spec.SLOTS_PER_EPOCH + state.slot
last_slot = (fork_epoch + 1) * post_spec.SLOTS_PER_EPOCH
blocks.extend([
post_tag(block) for block in
_state_transition_across_slots(post_spec, state, to_slot, block_filter=_only_at(last_slot))
state_transition_across_slots(post_spec, state, to_slot, block_filter=only_at(last_slot))
])
assert state.slot % post_spec.SLOTS_PER_EPOCH == 0
@ -292,7 +215,7 @@ def _run_transition_test_with_attestations(state,
assert (state.slot + 1) % spec.SLOTS_PER_EPOCH == 0
# irregular state transition to handle fork:
state, block = _do_altair_fork(state, spec, post_spec, fork_epoch)
state, block = do_altair_fork(state, spec, post_spec, fork_epoch)
blocks.append(post_tag(block))
# continue regular state transition with new spec into next epoch
@ -405,11 +328,11 @@ def test_transition_with_no_attestations_until_after_fork(state, fork_epoch, spe
blocks = []
blocks.extend([
pre_tag(block) for block in
_state_transition_across_slots(spec, state, to_slot)
state_transition_across_slots(spec, state, to_slot)
])
# irregular state transition to handle fork:
state, block = _do_altair_fork(state, spec, post_spec, fork_epoch)
state, block = do_altair_fork(state, spec, post_spec, fork_epoch)
blocks.append(post_tag(block))
# continue regular state transition but add attestations

View File

@ -0,0 +1,335 @@
from enum import Enum, auto
from eth2spec.test.helpers.attester_slashings import (
get_valid_attester_slashing_by_indices,
)
from eth2spec.test.helpers.attestations import next_slots_with_attestations
from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot,
build_empty_block,
sign_block,
)
from eth2spec.test.helpers.deposits import (
prepare_state_and_deposit,
)
from eth2spec.test.helpers.proposer_slashings import (
get_valid_proposer_slashing,
)
from eth2spec.test.helpers.state import (
next_slot,
state_transition_and_sign_block,
transition_to,
)
from eth2spec.test.helpers.voluntary_exits import (
prepare_signed_exits,
)
class OperationType(Enum):
PROPOSER_SLASHING = auto()
ATTESTER_SLASHING = auto()
DEPOSIT = auto()
VOLUNTARY_EXIT = auto()
def _set_operations_by_dict(block, operation_dict):
for key, value in operation_dict.items():
setattr(block.body, key, value)
def _state_transition_and_sign_block_at_slot(spec,
state,
operation_dict=None):
"""
Cribbed from ``transition_unsigned_block`` helper
where the early parts of the state transition have already
been applied to ``state``.
Used to produce a block during an irregular state transition.
The optional `operation_dict` is a dict of {'<BeaconBlockBody field>': <value>}.
This is used for assigning the block operations.
p.s. we can't just pass `body` and assign it because randao_reveal and eth1_data was set in `build_empty_block`
Thus use dict to pass operations.
"""
block = build_empty_block(spec, state)
if operation_dict:
_set_operations_by_dict(block, operation_dict)
assert state.latest_block_header.slot < block.slot
assert state.slot == block.slot
spec.process_block(state, block)
block.state_root = state.hash_tree_root()
return sign_block(spec, state, block)
def _all_blocks(_):
return True
def skip_slots(*slots):
"""
Skip making a block if its slot is
passed as an argument to this filter
"""
def f(state_at_prior_slot):
return state_at_prior_slot.slot + 1 not in slots
return f
def no_blocks(_):
return False
def only_at(slot):
"""
Only produce a block if its slot is ``slot``.
"""
def f(state_at_prior_slot):
return state_at_prior_slot.slot + 1 == slot
return f
def state_transition_across_slots(spec, state, to_slot, block_filter=_all_blocks):
assert state.slot < to_slot
while state.slot < to_slot:
should_make_block = block_filter(state)
if should_make_block:
block = build_empty_block_for_next_slot(spec, state)
signed_block = state_transition_and_sign_block(spec, state, block)
yield signed_block
else:
next_slot(spec, state)
def state_transition_across_slots_with_ignoring_proposers(spec,
state,
to_slot,
ignoring_proposers,
only_last_block=False):
"""
The slashed validators can't be proposers. Here we ignore the given `ignoring_proposers`
and ensure that the result state was computed with a block with slot >= to_slot.
"""
assert state.slot < to_slot
found_valid = False
while state.slot < to_slot or not found_valid:
if state.slot + 1 < to_slot and only_last_block:
next_slot(spec, state)
continue
future_state = state.copy()
next_slot(spec, future_state)
proposer_index = spec.get_beacon_proposer_index(future_state)
if proposer_index not in ignoring_proposers:
block = build_empty_block_for_next_slot(spec, state)
signed_block = state_transition_and_sign_block(spec, state, block)
yield signed_block
if state.slot >= to_slot:
found_valid = True
else:
next_slot(spec, state)
def do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True, operation_dict=None):
spec.process_slots(state, state.slot + 1)
assert state.slot % spec.SLOTS_PER_EPOCH == 0
assert spec.get_current_epoch(state) == fork_epoch
state = post_spec.upgrade_to_altair(state)
assert state.fork.epoch == fork_epoch
assert state.fork.previous_version == post_spec.config.GENESIS_FORK_VERSION
assert state.fork.current_version == post_spec.config.ALTAIR_FORK_VERSION
if with_block:
return state, _state_transition_and_sign_block_at_slot(post_spec, state, operation_dict=operation_dict)
else:
return state, None
def transition_until_fork(spec, state, fork_epoch):
to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1
transition_to(spec, state, to_slot)
def _transition_until_fork_minus_one(spec, state, fork_epoch):
to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 2
transition_to(spec, state, to_slot)
def transition_to_next_epoch_and_append_blocks(spec,
state,
post_tag,
blocks,
only_last_block=False,
ignoring_proposers=None):
to_slot = spec.SLOTS_PER_EPOCH + state.slot
if only_last_block:
block_filter = only_at(to_slot)
else:
block_filter = _all_blocks
if ignoring_proposers is None:
result_blocks = state_transition_across_slots(spec, state, to_slot, block_filter=block_filter)
else:
result_blocks = state_transition_across_slots_with_ignoring_proposers(
spec,
state,
to_slot,
ignoring_proposers,
only_last_block=only_last_block,
)
blocks.extend([
post_tag(block) for block in
result_blocks
])
def run_transition_with_operation(state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
operation_type,
operation_at_slot):
"""
Generate `operation_type` operation with the spec before fork.
The operation would be included into the block at `operation_at_slot`.
"""
is_at_fork = operation_at_slot == fork_epoch * spec.SLOTS_PER_EPOCH
is_right_before_fork = operation_at_slot == fork_epoch * spec.SLOTS_PER_EPOCH - 1
assert is_at_fork or is_right_before_fork
if is_at_fork:
transition_until_fork(spec, state, fork_epoch)
elif is_right_before_fork:
_transition_until_fork_minus_one(spec, state, fork_epoch)
is_slashing_operation = operation_type in (OperationType.PROPOSER_SLASHING, OperationType.ATTESTER_SLASHING)
# prepare operation
selected_validator_index = None
if is_slashing_operation:
# avoid slashing the next proposer
future_state = state.copy()
next_slot(spec, future_state)
proposer_index = spec.get_beacon_proposer_index(future_state)
selected_validator_index = (proposer_index + 1) % len(state.validators)
if operation_type == OperationType.PROPOSER_SLASHING:
proposer_slashing = get_valid_proposer_slashing(
spec, state, slashed_index=selected_validator_index, signed_1=True, signed_2=True)
operation_dict = {'proposer_slashings': [proposer_slashing]}
else:
# operation_type == OperationType.ATTESTER_SLASHING:
attester_slashing = get_valid_attester_slashing_by_indices(
spec, state,
[selected_validator_index],
signed_1=True, signed_2=True,
)
operation_dict = {'attester_slashings': [attester_slashing]}
elif operation_type == OperationType.DEPOSIT:
# create a new deposit
selected_validator_index = len(state.validators)
amount = spec.MAX_EFFECTIVE_BALANCE
deposit = prepare_state_and_deposit(spec, state, selected_validator_index, amount, signed=True)
operation_dict = {'deposits': [deposit]}
elif operation_type == OperationType.VOLUNTARY_EXIT:
selected_validator_index = 0
signed_exits = prepare_signed_exits(spec, state, [selected_validator_index])
operation_dict = {'voluntary_exits': signed_exits}
blocks = []
if is_right_before_fork:
# add a block with operation.
block = build_empty_block_for_next_slot(spec, state)
_set_operations_by_dict(block, operation_dict)
signed_block = state_transition_and_sign_block(spec, state, block)
blocks.append(pre_tag(signed_block))
def _check_state():
if operation_type == OperationType.PROPOSER_SLASHING:
slashed_proposer = state.validators[proposer_slashing.signed_header_1.message.proposer_index]
assert slashed_proposer.slashed
elif operation_type == OperationType.ATTESTER_SLASHING:
indices = set(attester_slashing.attestation_1.attesting_indices).intersection(
attester_slashing.attestation_2.attesting_indices
)
assert selected_validator_index in indices
assert len(indices) > 0
for validator_index in indices:
assert state.validators[validator_index].slashed
elif operation_type == OperationType.DEPOSIT:
assert not post_spec.is_active_validator(
state.validators[selected_validator_index],
post_spec.get_current_epoch(state)
)
elif operation_type == OperationType.VOLUNTARY_EXIT:
validator = state.validators[selected_validator_index]
assert validator.exit_epoch < post_spec.FAR_FUTURE_EPOCH
if is_right_before_fork:
_check_state()
yield "pre", state
# irregular state transition to handle fork:
_operation_at_slot = operation_dict if is_at_fork else None
state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=_operation_at_slot)
blocks.append(post_tag(block))
if is_at_fork:
_check_state()
# after the fork
if operation_type == OperationType.DEPOSIT:
_transition_until_active(post_spec, state, post_tag, blocks, selected_validator_index)
else:
# avoid using the slashed validators as block proposers
ignoring_proposers = [selected_validator_index] if is_slashing_operation else None
# continue regular state transition with new spec into next epoch
transition_to_next_epoch_and_append_blocks(
post_spec,
state,
post_tag,
blocks,
only_last_block=True,
ignoring_proposers=ignoring_proposers,
)
yield "blocks", blocks
yield "post", state
def _transition_until_active(post_spec, state, post_tag, blocks, validator_index):
# continue regular state transition with new spec into next epoch
transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks)
# finalize activation_eligibility_epoch
_, blocks_in_epoch, state = next_slots_with_attestations(
post_spec,
state,
post_spec.SLOTS_PER_EPOCH * 2,
fill_cur_epoch=True,
fill_prev_epoch=True,
)
blocks.extend([post_tag(block) for block in blocks_in_epoch])
assert state.finalized_checkpoint.epoch >= state.validators[validator_index].activation_eligibility_epoch
# continue regular state transition with new spec into next epoch
transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True)
assert state.validators[validator_index].activation_epoch < post_spec.FAR_FUTURE_EPOCH
to_slot = state.validators[validator_index].activation_epoch * post_spec.SLOTS_PER_EPOCH
blocks.extend([
post_tag(block) for block in
state_transition_across_slots(post_spec, state, to_slot, block_filter=only_at(to_slot))
])
assert post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state))

View File

@ -33,7 +33,7 @@ def get_sample_genesis_execution_payload_header(spec,
random=eth1_block_hash,
block_number=0,
gas_limit=30000000,
base_fee_per_gas=spec.Bytes32('0x00ca9a3b00000000000000000000000000000000000000000000000000000000'),
base_fee_per_gas=1000000000,
block_hash=eth1_block_hash,
transactions_root=spec.Root(b'\x56' * 32),
)

View File

@ -6,7 +6,28 @@ from eth2spec.test.helpers.deposits import mock_deposit
from eth2spec.test.helpers.state import next_epoch
def set_some_activations(spec, state, rng, activation_epoch=None):
if activation_epoch is None:
activation_epoch = spec.get_current_epoch(state)
num_validators = len(state.validators)
selected_indices = []
for index in range(num_validators):
# If is slashed or exiting, skip
if state.validators[index].slashed or state.validators[index].exit_epoch != spec.FAR_FUTURE_EPOCH:
continue
# Set ~1/10 validators' activation_eligibility_epoch and activation_epoch
if rng.randrange(num_validators) < num_validators // 10:
state.validators[index].activation_eligibility_epoch = max(
int(activation_epoch) - int(spec.MAX_SEED_LOOKAHEAD) - 1,
spec.GENESIS_EPOCH,
)
state.validators[index].activation_epoch = activation_epoch
selected_indices.append(index)
return selected_indices
def set_some_new_deposits(spec, state, rng):
deposited_indices = []
num_validators = len(state.validators)
# Set ~1/10 to just recently deposited
for index in range(num_validators):
@ -15,46 +36,64 @@ def set_some_new_deposits(spec, state, rng):
continue
if rng.randrange(num_validators) < num_validators // 10:
mock_deposit(spec, state, index)
# Set ~half of selected to eligible for activation
if rng.choice([True, False]):
# Set ~half of selected to eligible for activation
state.validators[index].activation_eligibility_epoch = spec.get_current_epoch(state)
else:
# The validators that just made a deposit
deposited_indices.append(index)
return deposited_indices
def exit_random_validators(spec, state, rng, fraction=None):
if fraction is None:
# Exit ~1/2
fraction = 0.5
def exit_random_validators(spec, state, rng, fraction=0.5, exit_epoch=None, withdrawable_epoch=None, from_epoch=None):
"""
Set some validators' exit_epoch and withdrawable_epoch.
if spec.get_current_epoch(state) < 5:
# Move epochs forward to allow for some validators already exited/withdrawable
for _ in range(5):
next_epoch(spec, state)
If exit_epoch is configured, use the given exit_epoch. Otherwise, randomly set exit_epoch and withdrawable_epoch.
"""
if from_epoch is None:
from_epoch = spec.MAX_SEED_LOOKAHEAD + 1
epoch_diff = int(from_epoch) - int(spec.get_current_epoch(state))
for _ in range(epoch_diff):
# NOTE: if `epoch_diff` is negative, then this loop body does not execute.
next_epoch(spec, state)
current_epoch = spec.get_current_epoch(state)
exited_indices = []
for index in spec.get_active_validator_indices(state, current_epoch):
sampled = rng.random() < fraction
if not sampled:
continue
exited_indices.append(index)
validator = state.validators[index]
validator.exit_epoch = rng.choice([current_epoch, current_epoch - 1, current_epoch - 2, current_epoch - 3])
# ~1/2 are withdrawable (note, unnatural span between exit epoch and withdrawable epoch)
if rng.choice([True, False]):
validator.withdrawable_epoch = current_epoch
if exit_epoch is None:
assert withdrawable_epoch is None
validator.exit_epoch = rng.choice([current_epoch, current_epoch - 1, current_epoch - 2, current_epoch - 3])
# ~1/2 are withdrawable (note, unnatural span between exit epoch and withdrawable epoch)
if rng.choice([True, False]):
validator.withdrawable_epoch = current_epoch
else:
validator.withdrawable_epoch = current_epoch + 1
else:
validator.withdrawable_epoch = current_epoch + 1
validator.exit_epoch = exit_epoch
if withdrawable_epoch is None:
validator.withdrawable_epoch = validator.exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY
else:
validator.withdrawable_epoch = withdrawable_epoch
return exited_indices
def slash_random_validators(spec, state, rng, fraction=None):
if fraction is None:
# Slash ~1/2 of validators
fraction = 0.5
def slash_random_validators(spec, state, rng, fraction=0.5):
slashed_indices = []
for index in range(len(state.validators)):
# slash at least one validator
sampled = rng.random() < fraction
if index == 0 or sampled:
spec.slash_validator(state, index)
slashed_indices.append(index)
return slashed_indices
def randomize_epoch_participation(spec, state, epoch, rng):
@ -123,7 +162,7 @@ def randomize_attestation_participation(spec, state, rng=Random(8020)):
randomize_epoch_participation(spec, state, spec.get_current_epoch(state), rng)
def randomize_state(spec, state, rng=Random(8020), exit_fraction=None, slash_fraction=None):
def randomize_state(spec, state, rng=Random(8020), exit_fraction=0.5, slash_fraction=0.5):
set_some_new_deposits(spec, state, rng)
exit_random_validators(spec, state, rng, fraction=exit_fraction)
slash_random_validators(spec, state, rng, fraction=slash_fraction)

View File

@ -0,0 +1,7 @@
# Random tests
The random tests are generated with various randomized states and blocks.
## Test case format
- `random` handler: same as the [`blocks`](../sanity/blocks.md) handler test case format from sanity tests.

View File

@ -1,7 +1,13 @@
from typing import Iterable
from eth2spec.test.helpers.constants import ALTAIR, MINIMAL, MAINNET, PHASE0
from eth2spec.test.altair.transition import test_transition as test_altair_transition
from eth2spec.test.altair.transition import (
test_transition as test_altair_transition,
test_activations_and_exits as test_altair_activations_and_exits,
test_leaking as test_altair_leaking,
test_slashing as test_altair_slashing,
test_operations as test_altair_operations,
)
from eth2spec.gen_helpers.gen_base import gen_runner, gen_typing
from eth2spec.gen_helpers.gen_from_tests.gen import generate_from_tests
@ -25,7 +31,13 @@ def create_provider(tests_src, preset_name: str, pre_fork_name: str, post_fork_n
return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn)
TRANSITION_TESTS = ((PHASE0, ALTAIR, test_altair_transition),)
TRANSITION_TESTS = (
(PHASE0, ALTAIR, test_altair_transition),
(PHASE0, ALTAIR, test_altair_activations_and_exits),
(PHASE0, ALTAIR, test_altair_leaking),
(PHASE0, ALTAIR, test_altair_slashing),
(PHASE0, ALTAIR, test_altair_operations),
)
if __name__ == "__main__":