Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Confirmation rule prerequisite - fork choice filter change #3431

Merged
Merged
45 changes: 28 additions & 17 deletions specs/phase0/fork-choice.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
```

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
```

Expand All @@ -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
Expand Down Expand 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this is the substantive change

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(
saltiniroberto marked this conversation as resolved.
Show resolved Hide resolved
store,
block_root,
Expand Down Expand Up @@ -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)
```
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
92 changes: 74 additions & 18 deletions tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_reorg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
state_a = state.copy()

if is_justifying_previous_epoch:
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:
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
state_a = state.copy()

if is_justifying_previous_epoch:
# try to find the block that can justify epoch 3
# 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
# 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
Expand All @@ -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 = []
Expand All @@ -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:
Expand All @@ -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)
Expand All @@ -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
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()
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()
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

Expand Down