From dc01cb0f4da5a5fc0f64c822077893103822139a Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 4 Oct 2018 12:43:30 -0500 Subject: [PATCH 1/5] [fix-recent_block_hashes] fix issue with recent_block_hashes when slots are skipped --- specs/beacon-chain.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index c36b32c9e..4962b2e1d 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -141,7 +141,7 @@ fields = { 'pending_attestations': [AttestationRecord], # Special objects that have not yet been processed 'pending_specials': [SpecialObject], - # Most recent 2 * CYCLE_LENGTH block hashes, older to newer + # recent block hashes needed to process attestations, older to newer 'recent_block_hashes': ['hash32'], # RANDAO state 'randao_mix': 'hash32' @@ -352,8 +352,8 @@ def get_shards_and_committees_for_slot(crystallized_state, slot): return crystallized_state.shard_and_committee_for_slots[slot - earliest_slot_in_array] def get_block_hash(active_state, curblock, slot): - earliest_slot_in_array = curblock.slot - CYCLE_LENGTH * 2 - assert earliest_slot_in_array <= slot < earliest_slot_in_array + CYCLE_LENGTH * 2 + earliest_slot_in_array = curblock.slot - len(active_state.recent_block_hashes) + assert earliest_slot_in_array <= slot < curblock.slot return active_state.recent_block_hashes[slot - earliest_slot_in_array] ``` @@ -444,13 +444,13 @@ This procedure should be carried out every block. First, set `recent_block_hashes` to the output of the following, where `parent_hash` is the hash of the immediate previous block (ie. must be equal to `ancestor_hashes[0]`): ```python -def get_new_recent_block_hashes(old_block_hashes, parent_slot, - current_slot, parent_hash): +def append_to_recent_block_hashes(old_block_hashes, parent_slot, + current_slot, parent_hash): d = current_slot - parent_slot - return old_block_hashes[d:] + [parent_hash] * min(d, len(old_block_hashes)) + return old_block_hashes + [parent_hash] * min(d, len(old_block_hashes)) ``` -The output of `get_block_hash` should not change, except that it will no longer throw for `current_slot - 1`, and will now throw for `current_slot - CYCLE_LENGTH * 2 - 1`. Also, check that the block's `ancestor_hashes` array was correctly updated, using the following algorithm: +The output of `get_block_hash` should not change, except that it will no longer throw for `current_slot - 1`. Also, check that the block's `ancestor_hashes` array was correctly updated, using the following algorithm: ```python def update_ancestor_hashes(parent_ancestor_hashes, parent_slot_number, parent_hash): @@ -542,6 +542,7 @@ For each `SpecialObject` `obj` in `active_state.pending_specials`: * Set `crystallized_state.last_state_recalculation += CYCLE_LENGTH` * Remove all attestation records older than slot `crystallized_state.last_state_recalculation` * Empty the `active_state.pending_specials` list +* Set `active_state.recent_block_hashes = active_state.recent_block_hashes[CYCLE_LENGTH:]` * Set `shard_and_committee_for_slots[:CYCLE_LENGTH] = shard_and_committee_for_slots[CYCLE_LENGTH:]` ### Dynasty transition From 5b31268abcd5652f30f1e2be784d75bb3b2199eb Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 4 Oct 2018 12:53:53 -0500 Subject: [PATCH 2/5] [fix-recent_block_hashes] fix formatting --- specs/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index 4962b2e1d..dee4ac0e6 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -445,7 +445,7 @@ First, set `recent_block_hashes` to the output of the following, where `parent_h ```python def append_to_recent_block_hashes(old_block_hashes, parent_slot, - current_slot, parent_hash): + current_slot, parent_hash): d = current_slot - parent_slot return old_block_hashes + [parent_hash] * min(d, len(old_block_hashes)) ``` From 57cbc5434cad66113dd049f0c9ef737272826a59 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 4 Oct 2018 18:25:24 -0500 Subject: [PATCH 3/5] [fix-recent_block_hashes] remove min requirement from append_to_recent_block_hashes --- specs/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index dee4ac0e6..f76e6a08c 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -447,7 +447,7 @@ First, set `recent_block_hashes` to the output of the following, where `parent_h def append_to_recent_block_hashes(old_block_hashes, parent_slot, current_slot, parent_hash): d = current_slot - parent_slot - return old_block_hashes + [parent_hash] * min(d, len(old_block_hashes)) + return old_block_hashes + [parent_hash] * d ``` The output of `get_block_hash` should not change, except that it will no longer throw for `current_slot - 1`. Also, check that the block's `ancestor_hashes` array was correctly updated, using the following algorithm: From fdf8162c59e31a5c8db1a232cc7e60d5f23d668f Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 16 Oct 2018 12:55:03 +0100 Subject: [PATCH 4/5] Move from signed integers to unsigned integers --- specs/beacon-chain.md | 62 ++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index 379e32b11..e923cb8d4 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -93,7 +93,7 @@ A `BeaconBlock` has the following fields: ```python { # Slot number - 'slot': 'int64', + 'slot': 'uint64', # Proposer RANDAO reveal 'randao_reveal': 'hash32', # Recent PoW chain reference (block hash) @@ -117,9 +117,9 @@ An `AttestationRecord` has the following fields: ```python { # Slot number - 'slot': 'int64', + 'slot': 'uint64', # Shard number - 'shard': 'int16', + 'shard': 'uint16', # Block hashes not part of the current chain, oldest to newest 'oblique_parent_hashes': ['hash32'], # Shard block hash being attested to @@ -127,11 +127,11 @@ An `AttestationRecord` has the following fields: # Attester participation bitfield (1 bit per attester) 'attester_bitfield': 'bytes', # Slot of last justified block - 'justified_slot': 'int64', + 'justified_slot': 'uint64', # Hash of last justified block 'justified_block_hash': 'hash32', # BLS aggregate signature - 'aggregate_sig': ['int256'] + 'aggregate_sig': ['uint256'] } ``` @@ -140,17 +140,17 @@ An `AttestationSignedData` has the following fields: ```python { # Chain version - 'version': 'int64', + 'version': 'uint64', # Slot number - 'slot': 'int64', + 'slot': 'uint64', # Shard number - 'shard': 'int16', + 'shard': 'uint16', # 31 parent hashes 'parent_hashes': ['hash32'], # Shard block hash 'shard_block_hash': 'hash32', # Slot of last justified block referenced in the attestation - 'justified_slot': 'int64' + 'justified_slot': 'uint64' } ``` @@ -159,7 +159,7 @@ A `SpecialRecord` has the following fields: ```python { # Kind - 'kind': 'int8', + 'kind': 'uint8', # Data 'data': ['bytes'] } @@ -189,30 +189,30 @@ The `CrystallizedState` has the following fields: ```python { # Slot of last validator set change - 'validator_set_change_slot': 'int64', + 'validator_set_change_slot': 'uint64', # List of validators 'validators': [ValidatorRecord], # Most recent crosslink for each shard 'crosslinks': [CrosslinkRecord], # Last crystallized state recalculation - 'last_state_recalculation_slot': 'int64', + 'last_state_recalculation_slot': 'uint64', # Last finalized slot - 'last_finalized_slot': 'int64', + 'last_finalized_slot': 'uint64', # Last justified slot - 'last_justified_slot': 'int64', + 'last_justified_slot': 'uint64', # Number of consecutive justified slots - 'justified_streak': 'int64', + 'justified_streak': 'uint64', # Committee members and their assigned shard, per slot 'shard_and_committee_for_slots': [[ShardAndCommittee]], # Total deposits penalized in the given withdrawal period - 'deposits_penalized_in_period': ['int32'], + 'deposits_penalized_in_period': ['uint32'], # Hash chain of validator set changes (for light clients to easily track deltas) 'validator_set_delta_hash_chain': 'hash32' # Parameters relevant to hard forks / versioning. # Should be updated only by hard forks. - 'pre_fork_version': 'int32', - 'post_fork_version': 'int32', - 'fork_slot_number': 'int64', + 'pre_fork_version': 'uint32', + 'post_fork_version': 'uint32', + 'fork_slot_number': 'uint64', } ``` @@ -221,21 +221,21 @@ A `ValidatorRecord` has the following fields: ```python { # BLS public key - 'pubkey': 'int256', + 'pubkey': 'uint256', # Withdrawal shard number - 'withdrawal_shard': 'int16', + 'withdrawal_shard': 'uint16', # Withdrawal address 'withdrawal_address': 'address', # RANDAO commitment 'randao_commitment': 'hash32', # Slot the RANDAO commitment was last changed - 'randao_last_change': 'int64', + 'randao_last_change': 'uint64', # Balance - 'balance': 'int64', + 'balance': 'uint64', # Status code - 'status': 'int8', + 'status': 'uint8', # Slot when validator exited (or 0) - 'exit_slot': 'int64' + 'exit_slot': 'uint64' } ``` @@ -246,7 +246,7 @@ A `CrosslinkRecord` has the following fields: # Since last validator set change? 'recently_changed': 'bool', # Slot number - 'slot': 'int64', + 'slot': 'uint64', # Beacon chain block hash 'shard_block_hash': 'hash32' } @@ -257,9 +257,9 @@ A `ShardAndCommittee` object has the following fields: ```python { # Shard number - 'shard': 'int16', + 'shard': 'uint16', # Validator indices - 'committee': ['int24'] + 'committee': ['uint24'] } ``` @@ -670,6 +670,8 @@ For every `(shard, shard_block_hash)` tuple: #### Balance recalculations related to FFG rewards +Note: When applying penalties in the following balance recalculations implementers should make sure the `uint64` does not underflow. + * Let `total_balance` be the total balance of active validators. * Let `total_balance_in_eth = total_balance // GWEI_PER_ETH`. * Let `reward_quotient = BASE_REWARD_QUOTIENT * int_sqrt(total_balance_in_eth)`. (The per-slot maximum interest rate is `1/reward_quotient`.) @@ -706,8 +708,8 @@ In addition, validators with `status == PENALIZED` lose `B // reward_quotient + For each `SpecialRecord` `obj` in `active_state.pending_specials`: -* **[covers logouts]**: If `obj.kind == LOGOUT`, interpret `data[0]` as a validator index as an `int32` and `data[1]` as a signature. If `BLSVerify(pubkey=validators[data[0]].pubkey, msg=hash(LOGOUT_MESSAGE + bytes8(version)), sig=data[1])`, where `version = pre_fork_version if slot < fork_slot_number else post_fork_version`, and `validators[i].status == ACTIVE`, run `exit_validator(data[0], crystallized_state, penalize=False, current_slot=block.slot)` -* **[covers `NO_DBL_VOTE`, `NO_SURROUND`, `NO_DBL_PROPOSE` slashing conditions]:** If `obj.kind == CASPER_SLASHING`, interpret `data[0]` as a list of concatenated `int32` values where each value represents an index into `validators`, `data[1]` as the data being signed and `data[2]` as an aggregate signature. Interpret `data[3:6]` similarly. Verify that both signatures are valid, that the two signatures are signing distinct data, and that they are either signing the same slot number, or that one surrounds the other (ie. `source1 < source2 < target2 < target1`). Let `indices` be the list of indices in both signatures; verify that its length is at least 1. For each validator index `v` in `indices`, if its `status` does not equal `PENALIZED`, then run `exit_validator(v, crystallized_state, penalize=True, current_slot=block.slot)` +* **[covers logouts]**: If `obj.kind == LOGOUT`, interpret `data[0]` as a validator index as an `uint32` and `data[1]` as a signature. If `BLSVerify(pubkey=validators[data[0]].pubkey, msg=hash(LOGOUT_MESSAGE + bytes8(version)), sig=data[1])`, where `version = pre_fork_version if slot < fork_slot_number else post_fork_version`, and `validators[i].status == ACTIVE`, run `exit_validator(data[0], crystallized_state, penalize=False, current_slot=block.slot)` +* **[covers `NO_DBL_VOTE`, `NO_SURROUND`, `NO_DBL_PROPOSE` slashing conditions]:** If `obj.kind == CASPER_SLASHING`, interpret `data[0]` as a list of concatenated `uint32` values where each value represents an index into `validators`, `data[1]` as the data being signed and `data[2]` as an aggregate signature. Interpret `data[3:6]` similarly. Verify that both signatures are valid, that the two signatures are signing distinct data, and that they are either signing the same slot number, or that one surrounds the other (ie. `source1 < source2 < target2 < target1`). Let `indices` be the list of indices in both signatures; verify that its length is at least 1. For each validator index `v` in `indices`, if its `status` does not equal `PENALIZED`, then run `exit_validator(v, crystallized_state, penalize=True, current_slot=block.slot)` * **[covers RANDAO updates]**: If `obj.kind == RANDAO_REVEAL`, interpret `data[0]` as an integer and `data[1]` as a hash32. Set `validators[data[0]].randao_commitment = data[1]`. #### Finally... From 9135fd9085b68f2499040523bc650cab1eb863d5 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 16 Oct 2018 10:35:09 -0500 Subject: [PATCH 5/5] fix small typo --- specs/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index a4fefea91..75f37ee2e 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -436,7 +436,7 @@ def get_shards_and_committees_for_slot(crystallized_state: CrystallizedState, def get_block_hash(active_state: ActiveState, current_block: BeaconBlock, slot: int) -> Hash32: - earliest_slot_in_array = curblock.slot - len(active_state.recent_block_hashes) + earliest_slot_in_array = current_block.slot - len(active_state.recent_block_hashes) assert earliest_slot_in_array <= slot < current_block.slot return active_state.recent_block_hashes[slot - earliest_slot_in_array] ```