Merge pull request #3290 from ethereum/fork-choice-upgrade

Fork choice upgrade
This commit is contained in:
Danny Ryan 2023-03-15 10:51:09 -06:00 committed by GitHub
commit a0eb23f108
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 2150 additions and 664 deletions

View File

@ -18,12 +18,6 @@ HYSTERESIS_DOWNWARD_MULTIPLIER: 1
HYSTERESIS_UPWARD_MULTIPLIER: 5
# Fork Choice
# ---------------------------------------------------------------
# 2**3 (= 8)
SAFE_SLOTS_TO_UPDATE_JUSTIFIED: 8
# Gwei values
# ---------------------------------------------------------------
# 2**0 * 10**9 (= 1,000,000,000) Gwei

View File

@ -18,12 +18,6 @@ HYSTERESIS_DOWNWARD_MULTIPLIER: 1
HYSTERESIS_UPWARD_MULTIPLIER: 5
# Fork Choice
# ---------------------------------------------------------------
# 2**1 (= 1)
SAFE_SLOTS_TO_UPDATE_JUSTIFIED: 2
# Gwei values
# ---------------------------------------------------------------
# 2**0 * 10**9 (= 1,000,000,000) Gwei

View File

@ -174,6 +174,7 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
# Check the block is valid and compute the post-state
state = pre_state.copy()
block_root = hash_tree_root(block)
state_transition(state, signed_block, True)
# [New in Bellatrix]
@ -181,9 +182,9 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
validate_merge_block(block)
# Add new block to the store
store.blocks[hash_tree_root(block)] = block
store.blocks[block_root] = block
# Add new state for this block to the store
store.block_states[hash_tree_root(block)] = state
store.block_states[block_root] = state
# Add proposer score boost if the block is timely
time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT
@ -191,15 +192,9 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
if get_current_slot(store) == block.slot and is_before_attesting_interval:
store.proposer_boost_root = hash_tree_root(block)
# Update justified checkpoint
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch:
store.best_justified_checkpoint = state.current_justified_checkpoint
if should_update_justified_checkpoint(store, state.current_justified_checkpoint):
store.justified_checkpoint = state.current_justified_checkpoint
# Update checkpoints in store if necessary
update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint)
# Update finalized checkpoint
if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch:
store.finalized_checkpoint = state.finalized_checkpoint
store.justified_checkpoint = state.current_justified_checkpoint
# Eagerly compute unrealized justification and finality.
compute_pulled_up_tip(store, block_root)
```

View File

@ -91,6 +91,7 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
# Check the block is valid and compute the post-state
state = pre_state.copy()
block_root = hash_tree_root(block)
state_transition(state, signed_block, True)
# Check the merge transition
@ -98,9 +99,9 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
validate_merge_block(block)
# Add new block to the store
store.blocks[hash_tree_root(block)] = block
store.blocks[block_root] = block
# Add new state for this block to the store
store.block_states[hash_tree_root(block)] = state
store.block_states[block_root] = state
# Add proposer score boost if the block is timely
time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT
@ -108,15 +109,9 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
if get_current_slot(store) == block.slot and is_before_attesting_interval:
store.proposer_boost_root = hash_tree_root(block)
# Update justified checkpoint
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch:
store.best_justified_checkpoint = state.current_justified_checkpoint
if should_update_justified_checkpoint(store, state.current_justified_checkpoint):
store.justified_checkpoint = state.current_justified_checkpoint
# Update checkpoints in store if necessary
update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint)
# Update finalized checkpoint
if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch:
store.finalized_checkpoint = state.finalized_checkpoint
store.justified_checkpoint = state.current_justified_checkpoint
# Eagerly compute unrealized justification and finality.
compute_pulled_up_tip(store, block_root)
```

View File

@ -8,10 +8,10 @@
- [Introduction](#introduction)
- [Fork choice](#fork-choice)
- [Constant](#constant)
- [Preset](#preset)
- [Configuration](#configuration)
- [Helpers](#helpers)
- [`LatestMessage`](#latestmessage)
- [`is_previous_epoch_justified`](#is_previous_epoch_justified)
- [`Store`](#store)
- [`get_forkchoice_store`](#get_forkchoice_store)
- [`get_slots_since_genesis`](#get_slots_since_genesis)
@ -19,10 +19,16 @@
- [`compute_slots_since_epoch_start`](#compute_slots_since_epoch_start)
- [`get_ancestor`](#get_ancestor)
- [`get_weight`](#get_weight)
- [`get_voting_source`](#get_voting_source)
- [`filter_block_tree`](#filter_block_tree)
- [`get_filtered_block_tree`](#get_filtered_block_tree)
- [`get_head`](#get_head)
- [`should_update_justified_checkpoint`](#should_update_justified_checkpoint)
- [`update_checkpoints`](#update_checkpoints)
- [`update_unrealized_checkpoints`](#update_unrealized_checkpoints)
- [Pull-up tip helpers](#pull-up-tip-helpers)
- [`compute_pulled_up_tip`](#compute_pulled_up_tip)
- [`on_tick` helpers](#on_tick-helpers)
- [`on_tick_per_slot`](#on_tick_per_slot)
- [`on_attestation` helpers](#on_attestation-helpers)
- [`validate_target_epoch_against_current_time`](#validate_target_epoch_against_current_time)
- [`validate_on_attestation`](#validate_on_attestation)
@ -67,12 +73,6 @@ Any of the above handlers that trigger an unhandled exception (e.g. a failed ass
| -------------------- | ----------- |
| `INTERVALS_PER_SLOT` | `uint64(3)` |
### Preset
| Name | Value | Unit | Duration |
| -------------------------------- | ------------ | :---: | :--------: |
| `SAFE_SLOTS_TO_UPDATE_JUSTIFIED` | `2**3` (= 8) | slots | 96 seconds |
### Configuration
| Name | Value |
@ -92,8 +92,26 @@ class LatestMessage(object):
root: Root
```
### `is_previous_epoch_justified`
```python
def is_previous_epoch_justified(store: Store) -> bool:
current_slot = get_current_slot(store)
current_epoch = compute_epoch_at_slot(current_slot)
return store.justified_checkpoint.epoch + 1 == current_epoch
```
#### `Store`
The `Store` is responsible for tracking information required for the fork choice algorithm. The important fields being tracked are described below:
- `justified_checkpoint`: the justified checkpoint used as the starting point for the LMD GHOST fork choice algorithm.
- `finalized_checkpoint`: the highest known finalized checkpoint. The fork choice only considers blocks that are not conflicting with this checkpoint.
- `unrealized_justified_checkpoint` & `unrealized_finalized_checkpoint`: these track the highest justified & finalized checkpoints resp., without regard to whether on-chain ***realization*** has occurred, i.e. FFG processing of new attestations within the state transition function. This is an important distinction from `justified_checkpoint` & `finalized_checkpoint`, because they will only track the checkpoints that are realized on-chain. Note that on-chain processing of FFG information only happens at epoch boundaries.
- `unrealized_justifications`: stores a map of block root to the unrealized justified checkpoint observed in that block.
```python
@dataclass
class Store(object):
@ -101,13 +119,15 @@ class Store(object):
genesis_time: uint64
justified_checkpoint: Checkpoint
finalized_checkpoint: Checkpoint
best_justified_checkpoint: Checkpoint
unrealized_justified_checkpoint: Checkpoint
unrealized_finalized_checkpoint: Checkpoint
proposer_boost_root: Root
equivocating_indices: Set[ValidatorIndex]
blocks: Dict[Root, BeaconBlock] = field(default_factory=dict)
block_states: Dict[Root, BeaconState] = field(default_factory=dict)
checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict)
latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict)
unrealized_justifications: Dict[Root, Checkpoint] = field(default_factory=dict)
```
#### `get_forkchoice_store`
@ -130,12 +150,14 @@ def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -
genesis_time=anchor_state.genesis_time,
justified_checkpoint=justified_checkpoint,
finalized_checkpoint=finalized_checkpoint,
best_justified_checkpoint=justified_checkpoint,
unrealized_justified_checkpoint=justified_checkpoint,
unrealized_finalized_checkpoint=finalized_checkpoint,
proposer_boost_root=proposer_boost_root,
equivocating_indices=set(),
blocks={anchor_root: copy(anchor_block)},
block_states={anchor_root: copy(anchor_state)},
checkpoint_states={justified_checkpoint: copy(anchor_state)},
unrealized_justifications={anchor_root: justified_checkpoint}
)
```
@ -167,11 +189,7 @@ def get_ancestor(store: Store, root: Root, slot: Slot) -> Root:
block = store.blocks[root]
if block.slot > slot:
return get_ancestor(store, block.parent_root, slot)
elif block.slot == slot:
return root
else:
# root is older than queried slot, thus a skip slot. Return most recent root prior to slot
return root
return root
```
#### `get_weight`
@ -179,9 +197,12 @@ def get_ancestor(store: Store, root: Root, slot: Slot) -> Root:
```python
def get_weight(store: Store, root: Root) -> Gwei:
state = store.checkpoint_states[store.justified_checkpoint]
active_indices = get_active_validator_indices(state, get_current_epoch(state))
unslashed_and_active_indices = [
i for i in get_active_validator_indices(state, get_current_epoch(state))
if not state.validators[i].slashed
]
attestation_score = Gwei(sum(
state.validators[i].effective_balance for i in active_indices
state.validators[i].effective_balance for i in unslashed_and_active_indices
if (i in store.latest_messages
and i not in store.equivocating_indices
and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root)
@ -199,8 +220,30 @@ def get_weight(store: Store, root: Root) -> Gwei:
return attestation_score + proposer_score
```
#### `get_voting_source`
```python
def get_voting_source(store: Store, block_root: Root) -> Checkpoint:
"""
Compute the voting source checkpoint in event that block with root ``block_root`` is the head block
"""
block = store.blocks[block_root]
current_epoch = compute_epoch_at_slot(get_current_slot(store))
block_epoch = compute_epoch_at_slot(block.slot)
if current_epoch > block_epoch:
# The block is from a prior epoch, the voting source will be pulled-up
return store.unrealized_justifications[block_root]
else:
# The block is not from a prior epoch, therefore the voting source is not pulled up
head_state = store.block_states[block_root]
return head_state.current_justified_checkpoint
```
#### `filter_block_tree`
*Note*: External calls to `filter_block_tree` (i.e., any calls that are not made by the recursive logic in this function) MUST set `block_root` to `store.justified_checkpoint`.
```python
def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconBlock]) -> bool:
block = store.blocks[block_root]
@ -218,17 +261,29 @@ def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconB
return True
return False
# If leaf block, check finalized/justified checkpoints as matching latest.
head_state = store.block_states[block_root]
current_epoch = compute_epoch_at_slot(get_current_slot(store))
voting_source = get_voting_source(store, block_root)
# The voting source should be at the same height as the store's justified checkpoint
correct_justified = (
store.justified_checkpoint.epoch == GENESIS_EPOCH
or head_state.current_justified_checkpoint == store.justified_checkpoint
or voting_source.epoch == store.justified_checkpoint.epoch
)
# If the previous epoch is justified, the block should be pulled-up. In this case, check that unrealized
# justification is higher than the store and that the voting source is not more than two epochs ago
if not correct_justified and is_previous_epoch_justified(store):
correct_justified = (
store.unrealized_justifications[block_root].epoch >= store.justified_checkpoint.epoch and
voting_source.epoch + 2 >= current_epoch
)
finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
correct_finalized = (
store.finalized_checkpoint.epoch == GENESIS_EPOCH
or head_state.finalized_checkpoint == store.finalized_checkpoint
or store.finalized_checkpoint.root == get_ancestor(store, block_root, finalized_slot)
)
# If expected finalized/justified, add to viable block-tree and signal viability to parent.
if correct_justified and correct_finalized:
blocks[block_root] = block
@ -272,25 +327,80 @@ def get_head(store: Store) -> Root:
head = max(children, key=lambda root: (get_weight(store, root), root))
```
#### `should_update_justified_checkpoint`
#### `update_checkpoints`
```python
def should_update_justified_checkpoint(store: Store, new_justified_checkpoint: Checkpoint) -> bool:
def update_checkpoints(store: Store, justified_checkpoint: Checkpoint, finalized_checkpoint: Checkpoint) -> None:
"""
To address the bouncing attack, only update conflicting justified
checkpoints in the fork choice if in the early slots of the epoch.
Otherwise, delay incorporation of new justified checkpoint until next epoch boundary.
See https://ethresear.ch/t/prevention-of-bouncing-attack-on-ffg/6114 for more detailed analysis and discussion.
Update checkpoints in store if necessary
"""
if compute_slots_since_epoch_start(get_current_slot(store)) < SAFE_SLOTS_TO_UPDATE_JUSTIFIED:
return True
# Update justified checkpoint
if justified_checkpoint.epoch > store.justified_checkpoint.epoch:
store.justified_checkpoint = justified_checkpoint
justified_slot = compute_start_slot_at_epoch(store.justified_checkpoint.epoch)
if not get_ancestor(store, new_justified_checkpoint.root, justified_slot) == store.justified_checkpoint.root:
return False
# Update finalized checkpoint
if finalized_checkpoint.epoch > store.finalized_checkpoint.epoch:
store.finalized_checkpoint = finalized_checkpoint
```
return True
#### `update_unrealized_checkpoints`
```python
def update_unrealized_checkpoints(store: Store, unrealized_justified_checkpoint: Checkpoint,
unrealized_finalized_checkpoint: Checkpoint) -> None:
"""
Update unrealized checkpoints in store if necessary
"""
# Update unrealized justified checkpoint
if unrealized_justified_checkpoint.epoch > store.unrealized_justified_checkpoint.epoch:
store.unrealized_justified_checkpoint = unrealized_justified_checkpoint
# Update unrealized finalized checkpoint
if unrealized_finalized_checkpoint.epoch > store.unrealized_finalized_checkpoint.epoch:
store.unrealized_finalized_checkpoint = unrealized_finalized_checkpoint
```
#### Pull-up tip helpers
##### `compute_pulled_up_tip`
```python
def compute_pulled_up_tip(store: Store, block_root: Root) -> None:
state = store.block_states[block_root].copy()
# Pull up the post-state of the block to the next epoch boundary
process_justification_and_finalization(state)
store.unrealized_justifications[block_root] = state.current_justified_checkpoint
update_unrealized_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint)
# If the block is from a prior epoch, apply the realized values
block_epoch = compute_epoch_at_slot(store.blocks[block_root].slot)
current_epoch = compute_epoch_at_slot(get_current_slot(store))
if block_epoch < current_epoch:
update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint)
```
#### `on_tick` helpers
##### `on_tick_per_slot`
```python
def on_tick_per_slot(store: Store, time: uint64) -> None:
previous_slot = get_current_slot(store)
# Update store time
store.time = time
current_slot = get_current_slot(store)
# If this is a new slot, reset store.proposer_boost_root
if current_slot > previous_slot:
store.proposer_boost_root = Root()
# If a new epoch, pull-up justification and finalization from previous epoch
if current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0:
update_checkpoints(store, store.unrealized_justified_checkpoint, store.unrealized_finalized_checkpoint)
```
#### `on_attestation` helpers
@ -323,7 +433,7 @@ def validate_on_attestation(store: Store, attestation: Attestation, is_from_bloc
# Check that the epoch number and slot number are matching
assert target.epoch == compute_epoch_at_slot(attestation.data.slot)
# Attestations target be for a known block. If target block is unknown, delay consideration until the block is found
# Attestation target must be for a known block. If target block is unknown, delay consideration until block is found
assert target.root in store.blocks
# Attestations must be for a known block. If block is unknown, delay consideration until the block is found
@ -371,27 +481,13 @@ def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIn
```python
def on_tick(store: Store, time: uint64) -> None:
previous_slot = get_current_slot(store)
# update store time
store.time = time
current_slot = get_current_slot(store)
# Reset store.proposer_boost_root if this is a new slot
if current_slot > previous_slot:
store.proposer_boost_root = Root()
# Not a new epoch, return
if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0):
return
# Update store.justified_checkpoint if a better checkpoint on the store.finalized_checkpoint chain
if store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
ancestor_at_finalized_slot = get_ancestor(store, store.best_justified_checkpoint.root, finalized_slot)
if ancestor_at_finalized_slot == store.finalized_checkpoint.root:
store.justified_checkpoint = store.best_justified_checkpoint
# If the ``store.time`` falls behind, while loop catches up slot by slot
# to ensure that every previous slot is processed with ``on_tick_per_slot``
tick_slot = (time - store.genesis_time) // SECONDS_PER_SLOT
while get_current_slot(store) < tick_slot:
previous_time = store.genesis_time + (get_current_slot(store) + 1) * SECONDS_PER_SLOT
on_tick_per_slot(store, previous_time)
on_tick_per_slot(store, time)
```
#### `on_block`
@ -414,11 +510,12 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
# Check the block is valid and compute the post-state
state = pre_state.copy()
block_root = hash_tree_root(block)
state_transition(state, signed_block, True)
# Add new block to the store
store.blocks[hash_tree_root(block)] = block
store.blocks[block_root] = block
# Add new state for this block to the store
store.block_states[hash_tree_root(block)] = state
store.block_states[block_root] = state
# Add proposer score boost if the block is timely
time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT
@ -426,17 +523,11 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
if get_current_slot(store) == block.slot and is_before_attesting_interval:
store.proposer_boost_root = hash_tree_root(block)
# Update justified checkpoint
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch:
store.best_justified_checkpoint = state.current_justified_checkpoint
if should_update_justified_checkpoint(store, state.current_justified_checkpoint):
store.justified_checkpoint = state.current_justified_checkpoint
# Update checkpoints in store if necessary
update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint)
# Update finalized checkpoint
if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch:
store.finalized_checkpoint = state.finalized_checkpoint
store.justified_checkpoint = state.current_justified_checkpoint
# Eagerly compute unrealized justification and finality
compute_pulled_up_tip(store, block_root)
```
#### `on_attestation`

View File

@ -187,7 +187,7 @@ def add_attestations_to_state(spec, state, attestations, slot):
spec.process_attestation(state, attestation)
def _get_valid_attestation_at_slot(state, spec, slot_to_attest, participation_fn=None):
def get_valid_attestation_at_slot(state, spec, slot_to_attest, participation_fn=None):
committees_per_slot = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot_to_attest))
for index in range(committees_per_slot):
def participants_filter(comm):
@ -262,7 +262,7 @@ def state_transition_with_full_block(spec,
if fill_cur_epoch and state.slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY:
slot_to_attest = state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1
if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)):
attestations = _get_valid_attestation_at_slot(
attestations = get_valid_attestation_at_slot(
state,
spec,
slot_to_attest,
@ -272,7 +272,7 @@ def state_transition_with_full_block(spec,
block.body.attestations.append(attestation)
if fill_prev_epoch:
slot_to_attest = state.slot - spec.SLOTS_PER_EPOCH + 1
attestations = _get_valid_attestation_at_slot(
attestations = get_valid_attestation_at_slot(
state,
spec,
slot_to_attest,
@ -300,7 +300,7 @@ def state_transition_with_full_attestations_block(spec, state, fill_cur_epoch, f
slots = state.slot % spec.SLOTS_PER_EPOCH
for slot_offset in range(slots):
target_slot = state.slot - slot_offset
attestations += _get_valid_attestation_at_slot(
attestations += get_valid_attestation_at_slot(
state,
spec,
target_slot,
@ -311,7 +311,7 @@ def state_transition_with_full_attestations_block(spec, state, fill_cur_epoch, f
slots = spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH
for slot_offset in range(1, slots):
target_slot = state.slot - (state.slot % spec.SLOTS_PER_EPOCH) - slot_offset
attestations += _get_valid_attestation_at_slot(
attestations += get_valid_attestation_at_slot(
state,
spec,
target_slot,

View File

@ -3,6 +3,7 @@ from eth2spec.test.exceptions import BlockNotFoundException
from eth2spec.test.helpers.attestations import (
next_epoch_with_attestations,
next_slots_with_attestations,
state_transition_with_full_block,
)
@ -16,12 +17,13 @@ def get_anchor_root(spec, state):
def tick_and_add_block(spec, store, signed_block, test_steps, valid=True,
merge_block=False, block_not_found=False, is_optimistic=False):
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
if merge_block:
assert spec.is_merge_transition_block(pre_state, signed_block.message.body)
if store.time < block_time:
on_tick_and_append_step(spec, store, block_time, test_steps)
block_time = pre_state.genesis_time + signed_block.message.slot * spec.config.SECONDS_PER_SLOT
while store.time < block_time:
time = pre_state.genesis_time + (spec.get_current_slot(store) + 1) * spec.config.SECONDS_PER_SLOT
on_tick_and_append_step(spec, store, time, test_steps)
post_state = yield from add_block(
spec, store, signed_block, test_steps,
@ -39,6 +41,11 @@ def add_attestation(spec, store, attestation, test_steps, is_from_block=False):
test_steps.append({'attestation': get_attestation_file_name(attestation)})
def add_attestations(spec, store, attestations, test_steps, is_from_block=False):
for attestation in attestations:
yield from add_attestation(spec, store, attestation, test_steps, is_from_block=is_from_block)
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)]
@ -90,6 +97,7 @@ def get_attester_slashing_file_name(attester_slashing):
def on_tick_and_append_step(spec, store, time, test_steps):
spec.on_tick(store, time)
test_steps.append({'tick': int(time)})
output_store_checks(spec, store, test_steps)
def run_on_block(spec, store, signed_block, valid=True):
@ -153,25 +161,7 @@ def add_block(spec,
assert store.blocks[block_root] == signed_block.message
assert store.block_states[block_root].hash_tree_root() == signed_block.message.state_root
if not is_optimistic:
test_steps.append({
'checks': {
'time': int(store.time),
'head': get_formatted_head_output(spec, store),
'justified_checkpoint': {
'epoch': int(store.justified_checkpoint.epoch),
'root': encode_hex(store.justified_checkpoint.root),
},
'finalized_checkpoint': {
'epoch': int(store.finalized_checkpoint.epoch),
'root': encode_hex(store.finalized_checkpoint.root),
},
'best_justified_checkpoint': {
'epoch': int(store.best_justified_checkpoint.epoch),
'root': encode_hex(store.best_justified_checkpoint.root),
},
'proposer_boost_root': encode_hex(store.proposer_boost_root),
}
})
output_store_checks(spec, store, test_steps)
return store.block_states[signed_block.message.hash_tree_root()]
@ -217,6 +207,32 @@ def get_formatted_head_output(spec, store):
}
def output_head_check(spec, store, test_steps):
test_steps.append({
'checks': {
'head': get_formatted_head_output(spec, store),
}
})
def output_store_checks(spec, store, test_steps):
test_steps.append({
'checks': {
'time': int(store.time),
'head': get_formatted_head_output(spec, store),
'justified_checkpoint': {
'epoch': int(store.justified_checkpoint.epoch),
'root': encode_hex(store.justified_checkpoint.root),
},
'finalized_checkpoint': {
'epoch': int(store.finalized_checkpoint.epoch),
'root': encode_hex(store.finalized_checkpoint.root),
},
'proposer_boost_root': encode_hex(store.proposer_boost_root),
}
})
def apply_next_epoch_with_attestations(spec,
state,
store,
@ -263,6 +279,39 @@ def apply_next_slots_with_attestations(spec,
return post_state, store, last_signed_block
def is_ready_to_justify(spec, state):
"""
Check if the given ``state`` will trigger justification updates at epoch boundary.
"""
temp_state = state.copy()
spec.process_justification_and_finalization(temp_state)
return temp_state.current_justified_checkpoint.epoch > state.current_justified_checkpoint.epoch
def find_next_justifying_slot(spec,
state,
fill_cur_epoch,
fill_prev_epoch,
participation_fn=None):
temp_state = state.copy()
signed_blocks = []
justifying_slot = None
while justifying_slot is None:
signed_block = state_transition_with_full_block(
spec,
temp_state,
fill_cur_epoch,
fill_prev_epoch,
participation_fn,
)
signed_blocks.append(signed_block)
if is_ready_to_justify(spec, temp_state):
justifying_slot = temp_state.slot
return signed_blocks, justifying_slot
def get_pow_block_file_name(pow_block):
return f"pow_block_{encode_hex(pow_block.block_hash)}"

View File

@ -1,9 +1,9 @@
import random
from eth_utils import encode_hex
from eth2spec.test.context import (
spec_state_test,
with_all_phases,
with_altair_and_later,
with_presets,
)
from eth2spec.test.helpers.attestations import get_valid_attestation, next_epoch_with_attestations
@ -22,6 +22,8 @@ from eth2spec.test.helpers.fork_choice import (
add_attestation,
tick_and_run_on_attestation,
tick_and_add_block,
output_head_check,
apply_next_epoch_with_attestations,
)
from eth2spec.test.helpers.forks import (
is_post_altair,
@ -71,11 +73,7 @@ def test_chain_no_attestations(spec, state):
anchor_root = get_anchor_root(spec, state)
assert spec.get_head(store) == anchor_root
test_steps.append({
'checks': {
'head': get_formatted_head_output(spec, store),
}
})
output_head_check(spec, store, test_steps)
# On receiving a block of `GENESIS_SLOT + 1` slot
block_1 = build_empty_block_for_next_slot(spec, state)
@ -88,11 +86,7 @@ def test_chain_no_attestations(spec, state):
yield from tick_and_add_block(spec, store, signed_block_2, test_steps)
assert spec.get_head(store) == spec.hash_tree_root(block_2)
test_steps.append({
'checks': {
'head': get_formatted_head_output(spec, store),
}
})
output_head_check(spec, store, test_steps)
yield 'steps', test_steps
@ -109,11 +103,7 @@ def test_split_tie_breaker_no_attestations(spec, state):
yield 'anchor_block', anchor_block
anchor_root = get_anchor_root(spec, state)
assert spec.get_head(store) == anchor_root
test_steps.append({
'checks': {
'head': get_formatted_head_output(spec, store),
}
})
output_head_check(spec, store, test_steps)
# Create block at slot 1
block_1_state = genesis_state.copy()
@ -135,11 +125,7 @@ def test_split_tie_breaker_no_attestations(spec, state):
highest_root = max(spec.hash_tree_root(block_1), spec.hash_tree_root(block_2))
assert spec.get_head(store) == highest_root
test_steps.append({
'checks': {
'head': get_formatted_head_output(spec, store),
}
})
output_head_check(spec, store, test_steps)
yield 'steps', test_steps
@ -156,11 +142,7 @@ def test_shorter_chain_but_heavier_weight(spec, state):
yield 'anchor_block', anchor_block
anchor_root = get_anchor_root(spec, state)
assert spec.get_head(store) == anchor_root
test_steps.append({
'checks': {
'head': get_formatted_head_output(spec, store),
}
})
output_head_check(spec, store, test_steps)
# build longer tree
long_state = genesis_state.copy()
@ -183,11 +165,7 @@ def test_shorter_chain_but_heavier_weight(spec, state):
yield from tick_and_run_on_attestation(spec, store, short_attestation, test_steps)
assert spec.get_head(store) == spec.hash_tree_root(short_block)
test_steps.append({
'checks': {
'head': get_formatted_head_output(spec, store),
}
})
output_head_check(spec, store, test_steps)
yield 'steps', test_steps
@ -203,11 +181,7 @@ def test_filtered_block_tree(spec, state):
yield 'anchor_block', anchor_block
anchor_root = get_anchor_root(spec, state)
assert spec.get_head(store) == anchor_root
test_steps.append({
'checks': {
'head': get_formatted_head_output(spec, store),
}
})
output_head_check(spec, store, test_steps)
# transition state past initial couple of epochs
next_epoch(spec, state)
@ -227,13 +201,7 @@ def test_filtered_block_tree(spec, state):
# the last block in the branch should be the head
expected_head_root = spec.hash_tree_root(signed_blocks[-1].message)
assert spec.get_head(store) == expected_head_root
test_steps.append({
'checks': {
'head': get_formatted_head_output(spec, store),
'justified_checkpoint_root': encode_hex(store.justified_checkpoint.root),
}
})
output_head_check(spec, store, test_steps)
#
# create branch containing the justified block but not containing enough on
@ -274,11 +242,7 @@ def test_filtered_block_tree(spec, state):
# ensure that get_head still returns the head from the previous branch
assert spec.get_head(store) == expected_head_root
test_steps.append({
'checks': {
'head': get_formatted_head_output(spec, store)
}
})
output_head_check(spec, store, test_steps)
yield 'steps', test_steps
@ -295,11 +259,7 @@ def test_proposer_boost_correct_head(spec, state):
yield 'anchor_block', anchor_block
anchor_root = get_anchor_root(spec, state)
assert spec.get_head(store) == anchor_root
test_steps.append({
'checks': {
'head': get_formatted_head_output(spec, store),
}
})
output_head_check(spec, store, test_steps)
# Build block that serves as head ONLY on timely arrival, and ONLY in that slot
state_1 = genesis_state.copy()
@ -337,19 +297,14 @@ def test_proposer_boost_correct_head(spec, state):
on_tick_and_append_step(spec, store, time, test_steps)
assert store.proposer_boost_root == spec.Root()
assert spec.get_head(store) == spec.hash_tree_root(block_2)
test_steps.append({
'checks': {
'head': get_formatted_head_output(spec, store),
}
})
output_head_check(spec, store, test_steps)
yield 'steps', test_steps
@with_all_phases
@spec_state_test
def test_discard_equivocations(spec, state):
def test_discard_equivocations_on_attester_slashing(spec, state):
test_steps = []
genesis_state = state.copy()
@ -359,11 +314,7 @@ def test_discard_equivocations(spec, state):
yield 'anchor_block', anchor_block
anchor_root = get_anchor_root(spec, state)
assert spec.get_head(store) == anchor_root
test_steps.append({
'checks': {
'head': get_formatted_head_output(spec, store),
}
})
output_head_check(spec, store, test_steps)
# Build block that serves as head before discarding equivocations
state_1 = genesis_state.copy()
@ -418,11 +369,359 @@ def test_discard_equivocations(spec, state):
# The head should revert to block_2
yield from add_attester_slashing(spec, store, attester_slashing, test_steps)
assert spec.get_head(store) == spec.hash_tree_root(block_2)
test_steps.append({
'checks': {
'head': get_formatted_head_output(spec, store),
}
})
output_head_check(spec, store, test_steps)
yield 'steps', test_steps
@with_all_phases
@spec_state_test
@with_presets([MINIMAL], reason="too slow")
def test_discard_equivocations_slashed_validator_censoring(spec, state):
# Check that the store does not count LMD votes from validators that are slashed in the justified state
test_steps = []
# Initialization
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 0
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 0
assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0
# We will slash all validators voting at the 2nd slot of epoch 0
current_slot = spec.get_current_slot(store)
eqv_slot = current_slot + 1
eqv_epoch = spec.compute_epoch_at_slot(eqv_slot)
assert eqv_slot % spec.SLOTS_PER_EPOCH == 1
assert eqv_epoch == 0
slashed_validators = []
comm_count = spec.get_committee_count_per_slot(state, eqv_epoch)
for comm_index in range(comm_count):
comm = spec.get_beacon_committee(state, eqv_slot, comm_index)
slashed_validators += comm
assert len(slashed_validators) > 0
# Slash those validators in the state
for val_index in slashed_validators:
state.validators[val_index].slashed = True
# Store this state as the anchor state
anchor_state = state.copy()
# Generate an anchor block with correct state root
anchor_block = spec.BeaconBlock(state_root=anchor_state.hash_tree_root())
yield 'anchor_state', anchor_state
yield 'anchor_block', anchor_block
# Get a new store with the anchor state & anchor block
store = spec.get_forkchoice_store(anchor_state, anchor_block)
# Now generate the store checks
current_time = anchor_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
# Create two competing blocks at eqv_slot
next_slots(spec, state, eqv_slot - state.slot - 1)
assert state.slot == eqv_slot - 1
state_1 = state.copy()
block_1 = build_empty_block_for_next_slot(spec, state_1)
signed_block_1 = state_transition_and_sign_block(spec, state_1, block_1)
state_2 = state.copy()
block_2 = build_empty_block_for_next_slot(spec, state_2)
block_2.body.graffiti = block_2.body.graffiti = b'\x42' * 32
signed_block_2 = state_transition_and_sign_block(spec, state_2, block_2)
assert block_1.slot == block_2.slot == eqv_slot
# Add both blocks to the store
yield from tick_and_add_block(spec, store, signed_block_1, test_steps)
yield from tick_and_add_block(spec, store, signed_block_2, test_steps)
# Find out which block will win in tie breaking
if spec.hash_tree_root(block_1) < spec.hash_tree_root(block_2):
block_low_root = block_1.hash_tree_root()
block_low_root_post_state = state_1
block_high_root = block_2.hash_tree_root()
else:
block_low_root = block_2.hash_tree_root()
block_low_root_post_state = state_2
block_high_root = block_1.hash_tree_root()
assert block_low_root < block_high_root
# Tick to next slot so proposer boost does not apply
current_time = store.genesis_time + (block_1.slot + 1) * spec.config.SECONDS_PER_SLOT
on_tick_and_append_step(spec, store, current_time, test_steps)
# Check that block with higher root wins
assert spec.get_head(store) == block_high_root
# Create attestation for block with lower root
attestation = get_valid_attestation(spec, block_low_root_post_state, slot=eqv_slot, index=0, signed=True)
# Check that all attesting validators were slashed in the anchor state
att_comm = spec.get_beacon_committee(block_low_root_post_state, eqv_slot, 0)
for i in att_comm:
assert anchor_state.validators[i].slashed
# Add attestation to the store
yield from add_attestation(spec, store, attestation, test_steps)
# Check that block with higher root still wins
assert spec.get_head(store) == block_high_root
output_head_check(spec, store, test_steps)
yield 'steps', test_steps
@with_altair_and_later
@spec_state_test
@with_presets([MINIMAL], reason="too slow")
def test_voting_source_within_two_epoch(spec, state):
"""
Check that the store allows for a head block that has:
- store.voting_source[block_root].epoch != store.justified_checkpoint.epoch, and
- store.unrealized_justifications[block_root].epoch >= store.justified_checkpoint.epoch, and
- store.voting_source[block_root].epoch + 2 >= current_epoch, and
- store.finalized_checkpoint.root == get_ancestor(store, block_root, finalized_slot)
"""
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
next_epoch(spec, state)
on_tick_and_append_step(spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps)
# Fill epoch 1 to 3
for _ in range(3):
state, store, _ = yield from apply_next_epoch_with_attestations(
spec, state, store, True, True, test_steps=test_steps)
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
assert store.finalized_checkpoint.epoch == 2
# Copy the state to use later
fork_state = state.copy()
# Fill epoch 4
state, store, _ = yield from apply_next_epoch_with_attestations(
spec, state, store, True, True, test_steps=test_steps)
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 4
assert store.finalized_checkpoint.epoch == 3
# Create a fork from the earlier saved state
next_epoch(spec, fork_state)
assert spec.compute_epoch_at_slot(fork_state.slot) == 5
_, signed_blocks, fork_state = next_epoch_with_attestations(spec, fork_state, True, True)
# Only keep the blocks from epoch 5, so discard the last generated block
signed_blocks = signed_blocks[:-1]
last_fork_block = signed_blocks[-1].message
assert spec.compute_epoch_at_slot(last_fork_block.slot) == 5
# Now add the fork to the store
for signed_block in signed_blocks:
yield from tick_and_add_block(spec, store, signed_block, test_steps)
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 4
assert store.finalized_checkpoint.epoch == 3
# Check that the last block from the fork is the head
# LMD votes for the competing branch are overwritten so this fork should win
last_fork_block_root = last_fork_block.hash_tree_root()
# assert store.voting_source[last_fork_block_root].epoch != store.justified_checkpoint.epoch
assert store.unrealized_justifications[last_fork_block_root].epoch >= store.justified_checkpoint.epoch
# assert store.voting_source[last_fork_block_root].epoch + 2 >= \
# spec.compute_epoch_at_slot(spec.get_current_slot(store))
finalized_slot = spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
assert store.finalized_checkpoint.root == spec.get_ancestor(store, last_fork_block_root, finalized_slot)
assert spec.get_head(store) == last_fork_block_root
yield 'steps', test_steps
@with_altair_and_later
@spec_state_test
@with_presets([MINIMAL], reason="too slow")
def test_voting_source_beyond_two_epoch(spec, state):
"""
Check that the store doesn't allow for a head block that has:
- store.voting_source[block_root].epoch != store.justified_checkpoint.epoch, and
- store.unrealized_justifications[block_root].epoch >= store.justified_checkpoint.epoch, and
- store.voting_source[block_root].epoch + 2 < current_epoch, and
- store.finalized_checkpoint.root == get_ancestor(store, block_root, finalized_slot)
"""
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
next_epoch(spec, state)
on_tick_and_append_step(spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps)
# Fill epoch 1 to 3
for _ in range(3):
state, store, _ = yield from apply_next_epoch_with_attestations(
spec, state, store, True, True, test_steps=test_steps)
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
assert store.finalized_checkpoint.epoch == 2
# Copy the state to use later
fork_state = state.copy()
# Fill epoch 4 and 5
for _ in range(2):
state, store, _ = yield from apply_next_epoch_with_attestations(
spec, state, store, True, True, test_steps=test_steps)
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 6
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 5
assert store.finalized_checkpoint.epoch == 4
# Create a fork from the earlier saved state
for _ in range(2):
next_epoch(spec, fork_state)
assert spec.compute_epoch_at_slot(fork_state.slot) == 6
assert fork_state.current_justified_checkpoint.epoch == 3
_, signed_blocks, fork_state = next_epoch_with_attestations(spec, fork_state, True, True)
# Only keep the blocks from epoch 6, so discard the last generated block
signed_blocks = signed_blocks[:-1]
last_fork_block = signed_blocks[-1].message
assert spec.compute_epoch_at_slot(last_fork_block.slot) == 6
# Store the head before adding the fork to the store
correct_head = spec.get_head(store)
# Now add the fork to the store
for signed_block in signed_blocks:
yield from tick_and_add_block(spec, store, signed_block, test_steps)
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 6
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 5
assert store.finalized_checkpoint.epoch == 4
last_fork_block_root = last_fork_block.hash_tree_root()
last_fork_block_state = store.block_states[last_fork_block_root]
assert last_fork_block_state.current_justified_checkpoint.epoch == 3
# Check that the head is unchanged
# assert store.voting_source[last_fork_block_root].epoch != store.justified_checkpoint.epoch
assert store.unrealized_justifications[last_fork_block_root].epoch >= store.justified_checkpoint.epoch
# assert store.voting_source[last_fork_block_root].epoch + 2 < \
# spec.compute_epoch_at_slot(spec.get_current_slot(store))
finalized_slot = spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
assert store.finalized_checkpoint.root == spec.get_ancestor(store, last_fork_block_root, finalized_slot)
assert spec.get_head(store) == correct_head
yield 'steps', test_steps
"""
Note:
We are unable to generate test vectors that check failure of the correct_finalized condition.
We cannot generate a block that:
- has !correct_finalized, and
- has correct_justified, and
- is a descendant of store.justified_checkpoint.root
The block being a descendant of store.justified_checkpoint.root is necessary because
filter_block_tree descends the tree starting at store.justified_checkpoint.root
@with_all_phases
@spec_state_test
def test_incorrect_finalized(spec, state):
# Check that the store doesn't allow for a head block that has:
# - store.voting_source[block_root].epoch == store.justified_checkpoint.epoch, and
# - store.finalized_checkpoint.epoch != GENESIS_EPOCH, and
# - store.finalized_checkpoint.root != get_ancestor(store, block_root, finalized_slot)
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
next_epoch(spec, state)
on_tick_and_append_step(spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps)
# Fill epoch 1 to 4
for _ in range(4):
state, store, _ = yield from apply_next_epoch_with_attestations(
spec, state, store, True, True, test_steps=test_steps)
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 4
assert store.finalized_checkpoint.epoch == 3
# Identify the fork block as the last block in epoch 4
fork_block_root = state.latest_block_header.parent_root
fork_block = store.blocks[fork_block_root]
assert spec.compute_epoch_at_slot(fork_block.slot) == 4
# Copy the state to use later
fork_state = store.block_states[fork_block_root].copy()
assert spec.compute_epoch_at_slot(fork_state.slot) == 4
assert fork_state.current_justified_checkpoint.epoch == 3
assert fork_state.finalized_checkpoint.epoch == 2
# Create a fork from the earlier saved state
for _ in range(2):
next_epoch(spec, fork_state)
assert spec.compute_epoch_at_slot(fork_state.slot) == 6
assert fork_state.current_justified_checkpoint.epoch == 4
assert fork_state.finalized_checkpoint.epoch == 3
# Fill epoch 6
signed_blocks = []
_, signed_blocks_1, fork_state = next_epoch_with_attestations(spec, fork_state, True, False)
signed_blocks += signed_blocks_1
assert spec.compute_epoch_at_slot(fork_state.slot) == 7
# Check that epoch 6 is justified in this fork - it will be used as voting source for the tip of this fork
assert fork_state.current_justified_checkpoint.epoch == 6
assert fork_state.finalized_checkpoint.epoch == 3
# Create a chain in epoch 7 that has new justification for epoch 7
_, signed_blocks_2, fork_state = next_epoch_with_attestations(spec, fork_state, True, False)
# Only keep the blocks from epoch 7, so discard the last generated block
signed_blocks_2 = signed_blocks_2[:-1]
signed_blocks += signed_blocks_2
last_fork_block = signed_blocks[-1].message
assert spec.compute_epoch_at_slot(last_fork_block.slot) == 7
# Now add the fork to the store
for signed_block in signed_blocks:
yield from tick_and_add_block(spec, store, signed_block, test_steps)
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 7
assert store.justified_checkpoint.epoch == 6
assert store.finalized_checkpoint.epoch == 3
# Fill epoch 5 and 6 in the original chain
for _ in range(2):
state, store, signed_head_block = yield from apply_next_epoch_with_attestations(
spec, state, store, True, False, test_steps=test_steps)
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 7
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 6
assert store.finalized_checkpoint.epoch == 5
# Store the expected head
head_root = signed_head_block.message.hash_tree_root()
# Check that the head is unchanged
last_fork_block_root = last_fork_block.hash_tree_root()
assert store.voting_source[last_fork_block_root].epoch == store.justified_checkpoint.epoch
assert store.finalized_checkpoint.epoch != spec.GENESIS_EPOCH
finalized_slot = spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
assert store.finalized_checkpoint.root != spec.get_ancestor(store, last_fork_block_root, finalized_slot)
assert spec.get_head(store) != last_fork_block_root
assert spec.get_head(store) == head_root
yield 'steps', test_steps
"""

View File

@ -0,0 +1,498 @@
from eth2spec.test.context import (
spec_state_test,
with_all_phases,
with_presets,
)
from eth2spec.test.helpers.constants import (
MINIMAL,
)
from eth2spec.test.helpers.attestations import (
state_transition_with_full_block,
get_valid_attestation,
get_valid_attestation_at_slot,
)
from eth2spec.test.helpers.block import (
build_empty_block,
build_empty_block_for_next_slot,
)
from eth2spec.test.helpers.fork_choice import (
get_genesis_forkchoice_store_and_block,
on_tick_and_append_step,
add_attestations,
tick_and_add_block,
apply_next_epoch_with_attestations,
find_next_justifying_slot,
is_ready_to_justify,
)
from eth2spec.test.helpers.state import (
state_transition_and_sign_block,
next_epoch,
next_slot,
transition_to,
)
TESTING_PRESETS = [MINIMAL]
@with_all_phases
@spec_state_test
@with_presets(TESTING_PRESETS, reason="too slow")
def test_simple_attempted_reorg_without_enough_ffg_votes(spec, state):
"""
[Case 1]
{ epoch 4 }{ epoch 5 }
[c4]<--[a]<--[-]<--[y]
____[-]<--[z]
At c4, c3 is the latest justified checkpoint (or something earlier)
The block y doesn't have enough votes to justify c4.
The block z also doesn't have enough votes to justify c4.
"""
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
next_epoch(spec, state)
on_tick_and_append_step(spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps)
# Fill epoch 1 to 3
for _ in range(3):
state, store, _ = yield from apply_next_epoch_with_attestations(
spec, state, store, True, True, test_steps=test_steps)
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
# create block_a, it needs 2 more full blocks to justify epoch 4
signed_blocks, justifying_slot = find_next_justifying_slot(spec, state, True, True)
assert spec.compute_epoch_at_slot(justifying_slot) == spec.get_current_epoch(state)
for signed_block in signed_blocks[:-2]:
yield from tick_and_add_block(spec, store, signed_block, test_steps)
assert spec.get_head(store) == signed_block.message.hash_tree_root()
state = store.block_states[spec.get_head(store)].copy()
assert state.current_justified_checkpoint.epoch == 3
next_slot(spec, state)
state_a = state.copy()
# to test the "no withholding" situation, temporarily store the blocks in lists
signed_blocks_of_y = []
signed_blocks_of_z = []
# add an empty block on chain y
block_y = build_empty_block_for_next_slot(spec, state)
signed_block_y = state_transition_and_sign_block(spec, state, block_y)
signed_blocks_of_y.append(signed_block_y)
# chain y has some on-chain attestations, but not enough to justify c4
signed_block_y = state_transition_with_full_block(spec, state, True, True)
assert not is_ready_to_justify(spec, state)
signed_blocks_of_y.append(signed_block_y)
assert store.justified_checkpoint.epoch == 3
state = state_a.copy()
signed_block_z = None
# add one block on chain z, which is not enough to justify c4
attestation = get_valid_attestation(spec, state, slot=state.slot, signed=True)
block_z = build_empty_block_for_next_slot(spec, state)
block_z.body.attestations = [attestation]
signed_block_z = state_transition_and_sign_block(spec, state, block_z)
signed_blocks_of_z.append(signed_block_z)
# add an empty block on chain z
block_z = build_empty_block_for_next_slot(spec, state)
signed_block_z = state_transition_and_sign_block(spec, state, block_z)
signed_blocks_of_z.append(signed_block_z)
# ensure z couldn't justify c4
assert not is_ready_to_justify(spec, state)
# apply blocks to store
# (i) slot block_a.slot + 1
signed_block_y = signed_blocks_of_y.pop(0)
yield from tick_and_add_block(spec, store, signed_block_y, test_steps)
# apply block of chain `z`
signed_block_z = signed_blocks_of_z.pop(0)
yield from tick_and_add_block(spec, store, signed_block_z, test_steps)
# (ii) slot block_a.slot + 2
# apply block of chain `z`
signed_block_z = signed_blocks_of_z.pop(0)
yield from tick_and_add_block(spec, store, signed_block_z, test_steps)
# apply block of chain `y`
signed_block_y = signed_blocks_of_y.pop(0)
yield from tick_and_add_block(spec, store, signed_block_y, test_steps)
# chain `y` remains the winner since it arrives earlier than `z`
assert spec.get_head(store) == signed_block_y.message.hash_tree_root()
assert len(signed_blocks_of_y) == len(signed_blocks_of_z) == 0
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4
# tick to the prior of the epoch boundary
slot = state.slot + spec.SLOTS_PER_EPOCH - (state.slot % spec.SLOTS_PER_EPOCH) - 1
current_time = slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
on_tick_and_append_step(spec, store, current_time, test_steps)
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4
# chain `y` reminds the winner
assert spec.get_head(store) == signed_block_y.message.hash_tree_root()
# to next block
next_epoch(spec, state)
current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
on_tick_and_append_step(spec, store, current_time, test_steps)
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5
assert spec.get_head(store) == signed_block_y.message.hash_tree_root()
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
yield 'steps', test_steps
def _run_delayed_justification(spec, state, attemped_reorg, is_justifying_previous_epoch):
"""
"""
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
next_epoch(spec, state)
on_tick_and_append_step(spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps)
# Fill epoch 1 to 2
for _ in range(2):
state, store, _ = yield from apply_next_epoch_with_attestations(
spec, state, store, True, True, test_steps=test_steps)
if is_justifying_previous_epoch:
state, store, _ = yield from apply_next_epoch_with_attestations(
spec, state, store, False, False, test_steps=test_steps)
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 2
else:
state, store, _ = yield from apply_next_epoch_with_attestations(
spec, state, store, True, True, test_steps=test_steps)
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
if is_justifying_previous_epoch:
# try to find the block that can justify epoch 3
signed_blocks, justifying_slot = find_next_justifying_slot(spec, state, False, True)
else:
# try to find the block that can justify epoch 4
signed_blocks, justifying_slot = find_next_justifying_slot(spec, state, True, True)
assert spec.compute_epoch_at_slot(justifying_slot) == spec.get_current_epoch(state)
for signed_block in signed_blocks:
yield from tick_and_add_block(spec, store, signed_block, test_steps)
spec.get_head(store) == signed_block.message.hash_tree_root()
state = store.block_states[spec.get_head(store)].copy()
if is_justifying_previous_epoch:
assert state.current_justified_checkpoint.epoch == 2
else:
assert state.current_justified_checkpoint.epoch == 3
assert is_ready_to_justify(spec, state)
state_b = state.copy()
# add chain y
if is_justifying_previous_epoch:
signed_block_y = state_transition_with_full_block(spec, state, False, True)
else:
signed_block_y = state_transition_with_full_block(spec, state, True, True)
yield from tick_and_add_block(spec, store, signed_block_y, test_steps)
assert spec.get_head(store) == signed_block_y.message.hash_tree_root()
if is_justifying_previous_epoch:
assert store.justified_checkpoint.epoch == 2
else:
assert store.justified_checkpoint.epoch == 3
# add attestations of y
temp_state = state.copy()
next_slot(spec, temp_state)
attestations_for_y = list(get_valid_attestation_at_slot(temp_state, spec, signed_block_y.message.slot))
current_time = temp_state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
on_tick_and_append_step(spec, store, current_time, test_steps)
yield from add_attestations(spec, store, attestations_for_y, test_steps)
assert spec.get_head(store) == signed_block_y.message.hash_tree_root()
if attemped_reorg:
# add chain z
state = state_b.copy()
slot = state.slot + spec.SLOTS_PER_EPOCH - (state.slot % spec.SLOTS_PER_EPOCH) - 1
transition_to(spec, state, slot)
block_z = build_empty_block_for_next_slot(spec, state)
assert spec.compute_epoch_at_slot(block_z.slot) == 5
signed_block_z = state_transition_and_sign_block(spec, state, block_z)
yield from tick_and_add_block(spec, store, signed_block_z, test_steps)
else:
# next epoch
state = state_b.copy()
next_epoch(spec, state)
current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
on_tick_and_append_step(spec, store, current_time, test_steps)
# no reorg
assert spec.get_head(store) == signed_block_y.message.hash_tree_root()
if is_justifying_previous_epoch:
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
else:
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 4
yield 'steps', test_steps
@with_all_phases
@spec_state_test
@with_presets(TESTING_PRESETS, reason="too slow")
def test_simple_attempted_reorg_delayed_justification_current_epoch(spec, state):
"""
[Case 2]
{ epoch 4 }{ epoch 5 }
[c4]<--[b]<--[y]
______________[z]
At c4, c3 is the latest justified checkpoint (or something earlier)
block_b: the block that can justify c4.
z: the child of block of x at the first slot of epoch 5.
block z can reorg the chain from block y.
"""
yield from _run_delayed_justification(spec, state, attemped_reorg=True, is_justifying_previous_epoch=False)
def _run_include_votes_of_another_empty_chain(spec, state, enough_ffg, is_justifying_previous_epoch):
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
next_epoch(spec, state)
on_tick_and_append_step(spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps)
# Fill epoch 1 to 2
for _ in range(2):
state, store, _ = yield from apply_next_epoch_with_attestations(
spec, state, store, True, True, test_steps=test_steps)
if is_justifying_previous_epoch:
block_a = build_empty_block_for_next_slot(spec, state)
signed_block_a = state_transition_and_sign_block(spec, state, block_a)
yield from tick_and_add_block(spec, store, signed_block_a, test_steps)
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 2
else:
# fill one more epoch
state, store, _ = yield from apply_next_epoch_with_attestations(
spec, state, store, True, True, test_steps=test_steps)
signed_block_a = state_transition_with_full_block(spec, state, True, True)
yield from tick_and_add_block(spec, store, signed_block_a, test_steps)
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
spec.get_head(store) == signed_block_a.message.hash_tree_root()
state = store.block_states[spec.get_head(store)].copy()
if is_justifying_previous_epoch:
assert state.current_justified_checkpoint.epoch == 2
else:
assert state.current_justified_checkpoint.epoch == 3
state_a = state.copy()
if is_justifying_previous_epoch:
# try to find the block that can justify epoch 3
_, justifying_slot = find_next_justifying_slot(spec, state, False, True)
else:
# try to find the block that can justify epoch 4
_, justifying_slot = find_next_justifying_slot(spec, state, True, True)
last_slot_of_z = justifying_slot if enough_ffg else justifying_slot - 1
last_slot_of_y = justifying_slot if is_justifying_previous_epoch else last_slot_of_z - 1
# to test the "no withholding" situation, temporarily store the blocks in lists
signed_blocks_of_y = []
# build an empty chain to the slot prior epoch boundary
signed_blocks_of_empty_chain = []
states_of_empty_chain = []
for slot in range(state.slot + 1, last_slot_of_y + 1):
block = build_empty_block(spec, state, slot=slot)
signed_block = state_transition_and_sign_block(spec, state, block)
signed_blocks_of_empty_chain.append(signed_block)
states_of_empty_chain.append(state.copy())
signed_blocks_of_y.append(signed_block)
signed_block_y = signed_blocks_of_empty_chain[-1]
# create 2/3 votes for the empty chain
attestations_for_y = []
# target_is_current = not is_justifying_previous_epoch
attestations = list(get_valid_attestation_at_slot(state, spec, state_a.slot))
attestations_for_y.append(attestations)
for state in states_of_empty_chain:
attestations = list(get_valid_attestation_at_slot(state, spec, state.slot))
attestations_for_y.append(attestations)
state = state_a.copy()
signed_block_z = None
for slot in range(state_a.slot + 1, last_slot_of_z + 1):
# apply chain y, the empty chain
if slot <= last_slot_of_y and len(signed_blocks_of_y) > 0:
signed_block_y = signed_blocks_of_y.pop(0)
assert signed_block_y.message.slot == slot
yield from tick_and_add_block(spec, store, signed_block_y, test_steps)
# apply chain z, a fork chain that includes these attestations_for_y
block = build_empty_block(spec, state, slot=slot)
if (
len(attestations_for_y) > 0 and (
(not is_justifying_previous_epoch)
or (is_justifying_previous_epoch and attestations_for_y[0][0].data.slot == slot - 5)
)
):
block.body.attestations = attestations_for_y.pop(0)
signed_block_z = state_transition_and_sign_block(spec, state, block)
if signed_block_y != signed_block_z:
yield from tick_and_add_block(spec, store, signed_block_z, test_steps)
if is_ready_to_justify(spec, state):
break
assert spec.get_head(store) == signed_block_y.message.hash_tree_root()
if is_justifying_previous_epoch:
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 2
else:
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
if enough_ffg:
assert is_ready_to_justify(spec, state)
else:
assert not is_ready_to_justify(spec, state)
# to next epoch
next_epoch(spec, state)
current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
on_tick_and_append_step(spec, store, current_time, test_steps)
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5
if enough_ffg:
# reorg
assert spec.get_head(store) == signed_block_z.message.hash_tree_root()
if is_justifying_previous_epoch:
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
else:
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 4
else:
# no reorg
assert spec.get_head(store) == signed_block_y.message.hash_tree_root()
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
yield 'steps', test_steps
@with_all_phases
@spec_state_test
@with_presets(TESTING_PRESETS, reason="too slow")
def test_include_votes_another_empty_chain_with_enough_ffg_votes_current_epoch(spec, state):
"""
[Case 3]
"""
yield from _run_include_votes_of_another_empty_chain(
spec, state, enough_ffg=True, is_justifying_previous_epoch=False)
@with_all_phases
@spec_state_test
@with_presets(TESTING_PRESETS, reason="too slow")
def test_include_votes_another_empty_chain_without_enough_ffg_votes_current_epoch(spec, state):
"""
[Case 4]
"""
yield from _run_include_votes_of_another_empty_chain(
spec, state, enough_ffg=False, is_justifying_previous_epoch=False)
@with_all_phases
@spec_state_test
@with_presets(TESTING_PRESETS, reason="too slow")
def test_delayed_justification_current_epoch(spec, state):
"""
[Case 5]
To compare with ``test_simple_attempted_reorg_delayed_justification_current_epoch``,
this is the basic case if there is no chain z
{ epoch 4 }{ epoch 5 }
[c4]<--[b]<--[y]
At c4, c3 is the latest justified checkpoint.
block_b: the block that can justify c4.
"""
yield from _run_delayed_justification(spec, state, attemped_reorg=False, is_justifying_previous_epoch=False)
@with_all_phases
@spec_state_test
@with_presets(TESTING_PRESETS, reason="too slow")
def test_delayed_justification_previous_epoch(spec, state):
"""
[Case 6]
Similar to ``test_delayed_justification_current_epoch``,
but includes attestations during epoch N to justify checkpoint N-1.
{ epoch 3 }{ epoch 4 }{ epoch 5 }
[c3]<---------------[c4]---[b]<---------------------------------[y]
"""
yield from _run_delayed_justification(spec, state, attemped_reorg=False, is_justifying_previous_epoch=True)
@with_all_phases
@spec_state_test
@with_presets(TESTING_PRESETS, reason="too slow")
def test_simple_attempted_reorg_delayed_justification_previous_epoch(spec, state):
"""
[Case 7]
Similar to ``test_simple_attempted_reorg_delayed_justification_current_epoch``,
but includes attestations during epoch N to justify checkpoint N-1.
{ epoch 3 }{ epoch 4 }{ epoch 5 }
[c3]<---------------[c4]<--[b]<--[y]
______________[z]
At c4, c2 is the latest justified checkpoint.
block_b: the block that can justify c3.
z: the child of block of x at the first slot of epoch 5.
block z can reorg the chain from block y.
"""
yield from _run_delayed_justification(spec, state, attemped_reorg=True, is_justifying_previous_epoch=True)
@with_all_phases
@spec_state_test
@with_presets(TESTING_PRESETS, reason="too slow")
def test_include_votes_another_empty_chain_with_enough_ffg_votes_previous_epoch(spec, state):
"""
[Case 8]
Similar to ``test_include_votes_another_empty_chain_with_enough_ffg_votes_current_epoch``,
but includes attestations during epoch N to justify checkpoint N-1.
"""
yield from _run_include_votes_of_another_empty_chain(
spec, state, enough_ffg=True, is_justifying_previous_epoch=True)

View File

@ -0,0 +1,205 @@
from eth2spec.test.context import (
spec_state_test,
with_altair_and_later,
with_presets,
)
from eth2spec.test.helpers.constants import (
MINIMAL,
)
from eth2spec.test.helpers.attestations import (
state_transition_with_full_block,
)
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,
apply_next_epoch_with_attestations,
find_next_justifying_slot,
)
from eth2spec.test.helpers.state import (
state_transition_and_sign_block,
next_epoch,
)
TESTING_PRESETS = [MINIMAL]
@with_altair_and_later
@spec_state_test
@with_presets(TESTING_PRESETS, reason="too slow")
def test_withholding_attack(spec, state):
"""
"""
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
next_epoch(spec, state)
on_tick_and_append_step(spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps)
# Fill epoch 1 to 3
for _ in range(3):
state, store, _ = yield from apply_next_epoch_with_attestations(
spec, state, store, True, True, test_steps=test_steps)
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
# Create the attack block that includes justifying attestations for epoch 4
# This block is withheld & revealed only in epoch 5
signed_blocks, justifying_slot = find_next_justifying_slot(spec, state, True, False)
assert spec.compute_epoch_at_slot(justifying_slot) == spec.get_current_epoch(state)
assert len(signed_blocks) > 1
signed_attack_block = signed_blocks[-1]
for signed_block in signed_blocks[:-1]:
yield from tick_and_add_block(spec, store, signed_block, test_steps)
assert spec.get_head(store) == signed_block.message.hash_tree_root()
assert spec.get_head(store) == signed_blocks[-2].message.hash_tree_root()
state = store.block_states[spec.get_head(store)].copy()
assert spec.compute_epoch_at_slot(state.slot) == 4
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
# Create an honest chain in epoch 5 that includes the justifying attestations from the attack block
next_epoch(spec, state)
assert spec.compute_epoch_at_slot(state.slot) == 5
assert state.current_justified_checkpoint.epoch == 3
# Create two block in the honest chain with full attestations, and add to the store
for _ in range(2):
signed_block = state_transition_with_full_block(spec, state, True, False)
yield from tick_and_add_block(spec, store, signed_block, test_steps)
# Create final block in the honest chain that includes the justifying attestations from the attack block
honest_block = build_empty_block_for_next_slot(spec, state)
honest_block.body.attestations = signed_attack_block.message.body.attestations
signed_honest_block = state_transition_and_sign_block(spec, state, honest_block)
# Add the honest block to the store
yield from tick_and_add_block(spec, store, signed_honest_block, test_steps)
assert spec.get_head(store) == signed_honest_block.message.hash_tree_root()
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
# Tick to the next slot so proposer boost is not a factor in choosing the head
current_time = (honest_block.slot + 1) * spec.config.SECONDS_PER_SLOT + store.genesis_time
on_tick_and_append_step(spec, store, current_time, test_steps)
assert spec.get_head(store) == signed_honest_block.message.hash_tree_root()
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
# Upon revealing the withheld attack block, the honest block should still be the head
yield from tick_and_add_block(spec, store, signed_attack_block, test_steps)
assert spec.get_head(store) == signed_honest_block.message.hash_tree_root()
# As a side effect of the pull-up logic, the attack block is pulled up and store.justified_checkpoint is updated
assert store.justified_checkpoint.epoch == 4
# Even after going to the next epoch, the honest block should remain the head
slot = spec.get_current_slot(store) + spec.SLOTS_PER_EPOCH - (state.slot % spec.SLOTS_PER_EPOCH)
current_time = slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
on_tick_and_append_step(spec, store, current_time, test_steps)
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 6
assert spec.get_head(store) == signed_honest_block.message.hash_tree_root()
yield 'steps', test_steps
@with_altair_and_later
@spec_state_test
@with_presets(TESTING_PRESETS, reason="too slow")
def test_withholding_attack_unviable_honest_chain(spec, state):
"""
Checks that the withholding attack succeeds for one epoch if the honest chain has a voting source beyond
two epochs ago.
"""
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
next_epoch(spec, state)
on_tick_and_append_step(spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps)
# Fill epoch 1 to 3
for _ in range(3):
state, store, _ = yield from apply_next_epoch_with_attestations(
spec, state, store, True, True, test_steps=test_steps)
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
next_epoch(spec, state)
assert spec.compute_epoch_at_slot(state.slot) == 5
# Create the attack block that includes justifying attestations for epoch 5
# This block is withheld & revealed only in epoch 6
signed_blocks, justifying_slot = find_next_justifying_slot(spec, state, True, False)
assert spec.compute_epoch_at_slot(justifying_slot) == spec.get_current_epoch(state)
assert len(signed_blocks) > 1
signed_attack_block = signed_blocks[-1]
for signed_block in signed_blocks[:-1]:
yield from tick_and_add_block(spec, store, signed_block, test_steps)
assert spec.get_head(store) == signed_block.message.hash_tree_root()
assert spec.get_head(store) == signed_blocks[-2].message.hash_tree_root()
state = store.block_states[spec.get_head(store)].copy()
assert spec.compute_epoch_at_slot(state.slot) == 5
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
# Create an honest chain in epoch 6 that includes the justifying attestations from the attack block
next_epoch(spec, state)
assert spec.compute_epoch_at_slot(state.slot) == 6
assert state.current_justified_checkpoint.epoch == 3
# Create two block in the honest chain with full attestations, and add to the store
for _ in range(2):
signed_block = state_transition_with_full_block(spec, state, True, False)
assert state.current_justified_checkpoint.epoch == 3
yield from tick_and_add_block(spec, store, signed_block, test_steps)
# Create final block in the honest chain that includes the justifying attestations from the attack block
honest_block = build_empty_block_for_next_slot(spec, state)
honest_block.body.attestations = signed_attack_block.message.body.attestations
signed_honest_block = state_transition_and_sign_block(spec, state, honest_block)
honest_block_root = signed_honest_block.message.hash_tree_root()
assert state.current_justified_checkpoint.epoch == 3
# Add the honest block to the store
yield from tick_and_add_block(spec, store, signed_honest_block, test_steps)
current_epoch = spec.compute_epoch_at_slot(spec.get_current_slot(store))
assert current_epoch == 6
# assert store.voting_source[honest_block_root].epoch == 3
assert spec.get_head(store) == honest_block_root
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 6
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
# Tick to the next slot so proposer boost is not a factor in choosing the head
current_time = (honest_block.slot + 1) * spec.config.SECONDS_PER_SLOT + store.genesis_time
on_tick_and_append_step(spec, store, current_time, test_steps)
assert spec.get_head(store) == honest_block_root
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 6
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
# Upon revealing the withheld attack block, it should become the head
yield from tick_and_add_block(spec, store, signed_attack_block, test_steps)
# The attack block is pulled up and store.justified_checkpoint is updated
assert store.justified_checkpoint.epoch == 5
attack_block_root = signed_attack_block.message.hash_tree_root()
assert spec.get_head(store) == attack_block_root
# After going to the next epoch, the honest block should become the head
slot = spec.get_current_slot(store) + spec.SLOTS_PER_EPOCH - (state.slot % spec.SLOTS_PER_EPOCH)
current_time = slot * spec.config.SECONDS_PER_SLOT + store.genesis_time
on_tick_and_append_step(spec, store, current_time, test_steps)
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 7
# assert store.voting_source[honest_block_root].epoch == 5
assert spec.get_head(store) == honest_block_root
yield 'steps', test_steps

View File

@ -1,87 +0,0 @@
from copy import deepcopy
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.test.context import (
spec_state_test,
with_all_phases,
)
from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot,
)
from eth2spec.test.helpers.fork_choice import (
get_genesis_forkchoice_store,
run_on_block,
apply_next_epoch_with_attestations,
)
from eth2spec.test.helpers.state import (
next_epoch,
state_transition_and_sign_block,
)
@with_all_phases
@spec_state_test
def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state):
"""
NOTE: test_new_justified_is_later_than_store_justified also tests best_justified_checkpoint
"""
# Initialization
store = get_genesis_forkchoice_store(spec, state)
next_epoch(spec, state)
spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT)
state, store, last_signed_block = yield from apply_next_epoch_with_attestations(
spec, state, store, True, False)
last_block_root = hash_tree_root(last_signed_block.message)
# NOTE: Mock fictitious justified checkpoint in store
store.justified_checkpoint = spec.Checkpoint(
epoch=spec.compute_epoch_at_slot(last_signed_block.message.slot),
root=spec.Root("0x4a55535449464945440000000000000000000000000000000000000000000000")
)
next_epoch(spec, state)
spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT)
# Create new higher justified checkpoint not in branch of store's justified checkpoint
just_block = build_empty_block_for_next_slot(spec, state)
store.blocks[just_block.hash_tree_root()] = just_block
# Step time past safe slots
spec.on_tick(store, store.time + spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED * spec.config.SECONDS_PER_SLOT)
assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED
previously_finalized = store.finalized_checkpoint
previously_justified = store.justified_checkpoint
# Add a series of new blocks with "better" justifications
best_justified_checkpoint = spec.Checkpoint(epoch=0)
for i in range(3, 0, -1):
# Mutate store
just_state = store.block_states[last_block_root]
new_justified = spec.Checkpoint(
epoch=previously_justified.epoch + i,
root=just_block.hash_tree_root(),
)
if new_justified.epoch > best_justified_checkpoint.epoch:
best_justified_checkpoint = new_justified
just_state.current_justified_checkpoint = new_justified
block = build_empty_block_for_next_slot(spec, just_state)
signed_block = state_transition_and_sign_block(spec, deepcopy(just_state), block)
# NOTE: Mock store so that the modified state could be accessed
parent_block = store.blocks[last_block_root].copy()
parent_block.state_root = just_state.hash_tree_root()
store.blocks[block.parent_root] = parent_block
store.block_states[block.parent_root] = just_state.copy()
assert block.parent_root in store.blocks.keys()
assert block.parent_root in store.block_states.keys()
run_on_block(spec, store, signed_block)
assert store.finalized_checkpoint == previously_finalized
assert store.justified_checkpoint == previously_justified
# ensure the best from the series was stored
assert store.best_justified_checkpoint == best_justified_checkpoint

View File

@ -18,7 +18,6 @@ def run_on_tick(spec, store, time, new_justified_checkpoint=False):
assert store.time == time
if new_justified_checkpoint:
assert store.justified_checkpoint == store.best_justified_checkpoint
assert store.justified_checkpoint.epoch > previous_justified_checkpoint.epoch
assert store.justified_checkpoint.root != previous_justified_checkpoint.root
else:
@ -32,12 +31,12 @@ def test_basic(spec, state):
run_on_tick(spec, store, store.time + 1)
"""
@with_all_phases
@spec_state_test
def test_update_justified_single_on_store_finalized_chain(spec, state):
store = get_genesis_forkchoice_store(spec, state)
# [Mock store.best_justified_checkpoint]
# Create a block at epoch 1
next_epoch(spec, state)
block = build_empty_block_for_next_slot(spec, state)
@ -58,8 +57,6 @@ def test_update_justified_single_on_store_finalized_chain(spec, state):
state_transition_and_sign_block(spec, state, block)
store.blocks[block.hash_tree_root()] = block
store.block_states[block.hash_tree_root()] = state
# Mock store.best_justified_checkpoint
store.best_justified_checkpoint = state.current_justified_checkpoint.copy()
run_on_tick(
spec,
@ -67,6 +64,7 @@ def test_update_justified_single_on_store_finalized_chain(spec, state):
store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT,
new_justified_checkpoint=True
)
"""
@with_all_phases
@ -89,7 +87,6 @@ def test_update_justified_single_not_on_store_finalized_chain(spec, state):
root=block.hash_tree_root(),
)
# [Mock store.best_justified_checkpoint]
# Create a block at epoch 1
state = init_state.copy()
next_epoch(spec, state)
@ -112,79 +109,9 @@ def test_update_justified_single_not_on_store_finalized_chain(spec, state):
state_transition_and_sign_block(spec, state, block)
store.blocks[block.hash_tree_root()] = block.copy()
store.block_states[block.hash_tree_root()] = state.copy()
# Mock store.best_justified_checkpoint
store.best_justified_checkpoint = state.current_justified_checkpoint.copy()
run_on_tick(
spec,
store,
store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT,
)
@with_all_phases
@spec_state_test
def test_no_update_same_slot_at_epoch_boundary(spec, state):
store = get_genesis_forkchoice_store(spec, state)
seconds_per_epoch = spec.config.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
store.best_justified_checkpoint = spec.Checkpoint(
epoch=store.justified_checkpoint.epoch + 1,
root=b'\x55' * 32,
)
# set store time to already be at epoch boundary
store.time = seconds_per_epoch
run_on_tick(spec, store, store.time + 1)
@with_all_phases
@spec_state_test
def test_no_update_not_epoch_boundary(spec, state):
store = get_genesis_forkchoice_store(spec, state)
store.best_justified_checkpoint = spec.Checkpoint(
epoch=store.justified_checkpoint.epoch + 1,
root=b'\x55' * 32,
)
run_on_tick(spec, store, store.time + spec.config.SECONDS_PER_SLOT)
@with_all_phases
@spec_state_test
def test_no_update_new_justified_equal_epoch(spec, state):
store = get_genesis_forkchoice_store(spec, state)
seconds_per_epoch = spec.config.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
store.best_justified_checkpoint = spec.Checkpoint(
epoch=store.justified_checkpoint.epoch + 1,
root=b'\x55' * 32,
)
store.justified_checkpoint = spec.Checkpoint(
epoch=store.best_justified_checkpoint.epoch,
root=b'\44' * 32,
)
run_on_tick(spec, store, store.time + seconds_per_epoch)
@with_all_phases
@spec_state_test
def test_no_update_new_justified_later_epoch(spec, state):
store = get_genesis_forkchoice_store(spec, state)
seconds_per_epoch = spec.config.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
store.best_justified_checkpoint = spec.Checkpoint(
epoch=store.justified_checkpoint.epoch + 1,
root=b'\x55' * 32,
)
store.justified_checkpoint = spec.Checkpoint(
epoch=store.best_justified_checkpoint.epoch + 1,
root=b'\44' * 32,
)
run_on_tick(spec, store, store.time + seconds_per_epoch)

View File

@ -146,10 +146,6 @@ finalized_checkpoint: {
epoch: int, -- Integer value from store.finalized_checkpoint.epoch
root: string, -- Encoded 32-byte value from store.finalized_checkpoint.root
}
best_justified_checkpoint: {
epoch: int, -- Integer value from store.best_justified_checkpoint.epoch
root: string, -- Encoded 32-byte value from store.best_justified_checkpoint.root
}
proposer_boost_root: string -- Encoded 32-byte value from store.proposer_boost_root
```
@ -160,7 +156,6 @@ For example:
head: {slot: 32, root: '0xdaa1d49d57594ced0c35688a6da133abb086d191a2ebdfd736fad95299325aeb'}
justified_checkpoint: {epoch: 3, root: '0xc25faab4acab38d3560864ca01e4d5cc4dc2cd473da053fbc03c2669143a2de4'}
finalized_checkpoint: {epoch: 2, root: '0x40d32d6283ec11c53317a46808bc88f55657d93b95a1af920403187accf48f4f'}
best_justified_checkpoint: {epoch: 3, root: '0xc25faab4acab38d3560864ca01e4d5cc4dc2cd473da053fbc03c2669143a2de4'}
proposer_boost_root: '0xdaa1d49d57594ced0c35688a6da133abb086d191a2ebdfd736fad95299325aeb'
```

View File

@ -7,6 +7,8 @@ if __name__ == "__main__":
'get_head',
'on_block',
'ex_ante',
'reorg',
'withholding',
]}
# No additional Altair specific finality tests, yet.
altair_mods = phase_0_mods