From 0759e170a7faafca0cee7b9b8929b0c6e15bcf77 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Thu, 7 Mar 2019 01:38:03 -0600 Subject: [PATCH 01/36] High/low balance separation See #685 for reasoning --- specs/core/0_beacon-chain.md | 95 +++++++++++++++++++++++++++--------- 1 file changed, 72 insertions(+), 23 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index bd709218a..3c2d90b69 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -64,6 +64,10 @@ - [`get_epoch_start_slot`](#get_epoch_start_slot) - [`is_active_validator`](#is_active_validator) - [`get_active_validator_indices`](#get_active_validator_indices) + - [`get_balance`](#get_balance) + - [`set_balance`](#set_balance) + - [`increase_balance`](#increase_balance) + - [`decrease_balance`](#decrease_balance) - [`get_permuted_index`](#get_permuted_index) - [`split`](#split) - [`get_epoch_committee_count`](#get_epoch_committee_count) @@ -205,10 +209,10 @@ Code snippets appearing in `this style` are to be interpreted as Python code. | Name | Value | Unit | | - | - | :-: | -| `MIN_DEPOSIT_AMOUNT` | `2**0 * 10**9` (= 1,000,000,000) | Gwei | +| `MIN_DEPOSIT_AMOUNT` | `2**0 * 10**9` (= 2,000,000,000) | Gwei | | `MAX_DEPOSIT_AMOUNT` | `2**5 * 10**9` (= 32,000,000,000) | Gwei | -| `FORK_CHOICE_BALANCE_INCREMENT` | `2**0 * 10**9` (= 1,000,000,000) | Gwei | | `EJECTION_BALANCE` | `2**4 * 10**9` (= 16,000,000,000) | Gwei | +| `HIGH_BALANCE_INCREMENT` | `10 ** 9` (= 1,000,000,000) | Gwei | ### Initial values @@ -516,7 +520,7 @@ The following data structures are defined as [SimpleSerialize (SSZ)](https://git # Validator registry 'validator_registry': [Validator], - 'validator_balances': ['uint64'], + 'low_balances': ['uint32'], 'validator_registry_update_epoch': 'uint64', # Randomness and committees @@ -570,6 +574,8 @@ The following data structures are defined as [SimpleSerialize (SSZ)](https://git 'initiated_exit': 'bool', # Was the validator slashed 'slashed': 'bool', + # Rounded balance + 'high_balance': 'uint32' } ``` @@ -749,6 +755,45 @@ def get_active_validator_indices(validators: List[Validator], epoch: Epoch) -> L return [i for i, v in enumerate(validators) if is_active_validator(v, epoch)] ``` +### `get_balance` + +```python +def get_balance(state: BeaconState, index: int) -> int: + return ( + state.validator_registry[index].high_balance * HIGH_BALANCE_INCREMENT + + state.low_balances[index] + ) +``` +#### `set_balance` + +````python +def set_balance(state: BeaconState, index: int, new_balance: int) -> None: + validator = state.validator_registry[index] + HALF_INCREMENT = HIGH_BALANCE_INCREMENT // 2 + if ( + validator.rounded_balance * HIGH_BALANCE_INCREMENT > new_balance or + validator.rounded_balance * HIGH_BALANCE_INCREMENT + HALF_INCREMENT * 3 < new_balance + ): + validator.rounded_balance = new_balance // HIGH_BALANCE_INCREMENT + state.validator_fractional_balances[index] = ( + new_balance - validator.rounded_balance * HIGH_BALANCE_INCREMENT + ) +```` + +#### `increase_balance` + +````python +def increase_balance(state: BeaconState, index: int, delta: int) -> None: + set_balance(state, index, get_balance(state, index) + delta) +```` + +#### `decrease_balance` + +````python +def decrease_balance(state: BeaconState, index: int, delta: int) -> None: + set_balance(state, index, get_balance(state, index) - delta) +```` + ### `get_permuted_index` ```python @@ -1105,7 +1150,7 @@ def get_effective_balance(state: BeaconState, index: ValidatorIndex) -> Gwei: """ Return the effective balance (also known as "balance at stake") for a validator with the given ``index``. """ - return min(state.validator_balances[index], MAX_DEPOSIT_AMOUNT) + return min(get_balance(state, index), MAX_DEPOSIT_AMOUNT) ``` ### `get_total_balance` @@ -1351,17 +1396,18 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: withdrawable_epoch=FAR_FUTURE_EPOCH, initiated_exit=False, slashed=False, + high_balance=0 ) # Note: In phase 2 registry indices that have been withdrawn for a long time will be recycled. state.validator_registry.append(validator) - state.validator_balances.append(amount) + state.low_balances.append(0) + set_balance(state, len(state.validator_registry)-1, amount) else: # Increase balance by deposit amount index = validator_pubkeys.index(pubkey) assert state.validator_registry[index].withdrawal_credentials == withdrawal_credentials - - state.validator_balances[index] += amount + increase_balance(state, index, amount) ``` ### Routines for updating validator status @@ -1426,8 +1472,8 @@ def slash_validator(state: BeaconState, index: ValidatorIndex) -> None: whistleblower_index = get_beacon_proposer_index(state, state.slot) whistleblower_reward = get_effective_balance(state, index) // WHISTLEBLOWER_REWARD_QUOTIENT - state.validator_balances[whistleblower_index] += whistleblower_reward - state.validator_balances[index] -= whistleblower_reward + increase_balance(state, whistleblower_index, whistleblower_reward) + decrease_balance(state, index, whistleblower_reward) validator.slashed = True validator.withdrawable_epoch = get_current_epoch(state) + LATEST_SLASHED_EXIT_LENGTH ``` @@ -1545,7 +1591,7 @@ def get_genesis_beacon_state(genesis_validator_deposits: List[Deposit], # Validator registry validator_registry=[], - validator_balances=[], + low_balances=[], validator_registry_update_epoch=GENESIS_EPOCH, # Randomness and committees @@ -1657,9 +1703,12 @@ def lmd_ghost(store: Store, start_state: BeaconState, start_block: BeaconBlock) for validator_index in active_validator_indices ] + # Use the rounded-balance-with-hysteresis supplied by the protocol for fork + # choice voting. This reduces the number of recomputations that need to be + # made for optimized implementations that precompute and save data def get_vote_count(block: BeaconBlock) -> int: return sum( - get_effective_balance(start_state.validator_balances[validator_index]) // FORK_CHOICE_BALANCE_INCREMENT + start_state.validator_registry[validator_index].high_balance for validator_index, target in attestation_targets if get_ancestor(store, target, block.slot) == block ) @@ -1956,12 +2005,12 @@ def process_transfer(state: BeaconState, transfer: Transfer) -> None: Note that this function mutates ``state``. """ # Verify the amount and fee aren't individually too big (for anti-overflow purposes) - assert state.validator_balances[transfer.sender] >= max(transfer.amount, transfer.fee) + assert get_balance(state, transfer.sender) >= max(transfer.amount, transfer.fee) # Verify that we have enough ETH to send, and that after the transfer the balance will be either # exactly zero or at least MIN_DEPOSIT_AMOUNT assert ( - state.validator_balances[transfer.sender] == transfer.amount + transfer.fee or - state.validator_balances[transfer.sender] >= transfer.amount + transfer.fee + MIN_DEPOSIT_AMOUNT + get_balance(state, transfer.sender) == transfer.amount + transfer.fee or + get_balance(state, transfer.sender) >= transfer.amount + transfer.fee + MIN_DEPOSIT_AMOUNT ) # A transfer is valid in only one slot assert state.slot == transfer.slot @@ -1983,9 +2032,9 @@ def process_transfer(state: BeaconState, transfer: Transfer) -> None: domain=get_domain(state.fork, slot_to_epoch(transfer.slot), DOMAIN_TRANSFER) ) # Process the transfer - state.validator_balances[transfer.sender] -= transfer.amount + transfer.fee - state.validator_balances[transfer.recipient] += transfer.amount - state.validator_balances[get_beacon_proposer_index(state, state.slot)] += transfer.fee + decrease_balance(state, transfer.sender, transfer.amount + transfer.fee) + increase_balance(state, transfer.recipient, transfer.amount) + increase_balance(state, get_beacon_proposer_index(state, state.slot), transfer.fee) ``` ### Per-epoch processing @@ -2320,10 +2369,10 @@ def apply_rewards(state: BeaconState) -> None: deltas1 = get_justification_and_finalization_deltas(state) deltas2 = get_crosslink_deltas(state) for i in range(len(state.validator_registry)): - state.validator_balances[i] = max( + set_balance(state, i, max( 0, - state.validator_balances[i] + deltas1[0][i] + deltas2[0][i] - deltas1[1][i] - deltas2[1][i] - ) + get_balance(state, i) + deltas1[0][i] + deltas2[0][i] - deltas1[1][i] - deltas2[1][i] + )) ``` #### Ejections @@ -2337,7 +2386,7 @@ def process_ejections(state: BeaconState) -> None: and eject active validators with balance below ``EJECTION_BALANCE``. """ for index in get_active_validator_indices(state.validator_registry, get_current_epoch(state)): - if state.validator_balances[index] < EJECTION_BALANCE: + if get_balance(state, index) < EJECTION_BALANCE: exit_validator(state, index) ``` @@ -2380,7 +2429,7 @@ def update_validator_registry(state: BeaconState) -> None: # Activate validators within the allowable balance churn balance_churn = 0 for index, validator in enumerate(state.validator_registry): - if validator.activation_epoch == FAR_FUTURE_EPOCH and state.validator_balances[index] >= MAX_DEPOSIT_AMOUNT: + if validator.activation_epoch == FAR_FUTURE_EPOCH and get_balance(state, index) >= MAX_DEPOSIT_AMOUNT: # Check the balance churn would be within the allowance balance_churn += get_effective_balance(state, index) if balance_churn > max_balance_churn: @@ -2461,7 +2510,7 @@ def process_slashings(state: BeaconState) -> None: get_effective_balance(state, index) * min(total_penalties * 3, total_balance) // total_balance, get_effective_balance(state, index) // MIN_PENALTY_QUOTIENT ) - state.validator_balances[index] -= penalty + decrease_balance(state, index, penalty) ``` ```python From be4b912373b9ee89851e217e8f500f444ef0e1fa Mon Sep 17 00:00:00 2001 From: vbuterin Date: Thu, 7 Mar 2019 04:02:53 -0600 Subject: [PATCH 02/36] Added underflow checking to decrease_balance --- specs/core/0_beacon-chain.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 3c2d90b69..b0b3dbb2a 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -791,7 +791,8 @@ def increase_balance(state: BeaconState, index: int, delta: int) -> None: ````python def decrease_balance(state: BeaconState, index: int, delta: int) -> None: - set_balance(state, index, get_balance(state, index) - delta) + cur_balance = get_balance(state, index) + set_balance(state, index, cur_balance - delta if cur_balance >= delta else 0) ```` ### `get_permuted_index` From f9a07f7653890fd74c6c023182ccb56004b5579d Mon Sep 17 00:00:00 2001 From: vbuterin Date: Thu, 7 Mar 2019 04:04:05 -0600 Subject: [PATCH 03/36] Fixed MIN_DEPOSIT_AMOUNT --- 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 b0b3dbb2a..c548dbe14 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -209,10 +209,10 @@ Code snippets appearing in `this style` are to be interpreted as Python code. | Name | Value | Unit | | - | - | :-: | -| `MIN_DEPOSIT_AMOUNT` | `2**0 * 10**9` (= 2,000,000,000) | Gwei | +| `MIN_DEPOSIT_AMOUNT` | `10**9` (= 1,000,000,000) | Gwei | | `MAX_DEPOSIT_AMOUNT` | `2**5 * 10**9` (= 32,000,000,000) | Gwei | | `EJECTION_BALANCE` | `2**4 * 10**9` (= 16,000,000,000) | Gwei | -| `HIGH_BALANCE_INCREMENT` | `10 ** 9` (= 1,000,000,000) | Gwei | +| `HIGH_BALANCE_INCREMENT` | `10**9` (= 1,000,000,000) | Gwei | ### Initial values From bf6bdbb0210ee8020cac21b3f731178caad03ab7 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 11 Mar 2019 12:38:11 -0600 Subject: [PATCH 04/36] cleanup minor var errors --- specs/core/0_beacon-chain.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index f5fa2128a..ec9eedb51 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -768,12 +768,12 @@ def set_balance(state: BeaconState, index: int, new_balance: int) -> None: validator = state.validator_registry[index] HALF_INCREMENT = HIGH_BALANCE_INCREMENT // 2 if ( - validator.rounded_balance * HIGH_BALANCE_INCREMENT > new_balance or - validator.rounded_balance * HIGH_BALANCE_INCREMENT + HALF_INCREMENT * 3 < new_balance + validator.high_balance * HIGH_BALANCE_INCREMENT > new_balance or + validator.high_balance * HIGH_BALANCE_INCREMENT + HALF_INCREMENT * 3 < new_balance ): - validator.rounded_balance = new_balance // HIGH_BALANCE_INCREMENT - state.validator_fractional_balances[index] = ( - new_balance - validator.rounded_balance * HIGH_BALANCE_INCREMENT + validator.high_balance = new_balance // HIGH_BALANCE_INCREMENT + state.low_balances[index] = ( + new_balance - validator.high_balance * HIGH_BALANCE_INCREMENT ) ```` From a7544864d5de8eaa27f4630d2740f4acc8383d99 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 12 Mar 2019 10:02:52 +0000 Subject: [PATCH 05/36] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index ec9eedb51..7d59a9e6d 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -203,10 +203,10 @@ Code snippets appearing in `this style` are to be interpreted as Python code. | Name | Value | Unit | | - | - | :-: | -| `MIN_DEPOSIT_AMOUNT` | `10**9` (= 1,000,000,000) | Gwei | +| `MIN_DEPOSIT_AMOUNT` | `2**0 * 10**9` (= 1,000,000,000) | Gwei | | `MAX_DEPOSIT_AMOUNT` | `2**5 * 10**9` (= 32,000,000,000) | Gwei | | `EJECTION_BALANCE` | `2**4 * 10**9` (= 16,000,000,000) | Gwei | -| `HIGH_BALANCE_INCREMENT` | `10**9` (= 1,000,000,000) | Gwei | +| `HIGH_BALANCE_INCREMENT` | `2**0 * 10**9` (= 1,000,000,000) | Gwei | ### Initial values @@ -440,7 +440,7 @@ The types are defined topologically to aid in facilitating an executable version # Was the validator slashed 'slashed': 'bool', # Rounded balance - 'high_balance': 'uint32' + 'high_balance': 'uint64' } ``` @@ -756,25 +756,17 @@ def get_active_validator_indices(validators: List[Validator], epoch: Epoch) -> L ```python def get_balance(state: BeaconState, index: int) -> int: - return ( - state.validator_registry[index].high_balance * HIGH_BALANCE_INCREMENT + - state.low_balances[index] - ) + return state.validator_registry[index].high_balance + state.low_balances[index] ``` #### `set_balance` ````python -def set_balance(state: BeaconState, index: int, new_balance: int) -> None: +def set_balance(state: BeaconState, index: int, balance: int) -> None: validator = state.validator_registry[index] HALF_INCREMENT = HIGH_BALANCE_INCREMENT // 2 - if ( - validator.high_balance * HIGH_BALANCE_INCREMENT > new_balance or - validator.high_balance * HIGH_BALANCE_INCREMENT + HALF_INCREMENT * 3 < new_balance - ): - validator.high_balance = new_balance // HIGH_BALANCE_INCREMENT - state.low_balances[index] = ( - new_balance - validator.high_balance * HIGH_BALANCE_INCREMENT - ) + if validator.high_balance > balance or validator.high_balance + 3 * HALF_INCREMENT < balance: + validator.high_balance = balance - balance % HIGH_BALANCE_INCREMENT + state.low_balances[index] = balance - validator.high_balance ```` #### `increase_balance` From 0a349f8bdc31d08f2c6f4a5b8e98427845c1716e Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 12 Mar 2019 15:58:31 +0000 Subject: [PATCH 06/36] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 7d59a9e6d..63737962d 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -601,7 +601,7 @@ The types are defined topologically to aid in facilitating an executable version # Validator registry 'validator_registry': [Validator], - 'low_balances': ['uint32'], + 'balances': ['uint64'], 'validator_registry_update_epoch': 'uint64', # Randomness and committees @@ -756,7 +756,7 @@ def get_active_validator_indices(validators: List[Validator], epoch: Epoch) -> L ```python def get_balance(state: BeaconState, index: int) -> int: - return state.validator_registry[index].high_balance + state.low_balances[index] + return state.balances[index] ``` #### `set_balance` @@ -766,7 +766,7 @@ def set_balance(state: BeaconState, index: int, balance: int) -> None: HALF_INCREMENT = HIGH_BALANCE_INCREMENT // 2 if validator.high_balance > balance or validator.high_balance + 3 * HALF_INCREMENT < balance: validator.high_balance = balance - balance % HIGH_BALANCE_INCREMENT - state.low_balances[index] = balance - validator.high_balance + state.balances[index] = balance ```` #### `increase_balance` @@ -1377,7 +1377,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: # Note: In phase 2 registry indices that have been withdrawn for a long time will be recycled. state.validator_registry.append(validator) - state.low_balances.append(0) + state.balances.append(0) set_balance(state, len(state.validator_registry)-1, amount) else: # Increase balance by deposit amount @@ -1567,7 +1567,7 @@ def get_genesis_beacon_state(genesis_validator_deposits: List[Deposit], # Validator registry validator_registry=[], - low_balances=[], + balances=[], validator_registry_update_epoch=GENESIS_EPOCH, # Randomness and committees From 4442dfffb97c04a0697dd84bb85ba692613a3fff Mon Sep 17 00:00:00 2001 From: vbuterin Date: Wed, 13 Mar 2019 21:42:49 -0500 Subject: [PATCH 07/36] Fair proposer selection probability Note that as a side effect, proposer selection becomes less predictable, but I don't feel like this is a large downside. --- specs/core/0_beacon-chain.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index daa1bc108..5ca59c66b 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1014,7 +1014,13 @@ def get_beacon_proposer_index(state: BeaconState, assert previous_epoch <= epoch <= next_epoch - first_committee, _ = get_crosslink_committees_at_slot(state, slot, registry_change)[0] + first_committee, _ = get_crosslink_committees_at_slot(state, slot, registry_change)[0] i = 0 + while i < len(first_committee): + rand_byte = hash(generate_seed(get_current_epoch(state)) + int_to_bytes8(i // 32))[i % 32] + candidate = first_committee[(epoch % i) % len(first_committee)] + if get_effective_balance(state, candidate) * 256 > MAX_DEPOSIT_AMOUNT * rand_byte: + return candidate + i += 1 return first_committee[epoch % len(first_committee)] ``` From 23d15f51a799fa9fc3c2a7aa5493b05fcad81568 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 14 Mar 2019 18:57:17 +0000 Subject: [PATCH 08/36] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 5ca59c66b..d0be14e47 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1014,14 +1014,14 @@ def get_beacon_proposer_index(state: BeaconState, assert previous_epoch <= epoch <= next_epoch - first_committee, _ = get_crosslink_committees_at_slot(state, slot, registry_change)[0] i = 0 - while i < len(first_committee): + first_committee, _ = get_crosslink_committees_at_slot(state, slot, registry_change)[0] + i = 0 + while True: rand_byte = hash(generate_seed(get_current_epoch(state)) + int_to_bytes8(i // 32))[i % 32] candidate = first_committee[(epoch % i) % len(first_committee)] if get_effective_balance(state, candidate) * 256 > MAX_DEPOSIT_AMOUNT * rand_byte: return candidate i += 1 - return first_committee[epoch % len(first_committee)] ``` ### `verify_merkle_branch` From bbc51391153169e2d8071ab059b9b5001b5da072 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 14 Mar 2019 19:01:32 +0000 Subject: [PATCH 09/36] Update 0_beacon-chain.md Assuming `epoch % i` is a bug, and you meant `epoch + i`. @vbuterin --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index d0be14e47..f2d06472b 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1018,7 +1018,7 @@ def get_beacon_proposer_index(state: BeaconState, i = 0 while True: rand_byte = hash(generate_seed(get_current_epoch(state)) + int_to_bytes8(i // 32))[i % 32] - candidate = first_committee[(epoch % i) % len(first_committee)] + candidate = first_committee[(epoch + i) % len(first_committee)] if get_effective_balance(state, candidate) * 256 > MAX_DEPOSIT_AMOUNT * rand_byte: return candidate i += 1 From 4a5ef988138772f7c1851b4c72634af217142d2f Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 15 Mar 2019 10:51:08 +0000 Subject: [PATCH 10/36] Move to SHA256 SHA256 is de facto blockchain standard. Standardisation of the hash function is a prerequisite for [full standardisation of BLS12-381 signatures](https://github.com/ethereum/eth2.0-specs/issues/605). Blockchain projects are likely to provide a cheap SHA256 opcods/precompile, and unlikely to provide a Keccak256 equivelent. (Even WASM-enabled blockchains are likely to provide a SHA256 opcode/precompile since WASM does *not* natively support optimised SHA256 CPU instructions.) With Ethereum 2.0 embracing SHA256 the wider industry is more likely to converge towards a unified cross-blockchain communication scheme via Merkle receipts. There are no security blockers with SHA256 (see comments by Dan Boneh [here](https://github.com/ethereum/eth2.0-specs/issues/612#issuecomment-470452562)). --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index daa1bc108..1d474f618 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -660,7 +660,7 @@ def xor(bytes1: Bytes32, bytes2: Bytes32) -> Bytes32: ### `hash` -The hash function is denoted by `hash`. In Phase 0 the beacon chain is deployed with the same hash function as Ethereum 1.0, i.e. Keccak-256 (also incorrectly known as SHA3). +The `hash` function is SHA256. Note: We aim to migrate to a S[T/N]ARK-friendly hash function in a future Ethereum 2.0 deployment phase. From dac43eb564a3da19bf878364295486d0b7c03fb2 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 15 Mar 2019 11:18:06 +0000 Subject: [PATCH 11/36] Simplify deposits Fix #760 --- specs/core/0_beacon-chain.md | 49 +++++++++++------------------------- 1 file changed, 14 insertions(+), 35 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index daa1bc108..2d168cfc2 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -29,7 +29,6 @@ - [`AttestationData`](#attestationdata) - [`AttestationDataAndCustodyBit`](#attestationdataandcustodybit) - [`SlashableAttestation`](#slashableattestation) - - [`DepositInput`](#depositinput) - [`DepositData`](#depositdata) - [`BeaconBlockHeader`](#beaconblockheader) - [`Validator`](#validator) @@ -377,7 +376,7 @@ The types are defined topologically to aid in facilitating an executable version } ``` -#### `DepositInput` +#### `DepositData` ```python { @@ -385,21 +384,10 @@ The types are defined topologically to aid in facilitating an executable version 'pubkey': 'bytes48', # Withdrawal credentials 'withdrawal_credentials': 'bytes32', - # A BLS signature of this `DepositInput` - 'proof_of_possession': 'bytes96', -} -``` - -#### `DepositData` - -```python -{ # Amount in Gwei 'amount': 'uint64', - # Timestamp from deposit contract - 'timestamp': 'uint64', - # Deposit input - 'deposit_input': DepositInput, + # Container self-signature + 'proof_of_possession': 'bytes96', } ``` @@ -512,7 +500,7 @@ The types are defined topologically to aid in facilitating an executable version # Index in the deposit tree 'index': 'uint64', # Data - 'deposit_data': DepositData, + 'data': DepositData, } ``` @@ -1278,19 +1266,12 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: Process a deposit from Ethereum 1.0. Note that this function mutates ``state``. """ - deposit_input = deposit.deposit_data.deposit_input - - # Should equal 8 bytes for deposit_data.amount + - # 8 bytes for deposit_data.timestamp + - # 176 bytes for deposit_data.deposit_input - # It should match the deposit_data in the eth1.0 deposit contract - serialized_deposit_data = serialize(deposit.deposit_data) # Deposits must be processed in order assert deposit.index == state.deposit_index # Verify the Merkle branch merkle_branch_is_valid = verify_merkle_branch( - leaf=hash(serialized_deposit_data), + leaf=hash(serialize(deposit.data)), # 48 + 32 + 8 + 96 = 184 bytes serialisation proof=deposit.proof, depth=DEPOSIT_CONTRACT_TREE_DEPTH, index=deposit.index, @@ -1305,16 +1286,14 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: state.deposit_index += 1 validator_pubkeys = [v.pubkey for v in state.validator_registry] - pubkey = deposit_input.pubkey - amount = deposit.deposit_data.amount - withdrawal_credentials = deposit_input.withdrawal_credentials + pubkey = deposit.data.pubkey if pubkey not in validator_pubkeys: # Verify the proof of possession proof_is_valid = bls_verify( - pubkey=deposit_input.pubkey, - message_hash=signed_root(deposit_input), - signature=deposit_input.proof_of_possession, + pubkey=pubkey, + message_hash=signed_root(deposit.data), + signature=deposit.data.proof_of_possession, domain=get_domain( state.fork, get_current_epoch(state), @@ -1327,7 +1306,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: # Add new validator validator = Validator( pubkey=pubkey, - withdrawal_credentials=withdrawal_credentials, + withdrawal_credentials=deposit.data.withdrawal_credentials, activation_epoch=FAR_FUTURE_EPOCH, exit_epoch=FAR_FUTURE_EPOCH, withdrawable_epoch=FAR_FUTURE_EPOCH, @@ -1337,10 +1316,10 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: # Note: In phase 2 registry indices that have been withdrawn for a long time will be recycled. state.validator_registry.append(validator) - state.validator_balances.append(amount) + state.validator_balances.append(deposit.data.amount) else: # Increase balance by deposit amount - state.validator_balances[validator_pubkeys.index(pubkey)] += amount + state.validator_balances[validator_pubkeys.index(pubkey)] += deposit.data.amount ``` ### Routines for updating validator status @@ -1430,11 +1409,11 @@ The initial deployment phases of Ethereum 2.0 are implemented without consensus ### Deposit arguments -The deposit contract has a single `deposit` function which takes as argument a SimpleSerialize'd `DepositInput`. +The deposit contract has a single `deposit` function which takes as argument a SimpleSerialize'd `DepositData`. ### Withdrawal credentials -One of the `DepositInput` fields is `withdrawal_credentials`. It is a commitment to credentials for withdrawals to shards. The first byte of `withdrawal_credentials` is a version number. As of now the only expected format is as follows: +One of the `DepositData` fields is `withdrawal_credentials`. It is a commitment to credentials for withdrawals to shards. The first byte of `withdrawal_credentials` is a version number. As of now the only expected format is as follows: * `withdrawal_credentials[:1] == BLS_WITHDRAWAL_PREFIX_BYTE` * `withdrawal_credentials[1:] == hash(withdrawal_pubkey)[1:]` where `withdrawal_pubkey` is a BLS pubkey From 58603f276e3dc137599d6684b7e47650f03871b7 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 15 Mar 2019 11:28:55 +0000 Subject: [PATCH 12/36] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 2d168cfc2..9f8bec933 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1271,7 +1271,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: # Verify the Merkle branch merkle_branch_is_valid = verify_merkle_branch( - leaf=hash(serialize(deposit.data)), # 48 + 32 + 8 + 96 = 184 bytes serialisation + leaf=hash(serialize(deposit.data)), # 48 + 32 + 8 + 96 = 184 bytes serialization proof=deposit.proof, depth=DEPOSIT_CONTRACT_TREE_DEPTH, index=deposit.index, From dc4b652f72339063bfbaae378e850d173168c9f6 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 19 Mar 2019 11:03:42 +0000 Subject: [PATCH 13/36] Only slash active validators This is to prevent a spam/DoS attack where validators with zero balance get "slashed" but no validator loses any balance. --- specs/core/0_beacon-chain.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index a631bf2fc..2113472e3 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -2315,8 +2315,8 @@ def process_proposer_slashing(state: BeaconState, assert slot_to_epoch(proposer_slashing.header_1.slot) == slot_to_epoch(proposer_slashing.header_2.slot) # But the headers are different assert proposer_slashing.header_1 != proposer_slashing.header_2 - # Proposer is not yet slashed - assert proposer.slashed is False + # Proposer is active and not already slashed + assert is_active_validator(proposer) and proposer.slashed is False # Signatures are valid for header in (proposer_slashing.header_1, proposer_slashing.header_2): assert bls_verify( @@ -2355,6 +2355,7 @@ def process_attester_slashing(state: BeaconState, index for index in attestation1.validator_indices if ( index in attestation2.validator_indices and + is_active_validator(state.validator_registry[index]) and state.validator_registry[index].slashed is False ) ] From 2b454d57f11d8e1bde78dd1aa83116df2b2417ee Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 19 Mar 2019 11:08:17 +0000 Subject: [PATCH 14/36] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 2113472e3..9ed620b83 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -59,6 +59,7 @@ - [`get_current_epoch`](#get_current_epoch) - [`get_epoch_start_slot`](#get_epoch_start_slot) - [`is_active_validator`](#is_active_validator) + - [`is_slashable_validator`](#is_slashable_validator) - [`get_active_validator_indices`](#get_active_validator_indices) - [`get_permuted_index`](#get_permuted_index) - [`split`](#split) @@ -737,6 +738,18 @@ def is_active_validator(validator: Validator, epoch: Epoch) -> bool: return validator.activation_epoch <= epoch < validator.exit_epoch ``` +### `is_slashable_validator` +```python +def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool: + """ + Check if ``validator`` is slashable. + """ + return ( + validator.activation_epoch <= epoch < validator.withdrawable_epoch and + validator.slashed is False + ) +``` + ### `get_active_validator_indices` ```python @@ -2315,8 +2328,8 @@ def process_proposer_slashing(state: BeaconState, assert slot_to_epoch(proposer_slashing.header_1.slot) == slot_to_epoch(proposer_slashing.header_2.slot) # But the headers are different assert proposer_slashing.header_1 != proposer_slashing.header_2 - # Proposer is active and not already slashed - assert is_active_validator(proposer) and proposer.slashed is False + # Check proposer is slashable + assert is_slashable_validator(proposer) # Signatures are valid for header in (proposer_slashing.header_1, proposer_slashing.header_2): assert bls_verify( @@ -2355,8 +2368,7 @@ def process_attester_slashing(state: BeaconState, index for index in attestation1.validator_indices if ( index in attestation2.validator_indices and - is_active_validator(state.validator_registry[index]) and - state.validator_registry[index].slashed is False + is_slashable_validator(state.validator_registry[index]) ) ] assert len(slashable_indices) >= 1 From 0c383ce4a1d4770bdb21975023a2ca7a3ef5f522 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 19 Mar 2019 11:11:18 +0000 Subject: [PATCH 15/36] Update 0_beacon-chain.md --- 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 9ed620b83..d377b8d45 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1412,7 +1412,6 @@ def slash_validator(state: BeaconState, index: ValidatorIndex) -> None: Note that this function mutates ``state``. """ validator = state.validator_registry[index] - assert state.slot < get_epoch_start_slot(validator.withdrawable_epoch) # [TO BE REMOVED IN PHASE 2] exit_validator(state, index) state.latest_slashed_balances[get_current_epoch(state) % LATEST_SLASHED_EXIT_LENGTH] += get_effective_balance(state, index) From e91036cfc9fbf9d05b03da0180ed5be95cc916ca Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 19 Mar 2019 11:12:50 +0000 Subject: [PATCH 16/36] Update 0_beacon-chain.md --- 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 d377b8d45..4a6170418 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -2328,7 +2328,7 @@ def process_proposer_slashing(state: BeaconState, # But the headers are different assert proposer_slashing.header_1 != proposer_slashing.header_2 # Check proposer is slashable - assert is_slashable_validator(proposer) + assert is_slashable_validator(proposer, get_current_epoch(state)) # Signatures are valid for header in (proposer_slashing.header_1, proposer_slashing.header_2): assert bls_verify( @@ -2367,7 +2367,7 @@ def process_attester_slashing(state: BeaconState, index for index in attestation1.validator_indices if ( index in attestation2.validator_indices and - is_slashable_validator(state.validator_registry[index]) + is_slashable_validator(state.validator_registry[index], get_current_epoch(state)) ) ] assert len(slashable_indices) >= 1 From dd39d25c86d812e7d5ac24e6bc5f043426e3617d Mon Sep 17 00:00:00 2001 From: vbuterin Date: Tue, 19 Mar 2019 09:32:06 -0500 Subject: [PATCH 17/36] Replace committee exponential backoff with max progress Removes the mechanism that only rotates committees if blocks have been finalized and every shard has been crosslinked or at exponentially decreasing intervals, and replaces it with a rule that shard committees can only progress a maximum of 64 epochs at a time to preserve the invariant that maximum possible work required per epoch for a validator is O(1). --- specs/core/0_beacon-chain.md | 95 +++++++++++------------------------- 1 file changed, 28 insertions(+), 67 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index a631bf2fc..6877b9358 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -185,8 +185,10 @@ Code snippets appearing in `this style` are to be interpreted as Python code. | `MAX_INDICES_PER_SLASHABLE_VOTE` | `2**12` (= 4,096) | | `MAX_EXIT_DEQUEUES_PER_EPOCH` | `2**2` (= 4) | | `SHUFFLE_ROUND_COUNT` | 90 | +| `MAX_CROSSLINK_EPOCHS` | `2**6` (= 64) | * For the safety of crosslinks `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.) +* `MAX_CROSSLINK_EPOCHS` should be a small constant times `SHARD_COUNT // EPOCH_LENGTH` ### Deposit contract @@ -598,12 +600,7 @@ The types are defined topologically to aid in facilitating an executable version # Randomness and committees 'latest_randao_mixes': ['bytes32', LATEST_RANDAO_MIXES_LENGTH], - 'previous_shuffling_start_shard': 'uint64', 'current_shuffling_start_shard': 'uint64', - 'previous_shuffling_epoch': 'uint64', - 'current_shuffling_epoch': 'uint64', - 'previous_shuffling_seed': 'bytes32', - 'current_shuffling_seed': 'bytes32', # Finality 'previous_epoch_attestations': [PendingAttestation], @@ -849,7 +846,7 @@ def get_current_epoch_committee_count(state: BeaconState) -> int: """ current_active_validators = get_active_validator_indices( state.validator_registry, - state.current_shuffling_epoch, + get_current_epoch(state), ) return get_epoch_committee_count(len(current_active_validators)) ``` @@ -886,40 +883,30 @@ def get_crosslink_committees_at_slot(state: BeaconState, next_epoch = current_epoch + 1 assert previous_epoch <= epoch <= next_epoch + committees_per_epoch = get_epoch_committee_count(get_active_validator_indices( + state.validator_registry, + epoch, + )) if epoch == current_epoch: - committees_per_epoch = get_current_epoch_committee_count(state) - seed = state.current_shuffling_seed - shuffling_epoch = state.current_shuffling_epoch shuffling_start_shard = state.current_shuffling_start_shard elif epoch == previous_epoch: - committees_per_epoch = get_previous_epoch_committee_count(state) - seed = state.previous_shuffling_seed - shuffling_epoch = state.previous_shuffling_epoch - shuffling_start_shard = state.previous_shuffling_start_shard + shuffling_start_shard = ( + state.current_shuffling_start_shard - EPOCH_LENGTH * committees_per_epoch + ) % SHARD_COUNT elif epoch == next_epoch: - epochs_since_last_registry_update = current_epoch - state.validator_registry_update_epoch - if registry_change: - committees_per_epoch = get_next_epoch_committee_count(state) - seed = generate_seed(state, next_epoch) - shuffling_epoch = next_epoch - current_committees_per_epoch = get_current_epoch_committee_count(state) - shuffling_start_shard = (state.current_shuffling_start_shard + current_committees_per_epoch) % SHARD_COUNT - elif epochs_since_last_registry_update > 1 and is_power_of_two(epochs_since_last_registry_update): - committees_per_epoch = get_next_epoch_committee_count(state) - seed = generate_seed(state, next_epoch) - shuffling_epoch = next_epoch - shuffling_start_shard = state.current_shuffling_start_shard - else: - committees_per_epoch = get_current_epoch_committee_count(state) - seed = state.current_shuffling_seed - shuffling_epoch = state.current_shuffling_epoch - shuffling_start_shard = state.current_shuffling_start_shard + current_epoch_committees = get_epoch_committee_count(get_active_validator_indices( + state.validator_registry, + current_epoch, + )) + shuffling_start_shard = ( + state.current_shuffling_start_shard + EPOCH_LENGTH * current_epoch_committees + ) % SHARD_COUNT shuffling = get_shuffling( - seed, + generate_seed(state, epoch), state.validator_registry, - shuffling_epoch, + epoch, ) offset = slot % SLOTS_PER_EPOCH committees_per_slot = committees_per_epoch // SLOTS_PER_EPOCH @@ -1529,12 +1516,7 @@ def get_genesis_beacon_state(genesis_validator_deposits: List[Deposit], # Randomness and committees latest_randao_mixes=Vector([ZERO_HASH for _ in range(LATEST_RANDAO_MIXES_LENGTH)]), - previous_shuffling_start_shard=GENESIS_START_SHARD, current_shuffling_start_shard=GENESIS_START_SHARD, - previous_shuffling_epoch=GENESIS_EPOCH, - current_shuffling_epoch=GENESIS_EPOCH, - previous_shuffling_seed=ZERO_HASH, - current_shuffling_seed=ZERO_HASH, # Finality previous_epoch_attestations=[], @@ -1574,7 +1556,6 @@ def get_genesis_beacon_state(genesis_validator_deposits: List[Deposit], genesis_active_index_root = hash_tree_root(get_active_validator_indices(state.validator_registry, GENESIS_EPOCH)) for index in range(LATEST_ACTIVE_INDEX_ROOTS_LENGTH): state.latest_active_index_roots[index] = genesis_active_index_root - state.current_shuffling_seed = generate_seed(state, GENESIS_EPOCH) return state ``` @@ -1855,7 +1836,7 @@ def process_crosslinks(state: BeaconState) -> None: total_balance = get_total_balance(state, crosslink_committee) if 3 * participating_balance >= 2 * total_balance: state.latest_crosslinks[shard] = Crosslink( - epoch=slot_to_epoch(slot), + epoch=min(slot_to_epoch(slot), state.latest_crosslinks[shard].epoch + MAX_CROSSLINK_EPOCHS), crosslink_data_root=winning_root ) ``` @@ -2060,14 +2041,6 @@ def should_update_validator_registry(state: BeaconState) -> bool: # Must have finalized a new block if state.finalized_epoch <= state.validator_registry_update_epoch: return False - # Must have processed new crosslinks on all shards of the current epoch - shards_to_check = [ - (state.current_shuffling_start_shard + i) % SHARD_COUNT - for i in range(get_current_epoch_committee_count(state)) - ] - for shard in shards_to_check: - if state.latest_crosslinks[shard].epoch <= state.validator_registry_update_epoch: - return False return True ``` @@ -2119,30 +2092,17 @@ def update_validator_registry(state: BeaconState) -> None: Run the following function: ```python -def update_registry_and_shuffling_data(state: BeaconState) -> None: - # First set previous shuffling data to current shuffling data - state.previous_shuffling_epoch = state.current_shuffling_epoch - state.previous_shuffling_start_shard = state.current_shuffling_start_shard - state.previous_shuffling_seed = state.current_shuffling_seed +def update_registry(state: BeaconState) -> None: current_epoch = get_current_epoch(state) next_epoch = current_epoch + 1 # Check if we should update, and if so, update if should_update_validator_registry(state): update_validator_registry(state) - # If we update the registry, update the shuffling data and shards as well - state.current_shuffling_epoch = next_epoch - state.current_shuffling_start_shard = ( - state.current_shuffling_start_shard + - get_current_epoch_committee_count(state) % SHARD_COUNT - ) - state.current_shuffling_seed = generate_seed(state, state.current_shuffling_epoch) - else: - # If processing at least one crosslink keeps failing, then reshuffle every power of two, - # but don't update the current_shuffling_start_shard - epochs_since_last_registry_update = current_epoch - state.validator_registry_update_epoch - if epochs_since_last_registry_update > 1 and is_power_of_two(epochs_since_last_registry_update): - state.current_shuffling_epoch = next_epoch - state.current_shuffling_seed = generate_seed(state, state.current_shuffling_epoch) + # If we update the registry, update the shuffling data 2/3 or and shards as well + state.current_shuffling_start_shard = ( + state.current_shuffling_start_shard + + get_current_epoch_committee_count(state) % SHARD_COUNT + ) ``` **Invariant**: the active index root that is hashed into the shuffling seed actually is the `hash_tree_root` of the validator set that is used for that epoch. @@ -2397,7 +2357,8 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: # the attestation is trying to create Crosslink( crosslink_data_root=attestation.data.crosslink_data_root, - epoch=slot_to_epoch(attestation.data.slot) + epoch=min(slot_to_epoch(attestation.data.slot), + attestation.data.previous_crosslink.epoch + MAX_CROSSLINK_EPOCHS) ) } assert state.latest_crosslinks[attestation.data.shard] in acceptable_crosslink_data From db92235d9ed3eeffa846e50eef82895567cf77c7 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Tue, 19 Mar 2019 09:34:37 -0500 Subject: [PATCH 18/36] Removed some no-longer-necessary functions --- specs/core/0_beacon-chain.md | 35 +---------------------------------- 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 6877b9358..9e52148a0 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -64,9 +64,7 @@ - [`split`](#split) - [`get_epoch_committee_count`](#get_epoch_committee_count) - [`get_shuffling`](#get_shuffling) - - [`get_previous_epoch_committee_count`](#get_previous_epoch_committee_count) - [`get_current_epoch_committee_count`](#get_current_epoch_committee_count) - - [`get_next_epoch_committee_count`](#get_next_epoch_committee_count) - [`get_crosslink_committees_at_slot`](#get_crosslink_committees_at_slot) - [`get_block_root`](#get_block_root) - [`get_state_root`](#get_state_root) @@ -823,20 +821,6 @@ def get_shuffling(seed: Bytes32, **Note**: this definition and the next few definitions make heavy use of repetitive computing. Production implementations are expected to appropriately use caching/memoization to avoid redoing work. -### `get_previous_epoch_committee_count` - -```python -def get_previous_epoch_committee_count(state: BeaconState) -> int: - """ - Return the number of committees in the previous epoch of the given ``state``. - """ - previous_active_validators = get_active_validator_indices( - state.validator_registry, - state.previous_shuffling_epoch, - ) - return get_epoch_committee_count(len(previous_active_validators)) -``` - ### `get_current_epoch_committee_count` ```python @@ -851,20 +835,6 @@ def get_current_epoch_committee_count(state: BeaconState) -> int: return get_epoch_committee_count(len(current_active_validators)) ``` -### `get_next_epoch_committee_count` - -```python -def get_next_epoch_committee_count(state: BeaconState) -> int: - """ - Return the number of committees in the next epoch of the given ``state``. - """ - next_active_validators = get_active_validator_indices( - state.validator_registry, - get_current_epoch(state) + 1, - ) - return get_epoch_committee_count(len(next_active_validators)) -``` - ### `get_crosslink_committees_at_slot` ```python @@ -895,10 +865,7 @@ def get_crosslink_committees_at_slot(state: BeaconState, state.current_shuffling_start_shard - EPOCH_LENGTH * committees_per_epoch ) % SHARD_COUNT elif epoch == next_epoch: - current_epoch_committees = get_epoch_committee_count(get_active_validator_indices( - state.validator_registry, - current_epoch, - )) + current_epoch_committees = get_current_epoch_committee_count(state) shuffling_start_shard = ( state.current_shuffling_start_shard + EPOCH_LENGTH * current_epoch_committees ) % SHARD_COUNT From c5ee74d5e03376ec5c3bef1d294aaa9a3da831f6 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Tue, 19 Mar 2019 11:21:17 -0500 Subject: [PATCH 19/36] Justin fixes --- specs/core/0_beacon-chain.md | 37 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 9e52148a0..c14ff9736 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -183,10 +183,8 @@ Code snippets appearing in `this style` are to be interpreted as Python code. | `MAX_INDICES_PER_SLASHABLE_VOTE` | `2**12` (= 4,096) | | `MAX_EXIT_DEQUEUES_PER_EPOCH` | `2**2` (= 4) | | `SHUFFLE_ROUND_COUNT` | 90 | -| `MAX_CROSSLINK_EPOCHS` | `2**6` (= 64) | * For the safety of crosslinks `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.) -* `MAX_CROSSLINK_EPOCHS` should be a small constant times `SHARD_COUNT // EPOCH_LENGTH` ### Deposit contract @@ -232,6 +230,10 @@ Code snippets appearing in `this style` are to be interpreted as Python code. | `SLOTS_PER_HISTORICAL_ROOT` | `2**13` (= 8,192) | slots | ~13 hours | | `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `2**8` (= 256) | epochs | ~27 hours | | `PERSISTENT_COMMITTEE_PERIOD` | `2**11` (= 2,048) | epochs | 9 days | +| `MAX_CROSSLINK_EPOCHS` | `2**6` (= 64) | + +* `MAX_CROSSLINK_EPOCHS` should be a small constant times `SHARD_COUNT // SLOTS_PER_EPOCH` + ### State list lengths @@ -598,7 +600,7 @@ The types are defined topologically to aid in facilitating an executable version # Randomness and committees 'latest_randao_mixes': ['bytes32', LATEST_RANDAO_MIXES_LENGTH], - 'current_shuffling_start_shard': 'uint64', + 'latest_start_shard': 'uint64', # Finality 'previous_epoch_attestations': [PendingAttestation], @@ -859,15 +861,15 @@ def get_crosslink_committees_at_slot(state: BeaconState, )) if epoch == current_epoch: - shuffling_start_shard = state.current_shuffling_start_shard + shuffling_start_shard = state.latest_start_shard elif epoch == previous_epoch: shuffling_start_shard = ( - state.current_shuffling_start_shard - EPOCH_LENGTH * committees_per_epoch + state.latest_start_shard - SLOTS_PER_EPOCH * committees_per_epoch ) % SHARD_COUNT elif epoch == next_epoch: current_epoch_committees = get_current_epoch_committee_count(state) shuffling_start_shard = ( - state.current_shuffling_start_shard + EPOCH_LENGTH * current_epoch_committees + state.latest_start_shard + EPOCH_LENGTH * current_epoch_committees ) % SHARD_COUNT shuffling = get_shuffling( @@ -1483,7 +1485,7 @@ def get_genesis_beacon_state(genesis_validator_deposits: List[Deposit], # Randomness and committees latest_randao_mixes=Vector([ZERO_HASH for _ in range(LATEST_RANDAO_MIXES_LENGTH)]), - current_shuffling_start_shard=GENESIS_START_SHARD, + latest_start_shard=GENESIS_START_SHARD, # Finality previous_epoch_attestations=[], @@ -2003,14 +2005,6 @@ def process_ejections(state: BeaconState) -> None: #### Validator registry and shuffling seed data -```python -def should_update_validator_registry(state: BeaconState) -> bool: - # Must have finalized a new block - if state.finalized_epoch <= state.validator_registry_update_epoch: - return False - return True -``` - ```python def update_validator_registry(state: BeaconState) -> None: """ @@ -2060,16 +2054,13 @@ Run the following function: ```python def update_registry(state: BeaconState) -> None: - current_epoch = get_current_epoch(state) - next_epoch = current_epoch + 1 # Check if we should update, and if so, update - if should_update_validator_registry(state): + if state.finalized_epoch > state.validator_registry_update_epoch: update_validator_registry(state) - # If we update the registry, update the shuffling data 2/3 or and shards as well - state.current_shuffling_start_shard = ( - state.current_shuffling_start_shard + - get_current_epoch_committee_count(state) % SHARD_COUNT - ) + state.latest_start_shard = ( + state.latest_start_shard + + get_current_epoch_committee_count(state) + ) % SHARD_COUNT ``` **Invariant**: the active index root that is hashed into the shuffling seed actually is the `hash_tree_root` of the validator set that is used for that epoch. From b50e148642d4a19d5517ab1ab689708b33ed7b53 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 19 Mar 2019 17:13:25 +0000 Subject: [PATCH 20/36] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index c14ff9736..07179aa0a 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -74,7 +74,6 @@ - [`get_beacon_proposer_index`](#get_beacon_proposer_index) - [`verify_merkle_branch`](#verify_merkle_branch) - [`get_attestation_participants`](#get_attestation_participants) - - [`is_power_of_two`](#is_power_of_two) - [`int_to_bytes1`, `int_to_bytes2`, ...](#int_to_bytes1-int_to_bytes2-) - [`bytes_to_int`](#bytes_to_int) - [`get_effective_balance`](#get_effective_balance) @@ -861,16 +860,12 @@ def get_crosslink_committees_at_slot(state: BeaconState, )) if epoch == current_epoch: - shuffling_start_shard = state.latest_start_shard + start_shard = state.latest_start_shard elif epoch == previous_epoch: - shuffling_start_shard = ( - state.latest_start_shard - SLOTS_PER_EPOCH * committees_per_epoch - ) % SHARD_COUNT + start_shard = (state.latest_start_shard - SLOTS_PER_EPOCH * committees_per_epoch) % SHARD_COUNT elif epoch == next_epoch: current_epoch_committees = get_current_epoch_committee_count(state) - shuffling_start_shard = ( - state.latest_start_shard + EPOCH_LENGTH * current_epoch_committees - ) % SHARD_COUNT + start_shard = (state.latest_start_shard + EPOCH_LENGTH * current_epoch_committees) % SHARD_COUNT shuffling = get_shuffling( generate_seed(state, epoch), @@ -879,7 +874,7 @@ def get_crosslink_committees_at_slot(state: BeaconState, ) offset = slot % SLOTS_PER_EPOCH committees_per_slot = committees_per_epoch // SLOTS_PER_EPOCH - slot_start_shard = (shuffling_start_shard + committees_per_slot * offset) % SHARD_COUNT + slot_start_shard = (start_shard + committees_per_slot * offset) % SHARD_COUNT return [ ( @@ -1017,16 +1012,6 @@ def get_attestation_participants(state: BeaconState, return participants ``` -### `is_power_of_two` - -```python -def is_power_of_two(value: int) -> bool: - """ - Check if ``value`` is a power of two integer. - """ - return (value > 0) and (value & (value - 1) == 0) -``` - ### `int_to_bytes1`, `int_to_bytes2`, ... `int_to_bytes1(x): return x.to_bytes(1, 'little')`, `int_to_bytes2(x): return x.to_bytes(2, 'little')`, and so on for all integers, particularly 1, 2, 3, 4, 8, 32, 48, 96. From 833691b8afe9ca68c75588e0f528780f200de0ee Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 20 Mar 2019 08:16:39 +0000 Subject: [PATCH 21/36] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 4935ab7d7..099d12b95 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -256,7 +256,7 @@ Code snippets appearing in `this style` are to be interpreted as Python code. | `MIN_PENALTY_QUOTIENT` | `2**5` (= 32) | * The `BASE_REWARD_QUOTIENT` parameter dictates the per-epoch reward. It corresponds to ~2.54% annual interest assuming 10 million participating ETH in every epoch. -* The `INACTIVITY_PENALTY_QUOTIENT` equals `INVERSE_SQRT_E_DROP_TIME**2` where `INVERSE_SQRT_E_DROP_TIME := 2**12 epochs` (~18 days) is the time it takes the inactivity penalty to reduce the balance of non-participating [validators](#dfn-validator) to about `1/sqrt(e) ~= 60.6%`. Indeed, the balance retained by offline [validators](#dfn-validator) after `n` epochs is about `(1-1/INACTIVITY_PENALTY_QUOTIENT)**(n**2/2)` so after `INVERSE_SQRT_E_DROP_TIME` epochs it is roughly `(1-1/INACTIVITY_PENALTY_QUOTIENT)**(INACTIVITY_PENALTY_QUOTIENT/2) ~= 1/sqrt(e)`. +* The `INACTIVITY_PENALTY_QUOTIENT` equals `INVERSE_SQRT_E_DROP_TIME**2` where `INVERSE_SQRT_E_DROP_TIME := 2**12 epochs` (~18 days) is the time it takes the inactivity penalty to reduce the balance of non-participating [validators](#dfn-validator) to about `1/sqrt(e) ~= 60.6%`. Indeed, the balance retained by offline [validators](#dfn-validator) after `n` epochs is about `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(n**2/2)` so after `INVERSE_SQRT_E_DROP_TIME` epochs it is roughly `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(INACTIVITY_PENALTY_QUOTIENT/2) ~= 1/sqrt(e)`. ### Max transactions per block @@ -789,7 +789,7 @@ def decrease_balance(state: BeaconState, index: int, delta: int) -> None: ```python def get_permuted_index(index: int, list_size: int, seed: Bytes32) -> int: """ - Return `p(index)` in a pseudorandom permutation `p` of `0...list_size-1` with ``seed`` as entropy. + Return `p(index)` in a pseudorandom permutation `p` of `0...list_size - 1` with ``seed`` as entropy. Utilizes 'swap or not' shuffling found in https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf @@ -1376,7 +1376,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: # Note: In phase 2 registry indices that have been withdrawn for a long time will be recycled. state.validator_registry.append(validator) state.balances.append(0) - set_balance(state, len(state.validator_registry)-1, amount) + set_balance(state, len(state.validator_registry) - 1, amount) else: # Increase balance by deposit amount index = validator_pubkeys.index(pubkey) From 47477b8e55dba85f7e4e12c3b0cf99bc594ac81d Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 21 Mar 2019 09:37:06 -0600 Subject: [PATCH 22/36] cleanup tests to use get_balance and set_balance --- .../block_processing/test_process_deposit.py | 10 ++++---- tests/phase0/test_sanity.py | 23 +++++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/tests/phase0/block_processing/test_process_deposit.py b/tests/phase0/block_processing/test_process_deposit.py index 297ad37f1..9f1b6add6 100644 --- a/tests/phase0/block_processing/test_process_deposit.py +++ b/tests/phase0/block_processing/test_process_deposit.py @@ -5,6 +5,7 @@ import build.phase0.spec as spec from build.phase0.spec import ( Deposit, + get_balance, process_deposit, ) from tests.phase0.helpers import ( @@ -38,8 +39,9 @@ def test_success(state, deposit_data_leaves, pubkeys, privkeys): process_deposit(post_state, deposit) assert len(post_state.validator_registry) == len(state.validator_registry) + 1 - assert len(post_state.validator_balances) == len(state.validator_balances) + 1 + assert len(post_state.balances) == len(state.balances) + 1 assert post_state.validator_registry[index].pubkey == pubkeys[index] + assert get_balance(post_state, index) == spec.MAX_DEPOSIT_AMOUNT assert post_state.deposit_index == post_state.latest_eth1_data.deposit_count return pre_state, deposit, post_state @@ -62,16 +64,16 @@ def test_success_top_up(state, deposit_data_leaves, pubkeys, privkeys): pre_state.latest_eth1_data.deposit_root = root pre_state.latest_eth1_data.deposit_count = len(deposit_data_leaves) - pre_balance = pre_state.validator_balances[validator_index] + pre_balance = get_balance(pre_state, validator_index) post_state = deepcopy(pre_state) process_deposit(post_state, deposit) assert len(post_state.validator_registry) == len(state.validator_registry) - assert len(post_state.validator_balances) == len(state.validator_balances) + assert len(post_state.balances) == len(state.balances) assert post_state.deposit_index == post_state.latest_eth1_data.deposit_count - assert post_state.validator_balances[validator_index] == pre_balance + amount + assert get_balance(post_state, validator_index) == pre_balance + amount return pre_state, deposit, post_state diff --git a/tests/phase0/test_sanity.py b/tests/phase0/test_sanity.py index 91bd9fe7a..ec03fb355 100644 --- a/tests/phase0/test_sanity.py +++ b/tests/phase0/test_sanity.py @@ -21,6 +21,7 @@ from build.phase0.spec import ( # functions get_active_validator_indices, get_attestation_participants, + get_balance, get_block_root, get_crosslink_committees_at_slot, get_current_epoch, @@ -28,6 +29,7 @@ from build.phase0.spec import ( get_state_root, advance_slot, cache_state, + set_balance, verify_merkle_branch, hash, ) @@ -168,7 +170,7 @@ def test_proposer_slashing(state, pubkeys, privkeys): assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH # lost whistleblower reward - assert test_state.validator_balances[validator_index] < state.validator_balances[validator_index] + assert get_balance(test_state, validator_index) < get_balance(state, validator_index) return state, [block], test_state @@ -203,7 +205,8 @@ def test_deposit_in_block(state, deposit_data_leaves, pubkeys, privkeys): state_transition(post_state, block) assert len(post_state.validator_registry) == len(state.validator_registry) + 1 - assert len(post_state.validator_balances) == len(state.validator_balances) + 1 + assert len(post_state.balances) == len(state.balances) + 1 + assert get_balance(post_state, index) == spec.MAX_DEPOSIT_AMOUNT assert post_state.validator_registry[index].pubkey == pubkeys[index] return pre_state, [block], post_state @@ -238,12 +241,12 @@ def test_deposit_top_up(state, pubkeys, privkeys, deposit_data_leaves): block = build_empty_block_for_next_slot(pre_state) block.body.deposits.append(deposit) - pre_balance = pre_state.validator_balances[validator_index] + pre_balance = get_balance(pre_state, validator_index) post_state = deepcopy(pre_state) state_transition(post_state, block) assert len(post_state.validator_registry) == len(pre_state.validator_registry) - assert len(post_state.validator_balances) == len(pre_state.validator_balances) - assert post_state.validator_balances[validator_index] == pre_balance + amount + assert len(post_state.balances) == len(pre_state.balances) + assert get_balance(post_state, validator_index) == pre_balance + amount return pre_state, [block], post_state @@ -412,8 +415,8 @@ def test_transfer(state, pubkeys, privkeys): recipient_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0] transfer_pubkey = pubkeys[-1] transfer_privkey = privkeys[-1] - amount = pre_state.validator_balances[sender_index] - pre_transfer_recipient_balance = pre_state.validator_balances[recipient_index] + amount = get_balance(pre_state, sender_index) + pre_transfer_recipient_balance = get_balance(pre_state, recipient_index) transfer = Transfer( sender=sender_index, recipient=recipient_index, @@ -448,8 +451,8 @@ def test_transfer(state, pubkeys, privkeys): block.body.transfers.append(transfer) state_transition(post_state, block) - sender_balance = post_state.validator_balances[sender_index] - recipient_balance = post_state.validator_balances[recipient_index] + sender_balance = get_balance(post_state, sender_index) + recipient_balance = get_balance(post_state, recipient_index) assert sender_balance == 0 assert recipient_balance == pre_transfer_recipient_balance + amount @@ -465,7 +468,7 @@ def test_ejection(state): assert pre_state.validator_registry[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH # set validator balance to below ejection threshold - pre_state.validator_balances[validator_index] = spec.EJECTION_BALANCE - 1 + set_balance(pre_state, validator_index, spec.EJECTION_BALANCE - 1) post_state = deepcopy(pre_state) # From f6da42ffb32fed8e22769dbf77f906889b1e02a2 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 21 Mar 2019 10:04:20 -0600 Subject: [PATCH 23/36] fix markdown issues --- specs/core/0_beacon-chain.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 3bc95f717..4eee3dcb5 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -61,9 +61,9 @@ - [`is_active_validator`](#is_active_validator) - [`get_active_validator_indices`](#get_active_validator_indices) - [`get_balance`](#get_balance) - - [`set_balance`](#set_balance) - - [`increase_balance`](#increase_balance) - - [`decrease_balance`](#decrease_balance) + - [`set_balance`](#set_balance) + - [`increase_balance`](#increase_balance) + - [`decrease_balance`](#decrease_balance) - [`get_permuted_index`](#get_permuted_index) - [`get_split_offset`](#get_split_offset) - [`get_epoch_committee_count`](#get_epoch_committee_count) @@ -760,31 +760,32 @@ def get_active_validator_indices(validators: List[Validator], epoch: Epoch) -> L def get_balance(state: BeaconState, index: int) -> int: return state.balances[index] ``` -#### `set_balance` -````python +### `set_balance` + +```python def set_balance(state: BeaconState, index: int, balance: int) -> None: validator = state.validator_registry[index] HALF_INCREMENT = HIGH_BALANCE_INCREMENT // 2 if validator.high_balance > balance or validator.high_balance + 3 * HALF_INCREMENT < balance: validator.high_balance = balance - balance % HIGH_BALANCE_INCREMENT state.balances[index] = balance -```` +``` -#### `increase_balance` +### `increase_balance` -````python +```python def increase_balance(state: BeaconState, index: int, delta: int) -> None: set_balance(state, index, get_balance(state, index) + delta) -```` +``` -#### `decrease_balance` +### `decrease_balance` -````python +```python def decrease_balance(state: BeaconState, index: int, delta: int) -> None: cur_balance = get_balance(state, index) set_balance(state, index, cur_balance - delta if cur_balance >= delta else 0) -```` +``` ### `get_permuted_index` From d3f175d7289befde23d9810a29ee1ca40f02216a Mon Sep 17 00:00:00 2001 From: terence tsao Date: Thu, 21 Mar 2019 11:33:36 -0700 Subject: [PATCH 24/36] Update sync_protocol.md --- specs/light_client/sync_protocol.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/specs/light_client/sync_protocol.md b/specs/light_client/sync_protocol.md index 8878545bb..575df60fe 100644 --- a/specs/light_client/sync_protocol.md +++ b/specs/light_client/sync_protocol.md @@ -1,6 +1,18 @@ -# Beacon chain light client syncing +# Beacon Chain Light Client Syncing + +__NOTICE__: This document is a work-in-progress for researchers and implementers. One of the design goals of the eth2 beacon chain is light-client friendlines, both to allow low-resource clients (mobile phones, IoT, etc) to maintain access to the blockchain in a reasonably safe way, but also to facilitate the development of "bridges" between the eth2 beacon chain and other chains. + +## Table of Contents + + +- [Beacon Chain Light Client Syncing](#beacon-chain-light-client-syncing) + - [Table of Contents](#table-of-contents) + - [Light client state](#light-client-state) + - [Updating the shuffled committee](#updating-the-shuffled-committee) + - [Computing the current committee](#computing-the-current-committee) + - [Verifying blocks](#verifying-blocks) + -One of the design goals of the eth2 beacon chain is light-client friendlines, both to allow low-resource clients (mobile phones, IoT, etc) to maintain access to the blockchain in a reasonably safe way, but also to facilitate the development of "bridges" between the eth2 beacon chain and other chains. ### Preliminaries From 3b403909e8d1571bc6a30ac9487d2ba49a9386cd Mon Sep 17 00:00:00 2001 From: vbuterin Date: Thu, 21 Mar 2019 16:29:14 -0500 Subject: [PATCH 25/36] Cosmetic improvement to reward/penalty functions --- specs/core/0_beacon-chain.md | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 1067c3dc0..05dda9fe7 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1995,12 +1995,8 @@ def compute_inactivity_leak_deltas(state: BeaconState) -> Tuple[List[Gwei], List ```python def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: - # deltas[0] for rewards - # deltas[1] for penalties - deltas = [ - [0 for index in range(len(state.validator_registry))], - [0 for index in range(len(state.validator_registry))] - ] + rewards = [0 for index in range(len(state.validator_registry))] + penalties = [0 for index in range(len(state.validator_registry))] previous_epoch_start_slot = get_epoch_start_slot(get_previous_epoch(state)) current_epoch_start_slot = get_epoch_start_slot(get_current_epoch(state)) for slot in range(previous_epoch_start_slot, current_epoch_start_slot): @@ -2010,10 +2006,10 @@ def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: total_balance = get_total_balance(state, crosslink_committee) for index in crosslink_committee: if index in participants: - deltas[0][index] += get_base_reward(state, index) * participating_balance // total_balance + rewards[index] += get_base_reward(state, index) * participating_balance // total_balance else: - deltas[1][index] += get_base_reward(state, index) - return deltas + penalties[index] += get_base_reward(state, index) + return [rewards, penalties] ``` #### Apply rewards @@ -2022,12 +2018,12 @@ Run the following: ```python def apply_rewards(state: BeaconState) -> None: - deltas1 = get_justification_and_finalization_deltas(state) - deltas2 = get_crosslink_deltas(state) + rewards1, penalties1 = get_justification_and_finalization_deltas(state) + rewards2, penalties2 = get_crosslink_deltas(state) for i in range(len(state.validator_registry)): state.validator_balances[i] = max( 0, - state.validator_balances[i] + deltas1[0][i] + deltas2[0][i] - deltas1[1][i] - deltas2[1][i] + state.validator_balances[i] + rewards1[i] + rewards2[i] - penalties1[i] - penalties2[i] ) ``` From 3ece05ccc1a5e126e934c57aa091386a4afeb8ef Mon Sep 17 00:00:00 2001 From: vbuterin Date: Thu, 21 Mar 2019 16:36:31 -0500 Subject: [PATCH 26/36] Small cosmetic change to slashable attestations --- specs/core/0_beacon-chain.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 1067c3dc0..c7c74279f 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -182,7 +182,7 @@ Code snippets appearing in `this style` are to be interpreted as Python code. | `SHARD_COUNT` | `2**10` (= 1,024) | | `TARGET_COMMITTEE_SIZE` | `2**7` (= 128) | | `MAX_BALANCE_CHURN_QUOTIENT` | `2**5` (= 32) | -| `MAX_INDICES_PER_SLASHABLE_VOTE` | `2**12` (= 4,096) | +| `MAX_SLASHABLE_ATTESTATION_PARTICIPANTS` | `2**12` (= 4,096) | | `MAX_EXIT_DEQUEUES_PER_EPOCH` | `2**2` (= 4) | | `SHUFFLE_ROUND_COUNT` | 90 | @@ -1159,7 +1159,7 @@ def verify_slashable_attestation(state: BeaconState, slashable_attestation: Slas if slashable_attestation.custody_bitfield != b'\x00' * len(slashable_attestation.custody_bitfield): # [TO BE REMOVED IN PHASE 1] return False - if len(slashable_attestation.validator_indices) == 0: + if not (1 <= len(slashable_attestation.validator_indices) <= MAX_SLASHABLE_ATTESTATION_PARTICIPANTS): return False for i in range(len(slashable_attestation.validator_indices) - 1): @@ -1169,9 +1169,6 @@ def verify_slashable_attestation(state: BeaconState, slashable_attestation: Slas if not verify_bitfield(slashable_attestation.custody_bitfield, len(slashable_attestation.validator_indices)): return False - if len(slashable_attestation.validator_indices) > MAX_INDICES_PER_SLASHABLE_VOTE: - return False - custody_bit_0_indices = [] custody_bit_1_indices = [] for i, validator_index in enumerate(slashable_attestation.validator_indices): From e313c5ba5abd949e2af87919a36f84937ee6b68c Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 21 Mar 2019 17:08:54 -0600 Subject: [PATCH 27/36] add tests for proposer slashing --- .../test_process_proposer_slashing.py | 97 +++++++++++++++++++ tests/phase0/helpers.py | 44 +++++++++ tests/phase0/test_sanity.py | 39 +------- 3 files changed, 144 insertions(+), 36 deletions(-) create mode 100644 tests/phase0/block_processing/test_process_proposer_slashing.py diff --git a/tests/phase0/block_processing/test_process_proposer_slashing.py b/tests/phase0/block_processing/test_process_proposer_slashing.py new file mode 100644 index 000000000..467d2164b --- /dev/null +++ b/tests/phase0/block_processing/test_process_proposer_slashing.py @@ -0,0 +1,97 @@ +from copy import deepcopy +import pytest + +import build.phase0.spec as spec +from build.phase0.spec import ( + get_balance, + get_current_epoch, + process_proposer_slashing, +) +from tests.phase0.helpers import ( + get_valid_proposer_slashing, +) + +# mark entire file as 'header' +pytestmark = pytest.mark.proposer_slashings + + +def run_proposer_slashing_processing(state, proposer_slashing, valid=True): + """ + Run ``process_proposer_slashing`` returning the pre and post state. + If ``valid == False``, run expecting ``AssertionError`` + """ + post_state = deepcopy(state) + + if not valid: + with pytest.raises(AssertionError): + process_proposer_slashing(post_state, proposer_slashing) + return state, None + + process_proposer_slashing(post_state, proposer_slashing) + + slashed_validator = post_state.validator_registry[proposer_slashing.proposer_index] + assert not slashed_validator.initiated_exit + assert slashed_validator.slashed + assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH + assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH + # lost whistleblower reward + assert ( + get_balance(post_state, proposer_slashing.proposer_index) < + get_balance(state, proposer_slashing.proposer_index) + ) + + return state, post_state + + +def test_success(state): + proposer_slashing = get_valid_proposer_slashing(state) + + pre_state, post_state = run_proposer_slashing_processing(state, proposer_slashing) + + return pre_state, proposer_slashing, post_state + + +def test_epochs_are_different(state): + proposer_slashing = get_valid_proposer_slashing(state) + + # set slots to be in different epochs + proposer_slashing.header_2.slot += spec.SLOTS_PER_EPOCH + + pre_state, post_state = run_proposer_slashing_processing(state, proposer_slashing, False) + + return pre_state, proposer_slashing, post_state + + +def test_headers_are_same(state): + proposer_slashing = get_valid_proposer_slashing(state) + + # set headers to be the same + proposer_slashing.header_2 = proposer_slashing.header_1 + + pre_state, post_state = run_proposer_slashing_processing(state, proposer_slashing, False) + + return pre_state, proposer_slashing, post_state + + +def test_proposer_is_slashed(state): + proposer_slashing = get_valid_proposer_slashing(state) + + # set proposer to slashed + state.validator_registry[proposer_slashing.proposer_index].slashed = True + + pre_state, post_state = run_proposer_slashing_processing(state, proposer_slashing, False) + + return pre_state, proposer_slashing, post_state + + +def test_proposer_is_withdrawn(state): + proposer_slashing = get_valid_proposer_slashing(state) + + # set proposer withdrawable_epoch in past + current_epoch = get_current_epoch(state) + proposer_index = proposer_slashing.proposer_index + state.validator_registry[proposer_index].withdrawable_epoch = current_epoch - 1 + + pre_state, post_state = run_proposer_slashing_processing(state, proposer_slashing, False) + + return pre_state, proposer_slashing, post_state diff --git a/tests/phase0/helpers.py b/tests/phase0/helpers.py index 5c61685a6..3987289bf 100644 --- a/tests/phase0/helpers.py +++ b/tests/phase0/helpers.py @@ -7,14 +7,18 @@ from build.phase0.utils.minimal_ssz import signed_root from build.phase0.spec import ( # constants EMPTY_SIGNATURE, + ZERO_HASH, # SSZ AttestationData, + BeaconBlockHeader, Deposit, DepositInput, DepositData, Eth1Data, + ProposerSlashing, VoluntaryExit, # functions + get_active_validator_indices, get_block_root, get_current_epoch, get_domain, @@ -199,3 +203,43 @@ def build_deposit(state, ) return deposit, root, deposit_data_leaves + + +def get_valid_proposer_slashing(state): + current_epoch = get_current_epoch(state) + validator_index = get_active_validator_indices(state.validator_registry, current_epoch)[-1] + privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey] + slot = state.slot + + header_1 = BeaconBlockHeader( + slot=slot, + previous_block_root=ZERO_HASH, + state_root=ZERO_HASH, + block_body_root=ZERO_HASH, + signature=EMPTY_SIGNATURE, + ) + header_2 = deepcopy(header_1) + header_2.previous_block_root = b'\x02' * 32 + header_2.slot = slot + 1 + + domain = get_domain( + fork=state.fork, + epoch=get_current_epoch(state), + domain_type=spec.DOMAIN_BEACON_BLOCK, + ) + header_1.signature = bls.sign( + message_hash=signed_root(header_1), + privkey=privkey, + domain=domain, + ) + header_2.signature = bls.sign( + message_hash=signed_root(header_2), + privkey=privkey, + domain=domain, + ) + + return ProposerSlashing( + proposer_index=validator_index, + header_1=header_1, + header_2=header_2, + ) diff --git a/tests/phase0/test_sanity.py b/tests/phase0/test_sanity.py index ec03fb355..444075a13 100644 --- a/tests/phase0/test_sanity.py +++ b/tests/phase0/test_sanity.py @@ -46,6 +46,7 @@ from tests.phase0.helpers import ( build_deposit_data, build_empty_block_for_next_slot, force_registry_change_at_next_epoch, + get_valid_proposer_slashing, ) @@ -117,42 +118,8 @@ def test_empty_epoch_transition_not_finalizing(state): def test_proposer_slashing(state, pubkeys, privkeys): test_state = deepcopy(state) - current_epoch = get_current_epoch(test_state) - validator_index = get_active_validator_indices(test_state.validator_registry, current_epoch)[-1] - privkey = privkeys[validator_index] - slot = spec.GENESIS_SLOT - header_1 = BeaconBlockHeader( - slot=slot, - previous_block_root=ZERO_HASH, - state_root=ZERO_HASH, - block_body_root=ZERO_HASH, - signature=EMPTY_SIGNATURE, - ) - header_2 = deepcopy(header_1) - header_2.previous_block_root = b'\x02' * 32 - header_2.slot = slot + 1 - - domain = get_domain( - fork=test_state.fork, - epoch=get_current_epoch(test_state), - domain_type=spec.DOMAIN_BEACON_BLOCK, - ) - header_1.signature = bls.sign( - message_hash=signed_root(header_1), - privkey=privkey, - domain=domain, - ) - header_2.signature = bls.sign( - message_hash=signed_root(header_2), - privkey=privkey, - domain=domain, - ) - - proposer_slashing = ProposerSlashing( - proposer_index=validator_index, - header_1=header_1, - header_2=header_2, - ) + proposer_slashing = get_valid_proposer_slashing(state) + validator_index = proposer_slashing.proposer_index # # Add to state via block transition From 11c3291817f3d590723298877c41d0bf244b789b Mon Sep 17 00:00:00 2001 From: terence tsao Date: Thu, 21 Mar 2019 16:30:45 -0700 Subject: [PATCH 28/36] Update sync_protocol.md --- specs/light_client/sync_protocol.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/light_client/sync_protocol.md b/specs/light_client/sync_protocol.md index 8878545bb..143f82a39 100644 --- a/specs/light_client/sync_protocol.md +++ b/specs/light_client/sync_protocol.md @@ -40,8 +40,8 @@ def get_later_start_epoch(slot: Slot) -> int: return slot - slot % PERSISTENT_COMMITTEE_PERIOD - PERSISTENT_COMMITTEE_PERIOD def get_earlier_period_data(block: ExtendedBeaconBlock, shard_id: Shard) -> PeriodData: - period_start = get_earlier_start_epoch(header.slot) - validator_count = len(get_active_validator_indices(state, period_start)) + period_start = get_earlier_start_epoch(block.slot) + validator_count = len(get_active_validator_indices(block.state, period_start)) committee_count = validator_count // (SHARD_COUNT * TARGET_COMMITTEE_SIZE) + 1 indices = get_shuffled_committee(block.state, shard_id, period_start, 0, committee_count) return PeriodData( @@ -51,8 +51,8 @@ def get_earlier_period_data(block: ExtendedBeaconBlock, shard_id: Shard) -> Peri ) def get_later_period_data(block: ExtendedBeaconBlock, shard_id: Shard) -> PeriodData: - period_start = get_later_start_epoch(header.slot) - validator_count = len(get_active_validator_indices(state, period_start)) + period_start = get_later_start_epoch(block.slot) + validator_count = len(get_active_validator_indices(block.state, period_start)) committee_count = validator_count // (SHARD_COUNT * TARGET_COMMITTEE_SIZE) + 1 indices = get_shuffled_committee(block.state, shard_id, period_start, 0, committee_count) return PeriodData( From ae67e9513b46e045b87bdc302f6b20c0fc341e2f Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 22 Mar 2019 12:56:54 +0800 Subject: [PATCH 29/36] Fix type hinting and add docstrings --- specs/core/0_beacon-chain.md | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index a67e6291c..c29aa113d 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -770,14 +770,21 @@ def get_active_validator_indices(validators: List[Validator], epoch: Epoch) -> L ### `get_balance` ```python -def get_balance(state: BeaconState, index: int) -> int: +def get_balance(state: BeaconState, index: ValidatorIndex) -> Gwei: + """ + Return the balance for a validator with the given ``index``. + """ return state.balances[index] ``` ### `set_balance` ```python -def set_balance(state: BeaconState, index: int, balance: int) -> None: +def set_balance(state: BeaconState, index: ValidatorIndex, balance: Gwei) -> None: + """ + Set the balance for a validator with the given ``index`` in both ``BeaconState`` + and validator's rounded balance ``high_balance``. + """ validator = state.validator_registry[index] HALF_INCREMENT = HIGH_BALANCE_INCREMENT // 2 if validator.high_balance > balance or validator.high_balance + 3 * HALF_INCREMENT < balance: @@ -788,16 +795,23 @@ def set_balance(state: BeaconState, index: int, balance: int) -> None: ### `increase_balance` ```python -def increase_balance(state: BeaconState, index: int, delta: int) -> None: +def increase_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: + """ + Increase the balance for a validator with the given ``index`` by ``delta``. + """ set_balance(state, index, get_balance(state, index) + delta) ``` ### `decrease_balance` ```python -def decrease_balance(state: BeaconState, index: int, delta: int) -> None: - cur_balance = get_balance(state, index) - set_balance(state, index, cur_balance - delta if cur_balance >= delta else 0) +def decrease_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: + """ + Decrease the balance for a validator with the given ``index`` by ``delta``. + Set to ``0`` when underflow. + """ + current_balance = get_balance(state, index) + set_balance(state, index, current_balance - delta if current_balance >= delta else 0) ``` ### `get_permuted_index` From fceebeec4e518886b8c2986baee019d2585d8132 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 25 Mar 2019 11:25:33 -0600 Subject: [PATCH 30/36] backport 839 into dev --- .gitignore | 1 + specs/core/0_beacon-chain.md | 11 ++-- tests/phase0/__init__.py | 0 .../block_processing/test_process_deposit.py | 17 ++++-- .../block_processing/test_voluntary_exit.py | 21 ++++---- tests/phase0/conftest.py | 18 ------- tests/phase0/helpers.py | 16 +++--- tests/phase0/test_sanity.py | 18 ++++--- utils/phase0/jsonize.py | 52 +++++++++++++++++++ 9 files changed, 101 insertions(+), 53 deletions(-) create mode 100644 tests/phase0/__init__.py create mode 100644 utils/phase0/jsonize.py diff --git a/.gitignore b/.gitignore index dfb38d170..f33dd5256 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ /.pytest_cache build/ +output/ diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index c29aa113d..e7e540a26 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -692,7 +692,8 @@ def get_temporary_block_header(block: BeaconBlock) -> BeaconBlockHeader: previous_block_root=block.previous_block_root, state_root=ZERO_HASH, block_body_root=hash_tree_root(block.body), - signature=block.signature, + # signed_root(block) is used for block id purposes so signature is a stub + signature=EMPTY_SIGNATURE, ) ``` @@ -1749,7 +1750,7 @@ def cache_state(state: BeaconState) -> None: state.latest_block_header.state_root = previous_slot_state_root # store latest known block for previous slot - state.latest_block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = hash_tree_root(state.latest_block_header) + state.latest_block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = signed_root(state.latest_block_header) ``` ### Per-epoch processing @@ -2198,8 +2199,8 @@ def update_registry_and_shuffling_data(state: BeaconState) -> None: state.current_shuffling_epoch = next_epoch state.current_shuffling_start_shard = ( state.current_shuffling_start_shard + - get_current_epoch_committee_count(state) % SHARD_COUNT - ) + get_current_epoch_committee_count(state) + ) % SHARD_COUNT state.current_shuffling_seed = generate_seed(state, state.current_shuffling_epoch) else: # If processing at least one crosslink keeps failing, then reshuffle every power of two, @@ -2315,7 +2316,7 @@ def process_block_header(state: BeaconState, block: BeaconBlock) -> None: # Verify that the slots match assert block.slot == state.slot # Verify that the parent matches - assert block.previous_block_root == hash_tree_root(state.latest_block_header) + assert block.previous_block_root == signed_root(state.latest_block_header) # Save current block as the new latest block state.latest_block_header = get_temporary_block_header(block) # Verify proposer is not slashed diff --git a/tests/phase0/__init__.py b/tests/phase0/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/phase0/block_processing/test_process_deposit.py b/tests/phase0/block_processing/test_process_deposit.py index 9f1b6add6..0726dddef 100644 --- a/tests/phase0/block_processing/test_process_deposit.py +++ b/tests/phase0/block_processing/test_process_deposit.py @@ -4,12 +4,14 @@ import pytest import build.phase0.spec as spec from build.phase0.spec import ( - Deposit, get_balance, + ZERO_HASH, process_deposit, ) from tests.phase0.helpers import ( build_deposit, + privkeys, + pubkeys, ) @@ -17,8 +19,10 @@ from tests.phase0.helpers import ( pytestmark = pytest.mark.voluntary_exits -def test_success(state, deposit_data_leaves, pubkeys, privkeys): +def test_success(state): pre_state = deepcopy(state) + # fill previous deposits with zero-hash + deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry) index = len(deposit_data_leaves) pubkey = pubkeys[index] @@ -47,8 +51,9 @@ def test_success(state, deposit_data_leaves, pubkeys, privkeys): return pre_state, deposit, post_state -def test_success_top_up(state, deposit_data_leaves, pubkeys, privkeys): +def test_success_top_up(state): pre_state = deepcopy(state) + deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry) validator_index = 0 amount = spec.MAX_DEPOSIT_AMOUNT // 4 @@ -78,8 +83,9 @@ def test_success_top_up(state, deposit_data_leaves, pubkeys, privkeys): return pre_state, deposit, post_state -def test_wrong_index(state, deposit_data_leaves, pubkeys, privkeys): +def test_wrong_index(state): pre_state = deepcopy(state) + deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry) index = len(deposit_data_leaves) pubkey = pubkeys[index] @@ -106,8 +112,9 @@ def test_wrong_index(state, deposit_data_leaves, pubkeys, privkeys): return pre_state, deposit, None -def test_bad_merkle_proof(state, deposit_data_leaves, pubkeys, privkeys): +def test_bad_merkle_proof(state): pre_state = deepcopy(state) + deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry) index = len(deposit_data_leaves) pubkey = pubkeys[index] diff --git a/tests/phase0/block_processing/test_voluntary_exit.py b/tests/phase0/block_processing/test_voluntary_exit.py index 0801e4292..6adc81464 100644 --- a/tests/phase0/block_processing/test_voluntary_exit.py +++ b/tests/phase0/block_processing/test_voluntary_exit.py @@ -10,6 +10,7 @@ from build.phase0.spec import ( ) from tests.phase0.helpers import ( build_voluntary_exit, + pubkey_to_privkey, ) @@ -17,7 +18,7 @@ from tests.phase0.helpers import ( pytestmark = pytest.mark.voluntary_exits -def test_success(state, pub_to_priv): +def test_success(state): pre_state = deepcopy(state) # # setup pre_state @@ -30,7 +31,7 @@ def test_success(state, pub_to_priv): # current_epoch = get_current_epoch(pre_state) validator_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0] - privkey = pub_to_priv[pre_state.validator_registry[validator_index].pubkey] + privkey = pubkey_to_privkey[pre_state.validator_registry[validator_index].pubkey] voluntary_exit = build_voluntary_exit( pre_state, @@ -52,11 +53,11 @@ def test_success(state, pub_to_priv): return pre_state, voluntary_exit, post_state -def test_validator_not_active(state, pub_to_priv): +def test_validator_not_active(state): pre_state = deepcopy(state) current_epoch = get_current_epoch(pre_state) validator_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0] - privkey = pub_to_priv[pre_state.validator_registry[validator_index].pubkey] + privkey = pubkey_to_privkey[pre_state.validator_registry[validator_index].pubkey] # # setup pre_state @@ -79,7 +80,7 @@ def test_validator_not_active(state, pub_to_priv): return pre_state, voluntary_exit, None -def test_validator_already_exited(state, pub_to_priv): +def test_validator_already_exited(state): pre_state = deepcopy(state) # # setup pre_state @@ -89,7 +90,7 @@ def test_validator_already_exited(state, pub_to_priv): current_epoch = get_current_epoch(pre_state) validator_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0] - privkey = pub_to_priv[pre_state.validator_registry[validator_index].pubkey] + privkey = pubkey_to_privkey[pre_state.validator_registry[validator_index].pubkey] # but validator already has exited pre_state.validator_registry[validator_index].exit_epoch = current_epoch + 2 @@ -110,7 +111,7 @@ def test_validator_already_exited(state, pub_to_priv): return pre_state, voluntary_exit, None -def test_validator_already_initiated_exit(state, pub_to_priv): +def test_validator_already_initiated_exit(state): pre_state = deepcopy(state) # # setup pre_state @@ -120,7 +121,7 @@ def test_validator_already_initiated_exit(state, pub_to_priv): current_epoch = get_current_epoch(pre_state) validator_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0] - privkey = pub_to_priv[pre_state.validator_registry[validator_index].pubkey] + privkey = pubkey_to_privkey[pre_state.validator_registry[validator_index].pubkey] # but validator already has initiated exit pre_state.validator_registry[validator_index].initiated_exit = True @@ -141,14 +142,14 @@ def test_validator_already_initiated_exit(state, pub_to_priv): return pre_state, voluntary_exit, None -def test_validator_not_active_long_enough(state, pub_to_priv): +def test_validator_not_active_long_enough(state): pre_state = deepcopy(state) # # setup pre_state # current_epoch = get_current_epoch(pre_state) validator_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0] - privkey = pub_to_priv[pre_state.validator_registry[validator_index].pubkey] + privkey = pubkey_to_privkey[pre_state.validator_registry[validator_index].pubkey] # but validator already has initiated exit pre_state.validator_registry[validator_index].initiated_exit = True diff --git a/tests/phase0/conftest.py b/tests/phase0/conftest.py index 395929028..36a087941 100644 --- a/tests/phase0/conftest.py +++ b/tests/phase0/conftest.py @@ -3,9 +3,6 @@ import pytest from build.phase0 import spec from tests.phase0.helpers import ( - privkeys_list, - pubkeys_list, - pubkey_to_privkey, create_genesis_state, ) @@ -25,21 +22,6 @@ MINIMAL_CONFIG = { } -@pytest.fixture -def privkeys(): - return privkeys_list - - -@pytest.fixture -def pubkeys(): - return pubkeys_list - - -@pytest.fixture -def pub_to_priv(): - return pubkey_to_privkey - - def overwrite_spec_config(config): for field in config: setattr(spec, field, config[field]) diff --git a/tests/phase0/helpers.py b/tests/phase0/helpers.py index 3987289bf..a0ede04e5 100644 --- a/tests/phase0/helpers.py +++ b/tests/phase0/helpers.py @@ -35,18 +35,20 @@ from build.phase0.utils.merkle_minimal import ( ) -privkeys_list = [i + 1 for i in range(1000)] -pubkeys_list = [bls.privtopub(privkey) for privkey in privkeys_list] -pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys_list, pubkeys_list)} +privkeys = [i + 1 for i in range(1000)] +pubkeys = [bls.privtopub(privkey) for privkey in privkeys] +pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys, pubkeys)} -def create_mock_genesis_validator_deposits(num_validators, deposit_data_leaves): +def create_mock_genesis_validator_deposits(num_validators, deposit_data_leaves=None): + if not deposit_data_leaves: + deposit_data_leaves = [] deposit_timestamp = 0 proof_of_possession = b'\x33' * 96 deposit_data_list = [] for i in range(num_validators): - pubkey = pubkeys_list[i] + pubkey = pubkeys[i] deposit_data = DepositData( amount=spec.MAX_DEPOSIT_AMOUNT, timestamp=deposit_timestamp, @@ -75,7 +77,7 @@ def create_mock_genesis_validator_deposits(num_validators, deposit_data_leaves): return genesis_validator_deposits, root -def create_genesis_state(num_validators, deposit_data_leaves): +def create_genesis_state(num_validators, deposit_data_leaves=None): initial_deposits, deposit_root = create_mock_genesis_validator_deposits( num_validators, deposit_data_leaves, @@ -105,7 +107,7 @@ def build_empty_block_for_next_slot(state): previous_block_header = deepcopy(state.latest_block_header) if previous_block_header.state_root == spec.ZERO_HASH: previous_block_header.state_root = state.hash_tree_root() - empty_block.previous_block_root = previous_block_header.hash_tree_root() + empty_block.previous_block_root = signed_root(previous_block_header) return empty_block diff --git a/tests/phase0/test_sanity.py b/tests/phase0/test_sanity.py index 444075a13..0b195fe96 100644 --- a/tests/phase0/test_sanity.py +++ b/tests/phase0/test_sanity.py @@ -47,6 +47,8 @@ from tests.phase0.helpers import ( build_empty_block_for_next_slot, force_registry_change_at_next_epoch, get_valid_proposer_slashing, + privkeys, + pubkeys, ) @@ -116,7 +118,7 @@ def test_empty_epoch_transition_not_finalizing(state): return state, [block], test_state -def test_proposer_slashing(state, pubkeys, privkeys): +def test_proposer_slashing(state): test_state = deepcopy(state) proposer_slashing = get_valid_proposer_slashing(state) validator_index = proposer_slashing.proposer_index @@ -142,9 +144,9 @@ def test_proposer_slashing(state, pubkeys, privkeys): return state, [block], test_state -def test_deposit_in_block(state, deposit_data_leaves, pubkeys, privkeys): +def test_deposit_in_block(state): pre_state = deepcopy(state) - test_deposit_data_leaves = deepcopy(deposit_data_leaves) + test_deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry) index = len(test_deposit_data_leaves) pubkey = pubkeys[index] @@ -179,9 +181,9 @@ def test_deposit_in_block(state, deposit_data_leaves, pubkeys, privkeys): return pre_state, [block], post_state -def test_deposit_top_up(state, pubkeys, privkeys, deposit_data_leaves): +def test_deposit_top_up(state): pre_state = deepcopy(state) - test_deposit_data_leaves = deepcopy(deposit_data_leaves) + test_deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry) validator_index = 0 amount = spec.MAX_DEPOSIT_AMOUNT // 4 @@ -218,7 +220,7 @@ def test_deposit_top_up(state, pubkeys, privkeys, deposit_data_leaves): return pre_state, [block], post_state -def test_attestation(state, pubkeys, privkeys): +def test_attestation(state): test_state = deepcopy(state) slot = state.slot shard = state.current_shuffling_start_shard @@ -287,7 +289,7 @@ def test_attestation(state, pubkeys, privkeys): return state, [attestation_block, epoch_block], test_state -def test_voluntary_exit(state, pubkeys, privkeys): +def test_voluntary_exit(state): pre_state = deepcopy(state) validator_index = get_active_validator_indices( pre_state.validator_registry, @@ -375,7 +377,7 @@ def test_no_exit_churn_too_long_since_change(state): return pre_state, [block], post_state -def test_transfer(state, pubkeys, privkeys): +def test_transfer(state): pre_state = deepcopy(state) current_epoch = get_current_epoch(pre_state) sender_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[-1] diff --git a/utils/phase0/jsonize.py b/utils/phase0/jsonize.py new file mode 100644 index 000000000..816192ec6 --- /dev/null +++ b/utils/phase0/jsonize.py @@ -0,0 +1,52 @@ +from .minimal_ssz import hash_tree_root + + +def jsonize(value, typ, include_hash_tree_roots=False): + if isinstance(typ, str) and typ[:4] == 'uint': + return value + elif typ == 'bool': + assert value in (True, False) + return value + elif isinstance(typ, list): + return [jsonize(element, typ[0], include_hash_tree_roots) for element in value] + elif isinstance(typ, str) and typ[:4] == 'byte': + return '0x' + value.hex() + elif hasattr(typ, 'fields'): + ret = {} + for field, subtype in typ.fields.items(): + ret[field] = jsonize(getattr(value, field), subtype, include_hash_tree_roots) + if include_hash_tree_roots: + ret[field + "_hash_tree_root"] = '0x' + hash_tree_root(getattr(value, field), subtype).hex() + if include_hash_tree_roots: + ret["hash_tree_root"] = '0x' + hash_tree_root(value, typ).hex() + return ret + else: + print(value, typ) + raise Exception("Type not recognized") + + +def dejsonize(json, typ): + if isinstance(typ, str) and typ[:4] == 'uint': + return json + elif typ == 'bool': + assert json in (True, False) + return json + elif isinstance(typ, list): + return [dejsonize(element, typ[0]) for element in json] + elif isinstance(typ, str) and typ[:4] == 'byte': + return bytes.fromhex(json[2:]) + elif hasattr(typ, 'fields'): + temp = {} + for field, subtype in typ.fields.items(): + temp[field] = dejsonize(json[field], subtype) + if field + "_hash_tree_root" in json: + assert(json[field + "_hash_tree_root"][2:] == + hash_tree_root(temp[field], subtype).hex()) + ret = typ(**temp) + if "hash_tree_root" in json: + assert(json["hash_tree_root"][2:] == + hash_tree_root(ret, typ).hex()) + return ret + else: + print(json, typ) + raise Exception("Type not recognized") From 0121adea3831ac527fcb4c8a63a04bd63ac8684f Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 26 Mar 2019 07:09:48 -0600 Subject: [PATCH 31/36] fix beacon proposer function and mod v-guide to not have lookahead for proposing --- specs/core/0_beacon-chain.md | 20 ++++++++-------- specs/validator/0_beacon-chain-validator.md | 26 +++++++++++++++------ 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 485233746..c96c28888 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1061,23 +1061,23 @@ def generate_seed(state: BeaconState, ```python def get_beacon_proposer_index(state: BeaconState, - slot: Slot, - registry_change: bool=False) -> ValidatorIndex: + slot: Slot) -> ValidatorIndex: """ Return the beacon proposer index for the ``slot``. + Due to proposer selection being based upon the validator balances during + the epoch in question, this can only be run for the current epoch. """ - epoch = slot_to_epoch(slot) current_epoch = get_current_epoch(state) - previous_epoch = get_previous_epoch(state) - next_epoch = current_epoch + 1 + assert slot_to_epoch(slot) == current_epoch - assert previous_epoch <= epoch <= next_epoch - - first_committee, _ = get_crosslink_committees_at_slot(state, slot, registry_change)[0] + first_committee, _ = get_crosslink_committees_at_slot(state, slot)[0] i = 0 while True: - rand_byte = hash(generate_seed(get_current_epoch(state)) + int_to_bytes8(i // 32))[i % 32] - candidate = first_committee[(epoch + i) % len(first_committee)] + rand_byte = hash( + generate_seed(state, current_epoch) + + int_to_bytes8(i // 32) + )[i % 32] + candidate = first_committee[(current_epoch + i) % len(first_committee)] if get_effective_balance(state, candidate) * 256 > MAX_DEPOSIT_AMOUNT * rand_byte: return candidate i += 1 diff --git a/specs/validator/0_beacon-chain-validator.md b/specs/validator/0_beacon-chain-validator.md index 62a7011b4..f1a10a048 100644 --- a/specs/validator/0_beacon-chain-validator.md +++ b/specs/validator/0_beacon-chain-validator.md @@ -338,15 +338,13 @@ def get_committee_assignment( state: BeaconState, epoch: Epoch, validator_index: ValidatorIndex, - registry_change: bool=False) -> Tuple[List[ValidatorIndex], Shard, Slot, bool]: + registry_change: bool=False) -> Tuple[List[ValidatorIndex], Shard, Slot]: """ Return the committee assignment in the ``epoch`` for ``validator_index`` and ``registry_change``. ``assignment`` returned is a tuple of the following form: * ``assignment[0]`` is the list of validators in the committee * ``assignment[1]`` is the shard to which the committee is assigned * ``assignment[2]`` is the slot at which the committee is assigned - * ``assignment[3]`` is a bool signalling if the validator is expected to propose - a beacon block at the assigned slot. """ previous_epoch = get_previous_epoch(state) next_epoch = get_current_epoch(state) + 1 @@ -367,15 +365,29 @@ def get_committee_assignment( if len(selected_committees) > 0: validators = selected_committees[0][0] shard = selected_committees[0][1] - is_proposer = validator_index == get_beacon_proposer_index(state, slot, registry_change=registry_change) - assignment = (validators, shard, slot, is_proposer) + assignment = (validators, shard, slot) return assignment ``` +A validator can use the following function to see if they are supposed to propose during their assigned committee slot. This function can only be run during the epoch of the slot in question and can not reliably be used to predict an epoch in advance. + +```python +def is_proposer_at_slot(state: BeaconState, + slot: Slot, + validator_index: ValidatorIndex) -> bool: + current_epoch = get_current_epoch(state) + assert slot_to_epoch(slot) == current_epoch + + return get_beacon_proposer_index(state, slot) == validator_index +``` + +_Note_: If a validator is assigned to the 0th slot of an epoch, the validator must run an empty slot transition from the previous epoch into the 0th slot of the epoch to be able to check if they are a proposer at that slot. + + ### Lookahead -The beacon chain shufflings are designed to provide a minimum of 1 epoch lookahead on the validator's upcoming assignments of proposing and attesting dictated by the shuffling and slot. +The beacon chain shufflings are designed to provide a minimum of 1 epoch lookahead on the validator's upcoming committee assignments for attesting dictated by the shuffling and slot. Note that this lookahead does not apply to proposing which must checked during the epoch in question. There are three possibilities for the shuffling at the next epoch: 1. The shuffling changes due to a "validator registry change". @@ -386,7 +398,7 @@ Either (2) or (3) occurs if (1) fails. The choice between (2) and (3) is determi When querying for assignments in the next epoch there are two options -- with and without a `registry_change` -- which is the optional fourth parameter of the `get_committee_assignment`. -`get_committee_assignment` should be called at the start of each epoch to get the assignment for the next epoch (`current_epoch + 1`). A validator should always plan for assignments from both values of `registry_change` unless the validator can concretely eliminate one of the options. Planning for future assignments involves noting at which future slot one might have to attest and propose and also which shard one should begin syncing (in phase 1+). +`get_committee_assignment` should be called at the start of each epoch to get the assignment for the next epoch (`current_epoch + 1`). A validator should always plan for assignments from both values of `registry_change` unless the validator can concretely eliminate one of the options. Planning for future assignments involves noting at which future slot one might have to attest and also which shard one should begin syncing (in phase 1+). Specifically, a validator should call both `get_committee_assignment(state, next_epoch, validator_index, registry_change=True)` and `get_committee_assignment(state, next_epoch, validator_index, registry_change=False)` when checking for next epoch assignments. From be57cafbfbd70368a133a4bd1bc274b306f2af0e Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 26 Mar 2019 07:17:08 -0600 Subject: [PATCH 32/36] switch utils hash-function to sha256 --- utils/phase0/hash_function.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/utils/phase0/hash_function.py b/utils/phase0/hash_function.py index 21e6555bf..3fee63d82 100644 --- a/utils/phase0/hash_function.py +++ b/utils/phase0/hash_function.py @@ -1,7 +1,6 @@ -# from hashlib import sha256 -from eth_utils import keccak +from hashlib import sha256 +# from eth_utils import keccak -# def hash(x): return sha256(x).digest() -def hash(x): - return keccak(x) +def hash(x): return sha256(x).digest() +# def hash(x): return keccak(x) From 5a708bae348221bfe1926775b31a4de05b23a090 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 26 Mar 2019 07:38:51 -0600 Subject: [PATCH 33/36] fix tests --- tests/phase0/helpers.py | 29 ++++++++++------------------- tests/phase0/test_sanity.py | 4 ++-- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/tests/phase0/helpers.py b/tests/phase0/helpers.py index a0ede04e5..3c68c2c8c 100644 --- a/tests/phase0/helpers.py +++ b/tests/phase0/helpers.py @@ -12,7 +12,6 @@ from build.phase0.spec import ( AttestationData, BeaconBlockHeader, Deposit, - DepositInput, DepositData, Eth1Data, ProposerSlashing, @@ -43,21 +42,17 @@ pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys, pubkey def create_mock_genesis_validator_deposits(num_validators, deposit_data_leaves=None): if not deposit_data_leaves: deposit_data_leaves = [] - deposit_timestamp = 0 proof_of_possession = b'\x33' * 96 deposit_data_list = [] for i in range(num_validators): pubkey = pubkeys[i] deposit_data = DepositData( + pubkey=pubkey, + # insecurely use pubkey as withdrawal key as well + withdrawal_credentials=spec.BLS_WITHDRAWAL_PREFIX_BYTE + hash(pubkey)[1:], amount=spec.MAX_DEPOSIT_AMOUNT, - timestamp=deposit_timestamp, - deposit_input=DepositInput( - pubkey=pubkey, - # insecurely use pubkey as withdrawal key as well - withdrawal_credentials=spec.BLS_WITHDRAWAL_PREFIX_BYTE + hash(pubkey)[1:], - proof_of_possession=proof_of_possession, - ), + proof_of_possession=proof_of_possession, ) item = hash(deposit_data.serialize()) deposit_data_leaves.append(item) @@ -72,7 +67,7 @@ def create_mock_genesis_validator_deposits(num_validators, deposit_data_leaves=N genesis_validator_deposits.append(Deposit( proof=list(get_merkle_proof(tree, item_index=i)), index=i, - deposit_data=deposit_data_list[i] + data=deposit_data_list[i] )) return genesis_validator_deposits, root @@ -112,14 +107,15 @@ def build_empty_block_for_next_slot(state): def build_deposit_data(state, pubkey, privkey, amount): - deposit_input = DepositInput( + deposit_data = DepositData( pubkey=pubkey, # insecurely use pubkey as withdrawal key as well withdrawal_credentials=spec.BLS_WITHDRAWAL_PREFIX_BYTE + hash(pubkey)[1:], + amount=amount, proof_of_possession=EMPTY_SIGNATURE, ) proof_of_possession = bls.sign( - message_hash=signed_root(deposit_input), + message_hash=signed_root(deposit_data), privkey=privkey, domain=get_domain( state.fork, @@ -127,12 +123,7 @@ def build_deposit_data(state, pubkey, privkey, amount): spec.DOMAIN_DEPOSIT, ) ) - deposit_input.proof_of_possession = proof_of_possession - deposit_data = DepositData( - amount=amount, - timestamp=0, - deposit_input=deposit_input, - ) + deposit_data.proof_of_possession = proof_of_possession return deposit_data @@ -201,7 +192,7 @@ def build_deposit(state, deposit = Deposit( proof=list(proof), index=index, - deposit_data=deposit_data, + data=deposit_data, ) return deposit, root, deposit_data_leaves diff --git a/tests/phase0/test_sanity.py b/tests/phase0/test_sanity.py index 0b195fe96..19e75f672 100644 --- a/tests/phase0/test_sanity.py +++ b/tests/phase0/test_sanity.py @@ -163,7 +163,7 @@ def test_deposit_in_block(state): deposit = Deposit( proof=list(proof), index=index, - deposit_data=deposit_data, + data=deposit_data, ) pre_state.latest_eth1_data.deposit_root = root @@ -202,7 +202,7 @@ def test_deposit_top_up(state): deposit = Deposit( proof=list(proof), index=merkle_index, - deposit_data=deposit_data, + data=deposit_data, ) pre_state.latest_eth1_data.deposit_root = root From a8410b8b843608bbbcec9c4cad76898f7435ae07 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 26 Mar 2019 11:27:07 -0600 Subject: [PATCH 34/36] add some attestation tests. fix genesi crosslink bug --- specs/core/0_beacon-chain.md | 11 ++- .../test_process_attestation.py | 67 +++++++++++++++++++ tests/phase0/helpers.py | 50 ++++++++++++++ tests/phase0/test_sanity.py | 50 +------------- 4 files changed, 124 insertions(+), 54 deletions(-) create mode 100644 tests/phase0/block_processing/test_process_attestation.py diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index e628c7057..4b52bd2fa 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -905,21 +905,20 @@ def get_crosslink_committees_at_slot(state: BeaconState, next_epoch = current_epoch + 1 assert previous_epoch <= epoch <= next_epoch - active_validator_indices = get_active_validator_indices( + indices = get_active_validator_indices( state.validator_registry, epoch, ) - committees_per_epoch = get_epoch_committee_count(len(active_validator_indices)) + committees_per_epoch = get_epoch_committee_count(len(indices)) if epoch == current_epoch: start_shard = state.latest_start_shard elif epoch == previous_epoch: - start_shard = (state.latest_start_shard - SLOTS_PER_EPOCH * committees_per_epoch) % SHARD_COUNT + start_shard = (state.latest_start_shard - committees_per_epoch) % SHARD_COUNT elif epoch == next_epoch: current_epoch_committees = get_current_epoch_committee_count(state) - start_shard = (state.latest_start_shard + EPOCH_LENGTH * current_epoch_committees) % SHARD_COUNT + start_shard = (state.latest_start_shard + current_epoch_committees) % SHARD_COUNT - indices = get_active_validator_indices(state.validator_registry, epoch) committees_per_slot = committees_per_epoch // SLOTS_PER_EPOCH offset = slot % SLOTS_PER_EPOCH slot_start_shard = (start_shard + committees_per_slot * offset) % SHARD_COUNT @@ -1830,7 +1829,7 @@ Run the following function: ```python def process_crosslinks(state: BeaconState) -> None: current_epoch = get_current_epoch(state) - previous_epoch = current_epoch - 1 + previous_epoch = max(current_epoch - 1, GENESIS_EPOCH) next_epoch = current_epoch + 1 for slot in range(get_epoch_start_slot(previous_epoch), get_epoch_start_slot(next_epoch)): for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot): diff --git a/tests/phase0/block_processing/test_process_attestation.py b/tests/phase0/block_processing/test_process_attestation.py new file mode 100644 index 000000000..80770fdf9 --- /dev/null +++ b/tests/phase0/block_processing/test_process_attestation.py @@ -0,0 +1,67 @@ +from copy import deepcopy +import pytest + +import build.phase0.spec as spec + +from build.phase0.state_transition import ( + state_transition, +) +from build.phase0.spec import ( + ZERO_HASH, + get_current_epoch, + process_attestation, + slot_to_epoch, +) +from tests.phase0.helpers import ( + build_empty_block_for_next_slot, + get_valid_attestation, +) + + +# mark entire file as 'attestations' +pytestmark = pytest.mark.attestations + + +def run_attestation_processing(state, attestation, valid=True): + """ + Run ``process_attestation`` returning the pre and post state. + If ``valid == False``, run expecting ``AssertionError`` + """ + post_state = deepcopy(state) + + if not valid: + with pytest.raises(AssertionError): + process_attestation(post_state, attestation) + return state, None + + process_attestation(post_state, attestation) + + current_epoch = get_current_epoch(state) + target_epoch = slot_to_epoch(attestation.data.slot) + if target_epoch == current_epoch: + assert len(post_state.current_epoch_attestations) == len(state.current_epoch_attestations) + 1 + else: + assert len(post_state.previous_epoch_attestations) == len(state.previous_epoch_attestations) + 1 + + + return state, post_state + + +def test_success(state): + attestation = get_valid_attestation(state) + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + pre_state, post_state = run_attestation_processing(state, attestation) + + return pre_state, attestation, post_state + + +def test_success_prevous_epoch(state): + attestation = get_valid_attestation(state) + block = build_empty_block_for_next_slot(state) + block.slot = state.slot + spec.SLOTS_PER_EPOCH + state_transition(state, block) + + pre_state, post_state = run_attestation_processing(state, attestation) + + return pre_state, attestation, post_state diff --git a/tests/phase0/helpers.py b/tests/phase0/helpers.py index 3c68c2c8c..d7f4ae6e8 100644 --- a/tests/phase0/helpers.py +++ b/tests/phase0/helpers.py @@ -9,7 +9,9 @@ from build.phase0.spec import ( EMPTY_SIGNATURE, ZERO_HASH, # SSZ + Attestation, AttestationData, + AttestationDataAndCustodyBit, BeaconBlockHeader, Deposit, DepositData, @@ -18,7 +20,9 @@ from build.phase0.spec import ( VoluntaryExit, # functions get_active_validator_indices, + get_attestation_participants, get_block_root, + get_crosslink_committees_at_slot, get_current_epoch, get_domain, get_empty_block, @@ -236,3 +240,49 @@ def get_valid_proposer_slashing(state): header_1=header_1, header_2=header_2, ) + + +def get_valid_attestation(state, slot=None): + if slot is None: + slot = state.slot + shard = state.latest_start_shard + attestation_data = build_attestation_data(state, slot, shard) + + crosslink_committees = get_crosslink_committees_at_slot(state, slot) + crosslink_committee = [committee for committee, _shard in crosslink_committees if _shard == attestation_data.shard][0] + + committee_size = len(crosslink_committee) + bitfield_length = (committee_size + 7) // 8 + aggregation_bitfield = b'\x01' + b'\x00' * (bitfield_length - 1) + custody_bitfield = b'\x00' * bitfield_length + attestation = Attestation( + aggregation_bitfield=aggregation_bitfield, + data=attestation_data, + custody_bitfield=custody_bitfield, + aggregate_signature=EMPTY_SIGNATURE, + ) + participants = get_attestation_participants( + state, + attestation.data, + attestation.aggregation_bitfield, + ) + assert len(participants) == 1 + + validator_index = participants[0] + privkey = privkeys[validator_index] + + message_hash = AttestationDataAndCustodyBit( + data=attestation.data, + custody_bit=0b0, + ).hash_tree_root() + + attestation.aggregation_signature = bls.sign( + message_hash=message_hash, + privkey=privkey, + domain=get_domain( + fork=state.fork, + epoch=get_current_epoch(state), + domain_type=spec.DOMAIN_ATTESTATION, + ) + ) + return attestation diff --git a/tests/phase0/test_sanity.py b/tests/phase0/test_sanity.py index a2cbadd9a..b287bde51 100644 --- a/tests/phase0/test_sanity.py +++ b/tests/phase0/test_sanity.py @@ -11,19 +11,13 @@ from build.phase0.spec import ( EMPTY_SIGNATURE, ZERO_HASH, # SSZ - Attestation, - AttestationDataAndCustodyBit, - BeaconBlockHeader, Deposit, Transfer, - ProposerSlashing, VoluntaryExit, # functions get_active_validator_indices, - get_attestation_participants, get_balance, get_block_root, - get_crosslink_committees_at_slot, get_current_epoch, get_domain, get_state_root, @@ -42,10 +36,10 @@ from build.phase0.utils.merkle_minimal import ( get_merkle_root, ) from tests.phase0.helpers import ( - build_attestation_data, build_deposit_data, build_empty_block_for_next_slot, force_registry_change_at_next_epoch, + get_valid_attestation, get_valid_proposer_slashing, privkeys, pubkeys, @@ -222,47 +216,7 @@ def test_deposit_top_up(state): def test_attestation(state): test_state = deepcopy(state) - slot = state.slot - shard = state.latest_start_shard - attestation_data = build_attestation_data(state, slot, shard) - - crosslink_committees = get_crosslink_committees_at_slot(state, slot) - crosslink_committee = [committee for committee, _shard in crosslink_committees if _shard == attestation_data.shard][0] - - committee_size = len(crosslink_committee) - bitfield_length = (committee_size + 7) // 8 - aggregation_bitfield = b'\x01' + b'\x00' * (bitfield_length - 1) - custody_bitfield = b'\x00' * bitfield_length - attestation = Attestation( - aggregation_bitfield=aggregation_bitfield, - data=attestation_data, - custody_bitfield=custody_bitfield, - aggregate_signature=EMPTY_SIGNATURE, - ) - participants = get_attestation_participants( - test_state, - attestation.data, - attestation.aggregation_bitfield, - ) - assert len(participants) == 1 - - validator_index = participants[0] - privkey = privkeys[validator_index] - - message_hash = AttestationDataAndCustodyBit( - data=attestation.data, - custody_bit=0b0, - ).hash_tree_root() - - attestation.aggregation_signature = bls.sign( - message_hash=message_hash, - privkey=privkey, - domain=get_domain( - fork=test_state.fork, - epoch=get_current_epoch(test_state), - domain_type=spec.DOMAIN_ATTESTATION, - ) - ) + attestation = get_valid_attestation(state) # # Add to state via block transition From 9fa6055a8a5b2c73774f143d3abdbe23323e93b4 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 26 Mar 2019 11:41:15 -0600 Subject: [PATCH 35/36] add more attestation tests --- .../test_process_attestation.py | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/tests/phase0/block_processing/test_process_attestation.py b/tests/phase0/block_processing/test_process_attestation.py index 80770fdf9..b34c64d95 100644 --- a/tests/phase0/block_processing/test_process_attestation.py +++ b/tests/phase0/block_processing/test_process_attestation.py @@ -65,3 +65,90 @@ def test_success_prevous_epoch(state): pre_state, post_state = run_attestation_processing(state, attestation) return pre_state, attestation, post_state + + +def test_before_inclusion_delay(state): + attestation = get_valid_attestation(state) + # do not increment slot to allow for inclusion delay + + pre_state, post_state = run_attestation_processing(state, attestation, False) + + return pre_state, attestation, post_state + + +def test_after_epoch_slots(state): + attestation = get_valid_attestation(state) + block = build_empty_block_for_next_slot(state) + # increment past latest inclusion slot + block.slot = state.slot + spec.SLOTS_PER_EPOCH + 1 + state_transition(state, block) + + pre_state, post_state = run_attestation_processing(state, attestation, False) + + return pre_state, attestation, post_state + + +def test_bad_source_epoch(state): + attestation = get_valid_attestation(state) + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + attestation.data.source_epoch += 10 + + pre_state, post_state = run_attestation_processing(state, attestation, False) + + return pre_state, attestation, post_state + + +def test_bad_source_root(state): + attestation = get_valid_attestation(state) + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + attestation.data.source_root = b'\x42'*32 + + pre_state, post_state = run_attestation_processing(state, attestation, False) + + return pre_state, attestation, post_state + + +def test_non_zero_crosslink_data_root(state): + attestation = get_valid_attestation(state) + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + attestation.data.crosslink_data_root = b'\x42'*32 + + pre_state, post_state = run_attestation_processing(state, attestation, False) + + return pre_state, attestation, post_state + + +def test_bad_previous_crosslink(state): + attestation = get_valid_attestation(state) + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + state.latest_crosslinks[attestation.data.shard].epoch += 10 + + pre_state, post_state = run_attestation_processing(state, attestation, False) + + return pre_state, attestation, post_state + + +def test_non_empty_custody_bitfield(state): + attestation = get_valid_attestation(state) + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + attestation.custody_bitfield = b'\x01' + attestation.custody_bitfield[1:] + + pre_state, post_state = run_attestation_processing(state, attestation, False) + + return pre_state, attestation, post_state + + +def test_empty_aggregation_bitfield(state): + attestation = get_valid_attestation(state) + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + attestation.aggregation_bitfield = b'\x00' * len(attestation.aggregation_bitfield) + + pre_state, post_state = run_attestation_processing(state, attestation, False) + + return pre_state, attestation, post_state From 2c5a68b5b5d4348ede49f07e50b943eb22c03414 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 26 Mar 2019 12:32:24 -0600 Subject: [PATCH 36/36] remove registry_change options from shuffling functions --- specs/core/0_beacon-chain.md | 8 ++----- specs/validator/0_beacon-chain-validator.md | 21 +++++-------------- .../test_process_attestation.py | 1 - 3 files changed, 7 insertions(+), 23 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 4b52bd2fa..38f5f56c5 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -891,13 +891,9 @@ def get_current_epoch_committee_count(state: BeaconState) -> int: ```python def get_crosslink_committees_at_slot(state: BeaconState, - slot: Slot, - registry_change: bool=False) -> List[Tuple[List[ValidatorIndex], Shard]]: + slot: Slot) -> List[Tuple[List[ValidatorIndex], Shard]]: """ Return the list of ``(committee, shard)`` tuples for the ``slot``. - - Note: There are two possible shufflings for crosslink committees for a - ``slot`` in the next epoch -- with and without a `registry_change` """ epoch = slot_to_epoch(slot) current_epoch = get_current_epoch(state) @@ -2339,7 +2335,7 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: # Check target epoch, source epoch, and source root target_epoch = slot_to_epoch(attestation.data.slot) assert (target_epoch, attestation.data.source_epoch, attestation.data.source_root) in { - (get_current_epoch(state), state.current_justified_epoch, state.current_justified_root), + (get_current_epoch(state), state.current_justified_epoch, state.current_justified_root), (get_previous_epoch(state), state.previous_justified_epoch, state.previous_justified_root), } diff --git a/specs/validator/0_beacon-chain-validator.md b/specs/validator/0_beacon-chain-validator.md index f1a10a048..4a4c63836 100644 --- a/specs/validator/0_beacon-chain-validator.md +++ b/specs/validator/0_beacon-chain-validator.md @@ -331,16 +331,15 @@ signed_attestation_data = bls_sign( ## Validator assignments -A validator can get the current and previous epoch committee assignments using the following helper via `get_committee_assignment(state, epoch, validator_index)` where `previous_epoch <= epoch <= current_epoch`. +A validator can get the current, previous, and next epoch committee assignments using the following helper via `get_committee_assignment(state, epoch, validator_index)` where `previous_epoch <= epoch <= next_epoch`. ```python def get_committee_assignment( state: BeaconState, epoch: Epoch, - validator_index: ValidatorIndex, - registry_change: bool=False) -> Tuple[List[ValidatorIndex], Shard, Slot]: + validator_index: ValidatorIndex) -> Tuple[List[ValidatorIndex], Shard, Slot]: """ - Return the committee assignment in the ``epoch`` for ``validator_index`` and ``registry_change``. + Return the committee assignment in the ``epoch`` for ``validator_index``. ``assignment`` returned is a tuple of the following form: * ``assignment[0]`` is the list of validators in the committee * ``assignment[1]`` is the shard to which the committee is assigned @@ -355,7 +354,6 @@ def get_committee_assignment( crosslink_committees = get_crosslink_committees_at_slot( state, slot, - registry_change=registry_change, ) selected_committees = [ committee # Tuple[List[ValidatorIndex], Shard] @@ -389,18 +387,9 @@ _Note_: If a validator is assigned to the 0th slot of an epoch, the validator mu The beacon chain shufflings are designed to provide a minimum of 1 epoch lookahead on the validator's upcoming committee assignments for attesting dictated by the shuffling and slot. Note that this lookahead does not apply to proposing which must checked during the epoch in question. -There are three possibilities for the shuffling at the next epoch: -1. The shuffling changes due to a "validator registry change". -2. The shuffling changes due to `epochs_since_last_registry_update` being an exact power of 2 greater than 1. -3. The shuffling remains the same (i.e. the validator is in the same shard committee). +`get_committee_assignment` should be called at the start of each epoch to get the assignment for the next epoch (`current_epoch + 1`). A validator should plan for future assignments which involves noting at which future slot one will have to attest and also which shard one should begin syncing (in phase 1+). -Either (2) or (3) occurs if (1) fails. The choice between (2) and (3) is deterministic based upon `epochs_since_last_registry_update`. - -When querying for assignments in the next epoch there are two options -- with and without a `registry_change` -- which is the optional fourth parameter of the `get_committee_assignment`. - -`get_committee_assignment` should be called at the start of each epoch to get the assignment for the next epoch (`current_epoch + 1`). A validator should always plan for assignments from both values of `registry_change` unless the validator can concretely eliminate one of the options. Planning for future assignments involves noting at which future slot one might have to attest and also which shard one should begin syncing (in phase 1+). - -Specifically, a validator should call both `get_committee_assignment(state, next_epoch, validator_index, registry_change=True)` and `get_committee_assignment(state, next_epoch, validator_index, registry_change=False)` when checking for next epoch assignments. +Specifically, a validator should call `get_committee_assignment(state, next_epoch, validator_index)` when checking for next epoch assignments. ## How to avoid slashing diff --git a/tests/phase0/block_processing/test_process_attestation.py b/tests/phase0/block_processing/test_process_attestation.py index b34c64d95..08cab11ff 100644 --- a/tests/phase0/block_processing/test_process_attestation.py +++ b/tests/phase0/block_processing/test_process_attestation.py @@ -43,7 +43,6 @@ def run_attestation_processing(state, attestation, valid=True): else: assert len(post_state.previous_epoch_attestations) == len(state.previous_epoch_attestations) + 1 - return state, post_state