Merge branch 'dev'

This commit is contained in:
Danny Ryan 2021-09-27 16:00:50 -06:00
commit 67fd7979ff
No known key found for this signature in database
GPG Key ID: 2765A792E42CE07A
28 changed files with 719 additions and 34 deletions

View File

@ -5,8 +5,10 @@ PRESET_BASE: 'mainnet'
# Transition # Transition
# --------------------------------------------------------------- # ---------------------------------------------------------------
# TBD, 2**256-1 is a placeholder # TBD, 2**256-2**10 is a placeholder
TERMINAL_TOTAL_DIFFICULTY: 115792089237316195423570985008687907853269984665640564039457584007913129639935 TERMINAL_TOTAL_DIFFICULTY: 115792089237316195423570985008687907853269984665640564039457584007913129638912
# By default, don't use this param
TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000
# Genesis # Genesis
@ -29,7 +31,7 @@ GENESIS_DELAY: 604800
# Altair # Altair
ALTAIR_FORK_VERSION: 0x01000000 ALTAIR_FORK_VERSION: 0x01000000
ALTAIR_FORK_EPOCH: 18446744073709551615 ALTAIR_FORK_EPOCH: 74240 # Oct 27, 2021, 10:56:23am UTC
# Merge # Merge
MERGE_FORK_VERSION: 0x02000000 MERGE_FORK_VERSION: 0x02000000
MERGE_FORK_EPOCH: 18446744073709551615 MERGE_FORK_EPOCH: 18446744073709551615

View File

@ -5,8 +5,10 @@ PRESET_BASE: 'minimal'
# Transition # Transition
# --------------------------------------------------------------- # ---------------------------------------------------------------
# TBD, 2**256-1 is a placeholder # TBD, 2**256-2**10 is a placeholder
TERMINAL_TOTAL_DIFFICULTY: 115792089237316195423570985008687907853269984665640564039457584007913129639935 TERMINAL_TOTAL_DIFFICULTY: 115792089237316195423570985008687907853269984665640564039457584007913129638912
# By default, don't use this param
TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000
# Genesis # Genesis

View File

@ -10,15 +10,18 @@ import textwrap
from typing import Dict, NamedTuple, List, Sequence, Optional, TypeVar from typing import Dict, NamedTuple, List, Sequence, Optional, TypeVar
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
import ast import ast
import subprocess
import sys
# NOTE: have to programmatically include third-party dependencies in `setup.py`. # NOTE: have to programmatically include third-party dependencies in `setup.py`.
def installPackage(package: str):
subprocess.check_call([sys.executable, '-m', 'pip', 'install', package])
RUAMEL_YAML_VERSION = "ruamel.yaml==0.16.5" RUAMEL_YAML_VERSION = "ruamel.yaml==0.16.5"
try: try:
import ruamel.yaml import ruamel.yaml
except ImportError: except ImportError:
import pip installPackage(RUAMEL_YAML_VERSION)
pip.main(["install", RUAMEL_YAML_VERSION])
from ruamel.yaml import YAML from ruamel.yaml import YAML
@ -26,8 +29,7 @@ MARKO_VERSION = "marko==1.0.2"
try: try:
import marko import marko
except ImportError: except ImportError:
import pip installPackage(MARKO_VERSION)
pip.main(["install", MARKO_VERSION])
from marko.block import Heading, FencedCode, LinkRefDef, BlankLine from marko.block import Heading, FencedCode, LinkRefDef, BlankLine
from marko.inline import CodeSpan from marko.inline import CodeSpan

View File

@ -1,7 +1,5 @@
# Altair -- Fork Logic # Altair -- Fork Logic
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents ## Table of contents
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- START doctoc generated TOC please keep comment here to allow auto update -->
@ -26,13 +24,13 @@ Warning: this configuration is not definitive.
| Name | Value | | Name | Value |
| - | - | | - | - |
| `ALTAIR_FORK_VERSION` | `Version('0x01000000')` | | `ALTAIR_FORK_VERSION` | `Version('0x01000000')` |
| `ALTAIR_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** | | `ALTAIR_FORK_EPOCH` | `Epoch(74240)` (Oct 27, 2021, 10:56:23am UTC) |
## Fork to Altair ## Fork to Altair
### Fork trigger ### Fork trigger
TBD. Social consensus, along with state conditions such as epoch boundary, finality, deposits, active validator count, etc. may be part of the decision process to trigger the fork. For now we assume the condition will be triggered at epoch `ALTAIR_FORK_EPOCH`. The fork is triggered at epoch `ALTAIR_FORK_EPOCH`.
Note that for the pure Altair networks, we don't apply `upgrade_to_altair` since it starts with Altair version logic. Note that for the pure Altair networks, we don't apply `upgrade_to_altair` since it starts with Altair version logic.

View File

@ -85,6 +85,7 @@ This patch adds transaction execution to the beacon chain as part of the Merge f
| Name | Value | | Name | Value |
| - | - | | - | - |
| `TERMINAL_TOTAL_DIFFICULTY` | **TBD** | | `TERMINAL_TOTAL_DIFFICULTY` | **TBD** |
| `TERMINAL_BLOCK_HASH` | `Hash32('0x0000000000000000000000000000000000000000000000000000000000000000')` |
## Containers ## Containers

View File

@ -4,6 +4,7 @@
- [The Merge -- Client Settings](#the-merge----client-settings) - [The Merge -- Client Settings](#the-merge----client-settings)
- [Override terminal total difficulty](#override-terminal-total-difficulty) - [Override terminal total difficulty](#override-terminal-total-difficulty)
- [Override terminal block hash](#override-terminal-block-hash)
<!-- END doctoc generated TOC please keep comment here to allow auto update --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->
@ -19,3 +20,9 @@ To coordinate manual overrides to [`TERMINAL_TOTAL_DIFFICULTY`](./beacon-chain.m
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. 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.
### Override terminal block hash
To allow for transition coordination around a specific PoW block, clients must also provide `--terminal-block-hash-override` as a configurable setting.
The value provided by this setting takes precedence over the pre-configured `TERMINAL_BLOCK_HASH` parameter.
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

@ -55,8 +55,7 @@ def notify_forkchoice_updated(self: ExecutionEngine, head_block_hash: Hash32, fi
### `PowBlock` ### `PowBlock`
```python ```python
@dataclass class PowBlock(Container):
class PowBlock(object):
block_hash: Hash32 block_hash: Hash32
parent_hash: Hash32 parent_hash: Hash32
total_difficulty: uint256 total_difficulty: uint256
@ -75,6 +74,9 @@ Used by fork-choice handler, `on_block`.
```python ```python
def is_valid_terminal_pow_block(block: PowBlock, parent: PowBlock) -> bool: def is_valid_terminal_pow_block(block: PowBlock, parent: PowBlock) -> bool:
if block.block_hash == TERMINAL_BLOCK_HASH:
return True
is_total_difficulty_reached = block.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY is_total_difficulty_reached = block.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY
is_parent_total_difficulty_valid = parent.total_difficulty < TERMINAL_TOTAL_DIFFICULTY is_parent_total_difficulty_valid = parent.total_difficulty < TERMINAL_TOTAL_DIFFICULTY
return is_total_difficulty_reached and is_parent_total_difficulty_valid return is_total_difficulty_reached and is_parent_total_difficulty_valid

View File

@ -97,28 +97,41 @@ To obtain an execution payload, a block proposer building a block on top of a `s
* `pow_chain` is a list that abstractly represents all blocks in the PoW chain * `pow_chain` is a list that abstractly represents all blocks in the PoW chain
* `fee_recipient` is the value suggested to be used for the `coinbase` field of the execution payload * `fee_recipient` is the value suggested to be used for the `coinbase` field of the execution payload
```python ```python
def get_pow_block_at_total_difficulty(total_difficulty: uint256, pow_chain: Sequence[PowBlock]) -> Optional[PowBlock]: def get_pow_block_at_terminal_total_difficulty(pow_chain: Sequence[PowBlock]) -> Optional[PowBlock]:
# `pow_chain` abstractly represents all blocks in the PoW chain # `pow_chain` abstractly represents all blocks in the PoW chain
for block in pow_chain: for block in pow_chain:
parent = get_pow_block(block.parent_hash) parent = get_pow_block(block.parent_hash)
if block.total_difficulty >= total_difficulty and parent.total_difficulty < total_difficulty: block_reached_ttd = block.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY
parent_reached_ttd = parent.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY
if block_reached_ttd and not parent_reached_ttd:
return block return block
return None return None
def get_terminal_pow_block(pow_chain: Sequence[PowBlock]) -> Optional[PowBlock]:
if TERMINAL_BLOCK_HASH != Hash32():
# Terminal block hash override takes precedence over terminal total difficulty
pow_block_overrides = [block for block in pow_chain if block.block_hash == TERMINAL_BLOCK_HASH]
if not any(pow_block_overrides):
return None
return pow_block_overrides[0]
return get_pow_block_at_terminal_total_difficulty(pow_chain)
def prepare_execution_payload(state: BeaconState, def prepare_execution_payload(state: BeaconState,
pow_chain: Sequence[PowBlock], pow_chain: Sequence[PowBlock],
fee_recipient: ExecutionAddress, fee_recipient: ExecutionAddress,
execution_engine: ExecutionEngine) -> Optional[PayloadId]: execution_engine: ExecutionEngine) -> Optional[PayloadId]:
if not is_merge_complete(state): if not is_merge_complete(state):
terminal_pow_block = get_pow_block_at_total_difficulty(TERMINAL_TOTAL_DIFFICULTY, pow_chain) terminal_pow_block = get_terminal_pow_block(pow_chain)
if terminal_pow_block is None: if terminal_pow_block is None:
# Pre-merge, no prepare payload call is needed # Pre-merge, no prepare payload call is needed
return None return None
else: # Signify merge via producing on top of the terminal PoW block
# Signify merge via producing on top of the last PoW block
parent_hash = terminal_pow_block.block_hash parent_hash = terminal_pow_block.block_hash
else: else:
# Post-merge, normal payload # Post-merge, normal payload

View File

@ -1 +1 @@
1.1.0-beta.5 1.1.0

View File

@ -8,8 +8,14 @@ from eth2spec.test.helpers.merkle import build_proof
@with_phases([ALTAIR]) @with_phases([ALTAIR])
@spec_state_test @spec_state_test
def test_next_sync_committee_tree(spec, state): def test_next_sync_committee_merkle_proof(spec, state):
yield "state", state
next_sync_committee_branch = build_proof(state.get_backing(), spec.NEXT_SYNC_COMMITTEE_INDEX) next_sync_committee_branch = build_proof(state.get_backing(), spec.NEXT_SYNC_COMMITTEE_INDEX)
yield "proof", {
"leaf": "0x" + state.next_sync_committee.hash_tree_root().hex(),
"leaf_index": spec.NEXT_SYNC_COMMITTEE_INDEX,
"branch": ['0x' + root.hex() for root in next_sync_committee_branch]
}
assert spec.is_valid_merkle_branch( assert spec.is_valid_merkle_branch(
leaf=state.next_sync_committee.hash_tree_root(), leaf=state.next_sync_committee.hash_tree_root(),
branch=next_sync_committee_branch, branch=next_sync_committee_branch,
@ -21,8 +27,15 @@ def test_next_sync_committee_tree(spec, state):
@with_phases([ALTAIR]) @with_phases([ALTAIR])
@spec_state_test @spec_state_test
def test_finality_root_tree(spec, state): def test_finality_root_merkle_proof(spec, state):
yield "state", state
finality_branch = build_proof(state.get_backing(), spec.FINALIZED_ROOT_INDEX) finality_branch = build_proof(state.get_backing(), spec.FINALIZED_ROOT_INDEX)
yield "proof", {
"leaf": "0x" + state.finalized_checkpoint.root.hex(),
"leaf_index": spec.FINALIZED_ROOT_INDEX,
"branch": ['0x' + root.hex() for root in finality_branch]
}
assert spec.is_valid_merkle_branch( assert spec.is_valid_merkle_branch(
leaf=state.finalized_checkpoint.root, leaf=state.finalized_checkpoint.root,
branch=finality_branch, branch=finality_branch,

View File

@ -1,2 +1,6 @@
class SkippedTest(Exception): class SkippedTest(Exception):
... ...
class BlockNotFoundException(Exception):
...

View File

@ -1,4 +1,7 @@
from random import Random
from eth_utils import encode_hex from eth_utils import encode_hex
from eth2spec.test.exceptions import BlockNotFoundException
from eth2spec.utils.ssz.ssz_typing import uint256
from eth2spec.test.helpers.attestations import ( from eth2spec.test.helpers.attestations import (
next_epoch_with_attestations, next_epoch_with_attestations,
next_slots_with_attestations, next_slots_with_attestations,
@ -22,15 +25,22 @@ def add_block_to_store(spec, store, signed_block):
spec.on_block(store, signed_block) spec.on_block(store, signed_block)
def tick_and_add_block(spec, store, signed_block, test_steps, valid=True, allow_invalid_attestations=False): def tick_and_add_block(spec, store, signed_block, test_steps, valid=True, allow_invalid_attestations=False,
merge_block=False, block_not_found=False):
pre_state = store.block_states[signed_block.message.parent_root] pre_state = store.block_states[signed_block.message.parent_root]
block_time = pre_state.genesis_time + signed_block.message.slot * spec.config.SECONDS_PER_SLOT block_time = pre_state.genesis_time + signed_block.message.slot * spec.config.SECONDS_PER_SLOT
if merge_block:
assert spec.is_merge_block(pre_state, signed_block.message.body)
if store.time < block_time: if store.time < block_time:
on_tick_and_append_step(spec, store, block_time, test_steps) on_tick_and_append_step(spec, store, block_time, test_steps)
post_state = yield from add_block( post_state = yield from add_block(
spec, store, signed_block, test_steps, valid=valid, allow_invalid_attestations=allow_invalid_attestations) spec, store, signed_block, test_steps,
valid=valid,
allow_invalid_attestations=allow_invalid_attestations,
block_not_found=block_not_found,
)
return post_state return post_state
@ -118,7 +128,13 @@ def run_on_block(spec, store, signed_block, valid=True):
assert store.blocks[signed_block.message.hash_tree_root()] == signed_block.message assert store.blocks[signed_block.message.hash_tree_root()] == signed_block.message
def add_block(spec, store, signed_block, test_steps, valid=True, allow_invalid_attestations=False): def add_block(spec,
store,
signed_block,
test_steps,
valid=True,
allow_invalid_attestations=False,
block_not_found=False):
""" """
Run on_block and on_attestation Run on_block and on_attestation
""" """
@ -127,7 +143,9 @@ def add_block(spec, store, signed_block, test_steps, valid=True, allow_invalid_a
if not valid: if not valid:
try: try:
run_on_block(spec, store, signed_block, valid=True) run_on_block(spec, store, signed_block, valid=True)
except AssertionError: except (AssertionError, BlockNotFoundException) as e:
if isinstance(e, BlockNotFoundException) and not block_not_found:
assert False
test_steps.append({ test_steps.append({
'block': get_block_file_name(signed_block), 'block': get_block_file_name(signed_block),
'valid': False, 'valid': False,
@ -227,3 +245,21 @@ def apply_next_slots_with_attestations(spec,
assert store.block_states[block_root].hash_tree_root() == post_state.hash_tree_root() assert store.block_states[block_root].hash_tree_root() == post_state.hash_tree_root()
return post_state, store, last_signed_block return post_state, store, last_signed_block
def prepare_empty_pow_block(spec, rng=Random(3131)):
return spec.PowBlock(
block_hash=spec.Hash32(spec.hash(bytearray(rng.getrandbits(8) for _ in range(32)))),
parent_hash=spec.Hash32(spec.hash(bytearray(rng.getrandbits(8) for _ in range(32)))),
total_difficulty=uint256(0),
difficulty=uint256(0)
)
def get_pow_block_file_name(pow_block):
return f"pow_block_{encode_hex(pow_block.block_hash)}"
def add_pow_block(spec, store, pow_block, test_steps):
yield get_pow_block_file_name(pow_block), pow_block
test_steps.append({'pow_block': get_pow_block_file_name(pow_block)})

View File

@ -1,3 +1,4 @@
from eth2spec.utils.ssz.ssz_typing import uint64
from eth2spec.test.helpers.execution_payload import ( from eth2spec.test.helpers.execution_payload import (
build_empty_execution_payload, build_empty_execution_payload,
get_execution_payload_header, get_execution_payload_header,
@ -227,3 +228,157 @@ def test_bad_timestamp_regular_payload(spec, state):
execution_payload.timestamp = execution_payload.timestamp + 1 execution_payload.timestamp = execution_payload.timestamp + 1
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
@with_merge_and_later
@spec_state_test
def test_gaslimit_zero_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.gas_limit = uint64(0)
yield from run_execution_payload_processing(spec, state, execution_payload)
@with_merge_and_later
@spec_state_test
def test_gaslimit_max_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.gas_limit = uint64(2**64 - 1)
yield from run_execution_payload_processing(spec, state, execution_payload)
@with_merge_and_later
@spec_state_test
def test_gaslimit_upper_plus_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.gas_limit = (
execution_payload.gas_limit +
execution_payload.gas_limit // spec.GAS_LIMIT_DENOMINATOR
)
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
@with_merge_and_later
@spec_state_test
def test_gaslimit_upper_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.gas_limit = (
execution_payload.gas_limit +
execution_payload.gas_limit // spec.GAS_LIMIT_DENOMINATOR - uint64(1)
)
yield from run_execution_payload_processing(spec, state, execution_payload)
@with_merge_and_later
@spec_state_test
def test_gaslimit_lower_minus_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.gas_limit = (
execution_payload.gas_limit -
execution_payload.gas_limit // spec.GAS_LIMIT_DENOMINATOR
)
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
@with_merge_and_later
@spec_state_test
def test_gaslimit_lower_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.gas_limit = (
execution_payload.gas_limit -
execution_payload.gas_limit // spec.GAS_LIMIT_DENOMINATOR + uint64(1)
)
yield from run_execution_payload_processing(spec, state, execution_payload)
@with_merge_and_later
@spec_state_test
def test_gaslimit_minimum_regular_payload(spec, state):
# pre-state
state = build_state_with_complete_transition(spec, state)
next_slot(spec, state)
state.latest_execution_payload_header.gas_limit = spec.MIN_GAS_LIMIT
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.gas_limit = execution_payload.gas_limit
yield from run_execution_payload_processing(spec, state, execution_payload)
@with_merge_and_later
@spec_state_test
def test_gaslimit_minimum_minus_regular_payload(spec, state):
# pre-state
state = build_state_with_complete_transition(spec, state)
next_slot(spec, state)
state.latest_execution_payload_header.gas_limit = spec.MIN_GAS_LIMIT
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.gas_limit = execution_payload.gas_limit - uint64(1)
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
@with_merge_and_later
@spec_state_test
def test_gasused_gaslimit_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.gas_used = execution_payload.gas_limit
yield from run_execution_payload_processing(spec, state, execution_payload)
@with_merge_and_later
@spec_state_test
def test_gasused_gaslimit_plus_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.gas_used = execution_payload.gas_limit + uint64(1)
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)

View File

@ -0,0 +1,173 @@
from eth2spec.utils.ssz.ssz_typing import uint256
from eth2spec.test.exceptions import BlockNotFoundException
from eth2spec.test.context import spec_state_test, with_phases, MERGE
from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot,
)
from eth2spec.test.helpers.fork_choice import (
get_genesis_forkchoice_store_and_block,
on_tick_and_append_step,
tick_and_add_block,
)
from eth2spec.test.helpers.state import (
state_transition_and_sign_block,
)
from eth2spec.test.helpers.fork_choice import (
prepare_empty_pow_block,
add_pow_block,
)
from eth2spec.test.helpers.execution_payload import (
build_state_with_incomplete_transition,
)
def with_pow_block_patch(spec, blocks, func):
def get_pow_block(hash: spec.Bytes32) -> spec.PowBlock:
for block in blocks:
if block.block_hash == hash:
return block
raise BlockNotFoundException()
get_pow_block_backup = spec.get_pow_block
spec.get_pow_block = get_pow_block
class AtomicBoolean():
value = False
is_called = AtomicBoolean()
def wrap(flag: AtomicBoolean):
yield from func()
flag.value = True
try:
yield from wrap(is_called)
finally:
spec.get_pow_block = get_pow_block_backup
assert is_called.value
@with_phases([MERGE])
@spec_state_test
def test_all_valid(spec, state):
test_steps = []
# Initialization
state = build_state_with_incomplete_transition(spec, state)
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
yield 'anchor_state', state
yield 'anchor_block', anchor_block
current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
on_tick_and_append_step(spec, store, current_time, test_steps)
assert store.time == current_time
pow_block_parent = prepare_empty_pow_block(spec)
pow_block_parent.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1)
pow_block = prepare_empty_pow_block(spec)
pow_block.parent_hash = pow_block_parent.block_hash
pow_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY
pow_blocks = [pow_block, pow_block_parent]
for pb in pow_blocks:
yield from add_pow_block(spec, store, pb, test_steps)
def run_func():
block = build_empty_block_for_next_slot(spec, state)
block.body.execution_payload.parent_hash = pow_block.block_hash
signed_block = state_transition_and_sign_block(spec, state, block)
yield from tick_and_add_block(spec, store, signed_block, test_steps, merge_block=True)
# valid
assert spec.get_head(store) == signed_block.message.hash_tree_root()
yield from with_pow_block_patch(spec, pow_blocks, run_func)
yield 'steps', test_steps
@with_phases([MERGE])
@spec_state_test
def test_block_lookup_failed(spec, state):
test_steps = []
# Initialization
state = build_state_with_incomplete_transition(spec, state)
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
yield 'anchor_state', state
yield 'anchor_block', anchor_block
current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
on_tick_and_append_step(spec, store, current_time, test_steps)
assert store.time == current_time
pow_block = prepare_empty_pow_block(spec)
pow_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1)
pow_blocks = [pow_block]
for pb in pow_blocks:
yield from add_pow_block(spec, store, pb, test_steps)
def run_func():
block = build_empty_block_for_next_slot(spec, state)
block.body.execution_payload.parent_hash = pow_block.block_hash
signed_block = state_transition_and_sign_block(spec, state, block)
yield from tick_and_add_block(spec, store, signed_block, test_steps, valid=False, merge_block=True,
block_not_found=True)
yield from with_pow_block_patch(spec, pow_blocks, run_func)
yield 'steps', test_steps
@with_phases([MERGE])
@spec_state_test
def test_too_early_for_merge(spec, state):
test_steps = []
# Initialization
state = build_state_with_incomplete_transition(spec, state)
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
yield 'anchor_state', state
yield 'anchor_block', anchor_block
current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
on_tick_and_append_step(spec, store, current_time, test_steps)
assert store.time == current_time
pow_block_parent = prepare_empty_pow_block(spec)
pow_block_parent.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(2)
pow_block = prepare_empty_pow_block(spec)
pow_block.parent_hash = pow_block_parent.block_hash
pow_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1)
pow_blocks = [pow_block, pow_block_parent]
for pb in pow_blocks:
yield from add_pow_block(spec, store, pb, test_steps)
def run_func():
block = build_empty_block_for_next_slot(spec, state)
block.body.execution_payload.parent_hash = pow_block.block_hash
signed_block = state_transition_and_sign_block(spec, state, block)
yield from tick_and_add_block(spec, store, signed_block, test_steps, valid=False, merge_block=True)
yield from with_pow_block_patch(spec, pow_blocks, run_func)
yield 'steps', test_steps
@with_phases([MERGE])
@spec_state_test
def test_too_late_for_merge(spec, state):
test_steps = []
# Initialization
state = build_state_with_incomplete_transition(spec, state)
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
yield 'anchor_state', state
yield 'anchor_block', anchor_block
current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
on_tick_and_append_step(spec, store, current_time, test_steps)
assert store.time == current_time
pow_block_parent = prepare_empty_pow_block(spec)
pow_block_parent.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY
pow_block = prepare_empty_pow_block(spec)
pow_block.parent_hash = pow_block_parent.block_hash
pow_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + uint256(1)
pow_blocks = [pow_block, pow_block_parent]
for pb in pow_blocks:
yield from add_pow_block(spec, store, pb, test_steps)
def run_func():
block = build_empty_block_for_next_slot(spec, state)
block.body.execution_payload.parent_hash = pow_block.block_hash
signed_block = state_transition_and_sign_block(spec, state, block)
yield from tick_and_add_block(spec, store, signed_block, test_steps, valid=False, merge_block=True)
yield from with_pow_block_patch(spec, pow_blocks, run_func)
yield 'steps', test_steps

View File

@ -0,0 +1,143 @@
from eth2spec.test.exceptions import BlockNotFoundException
from eth2spec.utils.ssz.ssz_typing import uint256
from eth2spec.test.helpers.fork_choice import (
prepare_empty_pow_block,
)
from eth2spec.test.context import spec_state_test, with_merge_and_later
# Copy of conditional merge part of `on_block(store: Store, signed_block: SignedBeaconBlock)` handler
def validate_transition_execution_payload(spec, execution_payload):
pow_block = spec.get_pow_block(execution_payload.parent_hash)
pow_parent = spec.get_pow_block(pow_block.parent_hash)
assert spec.is_valid_terminal_pow_block(pow_block, pow_parent)
def run_validate_transition_execution_payload(spec, block, parent_block, payload,
valid=True, block_lookup_success=True):
"""
Run ``validate_transition_execution_payload``
If ``valid == False``, run expecting ``AssertionError``
If ``block_lookup_success == False``, run expecting ``BlockNotFoundException``
"""
def get_pow_block(hash: spec.Bytes32) -> spec.PowBlock:
if hash == block.block_hash:
return block
elif hash == parent_block.block_hash:
return parent_block
else:
raise BlockNotFoundException()
save_pow_block = spec.get_pow_block
# Guido authorized everyone to do this
spec.get_pow_block = get_pow_block
exception_caught = False
block_not_found_exception_caught = False
try:
validate_transition_execution_payload(spec, payload)
except BlockNotFoundException:
block_not_found_exception_caught = True
except AssertionError:
exception_caught = True
except Exception as e:
spec.get_pow_block = save_pow_block
raise e
spec.get_pow_block = save_pow_block
if block_lookup_success:
assert not block_not_found_exception_caught
else:
assert block_not_found_exception_caught
if valid:
assert not exception_caught
else:
assert exception_caught
@with_merge_and_later
@spec_state_test
def test_valid_terminal_pow_block_success_valid(spec, state):
parent_block = prepare_empty_pow_block(spec)
parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1)
block = prepare_empty_pow_block(spec)
block.parent_hash = parent_block.block_hash
block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY
assert spec.is_valid_terminal_pow_block(block, parent_block)
@with_merge_and_later
@spec_state_test
def test_valid_terminal_pow_block_fail_before_terminal(spec, state):
parent_block = prepare_empty_pow_block(spec)
parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(2)
block = prepare_empty_pow_block(spec)
block.parent_hash = parent_block.block_hash
block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1)
assert not spec.is_valid_terminal_pow_block(block, parent_block)
@with_merge_and_later
@spec_state_test
def test_valid_terminal_pow_block_fail_just_after_terminal(spec, state):
parent_block = prepare_empty_pow_block(spec)
parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY
block = prepare_empty_pow_block(spec)
block.parent_hash = parent_block.block_hash
block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + uint256(1)
assert not spec.is_valid_terminal_pow_block(block, parent_block)
@with_merge_and_later
@spec_state_test
def test_validate_transition_execution_payload_success(spec, state):
parent_block = prepare_empty_pow_block(spec)
parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1)
block = prepare_empty_pow_block(spec)
block.parent_hash = parent_block.block_hash
block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY
payload = spec.ExecutionPayload()
payload.parent_hash = block.block_hash
run_validate_transition_execution_payload(spec, block, parent_block, payload)
@with_merge_and_later
@spec_state_test
def test_validate_transition_execution_payload_fail_block_lookup(spec, state):
parent_block = prepare_empty_pow_block(spec)
parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1)
block = prepare_empty_pow_block(spec)
block.parent_hash = parent_block.block_hash
block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY
payload = spec.ExecutionPayload()
run_validate_transition_execution_payload(spec, block, parent_block, payload,
block_lookup_success=False)
@with_merge_and_later
@spec_state_test
def test_validate_transition_execution_payload_fail_parent_block_lookup(spec, state):
parent_block = prepare_empty_pow_block(spec)
parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1)
block = prepare_empty_pow_block(spec)
block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY
payload = spec.ExecutionPayload()
payload.parent_hash = block.block_hash
run_validate_transition_execution_payload(spec, block, parent_block, payload,
block_lookup_success=False)
@with_merge_and_later
@spec_state_test
def test_validate_transition_execution_payload_fail_after_terminal(spec, state):
parent_block = prepare_empty_pow_block(spec)
parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY
block = prepare_empty_pow_block(spec)
block.parent_hash = parent_block.block_hash
block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + 1
payload = spec.ExecutionPayload()
payload.parent_hash = block.block_hash
run_validate_transition_execution_payload(spec, block, parent_block, payload, valid=False)

View File

@ -0,0 +1,55 @@
from eth2spec.test.helpers.execution_payload import (
build_empty_execution_payload,
build_state_with_incomplete_transition,
build_state_with_complete_transition,
)
from eth2spec.test.context import (
spec_state_test,
with_merge_and_later
)
@with_merge_and_later
@spec_state_test
def test_fail_merge_complete(spec, state):
state = build_state_with_incomplete_transition(spec, state)
assert not spec.is_merge_complete(state)
@with_merge_and_later
@spec_state_test
def test_success_merge_complete(spec, state):
state = build_state_with_complete_transition(spec, state)
assert spec.is_merge_complete(state)
# with_complete_transition', 'with_execution_payload', 'is_merge_block', 'is_execution_enabled'
expected_results = [
(True, True, False, True),
(True, False, False, True),
(False, True, True, True),
(False, False, False, False)
]
@with_merge_and_later
@spec_state_test
def test_is_merge_block_and_is_execution_enabled(spec, state):
for result in expected_results:
(
with_complete_transition,
with_execution_payload,
is_merge_block,
is_execution_enabled
) = result
if with_complete_transition:
state = build_state_with_complete_transition(spec, state)
else:
state = build_state_with_incomplete_transition(spec, state)
body = spec.BeaconBlockBody()
if with_execution_payload:
body.execution_payload = build_empty_execution_payload(spec, state)
assert spec.is_merge_block(state, body) == is_merge_block
assert spec.is_execution_enabled(state, body) == is_execution_enabled

View File

@ -69,6 +69,18 @@ The file is located in the same folder (see below).
After this step, the `store` object may have been updated. After this step, the `store` object may have been updated.
#### `on_merge_block` execution
Adds `PowBlock` data which is required for executing `on_block(store, block)`.
```yaml
{
pow_block: string -- the name of the `pow_block_<32-byte-root>.ssz_snappy` file.
To be used in `get_pow_block` lookup
}
```
The file is located in the same folder (see below).
PowBlocks should be used as return values for `get_pow_block(hash: Hash32) -> PowBlock` function if hashes match.
#### Checks step #### Checks step
The checks to verify the current status of `store`. The checks to verify the current status of `store`.

View File

@ -0,0 +1,8 @@
# Merkle tests
This series of tests provides reference test vectors for validating correct
generation and verification of merkle proofs based on static data.
Handlers:
- `single_proof`: see [Single leaf proof test format](./single_proof.md)
- Different types of merkle proofs may be supported in the future.

View File

@ -0,0 +1,28 @@
# Single leaf merkle proof tests
This series of tests provides reference test vectors for validating correct
generation and verification of merkle proofs based on static data.
## Test case format
### `state.ssz_snappy`
An SSZ-snappy encoded `BeaconState` object from which other data is generated.
### `proof.yaml`
A proof of the leaf value (a merkle root) at generalized-index `leaf_index` in the given `state`.
```yaml
leaf: Bytes32 # string, hex encoded, with 0x prefix
leaf_index: int # integer, decimal
branch: list of Bytes32 # list, each element is a string, hex encoded, with 0x prefix
```
## Condition
A test-runner can implement the following assertions:
- Check that `is_valid_merkle_branch` confirms `leaf` at `leaf_index` to verify
against `has_tree_root(state)` and `proof`.
- If the implementation supports generating merkle proofs, check that the
self-generated proof matches the `proof` provided with the test.

View File

@ -9,8 +9,14 @@ if __name__ == "__main__":
]} ]}
# No additional Altair specific finality tests, yet. # No additional Altair specific finality tests, yet.
altair_mods = phase_0_mods altair_mods = phase_0_mods
# No specific Merge tests yet. # For merge `on_merge_block` test kind added with `pow_block_N.ssz` files with several
merge_mods = altair_mods # PowBlock's which should be resolved by `get_pow_block(hash: Hash32) -> PowBlock` function
merge_mods = {
**{key: 'eth2spec.test.merge.fork_choice.test_' + key for key in [
'on_merge_block',
]},
**altair_mods,
}
all_mods = { all_mods = {
PHASE0: phase_0_mods, PHASE0: phase_0_mods,

View File

@ -1,5 +1,5 @@
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators
from eth2spec.test.helpers.constants import PHASE0, ALTAIR from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE
if __name__ == "__main__": if __name__ == "__main__":
@ -8,9 +8,12 @@ if __name__ == "__main__":
'validity', 'validity',
]} ]}
altair_mods = phase_0_mods altair_mods = phase_0_mods
# we have new unconditional lines in `initialize_beacon_state_from_eth1` and we want to test it
merge_mods = altair_mods
all_mods = { all_mods = {
PHASE0: phase_0_mods, PHASE0: phase_0_mods,
ALTAIR: altair_mods, ALTAIR: altair_mods,
MERGE: merge_mods,
} }
run_state_test_generators(runner_name="genesis", all_mods=all_mods) run_state_test_generators(runner_name="genesis", all_mods=all_mods)

View File

@ -0,0 +1,6 @@
# Merkle
The purpose of this test-generator is to provide test-vectors for validating the
correct merkleization of objects and corresponding merkle proofs.
Test-format documentation can be found [here](../../formats/merkle/README.md).

View File

View File

@ -0,0 +1,14 @@
from eth2spec.test.helpers.constants import ALTAIR
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators
if __name__ == "__main__":
altair_mods = {key: 'eth2spec.test.altair.merkle.test_' + key for key in [
'single_proof',
]}
all_mods = {
ALTAIR: altair_mods
}
run_state_test_generators(runner_name="merkle", all_mods=all_mods)

View File

@ -0,0 +1,2 @@
pytest>=4.4
../../../[generator]