2021-08-18 23:11:38 +00:00
# Phase 0 -- Beacon Chain Fork Choice
2019-04-22 13:20:48 +00:00
## Table of contents
<!-- TOC -->
2019-12-10 14:17:06 +00:00
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE - RUN doctoc TO UPDATE -->
2022-05-09 13:05:05 +00:00
- [Introduction ](#introduction )
- [Fork choice ](#fork-choice )
- [Constant ](#constant )
- [Configuration ](#configuration )
- [Helpers ](#helpers )
- [`LatestMessage` ](#latestmessage )
- [`Store` ](#store )
2023-04-21 08:16:51 +00:00
- [`is_previous_epoch_justified` ](#is_previous_epoch_justified )
2022-05-09 13:05:05 +00:00
- [`get_forkchoice_store` ](#get_forkchoice_store )
- [`get_slots_since_genesis` ](#get_slots_since_genesis )
- [`get_current_slot` ](#get_current_slot )
2023-06-15 01:07:30 +00:00
- [`get_current_store_epoch` ](#get_current_store_epoch )
2022-05-09 13:05:05 +00:00
- [`compute_slots_since_epoch_start` ](#compute_slots_since_epoch_start )
- [`get_ancestor` ](#get_ancestor )
2023-04-18 03:14:53 +00:00
- [`get_checkpoint_block` ](#get_checkpoint_block )
2023-06-15 01:07:30 +00:00
- [`get_proposer_score` ](#get_proposer_score )
2023-02-15 11:39:33 +00:00
- [`get_weight` ](#get_weight )
2023-03-09 23:17:11 +00:00
- [`get_voting_source` ](#get_voting_source )
2022-05-09 13:05:05 +00:00
- [`filter_block_tree` ](#filter_block_tree )
- [`get_filtered_block_tree` ](#get_filtered_block_tree )
- [`get_head` ](#get_head )
2023-03-09 23:17:11 +00:00
- [`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 )
2022-05-09 13:05:05 +00:00
- [`on_attestation` helpers ](#on_attestation-helpers )
- [`validate_target_epoch_against_current_time` ](#validate_target_epoch_against_current_time )
- [`validate_on_attestation` ](#validate_on_attestation )
- [`store_target_checkpoint_state` ](#store_target_checkpoint_state )
- [`update_latest_messages` ](#update_latest_messages )
- [Handlers ](#handlers )
- [`on_tick` ](#on_tick )
- [`on_block` ](#on_block )
- [`on_attestation` ](#on_attestation )
- [`on_attester_slashing` ](#on_attester_slashing )
2019-12-10 14:17:06 +00:00
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
2019-04-22 13:20:48 +00:00
<!-- /TOC -->
## Introduction
2021-08-18 23:11:38 +00:00
This document is the beacon chain fork choice spec, part of Phase 0. It assumes the [beacon chain state transition function spec ](./beacon-chain.md ).
2019-04-24 19:37:50 +00:00
2019-06-15 22:42:03 +00:00
## Fork choice
2023-01-24 13:08:41 +00:00
The head block root associated with a `store` is defined as `get_head(store)` . At genesis, let `store = get_forkchoice_store(genesis_state, genesis_block)` and update `store` by running:
2019-06-15 22:42:03 +00:00
2020-06-13 20:59:04 +00:00
- `on_tick(store, time)` whenever `time > store.time` where `time` is the current Unix time
- `on_block(store, block)` whenever a block `block: SignedBeaconBlock` is received
- `on_attestation(store, attestation)` whenever an attestation `attestation` is received
2023-01-24 13:25:40 +00:00
- `on_attester_slashing(store, attester_slashing)` whenever an attester slashing `attester_slashing` is received
2020-06-13 20:59:04 +00:00
Any of the above handlers that trigger an unhandled exception (e.g. a failed assert or an out-of-range list access) are considered invalid. Invalid calls to handlers must not modify `store` .
2019-06-15 22:42:03 +00:00
*Notes*:
2019-06-21 10:13:22 +00:00
1) **Leap seconds** : Slots will last `SECONDS_PER_SLOT + 1` or `SECONDS_PER_SLOT - 1` seconds around leap seconds. This is automatically handled by [UNIX time ](https://en.wikipedia.org/wiki/Unix_time ).
2019-06-15 22:42:03 +00:00
2) **Honest clocks** : Honest nodes are assumed to have clocks synchronized within `SECONDS_PER_SLOT` seconds of each other.
2021-08-18 23:11:38 +00:00
3) **Eth1 data** : The large `ETH1_FOLLOW_DISTANCE` specified in the [honest validator document ](./validator.md ) should ensure that `state.latest_eth1_data` of the canonical beacon chain remains consistent with the canonical Ethereum proof-of-work chain. If not, emergency manual intervention will be required.
2019-06-20 10:58:05 +00:00
4) **Manual forks** : Manual forks may arbitrarily change the fork choice rule but are expected to be enacted at epoch transitions, with the fork details reflected in `state.fork` .
2019-06-21 17:18:24 +00:00
5) **Implementation** : The implementation found in this specification is constructed for ease of understanding rather than for optimization in computation, space, or any other resource. A number of optimized alternatives can be found [here ](https://github.com/protolambda/lmd-ghost ).
2019-06-15 22:42:03 +00:00
2021-11-22 18:29:12 +00:00
### Constant
2022-05-09 08:17:24 +00:00
| Name | Value |
| -------------------- | ----------- |
2021-11-22 18:29:12 +00:00
| `INTERVALS_PER_SLOT` | `uint64(3)` |
2021-11-20 02:27:47 +00:00
### Configuration
2022-05-09 08:17:24 +00:00
| Name | Value |
| ---------------------- | ------------ |
2022-05-20 19:15:40 +00:00
| `PROPOSER_SCORE_BOOST` | `uint64(40)` |
2021-11-20 17:35:45 +00:00
- The proposer score boost is worth `PROPOSER_SCORE_BOOST` percentage of the committee's weight, i.e., for slot with committee weight `committee_weight` the boost weight is equal to `(committee_weight * PROPOSER_SCORE_BOOST) // 100` .
2021-11-20 02:27:47 +00:00
2019-06-20 10:58:05 +00:00
### Helpers
2019-04-22 13:20:48 +00:00
2019-06-25 20:42:37 +00:00
#### `LatestMessage`
```python
@dataclass (eq=True, frozen=True)
class LatestMessage(object):
epoch: Epoch
2019-11-12 20:29:58 +00:00
root: Root
2019-06-25 20:42:37 +00:00
```
2019-06-15 22:42:03 +00:00
#### `Store`
2023-03-09 23:17:11 +00:00
The `Store` is responsible for tracking information required for the fork choice algorithm. The important fields being tracked are described below:
2023-03-14 20:54:57 +00:00
- `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.
2023-03-09 23:17:11 +00:00
- `unrealized_justifications` : stores a map of block root to the unrealized justified checkpoint observed in that block.
2019-06-15 22:42:03 +00:00
```python
2019-06-17 14:48:33 +00:00
@dataclass
class Store(object):
2019-06-30 18:00:22 +00:00
time: uint64
2019-11-05 15:55:34 +00:00
genesis_time: uint64
2019-06-25 03:01:15 +00:00
justified_checkpoint: Checkpoint
finalized_checkpoint: Checkpoint
2023-03-09 23:17:11 +00:00
unrealized_justified_checkpoint: Checkpoint
unrealized_finalized_checkpoint: Checkpoint
2021-11-22 22:44:52 +00:00
proposer_boost_root: Root
2022-03-03 19:43:52 +00:00
equivocating_indices: Set[ValidatorIndex]
2020-03-10 19:20:57 +00:00
blocks: Dict[Root, BeaconBlock] = field(default_factory=dict)
2019-11-12 20:29:58 +00:00
block_states: Dict[Root, BeaconState] = field(default_factory=dict)
2019-06-21 18:55:55 +00:00
checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict)
2019-06-25 20:42:37 +00:00
latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict)
2023-03-09 23:17:11 +00:00
unrealized_justifications: Dict[Root, Checkpoint] = field(default_factory=dict)
2019-06-15 22:42:03 +00:00
```
2019-04-22 13:20:48 +00:00
2023-04-21 08:16:51 +00:00
#### `is_previous_epoch_justified`
```python
def is_previous_epoch_justified(store: Store) -> bool:
2023-06-15 01:07:30 +00:00
current_epoch = get_current_store_epoch(store)
2023-04-21 08:16:51 +00:00
return store.justified_checkpoint.epoch + 1 == current_epoch
```
2020-01-14 00:02:02 +00:00
#### `get_forkchoice_store`
The provided anchor-state will be regarded as a trusted state, to not roll back beyond.
This should be the genesis state for a full client.
2019-04-22 13:20:48 +00:00
2020-03-11 22:41:27 +00:00
*Note* With regards to fork choice, block headers are interchangeable with blocks. The spec is likely to move to headers for reduced overhead in test vectors and better encapsulation. Full implementations store blocks as part of their database and will often use full blocks when dealing with production fork choice.
2019-06-15 22:42:03 +00:00
```python
2020-09-15 04:51:11 +00:00
def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store:
assert anchor_block.state_root == hash_tree_root(anchor_state)
anchor_root = hash_tree_root(anchor_block)
2020-01-14 00:02:02 +00:00
anchor_epoch = get_current_epoch(anchor_state)
justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root)
finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root)
2021-11-22 22:44:52 +00:00
proposer_boost_root = Root()
2019-06-20 18:50:17 +00:00
return Store(
2020-04-23 15:46:27 +00:00
time=uint64(anchor_state.genesis_time + SECONDS_PER_SLOT * anchor_state.slot),
2020-01-14 00:02:02 +00:00
genesis_time=anchor_state.genesis_time,
2019-06-21 18:55:55 +00:00
justified_checkpoint=justified_checkpoint,
finalized_checkpoint=finalized_checkpoint,
2023-03-09 23:17:11 +00:00
unrealized_justified_checkpoint=justified_checkpoint,
unrealized_finalized_checkpoint=finalized_checkpoint,
2021-11-22 22:44:52 +00:00
proposer_boost_root=proposer_boost_root,
2022-03-03 19:43:52 +00:00
equivocating_indices=set(),
2020-09-15 04:51:11 +00:00
blocks={anchor_root: copy(anchor_block)},
2020-06-18 00:47:11 +00:00
block_states={anchor_root: copy(anchor_state)},
checkpoint_states={justified_checkpoint: copy(anchor_state)},
2023-03-09 23:17:11 +00:00
unrealized_justifications={anchor_root: justified_checkpoint}
2019-06-20 18:50:17 +00:00
)
2019-06-15 22:42:03 +00:00
```
2019-04-22 13:20:48 +00:00
2019-12-10 02:02:16 +00:00
#### `get_slots_since_genesis`
```python
2019-12-10 17:12:51 +00:00
def get_slots_since_genesis(store: Store) -> int:
return (store.time - store.genesis_time) // SECONDS_PER_SLOT
2019-12-10 02:02:16 +00:00
```
2019-11-07 00:20:21 +00:00
#### `get_current_slot`
2019-11-05 15:55:34 +00:00
```python
def get_current_slot(store: Store) -> Slot:
2019-12-10 17:12:51 +00:00
return Slot(GENESIS_SLOT + get_slots_since_genesis(store))
2019-11-05 15:55:34 +00:00
```
2023-06-15 01:07:30 +00:00
#### `get_current_store_epoch`
```python
def get_current_store_epoch(store: Store) -> Slot:
return compute_epoch_at_slot(get_current_slot(store))
```
2019-11-07 00:20:21 +00:00
#### `compute_slots_since_epoch_start`
```python
def compute_slots_since_epoch_start(slot: Slot) -> int:
return slot - compute_start_slot_at_epoch(compute_epoch_at_slot(slot))
```
2019-06-15 22:42:03 +00:00
#### `get_ancestor`
2019-04-22 13:20:48 +00:00
2019-06-15 22:42:03 +00:00
```python
2019-11-12 20:29:58 +00:00
def get_ancestor(store: Store, root: Root, slot: Slot) -> Root:
2019-06-15 22:42:03 +00:00
block = store.blocks[root]
2019-07-20 00:13:31 +00:00
if block.slot > slot:
return get_ancestor(store, block.parent_root, slot)
2023-03-14 21:49:41 +00:00
return root
2019-06-15 22:42:03 +00:00
```
2019-04-22 13:20:48 +00:00
2023-04-18 03:14:53 +00:00
#### `get_checkpoint_block`
2023-03-28 04:27:13 +00:00
```python
2023-04-18 03:14:53 +00:00
def get_checkpoint_block(store: Store, root: Root, epoch: Epoch) -> Root:
2023-03-28 04:27:13 +00:00
"""
2023-04-18 06:03:10 +00:00
Compute the checkpoint block for epoch ``epoch`` in the chain of block ``root``
2023-03-28 04:27:13 +00:00
"""
epoch_first_slot = compute_start_slot_at_epoch(epoch)
return get_ancestor(store, root, epoch_first_slot)
```
2023-06-15 01:07:30 +00:00
#### `get_proposer_score`
```python
def get_proposer_score(store: Store) -> Gwei:
justified_checkpoint_state = store.checkpoint_states[store.justified_checkpoint]
committee_weight = get_total_active_balance(justified_checkpoint_state) // SLOTS_PER_EPOCH
return (committee_weight * PROPOSER_SCORE_BOOST) // 100
```
2023-02-15 11:39:33 +00:00
#### `get_weight`
2019-04-22 13:20:48 +00:00
```python
2023-02-15 11:39:33 +00:00
def get_weight(store: Store, root: Root) -> Gwei:
2019-06-21 18:55:55 +00:00
state = store.checkpoint_states[store.justified_checkpoint]
2023-03-09 23:17:11 +00:00
unslashed_and_active_indices = [
i for i in get_active_validator_indices(state, get_current_epoch(state))
if not state.validators[i].slashed
]
2021-11-19 20:35:59 +00:00
attestation_score = Gwei(sum(
2023-03-09 23:17:11 +00:00
state.validators[i].effective_balance for i in unslashed_and_active_indices
2020-04-22 01:50:02 +00:00
if (i in store.latest_messages
2022-03-03 19:43:52 +00:00
and i not in store.equivocating_indices
2019-06-30 20:59:12 +00:00
and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root)
2019-06-19 18:27:54 +00:00
))
2021-12-02 17:57:28 +00:00
if store.proposer_boost_root == Root():
# Return only attestation score if ``proposer_boost_root`` is not set
return attestation_score
2021-12-02 21:00:06 +00:00
2021-12-02 17:57:28 +00:00
# Calculate proposer score if ``proposer_boost_root`` is set
2021-11-19 20:35:59 +00:00
proposer_score = Gwei(0)
2021-12-02 17:57:28 +00:00
# Boost is applied if ``root`` is an ancestor of ``proposer_boost_root``
if get_ancestor(store, store.proposer_boost_root, store.blocks[root].slot) == root:
2023-06-15 01:07:30 +00:00
proposer_score = get_proposer_score(store)
2021-11-19 20:35:59 +00:00
return attestation_score + proposer_score
2019-04-22 13:20:48 +00:00
```
2023-03-09 23:17:11 +00:00
#### `get_voting_source`
```python
def get_voting_source(store: Store, block_root: Root) -> Checkpoint:
"""
2023-03-14 20:54:57 +00:00
Compute the voting source checkpoint in event that block with root ``block_root`` is the head block
2023-03-13 20:48:34 +00:00
"""
2023-03-09 23:17:11 +00:00
block = store.blocks[block_root]
2023-06-15 01:07:30 +00:00
current_epoch = get_current_store_epoch(store)
2023-03-13 20:48:34 +00:00
block_epoch = compute_epoch_at_slot(block.slot)
2023-03-09 23:17:11 +00:00
if current_epoch > block_epoch:
2023-03-14 20:54:57 +00:00
# The block is from a prior epoch, the voting source will be pulled-up
2023-03-09 23:17:11 +00:00
return store.unrealized_justifications[block_root]
else:
2023-03-14 20:54:57 +00:00
# The block is not from a prior epoch, therefore the voting source is not pulled up
2023-03-09 23:17:11 +00:00
head_state = store.block_states[block_root]
2023-03-13 20:48:34 +00:00
return head_state.current_justified_checkpoint
2023-03-09 23:17:11 +00:00
```
2019-11-25 22:06:33 +00:00
#### `filter_block_tree`
2023-03-14 20:54:57 +00:00
*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` .
2023-03-09 23:17:11 +00:00
2019-11-25 22:06:33 +00:00
```python
2019-11-25 22:44:22 +00:00
def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconBlock]) -> bool:
2019-11-25 22:06:33 +00:00
block = store.blocks[block_root]
children = [
root for root in store.blocks.keys()
2019-11-25 22:44:22 +00:00
if store.blocks[root].parent_root == block_root
2019-11-25 22:06:33 +00:00
]
# If any children branches contain expected finalized/justified checkpoints,
# add to filtered block-tree and signal viability to parent.
if any(children):
2019-12-08 18:50:04 +00:00
filter_block_tree_result = [filter_block_tree(store, child, blocks) for child in children]
if any(filter_block_tree_result):
2019-11-25 22:06:33 +00:00
blocks[block_root] = block
return True
return False
2023-06-15 01:07:30 +00:00
current_epoch = get_current_store_epoch(store)
2023-03-09 23:17:11 +00:00
voting_source = get_voting_source(store, block_root)
2019-11-25 22:44:22 +00:00
2023-06-15 01:07:30 +00:00
# The voting source should be either at the same height as the store's justified checkpoint or
# not more than two epochs ago
2019-12-04 23:50:48 +00:00
correct_justified = (
store.justified_checkpoint.epoch == GENESIS_EPOCH
2023-03-09 23:17:11 +00:00
or voting_source.epoch == store.justified_checkpoint.epoch
2023-06-15 01:07:30 +00:00
or voting_source.epoch + 2 >= current_epoch
2019-12-04 23:50:48 +00:00
)
2023-03-09 23:17:11 +00:00
2023-04-18 03:26:16 +00:00
finalized_checkpoint_block = get_checkpoint_block(
store,
2023-04-18 03:49:08 +00:00
block_root,
2023-04-18 03:26:16 +00:00
store.finalized_checkpoint.epoch,
)
2019-12-04 23:50:48 +00:00
correct_finalized = (
store.finalized_checkpoint.epoch == GENESIS_EPOCH
2023-04-18 03:26:16 +00:00
or store.finalized_checkpoint.root == finalized_checkpoint_block
2019-12-04 23:50:48 +00:00
)
2023-03-09 23:17:11 +00:00
2019-12-04 23:50:48 +00:00
# If expected finalized/justified, add to viable block-tree and signal viability to parent.
if correct_justified and correct_finalized:
2019-11-25 22:06:33 +00:00
blocks[block_root] = block
return True
2019-12-04 23:50:48 +00:00
# Otherwise, branch not viable
2019-11-25 22:06:33 +00:00
return False
```
#### `get_filtered_block_tree`
```python
def get_filtered_block_tree(store: Store) -> Dict[Root, BeaconBlock]:
2019-12-04 23:50:48 +00:00
"""
2019-12-25 17:51:29 +00:00
Retrieve a filtered block tree from ``store``, only returning branches
2019-12-04 23:50:48 +00:00
whose leaf state's justified/finalized info agrees with that in ``store``.
"""
base = store.justified_checkpoint.root
2019-11-25 22:44:22 +00:00
blocks: Dict[Root, BeaconBlock] = {}
2019-12-04 23:50:48 +00:00
filter_block_tree(store, base, blocks)
2019-11-25 22:06:33 +00:00
return blocks
```
2019-06-15 22:42:03 +00:00
#### `get_head`
2019-04-22 13:20:48 +00:00
```python
2019-11-12 20:29:58 +00:00
def get_head(store: Store) -> Root:
2019-12-04 23:50:48 +00:00
# Get filtered block tree that only includes viable branches
2019-11-25 22:06:33 +00:00
blocks = get_filtered_block_tree(store)
2019-06-15 22:42:03 +00:00
# Execute the LMD-GHOST fork choice
2019-06-21 18:55:55 +00:00
head = store.justified_checkpoint.root
2019-06-15 22:42:03 +00:00
while True:
2019-06-20 20:48:10 +00:00
children = [
2019-11-25 22:06:33 +00:00
root for root in blocks.keys()
2020-10-07 23:52:51 +00:00
if blocks[root].parent_root == head
2019-06-20 20:48:10 +00:00
]
2019-04-22 13:20:48 +00:00
if len(children) == 0:
return head
2019-06-20 10:58:05 +00:00
# Sort by latest attesting balance with ties broken lexicographically
2021-11-30 15:55:03 +00:00
# Ties broken by favoring block with lexicographically higher root
2023-02-15 11:39:33 +00:00
head = max(children, key=lambda root: (get_weight(store, root), root))
2019-06-15 22:42:03 +00:00
```
2023-03-09 23:17:11 +00:00
#### `update_checkpoints`
2019-11-05 15:55:34 +00:00
```python
2023-03-09 23:17:11 +00:00
def update_checkpoints(store: Store, justified_checkpoint: Checkpoint, finalized_checkpoint: Checkpoint) -> None:
2019-11-07 18:51:53 +00:00
"""
2023-03-09 23:17:11 +00:00
Update checkpoints in store if necessary
"""
# Update justified checkpoint
if justified_checkpoint.epoch > store.justified_checkpoint.epoch:
store.justified_checkpoint = justified_checkpoint
# Update finalized checkpoint
if finalized_checkpoint.epoch > store.finalized_checkpoint.epoch:
store.finalized_checkpoint = finalized_checkpoint
```
2019-11-07 18:51:53 +00:00
2023-03-09 23:17:11 +00:00
#### `update_unrealized_checkpoints`
```python
def update_unrealized_checkpoints(store: Store, unrealized_justified_checkpoint: Checkpoint,
unrealized_finalized_checkpoint: Checkpoint) -> None:
2019-11-07 18:51:53 +00:00
"""
2023-03-09 23:17:11 +00:00
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
2019-11-05 15:55:34 +00:00
2023-03-09 23:17:11 +00:00
# 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)
2023-03-14 20:54:57 +00:00
# If the block is from a prior epoch, apply the realized values
2023-03-09 23:17:11 +00:00
block_epoch = compute_epoch_at_slot(store.blocks[block_root].slot)
2023-06-15 01:07:30 +00:00
current_epoch = get_current_store_epoch(store)
2023-03-09 23:17:11 +00:00
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)
2023-03-14 20:54:57 +00:00
# Update store time
2023-03-09 23:17:11 +00:00
store.time = time
current_slot = get_current_slot(store)
2023-03-14 20:54:57 +00:00
# If this is a new slot, reset store.proposer_boost_root
2023-03-09 23:17:11 +00:00
if current_slot > previous_slot:
store.proposer_boost_root = Root()
2019-11-05 15:55:34 +00:00
2023-03-14 20:54:57 +00:00
# If a new epoch, pull-up justification and finalization from previous epoch
2023-03-14 21:49:41 +00:00
if current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0:
2023-03-14 20:54:57 +00:00
update_checkpoints(store, store.unrealized_justified_checkpoint, store.unrealized_finalized_checkpoint)
2019-11-05 15:55:34 +00:00
```
2020-01-15 23:03:07 +00:00
#### `on_attestation` helpers
2021-11-13 10:23:21 +00:00
2021-11-18 01:15:12 +00:00
##### `validate_target_epoch_against_current_time`
2020-01-15 23:03:07 +00:00
```python
2021-11-18 01:15:12 +00:00
def validate_target_epoch_against_current_time(store: Store, attestation: Attestation) -> None:
2020-01-15 23:03:07 +00:00
target = attestation.data.target
# Attestations must be from the current or previous epoch
2023-06-15 01:07:30 +00:00
current_epoch = get_current_store_epoch(store)
2020-01-15 23:03:07 +00:00
# Use GENESIS_EPOCH for previous when genesis to avoid underflow
previous_epoch = current_epoch - 1 if current_epoch > GENESIS_EPOCH else GENESIS_EPOCH
2020-04-30 06:27:02 +00:00
# If attestation target is from a future epoch, delay consideration until the epoch arrives
2020-01-15 23:03:07 +00:00
assert target.epoch in [current_epoch, previous_epoch]
2021-11-13 10:23:21 +00:00
```
##### `validate_on_attestation`
```python
2021-11-13 10:38:53 +00:00
def validate_on_attestation(store: Store, attestation: Attestation, is_from_block: bool) -> None:
2021-11-13 10:23:21 +00:00
target = attestation.data.target
2021-11-13 10:38:53 +00:00
# If the given attestation is not from a beacon block message, we have to check the target epoch scope.
if not is_from_block:
2021-11-18 01:15:12 +00:00
validate_target_epoch_against_current_time(store, attestation)
2021-11-13 10:38:53 +00:00
2021-11-13 10:23:21 +00:00
# Check that the epoch number and slot number are matching
2020-01-15 23:03:07 +00:00
assert target.epoch == compute_epoch_at_slot(attestation.data.slot)
2023-03-14 20:54:57 +00:00
# Attestation target must be for a known block. If target block is unknown, delay consideration until block is found
2020-01-15 23:03:07 +00:00
assert target.root in store.blocks
# Attestations must be for a known block. If block is unknown, delay consideration until the block is found
assert attestation.data.beacon_block_root in store.blocks
# Attestations must not be for blocks in the future. If not, the attestation should not be considered
assert store.blocks[attestation.data.beacon_block_root].slot < = attestation.data.slot
2020-06-02 07:22:33 +00:00
# LMD vote must be consistent with FFG vote target
2023-04-18 03:14:53 +00:00
assert target.root == get_checkpoint_block(store, attestation.data.beacon_block_root, target.epoch)
2020-04-22 01:50:02 +00:00
2020-01-15 23:03:07 +00:00
# Attestations can only affect the fork choice of subsequent slots.
# Delay consideration in the fork choice until their slot is in the past.
assert get_current_slot(store) >= attestation.data.slot + 1
```
##### `store_target_checkpoint_state`
```python
def store_target_checkpoint_state(store: Store, target: Checkpoint) -> None:
# Store target checkpoint state if not yet seen
if target not in store.checkpoint_states:
2020-06-18 00:47:11 +00:00
base_state = copy(store.block_states[target.root])
2020-06-12 21:37:07 +00:00
if base_state.slot < compute_start_slot_at_epoch ( target . epoch ) :
process_slots(base_state, compute_start_slot_at_epoch(target.epoch))
2020-01-15 23:03:07 +00:00
store.checkpoint_states[target] = base_state
```
##### `update_latest_messages`
```python
2023-03-14 21:49:41 +00:00
def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None:
2020-01-15 23:03:07 +00:00
target = attestation.data.target
beacon_block_root = attestation.data.beacon_block_root
2022-03-03 19:43:52 +00:00
non_equivocating_attesting_indices = [i for i in attesting_indices if i not in store.equivocating_indices]
for i in non_equivocating_attesting_indices:
if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch:
store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root)
2020-01-15 23:03:07 +00:00
```
2019-06-15 22:42:03 +00:00
### Handlers
#### `on_tick`
```python
2019-06-30 18:00:22 +00:00
def on_tick(store: Store, time: uint64) -> None:
2023-03-14 20:54:57 +00:00
# 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``
2023-03-09 23:17:11 +00:00
tick_slot = (time - store.genesis_time) // SECONDS_PER_SLOT
while get_current_slot(store) < tick_slot:
previous_time = store.genesis_time + (get_current_slot(store) + 1) * SECONDS_PER_SLOT
on_tick_per_slot(store, previous_time)
on_tick_per_slot(store, time)
2019-04-22 13:20:48 +00:00
```
2019-05-01 23:10:01 +00:00
2019-06-15 22:42:03 +00:00
#### `on_block`
2019-05-01 23:10:01 +00:00
2019-06-15 22:42:03 +00:00
```python
2019-11-21 22:13:45 +00:00
def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
block = signed_block.message
2020-06-11 01:51:18 +00:00
# Parent block must be known
2019-06-25 03:09:57 +00:00
assert block.parent_root in store.block_states
2020-06-13 06:04:16 +00:00
# Make a copy of the state to avoid mutability issues
2020-06-18 00:47:11 +00:00
pre_state = copy(store.block_states[block.parent_root])
2022-07-29 07:13:32 +00:00
# Blocks cannot be in the future. If they are, their consideration must be delayed until they are in the past.
2019-12-10 17:12:51 +00:00
assert get_current_slot(store) >= block.slot
2020-01-21 01:10:39 +00:00
# Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor)
2020-01-21 00:40:36 +00:00
finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
2020-01-21 01:10:39 +00:00
assert block.slot > finalized_slot
2020-01-22 21:31:23 +00:00
# Check block is a descendant of the finalized block at the checkpoint finalized slot
2023-04-18 03:26:16 +00:00
finalized_checkpoint_block = get_checkpoint_block(
2023-04-05 03:38:20 +00:00
store,
block.parent_root,
store.finalized_checkpoint.epoch,
)
2023-06-15 01:07:30 +00:00
assert store.finalized_checkpoint.root == finalized_checkpoint_block
2020-01-21 01:10:39 +00:00
2019-06-15 22:42:03 +00:00
# Check the block is valid and compute the post-state
2020-10-15 07:58:26 +00:00
state = pre_state.copy()
2023-03-09 23:17:11 +00:00
block_root = hash_tree_root(block)
2020-10-15 07:58:26 +00:00
state_transition(state, signed_block, True)
2020-06-10 05:09:40 +00:00
# Add new block to the store
2023-03-09 23:17:11 +00:00
store.blocks[block_root] = block
2019-06-20 20:48:10 +00:00
# Add new state for this block to the store
2023-03-09 23:17:11 +00:00
store.block_states[block_root] = state
2019-06-21 18:55:55 +00:00
2021-11-19 20:35:59 +00:00
# Add proposer score boost if the block is timely
2021-11-23 15:02:04 +00:00
time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT
is_before_attesting_interval = time_into_slot < SECONDS_PER_SLOT / / INTERVALS_PER_SLOT
2021-11-22 18:29:12 +00:00
if get_current_slot(store) == block.slot and is_before_attesting_interval:
2021-11-22 22:44:52 +00:00
store.proposer_boost_root = hash_tree_root(block)
2021-11-19 20:35:59 +00:00
2023-03-09 23:17:11 +00:00
# Update checkpoints in store if necessary
update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint)
2019-06-21 18:55:55 +00:00
2023-03-14 20:54:57 +00:00
# Eagerly compute unrealized justification and finality
2023-03-09 23:17:11 +00:00
compute_pulled_up_tip(store, block_root)
2019-06-15 22:42:03 +00:00
```
2019-05-01 23:10:01 +00:00
2019-06-15 22:42:03 +00:00
#### `on_attestation`
```python
2021-11-13 10:23:21 +00:00
def on_attestation(store: Store, attestation: Attestation, is_from_block: bool=False) -> None:
2019-11-12 23:24:33 +00:00
"""
Run ``on_attestation`` upon receiving a new ``attestation`` from either within a block or directly on the wire.
An ``attestation`` that is asserted as invalid may be valid at a later time,
consider scheduling it for later processing in such case.
"""
2021-11-13 10:38:53 +00:00
validate_on_attestation(store, attestation, is_from_block)
2021-11-13 10:23:21 +00:00
2020-01-15 23:03:07 +00:00
store_target_checkpoint_state(store, attestation.data.target)
2019-06-20 20:48:10 +00:00
2020-01-15 23:03:07 +00:00
# Get state at the `target` to fully validate attestation
target_state = store.checkpoint_states[attestation.data.target]
2019-06-30 09:02:18 +00:00
indexed_attestation = get_indexed_attestation(target_state, attestation)
2019-06-30 22:10:28 +00:00
assert is_valid_indexed_attestation(target_state, indexed_attestation)
2019-06-20 20:48:10 +00:00
2020-01-15 23:03:07 +00:00
# Update latest messages for attesting indices
update_latest_messages(store, indexed_attestation.attesting_indices, attestation)
2019-06-15 22:42:03 +00:00
```
2022-03-01 19:42:49 +00:00
#### `on_attester_slashing`
2022-03-08 18:33:59 +00:00
*Note*: `on_attester_slashing` should be called while syncing and a client MUST maintain the equivocation set of `AttesterSlashing` s from at least the latest finalized checkpoint.
2022-03-08 14:52:03 +00:00
2022-03-01 19:42:49 +00:00
```python
def on_attester_slashing(store: Store, attester_slashing: AttesterSlashing) -> None:
"""
2022-03-03 20:50:05 +00:00
Run ``on_attester_slashing`` immediately upon receiving a new ``AttesterSlashing``
from either within a block or directly on the wire.
2022-03-01 19:42:49 +00:00
"""
attestation_1 = attester_slashing.attestation_1
attestation_2 = attester_slashing.attestation_2
assert is_slashable_attestation_data(attestation_1.data, attestation_2.data)
state = store.block_states[store.justified_checkpoint.root]
assert is_valid_indexed_attestation(state, attestation_1)
assert is_valid_indexed_attestation(state, attestation_2)
indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices)
2022-03-03 19:43:52 +00:00
for index in indices:
store.equivocating_indices.add(index)
2023-01-24 13:08:41 +00:00
```