From ea7ad28298e6060943aab69a29d6a99d2b752ae8 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Fri, 16 Nov 2018 17:33:49 -0600 Subject: [PATCH 1/7] simplify merkle tree chunking * pack small items tightly to fit more items in single chunk, decreasing the number of hash operations needed * remove chunk padding - hash algorithm will pad to its own block size anyway * express data length in number of items instead of binary bytes at leaf level (equivalent) --- specs/simple-serialize.md | 42 ++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/specs/simple-serialize.md b/specs/simple-serialize.md index defe9bba7..9c7744e68 100644 --- a/specs/simple-serialize.md +++ b/specs/simple-serialize.md @@ -402,40 +402,32 @@ Return the hash of the serialization of the value. 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 +# Merkle tree hash of a list of homogenous, non-empty 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)] + + if len(lst) == 0: + # Handle empty list case + chunkz = [b'\x00' * CHUNKSIZE] + elif len(lst[0]) < CHUNKSIZE: + # See how many items fit in a chunk + items_per_chunk = CHUNKSIZE // len(lst[0]) + + # Build a list of chunks based on the number of items in the chunk + chunkz = [b''.join(lst[i:i+items_per_chunk]) for i in range(0, len(lst), items_per_chunk)] + else: + # Leave large items alone + chunkz = lst + # 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) + return hash((chunkz[0] + datalen) ``` To `tree_hash` a list, we simply do: From a217e9b32c833d2f18f52fde5005fe4a7715c821 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 20 Nov 2018 08:12:56 -0600 Subject: [PATCH 2/7] tree_ssz: fix extra parens Co-Authored-By: arnetheduck --- 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 9c7744e68..e7df673a8 100644 --- a/specs/simple-serialize.md +++ b/specs/simple-serialize.md @@ -427,7 +427,7 @@ def merkle_hash(lst): 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] + datalen) + return hash(chunkz[0] + datalen) ``` To `tree_hash` a list, we simply do: From 745524b7624e2158bfb2834c4354ea48230b2f9a Mon Sep 17 00:00:00 2001 From: vbuterin Date: Wed, 21 Nov 2018 01:21:07 -0500 Subject: [PATCH 3/7] Added whistleblower reward (#144) * Added whistleblower reward * Update 0_beacon-chain.md * Fixed proposer.deposit += ... --- specs/core/0_beacon-chain.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index bd8c868cb..9ac39501c 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -50,6 +50,7 @@ The primary source of load on the beacon chain are "attestations". Attestations | `MIN_WITHDRAWAL_PERIOD` | 2**13 (= 8192) | slots | ~14 hours | | `DELETION_PERIOD` | 2**22 (= 4,194,304) | slots | ~290 days | | `COLLECTIVE_PENALTY_CALCULATION_PERIOD` | 2**20 (= 1,048,576) | slots | ~2.4 months | +| `SLASHING_WHISTLEBLOWER_REWARD_DENOMINATOR` | 2**9 (= 512) | | `BASE_REWARD_QUOTIENT` | 2**15 (= 32,768) | — | | `MAX_VALIDATOR_CHURN_QUOTIENT` | 2**5 (= 32) | — | | `POW_HASH_VOTING_PERIOD` | 2**10 (=1024) | - | @@ -732,13 +733,16 @@ def add_validator(validators: List[ValidatorRecord], ### Routine for removing a validator ```python -def exit_validator(index, state, penalize, current_slot): +def exit_validator(index, state, block, penalize, current_slot): validator = state.validators[index] validator.exit_slot = current_slot validator.exit_seq = state.current_exit_seq state.current_exit_seq += 1 if penalize: validator.status = PENALIZED + whistleblower_xfer_amount = validator.deposit // SLASHING_WHISTLEBLOWER_REWARD_DENOMINATOR + validator.deposit -= whistleblower_xfer_amount + get_beacon_proposer(state, block.slot).deposit += whistleblower_xfer_amount state.deposits_penalized_in_period[current_slot // COLLECTIVE_PENALTY_CALCULATION_PERIOD] += validator.balance else: validator.status = PENDING_EXIT @@ -881,7 +885,7 @@ Perform the following checks: * Let `fork_version = pre_fork_version if block.slot < fork_slot_number else post_fork_version`. Verify that `BLSVerify(pubkey=validators[data.validator_index].pubkey, msg=hash(LOGOUT_MESSAGE + bytes8(fork_version)), sig=data.signature)` * Verify that `validators[validator_index].status == ACTIVE`. -Run `exit_validator(data.validator_index, state, penalize=False, current_slot=block.slot)`. +Run `exit_validator(data.validator_index, state, block, penalize=False, current_slot=block.slot)`. #### CASPER_SLASHING @@ -903,7 +907,7 @@ Perform the following checks: * Let `intersection = [x for x in vote1_aggregate_sig_indices if x in vote2_aggregate_sig_indices]`. Verify that `len(intersection) >= 1`. * Verify that `vote1_data.justified_slot < vote2_data.justified_slot < vote2_data.slot <= vote1_data.slot`. -For each validator index `v` in `intersection`, if `state.validators[v].status` does not equal `PENALIZED`, then run `exit_validator(v, state, penalize=True, current_slot=block.slot)` +For each validator index `v` in `intersection`, if `state.validators[v].status` does not equal `PENALIZED`, then run `exit_validator(v, state, block, penalize=True, current_slot=block.slot)` #### PROPOSER_SLASHING @@ -1102,7 +1106,7 @@ def change_validators(validators: List[ValidatorRecord], current_slot: int) -> N * 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)` +* For any validator with index `v` with balance less than `MIN_ONLINE_DEPOSIT_SIZE` and status `ACTIVE`, run `exit_validator(v, state, block, 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` From fec46f9e0973753cb62b4d8a3553d472b9630a34 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Wed, 21 Nov 2018 18:37:14 -1000 Subject: [PATCH 4/7] Typo in Vyper contract --- specs/core/0_beacon-chain.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 9ac39501c..2b0cdd661 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -562,7 +562,7 @@ total_deposit_count: int128 def deposit(deposit_params: bytes[2048]): index:int128 = self.total_deposit_count + 2**POW_CONTRACT_MERKLE_TREE_DEPTH msg_gwei_bytes8: bytes[8] = slice(as_bytes32(msg.value / 10**9), 24, 8) - timestamp_bytes8: bytes[8] = slice(s_bytes32(block.timestamp), 24, 8) + timestamp_bytes8: bytes[8] = slice(as_bytes32(block.timestamp), 24, 8) deposit_data: bytes[2064] = concat(deposit_params, msg_gwei_bytes8, timestamp_bytes8) log.HashChainValue(self.receipt_tree[1], deposit_data, self.total_deposit_count) @@ -704,7 +704,7 @@ def add_validator(validators: List[ValidatorRecord], current_slot: int) -> int: # if following assert fails, validator induction failed # move on to next validator registration log - signed_message = as_bytes32(pubkey) + as_bytes2(withdrawal_shard) + withdrawal_address + randao_commitment + signed_message = bytes32(pubkey) + bytes2(withdrawal_shard) + withdrawal_address + randao_commitment assert BLSVerify(pub=pubkey, msg=hash(signed_message), sig=proof_of_possession) From f1b78cf01e6250bd2b206e2b173111a5e4f063b9 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 22 Nov 2018 16:28:24 +0800 Subject: [PATCH 5/7] ss: Add CC0 copyright declaration (#155) --- specs/simple-serialize.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/specs/simple-serialize.md b/specs/simple-serialize.md index e7df673a8..9e29558d7 100644 --- a/specs/simple-serialize.md +++ b/specs/simple-serialize.md @@ -457,3 +457,7 @@ return hash(b''.join([tree_hash(getattr(x, field)) for field in sorted(value.fie | Nim | [ https://github.com/status-im/nim-beacon-chain/blob/master/beacon_chain/ssz.nim ](https://github.com/status-im/nim-beacon-chain/blob/master/beacon_chain/ssz.nim) | Nim Implementation maintained SSZ. | | Rust | [ https://github.com/paritytech/shasper/tree/master/util/ssz ](https://github.com/paritytech/shasper/tree/master/util/ssz) | Shasper implementation of SSZ maintained by ParityTech. | | Javascript | [ https://github.com/ChainSafeSystems/ssz-js/blob/master/src/index.js ](https://github.com/ChainSafeSystems/ssz-js/blob/master/src/index.js) | Javascript Implementation maintained SSZ | + + +## Copyright +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). From 925ac0ecc0454228d19a950a51ef074ec5219db3 Mon Sep 17 00:00:00 2001 From: Terence Tsao Date: Thu, 22 Nov 2018 06:32:15 -1000 Subject: [PATCH 6/7] use convert function in Vyper --- specs/core/0_beacon-chain.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 2b0cdd661..13f7fe5fc 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -561,8 +561,8 @@ total_deposit_count: int128 @public def deposit(deposit_params: bytes[2048]): index:int128 = self.total_deposit_count + 2**POW_CONTRACT_MERKLE_TREE_DEPTH - msg_gwei_bytes8: bytes[8] = slice(as_bytes32(msg.value / 10**9), 24, 8) - timestamp_bytes8: bytes[8] = slice(as_bytes32(block.timestamp), 24, 8) + msg_gwei_bytes8: bytes[8] = slice(convert(msg.value / 10**9, 'bytes32'), 24, 8) + timestamp_bytes8: bytes[8] = slice(convert(block.timestamp, 'bytes32'), 24, 8) deposit_data: bytes[2064] = concat(deposit_params, msg_gwei_bytes8, timestamp_bytes8) log.HashChainValue(self.receipt_tree[1], deposit_data, self.total_deposit_count) From e2193b13240159b8eeee7ffd20b17e37fc5fd43e Mon Sep 17 00:00:00 2001 From: 4000D Date: Fri, 23 Nov 2018 13:54:11 +0900 Subject: [PATCH 7/7] fix hyperlink --- specs/core/0_beacon-chain.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 9ac39501c..e48f7a91c 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -60,8 +60,8 @@ 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); 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%. +* 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. @@ -853,7 +853,7 @@ Extend the list of `AttestationRecord` objects in the `state` with those include ### Verify proposer signature -Let `proposal_hash = hash(ProposalSignedData(fork_version, block.slot, 2**64 - 1, block_hash_without_sig))` where `block_hash_without_sig` is the hash of the block except setting `proposer_signature` to `[0, 0]`. +Let `proposal_hash = hash(ProposalSignedData(fork_version, block.slot, 2**64 - 1, block_hash_without_sig))` where `block_hash_without_sig` is the hash of the block except setting `proposer_signature` to `[0, 0]`. Verify that `BLSVerify(pubkey=get_beacon_proposer(state, block.slot).pubkey, data=proposal_hash, sig=block.proposer_signature)` passes. @@ -1009,7 +1009,7 @@ For every shard number `shard` for which a crosslink committee exists in the cyc * 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`. - + #### PoW chain related rules If `last_state_recalculation_slot % POW_HASH_VOTING_PERIOD == 0`, then: