mirror of
https://github.com/status-im/eth2.0-specs.git
synced 2025-01-12 11:44:41 +00:00
add initial fork choice bounce prevention and tests
This commit is contained in:
parent
6a90949856
commit
405e218598
@ -43,6 +43,12 @@ The head block root associated with a `store` is defined as `get_head(store)`. A
|
||||
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`.
|
||||
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).
|
||||
|
||||
### Configuration
|
||||
|
||||
| Name | Value | Unit | Duration |
|
||||
| - | - | :-: | :-: |
|
||||
| `SAFE_SLOTS_TO_UPDATE_JUSTIFIED` | `9` | slots | 108 seconds |
|
||||
|
||||
### Helpers
|
||||
|
||||
#### `LatestMessage`
|
||||
@ -60,8 +66,10 @@ class LatestMessage(object):
|
||||
@dataclass
|
||||
class Store(object):
|
||||
time: uint64
|
||||
genesis_time: uint64
|
||||
justified_checkpoint: Checkpoint
|
||||
finalized_checkpoint: Checkpoint
|
||||
queued_justified_checkpoints: List[Checkpoint, 2**40] = field(default_factory=list)
|
||||
blocks: Dict[Hash, BeaconBlock] = field(default_factory=dict)
|
||||
block_states: Dict[Hash, BeaconState] = field(default_factory=dict)
|
||||
checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict)
|
||||
@ -78,6 +86,7 @@ def get_genesis_store(genesis_state: BeaconState) -> Store:
|
||||
finalized_checkpoint = Checkpoint(epoch=GENESIS_EPOCH, root=root)
|
||||
return Store(
|
||||
time=genesis_state.genesis_time,
|
||||
genesis_time=genesis_state.genesis_time,
|
||||
justified_checkpoint=justified_checkpoint,
|
||||
finalized_checkpoint=finalized_checkpoint,
|
||||
blocks={root: genesis_block},
|
||||
@ -86,6 +95,11 @@ def get_genesis_store(genesis_state: BeaconState) -> Store:
|
||||
)
|
||||
```
|
||||
|
||||
```python
|
||||
def get_current_slot(store: Store) -> Slot:
|
||||
return Slot((store.time - store.genesis_time) // SECONDS_PER_SLOT)
|
||||
```
|
||||
|
||||
#### `get_ancestor`
|
||||
|
||||
```python
|
||||
@ -130,13 +144,44 @@ def get_head(store: Store) -> Hash:
|
||||
head = max(children, key=lambda root: (get_latest_attesting_balance(store, root), root))
|
||||
```
|
||||
|
||||
#### `should_update_justified_checkpoint`
|
||||
|
||||
```python
|
||||
def should_update_justified_checkpoint(store: Store, justified_checkpoint: Checkpoint) -> bool:
|
||||
current_epoch = compute_epoch_at_slot(get_current_slot(store))
|
||||
|
||||
if get_current_slot(store) - compute_start_slot_at_epoch(current_epoch) < SAFE_SLOTS_TO_UPDATE_JUSTIFIED:
|
||||
return True
|
||||
|
||||
justified_block = store.blocks[justified_checkpoint.root]
|
||||
if justified_block.slot <= compute_start_slot_at_epoch(store.justified_checkpoint.epoch):
|
||||
return False
|
||||
if not get_ancestor(store, justified_checkpoint.root, store.blocks[justified_checkpoint.root].slot):
|
||||
return False
|
||||
|
||||
return True
|
||||
```
|
||||
|
||||
### Handlers
|
||||
|
||||
#### `on_tick`
|
||||
|
||||
```python
|
||||
def on_tick(store: Store, time: uint64) -> None:
|
||||
previous_slot = get_current_slot(store)
|
||||
|
||||
# update store time
|
||||
store.time = time
|
||||
|
||||
current_slot = get_current_slot(store)
|
||||
# not a new epoch, return
|
||||
if not (current_slot > previous_slot and current_slot % SLOTS_PER_EPOCH == 0):
|
||||
return
|
||||
# if new epoch and there are queued_justified_checkpoints, update if any is better than the best in store
|
||||
if any(store.queued_justified_checkpoints):
|
||||
best_justified_checkpoint = max(store.queued_justified_checkpoints, key=lambda checkpoint: checkpoint.epoch)
|
||||
if best_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
|
||||
store.justified_checkpoint = best_justified_checkpoint
|
||||
```
|
||||
|
||||
#### `on_block`
|
||||
@ -164,7 +209,10 @@ def on_block(store: Store, block: BeaconBlock) -> None:
|
||||
|
||||
# Update justified checkpoint
|
||||
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
|
||||
store.justified_checkpoint = state.current_justified_checkpoint
|
||||
if should_update_justified_checkpoint(store, state.current_justified_checkpoint):
|
||||
store.justified_checkpoint = state.current_justified_checkpoint
|
||||
else:
|
||||
store.queued_justified_checkpoints.append(state.current_justified_checkpoint)
|
||||
|
||||
# Update finalized checkpoint
|
||||
if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch:
|
||||
|
72
test_libs/pyspec/eth2spec/test/fork_choice/test_on_tick.py
Normal file
72
test_libs/pyspec/eth2spec/test/fork_choice/test_on_tick.py
Normal file
@ -0,0 +1,72 @@
|
||||
from eth2spec.test.context import with_all_phases, spec_state_test
|
||||
|
||||
|
||||
def run_on_tick(spec, store, time, new_justified_checkpoint=None):
|
||||
previous_justified_checkpoint = store.justified_checkpoint
|
||||
|
||||
spec.on_tick(store, time)
|
||||
|
||||
assert store.time == time
|
||||
|
||||
if new_justified_checkpoint:
|
||||
assert store.justified_checkpoint == new_justified_checkpoint
|
||||
assert store.justified_checkpoint.epoch > previous_justified_checkpoint.epoch
|
||||
else:
|
||||
assert store.justified_checkpoint == previous_justified_checkpoint
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_basic(spec, state):
|
||||
store = spec.get_genesis_store(state)
|
||||
run_on_tick(spec, store, store.time + 1)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_update_justified_single(spec, state):
|
||||
store = spec.get_genesis_store(state)
|
||||
seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
|
||||
|
||||
new_justified = spec.Checkpoint(
|
||||
epoch=store.justified_checkpoint.epoch + 1,
|
||||
root=b'\x55' * 32,
|
||||
)
|
||||
|
||||
store.queued_justified_checkpoints.append(new_justified)
|
||||
|
||||
run_on_tick(spec, store, store.time + seconds_per_epoch, new_justified)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_update_justified_multiple(spec, state):
|
||||
store = spec.get_genesis_store(state)
|
||||
seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
|
||||
|
||||
new_justified = None # remember checkpoint with latest epoch
|
||||
for i in range(3):
|
||||
new_justified = spec.Checkpoint(
|
||||
epoch=store.justified_checkpoint.epoch + i + 1,
|
||||
root=i.to_bytes(1, byteorder='big') * 32,
|
||||
)
|
||||
store.queued_justified_checkpoints.append(new_justified)
|
||||
|
||||
run_on_tick(spec, store, store.time + seconds_per_epoch, new_justified)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_no_update_(spec, state):
|
||||
store = spec.get_genesis_store(state)
|
||||
seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
|
||||
|
||||
new_justified = spec.Checkpoint(
|
||||
epoch=store.justified_checkpoint.epoch + 1,
|
||||
root=b'\x55' * 32,
|
||||
)
|
||||
|
||||
store.queued_justified_checkpoints.append(new_justified)
|
||||
|
||||
run_on_tick(spec, store, store.time + seconds_per_epoch, new_justified)
|
||||
|
Loading…
x
Reference in New Issue
Block a user