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
|
HYSTERESIS_UPWARD_MULTIPLIER: 5
|
||||||
|
|
||||||
|
|
||||||
# Fork Choice
|
|
||||||
# ---------------------------------------------------------------
|
|
||||||
# 2**3 (= 8)
|
|
||||||
SAFE_SLOTS_TO_UPDATE_JUSTIFIED: 8
|
|
||||||
|
|
||||||
|
|
||||||
# Gwei values
|
# Gwei values
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# 2**0 * 10**9 (= 1,000,000,000) Gwei
|
# 2**0 * 10**9 (= 1,000,000,000) Gwei
|
||||||
|
|
|
@ -18,12 +18,6 @@ HYSTERESIS_DOWNWARD_MULTIPLIER: 1
|
||||||
HYSTERESIS_UPWARD_MULTIPLIER: 5
|
HYSTERESIS_UPWARD_MULTIPLIER: 5
|
||||||
|
|
||||||
|
|
||||||
# Fork Choice
|
|
||||||
# ---------------------------------------------------------------
|
|
||||||
# 2**1 (= 1)
|
|
||||||
SAFE_SLOTS_TO_UPDATE_JUSTIFIED: 2
|
|
||||||
|
|
||||||
|
|
||||||
# Gwei values
|
# Gwei values
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# 2**0 * 10**9 (= 1,000,000,000) Gwei
|
# 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
|
# Check the block is valid and compute the post-state
|
||||||
state = pre_state.copy()
|
state = pre_state.copy()
|
||||||
|
block_root = hash_tree_root(block)
|
||||||
state_transition(state, signed_block, True)
|
state_transition(state, signed_block, True)
|
||||||
|
|
||||||
# [New in Bellatrix]
|
# [New in Bellatrix]
|
||||||
|
@ -181,9 +182,9 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
|
||||||
validate_merge_block(block)
|
validate_merge_block(block)
|
||||||
|
|
||||||
# Add new block to the store
|
# 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
|
# 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
|
# Add proposer score boost if the block is timely
|
||||||
time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT
|
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:
|
if get_current_slot(store) == block.slot and is_before_attesting_interval:
|
||||||
store.proposer_boost_root = hash_tree_root(block)
|
store.proposer_boost_root = hash_tree_root(block)
|
||||||
|
|
||||||
# Update justified checkpoint
|
# Update checkpoints in store if necessary
|
||||||
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
|
update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint)
|
||||||
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 finalized checkpoint
|
# Eagerly compute unrealized justification and finality.
|
||||||
if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch:
|
compute_pulled_up_tip(store, block_root)
|
||||||
store.finalized_checkpoint = state.finalized_checkpoint
|
|
||||||
store.justified_checkpoint = state.current_justified_checkpoint
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -91,6 +91,7 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
|
||||||
|
|
||||||
# Check the block is valid and compute the post-state
|
# Check the block is valid and compute the post-state
|
||||||
state = pre_state.copy()
|
state = pre_state.copy()
|
||||||
|
block_root = hash_tree_root(block)
|
||||||
state_transition(state, signed_block, True)
|
state_transition(state, signed_block, True)
|
||||||
|
|
||||||
# Check the merge transition
|
# Check the merge transition
|
||||||
|
@ -98,9 +99,9 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
|
||||||
validate_merge_block(block)
|
validate_merge_block(block)
|
||||||
|
|
||||||
# Add new block to the store
|
# 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
|
# 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
|
# Add proposer score boost if the block is timely
|
||||||
time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT
|
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:
|
if get_current_slot(store) == block.slot and is_before_attesting_interval:
|
||||||
store.proposer_boost_root = hash_tree_root(block)
|
store.proposer_boost_root = hash_tree_root(block)
|
||||||
|
|
||||||
# Update justified checkpoint
|
# Update checkpoints in store if necessary
|
||||||
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
|
update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint)
|
||||||
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 finalized checkpoint
|
# Eagerly compute unrealized justification and finality.
|
||||||
if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch:
|
compute_pulled_up_tip(store, block_root)
|
||||||
store.finalized_checkpoint = state.finalized_checkpoint
|
|
||||||
store.justified_checkpoint = state.current_justified_checkpoint
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -8,10 +8,10 @@
|
||||||
- [Introduction](#introduction)
|
- [Introduction](#introduction)
|
||||||
- [Fork choice](#fork-choice)
|
- [Fork choice](#fork-choice)
|
||||||
- [Constant](#constant)
|
- [Constant](#constant)
|
||||||
- [Preset](#preset)
|
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
- [Helpers](#helpers)
|
- [Helpers](#helpers)
|
||||||
- [`LatestMessage`](#latestmessage)
|
- [`LatestMessage`](#latestmessage)
|
||||||
|
- [`is_previous_epoch_justified`](#is_previous_epoch_justified)
|
||||||
- [`Store`](#store)
|
- [`Store`](#store)
|
||||||
- [`get_forkchoice_store`](#get_forkchoice_store)
|
- [`get_forkchoice_store`](#get_forkchoice_store)
|
||||||
- [`get_slots_since_genesis`](#get_slots_since_genesis)
|
- [`get_slots_since_genesis`](#get_slots_since_genesis)
|
||||||
|
@ -19,10 +19,16 @@
|
||||||
- [`compute_slots_since_epoch_start`](#compute_slots_since_epoch_start)
|
- [`compute_slots_since_epoch_start`](#compute_slots_since_epoch_start)
|
||||||
- [`get_ancestor`](#get_ancestor)
|
- [`get_ancestor`](#get_ancestor)
|
||||||
- [`get_weight`](#get_weight)
|
- [`get_weight`](#get_weight)
|
||||||
|
- [`get_voting_source`](#get_voting_source)
|
||||||
- [`filter_block_tree`](#filter_block_tree)
|
- [`filter_block_tree`](#filter_block_tree)
|
||||||
- [`get_filtered_block_tree`](#get_filtered_block_tree)
|
- [`get_filtered_block_tree`](#get_filtered_block_tree)
|
||||||
- [`get_head`](#get_head)
|
- [`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)
|
- [`on_attestation` helpers](#on_attestation-helpers)
|
||||||
- [`validate_target_epoch_against_current_time`](#validate_target_epoch_against_current_time)
|
- [`validate_target_epoch_against_current_time`](#validate_target_epoch_against_current_time)
|
||||||
- [`validate_on_attestation`](#validate_on_attestation)
|
- [`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)` |
|
| `INTERVALS_PER_SLOT` | `uint64(3)` |
|
||||||
|
|
||||||
### Preset
|
|
||||||
|
|
||||||
| Name | Value | Unit | Duration |
|
|
||||||
| -------------------------------- | ------------ | :---: | :--------: |
|
|
||||||
| `SAFE_SLOTS_TO_UPDATE_JUSTIFIED` | `2**3` (= 8) | slots | 96 seconds |
|
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
| Name | Value |
|
| Name | Value |
|
||||||
|
@ -92,8 +92,26 @@ class LatestMessage(object):
|
||||||
root: Root
|
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`
|
#### `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
|
```python
|
||||||
@dataclass
|
@dataclass
|
||||||
class Store(object):
|
class Store(object):
|
||||||
|
@ -101,13 +119,15 @@ class Store(object):
|
||||||
genesis_time: uint64
|
genesis_time: uint64
|
||||||
justified_checkpoint: Checkpoint
|
justified_checkpoint: Checkpoint
|
||||||
finalized_checkpoint: Checkpoint
|
finalized_checkpoint: Checkpoint
|
||||||
best_justified_checkpoint: Checkpoint
|
unrealized_justified_checkpoint: Checkpoint
|
||||||
|
unrealized_finalized_checkpoint: Checkpoint
|
||||||
proposer_boost_root: Root
|
proposer_boost_root: Root
|
||||||
equivocating_indices: Set[ValidatorIndex]
|
equivocating_indices: Set[ValidatorIndex]
|
||||||
blocks: Dict[Root, BeaconBlock] = field(default_factory=dict)
|
blocks: Dict[Root, BeaconBlock] = field(default_factory=dict)
|
||||||
block_states: Dict[Root, BeaconState] = field(default_factory=dict)
|
block_states: Dict[Root, BeaconState] = field(default_factory=dict)
|
||||||
checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict)
|
checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict)
|
||||||
latest_messages: Dict[ValidatorIndex, LatestMessage] = 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`
|
#### `get_forkchoice_store`
|
||||||
|
@ -130,12 +150,14 @@ def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -
|
||||||
genesis_time=anchor_state.genesis_time,
|
genesis_time=anchor_state.genesis_time,
|
||||||
justified_checkpoint=justified_checkpoint,
|
justified_checkpoint=justified_checkpoint,
|
||||||
finalized_checkpoint=finalized_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,
|
proposer_boost_root=proposer_boost_root,
|
||||||
equivocating_indices=set(),
|
equivocating_indices=set(),
|
||||||
blocks={anchor_root: copy(anchor_block)},
|
blocks={anchor_root: copy(anchor_block)},
|
||||||
block_states={anchor_root: copy(anchor_state)},
|
block_states={anchor_root: copy(anchor_state)},
|
||||||
checkpoint_states={justified_checkpoint: 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]
|
block = store.blocks[root]
|
||||||
if block.slot > slot:
|
if block.slot > slot:
|
||||||
return get_ancestor(store, block.parent_root, slot)
|
return get_ancestor(store, block.parent_root, slot)
|
||||||
elif block.slot == slot:
|
return root
|
||||||
return root
|
|
||||||
else:
|
|
||||||
# root is older than queried slot, thus a skip slot. Return most recent root prior to slot
|
|
||||||
return root
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `get_weight`
|
#### `get_weight`
|
||||||
|
@ -179,9 +197,12 @@ def get_ancestor(store: Store, root: Root, slot: Slot) -> Root:
|
||||||
```python
|
```python
|
||||||
def get_weight(store: Store, root: Root) -> Gwei:
|
def get_weight(store: Store, root: Root) -> Gwei:
|
||||||
state = store.checkpoint_states[store.justified_checkpoint]
|
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(
|
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
|
if (i in store.latest_messages
|
||||||
and i not in store.equivocating_indices
|
and i not in store.equivocating_indices
|
||||||
and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root)
|
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
|
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`
|
#### `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
|
```python
|
||||||
def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconBlock]) -> bool:
|
def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconBlock]) -> bool:
|
||||||
block = store.blocks[block_root]
|
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 True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# If leaf block, check finalized/justified checkpoints as matching latest.
|
current_epoch = compute_epoch_at_slot(get_current_slot(store))
|
||||||
head_state = store.block_states[block_root]
|
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 = (
|
correct_justified = (
|
||||||
store.justified_checkpoint.epoch == GENESIS_EPOCH
|
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 = (
|
correct_finalized = (
|
||||||
store.finalized_checkpoint.epoch == GENESIS_EPOCH
|
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 expected finalized/justified, add to viable block-tree and signal viability to parent.
|
||||||
if correct_justified and correct_finalized:
|
if correct_justified and correct_finalized:
|
||||||
blocks[block_root] = block
|
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))
|
head = max(children, key=lambda root: (get_weight(store, root), root))
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `should_update_justified_checkpoint`
|
#### `update_checkpoints`
|
||||||
|
|
||||||
```python
|
```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
|
Update checkpoints in store if necessary
|
||||||
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.
|
|
||||||
"""
|
"""
|
||||||
if compute_slots_since_epoch_start(get_current_slot(store)) < SAFE_SLOTS_TO_UPDATE_JUSTIFIED:
|
# Update justified checkpoint
|
||||||
return True
|
if justified_checkpoint.epoch > store.justified_checkpoint.epoch:
|
||||||
|
store.justified_checkpoint = justified_checkpoint
|
||||||
|
|
||||||
justified_slot = compute_start_slot_at_epoch(store.justified_checkpoint.epoch)
|
# Update finalized checkpoint
|
||||||
if not get_ancestor(store, new_justified_checkpoint.root, justified_slot) == store.justified_checkpoint.root:
|
if finalized_checkpoint.epoch > store.finalized_checkpoint.epoch:
|
||||||
return False
|
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
|
#### `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
|
# Check that the epoch number and slot number are matching
|
||||||
assert target.epoch == compute_epoch_at_slot(attestation.data.slot)
|
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
|
assert target.root in store.blocks
|
||||||
|
|
||||||
# Attestations must be for a known block. If block is unknown, delay consideration until the block is found
|
# 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
|
```python
|
||||||
def on_tick(store: Store, time: uint64) -> None:
|
def on_tick(store: Store, time: uint64) -> None:
|
||||||
previous_slot = get_current_slot(store)
|
# 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``
|
||||||
# update store time
|
tick_slot = (time - store.genesis_time) // SECONDS_PER_SLOT
|
||||||
store.time = time
|
while get_current_slot(store) < tick_slot:
|
||||||
|
previous_time = store.genesis_time + (get_current_slot(store) + 1) * SECONDS_PER_SLOT
|
||||||
current_slot = get_current_slot(store)
|
on_tick_per_slot(store, previous_time)
|
||||||
|
on_tick_per_slot(store, time)
|
||||||
# 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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `on_block`
|
#### `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
|
# Check the block is valid and compute the post-state
|
||||||
state = pre_state.copy()
|
state = pre_state.copy()
|
||||||
|
block_root = hash_tree_root(block)
|
||||||
state_transition(state, signed_block, True)
|
state_transition(state, signed_block, True)
|
||||||
# Add new block to the store
|
# 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
|
# 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
|
# Add proposer score boost if the block is timely
|
||||||
time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT
|
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:
|
if get_current_slot(store) == block.slot and is_before_attesting_interval:
|
||||||
store.proposer_boost_root = hash_tree_root(block)
|
store.proposer_boost_root = hash_tree_root(block)
|
||||||
|
|
||||||
# Update justified checkpoint
|
# Update checkpoints in store if necessary
|
||||||
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
|
update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint)
|
||||||
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 finalized checkpoint
|
# Eagerly compute unrealized justification and finality
|
||||||
if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch:
|
compute_pulled_up_tip(store, block_root)
|
||||||
store.finalized_checkpoint = state.finalized_checkpoint
|
|
||||||
store.justified_checkpoint = state.current_justified_checkpoint
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `on_attestation`
|
#### `on_attestation`
|
||||||
|
|
|
@ -187,7 +187,7 @@ def add_attestations_to_state(spec, state, attestations, slot):
|
||||||
spec.process_attestation(state, attestation)
|
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))
|
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):
|
for index in range(committees_per_slot):
|
||||||
def participants_filter(comm):
|
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:
|
if fill_cur_epoch and state.slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY:
|
||||||
slot_to_attest = state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1
|
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)):
|
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,
|
state,
|
||||||
spec,
|
spec,
|
||||||
slot_to_attest,
|
slot_to_attest,
|
||||||
|
@ -272,7 +272,7 @@ def state_transition_with_full_block(spec,
|
||||||
block.body.attestations.append(attestation)
|
block.body.attestations.append(attestation)
|
||||||
if fill_prev_epoch:
|
if fill_prev_epoch:
|
||||||
slot_to_attest = state.slot - spec.SLOTS_PER_EPOCH + 1
|
slot_to_attest = state.slot - spec.SLOTS_PER_EPOCH + 1
|
||||||
attestations = _get_valid_attestation_at_slot(
|
attestations = get_valid_attestation_at_slot(
|
||||||
state,
|
state,
|
||||||
spec,
|
spec,
|
||||||
slot_to_attest,
|
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
|
slots = state.slot % spec.SLOTS_PER_EPOCH
|
||||||
for slot_offset in range(slots):
|
for slot_offset in range(slots):
|
||||||
target_slot = state.slot - slot_offset
|
target_slot = state.slot - slot_offset
|
||||||
attestations += _get_valid_attestation_at_slot(
|
attestations += get_valid_attestation_at_slot(
|
||||||
state,
|
state,
|
||||||
spec,
|
spec,
|
||||||
target_slot,
|
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
|
slots = spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH
|
||||||
for slot_offset in range(1, slots):
|
for slot_offset in range(1, slots):
|
||||||
target_slot = state.slot - (state.slot % spec.SLOTS_PER_EPOCH) - slot_offset
|
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,
|
state,
|
||||||
spec,
|
spec,
|
||||||
target_slot,
|
target_slot,
|
||||||
|
|
|
@ -3,6 +3,7 @@ from eth2spec.test.exceptions import BlockNotFoundException
|
||||||
from eth2spec.test.helpers.attestations import (
|
from eth2spec.test.helpers.attestations import (
|
||||||
next_epoch_with_attestations,
|
next_epoch_with_attestations,
|
||||||
next_slots_with_attestations,
|
next_slots_with_attestations,
|
||||||
|
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,
|
def tick_and_add_block(spec, store, signed_block, test_steps, valid=True,
|
||||||
merge_block=False, block_not_found=False, is_optimistic=False):
|
merge_block=False, block_not_found=False, is_optimistic=False):
|
||||||
pre_state = store.block_states[signed_block.message.parent_root]
|
pre_state = store.block_states[signed_block.message.parent_root]
|
||||||
block_time = pre_state.genesis_time + signed_block.message.slot * spec.config.SECONDS_PER_SLOT
|
|
||||||
if merge_block:
|
if merge_block:
|
||||||
assert spec.is_merge_transition_block(pre_state, signed_block.message.body)
|
assert spec.is_merge_transition_block(pre_state, signed_block.message.body)
|
||||||
|
|
||||||
if store.time < block_time:
|
block_time = pre_state.genesis_time + signed_block.message.slot * spec.config.SECONDS_PER_SLOT
|
||||||
on_tick_and_append_step(spec, store, block_time, test_steps)
|
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(
|
post_state = yield from add_block(
|
||||||
spec, store, signed_block, test_steps,
|
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)})
|
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):
|
def tick_and_run_on_attestation(spec, store, attestation, test_steps, is_from_block=False):
|
||||||
parent_block = store.blocks[attestation.data.beacon_block_root]
|
parent_block = store.blocks[attestation.data.beacon_block_root]
|
||||||
pre_state = store.block_states[spec.hash_tree_root(parent_block)]
|
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):
|
def on_tick_and_append_step(spec, store, time, test_steps):
|
||||||
spec.on_tick(store, time)
|
spec.on_tick(store, time)
|
||||||
test_steps.append({'tick': int(time)})
|
test_steps.append({'tick': int(time)})
|
||||||
|
output_store_checks(spec, store, test_steps)
|
||||||
|
|
||||||
|
|
||||||
def run_on_block(spec, store, signed_block, valid=True):
|
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.blocks[block_root] == signed_block.message
|
||||||
assert store.block_states[block_root].hash_tree_root() == signed_block.message.state_root
|
assert store.block_states[block_root].hash_tree_root() == signed_block.message.state_root
|
||||||
if not is_optimistic:
|
if not is_optimistic:
|
||||||
test_steps.append({
|
output_store_checks(spec, store, test_steps)
|
||||||
'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),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return store.block_states[signed_block.message.hash_tree_root()]
|
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,
|
def apply_next_epoch_with_attestations(spec,
|
||||||
state,
|
state,
|
||||||
store,
|
store,
|
||||||
|
@ -263,6 +279,39 @@ def apply_next_slots_with_attestations(spec,
|
||||||
return post_state, store, last_signed_block
|
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):
|
def get_pow_block_file_name(pow_block):
|
||||||
return f"pow_block_{encode_hex(pow_block.block_hash)}"
|
return f"pow_block_{encode_hex(pow_block.block_hash)}"
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import random
|
import random
|
||||||
from eth_utils import encode_hex
|
|
||||||
|
|
||||||
from eth2spec.test.context import (
|
from eth2spec.test.context import (
|
||||||
spec_state_test,
|
spec_state_test,
|
||||||
with_all_phases,
|
with_all_phases,
|
||||||
|
with_altair_and_later,
|
||||||
with_presets,
|
with_presets,
|
||||||
)
|
)
|
||||||
from eth2spec.test.helpers.attestations import get_valid_attestation, next_epoch_with_attestations
|
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,
|
add_attestation,
|
||||||
tick_and_run_on_attestation,
|
tick_and_run_on_attestation,
|
||||||
tick_and_add_block,
|
tick_and_add_block,
|
||||||
|
output_head_check,
|
||||||
|
apply_next_epoch_with_attestations,
|
||||||
)
|
)
|
||||||
from eth2spec.test.helpers.forks import (
|
from eth2spec.test.helpers.forks import (
|
||||||
is_post_altair,
|
is_post_altair,
|
||||||
|
@ -71,11 +73,7 @@ def test_chain_no_attestations(spec, state):
|
||||||
|
|
||||||
anchor_root = get_anchor_root(spec, state)
|
anchor_root = get_anchor_root(spec, state)
|
||||||
assert spec.get_head(store) == anchor_root
|
assert spec.get_head(store) == anchor_root
|
||||||
test_steps.append({
|
output_head_check(spec, store, test_steps)
|
||||||
'checks': {
|
|
||||||
'head': get_formatted_head_output(spec, store),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# On receiving a block of `GENESIS_SLOT + 1` slot
|
# On receiving a block of `GENESIS_SLOT + 1` slot
|
||||||
block_1 = build_empty_block_for_next_slot(spec, state)
|
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)
|
yield from tick_and_add_block(spec, store, signed_block_2, test_steps)
|
||||||
|
|
||||||
assert spec.get_head(store) == spec.hash_tree_root(block_2)
|
assert spec.get_head(store) == spec.hash_tree_root(block_2)
|
||||||
test_steps.append({
|
output_head_check(spec, store, test_steps)
|
||||||
'checks': {
|
|
||||||
'head': get_formatted_head_output(spec, store),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
yield 'steps', test_steps
|
yield 'steps', test_steps
|
||||||
|
|
||||||
|
@ -109,11 +103,7 @@ def test_split_tie_breaker_no_attestations(spec, state):
|
||||||
yield 'anchor_block', anchor_block
|
yield 'anchor_block', anchor_block
|
||||||
anchor_root = get_anchor_root(spec, state)
|
anchor_root = get_anchor_root(spec, state)
|
||||||
assert spec.get_head(store) == anchor_root
|
assert spec.get_head(store) == anchor_root
|
||||||
test_steps.append({
|
output_head_check(spec, store, test_steps)
|
||||||
'checks': {
|
|
||||||
'head': get_formatted_head_output(spec, store),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# Create block at slot 1
|
# Create block at slot 1
|
||||||
block_1_state = genesis_state.copy()
|
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))
|
highest_root = max(spec.hash_tree_root(block_1), spec.hash_tree_root(block_2))
|
||||||
assert spec.get_head(store) == highest_root
|
assert spec.get_head(store) == highest_root
|
||||||
test_steps.append({
|
output_head_check(spec, store, test_steps)
|
||||||
'checks': {
|
|
||||||
'head': get_formatted_head_output(spec, store),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
yield 'steps', test_steps
|
yield 'steps', test_steps
|
||||||
|
|
||||||
|
@ -156,11 +142,7 @@ def test_shorter_chain_but_heavier_weight(spec, state):
|
||||||
yield 'anchor_block', anchor_block
|
yield 'anchor_block', anchor_block
|
||||||
anchor_root = get_anchor_root(spec, state)
|
anchor_root = get_anchor_root(spec, state)
|
||||||
assert spec.get_head(store) == anchor_root
|
assert spec.get_head(store) == anchor_root
|
||||||
test_steps.append({
|
output_head_check(spec, store, test_steps)
|
||||||
'checks': {
|
|
||||||
'head': get_formatted_head_output(spec, store),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# build longer tree
|
# build longer tree
|
||||||
long_state = genesis_state.copy()
|
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)
|
yield from tick_and_run_on_attestation(spec, store, short_attestation, test_steps)
|
||||||
|
|
||||||
assert spec.get_head(store) == spec.hash_tree_root(short_block)
|
assert spec.get_head(store) == spec.hash_tree_root(short_block)
|
||||||
test_steps.append({
|
output_head_check(spec, store, test_steps)
|
||||||
'checks': {
|
|
||||||
'head': get_formatted_head_output(spec, store),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
yield 'steps', test_steps
|
yield 'steps', test_steps
|
||||||
|
|
||||||
|
@ -203,11 +181,7 @@ def test_filtered_block_tree(spec, state):
|
||||||
yield 'anchor_block', anchor_block
|
yield 'anchor_block', anchor_block
|
||||||
anchor_root = get_anchor_root(spec, state)
|
anchor_root = get_anchor_root(spec, state)
|
||||||
assert spec.get_head(store) == anchor_root
|
assert spec.get_head(store) == anchor_root
|
||||||
test_steps.append({
|
output_head_check(spec, store, test_steps)
|
||||||
'checks': {
|
|
||||||
'head': get_formatted_head_output(spec, store),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# transition state past initial couple of epochs
|
# transition state past initial couple of epochs
|
||||||
next_epoch(spec, state)
|
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
|
# the last block in the branch should be the head
|
||||||
expected_head_root = spec.hash_tree_root(signed_blocks[-1].message)
|
expected_head_root = spec.hash_tree_root(signed_blocks[-1].message)
|
||||||
assert spec.get_head(store) == expected_head_root
|
assert spec.get_head(store) == expected_head_root
|
||||||
|
output_head_check(spec, store, test_steps)
|
||||||
test_steps.append({
|
|
||||||
'checks': {
|
|
||||||
'head': get_formatted_head_output(spec, store),
|
|
||||||
'justified_checkpoint_root': encode_hex(store.justified_checkpoint.root),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# create branch containing the justified block but not containing enough on
|
# 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
|
# ensure that get_head still returns the head from the previous branch
|
||||||
assert spec.get_head(store) == expected_head_root
|
assert spec.get_head(store) == expected_head_root
|
||||||
test_steps.append({
|
output_head_check(spec, store, test_steps)
|
||||||
'checks': {
|
|
||||||
'head': get_formatted_head_output(spec, store)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
yield 'steps', test_steps
|
yield 'steps', test_steps
|
||||||
|
|
||||||
|
@ -295,11 +259,7 @@ def test_proposer_boost_correct_head(spec, state):
|
||||||
yield 'anchor_block', anchor_block
|
yield 'anchor_block', anchor_block
|
||||||
anchor_root = get_anchor_root(spec, state)
|
anchor_root = get_anchor_root(spec, state)
|
||||||
assert spec.get_head(store) == anchor_root
|
assert spec.get_head(store) == anchor_root
|
||||||
test_steps.append({
|
output_head_check(spec, store, test_steps)
|
||||||
'checks': {
|
|
||||||
'head': get_formatted_head_output(spec, store),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# Build block that serves as head ONLY on timely arrival, and ONLY in that slot
|
# Build block that serves as head ONLY on timely arrival, and ONLY in that slot
|
||||||
state_1 = genesis_state.copy()
|
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)
|
on_tick_and_append_step(spec, store, time, test_steps)
|
||||||
assert store.proposer_boost_root == spec.Root()
|
assert store.proposer_boost_root == spec.Root()
|
||||||
assert spec.get_head(store) == spec.hash_tree_root(block_2)
|
assert spec.get_head(store) == spec.hash_tree_root(block_2)
|
||||||
|
output_head_check(spec, store, test_steps)
|
||||||
test_steps.append({
|
|
||||||
'checks': {
|
|
||||||
'head': get_formatted_head_output(spec, store),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
yield 'steps', test_steps
|
yield 'steps', test_steps
|
||||||
|
|
||||||
|
|
||||||
@with_all_phases
|
@with_all_phases
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
def test_discard_equivocations(spec, state):
|
def test_discard_equivocations_on_attester_slashing(spec, state):
|
||||||
test_steps = []
|
test_steps = []
|
||||||
genesis_state = state.copy()
|
genesis_state = state.copy()
|
||||||
|
|
||||||
|
@ -359,11 +314,7 @@ def test_discard_equivocations(spec, state):
|
||||||
yield 'anchor_block', anchor_block
|
yield 'anchor_block', anchor_block
|
||||||
anchor_root = get_anchor_root(spec, state)
|
anchor_root = get_anchor_root(spec, state)
|
||||||
assert spec.get_head(store) == anchor_root
|
assert spec.get_head(store) == anchor_root
|
||||||
test_steps.append({
|
output_head_check(spec, store, test_steps)
|
||||||
'checks': {
|
|
||||||
'head': get_formatted_head_output(spec, store),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# Build block that serves as head before discarding equivocations
|
# Build block that serves as head before discarding equivocations
|
||||||
state_1 = genesis_state.copy()
|
state_1 = genesis_state.copy()
|
||||||
|
@ -418,11 +369,359 @@ def test_discard_equivocations(spec, state):
|
||||||
# The head should revert to block_2
|
# The head should revert to block_2
|
||||||
yield from add_attester_slashing(spec, store, attester_slashing, test_steps)
|
yield from add_attester_slashing(spec, store, attester_slashing, test_steps)
|
||||||
assert spec.get_head(store) == spec.hash_tree_root(block_2)
|
assert spec.get_head(store) == spec.hash_tree_root(block_2)
|
||||||
|
output_head_check(spec, store, test_steps)
|
||||||
test_steps.append({
|
|
||||||
'checks': {
|
|
||||||
'head': get_formatted_head_output(spec, store),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
yield 'steps', 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
|
assert store.time == time
|
||||||
|
|
||||||
if new_justified_checkpoint:
|
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.epoch > previous_justified_checkpoint.epoch
|
||||||
assert store.justified_checkpoint.root != previous_justified_checkpoint.root
|
assert store.justified_checkpoint.root != previous_justified_checkpoint.root
|
||||||
else:
|
else:
|
||||||
|
@ -32,12 +31,12 @@ def test_basic(spec, state):
|
||||||
run_on_tick(spec, store, store.time + 1)
|
run_on_tick(spec, store, store.time + 1)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
@with_all_phases
|
@with_all_phases
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
def test_update_justified_single_on_store_finalized_chain(spec, state):
|
def test_update_justified_single_on_store_finalized_chain(spec, state):
|
||||||
store = get_genesis_forkchoice_store(spec, state)
|
store = get_genesis_forkchoice_store(spec, state)
|
||||||
|
|
||||||
# [Mock store.best_justified_checkpoint]
|
|
||||||
# Create a block at epoch 1
|
# Create a block at epoch 1
|
||||||
next_epoch(spec, state)
|
next_epoch(spec, state)
|
||||||
block = build_empty_block_for_next_slot(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)
|
state_transition_and_sign_block(spec, state, block)
|
||||||
store.blocks[block.hash_tree_root()] = block
|
store.blocks[block.hash_tree_root()] = block
|
||||||
store.block_states[block.hash_tree_root()] = state
|
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(
|
run_on_tick(
|
||||||
spec,
|
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,
|
store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT,
|
||||||
new_justified_checkpoint=True
|
new_justified_checkpoint=True
|
||||||
)
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
@with_all_phases
|
@with_all_phases
|
||||||
|
@ -89,7 +87,6 @@ def test_update_justified_single_not_on_store_finalized_chain(spec, state):
|
||||||
root=block.hash_tree_root(),
|
root=block.hash_tree_root(),
|
||||||
)
|
)
|
||||||
|
|
||||||
# [Mock store.best_justified_checkpoint]
|
|
||||||
# Create a block at epoch 1
|
# Create a block at epoch 1
|
||||||
state = init_state.copy()
|
state = init_state.copy()
|
||||||
next_epoch(spec, state)
|
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)
|
state_transition_and_sign_block(spec, state, block)
|
||||||
store.blocks[block.hash_tree_root()] = block.copy()
|
store.blocks[block.hash_tree_root()] = block.copy()
|
||||||
store.block_states[block.hash_tree_root()] = state.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(
|
run_on_tick(
|
||||||
spec,
|
spec,
|
||||||
store,
|
store,
|
||||||
store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT,
|
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
|
epoch: int, -- Integer value from store.finalized_checkpoint.epoch
|
||||||
root: string, -- Encoded 32-byte value from store.finalized_checkpoint.root
|
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
|
proposer_boost_root: string -- Encoded 32-byte value from store.proposer_boost_root
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -160,7 +156,6 @@ For example:
|
||||||
head: {slot: 32, root: '0xdaa1d49d57594ced0c35688a6da133abb086d191a2ebdfd736fad95299325aeb'}
|
head: {slot: 32, root: '0xdaa1d49d57594ced0c35688a6da133abb086d191a2ebdfd736fad95299325aeb'}
|
||||||
justified_checkpoint: {epoch: 3, root: '0xc25faab4acab38d3560864ca01e4d5cc4dc2cd473da053fbc03c2669143a2de4'}
|
justified_checkpoint: {epoch: 3, root: '0xc25faab4acab38d3560864ca01e4d5cc4dc2cd473da053fbc03c2669143a2de4'}
|
||||||
finalized_checkpoint: {epoch: 2, root: '0x40d32d6283ec11c53317a46808bc88f55657d93b95a1af920403187accf48f4f'}
|
finalized_checkpoint: {epoch: 2, root: '0x40d32d6283ec11c53317a46808bc88f55657d93b95a1af920403187accf48f4f'}
|
||||||
best_justified_checkpoint: {epoch: 3, root: '0xc25faab4acab38d3560864ca01e4d5cc4dc2cd473da053fbc03c2669143a2de4'}
|
|
||||||
proposer_boost_root: '0xdaa1d49d57594ced0c35688a6da133abb086d191a2ebdfd736fad95299325aeb'
|
proposer_boost_root: '0xdaa1d49d57594ced0c35688a6da133abb086d191a2ebdfd736fad95299325aeb'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@ if __name__ == "__main__":
|
||||||
'get_head',
|
'get_head',
|
||||||
'on_block',
|
'on_block',
|
||||||
'ex_ante',
|
'ex_ante',
|
||||||
|
'reorg',
|
||||||
|
'withholding',
|
||||||
]}
|
]}
|
||||||
# No additional Altair specific finality tests, yet.
|
# No additional Altair specific finality tests, yet.
|
||||||
altair_mods = phase_0_mods
|
altair_mods = phase_0_mods
|
||||||
|
|
Loading…
Reference in New Issue