Merge pull request #3290 from ethereum/fork-choice-upgrade
Fork choice upgrade
This commit is contained in:
commit
a0eb23f108
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
```
|
||||
|
|
|
@ -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)
|
||||
```
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)}"
|
||||
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
```
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue