diff --git a/specs/merge/fork-choice.md b/specs/merge/fork-choice.md index 7a8db7dd9..cbfe33f4a 100644 --- a/specs/merge/fork-choice.md +++ b/specs/merge/fork-choice.md @@ -178,10 +178,7 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: # Add proposer score boost if the block is timely is_before_attesting_interval = store.time % SECONDS_PER_SLOT < SECONDS_PER_SLOT // INTERVALS_PER_SLOT if get_current_slot(store) == block.slot and is_before_attesting_interval: - store.proposer_score_boost = LatestMessage( - root=hash_tree_root(block), - epoch=compute_epoch_at_slot(block.slot) - ) + store.proposer_boost_root = hash_tree_root(block) # Update justified checkpoint if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 9c27a423f..6e50b99d4 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -100,7 +100,7 @@ class Store(object): justified_checkpoint: Checkpoint finalized_checkpoint: Checkpoint best_justified_checkpoint: Checkpoint - proposer_score_boost: LatestMessage + proposer_boost_root: Root blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) block_states: Dict[Root, BeaconState] = field(default_factory=dict) checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict) @@ -121,14 +121,14 @@ def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) - 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) - proposer_score_boost = LatestMessage(root=Root(), epoch=Epoch(0)) + proposer_boost_root = Root() return Store( time=uint64(anchor_state.genesis_time + SECONDS_PER_SLOT * anchor_state.slot), genesis_time=anchor_state.genesis_time, justified_checkpoint=justified_checkpoint, finalized_checkpoint=finalized_checkpoint, best_justified_checkpoint=justified_checkpoint, - proposer_score_boost=proposer_score_boost, + proposer_boost_root=proposer_boost_root, blocks={anchor_root: copy(anchor_block)}, block_states={anchor_root: copy(anchor_state)}, checkpoint_states={justified_checkpoint: copy(anchor_state)}, @@ -182,9 +182,9 @@ def get_latest_attesting_balance(store: Store, root: Root) -> Gwei: and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root) )) proposer_score = Gwei(0) - if store.proposer_score_boost.root != Root(): + if store.proposer_boost_root != Root(): block = store.blocks[root] - if get_ancestor(store, root, block.slot) == store.proposer_score_boost.root: + if get_ancestor(store, root, block.slot) == store.proposer_boost_root: num_validators = len(get_active_validator_indices(state, get_current_epoch(state))) avg_balance = get_total_active_balance(state) // num_validators committee_size = num_validators // SLOTS_PER_EPOCH @@ -371,9 +371,9 @@ def on_tick(store: Store, time: uint64) -> None: current_slot = get_current_slot(store) - # Reset store.proposer_score_boost if this is a new slot + # Reset store.proposer_boost_root if this is a new slot if current_slot > previous_slot: - store.proposer_score_boost = LatestMessage(root=Root(), epoch=Epoch(0)) + 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): @@ -416,10 +416,7 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: # Add proposer score boost if the block is timely is_before_attesting_interval = store.time % SECONDS_PER_SLOT < SECONDS_PER_SLOT // INTERVALS_PER_SLOT if get_current_slot(store) == block.slot and is_before_attesting_interval: - store.proposer_score_boost = LatestMessage( - root=hash_tree_root(block), - epoch=compute_epoch_at_slot(block.slot) - ) + store.proposer_boost_root = hash_tree_root(block) # Update justified checkpoint if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py index c4c9fffc1..f234125e0 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py @@ -707,7 +707,56 @@ def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): @with_all_phases @spec_state_test -def test_proposer_score_boost_same_slot_untimely_block(spec, state): +def test_proposer_boost(spec, state): + test_steps = [] + genesis_state = state.copy() + + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + + # Build block that serves as head ONLY on timely arrival, and ONLY in that slot + state = genesis_state.copy() + next_slots(spec, state, 3) + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + + # Process block on timely arrival just before end of boost interval + spec.on_tick(store, store.genesis_time + block.slot * spec.config.SECONDS_PER_SLOT + + spec.config.SECONDS_PER_SLOT // spec.INTERVALS_PER_SLOT - 1) + yield from tick_and_add_block(spec, store, signed_block, test_steps) + assert store.proposer_boost_root == spec.hash_tree_root(block) + assert spec.get_latest_attesting_balance(store, spec.hash_tree_root(block)) > 0 + + # Ensure that boost is removed after slot is over + spec.on_tick(store, store.genesis_time + block.slot * spec.config.SECONDS_PER_SLOT + + spec.config.SECONDS_PER_SLOT) + assert store.proposer_boost_root == spec.Root() + assert spec.get_latest_attesting_balance(store, spec.hash_tree_root(block)) == 0 + + next_slots(spec, state, 3) + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + + # Process block on timely arrival at start of boost interval + spec.on_tick(store, store.genesis_time + block.slot * spec.config.SECONDS_PER_SLOT) + yield from tick_and_add_block(spec, store, signed_block, test_steps) + assert store.proposer_boost_root == spec.hash_tree_root(block) + assert spec.get_latest_attesting_balance(store, spec.hash_tree_root(block)) > 0 + + # Ensure that boost is removed after slot is over + spec.on_tick(store, store.genesis_time + block.slot * spec.config.SECONDS_PER_SLOT + + spec.config.SECONDS_PER_SLOT) + assert store.proposer_boost_root == spec.Root() + assert spec.get_latest_attesting_balance(store, spec.hash_tree_root(block)) == 0 + + yield 'steps', test_steps + + +@with_all_phases +@spec_state_test +def test_proposer_boost_root_same_slot_untimely_block(spec, state): test_steps = [] genesis_state = state.copy() @@ -726,6 +775,6 @@ def test_proposer_score_boost_same_slot_untimely_block(spec, state): spec.on_tick(store, store.genesis_time + block.slot * spec.config.SECONDS_PER_SLOT + spec.config.SECONDS_PER_SLOT // spec.INTERVALS_PER_SLOT) yield from tick_and_add_block(spec, store, signed_block, test_steps) - assert store.proposer_score_boost.root == spec.Root() + assert store.proposer_boost_root == spec.Root() yield 'steps', test_steps