Add on_attester_slashing() and related test

This commit is contained in:
Aditya Asgaonkar 2022-03-01 11:42:49 -08:00
parent 72d4597131
commit 4fbe162655
2 changed files with 108 additions and 2 deletions

View File

@ -101,6 +101,7 @@ class Store(object):
finalized_checkpoint: Checkpoint
best_justified_checkpoint: Checkpoint
proposer_boost_root: Root
has_equivocated: Set[ValidatorIndex]
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)
@ -129,6 +130,7 @@ def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -
finalized_checkpoint=finalized_checkpoint,
best_justified_checkpoint=justified_checkpoint,
proposer_boost_root=proposer_boost_root,
has_equivocated=set(),
blocks={anchor_root: copy(anchor_block)},
block_states={anchor_root: copy(anchor_state)},
checkpoint_states={justified_checkpoint: copy(anchor_state)},
@ -179,6 +181,7 @@ def get_latest_attesting_balance(store: Store, root: Root) -> Gwei:
attestation_score = Gwei(sum(
state.validators[i].effective_balance for i in active_indices
if (i in store.latest_messages
and not i in store.has_equivocated
and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root)
))
if store.proposer_boost_root == Root():
@ -358,8 +361,9 @@ def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIn
target = attestation.data.target
beacon_block_root = attestation.data.beacon_block_root
for i in attesting_indices:
if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch:
store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root)
if i not in store.has_equivocated:
if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch:
store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root)
```
@ -459,3 +463,22 @@ def on_attestation(store: Store, attestation: Attestation, is_from_block: bool=F
# Update latest messages for attesting indices
update_latest_messages(store, indexed_attestation.attesting_indices, attestation)
```
#### `on_attester_slashing`
```python
def on_attester_slashing(store: Store, attester_slashing: AttesterSlashing) -> None:
"""
Run ``on_attester_slashing`` upon receiving a new ``AttesterSlashing`` from either within a block or directly on the wire.
"""
attestation_1 = attester_slashing.attestation_1
attestation_2 = attester_slashing.attestation_2
assert is_slashable_attestation_data(attestation_1.data, attestation_2.data)
state = store.block_states[store.justified_checkpoint.root]
assert is_valid_indexed_attestation(state, attestation_1)
assert is_valid_indexed_attestation(state, attestation_2)
indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices)
for index in sorted(indices):
store.has_equivocated.add(index)
```

View File

@ -24,6 +24,8 @@ from eth2spec.test.helpers.state import (
next_epoch,
state_transition_and_sign_block,
)
from tests.core.pyspec.eth2spec.test.helpers.block import apply_empty_block
from tests.core.pyspec.eth2spec.test.helpers.fork_choice import run_on_attestation
rng = random.Random(1001)
@ -338,3 +340,84 @@ def test_proposer_boost_correct_head(spec, state):
})
yield 'steps', test_steps
@with_all_phases
@spec_state_test
def test_discard_equivocations(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 before discarding equivocations
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 equivocating attestations to feed to store
state_eqv = state_1.copy()
block_eqv = apply_empty_block(spec, state_eqv, state_eqv.slot+1)
attestation_eqv = get_valid_attestation(spec, state_eqv, slot=block_eqv.slot, signed=True)
next_slots(spec, state_1, 1)
attestation = get_valid_attestation(spec, state_1, slot=block_eqv.slot, signed=True)
assert spec.is_slashable_attestation_data(attestation.data, attestation_eqv.data)
indexed_attestation = spec.get_indexed_attestation(state_1, attestation)
indexed_attestation_eqv = spec.get_indexed_attestation(state_eqv, attestation_eqv)
attester_slashing = spec.AttesterSlashing(attestation_1=indexed_attestation, attestation_2=indexed_attestation_eqv)
# Build block that serves as head after discarding equivocations
state_2 = genesis_state.copy()
next_slots(spec, state_2, 2)
block_2 = build_empty_block_for_next_slot(spec, state_2)
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(rng.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_eqv.slot + 2) slot time
time = store.genesis_time + (block_eqv.slot + 2) * spec.config.SECONDS_PER_SLOT
on_tick_and_append_step(spec, store, time, test_steps)
# Process block_2
yield from add_block(spec, store, signed_block_2, test_steps)
assert store.proposer_boost_root == spec.Root()
assert spec.get_head(store) == spec.hash_tree_root(block_2)
# Process block_1
# The head should remain block_2
yield from add_block(spec, store, signed_block_1, test_steps)
assert store.proposer_boost_root == spec.Root()
assert spec.get_head(store) == spec.hash_tree_root(block_2)
# Process attestation
# The head should change to block_1
run_on_attestation(spec, store, attestation)
assert spec.get_head(store) == spec.hash_tree_root(block_1)
# Process attester_slashing
# The head should revert to block_2
spec.on_attester_slashing(store, attester_slashing)
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