From 8e8eed23f324fa4708d1032c7e0001e403b8c4bd Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 16 Oct 2018 13:50:31 +0100 Subject: [PATCH 01/37] Misc minor cleanups --- specs/beacon-chain.md | 69 +++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index 379e32b11..4d0b2ebed 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -44,7 +44,7 @@ The primary source of load on the beacon chain are "attestations". Attestations | `MAX_VALIDATOR_CHURN_QUOTIENT` | 2**5 (= 32) | — | | `RANDAO_SLOTS_PER_LAYER` | 2**12 (=4096) | slots | ~18 hours | | `LOGOUT_MESSAGE` | `"LOGOUT"` | — | -| `MIN_ONLINE_DEPOSIT_SIZE` | 2**4 (= 16) | ETH | +| `MIN_BALANCE` | 2**4 (= 16) | ETH | **Notes** @@ -99,7 +99,7 @@ A `BeaconBlock` has the following fields: # Recent PoW chain reference (block hash) 'pow_chain_reference': 'hash32', # Skip list of previous block hashes - # i'th item is the most recent ancestor who's slot is a multiple of 2**i for i = 0, ..., 31 + # i'th item is the most recent ancestor whose slot is a multiple of 2**i for i = 0, ..., 31 'ancestor_hashes': ['hash32'], # Active state root 'active_state_root': 'hash32', @@ -230,7 +230,7 @@ A `ValidatorRecord` has the following fields: 'randao_commitment': 'hash32', # Slot the RANDAO commitment was last changed 'randao_last_change': 'int64', - # Balance + # Balance in Gwei 'balance': 'int64', # Status code 'status': 'int8', @@ -243,7 +243,7 @@ A `CrosslinkRecord` has the following fields: ```python { - # Since last validator set change? + # Flag indicating a recent validator set change 'recently_changed': 'bool', # Slot number 'slot': 'int64', @@ -300,22 +300,16 @@ Here's an example of its working (green is finalized blocks, yellow is justified We now define the state transition function. At the high level, the state transition is made up of two parts: -1. The per-block processing, which happens every block, and affects the `ActiveState` only -2. The crystallized state recalculation, which happens only if `block.slot >= last_state_recalculation_slot + CYCLE_LENGTH`, and affects the `CrystallizedState` and `ActiveState` - +1. The per-block processing, which happens every block, and affects the `ActiveState` only. +2. The crystallized state recalculation, which happens only if `block.slot >= last_state_recalculation_slot + CYCLE_LENGTH`, and affects the `CrystallizedState` and `ActiveState`. The crystallized state recalculation generally focuses on changes to the validator set, including adjusting balances and adding and removing validators, as well as processing crosslinks and managing block justification, and the per-block processing generally focuses on verifying aggregate signatures and saving temporary records relating to the in-block activity in the `ActiveState`. ### Helper functions -We start off by defining some helper algorithms. First, the function that selects the active validators: +Below are various helper functions. -```python -def get_active_validator_indices(validators): - return [i for i, v in enumerate(validators) if v.status == ACTIVE] -``` - -Now, a function that shuffles this list: +First a function that shuffles the validator list: ```python def shuffle(values: List[Any], @@ -403,7 +397,7 @@ Now, our combined helper method: def get_new_shuffling(seed: Hash32, validators: List[ValidatorRecord], crosslinking_start_shard: int) -> List[List[ShardAndCommittee]]: - active_validators = get_active_validator_indices(validators) + active_validators = [i for i, v in enumerate(validators) if v.status == ACTIVE] active_validators_size = len(active_validators) committees_per_slot = clamp( @@ -485,7 +479,6 @@ def int_sqrt(n: int) -> int: return x ``` - ### On startup Run the following code: @@ -571,7 +564,7 @@ def add_validator(validators: List[ValidatorRecord], withdrawal_address=withdrawal_address, randao_commitment=randao_commitment, randao_last_change=current_slot, - balance=DEPOSIT_SIZE * GWEI_PER_ETH, # in Gwei + balance=DEPOSIT_SIZE * GWEI_PER_ETH, status=PENDING_ACTIVATION, exit_slot=0 ) @@ -712,7 +705,7 @@ For each `SpecialRecord` `obj` in `active_state.pending_specials`: #### Finally... -* For any validator with index `v` with balance less than `MIN_ONLINE_DEPOSIT_SIZE` and status `ACTIVE`, run `exit_validator(v, crystallized_state, penalize=False, current_slot=block.slot)` +* For any validator with index `v` with balance less than `MIN_BALANCE` and status `ACTIVE`, run `exit_validator(v, crystallized_state, penalize=False, current_slot=block.slot)` * Set `crystallized_state.last_state_recalculation_slot += CYCLE_LENGTH` * Remove all attestation records older than slot `crystallized_state.last_state_recalculation_slot` * Empty the `active_state.pending_specials` list @@ -731,7 +724,7 @@ Then, run the following algorithm to update the validator set: ```python def change_validators(validators: List[ValidatorRecord]) -> None: # The active validator set - active_validators = get_active_validator_indices(validators) + active_validators = [i for i, v in enumerate(validators) if v.status == ACTIVE] # The total balance of active validators total_balance = sum([v.balance for i, v in enumerate(validators) if i in active_validators]) # The maximum total wei that can deposit+withdraw @@ -793,41 +786,41 @@ Finally: ### TODO -Note: This spec is ~60% complete. +Note: This spec is ~65% complete. **Missing** -* [ ] Specify how `crystallized_state_root` and `active_state_root` are constructed, including Merklelisation logic for light clients -* [ ] Specify the rules around acceptable values for `pow_chain_reference` +* [ ] Specify the Merklelisation rules for beacon state and blocks and merge `crystallized_state_root` and `active_state_root` ([issue 54](https://github.com/ethereum/eth2.0-specs/issues/54)) +* [ ] Specify the rules around acceptable values for `pow_chain_reference` ([issue 58](https://github.com/ethereum/eth2.0-specs/issues/58)) * [ ] Specify the shard chain blocks, blobs, proposers, etc. -* [ ] Specify the rules for forced deregistrations -* [ ] Specify the various assumptions (global clock, networking latency, validator honesty, validator liveness, etc.) -* [ ] Specify (in a separate Vyper file) the registration contract on the PoW chain -* [ ] Specify the bootstrapping logic for the beacon chain genesis (e.g. specify a minimum number validators before the genesis block) +* [ ] Specify the deposit contract on the PoW chain in Vyper +* [ ] Specify the beacon chain genesis rules ([issue 58](https://github.com/ethereum/eth2.0-specs/issues/58)) * [ ] Specify the logic for proofs of custody, including slashing conditions -* [ ] Add an appendix about the BLS12-381 curve -* [ ] Add an appendix on gossip networks and the offchain signature aggregation logic -* [ ] Add a glossary (in a separate `glossary.md`) to comprehensively and precisely define all the terms +* [ ] Specify BLSVerify and rework the spec for BLS12-381 throughout +* [ ] Specify the constraints for `SpecialRecord`s ([issue 43](https://github.com/ethereum/eth2.0-specs/issues/43)) * [ ] Undergo peer review, security audits and formal verification -**Possible rework/additions** +**Documentation** + +* [ ] Specify the various assumptions (global clock, networking latency, validator honesty, validator liveness, etc.) +* [ ] Add an appendix on gossip networks and the offchain signature aggregation logic +* [ ] Add a glossary (in a separate `glossary.md`) to comprehensively and precisely define all the terms +* [ ] Clearly document the various edge cases, e.g. with committee sizing +* [ ] Rework the document for readability + +**Possible modifications and additions** * [ ] Replace the IMD fork choice rule with LMD -* [ ] Merklelise `crystallized_state_root` and `active_state_root` into a single root -* [ ] Replace Blake with a STARK-friendly hash function +* [ ] Homogenise types to `uint64` ([PR 36](https://github.com/ethereum/eth2.0-specs/pull/36)) * [ ] Reduce the slot duration to 8 seconds * [ ] Allow for the delayed inclusion of aggregated signatures -* [ ] Use a separate networking-optimised serialisation format for networking -* [ ] Harden RANDAO against orphaned reveals -* [ ] Introduce a RANDAO slashing condition for early leakage +* [ ] Introduce a RANDAO slashing condition for early reveals * [ ] Use a separate hash function for the proof of possession * [ ] Rework the `ShardAndCommittee` data structures * [ ] Add a double-batched Merkle accumulator for historical beacon chain blocks * [ ] Allow for deposits larger than 32 ETH, as well as deposit top-ups -* [ ] Add penalties for a deposit below 32 ETH (or some other threshold) +* [ ] Add penalties for deposits below 32 ETH (or some other threshold) * [ ] Add a `SpecialRecord` to (re)register -* [ ] Rework the document for readability -* [ ] Clearly document the various edge cases, e.g. with committee sizing # Appendix ## Appendix A - Hash function From 7945ce58d593bcdd9d1ae8a2f20eed931471f923 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 16 Oct 2018 16:22:05 -0500 Subject: [PATCH 02/37] update recently_changed flag comment --- 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 4d0b2ebed..f07cfda45 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -243,7 +243,7 @@ A `CrosslinkRecord` has the following fields: ```python { - # Flag indicating a recent validator set change + # Flag indicating if crosslink was updated since most recent validator change 'recently_changed': 'bool', # Slot number 'slot': 'int64', From bb76b05f7d781fe5a4d90833c34b0700eff7851f Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 17 Oct 2018 12:52:48 -0500 Subject: [PATCH 03/37] fix merge conflicts --- specs/beacon-chain.md | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index 48c4511bf..cc4e0b57a 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -100,13 +100,8 @@ A `BeaconBlock` has the following fields: 'randao_reveal': 'hash32', # Recent PoW chain reference (block hash) 'pow_chain_reference': 'hash32', -<<<<<<< HEAD - # Skip list of previous block hashes - # i'th item is the most recent ancestor whose slot is a multiple of 2**i for i = 0, ..., 31 -======= # Skip list of previous beacon block hashes - # i'th item is the most recent ancestor who's slot is a multiple of 2**i for i = 0, ..., 31 ->>>>>>> master + # i'th item is the most recent ancestor whose slot is a multiple of 2**i for i = 0, ..., 31 'ancestor_hashes': ['hash32'], # Active state root 'active_state_root': 'hash32', @@ -307,17 +302,10 @@ Here's an example of its working (green is finalized blocks, yellow is justified We now define the state transition function. At the high level, the state transition is made up of two parts: -<<<<<<< HEAD 1. The per-block processing, which happens every block, and affects the `ActiveState` only. 2. The crystallized state recalculation, which happens only if `block.slot >= last_state_recalculation_slot + CYCLE_LENGTH`, and affects the `CrystallizedState` and `ActiveState`. -The crystallized state recalculation generally focuses on changes to the validator set, including adjusting balances and adding and removing validators, as well as processing crosslinks and managing block justification, and the per-block processing generally focuses on verifying aggregate signatures and saving temporary records relating to the in-block activity in the `ActiveState`. -======= -1. The per-block processing, which happens every block, and affects the `ActiveState` only -2. The crystallized state recalculation, which happens only if `block.slot >= last_state_recalculation_slot + CYCLE_LENGTH`, and affects the `CrystallizedState` and `ActiveState` - The crystallized state recalculation generally focuses on changes to the validator set, including adjusting balances and adding and removing validators, as well as processing crosslinks and managing block justification/finalization, and the per-block processing generally focuses on verifying aggregate signatures and saving temporary records relating to the in-block activity in the `ActiveState`. ->>>>>>> master ### Helper functions From b4b60ae16ac6a8278d2a1ebca6d8e59c7f696b55 Mon Sep 17 00:00:00 2001 From: Vitalik Buterin Date: Thu, 18 Oct 2018 13:54:57 -0400 Subject: [PATCH 04/37] Assign validators to persistent committees for shard block production --- specs/beacon-chain.md | 43 +++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index c22b71a24..111966c9b 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -42,6 +42,7 @@ The primary source of load on the beacon chain are "attestations". Attestations | `RANDAO_SLOTS_PER_LAYER` | 2**12 (= 4096) | slots | ~18 hours | | `SQRT_E_DROP_TIME` | 2**16 (= 65,536) | slots | ~12 days | | `WITHDRAWAL_PERIOD` | 2**19 (= 524,288) | slots | ~97 days | +| `SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD` | 2**16 (= 65,536) | slots | ~12 days | | `BASE_REWARD_QUOTIENT` | 2**15 (= 32,768) | — | | `MAX_VALIDATOR_CHURN_QUOTIENT` | 2**5 (= 32) | — | | `LOGOUT_MESSAGE` | `"LOGOUT"` | — | @@ -205,6 +206,9 @@ The `CrystallizedState` has the following fields: 'justified_streak': 'uint64', # Committee members and their assigned shard, per slot 'shard_and_committee_for_slots': [[ShardAndCommittee]], + # Persistent shard committees + 'current_persistent_committees': [['uint24']], + 'next_persistent_committees': [['uint24']], # Total deposits penalized in the given withdrawal period 'deposits_penalized_in_period': ['uint32'], # Hash chain of validator set changes (for light clients to easily track deltas) @@ -442,6 +446,15 @@ Here's a diagram of what's going on: ![](http://vitalik.ca/files/ShuffleAndAssign.png?1) +We also make a function for generating persistent committees: + +```python +def get_persistent_shuffling(validators: List[ValidatorRecord], seed: Hash32): + active_validators = get_active_validator_indices(validators) + shuffled_active_validator_indices = shuffle(active_validators, seed) + return split(shuffled_active_validator_indices, SHARD_COUNT) +``` + We also define two functions for retrieving data from the state: ```python @@ -716,15 +729,6 @@ For each `SpecialRecord` `obj` in `active_state.pending_specials`: * **[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... - -* For any validator with index `v` with balance less than `MIN_ONLINE_DEPOSIT_SIZE` and status `ACTIVE`, run `exit_validator(v, crystallized_state, penalize=False, current_slot=block.slot)` -* Set `crystallized_state.last_state_recalculation_slot += CYCLE_LENGTH` -* Remove all attestation records older than slot `crystallized_state.last_state_recalculation_slot` -* 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:]` - ### Validator set change A validator set change can happen after a state recalculation if all of the following criteria are satisfied: @@ -791,13 +795,32 @@ def change_validators(validators: List[ValidatorRecord]) -> None: # STUB: withdraw to shard chain ``` -Finally: +Then: * Set `crystallized_state.validator_set_change_slot = crystallized_state.last_state_recalculation_slot` * For all `c` in `crystallized_state.crosslinks`, set `c.recently_changed = False` * Let `next_start_shard = (shard_and_committee_for_slots[-1][-1].shard + 1) % SHARD_COUNT` * Set `shard_and_committee_for_slots[CYCLE_LENGTH:] = get_new_shuffling(active_state.randao_mix, validators, next_start_shard)` +#### Finally... + +* For any validator with index `v` with balance less than `MIN_ONLINE_DEPOSIT_SIZE` and status `ACTIVE`, run `exit_validator(v, crystallized_state, penalize=False, current_slot=block.slot)` +* Set `crystallized_state.last_state_recalculation_slot += CYCLE_LENGTH` +* Remove all attestation records older than slot `crystallized_state.last_state_recalculation_slot` +* 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:]` + +For any validator that was added or removed from the active validator list during this state recalculation: + +* If the validator was removed, remove their index from the `current_persistent_committees` and `next_persistent_committees` objects. +* If the validator was added with index `validator_index`, let `assigned_shard = hash(crystallized_state.randao_mix + bytes8(validator_index)) % SHARD_COUNT`. Add `validator_index` to the end of `next_persistent_committees[assigned_shard]`. + +If `block.slot % SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD == 0`, then: + +* Set `current_persistent_committees = next_persistent_committees` +* Set `next_persistent_committees = get_persistent_shuffling(validators, crystallized_state.randao_mix)` + ### TODO Note: This spec is ~60% complete. From 3e57bd526615ea801726b6651f5dbd25312dd9f9 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Thu, 18 Oct 2018 15:47:54 -0400 Subject: [PATCH 05/37] Update beacon-chain.md --- 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 111966c9b..bcf45c68c 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -449,7 +449,7 @@ Here's a diagram of what's going on: We also make a function for generating persistent committees: ```python -def get_persistent_shuffling(validators: List[ValidatorRecord], seed: Hash32): +def get_persistent_shuffling(validators: List[ValidatorRecord], seed: Hash32) -> List[List[int]]: active_validators = get_active_validator_indices(validators) shuffled_active_validator_indices = shuffle(active_validators, seed) return split(shuffled_active_validator_indices, SHARD_COUNT) From 3e7adb3981bfab6c726fa98bd66c5b75f30407e6 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Fri, 19 Oct 2018 12:05:00 -0400 Subject: [PATCH 06/37] Moved randao_mix from crystallized to active state --- specs/beacon-chain.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index bcf45c68c..fd2b47790 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -814,12 +814,12 @@ Then: For any validator that was added or removed from the active validator list during this state recalculation: * If the validator was removed, remove their index from the `current_persistent_committees` and `next_persistent_committees` objects. -* If the validator was added with index `validator_index`, let `assigned_shard = hash(crystallized_state.randao_mix + bytes8(validator_index)) % SHARD_COUNT`. Add `validator_index` to the end of `next_persistent_committees[assigned_shard]`. +* If the validator was added with index `validator_index`, let `assigned_shard = hash(active_state.randao_mix + bytes8(validator_index)) % SHARD_COUNT`. Add `validator_index` to the end of `next_persistent_committees[assigned_shard]`. If `block.slot % SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD == 0`, then: * Set `current_persistent_committees = next_persistent_committees` -* Set `next_persistent_committees = get_persistent_shuffling(validators, crystallized_state.randao_mix)` +* Set `next_persistent_committees = get_persistent_shuffling(validators, active_state.randao_mix)` ### TODO From 8f3318813fa572a2afd6e84151c6c00fd42a2dd8 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Fri, 19 Oct 2018 12:06:25 -0400 Subject: [PATCH 07/37] Update beacon-chain.md --- specs/beacon-chain.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index fd2b47790..791d3f21d 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -795,21 +795,18 @@ def change_validators(validators: List[ValidatorRecord]) -> None: # STUB: withdraw to shard chain ``` -Then: +#### Finally... * Set `crystallized_state.validator_set_change_slot = crystallized_state.last_state_recalculation_slot` * For all `c` in `crystallized_state.crosslinks`, set `c.recently_changed = False` -* Let `next_start_shard = (shard_and_committee_for_slots[-1][-1].shard + 1) % SHARD_COUNT` -* Set `shard_and_committee_for_slots[CYCLE_LENGTH:] = get_new_shuffling(active_state.randao_mix, validators, next_start_shard)` - -#### Finally... - * For any validator with index `v` with balance less than `MIN_ONLINE_DEPOSIT_SIZE` and status `ACTIVE`, run `exit_validator(v, crystallized_state, penalize=False, current_slot=block.slot)` * Set `crystallized_state.last_state_recalculation_slot += CYCLE_LENGTH` * Remove all attestation records older than slot `crystallized_state.last_state_recalculation_slot` * 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:]` +* Let `next_start_shard = (shard_and_committee_for_slots[-1][-1].shard + 1) % SHARD_COUNT` +* Set `shard_and_committee_for_slots[CYCLE_LENGTH:] = get_new_shuffling(active_state.randao_mix, validators, next_start_shard)` For any validator that was added or removed from the active validator list during this state recalculation: From 5d36ef4799e6d0a716bfb336390b4c5c88275fa9 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Fri, 19 Oct 2018 16:01:15 -0400 Subject: [PATCH 08/37] Update beacon-chain.md --- specs/beacon-chain.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index 791d3f21d..6c4011d3b 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -818,6 +818,23 @@ If `block.slot % SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD == 0`, then: * Set `current_persistent_committees = next_persistent_committees` * Set `next_persistent_committees = get_persistent_shuffling(validators, active_state.randao_mix)` +``` +### Possible alternative (run every state recalculation): + +active_validator_indices = get_active_validator_indices(validators) +for i in range(len(active_validator_indices) // SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD): + vid = active_validator_indices[hash(active_state.randao_mix + bytes8(i * 2)) % len(active_validator_indices)] + new_shard = hash(active_state.randao_mix + bytes8(i * 2 + 1)) % SHARD_COUNT + crystallized_state.persistent_shard_reassignments.append(ShardReassignmentRecord(validator_id=vid, shard=new_shard, slot=block.slot + SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD)) + +while len(crystallized_state.persistent_shard_reassignments) > 0 and crystallized_state.persistent_shard_reassignments[0].slot <= block.slot: + rec = crystallized_state.persistent_shard_reassignments[0] + for c in crystallized_state.current_persistent_committees: + if rec.validator_id in c: + c.pop(c.index(rec.validator_id)) + crystallized_state.current_persistent_committees[rec.shard].append(vid) +``` + ### TODO Note: This spec is ~60% complete. From bdd6336ebc2cc0489fce77a1251c3eb71e4f2ddc Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Fri, 26 Oct 2018 15:22:28 +0200 Subject: [PATCH 09/37] Adds specification for {de,}serializing container types --- specs/simple-serialize.md | 108 ++++++++++++++++++++++++++++++++++---- 1 file changed, 97 insertions(+), 11 deletions(-) diff --git a/specs/simple-serialize.md b/specs/simple-serialize.md index be58a68dd..0f639b43c 100644 --- a/specs/simple-serialize.md +++ b/specs/simple-serialize.md @@ -21,7 +21,7 @@ deserializing objects and data types. * [Hash97](#hash97) - [Bytes](#bytes) - [List/Vectors](#listvectors) - - [Container (TODO)](#container) + - [Container](#container) + [Deserialize/Decode](#deserializedecode) - [uint: 8/16/24/32/64/256](#uint-816243264256-1) - [Address](#address-1) @@ -31,7 +31,7 @@ deserializing objects and data types. * [Hash97](#hash97-1) - [Bytes](#bytes-1) - [List/Vectors](#listvectors-1) - - [Container (TODO)](#container-1) + - [Container](#container-1) * [Implementations](#implementations) ## About @@ -215,12 +215,51 @@ return serialized_len + serialized_list_string #### Container -``` -######################################## - TODO -######################################## -``` +A container represents a heterogenous, associative collection of key-value pairs. Each pair is referred to as a `field`. To get the value for a given field, you supply the key which is a symbol unique to the container referred to as the field's `name`. The container data type is analogous to the `struct` type found in many languages like C or Go. +To serialize a container, obtain the set of its field's names and sort them lexicographically. For each field name in this sorted list, obtain the corresponding value and serialize it. Tightly pack the complete set of serialized values in the same order as the sorted field names into a buffer. Calculate the size of this buffer of serialized bytes and encode as a `4-byte` **big endian** `uint32`. Prepend the encoded length to the buffer. The result of this concatenation is the final serialized value of the container. + + +| Check to perform | Code | +|:--------------------------------------------|:----------------------------| +| Length of serialized fields fits into 4 bytes | ``len(serialized) < 2**32`` | + +* To serialize: + +1. Get the names of the container's fields and sort them. + +2. For each name in the sorted list, obtain the corresponding value from the container and serialize it. Place this serialized value into a buffer. The serialized values should be tightly packed. + +3. Get the number of raw bytes in the serialized buffer. Encode that number as a `4-byte` **big endian** `uint32`. + +4. Prepend the length to the serialized buffer. + +**Example in Python** + +```python +def get_field_names(typ): + return typ.fields.keys() + +def get_value_for_field_name(value, field_name): + return getattr(value, field_name) + +def get_type_for_field_name(typ, field_name): + return typ.fields[field_name] + +serialized_buffer = b'' + +typ = type(value) +for field_name in sorted(get_field_names(typ)): + field_value = get_value_for_field_name(value, field_name) + field_type = get_type_for_field_name(typ, field_name) + serialized_buffer += serialize(field_value, field_type) + +assert(len(serialized_buffer) < 2**32) + +serialized_len = (len(serialized_buffer).to_bytes(LENGTH_BYTES, 'big')) + +return serialized_len + serialized_buffer +``` ### Deserialize/Decode @@ -355,10 +394,57 @@ return deserialized_list, new_index #### Container -``` -######################################## - TODO -######################################## +Refer to the section on container encoding for some definitions. + +To deserialize a container, loop over each field in the container and use the type of that field to know what kind of deserialization to perform. Consume successive elements of the data stream for each successful deserialization. + +Instantiate a container with the full set of deserialized data, matching each member with the corresponding field. + +| Check to perform | code | +|:------------------------------------------|:----------------------------------------------------------------| +| rawbytes has enough left for length | ``len(rawbytes) > current_index + LENGTH_BYTES`` | +| list is not greater than serialized bytes | ``len(rawbytes) > current_index + LENGTH_BYTES + total_length`` | + +* To deserialize: + +1. Get the names of the container's fields and sort them. + +2. For each name in the sorted list, attempt to deserialize a value for that type. Collect these values as they will be used to construct an instance of the container. + +3. Construct a container instance after successfully consuming the entire subset of the stream for the serialized container. + +**Example in Python** + +```python +def get_field_names(typ): + return typ.fields.keys() + +def get_value_for_field_name(value, field_name): + return getattr(value, field_name) + +def get_type_for_field_name(typ, field_name): + return typ.fields[field_name] + +class Container: + # this is the container; here we will define an empty class for demonstration + pass + +# get a reference to the type in some way... +container = Container() +typ = type(container) + +assert(len(rawbytes) > current_index + LENGTH_BYTES) +total_length = int.from_bytes(rawbytes[current_index:current_index + LENGTH_BYTES], 'big') +new_index = current_index + LENGTH_BYTES + total_length +assert(len(rawbytes) >= new_index) +item_index = current_index + LENGTH_BYTES + +values = {} +for field_name in sorted(get_field_names(typ)): + field_name_type = get_type_for_field_name(typ, field_name) + values[field_name], item_index = deserialize_at(data, field_name_type, item_index) +assert item_index == start + LENGTH_BYTES + length +return typ(**values), item_index ``` ## Implementations From a585653fe5bc792faaadaff624deaa6615af6c98 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Fri, 26 Oct 2018 15:25:35 +0200 Subject: [PATCH 10/37] formatting --- specs/simple-serialize.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/simple-serialize.md b/specs/simple-serialize.md index 0f639b43c..8ead4baa9 100644 --- a/specs/simple-serialize.md +++ b/specs/simple-serialize.md @@ -224,7 +224,7 @@ To serialize a container, obtain the set of its field's names and sort them lexi |:--------------------------------------------|:----------------------------| | Length of serialized fields fits into 4 bytes | ``len(serialized) < 2**32`` | -* To serialize: +To serialize: 1. Get the names of the container's fields and sort them. @@ -405,7 +405,7 @@ Instantiate a container with the full set of deserialized data, matching each me | rawbytes has enough left for length | ``len(rawbytes) > current_index + LENGTH_BYTES`` | | list is not greater than serialized bytes | ``len(rawbytes) > current_index + LENGTH_BYTES + total_length`` | -* To deserialize: +To deserialize: 1. Get the names of the container's fields and sort them. From 0064043e141efa39471a011c49aa3614a753012e Mon Sep 17 00:00:00 2001 From: mratsim Date: Sat, 27 Oct 2018 13:36:10 +0200 Subject: [PATCH 11/37] Mention that lists are of elements of homegeneous type --- specs/simple-serialize.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/specs/simple-serialize.md b/specs/simple-serialize.md index be58a68dd..f508e8298 100644 --- a/specs/simple-serialize.md +++ b/specs/simple-serialize.md @@ -186,6 +186,8 @@ return byte_length + value #### List/Vectors +Lists are a collection of elements of the same homogeneous type. + | Check to perform | Code | |:--------------------------------------------|:----------------------------| | Length of serialized list fits into 4 bytes | ``len(serialized) < 2**32`` | @@ -327,7 +329,7 @@ return rawbytes[bytes_start:bytes_end], new_index #### List/Vectors -Deserialize each object in the list. +Deserialize each element in the list. 1. Get the length of the serialized list. 2. Loop through deserializing each item in the list until you reach the entire length of the list. From 1276f1334be098c4d4414b35928ced3a46b5257d Mon Sep 17 00:00:00 2001 From: vbuterin Date: Tue, 30 Oct 2018 04:45:28 -0400 Subject: [PATCH 12/37] Update beacon-chain.md --- specs/beacon-chain.md | 45 +++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index 6c4011d3b..2e1b74eca 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -207,8 +207,8 @@ The `CrystallizedState` has the following fields: # Committee members and their assigned shard, per slot 'shard_and_committee_for_slots': [[ShardAndCommittee]], # Persistent shard committees - 'current_persistent_committees': [['uint24']], - 'next_persistent_committees': [['uint24']], + 'persistent_committees': [['uint24']], + 'shard_reassignment_records': [ShardReassignmentRecord], # Total deposits penalized in the given withdrawal period 'deposits_penalized_in_period': ['uint32'], # Hash chain of validator set changes (for light clients to easily track deltas) @@ -268,6 +268,19 @@ A `ShardAndCommittee` object has the following fields: } ``` +A `ShardReassignmentRecord` object has the following fields: + +```python +{ + # Which validator to reassign + 'validator_id': 'uint64', + # To which shard + 'shard': 'uint16', + # When + 'slot': 'uint64' +} +``` + ## Beacon chain processing The beacon chain is the "main chain" of the PoS system. The beacon chain's main responsibilities are: @@ -446,15 +459,6 @@ Here's a diagram of what's going on: ![](http://vitalik.ca/files/ShuffleAndAssign.png?1) -We also make a function for generating persistent committees: - -```python -def get_persistent_shuffling(validators: List[ValidatorRecord], seed: Hash32) -> List[List[int]]: - active_validators = get_active_validator_indices(validators) - shuffled_active_validator_indices = shuffle(active_validators, seed) - return split(shuffled_active_validator_indices, SHARD_COUNT) -``` - We also define two functions for retrieving data from the state: ```python @@ -486,6 +490,8 @@ def add_validator_set_change_record(crystallized_state: CrystallizedState, bytes1(flag) + bytes3(index) + bytes32(pubkey)) ``` +We define another set of helpers: `bytes1(x): return x.to_bytes(1, 'big')`, `bytes2(x): return x.to_bytes(2, 'big')`, and so on for all integers, particularly 1, 2, 3, 4, 8, 32. + 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. ```python @@ -537,6 +543,8 @@ def on_startup(initial_validator_entries: List[Any]) -> Tuple[CrystallizedState, last_justified_slot=0, justified_streak=0, shard_and_committee_for_slots=x + x, + persistent_shuffling=split(shuffle(validators, b'x\00'*32), SHARD_COUNT), + shard_reassignment_records=[], deposits_penalized_in_period=[], validator_set_delta_hash_chain=bytes([0] * 32), # stub pre_fork_version=INITIAL_FORK_VERSION, @@ -810,17 +818,12 @@ def change_validators(validators: List[ValidatorRecord]) -> None: For any validator that was added or removed from the active validator list during this state recalculation: -* If the validator was removed, remove their index from the `current_persistent_committees` and `next_persistent_committees` objects. +* If the validator was removed, remove their index from the `persistent_committees` and `next_persistent_committees` objects. * If the validator was added with index `validator_index`, let `assigned_shard = hash(active_state.randao_mix + bytes8(validator_index)) % SHARD_COUNT`. Add `validator_index` to the end of `next_persistent_committees[assigned_shard]`. -If `block.slot % SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD == 0`, then: - -* Set `current_persistent_committees = next_persistent_committees` -* Set `next_persistent_committees = get_persistent_shuffling(validators, active_state.randao_mix)` - -``` -### Possible alternative (run every state recalculation): +Now run the following code to reshuffle a few proposers: +```python active_validator_indices = get_active_validator_indices(validators) for i in range(len(active_validator_indices) // SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD): vid = active_validator_indices[hash(active_state.randao_mix + bytes8(i * 2)) % len(active_validator_indices)] @@ -829,10 +832,10 @@ for i in range(len(active_validator_indices) // SHARD_PERSISTENT_COMMITTEE_CHANG while len(crystallized_state.persistent_shard_reassignments) > 0 and crystallized_state.persistent_shard_reassignments[0].slot <= block.slot: rec = crystallized_state.persistent_shard_reassignments[0] - for c in crystallized_state.current_persistent_committees: + for c in crystallized_state.persistent_committees: if rec.validator_id in c: c.pop(c.index(rec.validator_id)) - crystallized_state.current_persistent_committees[rec.shard].append(vid) + crystallized_state.persistent_committees[rec.shard].append(vid) ``` ### TODO From 0df4141f051ba3401771ac2bc685ea5aa7fac20c Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 1 Nov 2018 08:08:23 +0100 Subject: [PATCH 13/37] make bytes more uniform --- 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 2e1b74eca..187e8e5f7 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -543,7 +543,7 @@ def on_startup(initial_validator_entries: List[Any]) -> Tuple[CrystallizedState, last_justified_slot=0, justified_streak=0, shard_and_committee_for_slots=x + x, - persistent_shuffling=split(shuffle(validators, b'x\00'*32), SHARD_COUNT), + persistent_shuffling=split(shuffle(validators, bytes([0] * 32)), SHARD_COUNT), shard_reassignment_records=[], deposits_penalized_in_period=[], validator_set_delta_hash_chain=bytes([0] * 32), # stub From cf70ed0b7c42b626f82057998856be17614b03e5 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Fri, 26 Oct 2018 15:22:28 +0200 Subject: [PATCH 14/37] Adds specification for {de,}serializing container types --- specs/simple-serialize.md | 108 ++++++++++++++++++++++++++++++++++---- 1 file changed, 97 insertions(+), 11 deletions(-) diff --git a/specs/simple-serialize.md b/specs/simple-serialize.md index f508e8298..fa53d9342 100644 --- a/specs/simple-serialize.md +++ b/specs/simple-serialize.md @@ -21,7 +21,7 @@ deserializing objects and data types. * [Hash97](#hash97) - [Bytes](#bytes) - [List/Vectors](#listvectors) - - [Container (TODO)](#container) + - [Container](#container) + [Deserialize/Decode](#deserializedecode) - [uint: 8/16/24/32/64/256](#uint-816243264256-1) - [Address](#address-1) @@ -31,7 +31,7 @@ deserializing objects and data types. * [Hash97](#hash97-1) - [Bytes](#bytes-1) - [List/Vectors](#listvectors-1) - - [Container (TODO)](#container-1) + - [Container](#container-1) * [Implementations](#implementations) ## About @@ -217,12 +217,51 @@ return serialized_len + serialized_list_string #### Container -``` -######################################## - TODO -######################################## -``` +A container represents a heterogenous, associative collection of key-value pairs. Each pair is referred to as a `field`. To get the value for a given field, you supply the key which is a symbol unique to the container referred to as the field's `name`. The container data type is analogous to the `struct` type found in many languages like C or Go. +To serialize a container, obtain the set of its field's names and sort them lexicographically. For each field name in this sorted list, obtain the corresponding value and serialize it. Tightly pack the complete set of serialized values in the same order as the sorted field names into a buffer. Calculate the size of this buffer of serialized bytes and encode as a `4-byte` **big endian** `uint32`. Prepend the encoded length to the buffer. The result of this concatenation is the final serialized value of the container. + + +| Check to perform | Code | +|:--------------------------------------------|:----------------------------| +| Length of serialized fields fits into 4 bytes | ``len(serialized) < 2**32`` | + +* To serialize: + +1. Get the names of the container's fields and sort them. + +2. For each name in the sorted list, obtain the corresponding value from the container and serialize it. Place this serialized value into a buffer. The serialized values should be tightly packed. + +3. Get the number of raw bytes in the serialized buffer. Encode that number as a `4-byte` **big endian** `uint32`. + +4. Prepend the length to the serialized buffer. + +**Example in Python** + +```python +def get_field_names(typ): + return typ.fields.keys() + +def get_value_for_field_name(value, field_name): + return getattr(value, field_name) + +def get_type_for_field_name(typ, field_name): + return typ.fields[field_name] + +serialized_buffer = b'' + +typ = type(value) +for field_name in sorted(get_field_names(typ)): + field_value = get_value_for_field_name(value, field_name) + field_type = get_type_for_field_name(typ, field_name) + serialized_buffer += serialize(field_value, field_type) + +assert(len(serialized_buffer) < 2**32) + +serialized_len = (len(serialized_buffer).to_bytes(LENGTH_BYTES, 'big')) + +return serialized_len + serialized_buffer +``` ### Deserialize/Decode @@ -357,10 +396,57 @@ return deserialized_list, new_index #### Container -``` -######################################## - TODO -######################################## +Refer to the section on container encoding for some definitions. + +To deserialize a container, loop over each field in the container and use the type of that field to know what kind of deserialization to perform. Consume successive elements of the data stream for each successful deserialization. + +Instantiate a container with the full set of deserialized data, matching each member with the corresponding field. + +| Check to perform | code | +|:------------------------------------------|:----------------------------------------------------------------| +| rawbytes has enough left for length | ``len(rawbytes) > current_index + LENGTH_BYTES`` | +| list is not greater than serialized bytes | ``len(rawbytes) > current_index + LENGTH_BYTES + total_length`` | + +* To deserialize: + +1. Get the names of the container's fields and sort them. + +2. For each name in the sorted list, attempt to deserialize a value for that type. Collect these values as they will be used to construct an instance of the container. + +3. Construct a container instance after successfully consuming the entire subset of the stream for the serialized container. + +**Example in Python** + +```python +def get_field_names(typ): + return typ.fields.keys() + +def get_value_for_field_name(value, field_name): + return getattr(value, field_name) + +def get_type_for_field_name(typ, field_name): + return typ.fields[field_name] + +class Container: + # this is the container; here we will define an empty class for demonstration + pass + +# get a reference to the type in some way... +container = Container() +typ = type(container) + +assert(len(rawbytes) > current_index + LENGTH_BYTES) +total_length = int.from_bytes(rawbytes[current_index:current_index + LENGTH_BYTES], 'big') +new_index = current_index + LENGTH_BYTES + total_length +assert(len(rawbytes) >= new_index) +item_index = current_index + LENGTH_BYTES + +values = {} +for field_name in sorted(get_field_names(typ)): + field_name_type = get_type_for_field_name(typ, field_name) + values[field_name], item_index = deserialize_at(data, field_name_type, item_index) +assert item_index == start + LENGTH_BYTES + length +return typ(**values), item_index ``` ## Implementations From 56066260ad5a68309863f0b3e4f4194899d901a7 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Fri, 26 Oct 2018 15:25:35 +0200 Subject: [PATCH 15/37] formatting --- specs/simple-serialize.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/simple-serialize.md b/specs/simple-serialize.md index fa53d9342..4c75032d5 100644 --- a/specs/simple-serialize.md +++ b/specs/simple-serialize.md @@ -226,7 +226,7 @@ To serialize a container, obtain the set of its field's names and sort them lexi |:--------------------------------------------|:----------------------------| | Length of serialized fields fits into 4 bytes | ``len(serialized) < 2**32`` | -* To serialize: +To serialize: 1. Get the names of the container's fields and sort them. @@ -407,7 +407,7 @@ Instantiate a container with the full set of deserialized data, matching each me | rawbytes has enough left for length | ``len(rawbytes) > current_index + LENGTH_BYTES`` | | list is not greater than serialized bytes | ``len(rawbytes) > current_index + LENGTH_BYTES + total_length`` | -* To deserialize: +To deserialize: 1. Get the names of the container's fields and sort them. From 9cf4f0c86e0d2471ae864bd7c3c7d51f3d04536a Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Sun, 4 Nov 2018 19:15:17 +0300 Subject: [PATCH 16/37] remove unused variable --- specs/beacon-chain.md | 1 - 1 file changed, 1 deletion(-) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index 6c967a8e4..a3fa31a91 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -404,7 +404,6 @@ def get_new_shuffling(seed: Hash32, validators: List[ValidatorRecord], crosslinking_start_shard: int) -> List[List[ShardAndCommittee]]: active_validators = get_active_validator_indices(validators) - active_validators_size = len(active_validators) committees_per_slot = clamp( 1, From 83c624d0e8af7276c4310b6bcf32ed135db1ac6a Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sun, 4 Nov 2018 23:26:24 +0100 Subject: [PATCH 17/37] add back in get_active_validator_indices --- specs/beacon-chain.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index 676ea0600..fb1c12491 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -311,7 +311,10 @@ The crystallized state recalculation generally focuses on changes to the validat Below are various helper functions. -First a function that shuffles the validator list: +def get_active_validator_indices(validators) + return [i for i, v in enumerate(validators) if v.status == ACTIVE] + +The following is a function that shuffles the validator list: ```python def shuffle(values: List[Any], @@ -399,7 +402,7 @@ Now, our combined helper method: def get_new_shuffling(seed: Hash32, validators: List[ValidatorRecord], crosslinking_start_shard: int) -> List[List[ShardAndCommittee]]: - active_validators = [i for i, v in enumerate(validators) if v.status == ACTIVE] + active_validators = get_active_validator_indices(validators) active_validators_size = len(active_validators) committees_per_slot = clamp( @@ -747,7 +750,7 @@ Then, run the following algorithm to update the validator set: ```python def change_validators(validators: List[ValidatorRecord]) -> None: # The active validator set - active_validators = [i for i, v in enumerate(validators) if v.status == ACTIVE] + active_validators = get_active_validator_indices(validators) # The total balance of active validators total_balance = sum([v.balance for i, v in enumerate(validators) if i in active_validators]) # The maximum total wei that can deposit+withdraw From d44eb8ecccb7383da2d18a073c29ff64eade626b Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sun, 4 Nov 2018 23:27:08 +0100 Subject: [PATCH 18/37] remove unnecessary comment --- 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 fb1c12491..5ff485dc1 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -583,7 +583,7 @@ def add_validator(validators: List[ValidatorRecord], withdrawal_address=withdrawal_address, randao_commitment=randao_commitment, randao_last_change=current_slot, - balance=DEPOSIT_SIZE * GWEI_PER_ETH, # in Gwei + balance=DEPOSIT_SIZE * GWEI_PER_ETH, status=status, exit_slot=0 ) From cc910d831a680c2247872ec7921d87bdaa300ff8 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 5 Nov 2018 13:38:25 +0100 Subject: [PATCH 19/37] cleanup shard reassignment logic --- specs/beacon-chain.md | 55 ++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index 187e8e5f7..bddf2ab57 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -208,7 +208,7 @@ The `CrystallizedState` has the following fields: 'shard_and_committee_for_slots': [[ShardAndCommittee]], # Persistent shard committees 'persistent_committees': [['uint24']], - 'shard_reassignment_records': [ShardReassignmentRecord], + 'persistent_shard_reassignments': [ShardReassignmentRecord], # Total deposits penalized in the given withdrawal period 'deposits_penalized_in_period': ['uint32'], # Hash chain of validator set changes (for light clients to easily track deltas) @@ -273,7 +273,7 @@ A `ShardReassignmentRecord` object has the following fields: ```python { # Which validator to reassign - 'validator_id': 'uint64', + 'validator_index': 'uint64', # To which shard 'shard': 'uint16', # When @@ -478,6 +478,8 @@ def get_block_hash(active_state: ActiveState, `get_block_hash(_, _, s)` should always return the block in the beacon chain at slot `s`, and `get_shards_and_committees_for_slot(_, s)` should not change unless the validator set changes. +We define another set of helpers to be used throughout: `bytes1(x): return x.to_bytes(1, 'big')`, `bytes2(x): return x.to_bytes(2, 'big')`, and so on for all integers, particularly 1, 2, 3, 4, 8, 32. + We define a function to "add a link" to the validator hash chain, used when a validator is added or removed: ```python @@ -490,8 +492,6 @@ def add_validator_set_change_record(crystallized_state: CrystallizedState, bytes1(flag) + bytes3(index) + bytes32(pubkey)) ``` -We define another set of helpers: `bytes1(x): return x.to_bytes(1, 'big')`, `bytes2(x): return x.to_bytes(2, 'big')`, and so on for all integers, particularly 1, 2, 3, 4, 8, 32. - 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. ```python @@ -803,38 +803,51 @@ def change_validators(validators: List[ValidatorRecord]) -> None: # STUB: withdraw to shard chain ``` -#### Finally... - -* Set `crystallized_state.validator_set_change_slot = crystallized_state.last_state_recalculation_slot` * For all `c` in `crystallized_state.crosslinks`, set `c.recently_changed = False` -* For any validator with index `v` with balance less than `MIN_ONLINE_DEPOSIT_SIZE` and status `ACTIVE`, run `exit_validator(v, crystallized_state, penalize=False, current_slot=block.slot)` -* Set `crystallized_state.last_state_recalculation_slot += CYCLE_LENGTH` -* Remove all attestation records older than slot `crystallized_state.last_state_recalculation_slot` -* 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:]` * Let `next_start_shard = (shard_and_committee_for_slots[-1][-1].shard + 1) % SHARD_COUNT` * Set `shard_and_committee_for_slots[CYCLE_LENGTH:] = get_new_shuffling(active_state.randao_mix, validators, next_start_shard)` +#### Finally... + +* Remove all attestation records older than slot `crystallized_state.last_state_recalculation_slot` +* Set `crystallized_state.validator_set_change_slot = crystallized_state.last_state_recalculation_slot` +* For any validator with index `v` with balance less than `MIN_ONLINE_DEPOSIT_SIZE` and status `ACTIVE`, run `exit_validator(v, crystallized_state, penalize=False, current_slot=block.slot)` +* Set `crystallized_state.last_state_recalculation_slot += CYCLE_LENGTH` +* Empty the `active_state.pending_specials` list +* Set `active_state.recent_block_hashes = active_state.recent_block_hashes[CYCLE_LENGTH:]` +* If a validator set change did _not_ occur during this state recalculation, set `shard_and_committee_for_slots[:CYCLE_LENGTH] = shard_and_committee_for_slots[CYCLE_LENGTH:]` + For any validator that was added or removed from the active validator list during this state recalculation: -* If the validator was removed, remove their index from the `persistent_committees` and `next_persistent_committees` objects. -* If the validator was added with index `validator_index`, let `assigned_shard = hash(active_state.randao_mix + bytes8(validator_index)) % SHARD_COUNT`. Add `validator_index` to the end of `next_persistent_committees[assigned_shard]`. +* If the validator was removed, remove their index from the `persistent_committees` and remove any `ShardReassignmentRecord`s containing their index from `persistent_shard_reassignments`. +* If the validator was added with index `validator_index`: + * let `assigned_shard = hash(active_state.randao_mix + bytes8(validator_index)) % SHARD_COUNT` + * let `reassignment_record = ShardReassignmentRecord(validator_index=validator_index, shard=assigned_shard, slot=block.slot + SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD)` + * Append `reassignment_record` to the end of `persistent_shard_reassignments` Now run the following code to reshuffle a few proposers: ```python active_validator_indices = get_active_validator_indices(validators) -for i in range(len(active_validator_indices) // SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD): +num_validators_to_reshuffle = len(active_validator_indices) // SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD +for i in range(num_validators_to_reshuffle): vid = active_validator_indices[hash(active_state.randao_mix + bytes8(i * 2)) % len(active_validator_indices)] new_shard = hash(active_state.randao_mix + bytes8(i * 2 + 1)) % SHARD_COUNT - crystallized_state.persistent_shard_reassignments.append(ShardReassignmentRecord(validator_id=vid, shard=new_shard, slot=block.slot + SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD)) - + shard_reassignment_record = ShardReassignmentRecord( + validator_index=vid, + shard=new_shard, + slot=block.slot + SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD + ) + crystallized_state.persistent_shard_reassignments.append(shard_reassignment_record) + while len(crystallized_state.persistent_shard_reassignments) > 0 and crystallized_state.persistent_shard_reassignments[0].slot <= block.slot: - rec = crystallized_state.persistent_shard_reassignments[0] - for c in crystallized_state.persistent_committees: - if rec.validator_id in c: - c.pop(c.index(rec.validator_id)) + rec = crystallized_state.persistent_shard_reassignments.pop(0) + for committee in crystallized_state.persistent_committees: + if rec.validator_index in committee: + committee.pop( + committee.index(rec.validator_index) + ) crystallized_state.persistent_committees[rec.shard].append(vid) ``` From 236ac268673b609a2560cd4bfc9bcfce8eab9631 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 5 Nov 2018 13:41:03 +0100 Subject: [PATCH 20/37] rename var --- specs/beacon-chain.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index bddf2ab57..88245f159 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -208,7 +208,7 @@ The `CrystallizedState` has the following fields: 'shard_and_committee_for_slots': [[ShardAndCommittee]], # Persistent shard committees 'persistent_committees': [['uint24']], - 'persistent_shard_reassignments': [ShardReassignmentRecord], + 'persistent_committee_reassignments': [ShardReassignmentRecord], # Total deposits penalized in the given withdrawal period 'deposits_penalized_in_period': ['uint32'], # Hash chain of validator set changes (for light clients to easily track deltas) @@ -820,11 +820,11 @@ def change_validators(validators: List[ValidatorRecord]) -> None: For any validator that was added or removed from the active validator list during this state recalculation: -* If the validator was removed, remove their index from the `persistent_committees` and remove any `ShardReassignmentRecord`s containing their index from `persistent_shard_reassignments`. +* If the validator was removed, remove their index from the `persistent_committees` and remove any `ShardReassignmentRecord`s containing their index from `persistent_committee_reassignments`. * If the validator was added with index `validator_index`: * let `assigned_shard = hash(active_state.randao_mix + bytes8(validator_index)) % SHARD_COUNT` * let `reassignment_record = ShardReassignmentRecord(validator_index=validator_index, shard=assigned_shard, slot=block.slot + SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD)` - * Append `reassignment_record` to the end of `persistent_shard_reassignments` + * Append `reassignment_record` to the end of `persistent_committee_reassignments` Now run the following code to reshuffle a few proposers: @@ -839,10 +839,10 @@ for i in range(num_validators_to_reshuffle): shard=new_shard, slot=block.slot + SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD ) - crystallized_state.persistent_shard_reassignments.append(shard_reassignment_record) + crystallized_state.persistent_committee_reassignments.append(shard_reassignment_record) -while len(crystallized_state.persistent_shard_reassignments) > 0 and crystallized_state.persistent_shard_reassignments[0].slot <= block.slot: - rec = crystallized_state.persistent_shard_reassignments.pop(0) +while len(crystallized_state.persistent_committee_reassignments) > 0 and crystallized_state.persistent_committee_reassignments[0].slot <= block.slot: + rec = crystallized_state.persistent_committee_reassignments.pop(0) for committee in crystallized_state.persistent_committees: if rec.validator_index in committee: committee.pop( From 189d44f5a5e06608953ef4c0d41dd55b2f263623 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 5 Nov 2018 13:45:08 +0100 Subject: [PATCH 21/37] move validator_set_last_changed to the validator set change section --- specs/beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index 88245f159..a53771a04 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -803,6 +803,7 @@ def change_validators(validators: List[ValidatorRecord]) -> None: # STUB: withdraw to shard chain ``` +* Set `crystallized_state.validator_set_change_slot = crystallized_state.last_state_recalculation_slot` * For all `c` in `crystallized_state.crosslinks`, set `c.recently_changed = False` * Set `shard_and_committee_for_slots[:CYCLE_LENGTH] = shard_and_committee_for_slots[CYCLE_LENGTH:]` * Let `next_start_shard = (shard_and_committee_for_slots[-1][-1].shard + 1) % SHARD_COUNT` @@ -811,12 +812,11 @@ def change_validators(validators: List[ValidatorRecord]) -> None: #### Finally... * Remove all attestation records older than slot `crystallized_state.last_state_recalculation_slot` -* Set `crystallized_state.validator_set_change_slot = crystallized_state.last_state_recalculation_slot` -* For any validator with index `v` with balance less than `MIN_ONLINE_DEPOSIT_SIZE` and status `ACTIVE`, run `exit_validator(v, crystallized_state, penalize=False, current_slot=block.slot)` -* Set `crystallized_state.last_state_recalculation_slot += CYCLE_LENGTH` * Empty the `active_state.pending_specials` list +* For any validator with index `v` with balance less than `MIN_ONLINE_DEPOSIT_SIZE` and status `ACTIVE`, run `exit_validator(v, crystallized_state, penalize=False, current_slot=block.slot)` * Set `active_state.recent_block_hashes = active_state.recent_block_hashes[CYCLE_LENGTH:]` * If a validator set change did _not_ occur during this state recalculation, set `shard_and_committee_for_slots[:CYCLE_LENGTH] = shard_and_committee_for_slots[CYCLE_LENGTH:]` +* Set `crystallized_state.last_state_recalculation_slot += CYCLE_LENGTH` For any validator that was added or removed from the active validator list during this state recalculation: From 1d817608fbecdd5f46b031744c2c3cdf7d6e9702 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 5 Nov 2018 14:04:40 +0100 Subject: [PATCH 22/37] update deserialize arg ordering in container type --- specs/simple-serialize.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/simple-serialize.md b/specs/simple-serialize.md index 4c75032d5..03af18621 100644 --- a/specs/simple-serialize.md +++ b/specs/simple-serialize.md @@ -444,7 +444,7 @@ item_index = current_index + LENGTH_BYTES values = {} for field_name in sorted(get_field_names(typ)): field_name_type = get_type_for_field_name(typ, field_name) - values[field_name], item_index = deserialize_at(data, field_name_type, item_index) + values[field_name], item_index = deserialize(data, item_index, field_name_type) assert item_index == start + LENGTH_BYTES + length return typ(**values), item_index ``` From 9ee65b2f22b6a0e16e572823c8ea95b83a5adcff Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 6 Nov 2018 09:02:55 +0100 Subject: [PATCH 23/37] add gitter badge to readme (#114) * add gitter badge to readme * fix gitter badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f05061c82..2d5a598fb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ # Ethereum 2.0 Specifications +[![Join the chat at https://gitter.im/ethereum/sharding](https://badges.gitter.im/ethereum/sharding.svg)](https://gitter.im/ethereum/sharding?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + This repo hosts the current eth2.0 specifications. Discussions about design rationale and proposed changes can be brought up and discussed as issues. Solidified, agreed upon changes to spec can be made through pull requests. From a6cf5c4da1fa8f9eb23b77005d7cee859a6f5583 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Tue, 6 Nov 2018 10:43:21 +0100 Subject: [PATCH 24/37] typos from persistent shard committee changes (#113) --- specs/beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index d1b7a81b6..fc8ab40c7 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -274,7 +274,7 @@ A `ShardReassignmentRecord` object has the following fields: ```python { # Which validator to reassign - 'validator_index': 'uint64', + 'validator_index': 'uint24', # To which shard 'shard': 'uint16', # When @@ -541,8 +541,8 @@ def on_startup(initial_validator_entries: List[Any]) -> Tuple[CrystallizedState, last_justified_slot=0, justified_streak=0, shard_and_committee_for_slots=x + x, - persistent_shuffling=split(shuffle(validators, bytes([0] * 32)), SHARD_COUNT), - shard_reassignment_records=[], + persistent_committees=split(shuffle(validators, bytes([0] * 32)), SHARD_COUNT), + persistent_committee_reassignments=[], deposits_penalized_in_period=[], validator_set_delta_hash_chain=bytes([0] * 32), # stub pre_fork_version=INITIAL_FORK_VERSION, From b8d85ef8bdb706d0c870a0aef98d580ce0440fa6 Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Tue, 6 Nov 2018 14:26:29 -0800 Subject: [PATCH 25/37] get_block_hash gets the block hash, not the block (#117) --- 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 fc8ab40c7..3adf2c7e0 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -474,7 +474,7 @@ def get_block_hash(active_state: ActiveState, return active_state.recent_block_hashes[slot - earliest_slot_in_array] ``` -`get_block_hash(_, _, s)` should always return the block in the beacon chain at slot `s`, and `get_shards_and_committees_for_slot(_, s)` should not change unless the validator set changes. +`get_block_hash(_, _, s)` should always return the block hash in the beacon chain at slot `s`, and `get_shards_and_committees_for_slot(_, s)` should not change unless the validator set changes. We define another set of helpers to be used throughout: `bytes1(x): return x.to_bytes(1, 'big')`, `bytes2(x): return x.to_bytes(2, 'big')`, and so on for all integers, particularly 1, 2, 3, 4, 8, 32. From 024ba75fb3b783f4048ed868b6c5edfb9c06130f Mon Sep 17 00:00:00 2001 From: vbuterin Date: Wed, 7 Nov 2018 17:47:27 +0100 Subject: [PATCH 26/37] Changed crosslink mechanics (#111) * Changed crosslink mechanics * Update beacon-chain.md * make start_shard clearer --- specs/beacon-chain.md | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index 3adf2c7e0..9ebbe2b26 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -210,6 +210,8 @@ The `CrystallizedState` has the following fields: # Persistent shard committees 'persistent_committees': [['uint24']], 'persistent_committee_reassignments': [ShardReassignmentRecord], + # Randao seed used for next shuffling + 'next_shuffling_seed': 'hash32', # Total deposits penalized in the given withdrawal period 'deposits_penalized_in_period': ['uint32'], # Hash chain of validator set changes (for light clients to easily track deltas) @@ -249,8 +251,6 @@ A `CrosslinkRecord` has the following fields: ```python { - # Flag indicating if crosslink was updated since most recent validator change - 'recently_changed': 'bool', # Slot number 'slot': 'uint64', # Shard chain block hash @@ -526,7 +526,6 @@ def on_startup(initial_validator_entries: List[Any]) -> Tuple[CrystallizedState, x = get_new_shuffling(bytes([0] * 32), validators, 0) crosslinks = [ CrosslinkRecord( - recently_changed=False, slot=0, hash=bytes([0] * 32) ) @@ -544,6 +543,7 @@ def on_startup(initial_validator_entries: List[Any]) -> Tuple[CrystallizedState, persistent_committees=split(shuffle(validators, bytes([0] * 32)), SHARD_COUNT), persistent_committee_reassignments=[], deposits_penalized_in_period=[], + next_shuffling_seed=b'\x00'*32, validator_set_delta_hash_chain=bytes([0] * 32), # stub pre_fork_version=INITIAL_FORK_VERSION, post_fork_version=INITIAL_FORK_VERSION, @@ -702,7 +702,7 @@ For every `(shard, shard_block_hash)` tuple: * Let `total_balance_attesting_to_h` be the total balance of validators that attested to the shard block with hash `shard_block_hash`. * Let `total_committee_balance` be the total balance in the committee of validators that could have attested to the shard block with hash `shard_block_hash`. -* If `3 * total_balance_attesting_to_h >= 2 * total_committee_balance` and `recently_changed is False`, set `crosslinks[shard] = CrosslinkRecord(recently_changed=True, slot=last_state_recalculation_slot + CYCLE_LENGTH, hash=shard_block_hash)`. +* If `3 * total_balance_attesting_to_h >= 2 * total_committee_balance`, set `crosslinks[shard] = CrosslinkRecord(slot=last_state_recalculation_slot + CYCLE_LENGTH, hash=shard_block_hash)`. #### Balance recalculations related to FFG rewards @@ -732,13 +732,13 @@ In addition, validators with `status == PENALIZED` lose `B // reward_quotient + For every shard number `shard` for which a crosslink committee exists in the cycle prior to the most recent cycle (`last_state_recalculation_slot - CYCLE_LENGTH ... last_state_recalculation_slot - 1`), let `V` be the corresponding validator set. Let `B` be the balance of any given validator whose balance we are adjusting, not including any balance changes from this round of state recalculation. For each `shard`, `V`: * Let `total_balance_of_v` be the total balance of `V`. +* Let `winning_shard_hash` be the hash that the largest total deposits signed for the `shard` during the cycle. +* Define a "participating validator" as a member of `V` that signed a crosslink of `winning_shard_hash`. * Let `total_balance_of_v_participating` be the total balance of the subset of `V` that participated. * Let `time_since_last_confirmation = block.slot - crosslinks[shard].slot`. -* If `recently_changed is False`, adjust balances as follows: +* Adjust balances as follows: * Participating validators gain `B // reward_quotient * (2 * total_balance_of_v_participating - total_balance_of_v) // total_balance_of_v`. - * Non-participating validators lose `B // reward_quotient + B * time_since_last_confirmation // quadratic_penalty_quotient`. - -In addition, validators with `status == PENALIZED` lose `B // reward_quotient + B * sum([time_since_last_confirmation(c) for c in committees]) // len(committees) // quadratic_penalty_quotient`, where `committees` is the set of committees processed and `time_since_last_confirmation(c)` is the value of `time_since_last_confirmation` in committee `c`. + * Non-participating validators lose `B // reward_quotient`. #### Process penalties, logouts and other special objects @@ -815,10 +815,17 @@ def change_validators(validators: List[ValidatorRecord]) -> None: ``` * Set `crystallized_state.validator_set_change_slot = crystallized_state.last_state_recalculation_slot` -* For all `c` in `crystallized_state.crosslinks`, set `c.recently_changed = False` * Set `shard_and_committee_for_slots[:CYCLE_LENGTH] = shard_and_committee_for_slots[CYCLE_LENGTH:]` * Let `next_start_shard = (shard_and_committee_for_slots[-1][-1].shard + 1) % SHARD_COUNT` -* Set `shard_and_committee_for_slots[CYCLE_LENGTH:] = get_new_shuffling(active_state.randao_mix, validators, next_start_shard)` +* Set `shard_and_committee_for_slots[CYCLE_LENGTH:] = get_new_shuffling(crystallized_state.next_shuffling_seed, validators, next_start_shard)` +* Set `crystallized_state.next_shuffling_seed = active_state.randao_mix` + +### If a validator set change does NOT happen + +* Set `shard_and_committee_for_slots[:CYCLE_LENGTH] = shard_and_committee_for_slots[CYCLE_LENGTH:]` +* Let `time_since_finality = block.slot - crystallized_state.validator_set_change_slot` +* Let `start_shard = shard_and_committee_for_slots[0][0].shard` +* If `time_since_finality * CYCLE_LENGTH <= MIN_VALIDATOR_SET_CHANGE_INTERVAL` or `time_since_finality` is an exact power of 2, set `shard_and_committee_for_slots[CYCLE_LENGTH:] = get_new_shuffling(crystallized_state.next_shuffling_seed, validators, start_shard)` and set `crystallized_state.next_shuffling_seed = active_state.randao_mix`. Note that `start_shard` is not changed from last cycle. #### Finally... @@ -826,7 +833,6 @@ def change_validators(validators: List[ValidatorRecord]) -> None: * Empty the `active_state.pending_specials` list * For any validator with index `v` with balance less than `MIN_ONLINE_DEPOSIT_SIZE` and status `ACTIVE`, run `exit_validator(v, crystallized_state, penalize=False, current_slot=block.slot)` * Set `active_state.recent_block_hashes = active_state.recent_block_hashes[CYCLE_LENGTH:]` -* If a validator set change did _not_ occur during this state recalculation, set `shard_and_committee_for_slots[:CYCLE_LENGTH] = shard_and_committee_for_slots[CYCLE_LENGTH:]` * Set `crystallized_state.last_state_recalculation_slot += CYCLE_LENGTH` For any validator that was added or removed from the active validator list during this state recalculation: From 1296a4863e62a3cda10cc6b6a7aca35fdc792a55 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Thu, 8 Nov 2018 08:28:44 -0800 Subject: [PATCH 27/37] helpful comment for persistent committee shuffling --- specs/beacon-chain.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index 9ebbe2b26..32ce5c44a 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -849,6 +849,8 @@ Now run the following code to reshuffle a few proposers: active_validator_indices = get_active_validator_indices(validators) num_validators_to_reshuffle = len(active_validator_indices) // SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD for i in range(num_validators_to_reshuffle): + # Multiplying i to 2 to ensure we have different input to all the required hashes in the shuffling + # and none of the hashes used for entropy in this loop will be the same vid = active_validator_indices[hash(active_state.randao_mix + bytes8(i * 2)) % len(active_validator_indices)] new_shard = hash(active_state.randao_mix + bytes8(i * 2 + 1)) % SHARD_COUNT shard_reassignment_record = ShardReassignmentRecord( From b76275482dd864d0c83a5d1881cad5cf4642d835 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 9 Nov 2018 11:39:03 +0100 Subject: [PATCH 28/37] extend ssz hash type to arbitrary lengths (#116) * refactor hash in simple serialize to support arbitrary byte length * make comment clearer * add deserialization of hashN * formatting * remove specific hash examples --- specs/simple-serialize.md | 92 ++++++--------------------------------- 1 file changed, 13 insertions(+), 79 deletions(-) diff --git a/specs/simple-serialize.md b/specs/simple-serialize.md index 03af18621..dd7952851 100644 --- a/specs/simple-serialize.md +++ b/specs/simple-serialize.md @@ -16,9 +16,6 @@ deserializing objects and data types. - [uint: 8/16/24/32/64/256](#uint-816243264256) - [Address](#address) - [Hash](#hash) - * [Hash32](#hash32) - * [Hash96](#hash96) - * [Hash97](#hash97) - [Bytes](#bytes) - [List/Vectors](#listvectors) - [Container](#container) @@ -26,9 +23,6 @@ deserializing objects and data types. - [uint: 8/16/24/32/64/256](#uint-816243264256-1) - [Address](#address-1) - [Hash](#hash-1) - * [Hash32](#hash32-1) - * [Hash96](#hash96-1) - * [Hash97](#hash97-1) - [Bytes](#bytes-1) - [List/Vectors](#listvectors-1) - [Container](#container-1) @@ -97,7 +91,7 @@ return b'\x01' if value is True else b'\x00' #### Address -The address should already come as a hash/byte format. Ensure that length is +The `address` should already come as a hash/byte format. Ensure that length is **20**. | Check to perform | Code | @@ -113,63 +107,24 @@ return value | Hash Type | Usage | |:---------:|:------------------------------------------------| -| `hash32` | Hash size of ``keccak`` or `blake2b[0.. < 32]`. | -| `hash96` | BLS Public Key Size. | -| `hash97` | BLS Public Key Size with recovery bit. | +| `hashN` | Hash of arbitrary byte length `N`. | -| Checks to perform | Code | -|:-----------------------------------|:---------------------| -| Length is correct (32) if `hash32` | ``len(value) == 32`` | -| Length is correct (96) if `hash96` | ``len(value) == 96`` | -| Length is correct (97) if `hash97` | ``len(value) == 97`` | +| Checks to perform | Code | +|:---------------------------------------|:---------------------| +| Length in bytes is correct for `hashN` | ``len(value) == N`` | - -**Example all together** +##### hashN ```python -if (type(value) == 'hash32'): - assert(len(value) == 32) -elif (type(value) == 'hash96'): - assert(len(value) == 96) -elif (type(value) == 'hash97'): - assert(len(value) == 97) -else: - raise TypeError('Invalid hash type supplied') +assert(len(value) == N) return value ``` -##### Hash32 - -Ensure 32 byte length and return the bytes. - -```python -assert(len(value) == 32) -return value -``` - -##### Hash96 - -Ensure 96 byte length and return the bytes. - -```python -assert(len(value) == 96) -return value -``` - -##### Hash97 - -Ensure 97 byte length and return the bytes. - -```python -assert(len(value) == 97) -return value -``` - #### Bytes -For general `byte` type: +For general `bytes` type: 1. Get the length/number of bytes; Encode into a `4-byte` integer. 2. Append the value to the length and return: ``[ length_bytes ] + [ value_bytes ]`` @@ -313,37 +268,16 @@ return rawbytes[current_index:current_index+20], new_index #### Hash -##### Hash32 +##### hashN -Return the 32 bytes. +Return the `N` bytes. ```python -assert(len(rawbytes) >= current_index + 32) -new_index = current_index + 32 -return rawbytes[current_index:current_index+32], new_index +assert(len(rawbytes) >= current_index + N) +new_index = current_index + N +return rawbytes[current_index:current_index+N], new_index ``` -##### Hash96 - -Return the 96 bytes. - -```python -assert(len(rawbytes) >= current_index + 96) -new_index = current_index + 96 -return rawbytes[current_index:current_index+96], new_index -``` - -##### Hash97 - -Return the 97 bytes. - -```python -assert(len(rawbytes) >= current_index + 97) -new_index = current_index + 97 -return rawbytes[current_index:current_index+97], new_index -``` - - #### Bytes Get the length of the bytes, return the bytes. From 899f86adacf1042e5c7b66ec8a97ee5479af9db2 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Fri, 9 Nov 2018 02:40:21 -0800 Subject: [PATCH 29/37] typo in reshuffle proposer (#118) --- 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 32ce5c44a..ca0bf4471 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -867,7 +867,7 @@ while len(crystallized_state.persistent_committee_reassignments) > 0 and crystal committee.pop( committee.index(rec.validator_index) ) - crystallized_state.persistent_committees[rec.shard].append(vid) + crystallized_state.persistent_committees[rec.shard].append(rec.validator_index) ``` ### TODO From 6034dbf4ff8f8133d5768dca9bd678a6cc31643f Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sun, 11 Nov 2018 09:15:43 +0000 Subject: [PATCH 30/37] update randao_last_change and fix some var names (#115) * update randao_last_change and fix some var names * fix some special record stuff --- specs/beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index ca0bf4471..c8bdf75dd 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -683,7 +683,7 @@ Additionally, verify and update the RANDAO reveal. This is done as follows: * Let `repeat_hash(x, n) = x if n == 0 else repeat_hash(hash(x), n-1)`. * Let `V = crystallized_state.validators[curblock_proposer_index]`. -* Verify that `repeat_hash(block.randao_reveal, (block.slot - V.randao_last_reveal) // RANDAO_SLOTS_PER_LAYER + 1) == V.randao_commitment`, and set `active_state.randao_mix = xor(active_state.randao_mix, block.randao_reveal)` and append to `ActiveState.pending_specials` a `SpecialObject(kind=RANDAO_CHANGE, data=[bytes8(curblock_proposer_index), block.randao_reveal])`. +* Verify that `repeat_hash(block.randao_reveal, (block.slot - V.randao_last_change) // RANDAO_SLOTS_PER_LAYER + 1) == V.randao_commitment`, and set `active_state.randao_mix = xor(active_state.randao_mix, block.randao_reveal)` and append to `ActiveState.pending_specials` a `SpecialObject(kind=RANDAO_CHANGE, data=[bytes8(curblock_proposer_index), block.randao_reveal, bytes8(block.slot)])`. ### State recalculations (every `CYCLE_LENGTH` slots) @@ -744,9 +744,9 @@ For every shard number `shard` for which a crosslink committee exists in the cyc For each `SpecialRecord` `obj` in `active_state.pending_specials`: -* **[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(fork_version)), sig=data[1])`, where `fork_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 `LOGOUT`]**: If `obj.kind == LOGOUT`, interpret `data[0:3]` as a `uint24` and `data[3:35]` as a `hash32`, where `validator_index = data[0:3]` and `signature = data[3:35]`. If `BLSVerify(pubkey=validators[validator_index].pubkey, msg=hash(LOGOUT_MESSAGE + bytes8(fork_version)), sig=signature)`, where `fork_version = pre_fork_version if slot < fork_slot_number else post_fork_version`, and `validators[validator_index].status == ACTIVE`, run `exit_validator(validator_index, 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]`. +* **[covers `RANDAO_CHANGE`]**: If `obj.kind == RANDAO_CHANGE`, interpret `data[0:3]` as a `uint24`, `data[3:35]` as a `hash32`, and `data[35:43]` as a `uint64`, where `block_proposer_index = data[0:3]`, `randao_commitment = data[3:35]`, and `randao_last_change = data[35:43]`. Set `validators[block_proposer_index].randao_commitment = randao_commitment`, and set `validators[block_proposer_index].randao_last_change = randao_last_change`. ### Validator set change From 2a1150e5b88195031710cc2a371594d6e7bac583 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Tue, 13 Nov 2018 11:24:49 -0800 Subject: [PATCH 31/37] fixed get_active_validator_indices typo (#126) * get_active_validator_indices fixes * add a newline --- specs/beacon-chain.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/specs/beacon-chain.md b/specs/beacon-chain.md index c8bdf75dd..e475db064 100644 --- a/specs/beacon-chain.md +++ b/specs/beacon-chain.md @@ -328,8 +328,11 @@ The crystallized state recalculation generally focuses on changes to the validat Below are various helper functions. +The following is a function that gets active validator indices from the validator list: +```python def get_active_validator_indices(validators) return [i for i, v in enumerate(validators) if v.status == ACTIVE] +``` The following is a function that shuffles the validator list: From 7d5436166e1bba5d7147cffa870404d4cd786906 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Wed, 14 Nov 2018 16:01:57 -0500 Subject: [PATCH 32/37] =?UTF-8?q?Add=20shard=20blocks,=20shard=20data=20ro?= =?UTF-8?q?ots=20and=20how=20data=20is=20computed=20into=20cros=E2=80=A6?= =?UTF-8?q?=20(#123)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add shard blocks, shard data roots and how data is computed into crosslinks Includes: * Shard block structure * Shard block header verification rule * Shard block fork choice rule * Shard block body verification rule * Crosslink verification rule Possible simplification: require `calc_block_maxbytes` to always output an exact power of two; if we desire the average maxbytes to be smooth, we can simply make it a pseudorandom chose between powers. This reduces some of the padding complexity. * create separate files for phases (#125) * create separate files for phases * fix links * add shard block pre processing conditions * cleanup * remove 'essentially' * Updated handling for beacon chain skipping slots. * Handle missing slots more * modify attestation validity rule for crosslink hash --- .../0_beacon-chain.md} | 25 +++- specs/core/1_shard-data-chains.md | 133 ++++++++++++++++++ 2 files changed, 151 insertions(+), 7 deletions(-) rename specs/{beacon-chain.md => core/0_beacon-chain.md} (96%) create mode 100644 specs/core/1_shard-data-chains.md diff --git a/specs/beacon-chain.md b/specs/core/0_beacon-chain.md similarity index 96% rename from specs/beacon-chain.md rename to specs/core/0_beacon-chain.md index e475db064..3ce5fef19 100644 --- a/specs/beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1,12 +1,14 @@ -# Ethereum 2.0 spec—Casper and sharding +# Ethereum 2.0 Phase 0 -- The Beacon Chain -###### tags: `spec`, `eth2.0`, `casper`, `sharding` +###### tags: `spec`, `eth2.0`, `casper`, `sharding`, `beacon` **NOTICE**: This document is a work-in-progress for researchers and implementers. It reflects recent spec changes and takes precedence over the [Python proof-of-concept implementation](https://github.com/ethereum/beacon_chain). ### Introduction -At the center of Ethereum 2.0 is a system chain called the "beacon chain". The beacon chain stores and manages the set of active proof-of-stake validators. In the initial deployment phases of Ethereum 2.0 the only mechanism to become a validator is to make a fixed-size one-way ETH deposit to a registration contract on the Ethereum 1.0 PoW chain. Induction as a validator happens after registration transaction receipts are processed by the beacon chain and after a queuing process. Deregistration is either voluntary or done forcibly as a penalty for misbehavior. +This document represents the specification for Phase 0 of Ethereum 2.0 -- The Beacon Chain. + +At the core of Ethereum 2.0 is a system chain called the "beacon chain". The beacon chain stores and manages the set of active proof-of-stake validators. In the initial deployment phases of Ethereum 2.0 the only mechanism to become a validator is to make a fixed-size one-way ETH deposit to a registration contract on the Ethereum 1.0 PoW chain. Induction as a validator happens after registration transaction receipts are processed by the beacon chain and after a queuing process. Deregistration is either voluntary or done forcibly as a penalty for misbehavior. The primary source of load on the beacon chain are "attestations". Attestations simultaneously attest to a shard block and a corresponding beacon chain block. A sufficient number of attestations for the same shard block create a "crosslink", confirming the shard segment up to that shard block into the beacon chain. Crosslinks also serve as infrastructure for asynchronous cross-shard communication. @@ -35,7 +37,7 @@ The primary source of load on the beacon chain are "attestations". Attestations | `MIN_BALANCE` | 2**4 (= 16) | ETH | | `MIN_ONLINE_DEPOSIT_SIZE` | 2**4 (= 16) | ETH | | `GWEI_PER_ETH` | 10**9 | Gwei/ETH | -| `MIN_COMMITTEE_SIZE` | 2**7 (= 128) | validators | +| `TARGET_COMMITTEE_SIZE` | 2**8 (= 256) | validators | | `GENESIS_TIME` | **TBD** | seconds | | `SLOT_DURATION` | 2**4 (= 16) | seconds | | `CYCLE_LENGTH` | 2**6 (= 64) | slots | ~17 minutes | @@ -51,7 +53,7 @@ The primary source of load on the beacon chain are "attestations". Attestations **Notes** -* See a recommended `MIN_COMMITTEE_SIZE` of 111 here https://vitalik.ca/files/Ithaca201807_Sharding.pdf). +* See a recommended min committee size of 111 here https://vitalik.ca/files/Ithaca201807_Sharding.pdf); our algorithm will generally ensure the committee size is at least half the target. * The `SQRT_E_DROP_TIME` constant is the amount of time it takes for the quadratic leak to cut deposits of non-participating validators by ~39.4%. * The `BASE_REWARD_QUOTIENT` constant is the per-slot interest rate assuming all validators are participating, assuming total deposits of 1 ETH. It corresponds to ~3.88% annual interest assuming 10 million participating ETH. * At most `1/MAX_VALIDATOR_CHURN_QUOTIENT` of the validators can change during each validator set change. @@ -127,6 +129,10 @@ An `AttestationRecord` has the following fields: 'oblique_parent_hashes': ['hash32'], # Shard block hash being attested to 'shard_block_hash': 'hash32', + # Last crosslink hash + 'last_crosslink_hash': 'hash32', + # Root of data between last hash and this one + 'shard_block_combined_data_root': 'hash32', # Attester participation bitfield (1 bit per attester) 'attester_bitfield': 'bytes', # Slot of last justified beacon block @@ -152,6 +158,10 @@ An `AttestationSignedData` has the following fields: 'parent_hashes': ['hash32'], # Shard block hash 'shard_block_hash': 'hash32', + # Last crosslink hash + 'last_crosslink_hash': 'hash32', + # Root of data between last hash and this one + 'shard_block_combined_data_root': 'hash32', # Slot of last justified beacon block referenced in the attestation 'justified_slot': 'uint64' } @@ -427,7 +437,7 @@ def get_new_shuffling(seed: Hash32, committees_per_slot = clamp( 1, SHARD_COUNT // CYCLE_LENGTH, - len(active_validators) // CYCLE_LENGTH // (MIN_COMMITTEE_SIZE * 2) + 1, + len(active_validators) // CYCLE_LENGTH // TARGET_COMMITTEE_SIZE, ) output = [] @@ -671,12 +681,13 @@ For each one of these attestations: * Verify that `slot <= parent.slot` and `slot >= max(parent.slot - CYCLE_LENGTH + 1, 0)`. * Verify that `justified_slot` is equal to or earlier than `last_justified_slot`. * Verify that `justified_block_hash` is the hash of the block in the current chain at the slot -- `justified_slot`. +* Verify that either `last_crosslink_hash` or `shard_block_hash` equals `state.crosslinks[shard].shard_block_hash`. * Compute `parent_hashes` = `[get_block_hash(active_state, block, slot - CYCLE_LENGTH + i) for i in range(1, CYCLE_LENGTH - len(oblique_parent_hashes) + 1)] + oblique_parent_hashes` (eg, if `CYCLE_LENGTH = 4`, `slot = 5`, the actual block hashes starting from slot 0 are `Z A B C D E F G H I J`, and `oblique_parent_hashes = [D', E']` then `parent_hashes = [B, C, D' E']`). Note that when *creating* an attestation for a block, the hash of that block itself won't yet be in the `active_state`, so you would need to add it explicitly. * Let `attestation_indices` be `get_shards_and_committees_for_slot(crystallized_state, slot)[x]`, choosing `x` so that `attestation_indices.shard` equals the `shard` value provided to find the set of validators that is creating this attestation record. * 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 `fork_version = pre_fork_version if slot < fork_slot_number else post_fork_version`. -* Verify that `aggregate_sig` verifies using the group pubkey generated and the serialized form of `AttestationSignedData(fork_version, slot, shard, parent_hashes, shard_block_hash, justified_slot)` as the message. +* Verify that `aggregate_sig` verifies using the group pubkey generated and the serialized form of `AttestationSignedData(fork_version, slot, shard, parent_hashes, shard_block_hash, last_crosslinked_hash, shard_block_combined_data_root, justified_slot)` as the message. Extend the list of `AttestationRecord` objects in the `active_state` with those included in the block, ordering the new additions in the same order as they came in the block. Similarly extend the list of `SpecialRecord` objects in the `active_state` with those included in the block. diff --git a/specs/core/1_shard-data-chains.md b/specs/core/1_shard-data-chains.md new file mode 100644 index 000000000..6cb6982ee --- /dev/null +++ b/specs/core/1_shard-data-chains.md @@ -0,0 +1,133 @@ +# Ethereum 2.0 Phase 1 -- Shard Data Chains + +###### tags: `spec`, `eth2.0`, `casper`, `sharding` + +**NOTICE**: This document is a work-in-progress for researchers and implementers. It reflects recent spec changes and takes precedence over the [Python proof-of-concept implementation](https://github.com/ethereum/beacon_chain). + +### Introduction + +This document represents the specification for Phase 1 of Ethereum 2.0 -- Shard Data Chains. Phase 1 depends on the implementation of [Phase 0 -- The Beacon Chain](0_beacon-chain.md). + +Ethereum 2.0 consists of a central beacon chain along with `SHARD_COUNT` shard chains. Phase 1 is primarily concerned with the construction, validity, and consensus on the _data_ of these shard chains. Phase 1 does not specify shard chain state execution or account balances. This is left for future phases. + +### Terminology + +### Constants + +Phase 1 depends upon all of the constants defined in [Phase 0](0_beacon-chain.md#constants) in addition to the following: + +| Constant | Value | Unit | Approximation | +| `CHUNK_SIZE` | 2**8 (= 256) | bytes | +| `MAX_SHARD_BLOCK_SIZE` | 2**15 (= 32768) | bytes | + +## Data Structures + +### Shard chain blocks + +A `ShardBlock` object has the following fields: + +```python +{ + # Slot number + 'slot': 'uint64', + # What shard is it on + 'shard_id': 'uint64', + # Parent block hash + 'parent_hash': 'hash32', + # Beacon chain block + 'beacon_chain_ref': 'hash32', + # Depth of the Merkle tree + 'data_tree_depth': 'uint8', + # Merkle root of data + 'data_root': 'hash32' + # State root (placeholder for now) + 'state_root': 'hash32', + # Attestation (including block signature) + 'attester_bitfield': 'bytes', + 'aggregate_sig': ['uint256'], +} +``` + +## Shard block processing + +For a block on a shard to be processed by a node, the following conditions must be met: + +* The `ShardBlock` pointed to by `parent_hash` has already been processed and accepted +* The signature for the block from the _proposer_ (see below for definition) of that block is included along with the block in the network message object + +To validate a block header on shard `shard_id`, compute as follows: + +* Verify that `beacon_chain_ref` is the hash of a block in the beacon chain with slot less than or equal to `slot`. Verify that `beacon_chain_ref` is equal to or a descendant of the `beacon_chain_ref` specified in the `ShardBlock` pointed to by `parent_hash`. +* Let `state` be the state of the beacon chain block referred to by `beacon_chain_ref`. Let `validators` be `[validators[i] for i in state.current_persistent_committees[shard_id]]`. +* Assert `len(attester_bitfield) == ceil_div8(len(validators))` +* Let `curblock_proposer_index = hash(state.randao_mix + bytes8(shard_id) + bytes8(slot)) % len(validators)`. Let `parent_proposer_index` be the same value calculated for the parent block. +* Make sure that the `parent_proposer_index`'th bit in the `attester_bitfield` is set to 1. +* Generate the group public key by adding the public keys of all the validators for whom the corresponding position in the bitfield is set to 1. Verify the `aggregate_sig` using this as the pubkey and the `parent_hash` as the message. + +### Verifying shard block data + +At network layer, we expect a shard block header to be broadcast along with its `block_body`. First, we define a helper function that takes as input beacon chain state and outputs the max block size in bytes: + +```python +def shard_block_maxbytes(state): + max_grains = MAX_SHARD_BLOCK_SIZE // CHUNK_SIZE + validators_at_target_committee_size = SHARD_COUNT * TARGET_COMMITTEE_SIZE + + # number of grains per block is proportional to the number of validators + # up until `validators_at_target_committee_size` + grains = min( + len(get_active_validator_indices(state.validators)) * max_grains // validators_at_target_committee_size, + max_grains + ) + + return CHUNK_SIZE * grains +``` + +* Verify that `len(block_body) == shard_block_maxbytes(state)` +* Define `filler_bytes = next_power_of_2(len(block_body)) - len(block_body)`. Compute a simple binary Merkle tree of `block_body + bytes([0] * filler_bytes)` and verify that the root equals the `data_root` in the header. + +### Verifying a crosslink + +A node should sign a crosslink only if the following conditions hold. **If a node has the capability to perform the required level of verification, it should NOT follow chains on which a crosslink for which these conditions do NOT hold has been included, or a sufficient number of signatures have been included that during the next state recalculation, a crosslink will be registered.** + +First, the conditions must recursively apply to the crosslink referenced in `last_crosslink_hash` for the same shard (unless `last_crosslink_hash` equals zero, in which case we are at the genesis). + +Second, we verify the `shard_block_combined_data_root`. Let `h` be the slot _immediately after_ the slot of the shard block included by the last crosslink, and `h+n-1` be the slot number of the block directly referenced by the current `shard_block_hash`. Let `B[i]` be the block at slot `h+i` in the shard chain. Let `bodies[0] .... bodies[n-1]` be the bodies of these blocks and `roots[0] ... roots[n-1]` the data roots. If there is a missing slot in the shard chain at position `h+i`, then `bodies[i] == b'\x00' * shard_block_maxbytes(state[i])` and `roots[i]` be the Merkle root of the empty data. Define `compute_merkle_root` be a simple Merkle root calculating function that takes as input a list of objects, where the list's length must be an exact power of two. Let `state[i]` be the beacon chain state at height `h+i` (if the beacon chain is missing a block at some slot, the state is unchanged), and `depths[i]` be equal to `log2(next_power_of_2(shard_block_maxbytes(state[i]) // CHUNK_SIZE))` (ie. the expected depth of the i'th data tree). We define the function for computing the combined data root as follows: + +```python +def get_zeroroot_at_depth(n): + o = b'\x00' * CHUNK_SIZE + for i in range(n): + o = hash(o + o) + return o + +def mk_combined_data_root(depths, roots): + default_value = get_zeroroot_at_depth(max(depths)) + data = [default_value for _ in range(next_power_of_2(len(roots)))] + for i, (depth, root) in enumerate(zip(depths, roots)): + value = root + for j in range(depth, max(depths)): + value = hash(value, get_zeroroot_at_depth(depth + j)) + data[i] = value + return compute_merkle_root(data) +``` + +This outputs the root of a tree of the data roots, with the data roots all adjusted to have the same height if needed. The tree can also be viewed as a tree of all of the underlying data concatenated together, appropriately padded. Here is an equivalent definition that uses bodies instead of roots [TODO: check equivalence]: + +```python +def mk_combined_data_root(depths, bodies): + default_value = get_zeroroot_at_depth(max(depths)) + padded_body_length = max([CHUNK_SIZE * 2**d for d in depths]) + data = b'' + for body in bodies: + padded_body = body + bytes([0] * (padded_body_length - len(body))) + data += padded_body + data += bytes([0] * (next_power_of_2(len(data)) - len(data)) + return compute_merkle_root([data[pos:pos+CHUNK_SIZE] for pos in range(0, len(data), CHUNK_SIZE)]) +``` + +Verify that the `shard_block_combined_data_root` is the output of these functions. + +### Shard block fork choice rule + +The fork choice rule for any shard is LMD GHOST using the validators currently assigned to that shard, but instead of being rooted in the genesis it is rooted in the block referenced in the most recent accepted crosslink (ie. `state.crosslinks[shard].shard_block_hash`). Only blocks whose `beacon_chain_ref` is the block in the main beacon chain at the specified `slot` should be considered (if the beacon chain skips a slot, then the block at that slot is considered to be the block in the beacon chain at the highest slot lower than a slot). From 86ec833172704ea0889b5d595d17f45ba1a6676f Mon Sep 17 00:00:00 2001 From: Ben Edgington Date: Thu, 15 Nov 2018 12:28:47 +0000 Subject: [PATCH 33/37] Remove MIN_BALANCE definition (#131) MIN_BALANCE is not used - I'm guessing it was replaced by MIN_ONLINE_DEPOSIT_SIZE. --- specs/core/0_beacon-chain.md | 1 - 1 file changed, 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 3ce5fef19..ba61cf8ec 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -34,7 +34,6 @@ The primary source of load on the beacon chain are "attestations". Attestations | --- | --- | :---: | - | | `SHARD_COUNT` | 2**10 (= 1,024)| shards | | `DEPOSIT_SIZE` | 2**5 (= 32) | ETH | -| `MIN_BALANCE` | 2**4 (= 16) | ETH | | `MIN_ONLINE_DEPOSIT_SIZE` | 2**4 (= 16) | ETH | | `GWEI_PER_ETH` | 10**9 | Gwei/ETH | | `TARGET_COMMITTEE_SIZE` | 2**8 (= 256) | validators | From 707adddc92a6fedb4b860f208ad7c096cfa651ad Mon Sep 17 00:00:00 2001 From: vbuterin Date: Thu, 15 Nov 2018 08:12:34 -0500 Subject: [PATCH 34/37] Added tree hashing algorithm (#120) * Added tree hashing algorithm * Update simple-serialize.md * add one more ref to tree_hash * Add the zero-item special case * list_to_glob to handle empty list --- specs/simple-serialize.md | 73 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/specs/simple-serialize.md b/specs/simple-serialize.md index dd7952851..defe9bba7 100644 --- a/specs/simple-serialize.md +++ b/specs/simple-serialize.md @@ -383,6 +383,79 @@ assert item_index == start + LENGTH_BYTES + length return typ(**values), item_index ``` +### Tree_hash + +The below `tree_hash` algorithm is defined recursively in the case of lists and containers, and it outputs a value equal to or less than 32 bytes in size. For the final output only (ie. not intermediate outputs), if the output is less than 32 bytes, right-zero-pad it to 32 bytes. The goal is collision resistance *within* each type, not between types. + +We define `hash(x)` as `BLAKE2b-512(x)[0:32]`. + +#### uint: 8/16/24/32/64/256, bool, address, hash32 + +Return the serialization of the value. + +#### bytes, hash96 + +Return the hash of the serialization of the value. + +#### List/Vectors + +First, we define some helpers and then the Merkle tree function. The constant `CHUNK_SIZE` is set to 128. + +```python +# Returns the smallest power of 2 equal to or higher than x +def next_power_of_2(x): + return x if x == 1 else next_power_of_2((x+1) // 2) * 2 + +# Extends data length to a power of 2 by minimally right-zero-padding +def extend_to_power_of_2(data): + return data + b'\x00' * (next_power_of_2(len(data)) - len(data)) + +# Concatenate a list of homogeneous objects into data and pad it +def list_to_glob(lst): + if len(lst) == 0: + return b'' + if len(lst[0]) != next_power_of_2(len(lst[0])): + lst = [extend_to_power_of_2(x) for x in lst] + data = b''.join(lst) + # Pad to chunksize + data += b'\x00' * (CHUNKSIZE - (len(data) % CHUNKSIZE or CHUNKSIZE)) + return data + +# Merkle tree hash of a list of items +def merkle_hash(lst): + # Turn list into padded data + data = list_to_glob(lst) + # Store length of list (to compensate for non-bijectiveness of padding) + datalen = len(lst).to_bytes(32, 'big') + # Convert to chunks + chunkz = [data[i:i+CHUNKSIZE] for i in range(0, len(data), CHUNKSIZE)] + # Tree-hash + while len(chunkz) > 1: + if len(chunkz) % 2 == 1: + chunkz.append(b'\x00' * CHUNKSIZE) + chunkz = [hash(chunkz[i] + chunkz[i+1]) for i in range(0, len(chunkz), 2)] + # Return hash of root and length data + return hash((chunkz[0] if len(chunks) > 0 else b'\x00' * 32) + datalen) +``` + +To `tree_hash` a list, we simply do: + +```python +return merkle_hash([tree_hash(item) for item in value]) +``` + +Where the inner `tree_hash` is a recursive application of the tree-hashing function (returning less than 32 bytes for short single values). + + +#### Container + +Recursively tree hash the values in the container in order sorted by key, and return the hash of the concatenation of the results. + +```python +return hash(b''.join([tree_hash(getattr(x, field)) for field in sorted(value.fields))) +``` + + ## Implementations | Language | Implementation | Description | From 5ed4392b5449000cd9d555344e1627dcdfe931fb Mon Sep 17 00:00:00 2001 From: vbuterin Date: Thu, 15 Nov 2018 08:20:25 -0500 Subject: [PATCH 35/37] Minimal replacement of active/crystallized state with single state (#122) * Minimal replacement of active/crystallized state with single state * Update specs/beacon-chain.md Co-Authored-By: vbuterin * Update specs/beacon-chain.md Co-Authored-By: vbuterin * State -> BeaconState * Update beacon-chain.md * Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 162 +++++++++++++++-------------------- 1 file changed, 71 insertions(+), 91 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index ba61cf8ec..52d6f896c 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -105,10 +105,8 @@ A `BeaconBlock` has the following fields: # Skip list of previous beacon block hashes # i'th item is the most recent ancestor whose slot is a multiple of 2**i for i = 0, ..., 31 'ancestor_hashes': ['hash32'], - # Active state root - 'active_state_root': 'hash32', - # Crystallized state root - 'crystallized_state_root': 'hash32', + # State root + 'state_root': 'hash32', # Attestations 'attestations': [AttestationRecord], # Specials (e.g. logouts, penalties) @@ -179,24 +177,7 @@ A `SpecialRecord` has the following fields: ### Beacon chain state -For convenience we define the beacon chain state in two parts: "active state" and "crystallized state". - -The `ActiveState` has the following fields: - -```python -{ - # Attestations not yet processed - 'pending_attestations': [AttestationRecord], - # Specials not yet been processed - 'pending_specials': [SpecialRecord] - # recent beacon block hashes needed to process attestations, older to newer - 'recent_block_hashes': ['hash32'], - # RANDAO state - 'randao_mix': 'hash32' -} -``` - -The `CrystallizedState` has the following fields: +The `BeaconState` has the following fields: ```python { @@ -206,7 +187,7 @@ The `CrystallizedState` has the following fields: 'validators': [ValidatorRecord], # Most recent crosslink for each shard 'crosslinks': [CrosslinkRecord], - # Last crystallized state recalculation + # Last cycle-boundary state recalculation 'last_state_recalculation_slot': 'uint64', # Last finalized slot 'last_finalized_slot': 'uint64', @@ -230,6 +211,14 @@ The `CrystallizedState` has the following fields: 'pre_fork_version': 'uint32', 'post_fork_version': 'uint32', 'fork_slot_number': 'uint64', + # Attestations not yet processed + 'pending_attestations': [AttestationRecord], + # Specials not yet been processed + 'pending_specials': [SpecialRecord] + # recent beacon block hashes needed to process attestations, older to newer + 'recent_block_hashes': ['hash32'], + # RANDAO state + 'randao_mix': 'hash32' } ``` @@ -328,10 +317,10 @@ Here's an example of its working (green is finalized blocks, yellow is justified We now define the state transition function. At the high level, the state transition is made up of two parts: -1. The per-block processing, which happens every block, and affects the `ActiveState` only. -2. The crystallized state recalculation, which happens only if `block.slot >= last_state_recalculation_slot + CYCLE_LENGTH`, and affects the `CrystallizedState` and `ActiveState`. +1. The per-block processing, which happens every block, and only affects a few parts of the `state`. +2. The inter-cycle state recalculation, which happens only if `block.slot >= last_state_recalculation_slot + CYCLE_LENGTH`, and affects the entire `state`. -The crystallized state recalculation generally focuses on changes to the validator set, including adjusting balances and adding and removing validators, as well as processing crosslinks and managing block justification/finalization, and the per-block processing generally focuses on verifying aggregate signatures and saving temporary records relating to the in-block activity in the `ActiveState`. +The inter-cycle state recalculation generally focuses on changes to the validator set, including adjusting balances and adding and removing validators, as well as processing crosslinks and managing block justification/finalization, while the per-block processing generally focuses on verifying aggregate signatures and saving temporary records relating to the per-block activity in the `BeaconState`. ### Helper functions @@ -472,18 +461,18 @@ Here's a diagram of what's going on: We also define two functions for retrieving data from the state: ```python -def get_shards_and_committees_for_slot(crystallized_state: CrystallizedState, +def get_shards_and_committees_for_slot(state: BeaconState, slot: int) -> List[ShardAndCommittee]: - earliest_slot_in_array = crystallized_state.last_state_recalculation - CYCLE_LENGTH + earliest_slot_in_array = state.last_state_recalculation - CYCLE_LENGTH assert earliest_slot_in_array <= slot < earliest_slot_in_array + CYCLE_LENGTH * 2 - return crystallized_state.shard_and_committee_for_slots[slot - earliest_slot_in_array] + return state.shard_and_committee_for_slots[slot - earliest_slot_in_array] -def get_block_hash(active_state: ActiveState, +def get_block_hash(state: BeaconState, current_block: BeaconBlock, slot: int) -> Hash32: - earliest_slot_in_array = current_block.slot - len(active_state.recent_block_hashes) + earliest_slot_in_array = current_block.slot - len(state.recent_block_hashes) assert earliest_slot_in_array <= slot < current_block.slot - return active_state.recent_block_hashes[slot - earliest_slot_in_array] + return state.recent_block_hashes[slot - earliest_slot_in_array] ``` `get_block_hash(_, _, s)` should always return the block hash in the beacon chain at slot `s`, and `get_shards_and_committees_for_slot(_, s)` should not change unless the validator set changes. @@ -493,12 +482,12 @@ 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(crystallized_state: CrystallizedState, +def add_validator_set_change_record(state: BeaconState, index: int, pubkey: int, flag: int) -> None: - crystallized_state.validator_set_delta_hash_chain = \ - hash(crystallized_state.validator_set_delta_hash_chain + + state.validator_set_delta_hash_chain = \ + hash(state.validator_set_delta_hash_chain + bytes1(flag) + bytes3(index) + bytes32(pubkey)) ``` @@ -519,7 +508,7 @@ def int_sqrt(n: int) -> int: Run the following code: ```python -def on_startup(initial_validator_entries: List[Any]) -> Tuple[CrystallizedState, ActiveState]: +def on_startup(initial_validator_entries: List[Any]) -> BeaconState: # Induct validators validators = [] for pubkey, proof_of_possession, withdrawal_shard, withdrawal_address, \ @@ -534,7 +523,7 @@ def on_startup(initial_validator_entries: List[Any]) -> Tuple[CrystallizedState, current_slot=0, status=ACTIVE, ) - # Setup crystallized state + # Setup state x = get_new_shuffling(bytes([0] * 32), validators, 0) crosslinks = [ CrosslinkRecord( @@ -543,7 +532,7 @@ def on_startup(initial_validator_entries: List[Any]) -> Tuple[CrystallizedState, ) for i in range(SHARD_COUNT) ] - crystallized_state = CrystallizedState( + state = BeaconState( validator_set_change_slot=0, validators=validators, crosslinks=crosslinks, @@ -559,25 +548,17 @@ def on_startup(initial_validator_entries: List[Any]) -> Tuple[CrystallizedState, validator_set_delta_hash_chain=bytes([0] * 32), # stub pre_fork_version=INITIAL_FORK_VERSION, post_fork_version=INITIAL_FORK_VERSION, - fork_slot_number=0 - ) - - # Setup active state - recent_block_hashes = [ - bytes([0] * 32) - for _ in range(CYCLE_LENGTH * 2) - ] - active_state = ActiveState( + fork_slot_number=0, pending_attestations=[], pending_specials=[], - recent_block_hashes=recent_block_hashes, + recent_block_hashes=[bytes([0] * 32) for _ in range(CYCLE_LENGTH * 2)], randao_mix=bytes([0] * 32) # stub ) - return crystallized_state, active_state + return state ``` -The `CrystallizedState()` and `ActiveState()` constructors should initialize all values to zero bytes, an empty value or an empty array depending on context. The `add_validator` routine is defined below. +The `add_validator` routine is defined below. ### Routine for adding a validator @@ -631,15 +612,15 @@ def add_validator(validators: List[ValidatorRecord], ## Routine for removing a validator ```python -def exit_validator(index, crystallized_state, penalize, current_slot): - validator = crystallized_state.validators[index] +def exit_validator(index, state, penalize, current_slot): + validator = state.validators[index] validator.exit_slot = current_slot if penalize: validator.status = PENALIZED - crystallized_state.deposits_penalized_in_period[current_slot // WITHDRAWAL_PERIOD] += validator.balance + state.deposits_penalized_in_period[current_slot // WITHDRAWAL_PERIOD] += validator.balance else: validator.status = PENDING_EXIT - add_validator_set_change_record(crystallized_state, index, validator.pubkey, EXIT) + add_validator_set_change_record(state, index, validator.pubkey, EXIT) ``` ### Per-block processing @@ -681,22 +662,22 @@ For each one of these attestations: * Verify that `justified_slot` is equal to or earlier than `last_justified_slot`. * Verify that `justified_block_hash` is the hash of the block in the current chain at the slot -- `justified_slot`. * Verify that either `last_crosslink_hash` or `shard_block_hash` equals `state.crosslinks[shard].shard_block_hash`. -* Compute `parent_hashes` = `[get_block_hash(active_state, block, slot - CYCLE_LENGTH + i) for i in range(1, CYCLE_LENGTH - len(oblique_parent_hashes) + 1)] + oblique_parent_hashes` (eg, if `CYCLE_LENGTH = 4`, `slot = 5`, the actual block hashes starting from slot 0 are `Z A B C D E F G H I J`, and `oblique_parent_hashes = [D', E']` then `parent_hashes = [B, C, D' E']`). Note that when *creating* an attestation for a block, the hash of that block itself won't yet be in the `active_state`, so you would need to add it explicitly. -* Let `attestation_indices` be `get_shards_and_committees_for_slot(crystallized_state, slot)[x]`, choosing `x` so that `attestation_indices.shard` equals the `shard` value provided to find the set of validators that is creating this attestation record. +* Compute `parent_hashes` = `[get_block_hash(state, block, slot - CYCLE_LENGTH + i) for i in range(1, CYCLE_LENGTH - len(oblique_parent_hashes) + 1)] + oblique_parent_hashes` (eg, if `CYCLE_LENGTH = 4`, `slot = 5`, the actual block hashes starting from slot 0 are `Z A B C D E F G H I J`, and `oblique_parent_hashes = [D', E']` then `parent_hashes = [B, C, D' E']`). Note that when *creating* an attestation for a block, the hash of that block itself won't yet be in the `state`, so you would need to add it explicitly. +* Let `attestation_indices` be `get_shards_and_committees_for_slot(state, slot)[x]`, choosing `x` so that `attestation_indices.shard` equals the `shard` value provided to find the set of validators that is creating this attestation record. * 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 `fork_version = pre_fork_version if slot < fork_slot_number else post_fork_version`. * Verify that `aggregate_sig` verifies using the group pubkey generated and the serialized form of `AttestationSignedData(fork_version, slot, shard, parent_hashes, shard_block_hash, last_crosslinked_hash, shard_block_combined_data_root, justified_slot)` as the message. -Extend the list of `AttestationRecord` objects in the `active_state` with those included in the block, ordering the new additions in the same order as they came in the block. Similarly extend the list of `SpecialRecord` objects in the `active_state` with those included in the block. +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. Similarly extend the list of `SpecialRecord` objects in the `state` with those included in the block. -Let `curblock_proposer_index` be the validator index of the `block.slot % len(get_shards_and_committees_for_slot(crystallized_state, block.slot)[0].committee)`'th attester in `get_shards_and_committees_for_slot(crystallized_state, block.slot)[0]`, and `parent_proposer_index` be the validator index of the parent block, calculated similarly. Verify that an attestation from the `parent_proposer_index`'th validator is part of the first (ie. item 0 in the array) `AttestationRecord` object; this attester can be considered to be the proposer of the parent block. In general, when a beacon block is produced, it is broadcasted at the network layer along with the attestation from its proposer. +Let `curblock_proposer_index` be the validator index of the `block.slot % len(get_shards_and_committees_for_slot(state, block.slot)[0].committee)`'th attester in `get_shards_and_committees_for_slot(state, block.slot)[0]`, and `parent_proposer_index` be the validator index of the parent block, calculated similarly. Verify that an attestation from the `parent_proposer_index`'th validator is part of the first (ie. item 0 in the array) `AttestationRecord` object; this attester can be considered to be the proposer of the parent block. In general, when a beacon block is produced, it is broadcasted at the network layer along with the attestation from its proposer. Additionally, verify and update the RANDAO reveal. This is done as follows: * Let `repeat_hash(x, n) = x if n == 0 else repeat_hash(hash(x), n-1)`. -* Let `V = crystallized_state.validators[curblock_proposer_index]`. -* Verify that `repeat_hash(block.randao_reveal, (block.slot - V.randao_last_change) // RANDAO_SLOTS_PER_LAYER + 1) == V.randao_commitment`, and set `active_state.randao_mix = xor(active_state.randao_mix, block.randao_reveal)` and append to `ActiveState.pending_specials` a `SpecialObject(kind=RANDAO_CHANGE, data=[bytes8(curblock_proposer_index), block.randao_reveal, bytes8(block.slot)])`. +* Let `V = state.validators[curblock_proposer_index]`. +* Verify that `repeat_hash(block.randao_reveal, (block.slot - V.randao_last_change) // RANDAO_SLOTS_PER_LAYER + 1) == V.randao_commitment`, and set `state.randao_mix = xor(state.randao_mix, block.randao_reveal)` and append to `state.pending_specials` a `SpecialObject(kind=RANDAO_CHANGE, data=[bytes8(curblock_proposer_index), block.randao_reveal, bytes8(block.slot)])`. ### State recalculations (every `CYCLE_LENGTH` slots) @@ -755,19 +736,19 @@ For every shard number `shard` for which a crosslink committee exists in the cyc #### Process penalties, logouts and other special objects -For each `SpecialRecord` `obj` in `active_state.pending_specials`: +For each `SpecialRecord` `obj` in `state.pending_specials`: -* **[covers `LOGOUT`]**: If `obj.kind == LOGOUT`, interpret `data[0:3]` as a `uint24` and `data[3:35]` as a `hash32`, where `validator_index = data[0:3]` and `signature = data[3:35]`. If `BLSVerify(pubkey=validators[validator_index].pubkey, msg=hash(LOGOUT_MESSAGE + bytes8(fork_version)), sig=signature)`, where `fork_version = pre_fork_version if slot < fork_slot_number else post_fork_version`, and `validators[validator_index].status == ACTIVE`, run `exit_validator(validator_index, 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_CHANGE`]**: If `obj.kind == RANDAO_CHANGE`, interpret `data[0:3]` as a `uint24`, `data[3:35]` as a `hash32`, and `data[35:43]` as a `uint64`, where `block_proposer_index = data[0:3]`, `randao_commitment = data[3:35]`, and `randao_last_change = data[35:43]`. Set `validators[block_proposer_index].randao_commitment = randao_commitment`, and set `validators[block_proposer_index].randao_last_change = randao_last_change`. +* **[covers `LOGOUT`]**: If `obj.kind == LOGOUT`, interpret `data[0]` as a `uint24` and `data[1]` as a `hash32`, where `validator_index = data[0]` and `signature = data[1]`. If `BLSVerify(pubkey=validators[validator_index].pubkey, msg=hash(LOGOUT_MESSAGE + bytes8(fork_version)), sig=signature)`, where `fork_version = pre_fork_version if slot < fork_slot_number else post_fork_version`, and `validators[validator_index].status == ACTIVE`, run `exit_validator(validator_index, 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, state, penalize=True, current_slot=block.slot)` +* **[covers `RANDAO_CHANGE`]**: If `obj.kind == RANDAO_CHANGE`, interpret `data[0]` as a `uint24`, `data[1]` as a `hash32`, and `data[2]` as a `uint64`, where `proposer_index = data[0]`, `randao_commitment = data[1]`, and `randao_last_change = data[2]`. Set `validators[proposer_index].randao_commitment = randao_commitment`, and set `validators[proposer_index].randao_last_change = randao_last_change`. ### Validator set change A validator set change can happen after a state recalculation if all of the following criteria are satisfied: -* `block.slot - crystallized_state.validator_set_change_slot >= MIN_VALIDATOR_SET_CHANGE_INTERVAL` -* `last_finalized_slot > crystallized_state.validator_set_change_slot` -* For every shard number `shard` in `shard_and_committee_for_slots`, `crosslinks[shard].slot > crystallized_state.validator_set_change_slot` +* `block.slot - state.validator_set_change_slot >= MIN_VALIDATOR_SET_CHANGE_INTERVAL` +* `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: @@ -789,7 +770,7 @@ def change_validators(validators: List[ValidatorRecord]) -> None: validators[i].status = ACTIVE total_changed += DEPOSIT_SIZE * GWEI_PER_ETH add_validator_set_change_record( - crystallized_state=crystallized_state, + state=state, index=i, pubkey=validators[i].pubkey, flag=ENTRY @@ -799,7 +780,7 @@ def change_validators(validators: List[ValidatorRecord]) -> None: validators[i].exit_slot = current_slot total_changed += validators[i].balance add_validator_set_change_record( - crystallized_state=crystallized_state, + state=state, index=i, pubkey=validators[i].pubkey, flag=EXIT @@ -810,9 +791,9 @@ def change_validators(validators: List[ValidatorRecord]) -> None: # Calculate the total ETH that has been penalized in the last ~2-3 withdrawal periods period_index = current_slot // WITHDRAWAL_PERIOD total_penalties = ( - (crystallized_state.deposits_penalized_in_period[period_index]) + - (crystallized_state.deposits_penalized_in_period[period_index - 1] if period_index >= 1 else 0) + - (crystallized_state.deposits_penalized_in_period[period_index - 2] if period_index >= 2 else 0) + (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) ) # Separate loop to withdraw validators that have been logged out for long enough, and # calculate their penalties if they were slashed @@ -827,32 +808,32 @@ def change_validators(validators: List[ValidatorRecord]) -> None: # STUB: withdraw to shard chain ``` -* Set `crystallized_state.validator_set_change_slot = crystallized_state.last_state_recalculation_slot` +* Set `state.validator_set_change_slot = state.last_state_recalculation_slot` * Set `shard_and_committee_for_slots[:CYCLE_LENGTH] = shard_and_committee_for_slots[CYCLE_LENGTH:]` * Let `next_start_shard = (shard_and_committee_for_slots[-1][-1].shard + 1) % SHARD_COUNT` -* Set `shard_and_committee_for_slots[CYCLE_LENGTH:] = get_new_shuffling(crystallized_state.next_shuffling_seed, validators, next_start_shard)` -* Set `crystallized_state.next_shuffling_seed = active_state.randao_mix` +* Set `shard_and_committee_for_slots[CYCLE_LENGTH:] = get_new_shuffling(state.next_shuffling_seed, validators, next_start_shard)` +* Set `state.next_shuffling_seed = state.randao_mix` ### If a validator set change does NOT happen * Set `shard_and_committee_for_slots[:CYCLE_LENGTH] = shard_and_committee_for_slots[CYCLE_LENGTH:]` -* Let `time_since_finality = block.slot - crystallized_state.validator_set_change_slot` +* Let `time_since_finality = block.slot - state.validator_set_change_slot` * Let `start_shard = shard_and_committee_for_slots[0][0].shard` -* If `time_since_finality * CYCLE_LENGTH <= MIN_VALIDATOR_SET_CHANGE_INTERVAL` or `time_since_finality` is an exact power of 2, set `shard_and_committee_for_slots[CYCLE_LENGTH:] = get_new_shuffling(crystallized_state.next_shuffling_seed, validators, start_shard)` and set `crystallized_state.next_shuffling_seed = active_state.randao_mix`. Note that `start_shard` is not changed from last cycle. +* If `time_since_finality * CYCLE_LENGTH <= MIN_VALIDATOR_SET_CHANGE_INTERVAL` or `time_since_finality` is an exact power of 2, set `shard_and_committee_for_slots[CYCLE_LENGTH:] = get_new_shuffling(state.next_shuffling_seed, validators, start_shard)` and set `state.next_shuffling_seed = state.randao_mix`. Note that `start_shard` is not changed from last cycle. #### Finally... -* Remove all attestation records older than slot `crystallized_state.last_state_recalculation_slot` -* Empty the `active_state.pending_specials` list -* For any validator with index `v` with balance less than `MIN_ONLINE_DEPOSIT_SIZE` and status `ACTIVE`, run `exit_validator(v, crystallized_state, penalize=False, current_slot=block.slot)` -* Set `active_state.recent_block_hashes = active_state.recent_block_hashes[CYCLE_LENGTH:]` -* Set `crystallized_state.last_state_recalculation_slot += CYCLE_LENGTH` +* Remove all attestation records older than slot `state.last_state_recalculation_slot` +* Empty the `state.pending_specials` list +* For any validator with index `v` with balance less than `MIN_ONLINE_DEPOSIT_SIZE` and status `ACTIVE`, run `exit_validator(v, state, penalize=False, current_slot=block.slot)` +* Set `state.recent_block_hashes = state.recent_block_hashes[CYCLE_LENGTH:]` +* Set `state.last_state_recalculation_slot += CYCLE_LENGTH` For any validator that was added or removed from the active validator list during this state recalculation: * If the validator was removed, remove their index from the `persistent_committees` and remove any `ShardReassignmentRecord`s containing their index from `persistent_committee_reassignments`. * If the validator was added with index `validator_index`: - * let `assigned_shard = hash(active_state.randao_mix + bytes8(validator_index)) % SHARD_COUNT` + * let `assigned_shard = hash(state.randao_mix + bytes8(validator_index)) % SHARD_COUNT` * let `reassignment_record = ShardReassignmentRecord(validator_index=validator_index, shard=assigned_shard, slot=block.slot + SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD)` * Append `reassignment_record` to the end of `persistent_committee_reassignments` @@ -864,23 +845,23 @@ num_validators_to_reshuffle = len(active_validator_indices) // SHARD_PERSISTENT_ for i in range(num_validators_to_reshuffle): # Multiplying i to 2 to ensure we have different input to all the required hashes in the shuffling # and none of the hashes used for entropy in this loop will be the same - vid = active_validator_indices[hash(active_state.randao_mix + bytes8(i * 2)) % len(active_validator_indices)] - new_shard = hash(active_state.randao_mix + bytes8(i * 2 + 1)) % SHARD_COUNT + vid = active_validator_indices[hash(state.randao_mix + bytes8(i * 2)) % len(active_validator_indices)] + new_shard = hash(state.randao_mix + bytes8(i * 2 + 1)) % SHARD_COUNT shard_reassignment_record = ShardReassignmentRecord( validator_index=vid, shard=new_shard, slot=block.slot + SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD ) - crystallized_state.persistent_committee_reassignments.append(shard_reassignment_record) + state.persistent_committee_reassignments.append(shard_reassignment_record) -while len(crystallized_state.persistent_committee_reassignments) > 0 and crystallized_state.persistent_committee_reassignments[0].slot <= block.slot: - rec = crystallized_state.persistent_committee_reassignments.pop(0) - for committee in crystallized_state.persistent_committees: +while len(state.persistent_committee_reassignments) > 0 and state.persistent_committee_reassignments[0].slot <= block.slot: + rec = state.persistent_committee_reassignments.pop(0) + for committee in state.persistent_committees: if rec.validator_index in committee: committee.pop( committee.index(rec.validator_index) ) - crystallized_state.persistent_committees[rec.shard].append(rec.validator_index) + state.persistent_committees[rec.shard].append(rec.validator_index) ``` ### TODO @@ -889,7 +870,6 @@ Note: This spec is ~65% complete. **Missing** -* [ ] Specify the Merklelisation rules for beacon state and blocks and merge `crystallized_state_root` and `active_state_root` ([issue 54](https://github.com/ethereum/eth2.0-specs/issues/54)) * [ ] Specify the rules around acceptable values for `pow_chain_reference` ([issue 58](https://github.com/ethereum/eth2.0-specs/issues/58)) * [ ] Specify the shard chain blocks, blobs, proposers, etc. * [ ] Specify the deposit contract on the PoW chain in Vyper From 2dfa7bc652f770521ca6dc8d0e2b7ad467d1a0f9 Mon Sep 17 00:00:00 2001 From: Chih Cheng Liang Date: Thu, 15 Nov 2018 23:51:32 +0100 Subject: [PATCH 36/37] Fix a broken markdown table (#134) --- specs/core/1_shard-data-chains.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/specs/core/1_shard-data-chains.md b/specs/core/1_shard-data-chains.md index 6cb6982ee..c01c6e98f 100644 --- a/specs/core/1_shard-data-chains.md +++ b/specs/core/1_shard-data-chains.md @@ -16,9 +16,10 @@ Ethereum 2.0 consists of a central beacon chain along with `SHARD_COUNT` shard c Phase 1 depends upon all of the constants defined in [Phase 0](0_beacon-chain.md#constants) in addition to the following: -| Constant | Value | Unit | Approximation | -| `CHUNK_SIZE` | 2**8 (= 256) | bytes | -| `MAX_SHARD_BLOCK_SIZE` | 2**15 (= 32768) | bytes | +| Constant | Value | Unit | Approximation | +|------------------------|-----------------|-------|---------------| +| `CHUNK_SIZE` | 2**8 (= 256) | bytes | | +| `MAX_SHARD_BLOCK_SIZE` | 2**15 (= 32768) | bytes | | ## Data Structures From 0f6ec916c7adc02e48b5a7670205da36d85267eb Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 16 Nov 2018 07:52:06 +0900 Subject: [PATCH 37/37] add design goals and links to phases in readme (#132) * add design goals and links to phases in readme * Update README.md --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 2d5a598fb..2c4b76f38 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,17 @@ [![Join the chat at https://gitter.im/ethereum/sharding](https://badges.gitter.im/ethereum/sharding.svg)](https://gitter.im/ethereum/sharding?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) This repo hosts the current eth2.0 specifications. Discussions about design rationale and proposed changes can be brought up and discussed as issues. Solidified, agreed upon changes to spec can be made through pull requests. + +# Specs + +Core specifications for eth2.0 client validation can be found in [specs/core](specs/core). These are divided into phases. Each subsequent phase depends upon the prior. The current phases specified are: +* [Phase 0 -- The Beacon Chain](specs/core/0_beacon-chain.md) +* [Phase 1 -- Shard Data Chains](specs/core/1_shard-data-chains.md) + +## Design goals +The following are the broad design goals for Ethereum 2.0: +* to minimize complexity, even at the cost of some losses in efficiency +* to remain live through major network partitions and when very large portions of nodes going offline +* to select all components such that they are either quantum secure or can be easily swapped out for quantum secure counterparts when available +* to utilize crypto and design techniques that allow for a large participation of validators in total and per unit time +* to allow for a typical consumer laptop with `O(C)` resources to process/validate `O(1)` shards (including any system level validation such as the beacon chain)