From 06d005999a3b8ebcde35ed89593e6efb573f3afc Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 2 Apr 2019 13:50:06 +1100 Subject: [PATCH 1/4] fix validator_indicies issue in process_attester_slashing --- specs/core/0_beacon-chain.md | 6 ++++-- tests/phase0/helpers.py | 13 +++++++++++++ tests/phase0/test_sanity.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index d08828692..ced7a7210 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -2291,10 +2291,12 @@ def process_attester_slashing(state: BeaconState, assert verify_indexed_attestation(state, attestation1) assert verify_indexed_attestation(state, attestation2) + validator_indices_1 = attestation1.custody_bit_0_indices + attestation1.custody_bit_1_indices + validator_indices_2 = attestation2.custody_bit_0_indices + attestation2.custody_bit_1_indices slashable_indices = [ - index for index in attestation1.validator_indices + index for index in validator_indices_1 if ( - index in attestation2.validator_indices and + index in validator_indices_2 and is_slashable_validator(state.validator_registry[index], get_current_epoch(state)) ) ] diff --git a/tests/phase0/helpers.py b/tests/phase0/helpers.py index e5e335d80..33f394def 100644 --- a/tests/phase0/helpers.py +++ b/tests/phase0/helpers.py @@ -12,6 +12,7 @@ from build.phase0.spec import ( Attestation, AttestationData, AttestationDataAndCustodyBit, + AttesterSlashing, BeaconBlockHeader, Deposit, DepositData, @@ -19,6 +20,7 @@ from build.phase0.spec import ( ProposerSlashing, VoluntaryExit, # functions + convert_to_indexed, get_active_validator_indices, get_attestation_participants, get_block_root, @@ -244,6 +246,17 @@ def get_valid_proposer_slashing(state): ) +def get_valid_attester_slashing(state): + attestation_1 = get_valid_attestation(state) + attestation_2 = deepcopy(attestation_1) + attestation_2.data.target_root = b'\x01'*32 + + return AttesterSlashing( + attestation_1=convert_to_indexed(state, attestation_1), + attestation_2=convert_to_indexed(state, attestation_2), + ) + + def get_valid_attestation(state, slot=None): if slot is None: slot = state.slot diff --git a/tests/phase0/test_sanity.py b/tests/phase0/test_sanity.py index 3b4497ca5..8e6bd2e94 100644 --- a/tests/phase0/test_sanity.py +++ b/tests/phase0/test_sanity.py @@ -40,6 +40,7 @@ from tests.phase0.helpers import ( build_empty_block_for_next_slot, force_registry_change_at_next_epoch, get_valid_attestation, + get_valid_attester_slashing, get_valid_proposer_slashing, privkeys, pubkeys, @@ -140,6 +141,33 @@ def test_proposer_slashing(state): return state, [block], test_state +def test_attester_slashing(state): + test_state = deepcopy(state) + attester_slashing = get_valid_attester_slashing(state) + validator_index = attester_slashing.attestation_1.custody_bit_0_indices[0] + + # + # Add to state via block transition + # + block = build_empty_block_for_next_slot(test_state) + block.body.attester_slashings.append(attester_slashing) + state_transition(test_state, block) + + assert not state.validator_registry[validator_index].initiated_exit + assert not state.validator_registry[validator_index].slashed + + slashed_validator = test_state.validator_registry[validator_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(test_state, validator_index) < get_balance(state, validator_index) + + return state, [block], test_state + + + def test_deposit_in_block(state): pre_state = deepcopy(state) test_deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry) From e037412f94d7c24477cd7723470e1735b866d920 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 2 Apr 2019 14:04:04 +1100 Subject: [PATCH 2/4] add process attester slashing tests --- .../test_process_attester_slashing.py | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 tests/phase0/block_processing/test_process_attester_slashing.py diff --git a/tests/phase0/block_processing/test_process_attester_slashing.py b/tests/phase0/block_processing/test_process_attester_slashing.py new file mode 100644 index 000000000..ed6d9adae --- /dev/null +++ b/tests/phase0/block_processing/test_process_attester_slashing.py @@ -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_attester_slashing, +) +from tests.phase0.helpers import ( + get_valid_attester_slashing, +) + +# mark entire file as 'attester_slashing' +pytestmark = pytest.mark.attester_slashings + + +def run_attester_slashing_processing(state, attester_slashing, valid=True): + """ + Run ``process_attester_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_attester_slashing(post_state, attester_slashing) + return state, None + + process_attester_slashing(post_state, attester_slashing) + + validator_index = attester_slashing.attestation_1.custody_bit_0_indices[0] + slashed_validator = post_state.validator_registry[validator_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, validator_index) < + get_balance(state, validator_index) + ) + + return state, post_state + + +def test_success_double(state): + attester_slashing = get_valid_attester_slashing(state) + + pre_state, post_state = run_attester_slashing_processing(state, attester_slashing) + + return pre_state, attester_slashing, post_state + + +def test_success_surround(state): + attester_slashing = get_valid_attester_slashing(state) + + # set attestion1 to surround attestation 2 + attester_slashing.attestation_1.data.source_epoch = attester_slashing.attestation_2.data.source_epoch - 1 + attester_slashing.attestation_1.data.slot = attester_slashing.attestation_2.data.slot + spec.SLOTS_PER_EPOCH + + pre_state, post_state = run_attester_slashing_processing(state, attester_slashing) + + return pre_state, attester_slashing, post_state + + +def test_same_data(state): + attester_slashing = get_valid_attester_slashing(state) + + attester_slashing.attestation_1.data = attester_slashing.attestation_2.data + + pre_state, post_state = run_attester_slashing_processing(state, attester_slashing, False) + + return pre_state, attester_slashing, post_state + + +def test_no_double_or_surround(state): + attester_slashing = get_valid_attester_slashing(state) + + attester_slashing.attestation_1.data.slot += spec.SLOTS_PER_EPOCH + + pre_state, post_state = run_attester_slashing_processing(state, attester_slashing, False) + + return pre_state, attester_slashing, post_state + +def test_participants_already_slashed(state): + attester_slashing = get_valid_attester_slashing(state) + + # set all indices to slashed + attestation_1 = attester_slashing.attestation_1 + validator_indices = attestation_1.custody_bit_0_indices + attestation_1.custody_bit_1_indices + for index in validator_indices: + state.validator_registry[index].slashed = True + + pre_state, post_state = run_attester_slashing_processing(state, attester_slashing, False) + + return pre_state, attester_slashing, post_state From 577fc740d09dcbb66848e84dbb141b75ce6952df Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 2 Apr 2019 14:08:22 +1100 Subject: [PATCH 3/4] lint --- tests/phase0/block_processing/test_process_attester_slashing.py | 1 + tests/phase0/test_sanity.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phase0/block_processing/test_process_attester_slashing.py b/tests/phase0/block_processing/test_process_attester_slashing.py index ed6d9adae..fcbc8f78b 100644 --- a/tests/phase0/block_processing/test_process_attester_slashing.py +++ b/tests/phase0/block_processing/test_process_attester_slashing.py @@ -83,6 +83,7 @@ def test_no_double_or_surround(state): return pre_state, attester_slashing, post_state + def test_participants_already_slashed(state): attester_slashing = get_valid_attester_slashing(state) diff --git a/tests/phase0/test_sanity.py b/tests/phase0/test_sanity.py index 8e6bd2e94..0010cb22f 100644 --- a/tests/phase0/test_sanity.py +++ b/tests/phase0/test_sanity.py @@ -167,7 +167,6 @@ def test_attester_slashing(state): return state, [block], test_state - def test_deposit_in_block(state): pre_state = deepcopy(state) test_deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry) From 014138baab78554e77e18620a9ba450787d800f1 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 3 Apr 2019 11:04:12 +1100 Subject: [PATCH 4/4] pr feedback --- specs/core/0_beacon-chain.md | 11 +++++--- .../test_process_attester_slashing.py | 27 +++++++++++++++---- tests/phase0/test_sanity.py | 8 ++++++ 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index ced7a7210..8b3f51c0a 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1187,6 +1187,9 @@ def verify_indexed_attestation(state: BeaconState, indexed_attestation: IndexedA custody_bit_0_indices = indexed_attestation.custody_bit_0_indices custody_bit_1_indices = indexed_attestation.custody_bit_1_indices + # ensure no duplicate indices across custody bits + assert len(set(custody_bit_0_indices).intersection(set(custody_bit_1_indices))) == 0 + if len(custody_bit_1_indices) > 0: # [TO BE REMOVED IN PHASE 1] return False @@ -2291,12 +2294,12 @@ def process_attester_slashing(state: BeaconState, assert verify_indexed_attestation(state, attestation1) assert verify_indexed_attestation(state, attestation2) - validator_indices_1 = attestation1.custody_bit_0_indices + attestation1.custody_bit_1_indices - validator_indices_2 = attestation2.custody_bit_0_indices + attestation2.custody_bit_1_indices + attesting_indices_1 = attestation1.custody_bit_0_indices + attestation1.custody_bit_1_indices + attesting_indices_2 = attestation2.custody_bit_0_indices + attestation2.custody_bit_1_indices slashable_indices = [ - index for index in validator_indices_1 + index for index in attesting_indices_1 if ( - index in validator_indices_2 and + index in attesting_indices_2 and is_slashable_validator(state.validator_registry[index], get_current_epoch(state)) ) ] diff --git a/tests/phase0/block_processing/test_process_attester_slashing.py b/tests/phase0/block_processing/test_process_attester_slashing.py index fcbc8f78b..06f214c4b 100644 --- a/tests/phase0/block_processing/test_process_attester_slashing.py +++ b/tests/phase0/block_processing/test_process_attester_slashing.py @@ -4,7 +4,7 @@ import pytest import build.phase0.spec as spec from build.phase0.spec import ( get_balance, - get_current_epoch, + get_beacon_proposer_index, process_attester_slashing, ) from tests.phase0.helpers import ( @@ -29,16 +29,22 @@ def run_attester_slashing_processing(state, attester_slashing, valid=True): process_attester_slashing(post_state, attester_slashing) - validator_index = attester_slashing.attestation_1.custody_bit_0_indices[0] - slashed_validator = post_state.validator_registry[validator_index] + slashed_index = attester_slashing.attestation_1.custody_bit_0_indices[0] + slashed_validator = post_state.validator_registry[slashed_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, validator_index) < - get_balance(state, validator_index) + get_balance(post_state, slashed_index) < + get_balance(state, slashed_index) + ) + proposer_index = get_beacon_proposer_index(state, state.slot) + # gained whistleblower reward + assert ( + get_balance(post_state, proposer_index) > + get_balance(state, proposer_index) ) return state, post_state @@ -96,3 +102,14 @@ def test_participants_already_slashed(state): pre_state, post_state = run_attester_slashing_processing(state, attester_slashing, False) return pre_state, attester_slashing, post_state + + +def test_custody_bit_0_and_1(state): + attester_slashing = get_valid_attester_slashing(state) + + attester_slashing.attestation_1.custody_bit_1_indices = ( + attester_slashing.attestation_1.custody_bit_0_indices + ) + pre_state, post_state = run_attester_slashing_processing(state, attester_slashing, False) + + return pre_state, attester_slashing, post_state diff --git a/tests/phase0/test_sanity.py b/tests/phase0/test_sanity.py index 0010cb22f..90825242f 100644 --- a/tests/phase0/test_sanity.py +++ b/tests/phase0/test_sanity.py @@ -17,6 +17,7 @@ from build.phase0.spec import ( # functions get_active_validator_indices, get_balance, + get_beacon_proposer_index, get_block_root, get_current_epoch, get_domain, @@ -164,6 +165,13 @@ def test_attester_slashing(state): # lost whistleblower reward assert get_balance(test_state, validator_index) < get_balance(state, validator_index) + proposer_index = get_beacon_proposer_index(test_state, test_state.slot) + # gained whistleblower reward + assert ( + get_balance(test_state, proposer_index) > + get_balance(state, proposer_index) + ) + return state, [block], test_state