From 66b152f79ecad957b38ecccc16ce2f131afd10cc Mon Sep 17 00:00:00 2001 From: vbuterin Date: Fri, 3 May 2019 05:07:11 -0500 Subject: [PATCH 1/4] Allow multiple bit challenges, and recover withdrawability Resolves #864 items 4, 7, 14 --- specs/core/1_custody-game.md | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/specs/core/1_custody-game.md b/specs/core/1_custody-game.md index d56526611..0de84ff06 100644 --- a/specs/core/1_custody-game.md +++ b/specs/core/1_custody-game.md @@ -146,7 +146,7 @@ This document details the beacon chain additions and changes in Phase 1 of Ether 'challenge_index': 'uint64', 'challenger_index': ValidatorIndex, 'responder_index': ValidatorIndex, - 'deadline': Epoch, + 'inclusion_epoch': Epoch, 'crosslink_data_root': Hash, 'depth': 'uint64', 'chunk_index': 'uint64', @@ -160,7 +160,7 @@ This document details the beacon chain additions and changes in Phase 1 of Ether 'challenge_index': 'uint64', 'challenger_index': ValidatorIndex, 'responder_index': ValidatorIndex, - 'deadline': Epoch, + 'inclusion_epoch': Epoch, 'crosslink_data_root': Hash, 'chunk_count': 'uint64', 'chunk_bits_merkle_root': Hash, @@ -485,7 +485,7 @@ def process_chunk_challenge(state: BeaconState, challenge_index=state.custody_challenge_index, challenger_index=get_beacon_proposer_index(state), responder_index=challenge.responder_index - deadline=get_current_epoch(state) + CUSTODY_RESPONSE_DEADLINE, + inclusion_epoch=get_current_epoch(state), crosslink_data_root=challenge.attestation.data.crosslink_data_root, depth=depth, chunk_index=challenge.chunk_index, @@ -528,10 +528,9 @@ def process_bit_challenge(state: BeaconState, attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bitfield) assert challenge.responder_index in attesters - # A validator can be the challenger or responder for at most one challenge at a time + # A validator can be the challenger for at most one challenge at a time for record in state.custody_bit_challenge_records: assert record.challenger_index != challenge.challenger_index - assert record.responder_index != challenge.responder_index # Verify the responder is a valid custody key epoch_to_sign = get_randao_epoch_for_custody_period( @@ -563,7 +562,7 @@ def process_bit_challenge(state: BeaconState, challenge_index=state.custody_challenge_index, challenger_index=challenge.challenger_index, responder_index=challenge.responder_index, - deadline=get_current_epoch(state) + CUSTODY_RESPONSE_DEADLINE, + inclusion_epoch=get_current_epoch(state), crosslink_data_root=challenge.attestation.data.crosslink_data_root, chunk_count=chunk_count, chunk_bits_merkle_root=merkle_root(pad_to_power_of_2((challenge.chunk_bits))), @@ -604,6 +603,8 @@ def process_chunk_challenge_response(state: BeaconState, assert response.chunk_index == challenge.chunk_index # Verify bit challenge data is null assert response.chunk_bits_branch == [] and response.chunk_bits_leaf == ZERO_HASH + # Verify minimum delay + assert get_current_epoch(state) >= challenge.inclusion_epoch + ACTIVATION_EXIT_DELAY # Verify the chunk matches the crosslink data root assert verify_merkle_branch( leaf=hash_tree_root(response.chunk), @@ -626,6 +627,9 @@ def process_bit_challenge_response(state: BeaconState, challenge: CustodyBitChallengeRecord) -> None: # Verify chunk index assert response.chunk_index < challenge.chunk_count + # Verify responder has not been slashed + responder = state.validator_registry[record.responder_index] + assert not responder.slashed # Verify the chunk matches the crosslink data root assert verify_merkle_branch( leaf=hash_tree_root(response.chunk), @@ -671,13 +675,13 @@ Run `process_challenge_deadlines(state)` immediately after `process_reveal_deadl ```python def process_challenge_deadlines(state: BeaconState) -> None: for challenge in state.custody_chunk_challenge_records: - if get_current_epoch(state) > challenge.deadline: + if get_current_epoch(state) > challenge.inclusion_epoch + CUSTODY_RESPONSE_DEADLINE: slash_validator(state, challenge.responder_index, challenge.challenger_index) records = state.custody_chunk_challenge_records records[records.index(challenge)] = CustodyChunkChallengeRecord() for challenge in state.custody_bit_challenge_records: - if get_current_epoch(state) > challenge.deadline: + if get_current_epoch(state) > challenge.inclusion_epoch + CUSTODY_RESPONSE_DEADLINE: slash_validator(state, challenge.responder_index, challenge.challenger_index) records = state.custody_bit_challenge_records records[records.index(challenge)] = CustodyBitChallengeRecord() @@ -688,6 +692,18 @@ Append this to `process_final_updates(state)`: ```python # Clean up exposed RANDAO key reveals state.exposed_derived_secrets[current_epoch % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS] = [] + # Reset withdrawable epochs if challenge records are empty + for index, validator in enumerate(state.validator_registry): + eligible = True + for records in (state.custody_chunk_challenge_records, state.bit_challenge_records): + for filter_func in (lambda rec: rec.challenger_index == index, lambda rec: rec.responder_index == index): + if len(list(filter(filter_func, records))) > 0: + eligible = False + if eligible: + if validator.exit_epoch == FAR_FUTURE_EPOCH: + validator.withdrawable_epoch = FAR_FUTURE_EPOCH + else: + validator.withdrawable_epoch = validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY ``` In `process_penalties_and_exits`, change the definition of `eligible` to the following (note that it is not a pure function because `state` is declared in the surrounding scope): From 2ccd357f0eab881eaf7f6bd39e09987467731c91 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 3 May 2019 21:05:54 +0800 Subject: [PATCH 2/4] Update specs/core/1_custody-game.md Co-Authored-By: vbuterin --- specs/core/1_custody-game.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/1_custody-game.md b/specs/core/1_custody-game.md index 0de84ff06..307e18573 100644 --- a/specs/core/1_custody-game.md +++ b/specs/core/1_custody-game.md @@ -628,7 +628,7 @@ def process_bit_challenge_response(state: BeaconState, # Verify chunk index assert response.chunk_index < challenge.chunk_count # Verify responder has not been slashed - responder = state.validator_registry[record.responder_index] + responder = state.validator_registry[challenge.responder_index] assert not responder.slashed # Verify the chunk matches the crosslink data root assert verify_merkle_branch( From 4db4d879301b130eba3667d8f455de8eb07e7c80 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 9 May 2019 14:57:36 +0800 Subject: [PATCH 3/4] Refactor `process_final_updates` --- specs/core/1_custody-game.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/specs/core/1_custody-game.md b/specs/core/1_custody-game.md index 1b6b1d2e4..e03e54ed0 100644 --- a/specs/core/1_custody-game.md +++ b/specs/core/1_custody-game.md @@ -683,13 +683,12 @@ Append this to `process_final_updates(state)`: # Clean up exposed RANDAO key reveals state.exposed_derived_secrets[current_epoch % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS] = [] # Reset withdrawable epochs if challenge records are empty + records = state.custody_chunk_challenge_records + state.bit_challenge_records + validator_indices_in_records = set( + [record.challenger_index for record in records] + [record.responder_index for record in records] + ) for index, validator in enumerate(state.validator_registry): - eligible = True - for records in (state.custody_chunk_challenge_records, state.bit_challenge_records): - for filter_func in (lambda rec: rec.challenger_index == index, lambda rec: rec.responder_index == index): - if len(list(filter(filter_func, records))) > 0: - eligible = False - if eligible: + if index not in validator_indices_in_records: if validator.exit_epoch == FAR_FUTURE_EPOCH: validator.withdrawable_epoch = FAR_FUTURE_EPOCH else: From 24edca3456166c11ebce912d3397c1618b79a3dc Mon Sep 17 00:00:00 2001 From: vbuterin Date: Fri, 17 May 2019 13:52:23 -0400 Subject: [PATCH 4/4] Fix to make Danny and hww happy --- specs/core/1_custody-game.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/specs/core/1_custody-game.md b/specs/core/1_custody-game.md index e03e54ed0..e28b30794 100644 --- a/specs/core/1_custody-game.md +++ b/specs/core/1_custody-game.md @@ -689,9 +689,7 @@ Append this to `process_final_updates(state)`: ) for index, validator in enumerate(state.validator_registry): if index not in validator_indices_in_records: - if validator.exit_epoch == FAR_FUTURE_EPOCH: - validator.withdrawable_epoch = FAR_FUTURE_EPOCH - else: + if validator.exit_epoch != FAR_FUTURE_EPOCH and validator.withdrawable_epoch == FAR_FUTURE_EPOCH: validator.withdrawable_epoch = validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY ```