mirror of
https://github.com/status-im/eth2.0-specs.git
synced 2025-01-27 02:45:28 +00:00
Merge pull request #2752 from ethereum/ex-ante-tests
This commit is contained in:
commit
4cd2334bdd
@ -267,6 +267,7 @@ def get_head(store: Store) -> Root:
|
||||
if len(children) == 0:
|
||||
return head
|
||||
# Sort by latest attesting balance with ties broken lexicographically
|
||||
# Ties broken by favoring block with lexicographically higher root
|
||||
head = max(children, key=lambda root: (get_latest_attesting_balance(store, root), root))
|
||||
```
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import pytest
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass
|
||||
import importlib
|
||||
from eth_utils import encode_hex
|
||||
|
||||
from eth2spec.phase0 import mainnet as spec_phase0_mainnet, minimal as spec_phase0_minimal
|
||||
from eth2spec.altair import mainnet as spec_altair_mainnet, minimal as spec_altair_minimal
|
||||
@ -85,10 +86,9 @@ class SpecForks(TypedDict, total=False):
|
||||
|
||||
def _prepare_state(balances_fn: Callable[[Any], Sequence[int]], threshold_fn: Callable[[Any], int],
|
||||
spec: Spec, phases: SpecForks):
|
||||
phase = phases[spec.fork]
|
||||
balances = balances_fn(phase)
|
||||
activation_threshold = threshold_fn(phase)
|
||||
state = create_genesis_state(spec=phase, validator_balances=balances,
|
||||
balances = balances_fn(spec)
|
||||
activation_threshold = threshold_fn(spec)
|
||||
state = create_genesis_state(spec=spec, validator_balances=balances,
|
||||
activation_threshold=activation_threshold)
|
||||
return state
|
||||
|
||||
@ -464,6 +464,32 @@ def with_presets(preset_bases, reason=None):
|
||||
return decorator
|
||||
|
||||
|
||||
def _get_basic_dict(ssz_dict: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Get dict of Python built-in types from a dict of SSZ objects.
|
||||
"""
|
||||
result = {}
|
||||
for k, v in ssz_dict.items():
|
||||
if isinstance(v, int):
|
||||
value = int(v)
|
||||
elif isinstance(v, bytes):
|
||||
value = encode_hex(v)
|
||||
else:
|
||||
value = str(v)
|
||||
result[k] = value
|
||||
return result
|
||||
|
||||
|
||||
def _get_copy_of_spec(spec):
|
||||
fork = spec.fork
|
||||
preset = spec.config.PRESET_BASE
|
||||
module_path = f"eth2spec.{fork}.{preset}"
|
||||
module_spec = importlib.util.find_spec(module_path)
|
||||
module = importlib.util.module_from_spec(module_spec)
|
||||
module_spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def with_config_overrides(config_overrides):
|
||||
"""
|
||||
WARNING: the spec_test decorator must wrap this, to ensure the decorated test actually runs.
|
||||
@ -474,20 +500,20 @@ def with_config_overrides(config_overrides):
|
||||
"""
|
||||
def decorator(fn):
|
||||
def wrapper(*args, spec: Spec, **kw):
|
||||
# remember the old config
|
||||
old_config = spec.config
|
||||
spec = _get_copy_of_spec(spec)
|
||||
|
||||
# apply our overrides to a copy of it, and apply it to the spec
|
||||
tmp_config = deepcopy(old_config._asdict()) # not a private method, there are multiple
|
||||
tmp_config.update(config_overrides)
|
||||
config = spec.config._asdict()
|
||||
config.update(config_overrides)
|
||||
config_types = spec.Configuration.__annotations__
|
||||
# Retain types of all config values
|
||||
test_config = {k: config_types[k](v) for k, v in tmp_config.items()}
|
||||
modified_config = {k: config_types[k](v) for k, v in config.items()}
|
||||
|
||||
# Output the config for test vectors (TODO: check config YAML encoding)
|
||||
yield 'config', 'data', test_config
|
||||
# To output the changed config to could be serialized with yaml test vectors,
|
||||
# the dict SSZ objects have to be converted into Python built-in types.
|
||||
output_config = _get_basic_dict(modified_config)
|
||||
yield 'config', 'data', output_config
|
||||
|
||||
spec.config = spec.Configuration(**test_config)
|
||||
spec.config = spec.Configuration(**modified_config)
|
||||
|
||||
# Run the function
|
||||
out = fn(*args, spec=spec, **kw)
|
||||
@ -495,10 +521,6 @@ def with_config_overrides(config_overrides):
|
||||
# it's generating things, and we need to complete it before setting back the config.
|
||||
if out is not None:
|
||||
yield from out
|
||||
|
||||
# Restore the old config and apply it
|
||||
spec.config = old_config
|
||||
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
@ -42,6 +42,12 @@ def tick_and_add_block(spec, store, signed_block, test_steps, valid=True,
|
||||
return post_state
|
||||
|
||||
|
||||
def add_attestation(spec, store, attestation, test_steps, is_from_block=False):
|
||||
spec.on_attestation(store, attestation, is_from_block=is_from_block)
|
||||
yield get_attestation_file_name(attestation), attestation
|
||||
test_steps.append({'attestation': get_attestation_file_name(attestation)})
|
||||
|
||||
|
||||
def tick_and_run_on_attestation(spec, store, attestation, test_steps, is_from_block=False):
|
||||
parent_block = store.blocks[attestation.data.beacon_block_root]
|
||||
pre_state = store.block_states[spec.hash_tree_root(parent_block)]
|
||||
@ -52,9 +58,7 @@ def tick_and_run_on_attestation(spec, store, attestation, test_steps, is_from_bl
|
||||
spec.on_tick(store, next_epoch_time)
|
||||
test_steps.append({'tick': int(next_epoch_time)})
|
||||
|
||||
spec.on_attestation(store, attestation, is_from_block=is_from_block)
|
||||
yield get_attestation_file_name(attestation), attestation
|
||||
test_steps.append({'attestation': get_attestation_file_name(attestation)})
|
||||
yield from add_attestation(spec, store, attestation, test_steps, is_from_block)
|
||||
|
||||
|
||||
def run_on_attestation(spec, store, attestation, is_from_block=False, valid=True):
|
||||
|
@ -0,0 +1,421 @@
|
||||
from eth2spec.test.context import (
|
||||
MAINNET,
|
||||
spec_state_test,
|
||||
with_all_phases,
|
||||
with_presets,
|
||||
)
|
||||
from eth2spec.test.helpers.attestations import (
|
||||
get_valid_attestation,
|
||||
sign_attestation,
|
||||
)
|
||||
from eth2spec.test.helpers.block import (
|
||||
build_empty_block,
|
||||
)
|
||||
from eth2spec.test.helpers.fork_choice import (
|
||||
get_genesis_forkchoice_store_and_block,
|
||||
on_tick_and_append_step,
|
||||
add_attestation,
|
||||
add_block,
|
||||
tick_and_add_block,
|
||||
)
|
||||
from eth2spec.test.helpers.state import (
|
||||
state_transition_and_sign_block,
|
||||
)
|
||||
|
||||
|
||||
def _apply_base_block_a(spec, state, store, test_steps):
|
||||
# On receiving block A at slot `N`
|
||||
block = build_empty_block(spec, state, slot=state.slot + 1)
|
||||
signed_block_a = state_transition_and_sign_block(spec, state, block)
|
||||
yield from tick_and_add_block(spec, store, signed_block_a, test_steps)
|
||||
assert spec.get_head(store) == signed_block_a.message.hash_tree_root()
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_ex_ante_vanilla(spec, state):
|
||||
"""
|
||||
With a single adversarial attestation
|
||||
Objects:
|
||||
Block A - slot N
|
||||
Block B (parent A) - slot N+1
|
||||
Block C (parent A) - slot N+2
|
||||
Attestation_1 (Block B); size `1` - slot N+1
|
||||
Steps:
|
||||
Block A received at N — A is head
|
||||
Block C received at N+2 — C is head
|
||||
Block B received at N+2 — C is head
|
||||
Attestation_1 received at N+2 — C is head
|
||||
"""
|
||||
test_steps = []
|
||||
# Initialization
|
||||
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
|
||||
|
||||
# On receiving block A at slot `N`
|
||||
yield from _apply_base_block_a(spec, state, store, test_steps)
|
||||
state_a = state.copy()
|
||||
|
||||
# Block B at slot `N + 1`, parent is A
|
||||
state_b = state_a.copy()
|
||||
block = build_empty_block(spec, state_a, slot=state_a.slot + 1)
|
||||
signed_block_b = state_transition_and_sign_block(spec, state_b, block)
|
||||
|
||||
# Block C at slot `N + 2`, parent is A
|
||||
state_c = state_a.copy()
|
||||
block = build_empty_block(spec, state_c, slot=state_a.slot + 2)
|
||||
signed_block_c = state_transition_and_sign_block(spec, state_c, block)
|
||||
|
||||
# Attestation_1 at slot `N + 1` voting for block B
|
||||
def _filter_participant_set(participants):
|
||||
return [next(iter(participants))]
|
||||
|
||||
attestation = get_valid_attestation(
|
||||
spec, state_b, slot=state_b.slot, signed=False, filter_participant_set=_filter_participant_set
|
||||
)
|
||||
attestation.data.beacon_block_root = signed_block_b.message.hash_tree_root()
|
||||
assert len([i for i in attestation.aggregation_bits if i == 1]) == 1
|
||||
sign_attestation(spec, state_b, attestation)
|
||||
|
||||
# Block C received at N+2 — C is head
|
||||
time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, time, test_steps)
|
||||
yield from add_block(spec, store, signed_block_c, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Block B received at N+2 — C is head due to proposer score boost
|
||||
yield from add_block(spec, store, signed_block_b, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Attestation_1 received at N+2 — C is head
|
||||
yield from add_attestation(spec, store, attestation, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
yield 'steps', test_steps
|
||||
|
||||
|
||||
def _get_greater_than_proposer_boost_score(spec, store, state, proposer_boost_root, root):
|
||||
"""
|
||||
Return the minimum attestation participant count such that attestation_score > proposer_score
|
||||
"""
|
||||
# calculate proposer boost score
|
||||
block = store.blocks[root]
|
||||
proposer_score = 0
|
||||
if spec.get_ancestor(store, root, block.slot) == proposer_boost_root:
|
||||
num_validators = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state)))
|
||||
avg_balance = spec.get_total_active_balance(state) // num_validators
|
||||
committee_size = num_validators // spec.SLOTS_PER_EPOCH
|
||||
committee_weight = committee_size * avg_balance
|
||||
proposer_score = (committee_weight * spec.config.PROPOSER_SCORE_BOOST) // 100
|
||||
|
||||
# calculate minimum participant count such that attestation_score > proposer_score
|
||||
base_effective_balance = state.validators[0].effective_balance
|
||||
|
||||
return proposer_score // base_effective_balance + 1
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@with_presets([MAINNET], reason="to create non-duplicate committee")
|
||||
@spec_state_test
|
||||
def test_ex_ante_attestations_is_greater_than_proposer_boost_with_boost(spec, state):
|
||||
"""
|
||||
Adversarial attestations > proposer boost
|
||||
Objects:
|
||||
Block A - slot N
|
||||
Block B (parent A) - slot N+1
|
||||
Block C (parent A) - slot N+2
|
||||
Attestation_set_1 (Block B); size `proposer_boost + 1` - slot N+1
|
||||
Steps:
|
||||
Block A received at N — A is head
|
||||
Block C received at N+2 — C is head
|
||||
Block B received at N+2 — C is head
|
||||
Attestation_1 received at N+2 — B is head
|
||||
"""
|
||||
test_steps = []
|
||||
# Initialization
|
||||
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
|
||||
|
||||
# On receiving block A at slot `N`
|
||||
yield from _apply_base_block_a(spec, state, store, test_steps)
|
||||
state_a = state.copy()
|
||||
|
||||
# Block B at slot `N + 1`, parent is A
|
||||
state_b = state_a.copy()
|
||||
block = build_empty_block(spec, state_a, slot=state_a.slot + 1)
|
||||
signed_block_b = state_transition_and_sign_block(spec, state_b, block)
|
||||
|
||||
# Block C at slot `N + 2`, parent is A
|
||||
state_c = state_a.copy()
|
||||
block = build_empty_block(spec, state_c, slot=state_a.slot + 2)
|
||||
signed_block_c = state_transition_and_sign_block(spec, state_c, block)
|
||||
|
||||
# Block C received at N+2 — C is head
|
||||
time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, time, test_steps)
|
||||
yield from add_block(spec, store, signed_block_c, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Block B received at N+2 — C is head due to proposer score boost
|
||||
yield from add_block(spec, store, signed_block_b, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Attestation_set_1 at slot `N + 1` voting for block B
|
||||
proposer_boost_root = signed_block_b.message.hash_tree_root()
|
||||
root = signed_block_b.message.hash_tree_root()
|
||||
participant_num = _get_greater_than_proposer_boost_score(spec, store, state, proposer_boost_root, root)
|
||||
|
||||
def _filter_participant_set(participants):
|
||||
return [index for i, index in enumerate(participants) if i < participant_num]
|
||||
|
||||
attestation = get_valid_attestation(
|
||||
spec, state_b, slot=state_b.slot, signed=False, filter_participant_set=_filter_participant_set
|
||||
)
|
||||
attestation.data.beacon_block_root = signed_block_b.message.hash_tree_root()
|
||||
assert len([i for i in attestation.aggregation_bits if i == 1]) == participant_num
|
||||
sign_attestation(spec, state_b, attestation)
|
||||
|
||||
# Attestation_set_1 received at N+2 — B is head because B's attestation_score > C's proposer_score.
|
||||
# (B's proposer_score = C's attestation_score = 0)
|
||||
yield from add_attestation(spec, store, attestation, test_steps)
|
||||
assert spec.get_head(store) == signed_block_b.message.hash_tree_root()
|
||||
|
||||
yield 'steps', test_steps
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_ex_ante_sandwich_without_attestations(spec, state):
|
||||
"""
|
||||
Simple Sandwich test with boost and no attestations.
|
||||
Obejcts:
|
||||
Block A - slot N
|
||||
Block B (parent A) - slot N+1
|
||||
Block C (parent A) - slot N+2
|
||||
Block D (parent B) - slot N+3
|
||||
Steps:
|
||||
Block A received at N — A is head
|
||||
Block C received at N+2 — C is head
|
||||
Block B received at N+2 — C is head (with boost)
|
||||
Block D received at N+3 — D is head (with boost)
|
||||
"""
|
||||
test_steps = []
|
||||
# Initialization
|
||||
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
|
||||
|
||||
# On receiving block A at slot `N`
|
||||
yield from _apply_base_block_a(spec, state, store, test_steps)
|
||||
state_a = state.copy()
|
||||
|
||||
# Block B at slot `N + 1`, parent is A
|
||||
state_b = state_a.copy()
|
||||
block = build_empty_block(spec, state_a, slot=state_a.slot + 1)
|
||||
signed_block_b = state_transition_and_sign_block(spec, state_b, block)
|
||||
|
||||
# Block C at slot `N + 2`, parent is A
|
||||
state_c = state_a.copy()
|
||||
block = build_empty_block(spec, state_c, slot=state_a.slot + 2)
|
||||
signed_block_c = state_transition_and_sign_block(spec, state_c, block)
|
||||
|
||||
# Block D at slot `N + 3`, parent is B
|
||||
state_d = state_b.copy()
|
||||
block = build_empty_block(spec, state_d, slot=state_a.slot + 3)
|
||||
signed_block_d = state_transition_and_sign_block(spec, state_d, block)
|
||||
|
||||
# Block C received at N+2 — C is head
|
||||
time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, time, test_steps)
|
||||
yield from add_block(spec, store, signed_block_c, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Block B received at N+2 — C is head, it has proposer score boost
|
||||
yield from add_block(spec, store, signed_block_b, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Block D received at N+3 - D is head, it has proposer score boost
|
||||
time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, time, test_steps)
|
||||
yield from add_block(spec, store, signed_block_d, test_steps)
|
||||
assert spec.get_head(store) == signed_block_d.message.hash_tree_root()
|
||||
|
||||
yield 'steps', test_steps
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_ex_ante_sandwich_with_honest_attestation(spec, state):
|
||||
"""
|
||||
Boosting necessary to sandwich attack.
|
||||
Objects:
|
||||
Block A - slot N
|
||||
Block B (parent A) - slot N+1
|
||||
Block C (parent A) - slot N+2
|
||||
Block D (parent B) - slot N+3
|
||||
Attestation_1 (Block C); size 1 - slot N+2 (honest)
|
||||
Steps:
|
||||
Block A received at N — A is head
|
||||
Block C received at N+2 — C is head
|
||||
Block B received at N+2 — C is head
|
||||
Attestation_1 received at N+3 — C is head
|
||||
Block D received at N+3 — D is head
|
||||
|
||||
"""
|
||||
test_steps = []
|
||||
# Initialization
|
||||
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
|
||||
|
||||
# On receiving block A at slot `N`
|
||||
yield from _apply_base_block_a(spec, state, store, test_steps)
|
||||
state_a = state.copy()
|
||||
|
||||
# Block B at slot `N + 1`, parent is A
|
||||
state_b = state_a.copy()
|
||||
block = build_empty_block(spec, state_a, slot=state_a.slot + 1)
|
||||
signed_block_b = state_transition_and_sign_block(spec, state_b, block)
|
||||
|
||||
# Block C at slot `N + 2`, parent is A
|
||||
state_c = state_a.copy()
|
||||
block = build_empty_block(spec, state_c, slot=state_a.slot + 2)
|
||||
signed_block_c = state_transition_and_sign_block(spec, state_c, block)
|
||||
|
||||
# Attestation_1 at N+2 voting for block C
|
||||
def _filter_participant_set(participants):
|
||||
return [next(iter(participants))]
|
||||
|
||||
attestation = get_valid_attestation(
|
||||
spec, state_c, slot=state_c.slot, signed=False, filter_participant_set=_filter_participant_set
|
||||
)
|
||||
attestation.data.beacon_block_root = signed_block_c.message.hash_tree_root()
|
||||
assert len([i for i in attestation.aggregation_bits if i == 1]) == 1
|
||||
sign_attestation(spec, state_c, attestation)
|
||||
|
||||
# Block D at slot `N + 3`, parent is B
|
||||
state_d = state_b.copy()
|
||||
block = build_empty_block(spec, state_d, slot=state_a.slot + 3)
|
||||
signed_block_d = state_transition_and_sign_block(spec, state_d, block)
|
||||
|
||||
# Block C received at N+2 — C is head
|
||||
time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, time, test_steps)
|
||||
yield from add_block(spec, store, signed_block_c, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Block B received at N+2 — C is head, it has proposer score boost
|
||||
yield from add_block(spec, store, signed_block_b, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Attestation_1 received at N+3 — C is head
|
||||
time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, time, test_steps)
|
||||
yield from add_attestation(spec, store, attestation, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Block D received at N+3 - D is head, it has proposer score boost
|
||||
yield from add_block(spec, store, signed_block_d, test_steps)
|
||||
assert spec.get_head(store) == signed_block_d.message.hash_tree_root()
|
||||
|
||||
yield 'steps', test_steps
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@with_presets([MAINNET], reason="to create non-duplicate committee")
|
||||
@spec_state_test
|
||||
def test_ex_ante_sandwich_with_boost_not_sufficient(spec, state):
|
||||
"""
|
||||
Boost not sufficient to sandwich attack.
|
||||
Objects:
|
||||
Block A - slot N
|
||||
Block B (parent A) - slot N+1
|
||||
Block C (parent A) - slot N+2
|
||||
Block D (parent B) - slot N+3
|
||||
Attestation_set_1 (Block C); size proposer_boost + 1 - slot N+2
|
||||
Steps:
|
||||
Block A received at N — A is head
|
||||
Block C received at N+2 — C is head
|
||||
Block B received at N+2 — C is head
|
||||
Attestation_set_1 received — C is head
|
||||
Block D received at N+3 — C is head
|
||||
"""
|
||||
test_steps = []
|
||||
# Initialization
|
||||
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
|
||||
|
||||
# On receiving block A at slot `N`
|
||||
yield from _apply_base_block_a(spec, state, store, test_steps)
|
||||
state_a = state.copy()
|
||||
|
||||
# Block B at slot `N + 1`, parent is A
|
||||
state_b = state_a.copy()
|
||||
block = build_empty_block(spec, state_a, slot=state_a.slot + 1)
|
||||
signed_block_b = state_transition_and_sign_block(spec, state_b, block)
|
||||
|
||||
# Block C at slot `N + 2`, parent is A
|
||||
state_c = state_a.copy()
|
||||
block = build_empty_block(spec, state_c, slot=state_a.slot + 2)
|
||||
signed_block_c = state_transition_and_sign_block(spec, state_c, block)
|
||||
|
||||
# Block D at slot `N + 3`, parent is B
|
||||
state_d = state_b.copy()
|
||||
block = build_empty_block(spec, state_d, slot=state_a.slot + 3)
|
||||
signed_block_d = state_transition_and_sign_block(spec, state_d, block)
|
||||
|
||||
# Block C received at N+2 — C is head
|
||||
time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, time, test_steps)
|
||||
yield from add_block(spec, store, signed_block_c, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Block B received at N+2 — C is head, it has proposer score boost
|
||||
yield from add_block(spec, store, signed_block_b, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Attestation_set_1 at N+2 voting for block C
|
||||
proposer_boost_root = signed_block_c.message.hash_tree_root()
|
||||
root = signed_block_c.message.hash_tree_root()
|
||||
participant_num = _get_greater_than_proposer_boost_score(spec, store, state, proposer_boost_root, root)
|
||||
|
||||
def _filter_participant_set(participants):
|
||||
return [index for i, index in enumerate(participants) if i < participant_num]
|
||||
|
||||
attestation = get_valid_attestation(
|
||||
spec, state_c, slot=state_c.slot, signed=False, filter_participant_set=_filter_participant_set
|
||||
)
|
||||
attestation.data.beacon_block_root = signed_block_c.message.hash_tree_root()
|
||||
assert len([i for i in attestation.aggregation_bits if i == 1]) == participant_num
|
||||
sign_attestation(spec, state_c, attestation)
|
||||
|
||||
# Attestation_1 received at N+3 — B is head because B's attestation_score > C's proposer_score.
|
||||
# (B's proposer_score = C's attestation_score = 0)
|
||||
time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
|
||||
on_tick_and_append_step(spec, store, time, test_steps)
|
||||
yield from add_attestation(spec, store, attestation, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
# Block D received at N+3 - C is head, D's boost not sufficient!
|
||||
yield from add_block(spec, store, signed_block_d, test_steps)
|
||||
assert spec.get_head(store) == signed_block_c.message.hash_tree_root()
|
||||
|
||||
yield 'steps', test_steps
|
@ -6,6 +6,7 @@ if __name__ == "__main__":
|
||||
phase_0_mods = {key: 'eth2spec.test.phase0.fork_choice.test_' + key for key in [
|
||||
'get_head',
|
||||
'on_block',
|
||||
'ex_ante',
|
||||
]}
|
||||
# No additional Altair specific finality tests, yet.
|
||||
altair_mods = phase_0_mods
|
||||
|
Loading…
x
Reference in New Issue
Block a user