Add proposer score boosting & related tests
This commit is contained in:
parent
7736c8bce6
commit
2d161b4244
|
@ -175,6 +175,14 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
|
|||
# Add new state for this block to the store
|
||||
store.block_states[hash_tree_root(block)] = state
|
||||
|
||||
# Add proposer score boost if the block is timely
|
||||
if (get_current_slot(store) == block.slot and
|
||||
store.time % SECONDS_PER_SLOT < SECONDS_PER_SLOT // ATTESTATION_OFFSET_QUOTIENT):
|
||||
store.proposer_score_boost = LatestMessage(
|
||||
root=hash_tree_root(block),
|
||||
epoch=compute_epoch_at_slot(block.slot)
|
||||
)
|
||||
|
||||
# Update justified checkpoint
|
||||
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
|
||||
if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch:
|
||||
|
|
|
@ -61,6 +61,7 @@ Any of the above handlers that trigger an unhandled exception (e.g. a failed ass
|
|||
| Name | Value | Unit | Duration |
|
||||
| - | - | :-: | :-: |
|
||||
| `SAFE_SLOTS_TO_UPDATE_JUSTIFIED` | `2**3` (= 8) | slots | 96 seconds |
|
||||
| `ATTESTATION_OFFSET_QUOTIENT` | `3` | - | - |
|
||||
|
||||
### Helpers
|
||||
|
||||
|
@ -83,6 +84,7 @@ class Store(object):
|
|||
justified_checkpoint: Checkpoint
|
||||
finalized_checkpoint: Checkpoint
|
||||
best_justified_checkpoint: Checkpoint
|
||||
proposer_score_boost: LatestMessage
|
||||
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)
|
||||
|
@ -103,12 +105,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))
|
||||
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,
|
||||
blocks={anchor_root: copy(anchor_block)},
|
||||
block_states={anchor_root: copy(anchor_state)},
|
||||
checkpoint_states={justified_checkpoint: copy(anchor_state)},
|
||||
|
@ -156,11 +160,23 @@ def get_ancestor(store: Store, root: Root, slot: Slot) -> Root:
|
|||
def get_latest_attesting_balance(store: Store, root: Root) -> Gwei:
|
||||
state = store.checkpoint_states[store.justified_checkpoint]
|
||||
active_indices = get_active_validator_indices(state, get_current_epoch(state))
|
||||
return Gwei(sum(
|
||||
attestation_score = Gwei(sum(
|
||||
state.validators[i].effective_balance for i in active_indices
|
||||
if (i in store.latest_messages
|
||||
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():
|
||||
block_slot = store.blocks[root].slot
|
||||
if get_ancestor(store, root, block_slot) == store.proposer_score_boost.root:
|
||||
num_validators = len(get_active_validator_indices(state, get_current_epoch(state)))
|
||||
avg_balance = get_total_active_balance(state) // num_validators
|
||||
block_epoch = compute_epoch_at_slot(block_slot)
|
||||
committee_size = get_committee_count_per_slot(state, block_epoch) * TARGET_COMMITTEE_SIZE
|
||||
committee_weight = committee_size * avg_balance
|
||||
proposer_score = committee_weight // 4
|
||||
return attestation_score + proposer_score
|
||||
|
||||
```
|
||||
|
||||
#### `filter_block_tree`
|
||||
|
@ -339,6 +355,10 @@ def on_tick(store: Store, time: uint64) -> None:
|
|||
store.time = time
|
||||
|
||||
current_slot = get_current_slot(store)
|
||||
# Reset store.proposer_score_boost if this is a new slot
|
||||
if store.proposer_score_boost.root != Root():
|
||||
if current_slot != store.blocks[store.proposer_score_boost.root].slot:
|
||||
store.proposer_score_boost = LatestMessage(root=Root(), epoch=Epoch(0))
|
||||
# Not a new epoch, return
|
||||
if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0):
|
||||
return
|
||||
|
@ -377,6 +397,14 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
|
|||
# Add new state for this block to the store
|
||||
store.block_states[hash_tree_root(block)] = state
|
||||
|
||||
# Add proposer score boost if the block is timely
|
||||
if (get_current_slot(store) == block.slot and
|
||||
store.time % SECONDS_PER_SLOT < SECONDS_PER_SLOT // ATTESTATION_OFFSET_QUOTIENT):
|
||||
store.proposer_score_boost = LatestMessage(
|
||||
root=hash_tree_root(block),
|
||||
epoch=compute_epoch_at_slot(block.slot)
|
||||
)
|
||||
|
||||
# Update justified checkpoint
|
||||
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
|
||||
if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import random
|
||||
from eth_utils import encode_hex
|
||||
|
||||
from eth2spec.test.context import (
|
||||
|
@ -19,6 +20,7 @@ from eth2spec.test.helpers.fork_choice import (
|
|||
add_block,
|
||||
)
|
||||
from eth2spec.test.helpers.state import (
|
||||
next_slots,
|
||||
next_epoch,
|
||||
state_transition_and_sign_block,
|
||||
)
|
||||
|
@ -103,17 +105,21 @@ def test_split_tie_breaker_no_attestations(spec, state):
|
|||
}
|
||||
})
|
||||
|
||||
# block at slot 1
|
||||
# Create block at slot 1
|
||||
block_1_state = genesis_state.copy()
|
||||
block_1 = build_empty_block_for_next_slot(spec, block_1_state)
|
||||
signed_block_1 = state_transition_and_sign_block(spec, block_1_state, block_1)
|
||||
yield from tick_and_add_block(spec, store, signed_block_1, test_steps)
|
||||
|
||||
# additional block at slot 1
|
||||
# Create additional block at slot 1
|
||||
block_2_state = genesis_state.copy()
|
||||
block_2 = build_empty_block_for_next_slot(spec, block_2_state)
|
||||
block_2.body.graffiti = b'\x42' * 32
|
||||
signed_block_2 = state_transition_and_sign_block(spec, block_2_state, block_2)
|
||||
|
||||
# Tick time past slot 1 so proposer score boost does not apply
|
||||
spec.on_tick(store, store.genesis_time + (block_2.slot + 1) * spec.config.SECONDS_PER_SLOT)
|
||||
|
||||
yield from tick_and_add_block(spec, store, signed_block_1, test_steps)
|
||||
yield from tick_and_add_block(spec, store, signed_block_2, test_steps)
|
||||
|
||||
highest_root = max(spec.hash_tree_root(block_1), spec.hash_tree_root(block_2))
|
||||
|
@ -261,3 +267,67 @@ def test_filtered_block_tree(spec, state):
|
|||
})
|
||||
|
||||
yield 'steps', test_steps
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_proposer_score_boost_basic(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
|
||||
anchor_root = get_anchor_root(spec, state)
|
||||
assert spec.get_head(store) == anchor_root
|
||||
test_steps.append({
|
||||
'checks': {
|
||||
'head': get_formatted_head_output(spec, store),
|
||||
}
|
||||
})
|
||||
|
||||
# Build block that serves as head ONLY on timely arrival, and ONLY in that slot
|
||||
state_1 = genesis_state.copy()
|
||||
next_slots(spec, state_1, 3)
|
||||
block_1 = build_empty_block_for_next_slot(spec, state_1)
|
||||
signed_block_1 = state_transition_and_sign_block(spec, state_1, block_1)
|
||||
|
||||
# Build block that serves as current head, and remains the head after block_1.slot
|
||||
state_2 = genesis_state.copy()
|
||||
next_slots(spec, state_2, 2)
|
||||
block_2 = build_empty_block_for_next_slot(spec, state_2)
|
||||
block_2.body.graffiti = spec.Bytes32(hex(random.getrandbits(8 * 32))[2:].zfill(64))
|
||||
signed_block_2 = state_transition_and_sign_block(spec, state_2.copy(), block_2)
|
||||
while spec.hash_tree_root(block_1) > spec.hash_tree_root(block_2):
|
||||
block_2.body.graffiti = spec.Bytes32(hex(random.getrandbits(8 * 32))[2:].zfill(64))
|
||||
signed_block_2 = state_transition_and_sign_block(spec, state_2.copy(), block_2)
|
||||
assert spec.hash_tree_root(block_1) < spec.hash_tree_root(block_2)
|
||||
|
||||
# Tick to block_1 slot time
|
||||
spec.on_tick(store, store.genesis_time + block_1.slot * spec.config.SECONDS_PER_SLOT)
|
||||
|
||||
# Process block_2
|
||||
yield from tick_and_add_block(spec, store, signed_block_2, test_steps)
|
||||
assert store.proposer_score_boost.root == spec.Root()
|
||||
assert spec.get_head(store) == spec.hash_tree_root(block_2)
|
||||
|
||||
# Process block_1 on timely arrival
|
||||
# The head should temporarily change to block_1
|
||||
yield from tick_and_add_block(spec, store, signed_block_1, test_steps)
|
||||
assert store.proposer_score_boost == spec.LatestMessage(root=spec.hash_tree_root(block_1),
|
||||
epoch=spec.compute_epoch_at_slot(block_1.slot))
|
||||
assert spec.get_head(store) == spec.hash_tree_root(block_1)
|
||||
|
||||
# After block_1.slot, the head should revert to block_2
|
||||
spec.on_tick(store, store.genesis_time + (block_1.slot + 1) * spec.config.SECONDS_PER_SLOT)
|
||||
assert store.proposer_score_boost.root == spec.Root()
|
||||
assert spec.get_head(store) == spec.hash_tree_root(block_2)
|
||||
|
||||
test_steps.append({
|
||||
'checks': {
|
||||
'head': get_formatted_head_output(spec, store),
|
||||
}
|
||||
})
|
||||
|
||||
yield 'steps', test_steps
|
||||
|
|
|
@ -703,3 +703,29 @@ def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state):
|
|||
assert store.justified_checkpoint == another_state.current_justified_checkpoint
|
||||
|
||||
yield 'steps', test_steps
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_proposer_score_boost_same_slot_untimely_block(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 untimely arrival in the same slot
|
||||
spec.on_tick(store, store.genesis_time + block.slot * spec.config.SECONDS_PER_SLOT +
|
||||
spec.config.SECONDS_PER_SLOT // spec.ATTESTATION_OFFSET_QUOTIENT)
|
||||
yield from tick_and_add_block(spec, store, signed_block, test_steps)
|
||||
assert store.proposer_score_boost.root == spec.Root()
|
||||
|
||||
yield 'steps', test_steps
|
||||
|
|
Loading…
Reference in New Issue