Merge pull request #3431 from saltiniroberto/fork-choice-changes-for-confirmaton-rule

Confirmation rule prerequisite - fork choice filter change
This commit is contained in:
danny 2024-01-16 08:49:21 -07:00 committed by GitHub
commit 9a54a32238
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 108 additions and 41 deletions

View File

@ -16,10 +16,12 @@
- [`get_forkchoice_store`](#get_forkchoice_store)
- [`get_slots_since_genesis`](#get_slots_since_genesis)
- [`get_current_slot`](#get_current_slot)
- [`get_current_store_epoch`](#get_current_store_epoch)
- [`compute_slots_since_epoch_start`](#compute_slots_since_epoch_start)
- [`get_ancestor`](#get_ancestor)
- [`calculate_committee_fraction`](#calculate_committee_fraction)
- [`get_checkpoint_block`](#get_checkpoint_block)
- [`get_proposer_score`](#get_proposer_score)
- [`get_weight`](#get_weight)
- [`get_voting_source`](#get_voting_source)
- [`filter_block_tree`](#filter_block_tree)
@ -140,8 +142,7 @@ class Store(object):
```python
def is_previous_epoch_justified(store: Store) -> bool:
current_slot = get_current_slot(store)
current_epoch = compute_epoch_at_slot(current_slot)
current_epoch = get_current_store_epoch(store)
return store.justified_checkpoint.epoch + 1 == current_epoch
```
@ -190,6 +191,13 @@ def get_current_slot(store: Store) -> Slot:
return Slot(GENESIS_SLOT + get_slots_since_genesis(store))
```
#### `get_current_store_epoch`
```python
def get_current_store_epoch(store: Store) -> Epoch:
return compute_epoch_at_slot(get_current_slot(store))
```
#### `compute_slots_since_epoch_start`
```python
@ -226,6 +234,15 @@ def get_checkpoint_block(store: Store, root: Root, epoch: Epoch) -> Root:
return get_ancestor(store, root, epoch_first_slot)
```
#### `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
```
#### `get_weight`
```python
@ -249,7 +266,7 @@ def get_weight(store: Store, root: Root) -> Gwei:
proposer_score = Gwei(0)
# 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:
proposer_score = calculate_committee_fraction(state, PROPOSER_SCORE_BOOST)
proposer_score = get_proposer_score(store)
return attestation_score + proposer_score
```
@ -261,7 +278,7 @@ 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))
current_epoch = get_current_store_epoch(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
@ -293,23 +310,17 @@ def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconB
return True
return False
current_epoch = compute_epoch_at_slot(get_current_slot(store))
current_epoch = get_current_store_epoch(store)
voting_source = get_voting_source(store, block_root)
# The voting source should be at the same height as the store's justified checkpoint
# The voting source should be either at the same height as the store's justified checkpoint or
# not more than two epochs ago
correct_justified = (
store.justified_checkpoint.epoch == GENESIS_EPOCH
or voting_source.epoch == store.justified_checkpoint.epoch
or voting_source.epoch + 2 >= current_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_checkpoint_block = get_checkpoint_block(
store,
block_root,
@ -519,7 +530,7 @@ def compute_pulled_up_tip(store: Store, block_root: Root) -> None:
# 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))
current_epoch = get_current_store_epoch(store)
if block_epoch < current_epoch:
update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint)
```
@ -556,7 +567,7 @@ def validate_target_epoch_against_current_time(store: Store, attestation: Attest
target = attestation.data.target
# Attestations must be from the current or previous epoch
current_epoch = compute_epoch_at_slot(get_current_slot(store))
current_epoch = get_current_store_epoch(store)
# Use GENESIS_EPOCH for previous when genesis to avoid underflow
previous_epoch = current_epoch - 1 if current_epoch > GENESIS_EPOCH else GENESIS_EPOCH
# If attestation target is from a future epoch, delay consideration until the epoch arrives
@ -653,7 +664,7 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
block.parent_root,
store.finalized_checkpoint.epoch,
)
assert store.finalized_checkpoint.root == finalized_checkpoint_block
assert store.finalized_checkpoint.root == finalized_checkpoint_block
# Check the block is valid and compute the post-state
state = pre_state.copy()

View File

@ -286,34 +286,43 @@ def _run_include_votes_of_another_empty_chain(spec, state, enough_ffg, is_justif
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:
# build chain with head in epoch 3 and justified checkpoint in epoch 2
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 spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 3
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 2
else:
# fill one more epoch
# build chain with head in epoch 4 and justified checkpoint in epoch 3
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 spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4
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)
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 3
assert spec.compute_epoch_at_slot(state.slot) == 3
assert state.current_justified_checkpoint.epoch == 2
else:
# try to find the block that can justify epoch 4
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4
assert spec.compute_epoch_at_slot(state.slot) == 4
assert state.current_justified_checkpoint.epoch == 3
if is_justifying_previous_epoch:
# try to find the block that can justify epoch 3 by including only previous epoch attesations
_, justifying_slot = find_next_justifying_slot(spec, state, False, True)
assert spec.compute_epoch_at_slot(justifying_slot) == 4
else:
# try to find the block that can justify epoch 4 by including current epoch attestations
_, justifying_slot = find_next_justifying_slot(spec, state, True, True)
assert spec.compute_epoch_at_slot(justifying_slot) == 4
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
@ -324,15 +333,14 @@ def _run_include_votes_of_another_empty_chain(spec, state, enough_ffg, is_justif
# 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]
assert spec.compute_epoch_at_slot(signed_block_y.message.slot) == 4
# create 2/3 votes for the empty chain
attestations_for_y = []
@ -345,7 +353,6 @@ def _run_include_votes_of_another_empty_chain(spec, state, enough_ffg, is_justif
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:
@ -368,12 +375,21 @@ def _run_include_votes_of_another_empty_chain(spec, state, enough_ffg, is_justif
if is_ready_to_justify(spec, state):
break
assert spec.get_head(store) == signed_block_y.message.hash_tree_root()
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4
assert spec.compute_epoch_at_slot(signed_block_y.message.slot) == 4
assert spec.compute_epoch_at_slot(signed_block_z.message.slot) == 4
# y is not filtered out & wins the LMD competition, so y should be the head
y_voting_source_epoch = spec.get_voting_source(store, signed_block_y.message.hash_tree_root()).epoch
if is_justifying_previous_epoch:
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 2
assert y_voting_source_epoch == 2
assert y_voting_source_epoch == store.justified_checkpoint.epoch
else:
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
assert y_voting_source_epoch == 3
assert y_voting_source_epoch == store.justified_checkpoint.epoch
assert spec.get_head(store) == signed_block_y.message.hash_tree_root()
if enough_ffg:
assert is_ready_to_justify(spec, state)
@ -386,17 +402,57 @@ def _run_include_votes_of_another_empty_chain(spec, state, enough_ffg, is_justif
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()
y_voting_source_epoch = spec.get_voting_source(store, signed_block_y.message.hash_tree_root()).epoch
if is_justifying_previous_epoch:
# y is filtered out & so z should be the head
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
assert y_voting_source_epoch == 2
assert y_voting_source_epoch != store.justified_checkpoint.epoch
assert not (y_voting_source_epoch + 2 >= spec.compute_epoch_at_slot(spec.get_current_slot(store)))
assert spec.get_head(store) == signed_block_z.message.hash_tree_root()
else:
if enough_ffg:
# y is not filtered out & wins the LMD competition, so y should be the head
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 4
assert y_voting_source_epoch == 3
assert y_voting_source_epoch != store.justified_checkpoint.epoch
assert y_voting_source_epoch + 2 >= spec.compute_epoch_at_slot(spec.get_current_slot(store))
assert spec.get_head(store) == signed_block_y.message.hash_tree_root()
else:
# y is not filtered out & wins the LMD competition, so y should be the head
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
assert y_voting_source_epoch == 3
assert y_voting_source_epoch == store.justified_checkpoint.epoch
assert spec.get_head(store) == signed_block_y.message.hash_tree_root()
# 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)) == 6
y_voting_source_epoch = spec.get_voting_source(store, signed_block_y.message.hash_tree_root()).epoch
if is_justifying_previous_epoch:
# y is filtered out & so z should be the head
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
assert y_voting_source_epoch == 2
assert y_voting_source_epoch != store.justified_checkpoint.epoch
assert not (y_voting_source_epoch + 2 >= spec.compute_epoch_at_slot(spec.get_current_slot(store)))
assert spec.get_head(store) == signed_block_z.message.hash_tree_root()
else:
if enough_ffg:
# y is filtered out & so z should be the head
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 4
assert y_voting_source_epoch == 3
assert y_voting_source_epoch != store.justified_checkpoint.epoch
assert not (y_voting_source_epoch + 2 >= spec.compute_epoch_at_slot(spec.get_current_slot(store)))
assert spec.get_head(store) == signed_block_z.message.hash_tree_root()
else:
# y is not filtered out & wins the LMD competition, so y should be the head
assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3
assert y_voting_source_epoch == 3
assert y_voting_source_epoch == store.justified_checkpoint.epoch
assert spec.get_head(store) == signed_block_y.message.hash_tree_root()
yield 'steps', test_steps