Merge pull request #806 from ethereum/JustinDrake-patch-19
Stricter slashing criteria
This commit is contained in:
commit
750383ed71
|
@ -59,6 +59,7 @@
|
||||||
- [`get_current_epoch`](#get_current_epoch)
|
- [`get_current_epoch`](#get_current_epoch)
|
||||||
- [`get_epoch_start_slot`](#get_epoch_start_slot)
|
- [`get_epoch_start_slot`](#get_epoch_start_slot)
|
||||||
- [`is_active_validator`](#is_active_validator)
|
- [`is_active_validator`](#is_active_validator)
|
||||||
|
- [`is_slashable_validator`](#is_slashable_validator)
|
||||||
- [`get_active_validator_indices`](#get_active_validator_indices)
|
- [`get_active_validator_indices`](#get_active_validator_indices)
|
||||||
- [`get_balance`](#get_balance)
|
- [`get_balance`](#get_balance)
|
||||||
- [`set_balance`](#set_balance)
|
- [`set_balance`](#set_balance)
|
||||||
|
@ -744,6 +745,18 @@ def is_active_validator(validator: Validator, epoch: Epoch) -> bool:
|
||||||
return validator.activation_epoch <= epoch < validator.exit_epoch
|
return validator.activation_epoch <= epoch < validator.exit_epoch
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `is_slashable_validator`
|
||||||
|
```python
|
||||||
|
def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool:
|
||||||
|
"""
|
||||||
|
Check if ``validator`` is slashable.
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
validator.activation_epoch <= epoch < validator.withdrawable_epoch and
|
||||||
|
validator.slashed is False
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
### `get_active_validator_indices`
|
### `get_active_validator_indices`
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
@ -1427,7 +1440,6 @@ def slash_validator(state: BeaconState, index: ValidatorIndex) -> None:
|
||||||
Note that this function mutates ``state``.
|
Note that this function mutates ``state``.
|
||||||
"""
|
"""
|
||||||
validator = state.validator_registry[index]
|
validator = state.validator_registry[index]
|
||||||
assert state.slot < get_epoch_start_slot(validator.withdrawable_epoch) # [TO BE REMOVED IN PHASE 2]
|
|
||||||
exit_validator(state, index)
|
exit_validator(state, index)
|
||||||
state.latest_slashed_balances[get_current_epoch(state) % LATEST_SLASHED_EXIT_LENGTH] += get_effective_balance(state, index)
|
state.latest_slashed_balances[get_current_epoch(state) % LATEST_SLASHED_EXIT_LENGTH] += get_effective_balance(state, index)
|
||||||
|
|
||||||
|
@ -2356,8 +2368,8 @@ def process_proposer_slashing(state: BeaconState,
|
||||||
assert slot_to_epoch(proposer_slashing.header_1.slot) == slot_to_epoch(proposer_slashing.header_2.slot)
|
assert slot_to_epoch(proposer_slashing.header_1.slot) == slot_to_epoch(proposer_slashing.header_2.slot)
|
||||||
# But the headers are different
|
# But the headers are different
|
||||||
assert proposer_slashing.header_1 != proposer_slashing.header_2
|
assert proposer_slashing.header_1 != proposer_slashing.header_2
|
||||||
# Proposer is not yet slashed
|
# Check proposer is slashable
|
||||||
assert proposer.slashed is False
|
assert is_slashable_validator(proposer, get_current_epoch(state))
|
||||||
# Signatures are valid
|
# Signatures are valid
|
||||||
for header in (proposer_slashing.header_1, proposer_slashing.header_2):
|
for header in (proposer_slashing.header_1, proposer_slashing.header_2):
|
||||||
assert bls_verify(
|
assert bls_verify(
|
||||||
|
@ -2396,7 +2408,7 @@ def process_attester_slashing(state: BeaconState,
|
||||||
index for index in attestation1.validator_indices
|
index for index in attestation1.validator_indices
|
||||||
if (
|
if (
|
||||||
index in attestation2.validator_indices and
|
index in attestation2.validator_indices and
|
||||||
state.validator_registry[index].slashed is False
|
is_slashable_validator(state.validator_registry[index], get_current_epoch(state))
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
assert len(slashable_indices) >= 1
|
assert len(slashable_indices) >= 1
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
from copy import deepcopy
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import build.phase0.spec as spec
|
||||||
|
from build.phase0.spec import (
|
||||||
|
get_balance,
|
||||||
|
get_current_epoch,
|
||||||
|
process_proposer_slashing,
|
||||||
|
)
|
||||||
|
from tests.phase0.helpers import (
|
||||||
|
get_valid_proposer_slashing,
|
||||||
|
)
|
||||||
|
|
||||||
|
# mark entire file as 'header'
|
||||||
|
pytestmark = pytest.mark.proposer_slashings
|
||||||
|
|
||||||
|
|
||||||
|
def run_proposer_slashing_processing(state, proposer_slashing, valid=True):
|
||||||
|
"""
|
||||||
|
Run ``process_proposer_slashing`` returning the pre and post state.
|
||||||
|
If ``valid == False``, run expecting ``AssertionError``
|
||||||
|
"""
|
||||||
|
post_state = deepcopy(state)
|
||||||
|
|
||||||
|
if not valid:
|
||||||
|
with pytest.raises(AssertionError):
|
||||||
|
process_proposer_slashing(post_state, proposer_slashing)
|
||||||
|
return state, None
|
||||||
|
|
||||||
|
process_proposer_slashing(post_state, proposer_slashing)
|
||||||
|
|
||||||
|
slashed_validator = post_state.validator_registry[proposer_slashing.proposer_index]
|
||||||
|
assert not slashed_validator.initiated_exit
|
||||||
|
assert slashed_validator.slashed
|
||||||
|
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
|
||||||
|
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
||||||
|
# lost whistleblower reward
|
||||||
|
assert (
|
||||||
|
get_balance(post_state, proposer_slashing.proposer_index) <
|
||||||
|
get_balance(state, proposer_slashing.proposer_index)
|
||||||
|
)
|
||||||
|
|
||||||
|
return state, post_state
|
||||||
|
|
||||||
|
|
||||||
|
def test_success(state):
|
||||||
|
proposer_slashing = get_valid_proposer_slashing(state)
|
||||||
|
|
||||||
|
pre_state, post_state = run_proposer_slashing_processing(state, proposer_slashing)
|
||||||
|
|
||||||
|
return pre_state, proposer_slashing, post_state
|
||||||
|
|
||||||
|
|
||||||
|
def test_epochs_are_different(state):
|
||||||
|
proposer_slashing = get_valid_proposer_slashing(state)
|
||||||
|
|
||||||
|
# set slots to be in different epochs
|
||||||
|
proposer_slashing.header_2.slot += spec.SLOTS_PER_EPOCH
|
||||||
|
|
||||||
|
pre_state, post_state = run_proposer_slashing_processing(state, proposer_slashing, False)
|
||||||
|
|
||||||
|
return pre_state, proposer_slashing, post_state
|
||||||
|
|
||||||
|
|
||||||
|
def test_headers_are_same(state):
|
||||||
|
proposer_slashing = get_valid_proposer_slashing(state)
|
||||||
|
|
||||||
|
# set headers to be the same
|
||||||
|
proposer_slashing.header_2 = proposer_slashing.header_1
|
||||||
|
|
||||||
|
pre_state, post_state = run_proposer_slashing_processing(state, proposer_slashing, False)
|
||||||
|
|
||||||
|
return pre_state, proposer_slashing, post_state
|
||||||
|
|
||||||
|
|
||||||
|
def test_proposer_is_slashed(state):
|
||||||
|
proposer_slashing = get_valid_proposer_slashing(state)
|
||||||
|
|
||||||
|
# set proposer to slashed
|
||||||
|
state.validator_registry[proposer_slashing.proposer_index].slashed = True
|
||||||
|
|
||||||
|
pre_state, post_state = run_proposer_slashing_processing(state, proposer_slashing, False)
|
||||||
|
|
||||||
|
return pre_state, proposer_slashing, post_state
|
||||||
|
|
||||||
|
|
||||||
|
def test_proposer_is_withdrawn(state):
|
||||||
|
proposer_slashing = get_valid_proposer_slashing(state)
|
||||||
|
|
||||||
|
# set proposer withdrawable_epoch in past
|
||||||
|
current_epoch = get_current_epoch(state)
|
||||||
|
proposer_index = proposer_slashing.proposer_index
|
||||||
|
state.validator_registry[proposer_index].withdrawable_epoch = current_epoch - 1
|
||||||
|
|
||||||
|
pre_state, post_state = run_proposer_slashing_processing(state, proposer_slashing, False)
|
||||||
|
|
||||||
|
return pre_state, proposer_slashing, post_state
|
|
@ -7,14 +7,18 @@ from build.phase0.utils.minimal_ssz import signed_root
|
||||||
from build.phase0.spec import (
|
from build.phase0.spec import (
|
||||||
# constants
|
# constants
|
||||||
EMPTY_SIGNATURE,
|
EMPTY_SIGNATURE,
|
||||||
|
ZERO_HASH,
|
||||||
# SSZ
|
# SSZ
|
||||||
AttestationData,
|
AttestationData,
|
||||||
|
BeaconBlockHeader,
|
||||||
Deposit,
|
Deposit,
|
||||||
DepositInput,
|
DepositInput,
|
||||||
DepositData,
|
DepositData,
|
||||||
Eth1Data,
|
Eth1Data,
|
||||||
|
ProposerSlashing,
|
||||||
VoluntaryExit,
|
VoluntaryExit,
|
||||||
# functions
|
# functions
|
||||||
|
get_active_validator_indices,
|
||||||
get_block_root,
|
get_block_root,
|
||||||
get_current_epoch,
|
get_current_epoch,
|
||||||
get_domain,
|
get_domain,
|
||||||
|
@ -199,3 +203,43 @@ def build_deposit(state,
|
||||||
)
|
)
|
||||||
|
|
||||||
return deposit, root, deposit_data_leaves
|
return deposit, root, deposit_data_leaves
|
||||||
|
|
||||||
|
|
||||||
|
def get_valid_proposer_slashing(state):
|
||||||
|
current_epoch = get_current_epoch(state)
|
||||||
|
validator_index = get_active_validator_indices(state.validator_registry, current_epoch)[-1]
|
||||||
|
privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
|
||||||
|
slot = state.slot
|
||||||
|
|
||||||
|
header_1 = BeaconBlockHeader(
|
||||||
|
slot=slot,
|
||||||
|
previous_block_root=ZERO_HASH,
|
||||||
|
state_root=ZERO_HASH,
|
||||||
|
block_body_root=ZERO_HASH,
|
||||||
|
signature=EMPTY_SIGNATURE,
|
||||||
|
)
|
||||||
|
header_2 = deepcopy(header_1)
|
||||||
|
header_2.previous_block_root = b'\x02' * 32
|
||||||
|
header_2.slot = slot + 1
|
||||||
|
|
||||||
|
domain = get_domain(
|
||||||
|
fork=state.fork,
|
||||||
|
epoch=get_current_epoch(state),
|
||||||
|
domain_type=spec.DOMAIN_BEACON_BLOCK,
|
||||||
|
)
|
||||||
|
header_1.signature = bls.sign(
|
||||||
|
message_hash=signed_root(header_1),
|
||||||
|
privkey=privkey,
|
||||||
|
domain=domain,
|
||||||
|
)
|
||||||
|
header_2.signature = bls.sign(
|
||||||
|
message_hash=signed_root(header_2),
|
||||||
|
privkey=privkey,
|
||||||
|
domain=domain,
|
||||||
|
)
|
||||||
|
|
||||||
|
return ProposerSlashing(
|
||||||
|
proposer_index=validator_index,
|
||||||
|
header_1=header_1,
|
||||||
|
header_2=header_2,
|
||||||
|
)
|
||||||
|
|
|
@ -46,6 +46,7 @@ from tests.phase0.helpers import (
|
||||||
build_deposit_data,
|
build_deposit_data,
|
||||||
build_empty_block_for_next_slot,
|
build_empty_block_for_next_slot,
|
||||||
force_registry_change_at_next_epoch,
|
force_registry_change_at_next_epoch,
|
||||||
|
get_valid_proposer_slashing,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -117,42 +118,8 @@ def test_empty_epoch_transition_not_finalizing(state):
|
||||||
|
|
||||||
def test_proposer_slashing(state, pubkeys, privkeys):
|
def test_proposer_slashing(state, pubkeys, privkeys):
|
||||||
test_state = deepcopy(state)
|
test_state = deepcopy(state)
|
||||||
current_epoch = get_current_epoch(test_state)
|
proposer_slashing = get_valid_proposer_slashing(state)
|
||||||
validator_index = get_active_validator_indices(test_state.validator_registry, current_epoch)[-1]
|
validator_index = proposer_slashing.proposer_index
|
||||||
privkey = privkeys[validator_index]
|
|
||||||
slot = spec.GENESIS_SLOT
|
|
||||||
header_1 = BeaconBlockHeader(
|
|
||||||
slot=slot,
|
|
||||||
previous_block_root=ZERO_HASH,
|
|
||||||
state_root=ZERO_HASH,
|
|
||||||
block_body_root=ZERO_HASH,
|
|
||||||
signature=EMPTY_SIGNATURE,
|
|
||||||
)
|
|
||||||
header_2 = deepcopy(header_1)
|
|
||||||
header_2.previous_block_root = b'\x02' * 32
|
|
||||||
header_2.slot = slot + 1
|
|
||||||
|
|
||||||
domain = get_domain(
|
|
||||||
fork=test_state.fork,
|
|
||||||
epoch=get_current_epoch(test_state),
|
|
||||||
domain_type=spec.DOMAIN_BEACON_BLOCK,
|
|
||||||
)
|
|
||||||
header_1.signature = bls.sign(
|
|
||||||
message_hash=signed_root(header_1),
|
|
||||||
privkey=privkey,
|
|
||||||
domain=domain,
|
|
||||||
)
|
|
||||||
header_2.signature = bls.sign(
|
|
||||||
message_hash=signed_root(header_2),
|
|
||||||
privkey=privkey,
|
|
||||||
domain=domain,
|
|
||||||
)
|
|
||||||
|
|
||||||
proposer_slashing = ProposerSlashing(
|
|
||||||
proposer_index=validator_index,
|
|
||||||
header_1=header_1,
|
|
||||||
header_2=header_2,
|
|
||||||
)
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Add to state via block transition
|
# Add to state via block transition
|
||||||
|
|
Loading…
Reference in New Issue