From defa7da1c647a0d65eee69fdd083fa6c84577632 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 26 Nov 2018 21:12:14 +0800 Subject: [PATCH 1/7] Refactor and fix on_startup --- specs/core/0_beacon-chain.md | 163 ++++++++++++++++++++++++----------- 1 file changed, 113 insertions(+), 50 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 3ee0a1646..6828ce26d 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -556,13 +556,17 @@ We define another set of helpers to be used throughout: `bytes1(x): return x.to_ We define a function to "add a link" to the validator hash chain, used when a validator is added or removed: ```python -def add_validator_set_change_record(state: BeaconState, - index: int, - pubkey: int, - flag: int) -> None: - state.validator_set_delta_hash_chain = \ - hash(state.validator_set_delta_hash_chain + - bytes1(flag) + bytes3(index) + bytes32(pubkey)) +def get_new_validator_set_delta_hash_chain(current_validator_set_delta_hash_chain: Hash32, + index: int, + pubkey: int, + flag: int) -> Hash32: + new_validator_set_delta_hash_chain = hash( + current_validator_set_delta_hash_chain + + bytes1(flag) + + bytes3(index) + + bytes32(pubkey) + ) + return new_validator_set_delta_hash_chain ``` Finally, we abstractly define `int_sqrt(n)` for use in reward/penalty calculations as the largest integer `k` such that `k**2 <= n`. Here is one possible implementation, though clients are free to use their own including standard libraries for [integer square root](https://en.wikipedia.org/wiki/Integer_square_root) if available and meet the specification. @@ -654,13 +658,22 @@ A valid block with slot `0` (the "genesis block") has the following values. Othe `STARTUP_STATE_ROOT` is the root of the initial state, computed by running the following code: ```python -def on_startup(initial_validator_entries: List[Any], genesis_time: uint64, processed_pow_receipt_root: Hash32) -> BeaconState: +def on_startup(current_validators: List[ValidatorRecord], + pre_fork_version: int, + post_fork_version: int, + fork_slot_number: int, + initial_validator_entries: List[Any], + genesis_time: uint64, + processed_pow_receipt_root: Hash32) -> BeaconState: # Induct validators validators = [] for pubkey, proof_of_possession, withdrawal_credentials, \ randao_commitment in initial_validator_entries: - add_validator( - validators=validators, + validators, _ = get_new_validators( + current_validators=validators, + pre_fork_version=pre_fork_version, + pre_fork_version=post_fork_version, + fork_slot_number=fork_slot_number, pubkey=pubkey, proof_of_possession=proof_of_possession, withdrawal_credentials=withdrawal_credentials, @@ -724,32 +737,52 @@ def min_empty_validator(validators: List[ValidatorRecord], current_slot: int): ``` ```python -def get_fork_version(state: State, slot: int) -> int: - return state.pre_fork_version if slot < state.fork_slot_number else state.post_fork_version +def get_fork_version(pre_fork_version: int, + post_fork_version: int, + fork_slot_number: int, + slot: int) -> int: + return pre_fork_version if slot < fork_slot_number else post_fork_version -def get_domain(state: State, slot: int, base_domain: int) -> int: - return get_fork_version(state, slot) * 2**32 + base_domain -``` +def get_domain(pre_fork_version: int, + post_fork_version: int, + fork_slot_number: int, + slot: int, + base_domain: int) -> int: + return get_fork_version( + pre_fork_version, + post_fork_version, + fork_slot_number, + slot + ) * 2**32 + base_domain -Now, to add a validator: - -```python -def add_validator(state: State, - pubkey: int, - proof_of_possession: bytes, - withdrawal_credentials: Hash32, - randao_commitment: Hash32, - status: int, - current_slot: int) -> int: +def get_new_validators(current_validators: List[ValidatorRecord], + pre_fork_version: int, + post_fork_version: int, + fork_slot_number: int, + pubkey: int, + proof_of_possession: bytes, + withdrawal_credentials: Hash32, + randao_commitment: Hash32, + status: int, + current_slot: int) -> Tuple[List[ValidatorRecord], int]: # if following assert fails, validator induction failed # move on to next validator registration log - signed_message = bytes32(pubkey) + bytes2(withdrawal_shard) + withdrawal_credentials + randao_commitment - assert BLSVerify(pub=pubkey, - msg=hash(signed_message), - sig=proof_of_possession, - domain=get_domain(state, current_slot, DOMAIN_DEPOSIT)) + signed_message = bytes32(pubkey) + withdrawal_credentials + randao_commitment + assert BLSVerify( + pub=pubkey, + msg=hash(signed_message), + sig=proof_of_possession, + domain=get_domain( + pre_fork_version, + post_fork_version, + fork_slot_number, + current_slot, + DOMAIN_DEPOSIT + ) + ) # Pubkey uniqueness - assert pubkey not in [v.pubkey for v in state.validators] + new_validators = copy.deepcopy(current_validators) + assert pubkey not in [v.pubkey for v in current_validators] rec = ValidatorRecord( pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, @@ -760,13 +793,38 @@ def add_validator(state: State, last_status_change_slot=current_slot, exit_seq=0 ) - index = min_empty_validator(state.validators) + index = min_empty_validator(current_validators) if index is None: - state.validators.append(rec) - return len(state.validators) - 1 + new_validators.append(rec) + return new_validators, len(new_validators.validators) - 1, else: - state.validators[index] = rec - return index + new_validators[index] = rec + return new_validators, index +``` + +Now, to add a validator: + +```python +def add_validator(state: BeaconState, + pubkey: int, + proof_of_possession: bytes, + withdrawal_credentials: Hash32, + randao_commitment: Hash32, + status: int, + current_slot: int) -> index: + state.validators, index = get_new_validators( + current_validators=state.validators, + pre_fork_version=state.pre_fork_version, + post_fork_version=state.post_fork_version, + fork_slot_number=state.fork_slot_number, + pubkey=pubkey, + proof_of_possession=proof_of_possession, + withdrawal_credentials=withdrawal_credentials, + randao_commitment=randao_commitment, + status=status, + current_slot=current_slot, + ) + return index ``` `BLSVerify` is a function for verifying a BLS12-381 signature, defined in the BLS12-381 spec. @@ -787,7 +845,12 @@ def exit_validator(index, state, block, penalize, current_slot): state.deposits_penalized_in_period[current_slot // COLLECTIVE_PENALTY_CALCULATION_PERIOD] += validator.balance else: validator.status = PENDING_EXIT - add_validator_set_change_record(state, index, validator.pubkey, EXIT) + state.validator_set_delta_hash_chain = get_new_validator_set_delta_hash_chain( + validator_set_delta_hash_chain=state.validator_set_delta_hash_chain, + index=index, + pubkey=validator.pubkey, + flag=EXIT, + ) ``` ## Per-block processing @@ -795,7 +858,7 @@ def exit_validator(index, state, block, penalize, current_slot): This procedure should be carried out every beacon block. * Let `parent_hash` be the hash of the immediate previous beacon block (ie. equal to `ancestor_hashes[0]`). -* Let `parent` be the beacon block with the hash `parent_hash` +* Let `parent` be the beacon block with the hash `parent_hash`. First, set `recent_block_hashes` to the output of the following: @@ -834,7 +897,7 @@ Verify that there are at most `MAX_ATTESTATION_COUNT` `AttestationRecord` object * Verify that `len(attester_bitfield) == ceil_div8(len(attestation_indices))`, where `ceil_div8 = (x + 7) // 8`. Verify that bits `len(attestation_indices)....` and higher, if present (i.e. `len(attestation_indices)` is not a multiple of 8), are all zero. * Derive a `group_public_key` by adding the public keys of all of the attesters in `attestation_indices` for whom the corresponding bit in `attester_bitfield` (the ith bit is `(attester_bitfield[i // 8] >> (7 - (i % 8))) % 2`) equals 1. * Let `data = AttestationSignedData(slot, shard, parent_hashes, shard_block_hash, last_crosslinked_hash, shard_block_combined_data_root, justified_slot)`. -* Check `BLSVerify(pubkey=group_public_key, msg=data, sig=aggregate_sig, domain=get_domain(state, slot, DOMAIN_ATTESTATION))`. +* Check `BLSVerify(pubkey=group_public_key, msg=data, sig=aggregate_sig, domain=get_domain(state.pre_fork_version, state.post_fork_version, state.fork_slot_number, slot, DOMAIN_ATTESTATION))`. * [TO BE REMOVED IN PHASE 1] Verify that `shard_block_hash == bytes([0] * 32)`. Extend the list of `AttestationRecord` objects in the `state` with those included in the block, ordering the new additions in the same order as they came in the block. @@ -843,7 +906,7 @@ Extend the list of `AttestationRecord` objects in the `state` with those include Let `proposal_hash = hash(ProposalSignedData(block.slot, 2**64 - 1, block_hash_without_sig))` where `block_hash_without_sig` is the hash of the block except setting `proposer_signature` to `[0, 0]`. -Verify that `BLSVerify(pubkey=get_beacon_proposer(state, block.slot).pubkey, data=proposal_hash, sig=block.proposer_signature, domain=get_domain(state, block.slot, DOMAIN_PROPOSAL))` passes. +Verify that `BLSVerify(pubkey=get_beacon_proposer(state, block.slot).pubkey, data=proposal_hash, sig=block.proposer_signature, domain=get_domain(state.pre_fork_version, state.post_fork_version, state.fork_slot_number, block.slot, DOMAIN_PROPOSAL))` passes. ### Verify and process RANDAO reveal @@ -882,7 +945,7 @@ For each `SpecialRecord` `obj` in `block.specials`, verify that its `kind` is on ``` Perform the following checks: -* Verify that `BLSVerify(pubkey=validators[data.validator_index].pubkey, msg=bytes([0] * 32), sig=data.signature, domain=get_domain(state, current_slot, DOMAIN_LOGOUT))` +* Verify that `BLSVerify(pubkey=validators[data.validator_index].pubkey, msg=bytes([0] * 32), sig=data.signature, domain=get_domain(state.pre_fork_version, state.post_fork_version, state.fork_slot_number, current_slot, DOMAIN_LOGOUT))` * Verify that `validators[validator_index].status == ACTIVE`. * Verify that `block.slot >= last_status_change_slot + SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD` @@ -903,7 +966,7 @@ Run `exit_validator(data.validator_index, state, block, penalize=False, current_ Perform the following checks: -* For each `vote`, verify that `BLSVerify(pubkey=aggregate_pubkey([validators[i].pubkey for i in vote_aggregate_sig_indices]), msg=vote_data, sig=vote_aggregate_sig, domain=get_domain(state, vote_data.slot, DOMAIN_ATTESTATION))` passes. +* For each `vote`, verify that `BLSVerify(pubkey=aggregate_pubkey([validators[i].pubkey for i in vote_aggregate_sig_indices]), msg=vote_data, sig=vote_aggregate_sig, domain=get_domain(state.pre_fork_version, state.post_fork_version, state.fork_slot_number, vote_data.slot, DOMAIN_ATTESTATION))` passes. * Verify that `vote1_data != vote2_data`. * Let `intersection = [x for x in vote1_aggregate_sig_indices if x in vote2_aggregate_sig_indices]`. Verify that `len(intersection) >= 1`. * Verify that `vote1_data.justified_slot < vote2_data.justified_slot < vote2_data.slot <= vote1_data.slot`. @@ -921,7 +984,7 @@ For each validator index `v` in `intersection`, if `state.validators[v].status` 'proposal1_signature': '[uint384]', } ``` -For each `proposal_signature`, verify that `BLSVerify(pubkey=validators[proposer_index].pubkey, msg=hash(proposal_data), sig=proposal_signature, domain=get_domain(state, proposal_data.slot, DOMAIN_PROPOSAL))` passes. Verify that `proposal1_data.slot == proposal2_data.slot` but `proposal1 != proposal2`. If `state.validators[proposer_index].status` does not equal `PENALIZED`, then run `exit_validator(proposer_index, state, penalize=True, current_slot=block.slot)` +For each `proposal_signature`, verify that `BLSVerify(pubkey=validators[proposer_index].pubkey, msg=hash(proposal_data), sig=proposal_signature, domain=get_domain(state.pre_fork_version, state.post_fork_version, state.fork_slot_number, proposal_data.slot, DOMAIN_PROPOSAL))` passes. Verify that `proposal1_data.slot == proposal2_data.slot` but `proposal1 != proposal2`. If `state.validators[proposer_index].status` does not equal `PENALIZED`, then run `exit_validator(proposer_index, state, penalize=True, current_slot=block.slot)` #### DEPOSIT_PROOF @@ -954,7 +1017,7 @@ def verify_merkle_branch(leaf: Hash32, branch: [Hash32], depth: int, index: int, Verify that `deposit_data.msg_value == DEPOSIT_SIZE` and `block.slot - (deposit_data.timestamp - state.genesis_time) // SLOT_DURATION < DELETION_PERIOD`. -Run `add_validator(validators, deposit_data.deposit_params.pubkey, deposit_data.deposit_params.proof_of_possession, deposit_data.deposit_params.withdrawal_credentials, deposit_data.deposit_params.randao_commitment, PENDING_ACTIVATION, block.slot)`. +Run `add_validator(state, pubkey=deposit_data.deposit_params.pubkey, proof_of_possession=deposit_data.deposit_params.proof_of_possession, withdrawal_credentials=deposit_data.deposit_params.withdrawal_credentials, randao_commitment=deposit_data.deposit_params.randao_commitment, status=PENDING_ACTIVATION, current_slot=block.slot)`. ## State recalculations (every `CYCLE_LENGTH` slots) @@ -1045,21 +1108,21 @@ def change_validators(validators: List[ValidatorRecord], current_slot: int) -> N if validators[i].status == PENDING_ACTIVATION: validators[i].status = ACTIVE total_changed += DEPOSIT_SIZE * GWEI_PER_ETH - add_validator_set_change_record( - state=state, + state.validator_set_delta_hash_chain = get_new_validator_set_delta_hash_chain( + validator_set_delta_hash_chain=state.validator_set_delta_hash_chain, index=i, pubkey=validators[i].pubkey, - flag=ENTRY + flag=ENTRY, ) if validators[i].status == PENDING_EXIT: validators[i].status = PENDING_WITHDRAW validators[i].last_status_change_slot = current_slot total_changed += validators[i].balance - add_validator_set_change_record( - state=state, + state.validator_set_delta_hash_chain = calcuate_validator_set_delta_hash_chain( + validator_set_delta_hash_chain=state.validator_set_delta_hash_chain, index=i, pubkey=validators[i].pubkey, - flag=EXIT + flag=EXIT, ) if total_changed >= max_allowable_change: break From 7bde8c415af18125ce92251b57f57b1951373bd6 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 26 Nov 2018 21:27:41 +0800 Subject: [PATCH 2/7] fix `get_new_validator_set_delta_hash_chain` --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 6828ce26d..19668730b 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1118,7 +1118,7 @@ def change_validators(validators: List[ValidatorRecord], current_slot: int) -> N validators[i].status = PENDING_WITHDRAW validators[i].last_status_change_slot = current_slot total_changed += validators[i].balance - state.validator_set_delta_hash_chain = calcuate_validator_set_delta_hash_chain( + state.validator_set_delta_hash_chain = get_new_validator_set_delta_hash_chain( validator_set_delta_hash_chain=state.validator_set_delta_hash_chain, index=i, pubkey=validators[i].pubkey, From 4067e0f25a0264ad51e7525bd386126a138868df Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 26 Nov 2018 21:43:59 +0800 Subject: [PATCH 3/7] fix --- specs/core/0_beacon-chain.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 19668730b..dae7d4a51 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -796,7 +796,7 @@ def get_new_validators(current_validators: List[ValidatorRecord], index = min_empty_validator(current_validators) if index is None: new_validators.append(rec) - return new_validators, len(new_validators.validators) - 1, + return new_validators, len(new_validators.validators) - 1 else: new_validators[index] = rec return new_validators, index @@ -811,7 +811,7 @@ def add_validator(state: BeaconState, withdrawal_credentials: Hash32, randao_commitment: Hash32, status: int, - current_slot: int) -> index: + current_slot: int) -> int: state.validators, index = get_new_validators( current_validators=state.validators, pre_fork_version=state.pre_fork_version, From b66f168468f50c30406002e3e2a29c0d493e7937 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 27 Nov 2018 00:21:59 +0800 Subject: [PATCH 4/7] PR feedback: reduce `on_startup` parameters --- specs/core/0_beacon-chain.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index dae7d4a51..5513ef235 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -660,10 +660,8 @@ A valid block with slot `0` (the "genesis block") has the following values. Othe ```python def on_startup(current_validators: List[ValidatorRecord], pre_fork_version: int, - post_fork_version: int, - fork_slot_number: int, initial_validator_entries: List[Any], - genesis_time: uint64, + genesis_time: int, processed_pow_receipt_root: Hash32) -> BeaconState: # Induct validators validators = [] @@ -672,8 +670,8 @@ def on_startup(current_validators: List[ValidatorRecord], validators, _ = get_new_validators( current_validators=validators, pre_fork_version=pre_fork_version, - pre_fork_version=post_fork_version, - fork_slot_number=fork_slot_number, + post_fork_version=pre_fork_version, + fork_slot_number=2**64 - 1, pubkey=pubkey, proof_of_possession=proof_of_possession, withdrawal_credentials=withdrawal_credentials, From 43ef444613db091032502bc1c8a249e67944bc50 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 27 Nov 2018 11:52:06 +0800 Subject: [PATCH 5/7] PR feedback: add `ForkData` --- specs/core/0_beacon-chain.md | 65 +++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 5513ef235..2d4eedd12 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -245,9 +245,7 @@ The `BeaconState` has the following fields: 'candidate_pow_receipt_roots': [CandidatePoWReceiptRootRecord], # Parameters relevant to hard forks / versioning. # Should be updated only by hard forks. - 'pre_fork_version': 'uint64', - 'post_fork_version': 'uint64', - 'fork_slot_number': 'uint64', + 'fork_data': ForkData, # Attestations not yet processed 'pending_attestations': [AttestationRecord], # recent beacon block hashes needed to process attestations, older to newer @@ -326,6 +324,18 @@ A `CandidatePoWReceiptRootRecord` object contains the following fields: } ``` +A `ForkData` object contains the following fields: +```python +{ + # Previous fork version + 'pre_fork_version': 'uint64', + # Post fork version + 'post_fork_version': 'uint64', + # Fork slot number + 'fork_slot_number': 'uint64' +} +``` + ## Beacon chain processing The beacon chain is the "main chain" of the PoS system. The beacon chain's main responsibilities are: @@ -669,9 +679,11 @@ def on_startup(current_validators: List[ValidatorRecord], randao_commitment in initial_validator_entries: validators, _ = get_new_validators( current_validators=validators, - pre_fork_version=pre_fork_version, - post_fork_version=pre_fork_version, - fork_slot_number=2**64 - 1, + fork_data=ForkData( + pre_fork_version=pre_fork_version, + post_fork_version=pre_fork_version, + fork_slot_number=2**64 - 1, + ), pubkey=pubkey, proof_of_possession=proof_of_possession, withdrawal_credentials=withdrawal_credentials, @@ -735,28 +747,23 @@ def min_empty_validator(validators: List[ValidatorRecord], current_slot: int): ``` ```python -def get_fork_version(pre_fork_version: int, - post_fork_version: int, - fork_slot_number: int, +def get_fork_version(fork_data: ForkData, slot: int) -> int: - return pre_fork_version if slot < fork_slot_number else post_fork_version - -def get_domain(pre_fork_version: int, - post_fork_version: int, - fork_slot_number: int, + if slot < fork_data.fork_slot_number: + return fork_data.pre_fork_version + else: + return fork_data.post_fork_version + +def get_domain(fork_data: ForkData, slot: int, base_domain: int) -> int: return get_fork_version( - pre_fork_version, - post_fork_version, - fork_slot_number, + fork_data, slot ) * 2**32 + base_domain def get_new_validators(current_validators: List[ValidatorRecord], - pre_fork_version: int, - post_fork_version: int, - fork_slot_number: int, + fork_data: ForkData, pubkey: int, proof_of_possession: bytes, withdrawal_credentials: Hash32, @@ -771,9 +778,7 @@ def get_new_validators(current_validators: List[ValidatorRecord], msg=hash(signed_message), sig=proof_of_possession, domain=get_domain( - pre_fork_version, - post_fork_version, - fork_slot_number, + fork_data, current_slot, DOMAIN_DEPOSIT ) @@ -812,9 +817,11 @@ def add_validator(state: BeaconState, current_slot: int) -> int: state.validators, index = get_new_validators( current_validators=state.validators, - pre_fork_version=state.pre_fork_version, - post_fork_version=state.post_fork_version, - fork_slot_number=state.fork_slot_number, + fork_data=ForkData( + pre_fork_version=state.pre_fork_version, + post_fork_version=state.post_fork_version, + fork_slot_number=state.fork_slot_number, + ), pubkey=pubkey, proof_of_possession=proof_of_possession, withdrawal_credentials=withdrawal_credentials, @@ -830,7 +837,11 @@ def add_validator(state: BeaconState, ### Routine for removing a validator ```python -def exit_validator(index, state, block, penalize, current_slot): +def exit_validator(index: int, + state: BeaconState, + block: BeaconBlock, + penalize: bool, + current_slot: int) -> None: validator = state.validators[index] validator.last_status_change_slot = current_slot validator.exit_seq = state.current_exit_seq From b27203c0cefbe0ede44e18374944b2a1c280e25e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 27 Nov 2018 23:54:09 +0800 Subject: [PATCH 6/7] PR feedback: use `fork_data` --- specs/core/0_beacon-chain.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 2d4eedd12..f765b7178 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -906,16 +906,15 @@ Verify that there are at most `MAX_ATTESTATION_COUNT` `AttestationRecord` object * Verify that `len(attester_bitfield) == ceil_div8(len(attestation_indices))`, where `ceil_div8 = (x + 7) // 8`. Verify that bits `len(attestation_indices)....` and higher, if present (i.e. `len(attestation_indices)` is not a multiple of 8), are all zero. * Derive a `group_public_key` by adding the public keys of all of the attesters in `attestation_indices` for whom the corresponding bit in `attester_bitfield` (the ith bit is `(attester_bitfield[i // 8] >> (7 - (i % 8))) % 2`) equals 1. * Let `data = AttestationSignedData(slot, shard, parent_hashes, shard_block_hash, last_crosslinked_hash, shard_block_combined_data_root, justified_slot)`. -* Check `BLSVerify(pubkey=group_public_key, msg=data, sig=aggregate_sig, domain=get_domain(state.pre_fork_version, state.post_fork_version, state.fork_slot_number, slot, DOMAIN_ATTESTATION))`. +* Check `BLSVerify(pubkey=group_public_key, msg=data, sig=aggregate_sig, domain=get_domain(state.fork_data, slot, DOMAIN_ATTESTATION))`. * [TO BE REMOVED IN PHASE 1] Verify that `shard_block_hash == bytes([0] * 32)`. Extend the list of `AttestationRecord` objects in the `state` with those included in the block, ordering the new additions in the same order as they came in the block. ### Verify proposer signature -Let `proposal_hash = hash(ProposalSignedData(block.slot, 2**64 - 1, block_hash_without_sig))` where `block_hash_without_sig` is the hash of the block except setting `proposer_signature` to `[0, 0]`. - -Verify that `BLSVerify(pubkey=get_beacon_proposer(state, block.slot).pubkey, data=proposal_hash, sig=block.proposer_signature, domain=get_domain(state.pre_fork_version, state.post_fork_version, state.fork_slot_number, block.slot, DOMAIN_PROPOSAL))` passes. +* Let `proposal_hash = hash(ProposalSignedData(block.slot, 2**64 - 1, block_hash_without_sig))` where `block_hash_without_sig` is the hash of the block except setting `proposer_signature` to `[0, 0]`. +* Verify that `BLSVerify(pubkey=get_beacon_proposer(state, block.slot).pubkey, data=proposal_hash, sig=block.proposer_signature, domain=get_domain(state.fork_data, block.slot, DOMAIN_PROPOSAL))` passes. ### Verify and process RANDAO reveal @@ -953,10 +952,9 @@ For each `SpecialRecord` `obj` in `block.specials`, verify that its `kind` is on } ``` Perform the following checks: - -* Verify that `BLSVerify(pubkey=validators[data.validator_index].pubkey, msg=bytes([0] * 32), sig=data.signature, domain=get_domain(state.pre_fork_version, state.post_fork_version, state.fork_slot_number, current_slot, DOMAIN_LOGOUT))` +* Verify that `BLSVerify(pubkey=validators[data.validator_index].pubkey, msg=bytes([0] * 32), sig=data.signature, domain=get_domain(state.fork_data, current_slot, DOMAIN_LOGOUT))`. * Verify that `validators[validator_index].status == ACTIVE`. -* Verify that `block.slot >= last_status_change_slot + SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD` +* Verify that `block.slot >= last_status_change_slot + SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD`. Run `exit_validator(data.validator_index, state, block, penalize=False, current_slot=block.slot)`. @@ -975,7 +973,7 @@ Run `exit_validator(data.validator_index, state, block, penalize=False, current_ Perform the following checks: -* For each `vote`, verify that `BLSVerify(pubkey=aggregate_pubkey([validators[i].pubkey for i in vote_aggregate_sig_indices]), msg=vote_data, sig=vote_aggregate_sig, domain=get_domain(state.pre_fork_version, state.post_fork_version, state.fork_slot_number, vote_data.slot, DOMAIN_ATTESTATION))` passes. +* For each `vote`, verify that `BLSVerify(pubkey=aggregate_pubkey([validators[i].pubkey for i in vote_aggregate_sig_indices]), msg=vote_data, sig=vote_aggregate_sig, domain=get_domain(state.fork_data, vote_data.slot, DOMAIN_ATTESTATION))` passes. * Verify that `vote1_data != vote2_data`. * Let `intersection = [x for x in vote1_aggregate_sig_indices if x in vote2_aggregate_sig_indices]`. Verify that `len(intersection) >= 1`. * Verify that `vote1_data.justified_slot < vote2_data.justified_slot < vote2_data.slot <= vote1_data.slot`. @@ -993,7 +991,7 @@ For each validator index `v` in `intersection`, if `state.validators[v].status` 'proposal1_signature': '[uint384]', } ``` -For each `proposal_signature`, verify that `BLSVerify(pubkey=validators[proposer_index].pubkey, msg=hash(proposal_data), sig=proposal_signature, domain=get_domain(state.pre_fork_version, state.post_fork_version, state.fork_slot_number, proposal_data.slot, DOMAIN_PROPOSAL))` passes. Verify that `proposal1_data.slot == proposal2_data.slot` but `proposal1 != proposal2`. If `state.validators[proposer_index].status` does not equal `PENALIZED`, then run `exit_validator(proposer_index, state, penalize=True, current_slot=block.slot)` +For each `proposal_signature`, verify that `BLSVerify(pubkey=validators[proposer_index].pubkey, msg=hash(proposal_data), sig=proposal_signature, domain=get_domain(state.fork_data, proposal_data.slot, DOMAIN_PROPOSAL))` passes. Verify that `proposal1_data.slot == proposal2_data.slot` but `proposal1 != proposal2`. If `state.validators[proposer_index].status` does not equal `PENALIZED`, then run `exit_validator(proposer_index, state, penalize=True, current_slot=block.slot)` #### DEPOSIT_PROOF From b1fc396ab4de5a2901f5db1b0ba9b1341ff51182 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 28 Nov 2018 01:06:09 +0800 Subject: [PATCH 7/7] Refactor `change_validators` --- specs/core/0_beacon-chain.md | 58 ++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 77cbf7be2..0a3ab96c1 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -773,9 +773,8 @@ def min_empty_validator_index(validators: List[ValidatorRecord], current_slot: i if v.status == WITHDRAWN and v.last_status_change_slot + DELETION_PERIOD <= current_slot: return i return None -``` -```python + def get_fork_version(fork_data: ForkData, slot: int) -> int: if slot < fork_data.fork_slot_number: @@ -783,6 +782,7 @@ def get_fork_version(fork_data: ForkData, else: return fork_data.post_fork_version + def get_domain(fork_data: ForkData, slot: int, base_domain: int) -> int: @@ -791,6 +791,7 @@ def get_domain(fork_data: ForkData, slot ) * 2**32 + base_domain + def get_new_validators(current_validators: List[ValidatorRecord], fork_data: ForkData, pubkey: int, @@ -846,6 +847,10 @@ def add_validator(state: BeaconState, randao_commitment: Hash32, status: int, current_slot: int) -> int: + """ + Add the validator into the given `state`. + Note that this function mutates `state`. + """ state.validators, index = get_new_validators( current_validators=state.validators, fork_data=ForkData( @@ -874,6 +879,10 @@ def exit_validator(index: int, block: BeaconBlock, penalize: bool, current_slot: int) -> None: + """ + Remove the validator with the given `index` from `state`. + Note that this function mutates `state`. + """ validator = state.validators[index] validator.last_status_change_slot = current_slot validator.exit_seq = state.current_exit_seq @@ -1163,10 +1172,16 @@ A validator set change can happen if all of the following criteria are satisfied * `last_finalized_slot > state.validator_set_change_slot` * For every shard number `shard` in `shard_and_committee_for_slots`, `crosslinks[shard].slot > state.validator_set_change_slot` -Then, run the following algorithm to update the validator set: +A helper function is defined as: ```python -def change_validators(validators: List[ValidatorRecord], current_slot: int) -> None: +def get_changed_validators(validators: List[ValidatorRecord], + deposits_penalized_in_period: List[int], + validator_set_delta_hash_chain: int, + current_slot: int) -> Tuple[List[ValidatorRecord], List[int], int]: + """ + Return changed validator set and `deposits_penalized_in_period`, `validator_set_delta_hash_chain`. + """ # The active validator set active_validators = get_active_validator_indices(validators) # The total balance of active validators @@ -1182,8 +1197,8 @@ def change_validators(validators: List[ValidatorRecord], current_slot: int) -> N if validators[i].status == PENDING_ACTIVATION: validators[i].status = ACTIVE total_changed += DEPOSIT_SIZE * GWEI_PER_ETH - state.validator_set_delta_hash_chain = get_new_validator_set_delta_hash_chain( - validator_set_delta_hash_chain=state.validator_set_delta_hash_chain, + validator_set_delta_hash_chain = get_new_validator_set_delta_hash_chain( + validator_set_delta_hash_chain=validator_set_delta_hash_chain, index=i, pubkey=validators[i].pubkey, flag=ENTRY, @@ -1192,8 +1207,8 @@ def change_validators(validators: List[ValidatorRecord], current_slot: int) -> N validators[i].status = PENDING_WITHDRAW validators[i].last_status_change_slot = current_slot total_changed += balance_at_stake(validators[i]) - state.validator_set_delta_hash_chain = get_new_validator_set_delta_hash_chain( - validator_set_delta_hash_chain=state.validator_set_delta_hash_chain, + validator_set_delta_hash_chain = get_new_validator_set_delta_hash_chain( + validator_set_delta_hash_chain=validator_set_delta_hash_chain, index=i, pubkey=validators[i].pubkey, flag=EXIT, @@ -1204,9 +1219,9 @@ def change_validators(validators: List[ValidatorRecord], current_slot: int) -> N # Calculate the total ETH that has been penalized in the last ~2-3 withdrawal periods period_index = current_slot // COLLECTIVE_PENALTY_CALCULATION_PERIOD total_penalties = ( - (state.deposits_penalized_in_period[period_index]) + - (state.deposits_penalized_in_period[period_index - 1] if period_index >= 1 else 0) + - (state.deposits_penalized_in_period[period_index - 2] if period_index >= 2 else 0) + (deposits_penalized_in_period[period_index]) + + (deposits_penalized_in_period[period_index - 1] if period_index >= 1 else 0) + + (deposits_penalized_in_period[period_index - 2] if period_index >= 2 else 0) ) # Separate loop to withdraw validators that have been logged out for long enough, and # calculate their penalties if they were slashed @@ -1222,7 +1237,26 @@ def change_validators(validators: List[ValidatorRecord], current_slot: int) -> N v.last_status_change_slot = current_slot withdraw_amount = v.balance - # STUB: withdraw to shard chain + # STUB: withdraw to shard chain + + return validators, deposits_penalized_in_period, validator_set_delta_hash_chain +``` + +Then, run the following algorithm to update the validator set: + +```python +def change_validators(state: BeaconState, + current_slot: int) -> None: + """ + Change validator set. + Note that this function mutates `state`. + """ + state.validators, state.deposits_penalized_in_period = get_changed_validators( + copy.deepcopy(state.validators), + copy.deepcopy(state.deposits_penalized_in_period), + state.validator_set_delta_hash_chain, + current_slot + ) ``` And perform the following updates to the `state`: