From fe13bab33803eea248385016825ae1a8c4ae27e6 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 23 Apr 2020 10:26:34 -0600 Subject: [PATCH 01/53] rework rewards/penalties to be more granular --- specs/phase0/beacon-chain.md | 101 +++++++++++++++++++++++++++-------- 1 file changed, 80 insertions(+), 21 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index cdf38dc1e..00e1d3a73 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1347,32 +1347,57 @@ def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: ``` ```python -def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: +def get_eligible_validator_indices(state: BeaconState) -> Sequence[ValidatorIndex]: previous_epoch = get_previous_epoch(state) - total_balance = get_total_active_balance(state) - rewards = [Gwei(0) for _ in range(len(state.validators))] - penalties = [Gwei(0) for _ in range(len(state.validators))] - eligible_validator_indices = [ + return [ ValidatorIndex(index) for index, v in enumerate(state.validators) if is_active_validator(v, previous_epoch) or (v.slashed and previous_epoch + 1 < v.withdrawable_epoch) ] +``` - # Micro-incentives for matching FFG source, FFG target, and head - matching_source_attestations = get_matching_source_attestations(state, previous_epoch) - matching_target_attestations = get_matching_target_attestations(state, previous_epoch) - matching_head_attestations = get_matching_head_attestations(state, previous_epoch) - for attestations in (matching_source_attestations, matching_target_attestations, matching_head_attestations): - unslashed_attesting_indices = get_unslashed_attesting_indices(state, attestations) - attesting_balance = get_total_balance(state, unslashed_attesting_indices) - for index in eligible_validator_indices: - if index in unslashed_attesting_indices: - increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from balance totals to avoid uint64 overflow - reward_numerator = get_base_reward(state, index) * (attesting_balance // increment) - rewards[index] += reward_numerator // (total_balance // increment) - else: - penalties[index] += get_base_reward(state, index) +```python +def compute_attestation_component_deltas(state: BeaconState, + attestations: Sequence[PendingAttestation] + ) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + rewards = [Gwei(0) for _ in range(len(state.validators))] + penalties = [Gwei(0) for _ in range(len(state.validators))] + total_balance = get_total_active_balance(state) + unslashed_attesting_indices = get_unslashed_attesting_indices(state, attestations) + attesting_balance = get_total_balance(state, unslashed_attesting_indices) + for index in get_eligible_validator_indices(state): + if index in unslashed_attesting_indices: + increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from balance totals to avoid uint64 overflow + reward_numerator = get_base_reward(state, index) * (attesting_balance // increment) + rewards[index] += reward_numerator // (total_balance // increment) + else: + penalties[index] += get_base_reward(state, index) + return rewards, penalties +``` +```python +def get_source_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + matching_source_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) + return compute_attestation_component_deltas(state, matching_source_attestations) +``` + +```python +def get_target_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + matching_target_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) + return compute_attestation_component_deltas(state, matching_target_attestations) +``` + +```python +def get_head_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + matching_head_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) + return compute_attestation_component_deltas(state, matching_head_attestations) +``` + +```python +def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: # Proposer and inclusion delay micro-rewards + rewards = [Gwei(0) for _ in range(len(state.validators))] + penalties = [Gwei(0) for _ in range(len(state.validators))] + matching_source_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) for index in get_unslashed_attesting_indices(state, matching_source_attestations): attestation = min([ a for a in matching_source_attestations @@ -1382,16 +1407,50 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence rewards[attestation.proposer_index] += proposer_reward max_attester_reward = get_base_reward(state, index) - proposer_reward rewards[index] += Gwei(max_attester_reward // attestation.inclusion_delay) + return rewards, penalties +``` +```python +def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: # Inactivity penalty - finality_delay = previous_epoch - state.finalized_checkpoint.epoch + rewards = [Gwei(0) for _ in range(len(state.validators))] + penalties = [Gwei(0) for _ in range(len(state.validators))] + finality_delay = get_previous_epoch(state) - state.finalized_checkpoint.epoch + if finality_delay > MIN_EPOCHS_TO_INACTIVITY_PENALTY: + matching_target_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) matching_target_attesting_indices = get_unslashed_attesting_indices(state, matching_target_attestations) - for index in eligible_validator_indices: + for index in get_eligible_validator_indices(state): penalties[index] += Gwei(BASE_REWARDS_PER_EPOCH * get_base_reward(state, index)) if index not in matching_target_attesting_indices: effective_balance = state.validators[index].effective_balance penalties[index] += Gwei(effective_balance * finality_delay // INACTIVITY_PENALTY_QUOTIENT) + return rewards, penalties +``` + +```python +def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + source_rewards, source_penalties = get_source_deltas(state) + target_rewards, target_penalties = get_target_deltas(state) + head_rewards, head_penalties = get_head_deltas(state) + inclusion_delay_rewards, _ = get_inclusion_delay_deltas(state) + _, inactivity_penalties = get_inactivity_penalty_deltas(state) + + rewards = [ + source_rewards[i] + + target_rewards[i] + + head_rewards[i] + + inclusion_delay_rewards[i] + for i in range(len(state.validators)) + ] + + penalties = [ + source_penalties[i] + + target_penalties[i] + + head_penalties[i] + + inactivity_penalties[i] + for i in range(len(state.validators)) + ] return rewards, penalties ``` From 7612667bbef59c7df5255d8bebde1ba6618ce18b Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 23 Apr 2020 11:13:09 -0600 Subject: [PATCH 02/53] minor feedback and fixes on rewards/penalites proposal Co-Authored-By: Hsiao-Wei Wang --- specs/phase0/beacon-chain.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 00e1d3a73..2d9429cf1 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1382,7 +1382,7 @@ def get_source_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei ```python def get_target_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: - matching_target_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) + matching_target_attestations = get_matching_target_attestations(state, get_previous_epoch(state)) return compute_attestation_component_deltas(state, matching_target_attestations) ``` @@ -1394,7 +1394,9 @@ def get_head_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]] ```python def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: - # Proposer and inclusion delay micro-rewards + """ + Return proposer and inclusion delay micro-rewards. + """ rewards = [Gwei(0) for _ in range(len(state.validators))] penalties = [Gwei(0) for _ in range(len(state.validators))] matching_source_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) @@ -1412,13 +1414,15 @@ def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequ ```python def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: - # Inactivity penalty + """ + Return inactivity penalty. + """ rewards = [Gwei(0) for _ in range(len(state.validators))] penalties = [Gwei(0) for _ in range(len(state.validators))] finality_delay = get_previous_epoch(state) - state.finalized_checkpoint.epoch if finality_delay > MIN_EPOCHS_TO_INACTIVITY_PENALTY: - matching_target_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) + matching_target_attestations = get_matching_target_attestations(state, get_previous_epoch(state)) matching_target_attesting_indices = get_unslashed_attesting_indices(state, matching_target_attestations) for index in get_eligible_validator_indices(state): penalties[index] += Gwei(BASE_REWARDS_PER_EPOCH * get_base_reward(state, index)) From bf806b9efaebf957d1399adac4bd839a58db537c Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 24 Apr 2020 15:01:18 +1000 Subject: [PATCH 03/53] Require "seen" aggregates to be valid --- specs/phase0/p2p-interface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index d3b9150d1..92194920b 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -289,7 +289,7 @@ There are two primary global topics used to propagate beacon blocks and aggregat - The block is proposed by the expected `proposer_index` for the block's slot in the context of the current shuffling (defined by `parent_root`/`slot`). If the `proposer_index` cannot immediately be verified against the expected shuffling, the block MAY be queued for later processing while proposers for the block's branch are calculated. - `beacon_aggregate_and_proof` - This topic is used to propagate aggregated attestations (as `SignedAggregateAndProof`s) to subscribing nodes (typically validators) to be included in future blocks. The following validations MUST pass before forwarding the `signed_aggregate_and_proof` on the network. (We define the following for convenience -- `aggregate_and_proof = signed_aggregate_and_proof.message` and `aggregate = aggregate_and_proof.aggregate`) - `aggregate.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot` (a client MAY queue future aggregates for processing at the appropriate slot). - - The aggregate attestation defined by `hash_tree_root(aggregate)` has _not_ already been seen (via aggregate gossip, within a verified block, or through the creation of an equivalent aggregate locally). + - The valid aggregate attestation defined by `hash_tree_root(aggregate)` has _not_ already been seen (via aggregate gossip, within a verified block, or through the creation of an equivalent aggregate locally). - The `aggregate` is the first valid aggregate received for the aggregator with index `aggregate_and_proof.aggregator_index` for the epoch `aggregate.data.target.epoch`. - The block being voted for (`aggregate.data.beacon_block_root`) passes validation. - `aggregate_and_proof.selection_proof` selects the validator as an aggregator for the slot -- i.e. `is_aggregator(state, aggregate.data.slot, aggregate.data.index, aggregate_and_proof.selection_proof)` returns `True`. From 1a81c873af99397d4f0cc69eb2a462d037d01b54 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 26 Apr 2020 16:24:16 +1000 Subject: [PATCH 04/53] Remove redundant check in fork choice --- specs/phase0/fork-choice.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 18c7a1580..35e2c5f56 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -278,8 +278,6 @@ def validate_on_attestation(store: Store, attestation: Attestation) -> None: # Attestations target be for a known block. If target block is unknown, delay consideration until the block is found assert target.root in store.blocks - # Attestations cannot be from future epochs. If they are, delay consideration until the epoch arrives - assert get_current_slot(store) >= compute_start_slot_at_epoch(target.epoch) # Attestations must be for a known block. If block is unknown, delay consideration until the block is found assert attestation.data.beacon_block_root in store.blocks From c841aa102bad17c0e06d0c1bb033d44591cad619 Mon Sep 17 00:00:00 2001 From: Raw Pong Ghmoa <58883403+q9f@users.noreply.github.com> Date: Sun, 26 Apr 2020 10:09:22 +0200 Subject: [PATCH 05/53] genesis: clarify that eth1 timestamp can be less than min genesis time --- specs/phase0/beacon-chain.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index cdf38dc1e..3ef4081ce 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1170,6 +1170,8 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, return state ``` +*Note*: The ETH1 block with `eth1_timestamp` meeting the minimum genesis active validator count criteria can also occur before `MIN_GENESIS_TIME`. + ### Genesis state Let `genesis_state = candidate_state` whenever `is_valid_genesis_state(candidate_state) is True` for the first time. From 3cc1fb901760f1c7ab35bcb69f4ce7c5dce78180 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Mon, 27 Apr 2020 14:34:50 -0700 Subject: [PATCH 06/53] Remove `/` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b6e25d570..fed65eedb 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This repository hosts the current Eth2 specifications. Discussions about design ## Specs -Core specifications for Eth2 clients be found in [specs/](specs/). These are divided into phases. Each subsequent phase depends upon the prior. The current phases specified are: +Core specifications for Eth2 clients be found in [specs](specs/). These are divided into phases. Each subsequent phase depends upon the prior. The current phases specified are: ### Phase 0 * [The Beacon Chain](specs/phase0/beacon-chain.md) From fa66475da46d334ed2214a7b72143829af9cafab Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 29 Apr 2020 00:04:44 +0800 Subject: [PATCH 07/53] Move `sanity` to under `phase_0` --- .../core/pyspec/eth2spec/test/{ => phase_0}/sanity/__init__.py | 0 .../pyspec/eth2spec/test/{ => phase_0}/sanity/test_blocks.py | 0 .../pyspec/eth2spec/test/{ => phase_0}/sanity/test_slots.py | 0 tests/generators/sanity/main.py | 2 +- 4 files changed, 1 insertion(+), 1 deletion(-) rename tests/core/pyspec/eth2spec/test/{ => phase_0}/sanity/__init__.py (100%) rename tests/core/pyspec/eth2spec/test/{ => phase_0}/sanity/test_blocks.py (100%) rename tests/core/pyspec/eth2spec/test/{ => phase_0}/sanity/test_slots.py (100%) diff --git a/tests/core/pyspec/eth2spec/test/sanity/__init__.py b/tests/core/pyspec/eth2spec/test/phase_0/sanity/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/sanity/__init__.py rename to tests/core/pyspec/eth2spec/test/phase_0/sanity/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase_0/sanity/test_blocks.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/sanity/test_blocks.py rename to tests/core/pyspec/eth2spec/test/phase_0/sanity/test_blocks.py diff --git a/tests/core/pyspec/eth2spec/test/sanity/test_slots.py b/tests/core/pyspec/eth2spec/test/phase_0/sanity/test_slots.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/sanity/test_slots.py rename to tests/core/pyspec/eth2spec/test/phase_0/sanity/test_slots.py diff --git a/tests/generators/sanity/main.py b/tests/generators/sanity/main.py index 74c85a9e8..89fb89838 100644 --- a/tests/generators/sanity/main.py +++ b/tests/generators/sanity/main.py @@ -5,7 +5,7 @@ from gen_base import gen_runner, gen_typing from gen_from_tests.gen import generate_from_tests from eth2spec.test.context import PHASE0 -from eth2spec.test.sanity import test_blocks, test_slots +from eth2spec.test.phase_0.sanity import test_blocks, test_slots from eth2spec.config import config_util from eth2spec.phase0 import spec as spec_phase0 from eth2spec.phase1 import spec as spec_phase1 From 5f18dd778c1d44203140a27f088b9596767189d5 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 28 Apr 2020 19:26:14 -0600 Subject: [PATCH 08/53] add baseline get_target_deltas tests --- specs/phase0/beacon-chain.md | 24 ++++- .../eth2spec/test/helpers/attestations.py | 33 ++++++- .../rewards/test_get_target_deltas.py | 92 +++++++++++++++++++ .../test_process_rewards_and_penalties.py | 28 +----- 4 files changed, 147 insertions(+), 30 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_target_deltas.py diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 33689ece9..f40f0096b 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1341,6 +1341,8 @@ def process_justification_and_finalization(state: BeaconState) -> None: #### Rewards and penalties +##### Helpers + ```python def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: total_balance = get_total_active_balance(state) @@ -1376,20 +1378,31 @@ def compute_attestation_component_deltas(state: BeaconState, return rewards, penalties ``` +##### Components of attestation deltas + ```python def get_source_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return attester micro-rewards/penalties for source-vote for each validator. + """ matching_source_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) return compute_attestation_component_deltas(state, matching_source_attestations) ``` ```python def get_target_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return attester micro-rewards/penalties for target-vote for each validator. + """ matching_target_attestations = get_matching_target_attestations(state, get_previous_epoch(state)) return compute_attestation_component_deltas(state, matching_target_attestations) ``` ```python def get_head_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return attester micro-rewards/penalties for head-vote for each validator. + """ matching_head_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) return compute_attestation_component_deltas(state, matching_head_attestations) ``` @@ -1397,7 +1410,7 @@ def get_head_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]] ```python def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: """ - Return proposer and inclusion delay micro-rewards. + Return proposer and inclusion delay micro-rewards/penalties for each validator. """ rewards = [Gwei(0) for _ in range(len(state.validators))] penalties = [Gwei(0) for _ in range(len(state.validators))] @@ -1417,7 +1430,7 @@ def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequ ```python def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: """ - Return inactivity penalty. + Return inactivity reward/penalty deltas for each validator. """ rewards = [Gwei(0) for _ in range(len(state.validators))] penalties = [Gwei(0) for _ in range(len(state.validators))] @@ -1434,8 +1447,13 @@ def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], S return rewards, penalties ``` +##### `get_attestation_deltas` + ```python def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return attestation reward/penalty deltas for each validator. + """ source_rewards, source_penalties = get_source_deltas(state) target_rewards, target_penalties = get_target_deltas(state) head_rewards, head_penalties = get_head_deltas(state) @@ -1461,6 +1479,8 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence return rewards, penalties ``` +##### `process_rewards_and_penalties` + ```python def process_rewards_and_penalties(state: BeaconState) -> None: if get_current_epoch(state) == GENESIS_EPOCH: diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 8215f5c5b..692e874de 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -1,7 +1,7 @@ from typing import List from eth2spec.test.context import expect_assertion_error, PHASE0 -from eth2spec.test.helpers.state import state_transition_and_sign_block +from eth2spec.test.helpers.state import state_transition_and_sign_block, next_epoch, next_slot from eth2spec.test.helpers.block import build_empty_block_for_next_slot from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls @@ -278,3 +278,34 @@ def next_epoch_with_attestations(spec, signed_blocks.append(signed_block) return state, signed_blocks, post_state + + +def prepare_state_with_full_attestations(spec, state, empty=False): + """ + Fill ``state`` with maximally full attestations. + Move to the start of the next epoch to ensure full epoch worth. + """ + # Go to start of next epoch to ensure can have full participation + next_epoch(spec, state) + + start_slot = state.slot + start_epoch = spec.get_current_epoch(state) + next_epoch_start_slot = spec.compute_start_slot_at_epoch(start_epoch + 1) + attestations = [] + for _ in range(spec.SLOTS_PER_EPOCH + spec.MIN_ATTESTATION_INCLUSION_DELAY): + # create an attestation for each index in each slot in epoch + if state.slot < next_epoch_start_slot: + for committee_index in range(spec.get_committee_count_at_slot(state, state.slot)): + attestation = get_valid_attestation(spec, state, index=committee_index, empty=empty, signed=True) + attestations.append(attestation) + # fill each created slot in state after inclusion delay + if state.slot >= start_slot + spec.MIN_ATTESTATION_INCLUSION_DELAY: + inclusion_slot = state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + include_attestations = [att for att in attestations if att.data.slot == inclusion_slot] + add_attestations_to_state(spec, state, include_attestations, state.slot) + next_slot(spec, state) + + assert state.slot == next_epoch_start_slot + spec.MIN_ATTESTATION_INCLUSION_DELAY + assert len(state.previous_epoch_attestations) == len(attestations) + + return attestations diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_target_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_target_deltas.py new file mode 100644 index 000000000..c30865cf0 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_target_deltas.py @@ -0,0 +1,92 @@ +from eth2spec.test.context import with_all_phases, spec_state_test +from eth2spec.test.helpers.attestations import prepare_state_with_full_attestations + + +def run_get_target_deltas(spec, state): + """ + Run ``process_block_header``, yielding: + - pre-state ('pre') + - rewards ('rewards') + - penalties ('penalties') + """ + yield 'pre', state + + rewards, penalties = spec.get_target_deltas(state) + + yield 'rewards', rewards + yield 'penalties', penalties + + matching_target_attestations = spec.get_matching_target_attestations(state, spec.get_previous_epoch(state)) + matching_target_indices = spec.get_unslashed_attesting_indices(state, matching_target_attestations) + for index in spec.get_eligible_validator_indices(state): + if index in matching_target_indices and not state.validators[index].slashed: + assert rewards[index] > 0 + assert penalties[index] == 0 + else: + assert rewards[index] == 0 + assert penalties[index] > 0 + + +@with_all_phases +@spec_state_test +def test_empty(spec, state): + # Do not add any attestations to state + + yield from run_get_target_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_full_all_correct(spec, state): + prepare_state_with_full_attestations(spec, state) + + yield from run_get_target_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_full_half_correct(spec, state): + prepare_state_with_full_attestations(spec, state) + + # Make half of pending attestations have bad target + for pending_attestation in state.previous_epoch_attestations[:len(state.previous_epoch_attestations) // 2]: + pending_attestation.data.target.root = b'\x66'*32 + + yield from run_get_target_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_half_full(spec, state): + prepare_state_with_full_attestations(spec, state) + + # Remove half of attestations + state.previous_epoch_attestations = state.previous_epoch_attestations[:len(state.previous_epoch_attestations) // 2] + + yield from run_get_target_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_one_correct(spec, state): + prepare_state_with_full_attestations(spec, state) + + # Remove half of attestations + state.previous_epoch_attestations = state.previous_epoch_attestations[:1] + + yield from run_get_target_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_with_slashed_validators(spec, state): + prepare_state_with_full_attestations(spec, state) + + # Slash half of validators + for validator in state.validators: + validator.slashed = True + + yield from run_get_target_deltas(spec, state) + +def test_some_zero_balances(spec, state): + diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_rewards_and_penalties.py b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_rewards_and_penalties.py index af695fe69..f1d86c373 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_rewards_and_penalties.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_rewards_and_penalties.py @@ -12,6 +12,7 @@ from eth2spec.test.helpers.state import ( from eth2spec.test.helpers.attestations import ( add_attestations_to_state, get_valid_attestation, + prepare_state_with_full_attestations, ) from eth2spec.test.helpers.attester_slashings import get_indexed_attestation_participants from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with @@ -21,33 +22,6 @@ def run_process_rewards_and_penalties(spec, state): yield from run_epoch_processing_with(spec, state, 'process_rewards_and_penalties') -def prepare_state_with_full_attestations(spec, state, empty=False): - # Go to start of next epoch to ensure can have full participation - next_epoch(spec, state) - - start_slot = state.slot - start_epoch = spec.get_current_epoch(state) - next_epoch_start_slot = spec.compute_start_slot_at_epoch(start_epoch + 1) - attestations = [] - for _ in range(spec.SLOTS_PER_EPOCH + spec.MIN_ATTESTATION_INCLUSION_DELAY): - # create an attestation for each index in each slot in epoch - if state.slot < next_epoch_start_slot: - for committee_index in range(spec.get_committee_count_at_slot(state, state.slot)): - attestation = get_valid_attestation(spec, state, index=committee_index, empty=empty, signed=True) - attestations.append(attestation) - # fill each created slot in state after inclusion delay - if state.slot >= start_slot + spec.MIN_ATTESTATION_INCLUSION_DELAY: - inclusion_slot = state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY - include_attestations = [att for att in attestations if att.data.slot == inclusion_slot] - add_attestations_to_state(spec, state, include_attestations, state.slot) - next_slot(spec, state) - - assert state.slot == next_epoch_start_slot + spec.MIN_ATTESTATION_INCLUSION_DELAY - assert len(state.previous_epoch_attestations) == len(attestations) - - return attestations - - @with_phases(['phase0']) @spec_state_test def test_genesis_epoch_no_attestations_no_penalties(spec, state): From cd27e5e045f8a2f5d3a073e496596126fcedfc3a Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 29 Apr 2020 09:56:48 -0600 Subject: [PATCH 09/53] add tests for source, target, head --- specs/phase0/beacon-chain.md | 6 +- .../pyspec/eth2spec/test/helpers/rewards.py | 117 ++++++++++++++++ .../rewards/test_get_head_deltas.py | 109 +++++++++++++++ .../rewards/test_get_source_deltas.py | 116 ++++++++++++++++ .../rewards/test_get_target_deltas.py | 125 ++++++++++-------- 5 files changed, 418 insertions(+), 55 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/helpers/rewards.py create mode 100644 tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_head_deltas.py create mode 100644 tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_source_deltas.py diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index f40f0096b..95f06f125 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -110,6 +110,10 @@ - [Helper functions](#helper-functions-1) - [Justification and finalization](#justification-and-finalization) - [Rewards and penalties](#rewards-and-penalties-1) + - [Helpers](#helpers) + - [Components of attestation deltas](#components-of-attestation-deltas) + - [`get_attestation_deltas`](#get_attestation_deltas) + - [`process_rewards_and_penalties`](#process_rewards_and_penalties) - [Registry updates](#registry-updates) - [Slashings](#slashings) - [Final updates](#final-updates) @@ -1403,7 +1407,7 @@ def get_head_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]] """ Return attester micro-rewards/penalties for head-vote for each validator. """ - matching_head_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) + matching_head_attestations = get_matching_head_attestations(state, get_previous_epoch(state)) return compute_attestation_component_deltas(state, matching_head_attestations) ``` diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py new file mode 100644 index 000000000..41497fe60 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -0,0 +1,117 @@ +from eth2spec.test.helpers.attestations import prepare_state_with_full_attestations + + +def run_attestation_component_deltas(spec, state, component_delta_fn, matching_att_fn): + """ + Run ``component_delta_fn``, yielding: + - pre-state ('pre') + - rewards ('rewards') + - penalties ('penalties') + """ + yield 'pre', state + + rewards, penalties = component_delta_fn(state) + + yield 'rewards', rewards + yield 'penalties', penalties + + matching_attestations = matching_att_fn(state, spec.get_previous_epoch(state)) + matching_indices = spec.get_unslashed_attesting_indices(state, matching_attestations) + for index in spec.get_eligible_validator_indices(state): + validator = state.validators[index] + enough_for_reward = ( + validator.effective_balance * spec.BASE_REWARD_FACTOR + > spec.integer_squareroot(spec.get_total_active_balance(state)) // spec.BASE_REWARDS_PER_EPOCH + ) + + if index in matching_indices and not validator.slashed: + if enough_for_reward: + assert rewards[index] > 0 + else: + assert rewards[index] == 0 + assert penalties[index] == 0 + else: + assert rewards[index] == 0 + if enough_for_reward: + assert penalties[index] > 0 + else: + assert penalties[index] == 0 + + +def test_empty(spec, state, runner): + # Do not add any attestations to state + + yield from runner(spec, state) + + +def test_full_all_correct(spec, state, runner): + prepare_state_with_full_attestations(spec, state) + + yield from runner(spec, state) + + +def test_half_full(spec, state, runner): + prepare_state_with_full_attestations(spec, state) + + # Remove half of attestations + state.previous_epoch_attestations = state.previous_epoch_attestations[:len(state.previous_epoch_attestations) // 2] + + yield from runner(spec, state) + + +def test_one_attestation_one_correct(spec, state, runner): + prepare_state_with_full_attestations(spec, state) + + # Remove half of attestations + state.previous_epoch_attestations = state.previous_epoch_attestations[:1] + + yield from runner(spec, state) + + +def test_with_slashed_validators(spec, state, runner): + prepare_state_with_full_attestations(spec, state) + + # Slash half of validators + for validator in state.validators[:len(state.validators) // 2]: + validator.slashed = True + + yield from runner(spec, state) + + +def test_some_zero_effective_balances_that_attested(spec, state, runner): + prepare_state_with_full_attestations(spec, state) + + # Set some balances to zero + state.validators[0].effective_balance = 0 + state.validators[1].effective_balance = 0 + + yield from runner(spec, state) + + +def test_some_zero_effective_balances_that_did_not_attest(spec, state, runner): + prepare_state_with_full_attestations(spec, state) + + # Set some balances to zero + attestation = state.previous_epoch_attestations[0] + # Remove attestation + state.previous_epoch_attestations = state.previous_epoch_attestations[1:] + # Set removed indices effective balance to zero + indices = spec.get_unslashed_attesting_indices(state, [attestation]) + for index in indices: + state.validators[index].effective_balance = 0 + + yield from runner(spec, state) + + +def test_full_fraction_incorrect(spec, state, correct_target, correct_head, fraction_incorrect, runner): + prepare_state_with_full_attestations(spec, state) + + # Make fraction_incorrect of pending attestations have bad target/head as specified + num_incorrect = int(fraction_incorrect * len(state.previous_epoch_attestations)) + for pending_attestation in state.previous_epoch_attestations[:num_incorrect]: + if not correct_target: + pending_attestation.data.target.root = b'\x55' * 32 + if not correct_head: + pending_attestation.data.beacon_block_root = b'\x66' * 32 + + yield from runner(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_head_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_head_deltas.py new file mode 100644 index 000000000..24e3aaac5 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_head_deltas.py @@ -0,0 +1,109 @@ +from eth2spec.test.context import with_all_phases, spec_state_test +from eth2spec.test.helpers.rewards import run_attestation_component_deltas +import eth2spec.test.helpers.rewards as rewards_helpers + + +def run_get_head_deltas(spec, state): + """ + Run ``get_head_deltas``, yielding: + - pre-state ('pre') + - rewards ('rewards') + - penalties ('penalties') + """ + + yield from run_attestation_component_deltas( + spec, + state, + spec.get_head_deltas, + spec.get_matching_head_attestations, + ) + + +@with_all_phases +@spec_state_test +def test_empty(spec, state): + yield from rewards_helpers.test_empty(spec, state, run_get_head_deltas) + + +@with_all_phases +@spec_state_test +def test_full_all_correct(spec, state): + yield from rewards_helpers.test_full_all_correct(spec, state, run_get_head_deltas) + + +@with_all_phases +@spec_state_test +def test_half_full(spec, state): + yield from rewards_helpers.test_half_full(spec, state, run_get_head_deltas) + + +@with_all_phases +@spec_state_test +def test_one_attestation_one_correct(spec, state): + yield from rewards_helpers.test_one_attestation_one_correct(spec, state, run_get_head_deltas) + + +@with_all_phases +@spec_state_test +def test_with_slashed_validators(spec, state): + yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_head_deltas) + + +@with_all_phases +@spec_state_test +def test_some_zero_effective_balances_that_attested(spec, state): + yield from rewards_helpers.test_some_zero_effective_balances_that_attested(spec, state, run_get_head_deltas) + + +@with_all_phases +@spec_state_test +def test_some_zero_effective_balances_that_did_not_attest(spec, state): + yield from rewards_helpers.test_some_zero_effective_balances_that_did_not_attest(spec, state, run_get_head_deltas) + + +@with_all_phases +@spec_state_test +def test_full_half_correct_target_incorrect_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=True, + correct_head=False, + fraction_incorrect=0.5, + runner=run_get_head_deltas + ) + + +@with_all_phases +@spec_state_test +def test_full_correct_target_incorrect_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=True, + correct_head=False, + fraction_incorrect=1.0, + runner=run_get_head_deltas + ) + + +@with_all_phases +@spec_state_test +def test_full_half_incorrect_target_incorrect_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=False, + correct_head=False, + fraction_incorrect=0.5, + runner=run_get_head_deltas + ) + + +@with_all_phases +@spec_state_test +def test_full_half_incorrect_target_correct_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=False, + correct_head=True, + fraction_incorrect=0.5, + runner=run_get_head_deltas + ) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_source_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_source_deltas.py new file mode 100644 index 000000000..da47bd204 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_source_deltas.py @@ -0,0 +1,116 @@ +from eth2spec.test.context import with_all_phases, spec_state_test +from eth2spec.test.helpers.rewards import run_attestation_component_deltas +import eth2spec.test.helpers.rewards as rewards_helpers + + +def run_get_source_deltas(spec, state): + """ + Run ``get_source_deltas``, yielding: + - pre-state ('pre') + - rewards ('rewards') + - penalties ('penalties') + """ + + yield from run_attestation_component_deltas( + spec, + state, + spec.get_source_deltas, + spec.get_matching_source_attestations, + ) + + +@with_all_phases +@spec_state_test +def test_empty(spec, state): + yield from rewards_helpers.test_empty(spec, state, run_get_source_deltas) + + +@with_all_phases +@spec_state_test +def test_full_all_correct(spec, state): + yield from rewards_helpers.test_full_all_correct(spec, state, run_get_source_deltas) + + +@with_all_phases +@spec_state_test +def test_half_full(spec, state): + yield from rewards_helpers.test_half_full(spec, state, run_get_source_deltas) + + +@with_all_phases +@spec_state_test +def test_one_attestation_one_correct(spec, state): + yield from rewards_helpers.test_one_attestation_one_correct(spec, state, run_get_source_deltas) + + +@with_all_phases +@spec_state_test +def test_with_slashed_validators(spec, state): + yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_source_deltas) + + +@with_all_phases +@spec_state_test +def test_some_zero_effective_balances_that_attested(spec, state): + yield from rewards_helpers.test_some_zero_effective_balances_that_attested(spec, state, run_get_source_deltas) + + +@with_all_phases +@spec_state_test +def test_some_zero_effective_balances_that_did_not_attest(spec, state): + yield from rewards_helpers.test_some_zero_effective_balances_that_did_not_attest(spec, state, run_get_source_deltas) + + +# +# NOTE: No source incorrect tests +# All PendingAttestations in state have source validated +# We choose to keep this invariant in these tests to not force clients to test with degenerate states +# + + +@with_all_phases +@spec_state_test +def test_full_half_correct_target_incorrect_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=True, + correct_head=False, + fraction_incorrect=0.5, + runner=run_get_source_deltas + ) + + +@with_all_phases +@spec_state_test +def test_full_correct_target_incorrect_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=True, + correct_head=False, + fraction_incorrect=1.0, + runner=run_get_source_deltas + ) + + +@with_all_phases +@spec_state_test +def test_full_half_incorrect_target_incorrect_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=False, + correct_head=False, + fraction_incorrect=0.5, + runner=run_get_source_deltas + ) + + +@with_all_phases +@spec_state_test +def test_full_half_incorrect_target_correct_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=False, + correct_head=True, + fraction_incorrect=0.5, + runner=run_get_source_deltas + ) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_target_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_target_deltas.py index c30865cf0..406471c67 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_target_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_target_deltas.py @@ -1,92 +1,109 @@ from eth2spec.test.context import with_all_phases, spec_state_test -from eth2spec.test.helpers.attestations import prepare_state_with_full_attestations +from eth2spec.test.helpers.rewards import run_attestation_component_deltas +import eth2spec.test.helpers.rewards as rewards_helpers def run_get_target_deltas(spec, state): """ - Run ``process_block_header``, yielding: + Run ``get_target_deltas``, yielding: - pre-state ('pre') - rewards ('rewards') - penalties ('penalties') """ - yield 'pre', state - rewards, penalties = spec.get_target_deltas(state) - - yield 'rewards', rewards - yield 'penalties', penalties - - matching_target_attestations = spec.get_matching_target_attestations(state, spec.get_previous_epoch(state)) - matching_target_indices = spec.get_unslashed_attesting_indices(state, matching_target_attestations) - for index in spec.get_eligible_validator_indices(state): - if index in matching_target_indices and not state.validators[index].slashed: - assert rewards[index] > 0 - assert penalties[index] == 0 - else: - assert rewards[index] == 0 - assert penalties[index] > 0 + yield from run_attestation_component_deltas( + spec, + state, + spec.get_target_deltas, + spec.get_matching_target_attestations, + ) @with_all_phases @spec_state_test def test_empty(spec, state): - # Do not add any attestations to state - - yield from run_get_target_deltas(spec, state) + yield from rewards_helpers.test_empty(spec, state, run_get_target_deltas) @with_all_phases @spec_state_test def test_full_all_correct(spec, state): - prepare_state_with_full_attestations(spec, state) - - yield from run_get_target_deltas(spec, state) - - -@with_all_phases -@spec_state_test -def test_full_half_correct(spec, state): - prepare_state_with_full_attestations(spec, state) - - # Make half of pending attestations have bad target - for pending_attestation in state.previous_epoch_attestations[:len(state.previous_epoch_attestations) // 2]: - pending_attestation.data.target.root = b'\x66'*32 - - yield from run_get_target_deltas(spec, state) + yield from rewards_helpers.test_full_all_correct(spec, state, run_get_target_deltas) @with_all_phases @spec_state_test def test_half_full(spec, state): - prepare_state_with_full_attestations(spec, state) - - # Remove half of attestations - state.previous_epoch_attestations = state.previous_epoch_attestations[:len(state.previous_epoch_attestations) // 2] - - yield from run_get_target_deltas(spec, state) + yield from rewards_helpers.test_half_full(spec, state, run_get_target_deltas) @with_all_phases @spec_state_test -def test_one_correct(spec, state): - prepare_state_with_full_attestations(spec, state) - - # Remove half of attestations - state.previous_epoch_attestations = state.previous_epoch_attestations[:1] - - yield from run_get_target_deltas(spec, state) +def test_one_attestation_one_correct(spec, state): + yield from rewards_helpers.test_one_attestation_one_correct(spec, state, run_get_target_deltas) @with_all_phases @spec_state_test def test_with_slashed_validators(spec, state): - prepare_state_with_full_attestations(spec, state) + yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_target_deltas) - # Slash half of validators - for validator in state.validators: - validator.slashed = True - yield from run_get_target_deltas(spec, state) +@with_all_phases +@spec_state_test +def test_some_zero_effective_balances_that_attested(spec, state): + yield from rewards_helpers.test_some_zero_effective_balances_that_attested(spec, state, run_get_target_deltas) -def test_some_zero_balances(spec, state): +@with_all_phases +@spec_state_test +def test_some_zero_effective_balances_that_did_not_attest(spec, state): + yield from rewards_helpers.test_some_zero_effective_balances_that_did_not_attest(spec, state, run_get_target_deltas) + + +@with_all_phases +@spec_state_test +def test_full_half_correct_target_incorrect_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=True, + correct_head=False, + fraction_incorrect=0.5, + runner=run_get_target_deltas + ) + + +@with_all_phases +@spec_state_test +def test_full_correct_target_incorrect_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=True, + correct_head=False, + fraction_incorrect=1.0, + runner=run_get_target_deltas + ) + + +@with_all_phases +@spec_state_test +def test_full_half_incorrect_target_incorrect_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=False, + correct_head=False, + fraction_incorrect=0.5, + runner=run_get_target_deltas + ) + + +@with_all_phases +@spec_state_test +def test_full_half_incorrect_target_correct_head(spec, state): + yield from rewards_helpers.test_full_fraction_incorrect( + spec, state, + correct_target=False, + correct_head=True, + fraction_incorrect=0.5, + runner=run_get_target_deltas + ) From 6a40f71a31724aab57ca90ff0a00fc5a536f62d6 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 29 Apr 2020 20:29:48 -0600 Subject: [PATCH 10/53] add note about beacon committees not going into attnets --- specs/phase0/validator.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index bc7510403..69be71279 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -524,6 +524,8 @@ Because Phase 0 does not have shards and thus does not have Shard Committees, th * Maintain advertisement of the randomly selected subnets in their node's ENR `attnets` entry by setting the randomly selected `subnet_id` bits to `True` (e.g. `ENR["attnets"][subnet_id] = True`) for all persistent attestation subnets * Set the lifetime of each random subscription to a random number of epochs between `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` and `2 * EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION]`. At the end of life for a subscription, select a new random subnet, update subnet subscriptions, and publish an updated ENR +*Note*: Short lived beacon committee assignments should not be added in into the ENR `attnets` entry. + *Note*: When preparing for a hard fork, a validator must select and subscribe to random subnets of the future fork versioning at least `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` epochs in advance of the fork. These new subnets for the fork are maintained in addition to those for the current fork until the fork occurs. After the fork occurs, let the subnets from the previous fork reach the end of life with no replacements. ## How to avoid slashing From 2dc515665181c5cb30e60148fb3be57073cb3a77 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 30 Apr 2020 16:27:02 +1000 Subject: [PATCH 11/53] Add message about delaying consideration --- specs/phase0/fork-choice.md | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 35e2c5f56..60d398dd5 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -273,6 +273,7 @@ def validate_on_attestation(store: Store, attestation: Attestation) -> None: current_epoch = compute_epoch_at_slot(get_current_slot(store)) # Use GENESIS_EPOCH for previous when genesis to avoid underflow previous_epoch = current_epoch - 1 if current_epoch > GENESIS_EPOCH else GENESIS_EPOCH + # If attestation target is from a future epoch, delay consideration until the epoch arrives assert target.epoch in [current_epoch, previous_epoch] assert target.epoch == compute_epoch_at_slot(attestation.data.slot) From 45ad270f762c3949507d65bc08d92b353b9b6eb9 Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 1 May 2020 19:14:01 +0200 Subject: [PATCH 12/53] test double proposer slashings and exits --- .../test/helpers/proposer_slashings.py | 10 +- .../eth2spec/test/sanity/test_blocks.py | 132 +++++++++++++----- 2 files changed, 106 insertions(+), 36 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py b/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py index ac2ebcf9c..d753d55dd 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py +++ b/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py @@ -2,9 +2,11 @@ from eth2spec.test.helpers.block_header import sign_block_header from eth2spec.test.helpers.keys import pubkey_to_privkey -def get_valid_proposer_slashing(spec, state, signed_1=False, signed_2=False): - current_epoch = spec.get_current_epoch(state) - validator_index = spec.get_active_validator_indices(state, current_epoch)[-1] +def get_valid_proposer_slashing(spec, state, random_root=b'\x99' * 32, + validator_index=None, signed_1=False, signed_2=False): + if validator_index is None: + current_epoch = spec.get_current_epoch(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[-1] privkey = pubkey_to_privkey[state.validators[validator_index].pubkey] slot = state.slot @@ -16,7 +18,7 @@ def get_valid_proposer_slashing(spec, state, signed_1=False, signed_2=False): body_root=b'\x55' * 32, ) header_2 = header_1.copy() - header_2.parent_root = b'\x99' * 32 + header_2.parent_root = random_root if signed_1: signed_header_1 = sign_block_header(spec, state, header_1, privkey) diff --git a/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py index b6b671872..3347d4f8b 100644 --- a/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py @@ -1,5 +1,3 @@ -from copy import deepcopy - from eth2spec.utils import bls from eth2spec.test.helpers.state import get_balance, state_transition_and_sign_block, next_slot, next_epoch @@ -228,7 +226,7 @@ def test_empty_epoch_transition_not_finalizing(spec, state): @spec_state_test def test_proposer_slashing(spec, state): # copy for later balance lookups. - pre_state = deepcopy(state) + pre_state = state.copy() proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) validator_index = proposer_slashing.signed_header_1.message.proposer_index @@ -256,11 +254,65 @@ def test_proposer_slashing(spec, state): assert get_balance(state, validator_index) < get_balance(pre_state, validator_index) +@with_all_phases +@spec_state_test +def test_double_same_proposer_slashings_same_block(spec, state): + proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) + validator_index = proposer_slashing.signed_header_1.message.proposer_index + assert not state.validators[validator_index].slashed + + yield 'pre', state + + block = build_empty_block_for_next_slot(spec, state) + block.body.proposer_slashings = [proposer_slashing, proposer_slashing] + signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) + + yield 'blocks', [signed_block] + yield 'post', None + + +@with_all_phases +@spec_state_test +def test_double_similar_proposer_slashings_same_block(spec, state): + validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] + + # Same validator, but different slashable offences in the same block + proposer_slashing_1 = get_valid_proposer_slashing(spec, state, random_root=b'\xaa' * 32, + validator_index=validator_index, + signed_1=True, signed_2=True) + proposer_slashing_2 = get_valid_proposer_slashing(spec, state, random_root=b'\xbb' * 32, + validator_index=validator_index, + signed_1=True, signed_2=True) + assert not state.validators[validator_index].slashed + + yield 'pre', state + + block = build_empty_block_for_next_slot(spec, state) + block.body.proposer_slashings = [proposer_slashing_1, proposer_slashing_2] + signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) + + yield 'blocks', [signed_block] + yield 'post', None + + +def check_attester_slashing_effect(spec, pre_state, state, validator_index): + slashed_validator = state.validators[validator_index] + 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(state, validator_index) < get_balance(pre_state, validator_index) + + proposer_index = spec.get_beacon_proposer_index(state) + # gained whistleblower reward + assert get_balance(state, proposer_index) > get_balance(pre_state, proposer_index) + + @with_all_phases @spec_state_test def test_attester_slashing(spec, state): # copy for later balance lookups. - pre_state = deepcopy(state) + pre_state = state.copy() attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) validator_index = get_indexed_attestation_participants(spec, attester_slashing.attestation_1)[0] @@ -280,19 +332,11 @@ def test_attester_slashing(spec, state): yield 'blocks', [signed_block] yield 'post', state - slashed_validator = state.validators[validator_index] - 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(state, validator_index) < get_balance(pre_state, validator_index) + check_attester_slashing_effect(spec, pre_state, state, validator_index) - proposer_index = spec.get_beacon_proposer_index(state) - # gained whistleblower reward - assert ( - get_balance(state, proposer_index) > - get_balance(pre_state, proposer_index) - ) +# TODO: currently mainnet limits attester-slashings per block to 1. +# When this is increased, it should be tested to cover varrious combinations +# of duplicate slashings and overlaps of slashed attestations within the same block @with_all_phases @@ -443,35 +487,38 @@ def test_attestation(spec, state): assert spec.hash_tree_root(state.previous_epoch_attestations) == pre_current_attestations_root +def prepare_signed_exits(spec, state, indices): + domain = spec.get_domain(state, spec.DOMAIN_VOLUNTARY_EXIT) + + def create_signed_exit(index): + exit = spec.VoluntaryExit( + epoch=spec.get_current_epoch(state), + validator_index=index, + ) + signing_root = spec.compute_signing_root(exit, domain) + return spec.SignedVoluntaryExit(message=exit, signature=bls.Sign(privkeys[index], signing_root)) + + return [create_signed_exit(index) for index in indices] + + # In phase1 a committee is computed for PERSISTENT_COMMITTEE_PERIOD slots ago, # exceeding the minimal-config randao mixes memory size. +# Applies to all voluntary-exit sanity block tests. + @with_phases(['phase0']) @spec_state_test def test_voluntary_exit(spec, state): - validator_index = spec.get_active_validator_indices( - state, - spec.get_current_epoch(state) - )[-1] + validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + signed_exits = prepare_signed_exits(spec, state, [validator_index]) yield 'pre', state - voluntary_exit = spec.VoluntaryExit( - epoch=spec.get_current_epoch(state), - validator_index=validator_index, - ) - domain = spec.get_domain(state, spec.DOMAIN_VOLUNTARY_EXIT) - signing_root = spec.compute_signing_root(voluntary_exit, domain) - signed_voluntary_exit = spec.SignedVoluntaryExit( - message=voluntary_exit, - signature=bls.Sign(privkeys[validator_index], signing_root) - ) - # Add to state via block transition initiate_exit_block = build_empty_block_for_next_slot(spec, state) - initiate_exit_block.body.voluntary_exits.append(signed_voluntary_exit) + initiate_exit_block.body.voluntary_exits = signed_exits signed_initiate_exit_block = state_transition_and_sign_block(spec, state, initiate_exit_block) assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH @@ -486,6 +533,27 @@ def test_voluntary_exit(spec, state): assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH +@with_phases(['phase0']) +@spec_state_test +def test_double_validator_exit_same_block(spec, state): + validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] + + # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + # Same index tries to exit twice, but should only be able to do so once. + signed_exits = prepare_signed_exits(spec, state, [validator_index, validator_index]) + yield 'pre', state + + # Add to state via block transition + initiate_exit_block = build_empty_block_for_next_slot(spec, state) + initiate_exit_block.body.voluntary_exits = signed_exits + signed_initiate_exit_block = state_transition_and_sign_block(spec, state, initiate_exit_block, expect_fail=True) + + yield 'blocks', [signed_initiate_exit_block] + yield 'post', None + + @with_all_phases @spec_state_test def test_balance_driven_status_transitions(spec, state): From feb27a14be1ae2b64a96f0469ffaf0bd68223c67 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 31 Mar 2020 12:37:36 +0800 Subject: [PATCH 13/53] beacon-chain.md: Replace block wrapper with signable pattern --- specs/phase1/beacon-chain.md | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 596b3818f..d90afda16 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -24,8 +24,9 @@ - [Extended `SignedBeaconBlock`](#extended-signedbeaconblock) - [Extended `BeaconState`](#extended-beaconstate) - [New containers](#new-containers) - - [`ShardBlockWrapper`](#shardblockwrapper) - - [`ShardSignableHeader`](#shardsignableheader) + - [`ShardBlock`](#shardblock) + - [`SignedShardBlock`](#signedshardblock) + - [`ShardBlockHeader`](#shardblockheader) - [`ShardState`](#shardstate) - [`ShardTransition`](#shardtransition) - [`CompactCommittee`](#compactcommittee) @@ -291,23 +292,28 @@ class BeaconState(Container): The following containers are new in Phase 1. -### `ShardBlockWrapper` - -_Wrapper for being broadcasted over the network._ +### `ShardBlock` ```python -class ShardBlockWrapper(Container): +class ShardBlock(Container): shard_parent_root: Root beacon_parent_root: Root slot: Slot body: ByteList[MAX_SHARD_BLOCK_SIZE] +``` + +### `SignedShardBlock` + +```python +class SignedShardBlock(Container): + message: ShardBlock signature: BLSSignature ``` -### `ShardSignableHeader` +### `ShardBlockHeader` ```python -class ShardSignableHeader(Container): +class ShardBlockHeader(Container): shard_parent_root: Root beacon_parent_root: Root slot: Slot @@ -700,7 +706,7 @@ def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTr shard_parent_root = state.shard_states[shard].latest_block_root for i in range(len(offset_slots)): if any(transition.shard_data_roots): - headers.append(ShardSignableHeader( + headers.append(ShardBlockHeader( shard_parent_root=shard_parent_root, parent_hash=get_block_root_at_slot(state, get_previous_slot(state.slot)), slot=offset_slots[i], From e9f1e4186d0e6cf70f32e68cd97f33a61eec5a2e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 2 Apr 2020 10:43:50 +0800 Subject: [PATCH 14/53] Add `proposer_index` to shard block --- specs/phase1/beacon-chain.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index d90afda16..0652fecad 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -299,6 +299,7 @@ class ShardBlock(Container): shard_parent_root: Root beacon_parent_root: Root slot: Slot + proposer_index: ValidatorIndex body: ByteList[MAX_SHARD_BLOCK_SIZE] ``` @@ -317,6 +318,7 @@ class ShardBlockHeader(Container): shard_parent_root: Root beacon_parent_root: Root slot: Slot + proposer_index: ValidatorIndex body_root: Root ``` From 5f69afea382740c696052a11e939329f46810f82 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 2 Apr 2020 10:53:46 +0800 Subject: [PATCH 15/53] Make `shard_state_transition` more like beacon state_transition function --- specs/phase1/fraud-proofs.md | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/specs/phase1/fraud-proofs.md b/specs/phase1/fraud-proofs.md index 0688f5f47..19077ba88 100644 --- a/specs/phase1/fraud-proofs.md +++ b/specs/phase1/fraud-proofs.md @@ -45,14 +45,30 @@ The proof verifies that one of the two conditions is false: ## Shard state transition function ```python -def shard_state_transition(shard: Shard, +def shard_state_transition(beacon_state: BeaconState, + shard: Shard, slot: Slot, pre_state: Root, previous_beacon_root: Root, - proposer_pubkey: BLSPubkey, - block_data: ByteList[MAX_SHARD_BLOCK_SIZE]) -> Root: + proposer_index: ValidatorIndex, + signed_block: SignedShardBlock, + validate_result: bool=True) -> Root: # We will add something more substantive in phase 2 - return hash(pre_state + hash_tree_root(previous_beacon_root) + hash_tree_root(block_data)) + + # Verify the proposer_index and signature + assert proposer_index == signed_block.message.proposer_index + if validate_result: + assert verify_shard_block_signature(beacon_state, signed_block) + + return hash(pre_state + hash_tree_root(previous_beacon_root) + hash_tree_root(signed_block.message.data)) +``` + +```python +def verify_shard_block_signature(beacon_state: BeaconState, + signed_block: SignedShardBlock) -> bool: + proposer = beacon_state.validators[signed_block.message.proposer_index] + signing_root = compute_signing_root(signed_block.message, get_domain(beacon_state, DOMAIN_SHARD_PROPOSAL)) + return bls.Verify(proposer.pubkey, signing_root, signed_block.signature) ``` ## Honest committee member behavior @@ -61,10 +77,10 @@ Suppose you are a committee member on shard `shard` at slot `current_slot`. Let * Initialize `proposals = []`, `shard_states = []`, `shard_state = state.shard_states[shard][-1]`, `start_slot = shard_state.slot`. * For `slot in get_offset_slots(state, start_slot)`, do the following: - * Look for all valid proposals for `slot`; that is, a Bytes `proposal` where `shard_state_transition(shard, slot, shard_state, get_block_root_at_slot(state, state.slot - 1), get_shard_proposer_index(state, shard, slot), proposal)` returns a result and does not throw an exception. Let `choices` be the set of non-empty valid proposals you discover. + * Look for all valid proposals for `slot`; that is, a SignedShardBlock `proposal` where `shard_state_transition(shard, slot, shard_state, get_block_root_at_slot(state, state.slot - 1), get_shard_proposer_index(state, slot, shard), proposal)` returns a result and does not throw an exception. Let `choices` be the set of non-empty valid proposals you discover. * If `len(choices) == 0`, do `proposals.append(make_empty_proposal(shard_state, slot))` * If `len(choices) == 1`, do `proposals.append(choices[0])` * If `len(choices) > 1`, let `winning_proposal` be the proposal with the largest number of total attestations from slots in `state.shard_next_slots[shard]....slot-1` supporting it or any of its descendants, breaking ties by choosing the first proposal locally seen. Do `proposals.append(winning_proposal)`. - * If `proposals[-1]` is NOT an empty proposal, set `shard_state = shard_state_transition(shard, slot, shard_state, get_block_root_at_slot(state, state.slot - 1), get_shard_proposer_index(state, shard, slot), proposals[-1])` and do `shard_states.append(shard_state)`. If it is an empty proposal, leave `shard_state` unchanged. + * If `proposals.message.data[-1]` is NOT an empty proposal, set `shard_state = shard_state_transition(shard, slot, shard_state, get_block_root_at_slot(state, state.slot - 1), get_shard_proposer_index(state, slot, shard), proposals[-1])` and do `shard_states.append(shard_state)`. If it is an empty proposal, leave `shard_state` unchanged. -Make an attestation using `shard_data_roots = [hash_tree_root(proposal) for proposal in proposals]` and `shard_state_roots = shard_states`. +Make an attestation using `shard_data_roots = [hash_tree_root(proposal.message.data) for proposal in proposals]` and `shard_state_roots = shard_states`. From be50020bf8134b82c5d7e46add6713f193ccddde Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 2 Apr 2020 11:20:22 +0800 Subject: [PATCH 16/53] Refactor `get_light_client_committee` to similar to `get_shard_committee` --- specs/phase1/beacon-chain.md | 40 ++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 0652fecad..fbfdce70e 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -43,8 +43,8 @@ - [`get_active_shard_count`](#get_active_shard_count) - [`get_online_validator_indices`](#get_online_validator_indices) - [`get_shard_committee`](#get_shard_committee) - - [`get_shard_proposer_index`](#get_shard_proposer_index) - [`get_light_client_committee`](#get_light_client_committee) + - [`get_shard_proposer_index`](#get_shard_proposer_index) - [`get_indexed_attestation`](#get_indexed_attestation) - [`get_updated_gasprice`](#get_updated_gasprice) - [`get_start_shard`](#get_start_shard) @@ -465,7 +465,30 @@ def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) - source_epoch -= SHARD_COMMITTEE_PERIOD active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) seed = get_seed(beacon_state, source_epoch, DOMAIN_SHARD_COMMITTEE) - return compute_committee(active_validator_indices, seed, shard, get_active_shard_count(beacon_state)) + return compute_committee( + indices=active_validator_indices, + seed=seed, + index=shard, + count=get_active_shard_count(beacon_state) + ) +``` + +#### `get_light_client_committee` + +```python +def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: + source_epoch = epoch - epoch % LIGHT_CLIENT_COMMITTEE_PERIOD + if source_epoch > 0: + source_epoch -= LIGHT_CLIENT_COMMITTEE_PERIOD + active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) + seed = get_seed(beacon_state, source_epoch, DOMAIN_LIGHT_CLIENT) + active_shards_count = get_active_shard_count(beacon_state) + return compute_committee( + indices=active_validator_indices, + seed=seed, + index=0, + count=active_shards_count, + )[:TARGET_COMMITTEE_SIZE] ``` #### `get_shard_proposer_index` @@ -477,19 +500,6 @@ def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard return committee[r % len(committee)] ``` -#### `get_light_client_committee` - -```python -def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: - source_epoch = epoch - epoch % LIGHT_CLIENT_COMMITTEE_PERIOD - if source_epoch > 0: - source_epoch -= LIGHT_CLIENT_COMMITTEE_PERIOD - active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) - seed = get_seed(beacon_state, source_epoch, DOMAIN_LIGHT_CLIENT) - active_shards = get_active_shard_count(beacon_state) - return compute_committee(active_validator_indices, seed, 0, active_shards)[:TARGET_COMMITTEE_SIZE] -``` - #### `get_indexed_attestation` ```python From 247a6c8fca24e98489295d56db58c32180158c88 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 2 Apr 2020 11:22:21 +0800 Subject: [PATCH 17/53] Add `verify_fraud_proof` function --- specs/phase1/fraud-proofs.md | 60 ++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/specs/phase1/fraud-proofs.md b/specs/phase1/fraud-proofs.md index 19077ba88..e72a8bb2a 100644 --- a/specs/phase1/fraud-proofs.md +++ b/specs/phase1/fraud-proofs.md @@ -6,7 +6,8 @@ - [Table of contents](#table-of-contents) - [Introduction](#introduction) - [Fraud proofs](#fraud-proofs) - - [Shard state transition function](#shard-state-transition-function) + - [Shard state transition function](#shard-state-transition-function) + - [Verifying the proof](#verifying-the-proof) - [Honest committee member behavior](#honest-committee-member-behavior) @@ -32,17 +33,12 @@ This document describes the shard transition function and fraud proofs as part o TODO. The intent is to have a single universal fraud proof type, which contains the following parts: 1. An on-time attestation on some `shard` signing a `ShardTransition` -2. An index `i` of a particular position to focus on +2. An index `index` of a particular position to focus on 3. The `ShardTransition` itself -4. The full body of the block -5. A Merkle proof to the `shard_states` in the parent block the attestation is referencing +4. The full body of the block `ShardBlock` +5. A Merkle proof to the `shard_states` in the parent block `parent_block` the attestation is referencing -The proof verifies that one of the two conditions is false: - -1. `custody_bits[i][j] != generate_custody_bit(subkey, block_contents)` for any `j` -2. `execute_state_transition(shard, slot, transition.shard_states[i-1].data, hash_tree_root(parent), get_shard_proposer_index(state, shard, slot), block_contents) != transition.shard_states[i].data` (if `i=0` then instead use `parent.shard_states[shard][-1].data`) - -## Shard state transition function +### Shard state transition function ```python def shard_state_transition(beacon_state: BeaconState, @@ -71,6 +67,50 @@ def verify_shard_block_signature(beacon_state: BeaconState, return bls.Verify(proposer.pubkey, signing_root, signed_block.signature) ``` +### Verifying the proof + +```python +def verify_fraud_proof(beacon_state: BeaconState, + subkey: BLSPubkey, + attestation: Attestation, + index: uint64, + transition: ShardTransition, + signed_block: SignedShardBlock, + parent_block: ShardBlock) -> bool: + # 1. Check if `custody_bits[index][j] != generate_custody_bit(subkey, block_contents)` for any `j` + shard = get_shard(beacon_state, attestation) + slot = attestation.data.slot + custody_bits = attestation.custody_bits_blocks + for j in range(custody_bits[index]): + if custody_bits[index][j] != generate_custody_bit(subkey, signed_block): + return True + + # 2. Verify the shard state transition + if index == 0: + parent_data = parent_block.shard_states[shard][-1].data + else: + parent_data = parent_block.shard_states[shard][index].data + + if shard_state_transition( + beacon_state, + shard, + slot, + transition.shard_states[index - 1].data, + hash_tree_root(parent_block), + get_shard_proposer_index(beacon_state, slot, shard), + signed_block, + ) != parent_data: + return True + + return False +``` + +```python +def generate_custody_bit(subkey: BLSPubkey, block: ShardBlock) -> bool: + # TODO + ... +``` + ## Honest committee member behavior Suppose you are a committee member on shard `shard` at slot `current_slot`. Let `state` be the head beacon state you are building on, and let `QUARTER_PERIOD = SECONDS_PER_SLOT // 4`. `2 * QUARTER_PERIOD` seconds into slot `slot`, run the following procedure: From 849d3f83bf4fd95ef47fd1fcee45494dc4a49e83 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 6 Apr 2020 17:48:24 +0800 Subject: [PATCH 18/53] Apply @terencechain 's review feedback Co-Authored-By: terence tsao --- specs/phase1/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index fbfdce70e..7c3ae786b 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -720,7 +720,7 @@ def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTr if any(transition.shard_data_roots): headers.append(ShardBlockHeader( shard_parent_root=shard_parent_root, - parent_hash=get_block_root_at_slot(state, get_previous_slot(state.slot)), + beacon_parent_root=get_block_root_at_slot(state, get_previous_slot(state.slot)), slot=offset_slots[i], body_root=transition.shard_data_roots[i] )) From 4e8a7ff1156dc3a12ccb718476782bc685de3a4f Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 6 Apr 2020 19:30:32 +0800 Subject: [PATCH 19/53] [squashed] shard transition wip Fix the wrong `get_shard_proposer_index` parameters order Phase 1 WIP Add shard transition basic test Fix lint error Fix --- specs/phase1/beacon-chain.md | 63 ++--- specs/phase1/fraud-proofs.md | 229 ++++++++++++++---- .../eth2spec/test/helpers/attestations.py | 40 ++- .../eth2spec/test/helpers/crosslinks.py | 28 +++ .../eth2spec/test/helpers/phase1/__init__.py | 0 .../test/helpers/phase1/attestations.py | 63 ----- .../test/helpers/phase1/shard_block.py | 71 ------ .../test/helpers/phase1/shard_state.py | 18 -- .../eth2spec/test/helpers/shard_block.py | 47 ++++ .../test_process_crosslink.py | 52 ++++ 10 files changed, 374 insertions(+), 237 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/helpers/crosslinks.py delete mode 100644 tests/core/pyspec/eth2spec/test/helpers/phase1/__init__.py delete mode 100644 tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py delete mode 100644 tests/core/pyspec/eth2spec/test/helpers/phase1/shard_block.py delete mode 100644 tests/core/pyspec/eth2spec/test/helpers/phase1/shard_state.py create mode 100644 tests/core/pyspec/eth2spec/test/helpers/shard_block.py create mode 100644 tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 7c3ae786b..4b22f8633 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -460,16 +460,17 @@ def get_online_validator_indices(state: BeaconState) -> Set[ValidatorIndex]: ```python def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) -> Sequence[ValidatorIndex]: - source_epoch = epoch - epoch % SHARD_COMMITTEE_PERIOD + source_epoch = epoch - epoch % SHARD_COMMITTEE_PERIOD if source_epoch > 0: source_epoch -= SHARD_COMMITTEE_PERIOD active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) seed = get_seed(beacon_state, source_epoch, DOMAIN_SHARD_COMMITTEE) + active_shards_count = get_active_shard_count(beacon_state) return compute_committee( indices=active_validator_indices, seed=seed, index=shard, - count=get_active_shard_count(beacon_state) + count=active_shards_count, ) ``` @@ -712,29 +713,35 @@ def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTr ) assert transition.start_slot == offset_slots[0] - # Reconstruct shard headers headers = [] + header = ShardBlockHeader() proposers = [] - shard_parent_root = state.shard_states[shard].latest_block_root - for i in range(len(offset_slots)): - if any(transition.shard_data_roots): - headers.append(ShardBlockHeader( - shard_parent_root=shard_parent_root, - beacon_parent_root=get_block_root_at_slot(state, get_previous_slot(state.slot)), - slot=offset_slots[i], - body_root=transition.shard_data_roots[i] - )) - proposers.append(get_shard_proposer_index(state, shard, offset_slots[i])) - shard_parent_root = hash_tree_root(headers[-1]) - - # Verify correct calculation of gas prices and slots prev_gasprice = state.shard_states[shard].gasprice + shard_parent_root = state.shard_states[shard].latest_block_root + beacon_parent_root = get_block_root_at_slot(state, get_previous_slot(state.slot)) for i in range(len(offset_slots)): + shard_block_length = transition.shard_block_lengths[i] + is_empty_proposal = (shard_block_length == 0) shard_state = transition.shard_states[i] - block_length = transition.shard_block_lengths[i] - assert shard_state.gasprice == get_updated_gasprice(prev_gasprice, block_length) - assert shard_state.slot == offset_slots[i] - prev_gasprice = shard_state.gasprice + proposal_index = get_shard_proposer_index(state, offset_slots[i], shard) + # Reconstruct shard headers + header = ShardBlockHeader( + shard_parent_root=shard_parent_root, + beacon_parent_root=beacon_parent_root, + proposer_index=proposal_index, + slot=offset_slots[i], + body_root=transition.shard_data_roots[i] + ) + shard_parent_root = hash_tree_root(header) + + if not is_empty_proposal: + # Only add non-empty signature + headers.append(header) + proposers.append(proposal_index) + # Verify correct calculation of gas prices and slots + assert shard_state.gasprice == get_updated_gasprice(prev_gasprice, shard_block_length) + assert shard_state.slot == offset_slots[i] + prev_gasprice = shard_state.gasprice pubkeys = [state.validators[proposer].pubkey for proposer in proposers] signing_roots = [ @@ -745,7 +752,8 @@ def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTr assert bls.AggregateVerify(zip(pubkeys, signing_roots), signature=transition.proposer_signature_aggregate) # Save updated state - state.shard_states[shard] = transition.shard_states[-1] + state.shard_states[shard] = transition.shard_states[len(transition.shard_states) - 1] + assert state.slot > 0 state.shard_states[shard].slot = state.slot - 1 ``` @@ -779,7 +787,9 @@ def process_crosslink_for_shard(state: BeaconState, # Attestation <-> shard transition consistency assert shard_transition_root == hash_tree_root(shard_transition) - assert attestation.data.head_shard_root == shard_transition.shard_data_roots[-1] + assert attestation.data.head_shard_root == shard_transition.shard_data_roots[ + len(shard_transition.shard_data_roots) - 1 + ] # Apply transition apply_shard_transition(state, shard, shard_transition) @@ -790,11 +800,11 @@ def process_crosslink_for_shard(state: BeaconState, increase_balance(state, beacon_proposer_index, proposer_reward) states_slots_lengths = zip( shard_transition.shard_states, - get_offset_slots(state, get_latest_slot_for_shard(state, shard)), + get_offset_slots(state, shard), shard_transition.shard_block_lengths ) for shard_state, slot, length in states_slots_lengths: - proposer_index = get_shard_proposer_index(state, shard, slot) + proposer_index = get_shard_proposer_index(state, slot, shard) decrease_balance(state, proposer_index, shard_state.gasprice * length) # Return winning transition root @@ -913,14 +923,13 @@ def process_light_client_signatures(state: BeaconState, block_body: BeaconBlockB total_reward += get_base_reward(state, participant_index) increase_balance(state, get_beacon_proposer_index(state), Gwei(total_reward // PROPOSER_REWARD_QUOTIENT)) - + slot = get_previous_slot(state.slot) - signing_root = compute_signing_root(get_block_root_at_slot(state, slot), + signing_root = compute_signing_root(get_block_root_at_slot(state, slot), get_domain(state, DOMAIN_LIGHT_CLIENT, compute_epoch_at_slot(slot))) assert bls.FastAggregateVerify(signer_pubkeys, signing_root, signature=block_body.light_client_signature) ``` - ### Epoch transition This epoch transition overrides the phase0 epoch transition: diff --git a/specs/phase1/fraud-proofs.md b/specs/phase1/fraud-proofs.md index e72a8bb2a..30b94d810 100644 --- a/specs/phase1/fraud-proofs.md +++ b/specs/phase1/fraud-proofs.md @@ -9,6 +9,8 @@ - [Shard state transition function](#shard-state-transition-function) - [Verifying the proof](#verifying-the-proof) - [Honest committee member behavior](#honest-committee-member-behavior) + - [Helper functions](#helper-functions) + - [Make attestations](#make-attestations) @@ -30,76 +32,76 @@ This document describes the shard transition function and fraud proofs as part o ## Fraud proofs -TODO. The intent is to have a single universal fraud proof type, which contains the following parts: - -1. An on-time attestation on some `shard` signing a `ShardTransition` -2. An index `index` of a particular position to focus on -3. The `ShardTransition` itself -4. The full body of the block `ShardBlock` -5. A Merkle proof to the `shard_states` in the parent block `parent_block` the attestation is referencing - ### Shard state transition function ```python def shard_state_transition(beacon_state: BeaconState, shard: Shard, slot: Slot, - pre_state: Root, - previous_beacon_root: Root, - proposer_index: ValidatorIndex, - signed_block: SignedShardBlock, - validate_result: bool=True) -> Root: - # We will add something more substantive in phase 2 - - # Verify the proposer_index and signature - assert proposer_index == signed_block.message.proposer_index - if validate_result: - assert verify_shard_block_signature(beacon_state, signed_block) - - return hash(pre_state + hash_tree_root(previous_beacon_root) + hash_tree_root(signed_block.message.data)) + shard_state: ShardState, + beacon_parent_root: Root, + signed_block: SignedShardBlock) -> None: + # Update shard state + shard_state.data = hash( + hash_tree_root(shard_state) + hash_tree_root(beacon_parent_root) + hash_tree_root(signed_block.message.body) + ) + shard_state.slot = slot + shard_state.latest_block_root = hash_tree_root(signed_block.message) ``` ```python def verify_shard_block_signature(beacon_state: BeaconState, signed_block: SignedShardBlock) -> bool: proposer = beacon_state.validators[signed_block.message.proposer_index] - signing_root = compute_signing_root(signed_block.message, get_domain(beacon_state, DOMAIN_SHARD_PROPOSAL)) + domain = get_domain(beacon_state, DOMAIN_SHARD_PROPOSAL, compute_epoch_at_slot(signed_block.message.slot)) + signing_root = compute_signing_root(signed_block.message, domain) return bls.Verify(proposer.pubkey, signing_root, signed_block.signature) ``` ### Verifying the proof +TODO. The intent is to have a single universal fraud proof type, which contains the following parts: + +1. An on-time attestation `attestation` on some shard `shard` signing a `transition: ShardTransition` +2. An index `offset_index` of a particular position to focus on +3. The `transition: ShardTransition` itself +4. The full body of the shard block `shard_block` +5. A Merkle proof to the `shard_states` in the parent block the attestation is referencing + +Call the following function to verify the proof: + ```python def verify_fraud_proof(beacon_state: BeaconState, - subkey: BLSPubkey, attestation: Attestation, - index: uint64, + offset_index: uint64, transition: ShardTransition, signed_block: SignedShardBlock, - parent_block: ShardBlock) -> bool: - # 1. Check if `custody_bits[index][j] != generate_custody_bit(subkey, block_contents)` for any `j` + subkey: BLSPubkey, + beacon_parent_block: BeaconBlock) -> bool: + # 1. Check if `custody_bits[offset_index][j] != generate_custody_bit(subkey, block_contents)` for any `j`. shard = get_shard(beacon_state, attestation) slot = attestation.data.slot custody_bits = attestation.custody_bits_blocks - for j in range(custody_bits[index]): - if custody_bits[index][j] != generate_custody_bit(subkey, signed_block): + for j in range(custody_bits[offset_index]): + if custody_bits[offset_index][j] != generate_custody_bit(subkey, signed_block): return True - # 2. Verify the shard state transition - if index == 0: - parent_data = parent_block.shard_states[shard][-1].data + # 2. Check if the shard state transition result is wrong between + # `transition.shard_states[offset_index - 1]` to `transition.shard_states[offset_index]`. + if offset_index == 0: + shard_state = beacon_parent_block.shard_transitions[shard][-1] else: - parent_data = parent_block.shard_states[shard][index].data + shard_state = transition.shard_states[offset_index - 1].copy() # Not doing the actual state updates here. - if shard_state_transition( - beacon_state, - shard, - slot, - transition.shard_states[index - 1].data, - hash_tree_root(parent_block), - get_shard_proposer_index(beacon_state, slot, shard), - signed_block, - ) != parent_data: + shard_state_transition( + beacon_state=beacon_state, + shard=shard, + slot=slot, + shard_state=shard_state, + beacon_parent_root=hash_tree_root(beacon_parent_block), + signed_block=signed_block, + ) + if shard_state.latest_block_root != transition.shard_states[offset_index].data: return True return False @@ -113,14 +115,141 @@ def generate_custody_bit(subkey: BLSPubkey, block: ShardBlock) -> bool: ## Honest committee member behavior -Suppose you are a committee member on shard `shard` at slot `current_slot`. Let `state` be the head beacon state you are building on, and let `QUARTER_PERIOD = SECONDS_PER_SLOT // 4`. `2 * QUARTER_PERIOD` seconds into slot `slot`, run the following procedure: +### Helper functions -* Initialize `proposals = []`, `shard_states = []`, `shard_state = state.shard_states[shard][-1]`, `start_slot = shard_state.slot`. -* For `slot in get_offset_slots(state, start_slot)`, do the following: - * Look for all valid proposals for `slot`; that is, a SignedShardBlock `proposal` where `shard_state_transition(shard, slot, shard_state, get_block_root_at_slot(state, state.slot - 1), get_shard_proposer_index(state, slot, shard), proposal)` returns a result and does not throw an exception. Let `choices` be the set of non-empty valid proposals you discover. - * If `len(choices) == 0`, do `proposals.append(make_empty_proposal(shard_state, slot))` - * If `len(choices) == 1`, do `proposals.append(choices[0])` - * If `len(choices) > 1`, let `winning_proposal` be the proposal with the largest number of total attestations from slots in `state.shard_next_slots[shard]....slot-1` supporting it or any of its descendants, breaking ties by choosing the first proposal locally seen. Do `proposals.append(winning_proposal)`. - * If `proposals.message.data[-1]` is NOT an empty proposal, set `shard_state = shard_state_transition(shard, slot, shard_state, get_block_root_at_slot(state, state.slot - 1), get_shard_proposer_index(state, slot, shard), proposals[-1])` and do `shard_states.append(shard_state)`. If it is an empty proposal, leave `shard_state` unchanged. +```python +def get_winning_proposal(beacon_state: BeaconState, proposals: Sequence[SignedShardBlock]) -> SignedShardBlock: + # TODO: Let `winning_proposal` be the proposal with the largest number of total attestationsfrom slots in + # `state.shard_next_slots[shard]....slot-1` supporting it or any of its descendants, breaking ties by choosing + # the first proposal locally seen. Do `proposals.append(winning_proposal)`. + return proposals[-1] # stub +``` -Make an attestation using `shard_data_roots = [hash_tree_root(proposal.message.data) for proposal in proposals]` and `shard_state_roots = shard_states`. +```python +def get_empty_body_block(shard_parent_root: Root, + beacon_parent_root: Root, + slot: Slot, + proposer_index: ValidatorIndex) -> ShardBlock: + return ShardBlock( + shard_parent_root=shard_parent_root, + beacon_parent_root=beacon_parent_root, + slot=slot, + proposer_index=proposer_index, + ) +``` + +```python +def is_empty_body(proposal: ShardBlock) -> bool: + # TODO + return len(proposal.body) == 0 +``` + +```python +def compute_shard_data_roots(proposals: Sequence[SignedShardBlock]) -> Sequence[Root]: + return [hash_tree_root(proposal.message.body) for proposal in proposals] +``` + +### Make attestations + +Suppose you are a committee member on shard `shard` at slot `current_slot` and you have received shard blocks `shard_blocks`. Let `state` be the head beacon state you are building on, and let `QUARTER_PERIOD = SECONDS_PER_SLOT // 4`. `2 * QUARTER_PERIOD` seconds into slot `current_slot`, run `get_shard_transition(beacon_state, shard, shard_blocks)` to get `shard_transition`. + +```python +def get_shard_transition(beacon_state: BeaconState, + shard: Shard, + shard_blocks: Sequence[SignedShardBlock]) -> ShardTransition: + offset_slots = get_offset_slots(beacon_state, shard) + start_slot = offset_slots[0] + proposals, shard_states, shard_data_roots = get_shard_state_transition_result(beacon_state, shard, shard_blocks) + + assert len(proposals) > 0 + assert len(shard_data_roots) > 0 + + shard_block_lengths = [] + proposer_signatures = [] + for proposal in proposals: + shard_block_lengths.append(len(proposal.message.body)) + if proposal.signature != BLSSignature(): + proposer_signatures.append(proposal.signature) + + proposer_signature_aggregate = bls.Aggregate(proposer_signatures) + + return ShardTransition( + start_slot=start_slot, + shard_block_lengths=shard_block_lengths, + shard_data_roots=shard_data_roots, + shard_states=shard_states, + proposer_signature_aggregate=proposer_signature_aggregate, + ) +``` + +```python +def get_shard_state_transition_result( + beacon_state: BeaconState, + shard: Shard, + shard_blocks: Sequence[SignedShardBlock], + validate_result: bool=True, +) -> Tuple[Sequence[SignedShardBlock], Sequence[ShardState], Sequence[Root]]: + proposals = [] + shard_states = [] + shard_state = beacon_state.shard_states[shard].copy() + shard_parent_root = beacon_state.shard_states[shard].latest_block_root + for slot in get_offset_slots(beacon_state, shard): + choices = [] + beacon_parent_root = get_block_root_at_slot(beacon_state, get_previous_slot(beacon_state.slot)) + proposer_index = get_shard_proposer_index(beacon_state, slot, shard) + shard_blocks_at_slot = [block for block in shard_blocks if block.message.slot == slot] + for block in shard_blocks_at_slot: + temp_shard_state = shard_state.copy() # Not doing the actual state updates here. + # Try to apply state transition to temp_shard_state. + try: + # Verify the proposer_index and signature + assert block.message.proposer_index == proposer_index + if validate_result: + assert verify_shard_block_signature(beacon_state, block) + + shard_state_transition( + beacon_state=beacon_state, + shard=shard, + slot=slot, + shard_state=temp_shard_state, + beacon_parent_root=beacon_parent_root, + signed_block=block, + ) + except Exception: + pass # TODO: throw error in the test helper + else: + choices.append(block) + + if len(choices) == 0: + block_header = get_empty_body_block( + shard_parent_root=shard_parent_root, + beacon_parent_root=beacon_parent_root, + slot=slot, + proposer_index=proposer_index, + ) + block = SignedShardBlock(message=block_header) + proposals.append(block) + elif len(choices) == 1: + proposals.append(choices[0]) + else: + proposals.append(get_winning_proposal(beacon_state, choices)) + + shard_parent_root = hash_tree_root(proposals[-1].message) + + if not is_empty_body(proposals[-1].message): + # Apply state transition to shard_state. + shard_state_transition( + beacon_state=beacon_state, + shard=shard, + slot=slot, + shard_state=shard_state, + beacon_parent_root=beacon_parent_root, + signed_block=block, + ) + + shard_states.append(shard_state) + + shard_data_roots = compute_shard_data_roots(proposals) + + return proposals, shard_states, shard_data_roots +``` diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 8215f5c5b..c203b737a 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -1,6 +1,6 @@ from typing import List -from eth2spec.test.context import expect_assertion_error, PHASE0 +from eth2spec.test.context import expect_assertion_error, PHASE0, PHASE1 from eth2spec.test.helpers.state import state_transition_and_sign_block from eth2spec.test.helpers.block import build_empty_block_for_next_slot from eth2spec.test.helpers.keys import privkeys @@ -43,7 +43,7 @@ def run_attestation_processing(spec, state, attestation, valid=True): yield 'post', state -def build_attestation_data(spec, state, slot, index): +def build_attestation_data(spec, state, slot, index, shard_transition=None): assert state.slot >= slot if slot == state.slot: @@ -66,12 +66,21 @@ def build_attestation_data(spec, state, slot, index): source_epoch = state.current_justified_checkpoint.epoch source_root = state.current_justified_checkpoint.root + if spec.fork == PHASE1 and shard_transition is not None: + shard_transition_root = shard_transition.hash_tree_root() + head_shard_root = shard_transition.shard_data_roots[len(shard_transition.shard_data_roots) - 1] + else: + shard_transition_root = spec.Root() + head_shard_root = spec.Root() + return spec.AttestationData( slot=slot, index=index, beacon_block_root=block_root, source=spec.Checkpoint(epoch=source_epoch, root=source_root), target=spec.Checkpoint(epoch=spec.compute_epoch_at_slot(slot), root=epoch_boundary_root), + head_shard_root=head_shard_root, + shard_transition_root=shard_transition_root, ) @@ -89,7 +98,7 @@ def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False) return attestation -def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=False): +def get_valid_on_time_attestation(spec, state, slot=None, index=None, shard_transition=None, signed=False): ''' Construct on-time attestation for next slot ''' @@ -98,7 +107,15 @@ def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=Fal if index is None: index = 0 - return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=True) + return get_valid_attestation( + spec, + state, + slot=slot, + index=index, + shard_transition=shard_transition, + signed=signed, + on_time=True, + ) def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False): @@ -113,13 +130,20 @@ def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False) return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=False) -def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signed=False, on_time=True): +def get_valid_attestation(spec, + state, + slot=None, + index=None, + shard_transition=None, + empty=False, + signed=False, + on_time=True): if slot is None: slot = state.slot if index is None: index = 0 - attestation_data = build_attestation_data(spec, state, slot, index) + attestation_data = build_attestation_data(spec, state, slot=slot, index=index, shard_transition=shard_transition) beacon_committee = spec.get_beacon_committee( state, @@ -138,7 +162,7 @@ def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signe if signed: sign_attestation(spec, state, attestation) - if spec.fork == 'phase1' and on_time: + if spec.fork == PHASE1 and on_time: attestation = convert_to_valid_on_time_attestation(spec, state, attestation, signed) return attestation @@ -210,7 +234,7 @@ def get_attestation_custody_signature(spec, state, attestation_data, block_index def sign_attestation(spec, state, attestation): - if spec.fork == 'phase1' and any(attestation.custody_bits_blocks): + if spec.fork == PHASE1 and any(attestation.custody_bits_blocks): sign_on_time_attestation(spec, state, attestation) return diff --git a/tests/core/pyspec/eth2spec/test/helpers/crosslinks.py b/tests/core/pyspec/eth2spec/test/helpers/crosslinks.py new file mode 100644 index 000000000..ea5da89d9 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/crosslinks.py @@ -0,0 +1,28 @@ +from eth2spec.test.context import expect_assertion_error + + +def run_crosslinks_processing(spec, state, shard_transitions, attestations, valid=True): + """ + Run ``process_attestation``, yielding: + - pre-state ('pre') + - shard_transitions ('shard_transitions') + - attestations ('attestations') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + # yield pre-state + yield 'pre', state + yield 'shard_transitions', shard_transitions + yield 'attestations', attestations + + # If the attestation is invalid, processing is aborted, and there is no post-state. + if not valid: + expect_assertion_error(lambda: spec.process_crosslinks(state, shard_transitions, attestations)) + yield 'post', None + return + + # process crosslinks + spec.process_crosslinks(state, shard_transitions, attestations) + + # yield post-state + yield 'post', state diff --git a/tests/core/pyspec/eth2spec/test/helpers/phase1/__init__.py b/tests/core/pyspec/eth2spec/test/helpers/phase1/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py deleted file mode 100644 index 0e16e1fac..000000000 --- a/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py +++ /dev/null @@ -1,63 +0,0 @@ -from eth2spec.utils.ssz.ssz_typing import Bitlist -from eth2spec.utils import bls - -from eth2spec.test.helpers.keys import privkeys -import eth2spec.test.helpers.attestations as phase0_attestations - - -def get_valid_on_time_attestation(spec, state, index=None, signed=False): - ''' - Construct on-time attestation for next slot - ''' - if index is None: - index = 0 - - attestation = phase0_attestations.get_valid_attestation(spec, state, state.slot, index, False) - shard = spec.get_shard(state, attestation) - offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), state.slot + 1) - - for _ in offset_slots: - attestation.custody_bits_blocks.append( - Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits]) - ) - - if signed: - sign_attestation(spec, state, attestation) - - return attestation - - -def sign_attestation(spec, state, attestation): - if not any(attestation.custody_bits_blocks): - phase0_attestations.sign_attestation(spec, state, attestation) - return - - committee = spec.get_beacon_committee(state, attestation.data.slot, attestation.data.index) - signatures = [] - for block_index, custody_bits in enumerate(attestation.custody_bits_blocks): - for participant, abit, cbit in zip(committee, attestation.aggregation_bits, custody_bits): - if not abit: - continue - signatures.append(get_attestation_custody_signature( - spec, - state, - attestation.data, - block_index, - cbit, - privkeys[participant] - )) - - attestation.signature = bls.Aggregate(signatures) - - -def get_attestation_custody_signature(spec, state, attestation_data, block_index, bit, privkey): - domain = spec.get_domain(state, spec.DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch) - signing_root = spec.compute_signing_root( - spec.AttestationCustodyBitWrapper( - attestation_data.hash_tree_root(), - block_index, - bit, - ), - domain, - ) - return bls.Sign(privkey, signing_root) diff --git a/tests/core/pyspec/eth2spec/test/helpers/phase1/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/phase1/shard_block.py deleted file mode 100644 index 6ef0cf79b..000000000 --- a/tests/core/pyspec/eth2spec/test/helpers/phase1/shard_block.py +++ /dev/null @@ -1,71 +0,0 @@ -from eth2spec.test.helpers.keys import privkeys -from eth2spec.utils import bls -from eth2spec.utils.bls import only_with_bls -from eth2spec.utils.ssz.ssz_impl import ( - hash_tree_root, -) - -from .attestations import ( - sign_shard_attestation, -) - - -@only_with_bls() -def sign_shard_block(spec, beacon_state, shard_state, block, proposer_index=None): - if proposer_index is None: - proposer_index = spec.get_shard_proposer_index(beacon_state, shard_state.shard, block.slot) - - privkey = privkeys[proposer_index] - domain = spec.get_domain(beacon_state, spec.DOMAIN_SHARD_PROPOSER, spec.compute_epoch_of_shard_slot(block.slot)) - signing_root = spec.compute_signing_root(block, domain) - block.signature = bls.Sign(privkey, signing_root) - - -def build_empty_shard_block(spec, - beacon_state, - shard_state, - slot, - signed=False, - full_attestation=False): - if slot is None: - slot = shard_state.slot - - previous_beacon_header = beacon_state.latest_block_header.copy() - if previous_beacon_header.state_root == spec.Bytes32(): - previous_beacon_header.state_root = beacon_state.hash_tree_root() - beacon_block_root = hash_tree_root(previous_beacon_header) - - previous_block_header = shard_state.latest_block_header.copy() - if previous_block_header.state_root == spec.Bytes32(): - previous_block_header.state_root = shard_state.hash_tree_root() - parent_root = hash_tree_root(previous_block_header) - - block = spec.ShardBlock( - shard=shard_state.shard, - slot=slot, - beacon_block_root=beacon_block_root, - parent_root=parent_root, - block_size_sum=shard_state.block_size_sum + spec.SHARD_HEADER_SIZE, - ) - - if full_attestation: - shard_committee = spec.get_shard_committee(beacon_state, shard_state.shard, block.slot) - block.aggregation_bits = list( - (True,) * len(shard_committee) + - (False,) * (spec.MAX_PERIOD_COMMITTEE_SIZE * 2 - len(shard_committee)) - ) - else: - shard_committee = [] - - block.attestations = sign_shard_attestation( - spec, - beacon_state, - shard_state, - block, - participants=shard_committee, - ) - - if signed: - sign_shard_block(spec, beacon_state, shard_state, block) - - return block diff --git a/tests/core/pyspec/eth2spec/test/helpers/phase1/shard_state.py b/tests/core/pyspec/eth2spec/test/helpers/phase1/shard_state.py deleted file mode 100644 index 24240b5fa..000000000 --- a/tests/core/pyspec/eth2spec/test/helpers/phase1/shard_state.py +++ /dev/null @@ -1,18 +0,0 @@ -from eth2spec.test.helpers.phase1.shard_block import sign_shard_block - - -def configure_shard_state(spec, beacon_state, shard=0): - beacon_state.slot = spec.Slot(spec.SHARD_GENESIS_EPOCH * spec.SLOTS_PER_EPOCH) - shard_state = spec.get_genesis_shard_state(spec.Shard(shard)) - shard_state.slot = spec.ShardSlot(spec.SHARD_GENESIS_EPOCH * spec.SHARD_SLOTS_PER_EPOCH) - return beacon_state, shard_state - - -def shard_state_transition_and_sign_block(spec, beacon_state, shard_state, block): - """ - Shard state transition via the provided ``block`` - then package the block with the state root and signature. - """ - spec.shard_state_transition(beacon_state, shard_state, block) - block.state_root = shard_state.hash_tree_root() - sign_shard_block(spec, beacon_state, shard_state, block) diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py new file mode 100644 index 000000000..0cb8a3dfd --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py @@ -0,0 +1,47 @@ +from eth2spec.test.helpers.keys import privkeys +from eth2spec.utils import bls +from eth2spec.utils.bls import only_with_bls + + +@only_with_bls() +def sign_shard_block(spec, beacon_state, shard, block, proposer_index=None): + slot = block.message.slot + if proposer_index is None: + proposer_index = spec.get_shard_proposer_index(beacon_state, slot, shard) + + privkey = privkeys[proposer_index] + domain = spec.get_domain(beacon_state, spec.DOMAIN_SHARD_PROPOSAL, spec.compute_epoch_at_slot(slot)) + signing_root = spec.compute_signing_root(block.message, domain) + block.signature = bls.Sign(privkey, signing_root) + + +def build_empty_shard_block(spec, + beacon_state, + shard, + slot, + body=None, + signed=False): + shard_state = beacon_state.shard_states[shard] + if slot is None: + slot = shard_state.slot + + if body is None: + body = [] + + proposer_index = spec.get_shard_proposer_index(beacon_state, slot, shard) + + block = spec.ShardBlock( + shard_parent_root=shard_state.latest_block_root, + beacon_parent_root=spec.get_block_root_at_slot(beacon_state, spec.get_previous_slot(beacon_state.slot)), + slot=slot, + proposer_index=proposer_index, + body=body, + ) + signed_block = spec.SignedShardBlock( + message=block, + ) + + if signed: + sign_shard_block(spec, beacon_state, shard, signed_block, proposer_index=proposer_index) + + return signed_block diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py new file mode 100644 index 000000000..1d5fe7c02 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py @@ -0,0 +1,52 @@ +from eth2spec.test.context import ( + with_all_phases_except, + spec_state_test, + always_bls, +) +from eth2spec.test.helpers.attestations import ( + get_valid_on_time_attestation, +) +from eth2spec.test.helpers.crosslinks import ( + run_crosslinks_processing, +) +from eth2spec.test.helpers.shard_block import build_empty_shard_block +from eth2spec.test.helpers.state import next_epoch, next_slot + + +@with_all_phases_except(['phase0']) +@spec_state_test +@always_bls +def test_basic_crosslinks(spec, state): + next_epoch(spec, state) + next_epoch(spec, state) + state = spec.upgrade_to_phase1(state) + next_slot(spec, state) + + committee_index = spec.CommitteeIndex(0) + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) + shard_block = build_empty_shard_block(spec, state, shard, body=b'\x12' * 10, slot=state.slot, signed=True) + shard_blocks = [shard_block] + + next_slot(spec, state) + + shard_transition = spec.get_shard_transition(state, shard, shard_blocks) + shard_transitions = [spec.ShardTransition()] * len(state.shard_states) + shard_transitions[shard] = shard_transition + + attestation = get_valid_on_time_attestation( + spec, + state, + slot=state.slot, + index=committee_index, + shard_transition=shard_transition, + signed=True, + ) + attestations = [attestation] + + offset_slots = spec.get_offset_slots(state, shard) + + yield from run_crosslinks_processing(spec, state, shard_transitions, attestations) + + shard_state = state.shard_states[shard] + assert shard_state.slot == offset_slots[-1] + assert shard_state.latest_block_root == shard_block.message.hash_tree_root() From afa12caf1f3d2595c79e1a53e901c9639e98f500 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sat, 11 Apr 2020 01:12:40 +0800 Subject: [PATCH 20/53] Refactor `get_shard_state_transition_result` --- specs/phase1/fraud-proofs.md | 184 +++++++++++++++++++++-------------- 1 file changed, 112 insertions(+), 72 deletions(-) diff --git a/specs/phase1/fraud-proofs.md b/specs/phase1/fraud-proofs.md index 30b94d810..5af904ff5 100644 --- a/specs/phase1/fraud-proofs.md +++ b/specs/phase1/fraud-proofs.md @@ -149,6 +149,118 @@ def compute_shard_data_roots(proposals: Sequence[SignedShardBlock]) -> Sequence[ return [hash_tree_root(proposal.message.body) for proposal in proposals] ``` +```python +def get_proposal_choices_at_slot(beacon_state: BeaconState, + shard_state: ShardState, + slot: Slot, + shard: Shard, + shard_blocks: Sequence[SignedShardBlock], + validate_result: bool=True) -> Sequence[SignedShardBlock]: + choices = [] + beacon_parent_root = get_block_root_at_slot(beacon_state, get_previous_slot(beacon_state.slot)) + proposer_index = get_shard_proposer_index(beacon_state, slot, shard) + shard_blocks_at_slot = [block for block in shard_blocks if block.message.slot == slot] + for block in shard_blocks_at_slot: + temp_shard_state = shard_state.copy() # Not doing the actual state updates here. + # Try to apply state transition to temp_shard_state. + try: + # Verify the proposer_index and signature + assert block.message.proposer_index == proposer_index + if validate_result: + assert verify_shard_block_signature(beacon_state, block) + + shard_state_transition( + beacon_state=beacon_state, + shard=shard, + slot=slot, + shard_state=temp_shard_state, + beacon_parent_root=beacon_parent_root, + signed_block=block, + ) + except Exception: + pass # TODO: throw error in the test helper + else: + choices.append(block) + return choices +``` + +```python +def get_proposal_at_slot(beacon_state: BeaconState, + shard_state: ShardState, + slot: Shard, + shard: Shard, + shard_parent_root: Root, + shard_blocks: Sequence[SignedShardBlock], + validate_result: bool=True) -> Tuple[SignedShardBlock, ShardState, Root]: + beacon_parent_root = get_block_root_at_slot(beacon_state, get_previous_slot(beacon_state.slot)) + proposer_index = get_shard_proposer_index(beacon_state, slot, shard) + shard_state = shard_state.copy() # Don't update the given shard_state + choices = get_proposal_choices_at_slot( + beacon_state=beacon_state, + shard_state=shard_state, + slot=slot, + shard=shard, + shard_blocks=shard_blocks, + validate_result=validate_result, + ) + if len(choices) == 0: + block_header = get_empty_body_block( + shard_parent_root=shard_parent_root, + beacon_parent_root=beacon_parent_root, + slot=slot, + proposer_index=proposer_index, + ) + proposal = SignedShardBlock(message=block_header) + elif len(choices) == 1: + proposal = choices[0] + else: + proposal = get_winning_proposal(beacon_state, choices) + + shard_parent_root = hash_tree_root(proposal.message) + + if not is_empty_body(proposal.message): + # Apply state transition to shard_state. + shard_state_transition( + beacon_state=beacon_state, + shard=shard, + slot=slot, + shard_state=shard_state, + beacon_parent_root=beacon_parent_root, + signed_block=proposal, + ) + + return proposal, shard_state, shard_parent_root +``` + +```python +def get_shard_state_transition_result( + beacon_state: BeaconState, + shard: Shard, + shard_blocks: Sequence[SignedShardBlock], + validate_result: bool=True, +) -> Tuple[Sequence[SignedShardBlock], Sequence[ShardState], Sequence[Root]]: + proposals = [] + shard_states = [] + shard_state = beacon_state.shard_states[shard].copy() + shard_parent_root = beacon_state.shard_states[shard].latest_block_root + for slot in get_offset_slots(beacon_state, shard): + proposal, shard_state, shard_parent_root = get_proposal_at_slot( + beacon_state=beacon_state, + shard_state=shard_state, + slot=slot, + shard=shard, + shard_parent_root=shard_parent_root, + shard_blocks=shard_blocks, + validate_result=validate_result, + ) + shard_states.append(shard_state) + proposals.append(proposal) + + shard_data_roots = compute_shard_data_roots(proposals) + + return proposals, shard_states, shard_data_roots +``` + ### Make attestations Suppose you are a committee member on shard `shard` at slot `current_slot` and you have received shard blocks `shard_blocks`. Let `state` be the head beacon state you are building on, and let `QUARTER_PERIOD = SECONDS_PER_SLOT // 4`. `2 * QUARTER_PERIOD` seconds into slot `current_slot`, run `get_shard_transition(beacon_state, shard, shard_blocks)` to get `shard_transition`. @@ -181,75 +293,3 @@ def get_shard_transition(beacon_state: BeaconState, proposer_signature_aggregate=proposer_signature_aggregate, ) ``` - -```python -def get_shard_state_transition_result( - beacon_state: BeaconState, - shard: Shard, - shard_blocks: Sequence[SignedShardBlock], - validate_result: bool=True, -) -> Tuple[Sequence[SignedShardBlock], Sequence[ShardState], Sequence[Root]]: - proposals = [] - shard_states = [] - shard_state = beacon_state.shard_states[shard].copy() - shard_parent_root = beacon_state.shard_states[shard].latest_block_root - for slot in get_offset_slots(beacon_state, shard): - choices = [] - beacon_parent_root = get_block_root_at_slot(beacon_state, get_previous_slot(beacon_state.slot)) - proposer_index = get_shard_proposer_index(beacon_state, slot, shard) - shard_blocks_at_slot = [block for block in shard_blocks if block.message.slot == slot] - for block in shard_blocks_at_slot: - temp_shard_state = shard_state.copy() # Not doing the actual state updates here. - # Try to apply state transition to temp_shard_state. - try: - # Verify the proposer_index and signature - assert block.message.proposer_index == proposer_index - if validate_result: - assert verify_shard_block_signature(beacon_state, block) - - shard_state_transition( - beacon_state=beacon_state, - shard=shard, - slot=slot, - shard_state=temp_shard_state, - beacon_parent_root=beacon_parent_root, - signed_block=block, - ) - except Exception: - pass # TODO: throw error in the test helper - else: - choices.append(block) - - if len(choices) == 0: - block_header = get_empty_body_block( - shard_parent_root=shard_parent_root, - beacon_parent_root=beacon_parent_root, - slot=slot, - proposer_index=proposer_index, - ) - block = SignedShardBlock(message=block_header) - proposals.append(block) - elif len(choices) == 1: - proposals.append(choices[0]) - else: - proposals.append(get_winning_proposal(beacon_state, choices)) - - shard_parent_root = hash_tree_root(proposals[-1].message) - - if not is_empty_body(proposals[-1].message): - # Apply state transition to shard_state. - shard_state_transition( - beacon_state=beacon_state, - shard=shard, - slot=slot, - shard_state=shard_state, - beacon_parent_root=beacon_parent_root, - signed_block=block, - ) - - shard_states.append(shard_state) - - shard_data_roots = compute_shard_data_roots(proposals) - - return proposals, shard_states, shard_data_roots -``` From e645d6b5fa7490134c1b30d98ae29aaa4cb46f00 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 13 Apr 2020 18:23:01 +0800 Subject: [PATCH 21/53] Rename `build_empty_shard_block` to `build_shard_block` --- .../core/pyspec/eth2spec/test/helpers/shard_block.py | 12 ++++++------ .../block_processing/test_process_crosslink.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py index 0cb8a3dfd..8f95cf73a 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py @@ -15,12 +15,12 @@ def sign_shard_block(spec, beacon_state, shard, block, proposer_index=None): block.signature = bls.Sign(privkey, signing_root) -def build_empty_shard_block(spec, - beacon_state, - shard, - slot, - body=None, - signed=False): +def build_shard_block(spec, + beacon_state, + shard, + slot, + body=None, + signed=False): shard_state = beacon_state.shard_states[shard] if slot is None: slot = shard_state.slot diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py index 1d5fe7c02..1585a1a45 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py @@ -9,7 +9,7 @@ from eth2spec.test.helpers.attestations import ( from eth2spec.test.helpers.crosslinks import ( run_crosslinks_processing, ) -from eth2spec.test.helpers.shard_block import build_empty_shard_block +from eth2spec.test.helpers.shard_block import build_shard_block from eth2spec.test.helpers.state import next_epoch, next_slot @@ -24,7 +24,7 @@ def test_basic_crosslinks(spec, state): committee_index = spec.CommitteeIndex(0) shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) - shard_block = build_empty_shard_block(spec, state, shard, body=b'\x12' * 10, slot=state.slot, signed=True) + shard_block = build_shard_block(spec, state, shard, body=b'\x12' * 10, slot=state.slot, signed=True) shard_blocks = [shard_block] next_slot(spec, state) From 9724cb832d9f909b21f88683721389ff90e70756 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 16 Apr 2020 17:18:11 +0800 Subject: [PATCH 22/53] Apply suggestions from code review from @djrtwo Co-Authored-By: Danny Ryan --- specs/phase1/beacon-chain.md | 8 ++++---- specs/phase1/fraud-proofs.md | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 4b22f8633..2410c534d 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -465,12 +465,12 @@ def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) - source_epoch -= SHARD_COMMITTEE_PERIOD active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) seed = get_seed(beacon_state, source_epoch, DOMAIN_SHARD_COMMITTEE) - active_shards_count = get_active_shard_count(beacon_state) + active_shard_count = get_active_shard_count(beacon_state) return compute_committee( indices=active_validator_indices, seed=seed, index=shard, - count=active_shards_count, + count=active_shard_count, ) ``` @@ -483,12 +483,12 @@ def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Seque source_epoch -= LIGHT_CLIENT_COMMITTEE_PERIOD active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) seed = get_seed(beacon_state, source_epoch, DOMAIN_LIGHT_CLIENT) - active_shards_count = get_active_shard_count(beacon_state) + active_shard_count = get_active_shard_count(beacon_state) return compute_committee( indices=active_validator_indices, seed=seed, index=0, - count=active_shards_count, + count=active_shard_count, )[:TARGET_COMMITTEE_SIZE] ``` diff --git a/specs/phase1/fraud-proofs.md b/specs/phase1/fraud-proofs.md index 5af904ff5..b476508a4 100644 --- a/specs/phase1/fraud-proofs.md +++ b/specs/phase1/fraud-proofs.md @@ -89,7 +89,7 @@ def verify_fraud_proof(beacon_state: BeaconState, # 2. Check if the shard state transition result is wrong between # `transition.shard_states[offset_index - 1]` to `transition.shard_states[offset_index]`. if offset_index == 0: - shard_state = beacon_parent_block.shard_transitions[shard][-1] + shard_state = beacon_parent_block.shard_transitions[shard].shard_states[-1] else: shard_state = transition.shard_states[offset_index - 1].copy() # Not doing the actual state updates here. @@ -119,7 +119,7 @@ def generate_custody_bit(subkey: BLSPubkey, block: ShardBlock) -> bool: ```python def get_winning_proposal(beacon_state: BeaconState, proposals: Sequence[SignedShardBlock]) -> SignedShardBlock: - # TODO: Let `winning_proposal` be the proposal with the largest number of total attestationsfrom slots in + # TODO: Let `winning_proposal` be the proposal with the largest number of total attestations from slots in # `state.shard_next_slots[shard]....slot-1` supporting it or any of its descendants, breaking ties by choosing # the first proposal locally seen. Do `proposals.append(winning_proposal)`. return proposals[-1] # stub @@ -263,7 +263,7 @@ def get_shard_state_transition_result( ### Make attestations -Suppose you are a committee member on shard `shard` at slot `current_slot` and you have received shard blocks `shard_blocks`. Let `state` be the head beacon state you are building on, and let `QUARTER_PERIOD = SECONDS_PER_SLOT // 4`. `2 * QUARTER_PERIOD` seconds into slot `current_slot`, run `get_shard_transition(beacon_state, shard, shard_blocks)` to get `shard_transition`. +Suppose you are a committee member on shard `shard` at slot `current_slot` and you have received shard blocks `shard_blocks` since the latest successful crosslink for `shard` into the beacon chain. Let `state` be the head beacon state you are building on, and let `QUARTER_PERIOD = SECONDS_PER_SLOT // 4`. `2 * QUARTER_PERIOD` seconds into slot `current_slot`, run `get_shard_transition(beacon_state, shard, shard_blocks)` to get `shard_transition`. ```python def get_shard_transition(beacon_state: BeaconState, From 85d5a9abaf0dfbf9d652da3be3f606880b54943d Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 16 Apr 2020 19:43:48 +0800 Subject: [PATCH 23/53] [squashed] shard chain updates wip PR feedback from Danny and some refactor 1. Add stub `PHASE_1_GENESIS_SLOT` 2. Rename `get_updated_gasprice` to `compute_updated_gasprice` 3. Rename `compute_shard_data_roots` to `compute_shard_body_roots` Apply shard transition for the skipped slots Refactor `shard_state_transition` Get `beacon_parent_root` from offset slot Add more test Add `verify_shard_block_message` Add `> 0` Keep `beacon_parent_block` unchanged in `is_valid_fraud_proof` Remove some lines Fix type Refactor + simplify skipped slot processing --- configs/mainnet.yaml | 2 + configs/minimal.yaml | 2 + specs/phase1/beacon-chain.md | 68 ++++---- specs/phase1/fraud-proofs.md | 164 ++++++++---------- specs/phase1/phase1-fork.md | 9 +- .../eth2spec/test/helpers/shard_block.py | 2 +- .../test_process_crosslink.py | 53 +++++- 7 files changed, 166 insertions(+), 134 deletions(-) diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 6d71cfa47..f5f38de5e 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -161,6 +161,8 @@ DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000 # Phase 1: Upgrade from Phase 0 # --------------------------------------------------------------- PHASE_1_FORK_VERSION: 0x01000000 +# [STUB] +PHASE_1_GENESIS_SLOT: 32 INITIAL_ACTIVE_SHARDS: 64 # Phase 1: General diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 9daf428b4..7fc255f59 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -162,6 +162,8 @@ DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000 # --------------------------------------------------------------- # [customized] for testnet distinction PHASE_1_FORK_VERSION: 0x01000001 +# [customized] for testing +PHASE_1_GENESIS_SLOT: 8 # [customized] reduced for testing INITIAL_ACTIVE_SHARDS: 4 diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 2410c534d..5c67fe4f2 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -39,6 +39,7 @@ - [`committee_to_compact_committee`](#committee_to_compact_committee) - [`compute_shard_from_committee_index`](#compute_shard_from_committee_index) - [`compute_offset_slots`](#compute_offset_slots) + - [`compute_updated_gasprice`](#compute_updated_gasprice) - [Beacon state accessors](#beacon-state-accessors) - [`get_active_shard_count`](#get_active_shard_count) - [`get_online_validator_indices`](#get_online_validator_indices) @@ -46,7 +47,6 @@ - [`get_light_client_committee`](#get_light_client_committee) - [`get_shard_proposer_index`](#get_shard_proposer_index) - [`get_indexed_attestation`](#get_indexed_attestation) - - [`get_updated_gasprice`](#get_updated_gasprice) - [`get_start_shard`](#get_start_shard) - [`get_shard`](#get_shard) - [`get_latest_slot_for_shard`](#get_latest_slot_for_shard) @@ -439,6 +439,20 @@ def compute_offset_slots(start_slot: Slot, end_slot: Slot) -> Sequence[Slot]: return [Slot(start_slot + x) for x in SHARD_BLOCK_OFFSETS if start_slot + x < end_slot] ``` +#### `compute_updated_gasprice` + +```python +def compute_updated_gasprice(prev_gasprice: Gwei, length: uint8) -> Gwei: + if length > TARGET_SHARD_BLOCK_SIZE: + delta = (prev_gasprice * (length - TARGET_SHARD_BLOCK_SIZE) + // TARGET_SHARD_BLOCK_SIZE // GASPRICE_ADJUSTMENT_COEFFICIENT) + return min(prev_gasprice + delta, MAX_GASPRICE) + else: + delta = (prev_gasprice * (TARGET_SHARD_BLOCK_SIZE - length) + // TARGET_SHARD_BLOCK_SIZE // GASPRICE_ADJUSTMENT_COEFFICIENT) + return max(prev_gasprice, MIN_GASPRICE + delta) - delta +``` + ### Beacon state accessors #### `get_active_shard_count` @@ -512,20 +526,6 @@ def get_indexed_attestation(beacon_state: BeaconState, attestation: Attestation) ) ``` -#### `get_updated_gasprice` - -```python -def get_updated_gasprice(prev_gasprice: Gwei, length: uint8) -> Gwei: - if length > TARGET_SHARD_BLOCK_SIZE: - delta = (prev_gasprice * (length - TARGET_SHARD_BLOCK_SIZE) - // TARGET_SHARD_BLOCK_SIZE // GASPRICE_ADJUSTMENT_COEFFICIENT) - return min(prev_gasprice + delta, MAX_GASPRICE) - else: - delta = (prev_gasprice * (TARGET_SHARD_BLOCK_SIZE - length) - // TARGET_SHARD_BLOCK_SIZE // GASPRICE_ADJUSTMENT_COEFFICIENT) - return max(prev_gasprice, MIN_GASPRICE + delta) - delta -``` - #### `get_start_shard` ```python @@ -617,7 +617,6 @@ def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: Indexe return bls.AggregateVerify(zip(all_pubkeys, all_signing_roots), signature=attestation.signature) ``` - ### Block processing ```python @@ -630,7 +629,6 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: process_operations(state, block.body) ``` - #### Operations ```python @@ -714,34 +712,31 @@ def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTr assert transition.start_slot == offset_slots[0] headers = [] - header = ShardBlockHeader() proposers = [] prev_gasprice = state.shard_states[shard].gasprice shard_parent_root = state.shard_states[shard].latest_block_root - beacon_parent_root = get_block_root_at_slot(state, get_previous_slot(state.slot)) for i in range(len(offset_slots)): shard_block_length = transition.shard_block_lengths[i] - is_empty_proposal = (shard_block_length == 0) + is_empty_proposal = shard_block_length == 0 shard_state = transition.shard_states[i] - proposal_index = get_shard_proposer_index(state, offset_slots[i], shard) - # Reconstruct shard headers - header = ShardBlockHeader( - shard_parent_root=shard_parent_root, - beacon_parent_root=beacon_parent_root, - proposer_index=proposal_index, - slot=offset_slots[i], - body_root=transition.shard_data_roots[i] - ) - shard_parent_root = hash_tree_root(header) - if not is_empty_proposal: - # Only add non-empty signature + proposal_index = get_shard_proposer_index(state, offset_slots[i], shard) + # Reconstruct shard headers + header = ShardBlockHeader( + shard_parent_root=shard_parent_root, + beacon_parent_root=get_block_root_at_slot(state, offset_slots[i]), + proposer_index=proposal_index, + slot=offset_slots[i], + body_root=transition.shard_data_roots[i] + ) + shard_parent_root = hash_tree_root(header) headers.append(header) proposers.append(proposal_index) - # Verify correct calculation of gas prices and slots - assert shard_state.gasprice == get_updated_gasprice(prev_gasprice, shard_block_length) - assert shard_state.slot == offset_slots[i] - prev_gasprice = shard_state.gasprice + + # Verify correct calculation of gas prices and slots + assert shard_state.gasprice == compute_updated_gasprice(prev_gasprice, shard_block_length) + assert shard_state.slot == offset_slots[i] + prev_gasprice = shard_state.gasprice pubkeys = [state.validators[proposer].pubkey for proposer in proposers] signing_roots = [ @@ -753,7 +748,6 @@ def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTr # Save updated state state.shard_states[shard] = transition.shard_states[len(transition.shard_states) - 1] - assert state.slot > 0 state.shard_states[shard].slot = state.slot - 1 ``` diff --git a/specs/phase1/fraud-proofs.md b/specs/phase1/fraud-proofs.md index b476508a4..c2178c10e 100644 --- a/specs/phase1/fraud-proofs.md +++ b/specs/phase1/fraud-proofs.md @@ -36,17 +36,48 @@ This document describes the shard transition function and fraud proofs as part o ```python def shard_state_transition(beacon_state: BeaconState, - shard: Shard, - slot: Slot, shard_state: ShardState, - beacon_parent_root: Root, signed_block: SignedShardBlock) -> None: # Update shard state - shard_state.data = hash( - hash_tree_root(shard_state) + hash_tree_root(beacon_parent_root) + hash_tree_root(signed_block.message.body) + prev_gasprice = shard_state.gasprice + if len(signed_block.message.body) == 0: + latest_block_root = shard_state.latest_block_root + else: + latest_block_root = hash_tree_root(signed_block.message) + + shard_state.data = compute_shard_transition_data( + beacon_state, + shard_state, + signed_block.message.beacon_parent_root, + signed_block.message.body, ) - shard_state.slot = slot - shard_state.latest_block_root = hash_tree_root(signed_block.message) + shard_state.gasprice = compute_updated_gasprice(prev_gasprice, len(signed_block.message.body)) + shard_state.slot = signed_block.message.slot + shard_state.latest_block_root = latest_block_root +``` + +```python +def compute_shard_transition_data(beacon_state: BeaconState, + shard_state: ShardState, + beacon_parent_root: Root, + shard_body_root: Root) -> Bytes32: + return hash( + hash_tree_root(shard_state) + beacon_parent_root + shard_body_root + ) +``` + +```python +def verify_shard_block_message(beacon_state: BeaconState, + shard_state: ShardState, + block: ShardBlock, + slot: Slot, + shard: Shard) -> bool: + assert block.shard_parent_root == shard_state.latest_block_root + assert block.beacon_parent_root == get_block_root_at_slot(beacon_state, slot) + assert block.slot == slot + assert block.proposer_index == get_shard_proposer_index(beacon_state, slot, shard) + assert 0 < len(block.body) <= MAX_SHARD_BLOCK_SIZE + return True ``` ```python @@ -67,20 +98,20 @@ TODO. The intent is to have a single universal fraud proof type, which contains 3. The `transition: ShardTransition` itself 4. The full body of the shard block `shard_block` 5. A Merkle proof to the `shard_states` in the parent block the attestation is referencing +6. The `subkey` to generate the custody bit Call the following function to verify the proof: ```python -def verify_fraud_proof(beacon_state: BeaconState, - attestation: Attestation, - offset_index: uint64, - transition: ShardTransition, - signed_block: SignedShardBlock, - subkey: BLSPubkey, - beacon_parent_block: BeaconBlock) -> bool: +def is_valid_fraud_proof(beacon_state: BeaconState, + attestation: Attestation, + offset_index: uint64, + transition: ShardTransition, + signed_block: SignedShardBlock, + subkey: BLSPubkey, + beacon_parent_block: BeaconBlock) -> bool: # 1. Check if `custody_bits[offset_index][j] != generate_custody_bit(subkey, block_contents)` for any `j`. shard = get_shard(beacon_state, attestation) - slot = attestation.data.slot custody_bits = attestation.custody_bits_blocks for j in range(custody_bits[offset_index]): if custody_bits[offset_index][j] != generate_custody_bit(subkey, signed_block): @@ -89,19 +120,12 @@ def verify_fraud_proof(beacon_state: BeaconState, # 2. Check if the shard state transition result is wrong between # `transition.shard_states[offset_index - 1]` to `transition.shard_states[offset_index]`. if offset_index == 0: - shard_state = beacon_parent_block.shard_transitions[shard].shard_states[-1] + shard_state = beacon_parent_block.shard_transitions[shard].shard_states[-1].copy() else: shard_state = transition.shard_states[offset_index - 1].copy() # Not doing the actual state updates here. - shard_state_transition( - beacon_state=beacon_state, - shard=shard, - slot=slot, - shard_state=shard_state, - beacon_parent_root=hash_tree_root(beacon_parent_block), - signed_block=signed_block, - ) - if shard_state.latest_block_root != transition.shard_states[offset_index].data: + shard_state_transition(beacon_state, shard_state, signed_block) + if shard_state.data != transition.shard_states[offset_index].data: return True return False @@ -126,26 +150,7 @@ def get_winning_proposal(beacon_state: BeaconState, proposals: Sequence[SignedSh ``` ```python -def get_empty_body_block(shard_parent_root: Root, - beacon_parent_root: Root, - slot: Slot, - proposer_index: ValidatorIndex) -> ShardBlock: - return ShardBlock( - shard_parent_root=shard_parent_root, - beacon_parent_root=beacon_parent_root, - slot=slot, - proposer_index=proposer_index, - ) -``` - -```python -def is_empty_body(proposal: ShardBlock) -> bool: - # TODO - return len(proposal.body) == 0 -``` - -```python -def compute_shard_data_roots(proposals: Sequence[SignedShardBlock]) -> Sequence[Root]: +def compute_shard_body_roots(proposals: Sequence[SignedShardBlock]) -> Sequence[Root]: return [hash_tree_root(proposal.message.body) for proposal in proposals] ``` @@ -155,28 +160,23 @@ def get_proposal_choices_at_slot(beacon_state: BeaconState, slot: Slot, shard: Shard, shard_blocks: Sequence[SignedShardBlock], - validate_result: bool=True) -> Sequence[SignedShardBlock]: + validate_signature: bool=True) -> Sequence[SignedShardBlock]: + """ + Return the valid shard blocks at the given ``slot``. + Note that this function doesn't change the state. + """ choices = [] - beacon_parent_root = get_block_root_at_slot(beacon_state, get_previous_slot(beacon_state.slot)) - proposer_index = get_shard_proposer_index(beacon_state, slot, shard) shard_blocks_at_slot = [block for block in shard_blocks if block.message.slot == slot] for block in shard_blocks_at_slot: temp_shard_state = shard_state.copy() # Not doing the actual state updates here. # Try to apply state transition to temp_shard_state. try: - # Verify the proposer_index and signature - assert block.message.proposer_index == proposer_index - if validate_result: + # Verify block message and signature + assert verify_shard_block_message(beacon_state, temp_shard_state, block.message, slot, shard) + if validate_signature: assert verify_shard_block_signature(beacon_state, block) - shard_state_transition( - beacon_state=beacon_state, - shard=shard, - slot=slot, - shard_state=temp_shard_state, - beacon_parent_root=beacon_parent_root, - signed_block=block, - ) + shard_state_transition(beacon_state, temp_shard_state, block) except Exception: pass # TODO: throw error in the test helper else: @@ -189,11 +189,12 @@ def get_proposal_at_slot(beacon_state: BeaconState, shard_state: ShardState, slot: Shard, shard: Shard, - shard_parent_root: Root, shard_blocks: Sequence[SignedShardBlock], - validate_result: bool=True) -> Tuple[SignedShardBlock, ShardState, Root]: - beacon_parent_root = get_block_root_at_slot(beacon_state, get_previous_slot(beacon_state.slot)) - proposer_index = get_shard_proposer_index(beacon_state, slot, shard) + validate_signature: bool=True) -> Tuple[SignedShardBlock, ShardState]: + """ + Return ``proposal``, ``shard_state`` of the given ``slot``. + Note that this function doesn't change the state. + """ shard_state = shard_state.copy() # Don't update the given shard_state choices = get_proposal_choices_at_slot( beacon_state=beacon_state, @@ -201,35 +202,20 @@ def get_proposal_at_slot(beacon_state: BeaconState, slot=slot, shard=shard, shard_blocks=shard_blocks, - validate_result=validate_result, + validate_signature=validate_signature, ) if len(choices) == 0: - block_header = get_empty_body_block( - shard_parent_root=shard_parent_root, - beacon_parent_root=beacon_parent_root, - slot=slot, - proposer_index=proposer_index, - ) - proposal = SignedShardBlock(message=block_header) + block = ShardBlock(slot=slot) + proposal = SignedShardBlock(message=block) elif len(choices) == 1: proposal = choices[0] else: proposal = get_winning_proposal(beacon_state, choices) - shard_parent_root = hash_tree_root(proposal.message) + # Apply state transition + shard_state_transition(beacon_state, shard_state, proposal) - if not is_empty_body(proposal.message): - # Apply state transition to shard_state. - shard_state_transition( - beacon_state=beacon_state, - shard=shard, - slot=slot, - shard_state=shard_state, - beacon_parent_root=beacon_parent_root, - signed_block=proposal, - ) - - return proposal, shard_state, shard_parent_root + return proposal, shard_state ``` ```python @@ -237,26 +223,24 @@ def get_shard_state_transition_result( beacon_state: BeaconState, shard: Shard, shard_blocks: Sequence[SignedShardBlock], - validate_result: bool=True, + validate_signature: bool=True, ) -> Tuple[Sequence[SignedShardBlock], Sequence[ShardState], Sequence[Root]]: proposals = [] shard_states = [] shard_state = beacon_state.shard_states[shard].copy() - shard_parent_root = beacon_state.shard_states[shard].latest_block_root for slot in get_offset_slots(beacon_state, shard): - proposal, shard_state, shard_parent_root = get_proposal_at_slot( + proposal, shard_state = get_proposal_at_slot( beacon_state=beacon_state, shard_state=shard_state, slot=slot, shard=shard, - shard_parent_root=shard_parent_root, shard_blocks=shard_blocks, - validate_result=validate_result, + validate_signature=validate_signature, ) shard_states.append(shard_state) proposals.append(proposal) - shard_data_roots = compute_shard_data_roots(proposals) + shard_data_roots = compute_shard_body_roots(proposals) return proposals, shard_states, shard_data_roots ``` diff --git a/specs/phase1/phase1-fork.md b/specs/phase1/phase1-fork.md index 173fceeb4..e95c12c72 100644 --- a/specs/phase1/phase1-fork.md +++ b/specs/phase1/phase1-fork.md @@ -7,7 +7,7 @@ - [Introduction](#introduction) - [Configuration](#configuration) - [Fork to Phase 1](#fork-to-phase-1) - - [Fork trigger.](#fork-trigger) + - [Fork trigger](#fork-trigger) - [Upgrading the state](#upgrading-the-state) @@ -35,17 +35,18 @@ Warning: this configuration is not definitive. | Name | Value | | - | - | | `PHASE_1_FORK_VERSION` | `Version('0x01000000')` | +| `PHASE_1_GENESIS_SLOT` | `2**5` **TBD** | | `INITIAL_ACTIVE_SHARDS` | `2**6` (= 64) | ## Fork to Phase 1 -### Fork trigger. +### Fork trigger -TBD. Social consensus, along with state conditions such as epoch boundary, finality, deposits, active validator count, etc. may be part of the decision process to trigger the fork. +TBD. Social consensus, along with state conditions such as epoch boundary, finality, deposits, active validator count, etc. may be part of the decision process to trigger the fork. For now we assume the condition will be triggered at slot `PHASE_1_GENESIS_SLOT`, where `PHASE_1_GENESIS_SLOT % SLOTS_PER_EPOCH == 0`. ### Upgrading the state -After `process_slots` of Phase 0 finishes, but before the first Phase 1 block is processed, an irregular state change is made to upgrade to Phase 1. +After `process_slots` of Phase 0 finishes, if `state.slot == PHASE_1_GENESIS_SLOT`, an irregular state change is made to upgrade to Phase 1. ```python def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState: diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py index 8f95cf73a..25d868b65 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py @@ -32,7 +32,7 @@ def build_shard_block(spec, block = spec.ShardBlock( shard_parent_root=shard_state.latest_block_root, - beacon_parent_root=spec.get_block_root_at_slot(beacon_state, spec.get_previous_slot(beacon_state.slot)), + beacon_parent_root=spec.get_block_root_at_slot(beacon_state, spec.get_previous_slot(slot)), slot=slot, proposer_index=proposer_index, body=body, diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py index 1585a1a45..6d1e65678 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py @@ -10,7 +10,7 @@ from eth2spec.test.helpers.crosslinks import ( run_crosslinks_processing, ) from eth2spec.test.helpers.shard_block import build_shard_block -from eth2spec.test.helpers.state import next_epoch, next_slot +from eth2spec.test.helpers.state import next_epoch, next_slot, next_slots @with_all_phases_except(['phase0']) @@ -24,7 +24,8 @@ def test_basic_crosslinks(spec, state): committee_index = spec.CommitteeIndex(0) shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) - shard_block = build_shard_block(spec, state, shard, body=b'\x12' * 10, slot=state.slot, signed=True) + body = b'1' * spec.MAX_SHARD_BLOCK_SIZE + shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True) shard_blocks = [shard_block] next_slot(spec, state) @@ -43,10 +44,58 @@ def test_basic_crosslinks(spec, state): ) attestations = [attestation] + pre_gasprice = state.shard_states[shard].gasprice offset_slots = spec.get_offset_slots(state, shard) + assert len(offset_slots) == 1 yield from run_crosslinks_processing(spec, state, shard_transitions, attestations) shard_state = state.shard_states[shard] assert shard_state.slot == offset_slots[-1] assert shard_state.latest_block_root == shard_block.message.hash_tree_root() + assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1] + assert shard_state.gasprice > pre_gasprice + + +@with_all_phases_except(['phase0']) +@spec_state_test +@always_bls +def test_multiple_offset_slots(spec, state): + next_epoch(spec, state) + next_epoch(spec, state) + state = spec.upgrade_to_phase1(state) + next_slot(spec, state) + + committee_index = spec.CommitteeIndex(0) + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) + body = b'1' * spec.MAX_SHARD_BLOCK_SIZE + shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True) + shard_blocks = [shard_block] + + next_slots(spec, state, 3) + + shard_transition = spec.get_shard_transition(state, shard, shard_blocks) + shard_transitions = [spec.ShardTransition()] * len(state.shard_states) + shard_transitions[shard] = shard_transition + + attestation = get_valid_on_time_attestation( + spec, + state, + slot=state.slot, + index=committee_index, + shard_transition=shard_transition, + signed=True, + ) + attestations = [attestation] + + pre_gasprice = state.shard_states[shard].gasprice + offset_slots = spec.get_offset_slots(state, shard) + assert len(offset_slots) == 3 + + yield from run_crosslinks_processing(spec, state, shard_transitions, attestations) + + shard_state = state.shard_states[shard] + assert shard_state.slot == offset_slots[-1] + assert shard_state.latest_block_root == shard_block.message.hash_tree_root() + assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1] + assert shard_state.gasprice > pre_gasprice From 40483b587b1945eace6a1aeb187608782179e3a6 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 20 Apr 2020 16:57:29 +0800 Subject: [PATCH 24/53] [squashed] shard chain updates wip Use `ShardBlock` in `shard_state_transition` PR feedback 1. Rename `ShardState.data` -> `ShardState.transition_digest` 2. Rename `compute_shard_transition_data` to `compute_shard_transition_digest` 3. Add `assert state.slot > PHASE_1_GENESIS_SLOT` just in case, may move it later Add `get_post_shard_state` as a pure function wrapper --- specs/phase1/beacon-chain.md | 5 ++- specs/phase1/fraud-proofs.md | 61 +++++++++++++++++++++--------------- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 5c67fe4f2..1fdf7ec4c 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -328,7 +328,7 @@ class ShardBlockHeader(Container): class ShardState(Container): slot: Slot gasprice: Gwei - data: Bytes32 + transition_digest: Bytes32 latest_block_root: Root ``` @@ -701,6 +701,9 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None: ```python def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTransition) -> None: + # TODO: only need to check it once when phase 1 starts + assert state.slot > PHASE_1_GENESIS_SLOT + # Correct data root count offset_slots = get_offset_slots(state, shard) assert ( diff --git a/specs/phase1/fraud-proofs.md b/specs/phase1/fraud-proofs.md index c2178c10e..a5068cd07 100644 --- a/specs/phase1/fraud-proofs.md +++ b/specs/phase1/fraud-proofs.md @@ -37,30 +37,43 @@ This document describes the shard transition function and fraud proofs as part o ```python def shard_state_transition(beacon_state: BeaconState, shard_state: ShardState, - signed_block: SignedShardBlock) -> None: + block: ShardBlock) -> None: # Update shard state prev_gasprice = shard_state.gasprice - if len(signed_block.message.body) == 0: + if len(block.body) == 0: latest_block_root = shard_state.latest_block_root else: - latest_block_root = hash_tree_root(signed_block.message) + latest_block_root = hash_tree_root(block) - shard_state.data = compute_shard_transition_data( + shard_state.transition_digest = compute_shard_transition_digest( beacon_state, shard_state, - signed_block.message.beacon_parent_root, - signed_block.message.body, + block.beacon_parent_root, + block.body, ) - shard_state.gasprice = compute_updated_gasprice(prev_gasprice, len(signed_block.message.body)) - shard_state.slot = signed_block.message.slot + shard_state.gasprice = compute_updated_gasprice(prev_gasprice, len(block.body)) + shard_state.slot = block.slot shard_state.latest_block_root = latest_block_root ``` ```python -def compute_shard_transition_data(beacon_state: BeaconState, - shard_state: ShardState, - beacon_parent_root: Root, - shard_body_root: Root) -> Bytes32: +def get_post_shard_state(beacon_state: BeaconState, + shard_state: ShardState, + block: ShardBlock) -> ShardState: + """ + A pure function that returns a new post ShardState instead of modifying the given `shard_state`. + """ + post_state = shard_state.copy() + shard_state_transition(beacon_state, post_state, block) + return post_state +``` + +```python +def compute_shard_transition_digest(beacon_state: BeaconState, + shard_state: ShardState, + beacon_parent_root: Root, + shard_body_root: Root) -> Bytes32: + # TODO: use SSZ hash tree root return hash( hash_tree_root(shard_state) + beacon_parent_root + shard_body_root ) @@ -107,25 +120,25 @@ def is_valid_fraud_proof(beacon_state: BeaconState, attestation: Attestation, offset_index: uint64, transition: ShardTransition, - signed_block: SignedShardBlock, + block: ShardBlock, subkey: BLSPubkey, beacon_parent_block: BeaconBlock) -> bool: # 1. Check if `custody_bits[offset_index][j] != generate_custody_bit(subkey, block_contents)` for any `j`. shard = get_shard(beacon_state, attestation) custody_bits = attestation.custody_bits_blocks for j in range(custody_bits[offset_index]): - if custody_bits[offset_index][j] != generate_custody_bit(subkey, signed_block): + if custody_bits[offset_index][j] != generate_custody_bit(subkey, block): return True # 2. Check if the shard state transition result is wrong between # `transition.shard_states[offset_index - 1]` to `transition.shard_states[offset_index]`. if offset_index == 0: - shard_state = beacon_parent_block.shard_transitions[shard].shard_states[-1].copy() + shard_state = beacon_parent_block.shard_transitions[shard].shard_states[-1] else: - shard_state = transition.shard_states[offset_index - 1].copy() # Not doing the actual state updates here. + shard_state = transition.shard_states[offset_index - 1] # Not doing the actual state updates here. - shard_state_transition(beacon_state, shard_state, signed_block) - if shard_state.data != transition.shard_states[offset_index].data: + shard_state = get_post_shard_state(beacon_state, shard_state, block) + if shard_state.transition_digest != transition.shard_states[offset_index].transition_digest: return True return False @@ -168,15 +181,14 @@ def get_proposal_choices_at_slot(beacon_state: BeaconState, choices = [] shard_blocks_at_slot = [block for block in shard_blocks if block.message.slot == slot] for block in shard_blocks_at_slot: - temp_shard_state = shard_state.copy() # Not doing the actual state updates here. - # Try to apply state transition to temp_shard_state. try: # Verify block message and signature - assert verify_shard_block_message(beacon_state, temp_shard_state, block.message, slot, shard) + # TODO these validations should have been checked upon receiving shard blocks. + assert verify_shard_block_message(beacon_state, shard_state, block.message, slot, shard) if validate_signature: assert verify_shard_block_signature(beacon_state, block) - shard_state_transition(beacon_state, temp_shard_state, block) + shard_state = get_post_shard_state(beacon_state, shard_state, block.message) except Exception: pass # TODO: throw error in the test helper else: @@ -195,7 +207,6 @@ def get_proposal_at_slot(beacon_state: BeaconState, Return ``proposal``, ``shard_state`` of the given ``slot``. Note that this function doesn't change the state. """ - shard_state = shard_state.copy() # Don't update the given shard_state choices = get_proposal_choices_at_slot( beacon_state=beacon_state, shard_state=shard_state, @@ -213,7 +224,7 @@ def get_proposal_at_slot(beacon_state: BeaconState, proposal = get_winning_proposal(beacon_state, choices) # Apply state transition - shard_state_transition(beacon_state, shard_state, proposal) + shard_state = get_post_shard_state(beacon_state, shard_state, proposal.message) return proposal, shard_state ``` @@ -227,7 +238,7 @@ def get_shard_state_transition_result( ) -> Tuple[Sequence[SignedShardBlock], Sequence[ShardState], Sequence[Root]]: proposals = [] shard_states = [] - shard_state = beacon_state.shard_states[shard].copy() + shard_state = beacon_state.shard_states[shard] for slot in get_offset_slots(beacon_state, shard): proposal, shard_state = get_proposal_at_slot( beacon_state=beacon_state, From c8a473ba24e130bd44c2cd28b64ce58e23b1814a Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 28 Apr 2020 10:46:13 +0800 Subject: [PATCH 25/53] Apply suggestions from code review Co-Authored-By: Danny Ryan --- specs/phase1/beacon-chain.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 1fdf7ec4c..fc9687545 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -497,12 +497,11 @@ def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Seque source_epoch -= LIGHT_CLIENT_COMMITTEE_PERIOD active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) seed = get_seed(beacon_state, source_epoch, DOMAIN_LIGHT_CLIENT) - active_shard_count = get_active_shard_count(beacon_state) return compute_committee( indices=active_validator_indices, seed=seed, index=0, - count=active_shard_count, + count=get_active_shard_count(beacon_state), )[:TARGET_COMMITTEE_SIZE] ``` From 524ba166d13d954e5d512b44bb9a5855f6f77116 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 28 Apr 2020 11:28:21 +0800 Subject: [PATCH 26/53] [squashed] shard chain updates wip Fix wrong field names Fix `build_attestation_data` and other PR feedback from Danny and Terence 1. Rename `get_previous_slot` to `compute_previous_slot` 2. Break down `build_empty_block` into `get_state_and_beacon_parent_root_at_slot`, use it in `build_shard_block` 3. Set defult `slot` to `shard_state.slot + 1` in `build_shard_block` Update `verify_shard_block_message`: check beacon_parent_root at fork choice rule stage instead of state transition Fix `beacon-chain.md` 1. Fix typo `attestation.slot == state.slot` -> `attestation.data.slot == state.slot` in `is_winning_attestation` 2. Check `verify_shard_transition_false_positives` **after** `process_operations` 3. Fix `shard_attestations` filter in `process_crosslinks`: since attestations come from block, should use `attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY == state.slot` 4. [TBD] Allow empty `light_client_signature` to make the tests pass 5. [TBD] Add `is_shard_attestation`, filter out empty `ShardTransition()` Rework `test_process_crosslink` Add basic phase 1 `test_blocks` Add more test cases Revert `is_shard_attestation` and fix test cases backward compatibility. Remove `test_process_beacon_block_no_shard_transition` and consider it as invalid case. --- specs/phase1/beacon-chain.md | 41 +++-- specs/phase1/fraud-proofs.md | 3 +- specs/phase1/phase1-fork.md | 4 +- .../eth2spec/test/helpers/attestations.py | 60 +++++-- .../pyspec/eth2spec/test/helpers/block.py | 27 ++-- .../eth2spec/test/helpers/shard_block.py | 49 +++++- .../test/phase_0/sanity/test_blocks.py | 11 +- .../test_process_crosslink.py | 147 ++++++++---------- .../eth2spec/test/phase_1/sanity/__init__.py | 0 .../test/phase_1/sanity/test_blocks.py | 141 +++++++++++++++++ 10 files changed, 352 insertions(+), 131 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase_1/sanity/__init__.py create mode 100644 tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index fc9687545..611cc1955 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -33,7 +33,7 @@ - [`AttestationCustodyBitWrapper`](#attestationcustodybitwrapper) - [Helper functions](#helper-functions) - [Misc](#misc-1) - - [`get_previous_slot`](#get_previous_slot) + - [`compute_previous_slot`](#compute_previous_slot) - [`pack_compact_validator`](#pack_compact_validator) - [`unpack_compact_validator`](#unpack_compact_validator) - [`committee_to_compact_committee`](#committee_to_compact_committee) @@ -52,6 +52,7 @@ - [`get_latest_slot_for_shard`](#get_latest_slot_for_shard) - [`get_offset_slots`](#get_offset_slots) - [Predicates](#predicates) + - [`is_shard_attestation`](#is_shard_attestation) - [`is_winning_attestation`](#is_winning_attestation) - [Updated `is_valid_indexed_attestation`](#updated-is_valid_indexed_attestation) - [Block processing](#block-processing) @@ -369,10 +370,10 @@ class AttestationCustodyBitWrapper(Container): ### Misc -#### `get_previous_slot` +#### `compute_previous_slot` ```python -def get_previous_slot(slot: Slot) -> Slot: +def compute_previous_slot(slot: Slot) -> Slot: if slot > 0: return Slot(slot - 1) else: @@ -556,6 +557,21 @@ def get_offset_slots(state: BeaconState, shard: Shard) -> Sequence[Slot]: ### Predicates +#### `is_shard_attestation` + +```python +def is_shard_attestation(state: BeaconState, + attestation: Attestation, + committee_index: CommitteeIndex) -> bool: + if not ( + attestation.data.index == committee_index + and attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY == state.slot + ): + return False + + return True +``` + #### `is_winning_attestation` ```python @@ -568,7 +584,7 @@ def is_winning_attestation(state: BeaconState, ``winning_root`` formed by ``committee_index`` committee at the current slot. """ return ( - attestation.slot == state.slot + attestation.data.slot == state.slot and attestation.data.index == committee_index and attestation.data.shard_transition_root == winning_root ) @@ -623,9 +639,9 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: process_block_header(state, block) process_randao(state, block.body) process_eth1_data(state, block.body) - verify_shard_transition_false_positives(state, block.body) process_light_client_signatures(state, block.body) process_operations(state, block.body) + verify_shard_transition_false_positives(state, block.body) ``` #### Operations @@ -684,7 +700,7 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None: # Correct data root count assert len(attestation.custody_bits_blocks) == len(get_offset_slots(state, shard)) # Correct parent block root - assert data.beacon_block_root == get_block_root_at_slot(state, get_previous_slot(state.slot)) + assert data.beacon_block_root == get_block_root_at_slot(state, compute_previous_slot(state.slot)) # Type 2: no shard transition, no custody bits else: # Ensure delayed attestation @@ -821,11 +837,12 @@ def process_crosslinks(state: BeaconState, for committee_index in map(CommitteeIndex, range(committee_count)): shard = compute_shard_from_committee_index(state, committee_index, state.slot) # All attestations in the block for this committee/shard and current slot + shard_transition = shard_transitions[shard] shard_attestations = [ attestation for attestation in attestations - if attestation.data.index == committee_index and attestation.data.slot == state.slot + if is_shard_attestation(state, attestation, committee_index) ] - shard_transition = shard_transitions[shard] + winning_root = process_crosslink_for_shard(state, committee_index, shard_transition, shard_attestations) if winning_root != Root(): # Mark relevant pending attestations as creating a successful crosslink @@ -920,10 +937,14 @@ def process_light_client_signatures(state: BeaconState, block_body: BeaconBlockB increase_balance(state, get_beacon_proposer_index(state), Gwei(total_reward // PROPOSER_REWARD_QUOTIENT)) - slot = get_previous_slot(state.slot) + slot = compute_previous_slot(state.slot) signing_root = compute_signing_root(get_block_root_at_slot(state, slot), get_domain(state, DOMAIN_LIGHT_CLIENT, compute_epoch_at_slot(slot))) - assert bls.FastAggregateVerify(signer_pubkeys, signing_root, signature=block_body.light_client_signature) + if len(signer_pubkeys) == 0: + # TODO: Or disallow empty light_client_signature? + return + else: + assert bls.FastAggregateVerify(signer_pubkeys, signing_root, signature=block_body.light_client_signature) ``` ### Epoch transition diff --git a/specs/phase1/fraud-proofs.md b/specs/phase1/fraud-proofs.md index a5068cd07..c676e142a 100644 --- a/specs/phase1/fraud-proofs.md +++ b/specs/phase1/fraud-proofs.md @@ -86,7 +86,6 @@ def verify_shard_block_message(beacon_state: BeaconState, slot: Slot, shard: Shard) -> bool: assert block.shard_parent_root == shard_state.latest_block_root - assert block.beacon_parent_root == get_block_root_at_slot(beacon_state, slot) assert block.slot == slot assert block.proposer_index == get_shard_proposer_index(beacon_state, slot, shard) assert 0 < len(block.body) <= MAX_SHARD_BLOCK_SIZE @@ -124,7 +123,6 @@ def is_valid_fraud_proof(beacon_state: BeaconState, subkey: BLSPubkey, beacon_parent_block: BeaconBlock) -> bool: # 1. Check if `custody_bits[offset_index][j] != generate_custody_bit(subkey, block_contents)` for any `j`. - shard = get_shard(beacon_state, attestation) custody_bits = attestation.custody_bits_blocks for j in range(custody_bits[offset_index]): if custody_bits[offset_index][j] != generate_custody_bit(subkey, block): @@ -133,6 +131,7 @@ def is_valid_fraud_proof(beacon_state: BeaconState, # 2. Check if the shard state transition result is wrong between # `transition.shard_states[offset_index - 1]` to `transition.shard_states[offset_index]`. if offset_index == 0: + shard = get_shard(beacon_state, attestation) shard_state = beacon_parent_block.shard_transitions[shard].shard_states[-1] else: shard_state = transition.shard_states[offset_index - 1] # Not doing the actual state updates here. diff --git a/specs/phase1/phase1-fork.md b/specs/phase1/phase1-fork.md index e95c12c72..cc7d8f33e 100644 --- a/specs/phase1/phase1-fork.md +++ b/specs/phase1/phase1-fork.md @@ -103,7 +103,7 @@ def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState: ShardState( slot=pre.slot, gasprice=MIN_GASPRICE, - data=Root(), + transition_digest=Root(), latest_block_root=Root(), ) for i in range(INITIAL_ACTIVE_SHARDS) ), @@ -111,7 +111,7 @@ def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState: current_light_committee=CompactCommittee(), # computed after state creation next_light_committee=CompactCommittee(), # Custody game - custody_challenge_index=0, + exposed_derived_secrets=[] * EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS, # exposed_derived_secrets will fully default to zeroes ) next_epoch = Epoch(epoch + 1) diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index c203b737a..9e98e83ea 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -1,7 +1,7 @@ from typing import List from eth2spec.test.context import expect_assertion_error, PHASE0, PHASE1 -from eth2spec.test.helpers.state import state_transition_and_sign_block +from eth2spec.test.helpers.state import state_transition_and_sign_block, next_slot, transition_to from eth2spec.test.helpers.block import build_empty_block_for_next_slot from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls @@ -43,7 +43,7 @@ def run_attestation_processing(spec, state, attestation, valid=True): yield 'post', state -def build_attestation_data(spec, state, slot, index, shard_transition=None): +def build_attestation_data(spec, state, slot, index, shard_transition=None, on_time=True): assert state.slot >= slot if slot == state.slot: @@ -66,28 +66,42 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None): source_epoch = state.current_justified_checkpoint.epoch source_root = state.current_justified_checkpoint.root - if spec.fork == PHASE1 and shard_transition is not None: - shard_transition_root = shard_transition.hash_tree_root() - head_shard_root = shard_transition.shard_data_roots[len(shard_transition.shard_data_roots) - 1] - else: - shard_transition_root = spec.Root() - head_shard_root = spec.Root() - - return spec.AttestationData( + attestation_data = spec.AttestationData( slot=slot, index=index, beacon_block_root=block_root, source=spec.Checkpoint(epoch=source_epoch, root=source_root), target=spec.Checkpoint(epoch=spec.compute_epoch_at_slot(slot), root=epoch_boundary_root), - head_shard_root=head_shard_root, - shard_transition_root=shard_transition_root, ) + if spec.fork == PHASE1: + if shard_transition is not None: + lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1 + attestation_data.head_shard_root = shard_transition.shard_data_roots[lastest_shard_data_root_index] + attestation_data.shard_transition_root = shard_transition.hash_tree_root() + else: + # No shard transition + shard = spec.get_shard(state, spec.Attestation(data=attestation_data)) + if on_time: + temp_state = state.copy() + next_slot(spec, temp_state) + shard_transition = spec.get_shard_transition(temp_state, shard, []) + lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1 + attestation_data.head_shard_root = shard_transition.shard_data_roots[lastest_shard_data_root_index] + attestation_data.shard_transition_root = shard_transition.hash_tree_root() + else: + attestation_data.head_shard_root = state.shard_states[shard].transition_digest + attestation_data.shard_transition_root = spec.Root() + return attestation_data + def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False): shard = spec.get_shard(state, attestation) - offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), state.slot + 1) - for offset_slot in offset_slots: + offset_slots = spec.compute_offset_slots( + spec.get_latest_slot_for_shard(state, shard), + attestation.data.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY, + ) + for _ in offset_slots: attestation.custody_bits_blocks.append( Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits]) ) @@ -143,7 +157,9 @@ def get_valid_attestation(spec, if index is None: index = 0 - attestation_data = build_attestation_data(spec, state, slot=slot, index=index, shard_transition=shard_transition) + attestation_data = build_attestation_data( + spec, state, slot=slot, index=index, shard_transition=shard_transition, on_time=on_time + ) beacon_committee = spec.get_beacon_committee( state, @@ -298,7 +314,21 @@ def next_epoch_with_attestations(spec, spec, post_state, slot_to_attest, index=index, signed=True, on_time=False) block.body.attestations.append(prev_attestation) + if spec.fork == PHASE1: + fill_block_shard_transitions_by_attestations(spec, post_state, block) + signed_block = state_transition_and_sign_block(spec, post_state, block) signed_blocks.append(signed_block) return state, signed_blocks, post_state + + +def fill_block_shard_transitions_by_attestations(spec, state, block): + block.body.shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS + for attestation in block.body.attestations: + shard = spec.get_shard(state, attestation) + if attestation.data.slot == state.slot: + temp_state = state.copy() + transition_to(spec, temp_state, slot=block.slot) + shard_transition = spec.get_shard_transition(temp_state, shard, []) + block.body.shard_transitions[shard] = shard_transition diff --git a/tests/core/pyspec/eth2spec/test/helpers/block.py b/tests/core/pyspec/eth2spec/test/helpers/block.py index 96cc30e35..e40d5e7d8 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/block.py @@ -71,24 +71,31 @@ def build_empty_block(spec, state, slot=None): """ if slot is None: slot = state.slot - if slot < state.slot: - raise Exception("build_empty_block cannot build blocks for past slots") - if slot > state.slot: - # transition forward in copied state to grab relevant data from state - state = state.copy() - spec.process_slots(state, slot) + state, parent_block_root = get_state_and_beacon_parent_root_at_slot(spec, state, slot) empty_block = spec.BeaconBlock() empty_block.slot = slot empty_block.proposer_index = spec.get_beacon_proposer_index(state) empty_block.body.eth1_data.deposit_count = state.eth1_deposit_index - previous_block_header = state.latest_block_header.copy() - if previous_block_header.state_root == spec.Root(): - previous_block_header.state_root = hash_tree_root(state) - empty_block.parent_root = hash_tree_root(previous_block_header) + empty_block.parent_root = parent_block_root apply_randao_reveal(spec, state, empty_block) return empty_block def build_empty_block_for_next_slot(spec, state): return build_empty_block(spec, state, state.slot + 1) + + +def get_state_and_beacon_parent_root_at_slot(spec, state, slot): + if slot < state.slot: + raise Exception("Cannot build blocks for past slots") + if slot > state.slot: + # transition forward in copied state to grab relevant data from state + state = state.copy() + spec.process_slots(state, slot) + + previous_block_header = state.latest_block_header.copy() + if previous_block_header.state_root == spec.Root(): + previous_block_header.state_root = hash_tree_root(state) + beacon_parent_root = hash_tree_root(previous_block_header) + return state, beacon_parent_root diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py index 25d868b65..922f4d748 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py @@ -1,3 +1,6 @@ +from eth2spec.test.helpers.attestations import get_valid_on_time_attestation +from eth2spec.test.helpers.block import get_state_and_beacon_parent_root_at_slot +from eth2spec.test.helpers.state import next_slots from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls from eth2spec.utils.bls import only_with_bls @@ -18,21 +21,22 @@ def sign_shard_block(spec, beacon_state, shard, block, proposer_index=None): def build_shard_block(spec, beacon_state, shard, - slot, + slot=None, body=None, signed=False): shard_state = beacon_state.shard_states[shard] if slot is None: - slot = shard_state.slot + slot = shard_state.slot + 1 if body is None: - body = [] + body = b'\x56' * 128 proposer_index = spec.get_shard_proposer_index(beacon_state, slot, shard) + beacon_state, beacon_parent_root = get_state_and_beacon_parent_root_at_slot(spec, beacon_state, slot) block = spec.ShardBlock( shard_parent_root=shard_state.latest_block_root, - beacon_parent_root=spec.get_block_root_at_slot(beacon_state, spec.get_previous_slot(slot)), + beacon_parent_root=beacon_parent_root, slot=slot, proposer_index=proposer_index, body=body, @@ -45,3 +49,40 @@ def build_shard_block(spec, sign_shard_block(spec, beacon_state, shard, signed_block, proposer_index=proposer_index) return signed_block + + +def build_shard_transitions_till_slot(spec, state, shards, shard_blocks, target_len_offset_slot): + state = state.copy() + next_slots(spec, state, target_len_offset_slot) + shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS + for shard in shards: + offset_slots = spec.get_offset_slots(state, shard) + len_offset_slots = len(offset_slots) + assert len_offset_slots == target_len_offset_slot + shard_blocks_of_shard = shard_blocks[shard] + shard_transition = spec.get_shard_transition(state, shard, shard_blocks_of_shard) + if len(shard_blocks_of_shard) > 0: + shard_block_root = shard_blocks_of_shard[-1].message.hash_tree_root() + assert shard_transition.shard_states[len_offset_slots - 1].latest_block_root == shard_block_root + assert shard_transition.shard_states[len_offset_slots - 1].slot == offset_slots[-1] + shard_transitions[shard] = shard_transition + + return shard_transitions + + +def build_attestation_with_shard_transition(spec, state, slot, index, target_len_offset_slot, shard_transition=None): + state = state.copy() + next_slots(spec, state, target_len_offset_slot) + attestation = get_valid_on_time_attestation( + spec, + state, + slot=slot, + index=index, + shard_transition=shard_transition, + signed=True, + ) + assert attestation.data.slot == slot + if shard_transition is not None: + assert target_len_offset_slot == len(shard_transition.shard_states) + assert attestation.data.shard_transition_root == shard_transition.hash_tree_root() + return attestation diff --git a/tests/core/pyspec/eth2spec/test/phase_0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase_0/sanity/test_blocks.py index b6b671872..29a9dcca2 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/sanity/test_blocks.py @@ -8,10 +8,13 @@ from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_e from eth2spec.test.helpers.keys import privkeys, pubkeys from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing, get_indexed_attestation_participants from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing -from eth2spec.test.helpers.attestations import get_valid_attestation +from eth2spec.test.helpers.attestations import get_valid_attestation, fill_block_shard_transitions_by_attestations from eth2spec.test.helpers.deposits import prepare_state_and_deposit -from eth2spec.test.context import spec_state_test, with_all_phases, expect_assertion_error, always_bls, with_phases +from eth2spec.test.context import ( + spec_state_test, with_all_phases, expect_assertion_error, always_bls, with_phases, + PHASE1 +) @with_all_phases @@ -420,12 +423,14 @@ def test_attestation(spec, state): yield 'pre', state - attestation = get_valid_attestation(spec, state, signed=True) + attestation = get_valid_attestation(spec, state, signed=True, on_time=True) # Add to state via block transition pre_current_attestations_len = len(state.current_epoch_attestations) attestation_block = build_empty_block(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) attestation_block.body.attestations.append(attestation) + if spec.fork == PHASE1: + fill_block_shard_transitions_by_attestations(spec, state, attestation_block) signed_attestation_block = state_transition_and_sign_block(spec, state, attestation_block) assert len(state.current_epoch_attestations) == pre_current_attestations_len + 1 diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py index 6d1e65678..912770da8 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py @@ -1,101 +1,78 @@ from eth2spec.test.context import ( + PHASE0, with_all_phases_except, spec_state_test, always_bls, ) -from eth2spec.test.helpers.attestations import ( - get_valid_on_time_attestation, +from eth2spec.test.helpers.crosslinks import run_crosslinks_processing +from eth2spec.test.helpers.shard_block import ( + build_attestation_with_shard_transition, + build_shard_block, + build_shard_transitions_till_slot, ) -from eth2spec.test.helpers.crosslinks import ( - run_crosslinks_processing, -) -from eth2spec.test.helpers.shard_block import build_shard_block -from eth2spec.test.helpers.state import next_epoch, next_slot, next_slots +from eth2spec.test.helpers.state import next_epoch, next_slot, transition_to -@with_all_phases_except(['phase0']) +def run_basic_crosslink_tests(spec, state, target_len_offset_slot): + next_epoch(spec, state) + next_epoch(spec, state) + state = spec.upgrade_to_phase1(state) + next_slot(spec, state) + + # At the beginning, let `x = state.slot`, `state.shard_states[shard].slot == x - 1` + slot_x = state.slot + committee_index = spec.CommitteeIndex(0) + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) + assert state.shard_states[shard].slot == slot_x - 1 + + # Create SignedShardBlock at slot `shard_state.slot + 1` -> x + body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE + shard_block = build_shard_block(spec, state, shard, body=body, signed=True) + shard_blocks = [shard_block] + + # Attester creates `attestation` at slot x + # Use temporary next state to get ShardTransition of shard block + shard_transitions = build_shard_transitions_till_slot( + spec, + state, + shards=[shard, ], + shard_blocks={shard: shard_blocks}, + target_len_offset_slot=target_len_offset_slot, + ) + shard_transition = shard_transitions[shard] + attestation = build_attestation_with_shard_transition( + spec, + state, + slot=slot_x + target_len_offset_slot - 1, + index=committee_index, + target_len_offset_slot=target_len_offset_slot, + shard_transition=shard_transition, + ) + pre_gasprice = state.shard_states[shard].gasprice + + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + pre_shard_state = state.shard_states[shard] + yield from run_crosslinks_processing(spec, state, shard_transitions, [attestation]) + + # After state transition, + assert state.slot == slot_x + target_len_offset_slot + shard_state = state.shard_states[shard] + assert shard_state != pre_shard_state + assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1] + + if target_len_offset_slot == 1: + assert shard_state.gasprice > pre_gasprice + + +@with_all_phases_except([PHASE0]) @spec_state_test @always_bls def test_basic_crosslinks(spec, state): - next_epoch(spec, state) - next_epoch(spec, state) - state = spec.upgrade_to_phase1(state) - next_slot(spec, state) - - committee_index = spec.CommitteeIndex(0) - shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) - body = b'1' * spec.MAX_SHARD_BLOCK_SIZE - shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True) - shard_blocks = [shard_block] - - next_slot(spec, state) - - shard_transition = spec.get_shard_transition(state, shard, shard_blocks) - shard_transitions = [spec.ShardTransition()] * len(state.shard_states) - shard_transitions[shard] = shard_transition - - attestation = get_valid_on_time_attestation( - spec, - state, - slot=state.slot, - index=committee_index, - shard_transition=shard_transition, - signed=True, - ) - attestations = [attestation] - - pre_gasprice = state.shard_states[shard].gasprice - offset_slots = spec.get_offset_slots(state, shard) - assert len(offset_slots) == 1 - - yield from run_crosslinks_processing(spec, state, shard_transitions, attestations) - - shard_state = state.shard_states[shard] - assert shard_state.slot == offset_slots[-1] - assert shard_state.latest_block_root == shard_block.message.hash_tree_root() - assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1] - assert shard_state.gasprice > pre_gasprice + run_basic_crosslink_tests(spec, state, target_len_offset_slot=1) -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test @always_bls def test_multiple_offset_slots(spec, state): - next_epoch(spec, state) - next_epoch(spec, state) - state = spec.upgrade_to_phase1(state) - next_slot(spec, state) - - committee_index = spec.CommitteeIndex(0) - shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) - body = b'1' * spec.MAX_SHARD_BLOCK_SIZE - shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True) - shard_blocks = [shard_block] - - next_slots(spec, state, 3) - - shard_transition = spec.get_shard_transition(state, shard, shard_blocks) - shard_transitions = [spec.ShardTransition()] * len(state.shard_states) - shard_transitions[shard] = shard_transition - - attestation = get_valid_on_time_attestation( - spec, - state, - slot=state.slot, - index=committee_index, - shard_transition=shard_transition, - signed=True, - ) - attestations = [attestation] - - pre_gasprice = state.shard_states[shard].gasprice - offset_slots = spec.get_offset_slots(state, shard) - assert len(offset_slots) == 3 - - yield from run_crosslinks_processing(spec, state, shard_transitions, attestations) - - shard_state = state.shard_states[shard] - assert shard_state.slot == offset_slots[-1] - assert shard_state.latest_block_root == shard_block.message.hash_tree_root() - assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1] - assert shard_state.gasprice > pre_gasprice + run_basic_crosslink_tests(spec, state, target_len_offset_slot=3) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/sanity/__init__.py b/tests/core/pyspec/eth2spec/test/phase_1/sanity/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py new file mode 100644 index 000000000..168915d02 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py @@ -0,0 +1,141 @@ +from eth2spec.test.context import ( + PHASE0, + with_all_phases_except, + spec_state_test, + always_bls, +) +from eth2spec.test.helpers.block import build_empty_block +from eth2spec.test.helpers.shard_block import ( + build_attestation_with_shard_transition, + build_shard_block, + build_shard_transitions_till_slot, +) +from eth2spec.test.helpers.state import next_epoch, next_slot, state_transition_and_sign_block + + +@with_all_phases_except([PHASE0]) +@spec_state_test +@always_bls +def test_process_beacon_block_with_normal_shard_transition(spec, state): + next_epoch(spec, state) + next_epoch(spec, state) + state = spec.upgrade_to_phase1(state) + next_slot(spec, state) + + target_len_offset_slot = 1 + + # At the beginning, let `x = state.slot`, `state.shard_states[shard].slot == x - 1` + slot_x = state.slot + committee_index = spec.CommitteeIndex(0) + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) + assert state.shard_states[shard].slot == slot_x - 1 + + # Create SignedShardBlock at slot `shard_state.slot + 1` -> x + body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE + shard_block = build_shard_block(spec, state, shard, body=body, signed=True) + shard_blocks = [shard_block] + + # Attester creates `attestation` at slot x + # Use temporary next state to get ShardTransition of shard block + shard_transitions = build_shard_transitions_till_slot( + spec, + state, + shards=[shard, ], + shard_blocks={shard: shard_blocks}, + target_len_offset_slot=target_len_offset_slot, + ) + shard_transition = shard_transitions[shard] + attestation = build_attestation_with_shard_transition( + spec, + state, + slot=slot_x + target_len_offset_slot - 1, + index=committee_index, + target_len_offset_slot=target_len_offset_slot, + shard_transition=shard_transition, + ) + pre_gasprice = state.shard_states[shard].gasprice + + # Propose beacon block at slot `x + 1` + pre_shard_state = state.shard_states[shard] + beacon_block_1 = build_empty_block(spec, state, slot=slot_x + target_len_offset_slot) + beacon_block_1.body.attestations = [attestation] + beacon_block_1.body.shard_transitions = shard_transitions + assert ( + beacon_block_1.slot == slot_x + target_len_offset_slot + == shard_transition.shard_states[0].slot + target_len_offset_slot + ) + state_transition_and_sign_block(spec, state, beacon_block_1) + + # After state transition + assert state.slot == slot_x + target_len_offset_slot + shard_state = state.shard_states[shard] + # latest_block_root has changed + assert shard_state.latest_block_root == shard_block.message.hash_tree_root() + assert shard_state != pre_shard_state + assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1] + + if target_len_offset_slot == 1 and len(shard_blocks) > 0: + assert shard_state.gasprice > pre_gasprice + + +@with_all_phases_except([PHASE0]) +@spec_state_test +@always_bls +def test_process_beacon_block_with_empty_proposal_transition(spec, state): + next_epoch(spec, state) + next_epoch(spec, state) + state = spec.upgrade_to_phase1(state) + next_slot(spec, state) + + target_len_offset_slot = 1 + + # At the beginning, let `x = state.slot`, `state.shard_states[shard].slot == x - 1` + slot_x = state.slot + committee_index = spec.CommitteeIndex(0) + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) + assert state.shard_states[shard].slot == slot_x - 1 + + # No new shard block + shard_blocks = [] + + # Attester creates `attestation` at slot x + # Use temporary next state to get ShardTransition of shard block + shard_transitions = build_shard_transitions_till_slot( + spec, + state, + shards=[shard, ], + shard_blocks={shard: shard_blocks}, + target_len_offset_slot=target_len_offset_slot, + ) + shard_transition = shard_transitions[shard] + attestation = build_attestation_with_shard_transition( + spec, + state, + slot=slot_x + target_len_offset_slot - 1, + index=committee_index, + target_len_offset_slot=target_len_offset_slot, + shard_transition=shard_transition, + ) + pre_gasprice = state.shard_states[shard].gasprice + + # Propose beacon block at slot `x + 1` + pre_shard_state = state.shard_states[shard] + beacon_block_1 = build_empty_block(spec, state, slot=slot_x + target_len_offset_slot) + beacon_block_1.body.attestations = [attestation] + beacon_block_1.body.shard_transitions = shard_transitions + assert ( + beacon_block_1.slot == slot_x + target_len_offset_slot + == shard_transition.shard_states[0].slot + target_len_offset_slot + ) + state_transition_and_sign_block(spec, state, beacon_block_1) + + # After state transition + assert state.slot == slot_x + target_len_offset_slot + shard_state = state.shard_states[shard] + # latest_block_root hasn't changed + assert shard_state.latest_block_root == pre_shard_state.latest_block_root + assert shard_state != pre_shard_state + assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1] + + if target_len_offset_slot == 1 and len(shard_blocks) > 0: + assert shard_state.gasprice > pre_gasprice From e758fb76c2ee67afa63a581c5b03022c6a739875 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sat, 2 May 2020 01:39:03 +0800 Subject: [PATCH 27/53] Check `head_shard_root` of all `transition_attestations` --- specs/phase1/beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 611cc1955..f58a95fb7 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -788,6 +788,9 @@ def process_crosslink_for_shard(state: BeaconState, for attestation in transition_attestations: participants = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) transition_participants = transition_participants.union(participants) + assert attestation.data.head_shard_root == shard_transition.shard_data_roots[ + len(shard_transition.shard_data_roots) - 1 + ] enough_online_stake = ( get_total_balance(state, online_indices.intersection(transition_participants)) * 3 >= @@ -799,9 +802,6 @@ def process_crosslink_for_shard(state: BeaconState, # Attestation <-> shard transition consistency assert shard_transition_root == hash_tree_root(shard_transition) - assert attestation.data.head_shard_root == shard_transition.shard_data_roots[ - len(shard_transition.shard_data_roots) - 1 - ] # Apply transition apply_shard_transition(state, shard, shard_transition) From ff850251130c807b1403a5c9a9cfd0ef17a28a01 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sat, 2 May 2020 01:56:25 +0800 Subject: [PATCH 28/53] PR feedback from terence --- specs/phase1/beacon-chain.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index f58a95fb7..c9d8eeb54 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -735,8 +735,12 @@ def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTr shard_parent_root = state.shard_states[shard].latest_block_root for i in range(len(offset_slots)): shard_block_length = transition.shard_block_lengths[i] - is_empty_proposal = shard_block_length == 0 shard_state = transition.shard_states[i] + # Verify correct calculation of gas prices and slots + assert shard_state.gasprice == compute_updated_gasprice(prev_gasprice, shard_block_length) + assert shard_state.slot == offset_slots[i] + # Collect the non-empty proposals result + is_empty_proposal = shard_block_length == 0 if not is_empty_proposal: proposal_index = get_shard_proposer_index(state, offset_slots[i], shard) # Reconstruct shard headers @@ -751,9 +755,6 @@ def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTr headers.append(header) proposers.append(proposal_index) - # Verify correct calculation of gas prices and slots - assert shard_state.gasprice == compute_updated_gasprice(prev_gasprice, shard_block_length) - assert shard_state.slot == offset_slots[i] prev_gasprice = shard_state.gasprice pubkeys = [state.validators[proposer].pubkey for proposer in proposers] From 977cd73379e383cb4f1b08d342f0d790ab513915 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sat, 2 May 2020 01:57:34 +0800 Subject: [PATCH 29/53] Refactor the tests --- .../eth2spec/test/helpers/shard_block.py | 33 ++-- .../pyspec/eth2spec/test/helpers/state.py | 10 ++ .../test_process_crosslink.py | 47 +++--- .../test/phase_1/sanity/test_blocks.py | 154 +++++++----------- 4 files changed, 106 insertions(+), 138 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py index 922f4d748..ef65d2427 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py @@ -1,6 +1,6 @@ from eth2spec.test.helpers.attestations import get_valid_on_time_attestation from eth2spec.test.helpers.block import get_state_and_beacon_parent_root_at_slot -from eth2spec.test.helpers.state import next_slots +from eth2spec.test.helpers.state import transition_to from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls from eth2spec.utils.bls import only_with_bls @@ -51,18 +51,17 @@ def build_shard_block(spec, return signed_block -def build_shard_transitions_till_slot(spec, state, shards, shard_blocks, target_len_offset_slot): - state = state.copy() - next_slots(spec, state, target_len_offset_slot) +def build_shard_transitions_till_slot(spec, state, shard_blocks, on_time_slot): + temp_state = state.copy() + transition_to(spec, temp_state, on_time_slot) shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS - for shard in shards: - offset_slots = spec.get_offset_slots(state, shard) + for shard, blocks in shard_blocks.items(): + offset_slots = spec.get_offset_slots(temp_state, shard) len_offset_slots = len(offset_slots) - assert len_offset_slots == target_len_offset_slot - shard_blocks_of_shard = shard_blocks[shard] - shard_transition = spec.get_shard_transition(state, shard, shard_blocks_of_shard) - if len(shard_blocks_of_shard) > 0: - shard_block_root = shard_blocks_of_shard[-1].message.hash_tree_root() + assert len_offset_slots == on_time_slot - state.shard_states[shard].slot - 1 + shard_transition = spec.get_shard_transition(temp_state, shard, blocks) + if len(blocks) > 0: + shard_block_root = blocks[-1].message.hash_tree_root() assert shard_transition.shard_states[len_offset_slots - 1].latest_block_root == shard_block_root assert shard_transition.shard_states[len_offset_slots - 1].slot == offset_slots[-1] shard_transitions[shard] = shard_transition @@ -70,19 +69,17 @@ def build_shard_transitions_till_slot(spec, state, shards, shard_blocks, target_ return shard_transitions -def build_attestation_with_shard_transition(spec, state, slot, index, target_len_offset_slot, shard_transition=None): - state = state.copy() - next_slots(spec, state, target_len_offset_slot) +def build_attestation_with_shard_transition(spec, state, index, on_time_slot, shard_transition=None): + temp_state = state.copy() + transition_to(spec, temp_state, on_time_slot - 1) attestation = get_valid_on_time_attestation( spec, - state, - slot=slot, + temp_state, index=index, shard_transition=shard_transition, signed=True, ) - assert attestation.data.slot == slot + assert attestation.data.slot == temp_state.slot if shard_transition is not None: - assert target_len_offset_slot == len(shard_transition.shard_states) assert attestation.data.shard_transition_root == shard_transition.hash_tree_root() return attestation diff --git a/tests/core/pyspec/eth2spec/test/helpers/state.py b/tests/core/pyspec/eth2spec/test/helpers/state.py index 46a7ce2b5..f08f22602 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/state.py +++ b/tests/core/pyspec/eth2spec/test/helpers/state.py @@ -30,6 +30,16 @@ def transition_to(spec, state, slot): assert state.slot == slot +def transition_to_valid_shard_slot(spec, state): + """ + Transition to slot `spec.PHASE_1_GENESIS_SLOT + 1` and fork at `spec.PHASE_1_GENESIS_SLOT`. + """ + transition_to(spec, state, spec.PHASE_1_GENESIS_SLOT) + state = spec.upgrade_to_phase1(state) # `upgrade_to_phase1` is a pure function + next_slot(spec, state) + return state + + def next_epoch(spec, state): """ Transition to the start slot of the next epoch diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py index 912770da8..1f066b344 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py @@ -10,69 +10,64 @@ from eth2spec.test.helpers.shard_block import ( build_shard_block, build_shard_transitions_till_slot, ) -from eth2spec.test.helpers.state import next_epoch, next_slot, transition_to +from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot -def run_basic_crosslink_tests(spec, state, target_len_offset_slot): - next_epoch(spec, state) - next_epoch(spec, state) - state = spec.upgrade_to_phase1(state) - next_slot(spec, state) - +def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True): + state = transition_to_valid_shard_slot(spec, state) # At the beginning, let `x = state.slot`, `state.shard_states[shard].slot == x - 1` slot_x = state.slot committee_index = spec.CommitteeIndex(0) shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) assert state.shard_states[shard].slot == slot_x - 1 - # Create SignedShardBlock at slot `shard_state.slot + 1` -> x + # Create SignedShardBlock body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE shard_block = build_shard_block(spec, state, shard, body=body, signed=True) shard_blocks = [shard_block] - - # Attester creates `attestation` at slot x - # Use temporary next state to get ShardTransition of shard block + # Create a shard_transitions that would be included at beacon block `state.slot + target_len_offset_slot` shard_transitions = build_shard_transitions_till_slot( spec, state, - shards=[shard, ], shard_blocks={shard: shard_blocks}, - target_len_offset_slot=target_len_offset_slot, + on_time_slot=state.slot + target_len_offset_slot, ) shard_transition = shard_transitions[shard] + # Create an attestation that would be included at beacon block `state.slot + target_len_offset_slot` attestation = build_attestation_with_shard_transition( spec, state, - slot=slot_x + target_len_offset_slot - 1, index=committee_index, - target_len_offset_slot=target_len_offset_slot, + on_time_slot=state.slot + target_len_offset_slot, shard_transition=shard_transition, ) pre_gasprice = state.shard_states[shard].gasprice - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + transition_to(spec, state, state.slot + target_len_offset_slot) pre_shard_state = state.shard_states[shard] - yield from run_crosslinks_processing(spec, state, shard_transitions, [attestation]) - # After state transition, - assert state.slot == slot_x + target_len_offset_slot - shard_state = state.shard_states[shard] - assert shard_state != pre_shard_state - assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1] + yield from run_crosslinks_processing(spec, state, shard_transitions, [attestation], valid=valid) - if target_len_offset_slot == 1: - assert shard_state.gasprice > pre_gasprice + if valid: + # After state transition, + assert state.slot == slot_x + target_len_offset_slot + shard_state = state.shard_states[shard] + assert shard_state != pre_shard_state + assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1] + + if target_len_offset_slot == 1: + assert shard_state.gasprice > pre_gasprice @with_all_phases_except([PHASE0]) @spec_state_test @always_bls def test_basic_crosslinks(spec, state): - run_basic_crosslink_tests(spec, state, target_len_offset_slot=1) + yield from run_basic_crosslink_tests(spec, state, target_len_offset_slot=1, valid=True) @with_all_phases_except([PHASE0]) @spec_state_test @always_bls def test_multiple_offset_slots(spec, state): - run_basic_crosslink_tests(spec, state, target_len_offset_slot=3) + yield from run_basic_crosslink_tests(spec, state, target_len_offset_slot=3, valid=True) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py index 168915d02..60af35d45 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py @@ -1,3 +1,5 @@ +from typing import Dict, Sequence + from eth2spec.test.context import ( PHASE0, with_all_phases_except, @@ -10,69 +12,74 @@ from eth2spec.test.helpers.shard_block import ( build_shard_block, build_shard_transitions_till_slot, ) -from eth2spec.test.helpers.state import next_epoch, next_slot, state_transition_and_sign_block +from eth2spec.test.helpers.state import state_transition_and_sign_block, transition_to_valid_shard_slot + + +def run_beacon_block_with_shard_blocks(spec, state, shard_blocks, target_len_offset_slot, committee_index, valid=True): + shard_transitions = build_shard_transitions_till_slot( + spec, state, shard_blocks, on_time_slot=state.slot + target_len_offset_slot + ) + attestations = [ + build_attestation_with_shard_transition( + spec, + state, + on_time_slot=state.slot + target_len_offset_slot, + index=committee_index, + shard_transition=shard_transitions[shard], + ) + for shard in shard_blocks.keys() + ] + + # Propose beacon block at slot `x + 1` + beacon_block = build_empty_block(spec, state, slot=state.slot + target_len_offset_slot) + beacon_block.body.attestations = attestations + beacon_block.body.shard_transitions = shard_transitions + + pre_shard_states = state.shard_states.copy() + yield 'pre', state.copy() + yield 'block', beacon_block + state_transition_and_sign_block(spec, state, beacon_block) + if valid: + yield 'post', state + else: + yield 'post', None + return + + for shard in range(spec.get_active_shard_count(state)): + post_shard_state = state.shard_states[shard] + if shard in shard_blocks: + # Shard state has been changed to state_transition result + assert post_shard_state == shard_transitions[shard].shard_states[ + len(shard_transitions[shard].shard_states) - 1 + ] + assert beacon_block.slot == shard_transitions[shard].shard_states[0].slot + target_len_offset_slot + assert post_shard_state.slot == state.slot - 1 + if len(shard_blocks[shard]) == 0: + # `latest_block_root` is the same + assert post_shard_state.latest_block_root == pre_shard_states[shard].latest_block_root @with_all_phases_except([PHASE0]) @spec_state_test @always_bls def test_process_beacon_block_with_normal_shard_transition(spec, state): - next_epoch(spec, state) - next_epoch(spec, state) - state = spec.upgrade_to_phase1(state) - next_slot(spec, state) + state = transition_to_valid_shard_slot(spec, state) target_len_offset_slot = 1 - - # At the beginning, let `x = state.slot`, `state.shard_states[shard].slot == x - 1` - slot_x = state.slot committee_index = spec.CommitteeIndex(0) shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) - assert state.shard_states[shard].slot == slot_x - 1 + assert state.shard_states[shard].slot == state.slot - 1 - # Create SignedShardBlock at slot `shard_state.slot + 1` -> x - body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE - shard_block = build_shard_block(spec, state, shard, body=body, signed=True) - shard_blocks = [shard_block] - - # Attester creates `attestation` at slot x - # Use temporary next state to get ShardTransition of shard block - shard_transitions = build_shard_transitions_till_slot( - spec, - state, - shards=[shard, ], - shard_blocks={shard: shard_blocks}, - target_len_offset_slot=target_len_offset_slot, - ) - shard_transition = shard_transitions[shard] - attestation = build_attestation_with_shard_transition( - spec, - state, - slot=slot_x + target_len_offset_slot - 1, - index=committee_index, - target_len_offset_slot=target_len_offset_slot, - shard_transition=shard_transition, - ) pre_gasprice = state.shard_states[shard].gasprice - # Propose beacon block at slot `x + 1` - pre_shard_state = state.shard_states[shard] - beacon_block_1 = build_empty_block(spec, state, slot=slot_x + target_len_offset_slot) - beacon_block_1.body.attestations = [attestation] - beacon_block_1.body.shard_transitions = shard_transitions - assert ( - beacon_block_1.slot == slot_x + target_len_offset_slot - == shard_transition.shard_states[0].slot + target_len_offset_slot - ) - state_transition_and_sign_block(spec, state, beacon_block_1) + # Create SignedShardBlock at slot `shard_state.slot + 1` + body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE + shard_block = build_shard_block(spec, state, shard, body=body, signed=True) + shard_blocks: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]} + + yield from run_beacon_block_with_shard_blocks(spec, state, shard_blocks, target_len_offset_slot, committee_index) - # After state transition - assert state.slot == slot_x + target_len_offset_slot shard_state = state.shard_states[shard] - # latest_block_root has changed - assert shard_state.latest_block_root == shard_block.message.hash_tree_root() - assert shard_state != pre_shard_state - assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1] if target_len_offset_slot == 1 and len(shard_blocks) > 0: assert shard_state.gasprice > pre_gasprice @@ -82,60 +89,19 @@ def test_process_beacon_block_with_normal_shard_transition(spec, state): @spec_state_test @always_bls def test_process_beacon_block_with_empty_proposal_transition(spec, state): - next_epoch(spec, state) - next_epoch(spec, state) - state = spec.upgrade_to_phase1(state) - next_slot(spec, state) + state = transition_to_valid_shard_slot(spec, state) target_len_offset_slot = 1 - - # At the beginning, let `x = state.slot`, `state.shard_states[shard].slot == x - 1` - slot_x = state.slot committee_index = spec.CommitteeIndex(0) shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) - assert state.shard_states[shard].slot == slot_x - 1 + assert state.shard_states[shard].slot == state.slot - 1 # No new shard block - shard_blocks = [] + shard_blocks = {} - # Attester creates `attestation` at slot x - # Use temporary next state to get ShardTransition of shard block - shard_transitions = build_shard_transitions_till_slot( - spec, - state, - shards=[shard, ], - shard_blocks={shard: shard_blocks}, - target_len_offset_slot=target_len_offset_slot, - ) - shard_transition = shard_transitions[shard] - attestation = build_attestation_with_shard_transition( - spec, - state, - slot=slot_x + target_len_offset_slot - 1, - index=committee_index, - target_len_offset_slot=target_len_offset_slot, - shard_transition=shard_transition, - ) pre_gasprice = state.shard_states[shard].gasprice - # Propose beacon block at slot `x + 1` - pre_shard_state = state.shard_states[shard] - beacon_block_1 = build_empty_block(spec, state, slot=slot_x + target_len_offset_slot) - beacon_block_1.body.attestations = [attestation] - beacon_block_1.body.shard_transitions = shard_transitions - assert ( - beacon_block_1.slot == slot_x + target_len_offset_slot - == shard_transition.shard_states[0].slot + target_len_offset_slot - ) - state_transition_and_sign_block(spec, state, beacon_block_1) - - # After state transition - assert state.slot == slot_x + target_len_offset_slot - shard_state = state.shard_states[shard] - # latest_block_root hasn't changed - assert shard_state.latest_block_root == pre_shard_state.latest_block_root - assert shard_state != pre_shard_state - assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1] + yield from run_beacon_block_with_shard_blocks(spec, state, shard_blocks, target_len_offset_slot, committee_index) if target_len_offset_slot == 1 and len(shard_blocks) > 0: - assert shard_state.gasprice > pre_gasprice + assert state.shard_states[shard].gasprice > pre_gasprice From b43e24acb610ae75497fdb10633a110bf6cddcfc Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sat, 2 May 2020 02:04:46 +0800 Subject: [PATCH 30/53] specs/phase1/fraud-proofs.md -> specs/phase1/shard-transition.md --- specs/phase1/{fraud-proofs.md => shard-transition.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename specs/phase1/{fraud-proofs.md => shard-transition.md} (100%) diff --git a/specs/phase1/fraud-proofs.md b/specs/phase1/shard-transition.md similarity index 100% rename from specs/phase1/fraud-proofs.md rename to specs/phase1/shard-transition.md From 4558c7db4ed4600895909420b4d0629cc93e0d56 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sat, 2 May 2020 02:22:31 +0800 Subject: [PATCH 31/53] Reorg the file structure --- setup.py | 2 +- specs/phase1/shard-transition.md | 109 ++++++++++++++++--------------- 2 files changed, 57 insertions(+), 54 deletions(-) diff --git a/setup.py b/setup.py index d1c62fb72..e0d6561dd 100644 --- a/setup.py +++ b/setup.py @@ -375,7 +375,7 @@ class PySpecCommand(Command): specs/phase0/fork-choice.md specs/phase1/custody-game.md specs/phase1/beacon-chain.md - specs/phase1/fraud-proofs.md + specs/phase1/shard-transition.md specs/phase1/fork-choice.md specs/phase1/phase1-fork.md """ diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index c676e142a..a8de508fb 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -1,38 +1,70 @@ - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [Ethereum 2.0 Phase 1 -- Shard Transition and Fraud Proofs](#ethereum-20-phase-1----shard-transition-and-fraud-proofs) - - [Table of contents](#table-of-contents) - - [Introduction](#introduction) - - [Fraud proofs](#fraud-proofs) - - [Shard state transition function](#shard-state-transition-function) - - [Verifying the proof](#verifying-the-proof) - - [Honest committee member behavior](#honest-committee-member-behavior) - - [Helper functions](#helper-functions) - - [Make attestations](#make-attestations) - - - # Ethereum 2.0 Phase 1 -- Shard Transition and Fraud Proofs **Notice**: This document is a work-in-progress for researchers and implementers. ## Table of contents - + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - TODO +- [Introduction](#introduction) +- [Helper functions](#helper-functions) + - [Misc](#misc) + - [Shard block verification functions](#shard-block-verification-functions) +- [Shard state transition](#shard-state-transition) +- [Fraud proofs](#fraud-proofs) + - [Verifying the proof](#verifying-the-proof) +- [Honest committee member behavior](#honest-committee-member-behavior) + - [Helper functions](#helper-functions-1) + - [Make attestations](#make-attestations) - + ## Introduction This document describes the shard transition function and fraud proofs as part of Phase 1 of Ethereum 2.0. -## Fraud proofs +## Helper functions -### Shard state transition function +### Misc + +```python +def compute_shard_transition_digest(beacon_state: BeaconState, + shard_state: ShardState, + beacon_parent_root: Root, + shard_body_root: Root) -> Bytes32: + # TODO: use SSZ hash tree root + return hash( + hash_tree_root(shard_state) + beacon_parent_root + shard_body_root + ) +``` + +### Shard block verification functions + +```python +def verify_shard_block_message(beacon_state: BeaconState, + shard_state: ShardState, + block: ShardBlock, + slot: Slot, + shard: Shard) -> bool: + assert block.shard_parent_root == shard_state.latest_block_root + assert block.slot == slot + assert block.proposer_index == get_shard_proposer_index(beacon_state, slot, shard) + assert 0 < len(block.body) <= MAX_SHARD_BLOCK_SIZE + return True +``` + +```python +def verify_shard_block_signature(beacon_state: BeaconState, + signed_block: SignedShardBlock) -> bool: + proposer = beacon_state.validators[signed_block.message.proposer_index] + domain = get_domain(beacon_state, DOMAIN_SHARD_PROPOSAL, compute_epoch_at_slot(signed_block.message.slot)) + signing_root = compute_signing_root(signed_block.message, domain) + return bls.Verify(proposer.pubkey, signing_root, signed_block.signature) +``` + +## Shard state transition ```python def shard_state_transition(beacon_state: BeaconState, @@ -56,6 +88,8 @@ def shard_state_transition(beacon_state: BeaconState, shard_state.latest_block_root = latest_block_root ``` +We have a pure function `get_post_shard_state` for describing the fraud proof verification and honest validator behavior. + ```python def get_post_shard_state(beacon_state: BeaconState, shard_state: ShardState, @@ -68,38 +102,7 @@ def get_post_shard_state(beacon_state: BeaconState, return post_state ``` -```python -def compute_shard_transition_digest(beacon_state: BeaconState, - shard_state: ShardState, - beacon_parent_root: Root, - shard_body_root: Root) -> Bytes32: - # TODO: use SSZ hash tree root - return hash( - hash_tree_root(shard_state) + beacon_parent_root + shard_body_root - ) -``` - -```python -def verify_shard_block_message(beacon_state: BeaconState, - shard_state: ShardState, - block: ShardBlock, - slot: Slot, - shard: Shard) -> bool: - assert block.shard_parent_root == shard_state.latest_block_root - assert block.slot == slot - assert block.proposer_index == get_shard_proposer_index(beacon_state, slot, shard) - assert 0 < len(block.body) <= MAX_SHARD_BLOCK_SIZE - return True -``` - -```python -def verify_shard_block_signature(beacon_state: BeaconState, - signed_block: SignedShardBlock) -> bool: - proposer = beacon_state.validators[signed_block.message.proposer_index] - domain = get_domain(beacon_state, DOMAIN_SHARD_PROPOSAL, compute_epoch_at_slot(signed_block.message.slot)) - signing_root = compute_signing_root(signed_block.message, domain) - return bls.Verify(proposer.pubkey, signing_root, signed_block.signature) -``` +## Fraud proofs ### Verifying the proof From 7a770186b5ba576bf14ce496dc2b0381d169840e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sat, 2 May 2020 02:25:11 +0800 Subject: [PATCH 32/53] Reorg beacon-chain spec a bit --- specs/phase1/beacon-chain.md | 80 ++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index c9d8eeb54..35f6a6425 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -16,8 +16,8 @@ - [Extended `AttestationData`](#extended-attestationdata) - [Extended `Attestation`](#extended-attestation) - [Extended `PendingAttestation`](#extended-pendingattestation) - - [`IndexedAttestation`](#indexedattestation) - - [Extended `AttesterSlashing`](#extended-attesterslashing) + - [Extended `IndexedAttestation`](#extended-indexedattestation) + - [Extended `AttesterSlashing`](#extended-attesterslashing) - [Extended `Validator`](#extended-validator) - [Extended `BeaconBlockBody`](#extended-beaconblockbody) - [Extended `BeaconBlock`](#extended-beaconblock) @@ -52,9 +52,9 @@ - [`get_latest_slot_for_shard`](#get_latest_slot_for_shard) - [`get_offset_slots`](#get_offset_slots) - [Predicates](#predicates) + - [Updated `is_valid_indexed_attestation`](#updated-is_valid_indexed_attestation) - [`is_shard_attestation`](#is_shard_attestation) - [`is_winning_attestation`](#is_winning_attestation) - - [Updated `is_valid_indexed_attestation`](#updated-is_valid_indexed_attestation) - [Block processing](#block-processing) - [Operations](#operations) - [New Attestation processing](#new-attestation-processing) @@ -154,7 +154,7 @@ class PendingAttestation(Container): crosslink_success: boolean ``` -### `IndexedAttestation` +### Extended `IndexedAttestation` ```python class IndexedAttestation(Container): @@ -162,7 +162,7 @@ class IndexedAttestation(Container): attestation: Attestation ``` -#### Extended `AttesterSlashing` +### Extended `AttesterSlashing` Note that the `attestation_1` and `attestation_2` have a new `IndexedAttestation` definition. @@ -557,39 +557,6 @@ def get_offset_slots(state: BeaconState, shard: Shard) -> Sequence[Slot]: ### Predicates -#### `is_shard_attestation` - -```python -def is_shard_attestation(state: BeaconState, - attestation: Attestation, - committee_index: CommitteeIndex) -> bool: - if not ( - attestation.data.index == committee_index - and attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY == state.slot - ): - return False - - return True -``` - -#### `is_winning_attestation` - -```python -def is_winning_attestation(state: BeaconState, - attestation: PendingAttestation, - committee_index: CommitteeIndex, - winning_root: Root) -> bool: - """ - Check if ``attestation`` helped contribute to the successful crosslink of - ``winning_root`` formed by ``committee_index`` committee at the current slot. - """ - return ( - attestation.data.slot == state.slot - and attestation.data.index == committee_index - and attestation.data.shard_transition_root == winning_root - ) -``` - #### Updated `is_valid_indexed_attestation` Note that this replaces the Phase 0 `is_valid_indexed_attestation`. @@ -632,6 +599,40 @@ def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: Indexe return bls.AggregateVerify(zip(all_pubkeys, all_signing_roots), signature=attestation.signature) ``` +#### `is_shard_attestation` + +```python +def is_shard_attestation(state: BeaconState, + attestation: Attestation, + committee_index: CommitteeIndex) -> bool: + if not ( + attestation.data.index == committee_index + and attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY == state.slot # Must be on-time attestation + # TODO: MIN_ATTESTATION_INCLUSION_DELAY should always be 1 + ): + return False + + return True +``` + +#### `is_winning_attestation` + +```python +def is_winning_attestation(state: BeaconState, + attestation: PendingAttestation, + committee_index: CommitteeIndex, + winning_root: Root) -> bool: + """ + Check if ``attestation`` helped contribute to the successful crosslink of + ``winning_root`` formed by ``committee_index`` committee at the current slot. + """ + return ( + attestation.data.slot == state.slot + and attestation.data.index == committee_index + and attestation.data.shard_transition_root == winning_root + ) +``` + ### Block processing ```python @@ -942,7 +943,8 @@ def process_light_client_signatures(state: BeaconState, block_body: BeaconBlockB signing_root = compute_signing_root(get_block_root_at_slot(state, slot), get_domain(state, DOMAIN_LIGHT_CLIENT, compute_epoch_at_slot(slot))) if len(signer_pubkeys) == 0: - # TODO: Or disallow empty light_client_signature? + # TODO: handle the empty light_client_signature case? + assert block_body.light_client_signature == BLSSignature() return else: assert bls.FastAggregateVerify(signer_pubkeys, signing_root, signature=block_body.light_client_signature) From eda249957e1891a1211738e67133ee2524a6efb6 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 4 May 2020 13:20:32 -0600 Subject: [PATCH 33/53] basic generators work --- .../pyspec/eth2spec/test/helpers/rewards.py | 10 +++- .../eth2spec/test/phase_0/rewards/__init__.py | 0 .../rewards/test_get_head_deltas.py | 0 .../rewards/test_get_source_deltas.py | 0 .../rewards/test_get_target_deltas.py | 0 tests/formats/rewards/README.md | 47 +++++++++++++++++++ tests/generators/rewards/README.md | 8 ++++ tests/generators/rewards/main.py | 44 +++++++++++++++++ tests/generators/rewards/requirements.txt | 2 + 9 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase_0/rewards/__init__.py rename tests/core/pyspec/eth2spec/test/phase_0/{epoch_processing => }/rewards/test_get_head_deltas.py (100%) rename tests/core/pyspec/eth2spec/test/phase_0/{epoch_processing => }/rewards/test_get_source_deltas.py (100%) rename tests/core/pyspec/eth2spec/test/phase_0/{epoch_processing => }/rewards/test_get_target_deltas.py (100%) create mode 100644 tests/formats/rewards/README.md create mode 100644 tests/generators/rewards/README.md create mode 100644 tests/generators/rewards/main.py create mode 100644 tests/generators/rewards/requirements.txt diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index 41497fe60..a98aa3415 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -1,4 +1,10 @@ from eth2spec.test.helpers.attestations import prepare_state_with_full_attestations +from eth2spec.utils.ssz.ssz_typing import Container, uint64, List + + +# HACK to get the generators outputting correctly +class Deltas(Container): + delta_list: List[uint64, 2**30] def run_attestation_component_deltas(spec, state, component_delta_fn, matching_att_fn): @@ -12,8 +18,8 @@ def run_attestation_component_deltas(spec, state, component_delta_fn, matching_a rewards, penalties = component_delta_fn(state) - yield 'rewards', rewards - yield 'penalties', penalties + yield 'rewards', Deltas(delta_list=rewards) + yield 'penalties', Deltas(delta_list=penalties) matching_attestations = matching_att_fn(state, spec.get_previous_epoch(state)) matching_indices = spec.get_unslashed_attesting_indices(state, matching_attestations) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/__init__.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_head_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_head_deltas.py rename to tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_source_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_source_deltas.py rename to tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_target_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/rewards/test_get_target_deltas.py rename to tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py diff --git a/tests/formats/rewards/README.md b/tests/formats/rewards/README.md new file mode 100644 index 000000000..dce2b5ac8 --- /dev/null +++ b/tests/formats/rewards/README.md @@ -0,0 +1,47 @@ +# Rewards tests + +The different rewards deltas sub-functions are testing individually with the test handlers, each returning the related `rewards`/`penalties`. +There is no "change" factor, the rewards/penalties outputs are pure functions with just the pre-state as input. +Hence, the format is shared between each test-handler. (See test condition documentation on how to run the tests.) + + +## Test case format + +### `meta.yaml` + +```yaml +description: string -- Optional description of test case, purely for debugging purposes. + Tests should use the directory name of the test case as identifier, not the description. +bls_setting: int -- see general test-format spec. +``` + +### `pre.yaml` + +A YAML-encoded `BeaconState`, the state before running the rewards sub-function. + +Also available as `pre.ssz`. + + +### `rewards.yaml` + +A YAML-encoded list of integers representing the 0th item in the return value (i.e. the rewards deltas) + +### `penalties.yaml` + +A YAML-encoded list of integers representing the 1st item in the return value (i.e. the penalties deltas) + +## Condition + +A handler of the `rewards` test-runner should process these cases, + calling the corresponding rewards deltas function (same name in spec). +This excludes all other parts of `process_rewards_and_penalties` + +The provided pre-state is ready to be input into the designated handler. + +The resulting `rewards`/`penalties` should match the return values of the +handler. Specifically the following must hold true: + +```python + rewards == handler(state)[0] + penalties == handler(state)[1] +``` diff --git a/tests/generators/rewards/README.md b/tests/generators/rewards/README.md new file mode 100644 index 000000000..60f106836 --- /dev/null +++ b/tests/generators/rewards/README.md @@ -0,0 +1,8 @@ +# Rewards + +Rewards covers the sub-functions of `process_rewards_and_penalties` for granular testing of components of the rewards function. + +A rewards test-runner can consume these sub-transition test-suites, + and handle different kinds of epoch sub-transitions by processing the cases using the specified test handler. + +Information on the format of the tests can be found in the [rewards test formats documentation](../../formats/rewards/README.md). diff --git a/tests/generators/rewards/main.py b/tests/generators/rewards/main.py new file mode 100644 index 000000000..caffc3bd1 --- /dev/null +++ b/tests/generators/rewards/main.py @@ -0,0 +1,44 @@ +from typing import Iterable + +from eth2spec.phase0 import spec as spec_phase0 +from eth2spec.phase1 import spec as spec_phase1 +from eth2spec.test.phase_0.rewards import ( + test_get_source_deltas, + test_get_target_deltas, + test_get_head_deltas, +) +from gen_base import gen_runner, gen_typing +from gen_from_tests.gen import generate_from_tests +from importlib import reload +from eth2spec.config import config_util +from eth2spec.test.context import PHASE0 + + +def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typing.TestProvider: + + def prepare_fn(configs_path: str) -> str: + config_util.prepare_config(configs_path, config_name) + reload(spec_phase0) + reload(spec_phase1) + return config_name + + def cases_fn() -> Iterable[gen_typing.TestCase]: + return generate_from_tests( + runner_name='rewards', + handler_name=handler_name, + src=tests_src, + fork_name=PHASE0, + ) + + return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn) + + +if __name__ == "__main__": + gen_runner.run_generator("epoch_processing", [ + create_provider('get_source_deltas', test_get_source_deltas, 'minimal'), + create_provider('get_source_deltas', test_get_source_deltas, 'mainnet'), + create_provider('get_target_deltas', test_get_target_deltas, 'minimal'), + create_provider('get_target_deltas', test_get_target_deltas, 'mainnet'), + create_provider('get_head_deltas', test_get_head_deltas, 'minimal'), + create_provider('get_head_deltas', test_get_head_deltas, 'mainnet'), + ]) diff --git a/tests/generators/rewards/requirements.txt b/tests/generators/rewards/requirements.txt new file mode 100644 index 000000000..b82314298 --- /dev/null +++ b/tests/generators/rewards/requirements.txt @@ -0,0 +1,2 @@ +../../core/gen_helpers +../../../ \ No newline at end of file From 5194c1f2d2dba0c1083b6d1cd075c9f41f0aba62 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 4 May 2020 16:53:29 -0600 Subject: [PATCH 34/53] add test_get_inclusion_delay_deltas --- .../pyspec/eth2spec/test/helpers/rewards.py | 66 ++++-- .../phase_0/rewards/test_get_head_deltas.py | 10 +- .../test_get_inclusion_delay_deltas.py | 208 ++++++++++++++++++ .../phase_0/rewards/test_get_source_deltas.py | 16 +- .../phase_0/rewards/test_get_target_deltas.py | 16 +- 5 files changed, 297 insertions(+), 19 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index a98aa3415..e8cd950ce 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -1,3 +1,5 @@ +from random import Random + from eth2spec.test.helpers.attestations import prepare_state_with_full_attestations from eth2spec.utils.ssz.ssz_typing import Container, uint64, List @@ -7,6 +9,13 @@ class Deltas(Container): delta_list: List[uint64, 2**30] +def has_enough_for_reward(spec, state, index): + return ( + state.validators[index].effective_balance * spec.BASE_REWARD_FACTOR + > spec.integer_squareroot(spec.get_total_active_balance(state)) // spec.BASE_REWARDS_PER_EPOCH + ) + + def run_attestation_component_deltas(spec, state, component_delta_fn, matching_att_fn): """ Run ``component_delta_fn``, yielding: @@ -25,11 +34,7 @@ def run_attestation_component_deltas(spec, state, component_delta_fn, matching_a matching_indices = spec.get_unslashed_attesting_indices(state, matching_attestations) for index in spec.get_eligible_validator_indices(state): validator = state.validators[index] - enough_for_reward = ( - validator.effective_balance * spec.BASE_REWARD_FACTOR - > spec.integer_squareroot(spec.get_total_active_balance(state)) // spec.BASE_REWARDS_PER_EPOCH - ) - + enough_for_reward = has_enough_for_reward(spec, state, index) if index in matching_indices and not validator.slashed: if enough_for_reward: assert rewards[index] > 0 @@ -56,15 +61,29 @@ def test_full_all_correct(spec, state, runner): yield from runner(spec, state) -def test_half_full(spec, state, runner): +def test_full_but_partial_participation(spec, state, runner, rng=Random(5522)): prepare_state_with_full_attestations(spec, state) - # Remove half of attestations - state.previous_epoch_attestations = state.previous_epoch_attestations[:len(state.previous_epoch_attestations) // 2] + for a in state.previous_epoch_attestations: + a.aggregation_bits = [rng.choice([True, False]) for _ in a.aggregation_bits] yield from runner(spec, state) +def test_partial(spec, state, fraction_filled, runner): + prepare_state_with_full_attestations(spec, state) + + # Remove portion of attestations + num_attestations = int(len(state.previous_epoch_attestations) * fraction_filled) + state.previous_epoch_attestations = state.previous_epoch_attestations[:num_attestations] + + yield from runner(spec, state) + + +def test_half_full(spec, state, runner): + yield from test_partial(spec, state, 0.5, runner) + + def test_one_attestation_one_correct(spec, state, runner): prepare_state_with_full_attestations(spec, state) @@ -84,12 +103,16 @@ def test_with_slashed_validators(spec, state, runner): yield from runner(spec, state) -def test_some_zero_effective_balances_that_attested(spec, state, runner): +def test_some_very_low_effective_balances_that_attested(spec, state, runner): + state.balances prepare_state_with_full_attestations(spec, state) - # Set some balances to zero + # Set some balances to be very low (including 0) state.validators[0].effective_balance = 0 - state.validators[1].effective_balance = 0 + state.validators[1].effective_balance = 2 + state.validators[2].effective_balance = 10 + state.validators[3].effective_balance = 100 + state.validators[4].effective_balance = 1000 yield from runner(spec, state) @@ -97,9 +120,8 @@ def test_some_zero_effective_balances_that_attested(spec, state, runner): def test_some_zero_effective_balances_that_did_not_attest(spec, state, runner): prepare_state_with_full_attestations(spec, state) - # Set some balances to zero - attestation = state.previous_epoch_attestations[0] # Remove attestation + attestation = state.previous_epoch_attestations[0] state.previous_epoch_attestations = state.previous_epoch_attestations[1:] # Set removed indices effective balance to zero indices = spec.get_unslashed_attesting_indices(state, [attestation]) @@ -121,3 +143,21 @@ def test_full_fraction_incorrect(spec, state, correct_target, correct_head, frac pending_attestation.data.beacon_block_root = b'\x66' * 32 yield from runner(spec, state) + + +def test_full_random(spec, state, runner, rng=Random(8020)): + prepare_state_with_full_attestations(spec, state) + + for pending_attestation in state.previous_epoch_attestations: + # 1/3 have bad target + if rng.randint(0, 2) == 0: + pending_attestation.data.target.root = b'\x55' * 32 + # 1/3 have bad head + if rng.randint(0, 2) == 0: + pending_attestation.data.beacon_block_root = b'\x66' * 32 + # ~50% participation + pending_attestation.aggregation_bits = [rng.choice([True, False]) for _ in pending_attestation.aggregation_bits] + # Random inclusion delay + pending_attestation.inclusion_delay = rng.randint(1, spec.SLOTS_PER_EPOCH) + + yield from runner(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py index 24e3aaac5..b9aab92cb 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py @@ -51,8 +51,8 @@ def test_with_slashed_validators(spec, state): @with_all_phases @spec_state_test -def test_some_zero_effective_balances_that_attested(spec, state): - yield from rewards_helpers.test_some_zero_effective_balances_that_attested(spec, state, run_get_head_deltas) +def test_some_very_low_effective_balances_that_attested(spec, state): + yield from rewards_helpers.test_some_very_low_effective_balances_that_attested(spec, state, run_get_head_deltas) @with_all_phases @@ -107,3 +107,9 @@ def test_full_half_incorrect_target_correct_head(spec, state): fraction_incorrect=0.5, runner=run_get_head_deltas ) + + +@with_all_phases +@spec_state_test +def test_full_random(spec, state): + yield from rewards_helpers.test_full_random(spec, state, run_get_head_deltas) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py new file mode 100644 index 000000000..b9ae9882c --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py @@ -0,0 +1,208 @@ +from random import Random + +from eth2spec.test.context import with_all_phases, spec_state_test +from eth2spec.test.helpers.attestations import prepare_state_with_full_attestations +from eth2spec.test.helpers.rewards import has_enough_for_reward +import eth2spec.test.helpers.rewards as rewards_helpers +from eth2spec.utils.ssz.ssz_typing import Container, uint64, List + + +# HACK to get the generators outputting correctly +class Deltas(Container): + delta_list: List[uint64, 2**30] + + +def run_get_inclusion_delay_deltas(spec, state): + """ + Run ``get_inclusion_delay_deltas``, yielding: + - pre-state ('pre') + - rewards ('rewards') + - penalties ('penalties') + """ + + yield 'pre', state + + rewards, penalties = spec.get_inclusion_delay_deltas(state) + + yield 'rewards', Deltas(delta_list=rewards) + yield 'penalties', Deltas(delta_list=penalties) + + eligible_attestations = spec.get_matching_source_attestations(state, spec.get_previous_epoch(state)) + attesting_indices = spec.get_unslashed_attesting_indices(state, eligible_attestations) + + rewarded_indices = set() + rewarded_proposer_indices = set() + # Ensure attesters with enough balance are rewarded for attestations + # Track those that are rewarded and track proposers that should be rewarded + for index in range(len(state.validators)): + if index in attesting_indices and has_enough_for_reward(spec, state, index): + assert rewards[index] > 0 + rewarded_indices.add(index) + + # Track proposer of earliest included attestation for the validator defined by index + earliest_attestation = min([ + a for a in eligible_attestations + if index in spec.get_attesting_indices(state, a.data, a.aggregation_bits) + ], key=lambda a: a.inclusion_delay) + rewarded_proposer_indices.add(earliest_attestation.proposer_index) + + # Ensure all expected proposers have been rewarded + # Track rewarde indices + proposing_indices = [a.proposer_index for a in eligible_attestations] + for index in proposing_indices: + if index in rewarded_proposer_indices: + assert rewards[index] > 0 + rewarded_indices.add(index) + + # Ensure all expected non-rewarded indices received no reward + for index in range(len(state.validators)): + assert penalties[index] == 0 + if index not in rewarded_indices: + assert rewards[index] == 0 + + +@with_all_phases +@spec_state_test +def test_empty(spec, state): + yield from rewards_helpers.test_empty(spec, state, run_get_inclusion_delay_deltas) + + +@with_all_phases +@spec_state_test +def test_full(spec, state): + yield from rewards_helpers.test_full_all_correct(spec, state, run_get_inclusion_delay_deltas) + + +@with_all_phases +@spec_state_test +def test_half_full(spec, state): + yield from rewards_helpers.test_half_full(spec, state, run_get_inclusion_delay_deltas) + + +@with_all_phases +@spec_state_test +def test_quarter_full(spec, state): + yield from rewards_helpers.test_partial(spec, state, 0.25, run_get_inclusion_delay_deltas) + + +@with_all_phases +@spec_state_test +def test_full_but_partial_participation(spec, state): + yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_inclusion_delay_deltas) + + +@with_all_phases +@spec_state_test +def test_with_slashed_validators(spec, state): + yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_inclusion_delay_deltas) + + +@with_all_phases +@spec_state_test +def test_some_very_low_effective_balances_that_attested(spec, state): + yield from rewards_helpers.test_some_very_low_effective_balances_that_attested( + spec, + state, + run_get_inclusion_delay_deltas + ) + + +@with_all_phases +@spec_state_test +def test_full_random(spec, state): + yield from rewards_helpers.test_full_random(spec, state, run_get_inclusion_delay_deltas) + + +@with_all_phases +@spec_state_test +def test_full_delay_one_slot(spec, state): + prepare_state_with_full_attestations(spec, state) + for a in state.previous_epoch_attestations: + a.inclusion_delay += 1 + + yield from run_get_inclusion_delay_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_full_delay_max_slots(spec, state): + prepare_state_with_full_attestations(spec, state) + for a in state.previous_epoch_attestations: + a.inclusion_delay += spec.SLOTS_PER_EPOCH + + yield from run_get_inclusion_delay_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_full_mixed_delay(spec, state): + rng = Random(1234) + + prepare_state_with_full_attestations(spec, state) + for a in state.previous_epoch_attestations: + a.inclusion_delay = rng.randint(1, spec.SLOTS_PER_EPOCH) + + yield from run_get_inclusion_delay_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_proposer_not_in_attestations(spec, state): + prepare_state_with_full_attestations(spec, state) + + # Get an attestation where the proposer is not in the committee + non_proposer_attestations = [] + for a in state.previous_epoch_attestations: + if a.proposer_index not in spec.get_unslashed_attesting_indices(state, [a]): + non_proposer_attestations.append(a) + + assert any(non_proposer_attestations) + state.previous_epoch_attestations = non_proposer_attestations + + yield from run_get_inclusion_delay_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_duplicate_attestations_at_later_slots(spec, state): + prepare_state_with_full_attestations(spec, state) + + # Remove 2/3 of attestations to make it more interesting + num_attestations = int(len(state.previous_epoch_attestations) * 0.33) + state.previous_epoch_attestations = state.previous_epoch_attestations[:num_attestations] + + # Get map of the proposer at each slot to make valid-looking duplicate attestations + per_slot_proposers = { + (a.data.slot + a.inclusion_delay): a.proposer_index + for a in state.previous_epoch_attestations + } + max_slot = max([a.data.slot + a.inclusion_delay for a in state.previous_epoch_attestations]) + later_attestations = [] + for a in state.previous_epoch_attestations: + # Do not create later duplicate if goes into next epoch + if a.data.slot + a.inclusion_delay >= max_slot: + continue + later_a = a.copy() + later_a.inclusion_delay += 1 + later_a.proposer_index = per_slot_proposers[later_a.data.slot + later_a.inclusion_delay] + later_attestations.append(later_a) + + assert any(later_attestations) + + state.previous_epoch_attestations = sorted( + state.previous_epoch_attestations + later_attestations, + key=lambda a: a.data.slot + a.inclusion_delay + ) + + yield from run_get_inclusion_delay_deltas(spec, state) + + +@with_all_phases +@spec_state_test +def test_all_balances_too_low_for_reward(spec, state): + prepare_state_with_full_attestations(spec, state) + + for index in range(len(state.validators)): + state.validators[index].effective_balance = 10 + + yield from run_get_inclusion_delay_deltas(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py index da47bd204..9063abfc6 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py @@ -37,6 +37,12 @@ def test_half_full(spec, state): yield from rewards_helpers.test_half_full(spec, state, run_get_source_deltas) +@with_all_phases +@spec_state_test +def test_full_but_partial_participation(spec, state): + yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_source_deltas) + + @with_all_phases @spec_state_test def test_one_attestation_one_correct(spec, state): @@ -51,8 +57,8 @@ def test_with_slashed_validators(spec, state): @with_all_phases @spec_state_test -def test_some_zero_effective_balances_that_attested(spec, state): - yield from rewards_helpers.test_some_zero_effective_balances_that_attested(spec, state, run_get_source_deltas) +def test_some_very_low_effective_balances_that_attested(spec, state): + yield from rewards_helpers.test_some_very_low_effective_balances_that_attested(spec, state, run_get_source_deltas) @with_all_phases @@ -114,3 +120,9 @@ def test_full_half_incorrect_target_correct_head(spec, state): fraction_incorrect=0.5, runner=run_get_source_deltas ) + + +@with_all_phases +@spec_state_test +def test_full_random(spec, state): + yield from rewards_helpers.test_full_random(spec, state, run_get_source_deltas) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py index 406471c67..ff20014c8 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py @@ -37,6 +37,12 @@ def test_half_full(spec, state): yield from rewards_helpers.test_half_full(spec, state, run_get_target_deltas) +@with_all_phases +@spec_state_test +def test_full_but_partial_participation(spec, state): + yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_target_deltas) + + @with_all_phases @spec_state_test def test_one_attestation_one_correct(spec, state): @@ -51,8 +57,8 @@ def test_with_slashed_validators(spec, state): @with_all_phases @spec_state_test -def test_some_zero_effective_balances_that_attested(spec, state): - yield from rewards_helpers.test_some_zero_effective_balances_that_attested(spec, state, run_get_target_deltas) +def test_some_very_low_effective_balances_that_attested(spec, state): + yield from rewards_helpers.test_some_very_low_effective_balances_that_attested(spec, state, run_get_target_deltas) @with_all_phases @@ -107,3 +113,9 @@ def test_full_half_incorrect_target_correct_head(spec, state): fraction_incorrect=0.5, runner=run_get_target_deltas ) + + +@with_all_phases +@spec_state_test +def test_full_random(spec, state): + yield from rewards_helpers.test_full_random(spec, state, run_get_target_deltas) From 8f569a8ddc05bc5d13c94a4dc7e2465c3eb1448a Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 4 May 2020 20:58:42 -0600 Subject: [PATCH 35/53] add inactivity penalty deltas tests --- .../pyspec/eth2spec/test/helpers/rewards.py | 14 +- .../phase_0/rewards/test_get_head_deltas.py | 14 +- .../test_get_inactivity_penalty_deltas.py | 204 ++++++++++++++++++ .../phase_0/rewards/test_get_source_deltas.py | 8 +- .../phase_0/rewards/test_get_target_deltas.py | 8 +- tests/generators/rewards/main.py | 6 + 6 files changed, 244 insertions(+), 10 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index e8cd950ce..09c99c3ac 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -32,7 +32,13 @@ def run_attestation_component_deltas(spec, state, component_delta_fn, matching_a matching_attestations = matching_att_fn(state, spec.get_previous_epoch(state)) matching_indices = spec.get_unslashed_attesting_indices(state, matching_attestations) - for index in spec.get_eligible_validator_indices(state): + eligible_indices = spec.get_eligible_validator_indices(state) + for index in range(len(state.validators)): + if index not in eligible_indices: + assert rewards[index] == 0 + assert penalties[index] == 0 + continue + validator = state.validators[index] enough_for_reward = has_enough_for_reward(spec, state, index) if index in matching_indices and not validator.slashed: @@ -117,7 +123,7 @@ def test_some_very_low_effective_balances_that_attested(spec, state, runner): yield from runner(spec, state) -def test_some_zero_effective_balances_that_did_not_attest(spec, state, runner): +def test_some_very_low_effective_balances_that_did_not_attest(spec, state, runner): prepare_state_with_full_attestations(spec, state) # Remove attestation @@ -125,8 +131,8 @@ def test_some_zero_effective_balances_that_did_not_attest(spec, state, runner): state.previous_epoch_attestations = state.previous_epoch_attestations[1:] # Set removed indices effective balance to zero indices = spec.get_unslashed_attesting_indices(state, [attestation]) - for index in indices: - state.validators[index].effective_balance = 0 + for i, index in enumerate(indices): + state.validators[index].effective_balance = i yield from runner(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py index b9aab92cb..237920732 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py @@ -37,6 +37,12 @@ def test_half_full(spec, state): yield from rewards_helpers.test_half_full(spec, state, run_get_head_deltas) +@with_all_phases +@spec_state_test +def test_full_but_partial_participation(spec, state): + yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_head_deltas) + + @with_all_phases @spec_state_test def test_one_attestation_one_correct(spec, state): @@ -57,8 +63,12 @@ def test_some_very_low_effective_balances_that_attested(spec, state): @with_all_phases @spec_state_test -def test_some_zero_effective_balances_that_did_not_attest(spec, state): - yield from rewards_helpers.test_some_zero_effective_balances_that_did_not_attest(spec, state, run_get_head_deltas) +def test_some_very_low_effective_balances_that_did_not_attest(spec, state): + yield from rewards_helpers.test_some_very_low_effective_balances_that_did_not_attest( + spec, + state, + run_get_head_deltas, + ) @with_all_phases diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py new file mode 100644 index 000000000..a81451e38 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py @@ -0,0 +1,204 @@ +from eth2spec.test.context import with_all_phases, spec_state_test +from eth2spec.test.helpers.rewards import has_enough_for_reward +from eth2spec.test.helpers.state import next_epoch +import eth2spec.test.helpers.rewards as rewards_helpers +from eth2spec.utils.ssz.ssz_typing import Container, uint64, List + + +# HACK to get the generators outputting correctly +class Deltas(Container): + delta_list: List[uint64, 2**30] + + +def run_get_inactivity_penalty_deltas(spec, state): + """ + Run ``get_inactivity_penalty_deltas``, yielding: + - pre-state ('pre') + - rewards ('rewards') + - penalties ('penalties') + """ + + yield 'pre', state + + rewards, penalties = spec.get_inactivity_penalty_deltas(state) + + yield 'rewards', Deltas(delta_list=rewards) + yield 'penalties', Deltas(delta_list=penalties) + + matching_attestations = spec.get_matching_target_attestations(state, spec.get_previous_epoch(state)) + matching_attesting_indices = spec.get_unslashed_attesting_indices(state, matching_attestations) + + finality_delay = spec.get_previous_epoch(state) - state.finalized_checkpoint.epoch + eligible_indices = spec.get_eligible_validator_indices(state) + for index in range(len(state.validators)): + assert rewards[index] == 0 + if index not in eligible_indices: + assert penalties[index] == 0 + continue + + if finality_delay > spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY: + base_penalty = spec.BASE_REWARDS_PER_EPOCH * spec.get_base_reward(state, index) + if not has_enough_for_reward(spec, state, index): + assert penalties[index] == 0 + elif index in matching_attesting_indices: + assert penalties[index] == base_penalty + else: + assert penalties[index] > base_penalty + else: + assert penalties[index] == 0 + + +def transition_state_to_leak(spec, state, epochs=None): + if epochs is None: + epochs = spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + assert epochs >= spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + + for _ in range(epochs): + next_epoch(spec, state) + + +@with_all_phases +@spec_state_test +def test_empty_no_leak(spec, state): + yield from rewards_helpers.test_empty(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_empty_leak(spec, state): + transition_state_to_leak(spec, state) + yield from rewards_helpers.test_empty(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_full_no_leak(spec, state): + yield from rewards_helpers.test_full_all_correct(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_full_leak(spec, state): + transition_state_to_leak(spec, state) + yield from rewards_helpers.test_full_all_correct(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_half_full_no_leak(spec, state): + yield from rewards_helpers.test_half_full(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_half_full_leak(spec, state): + transition_state_to_leak(spec, state) + yield from rewards_helpers.test_half_full(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_quarter_full_no_leak(spec, state): + yield from rewards_helpers.test_partial(spec, state, 0.25, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_quarter_full_leak(spec, state): + transition_state_to_leak(spec, state) + yield from rewards_helpers.test_partial(spec, state, 0.25, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_full_but_partial_participation_no_leak(spec, state): + yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_full_but_partial_participation_leak(spec, state): + transition_state_to_leak(spec, state) + yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_with_slashed_validators_no_leak(spec, state): + yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_with_slashed_validators_leak(spec, state): + transition_state_to_leak(spec, state) + yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_some_very_low_effective_balances_that_attested_no_leak(spec, state): + yield from rewards_helpers.test_some_very_low_effective_balances_that_attested( + spec, + state, + run_get_inactivity_penalty_deltas, + ) + + +@with_all_phases +@spec_state_test +def test_some_very_low_effective_balances_that_attested_leak(spec, state): + transition_state_to_leak(spec, state) + yield from rewards_helpers.test_some_very_low_effective_balances_that_attested( + spec, + state, + run_get_inactivity_penalty_deltas, + ) + + +@with_all_phases +@spec_state_test +def test_some_very_low_effective_balances_that_did_not_attest_no_leak(spec, state): + yield from rewards_helpers.test_some_very_low_effective_balances_that_did_not_attest( + spec, + state, + run_get_inactivity_penalty_deltas, + ) + + +@with_all_phases +@spec_state_test +def test_some_very_low_effective_balances_that_did_not_attest_leak(spec, state): + transition_state_to_leak(spec, state) + yield from rewards_helpers.test_some_very_low_effective_balances_that_did_not_attest( + spec, + state, + run_get_inactivity_penalty_deltas, + ) + + +@with_all_phases +@spec_state_test +def test_full_random_no_leak(spec, state): + yield from rewards_helpers.test_full_random(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_full_random_leak(spec, state): + transition_state_to_leak(spec, state) + yield from rewards_helpers.test_full_random(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_full_random_five_epoch_leak(spec, state): + transition_state_to_leak(spec, state, epochs=5) + yield from rewards_helpers.test_full_random(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_full_random_ten_epoch_leak(spec, state): + transition_state_to_leak(spec, state, epochs=10) + yield from rewards_helpers.test_full_random(spec, state, run_get_inactivity_penalty_deltas) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py index 9063abfc6..842728db8 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py @@ -63,8 +63,12 @@ def test_some_very_low_effective_balances_that_attested(spec, state): @with_all_phases @spec_state_test -def test_some_zero_effective_balances_that_did_not_attest(spec, state): - yield from rewards_helpers.test_some_zero_effective_balances_that_did_not_attest(spec, state, run_get_source_deltas) +def test_some_very_low_effective_balances_that_did_not_attest(spec, state): + yield from rewards_helpers.test_some_very_low_effective_balances_that_did_not_attest( + spec, + state, + run_get_source_deltas, + ) # diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py index ff20014c8..ad2a65d2a 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py @@ -63,8 +63,12 @@ def test_some_very_low_effective_balances_that_attested(spec, state): @with_all_phases @spec_state_test -def test_some_zero_effective_balances_that_did_not_attest(spec, state): - yield from rewards_helpers.test_some_zero_effective_balances_that_did_not_attest(spec, state, run_get_target_deltas) +def test_some_very_low_effective_balances_that_did_not_attest(spec, state): + yield from rewards_helpers.test_some_very_low_effective_balances_that_did_not_attest( + spec, + state, + run_get_target_deltas, + ) @with_all_phases diff --git a/tests/generators/rewards/main.py b/tests/generators/rewards/main.py index caffc3bd1..fd95dcfaa 100644 --- a/tests/generators/rewards/main.py +++ b/tests/generators/rewards/main.py @@ -6,6 +6,8 @@ from eth2spec.test.phase_0.rewards import ( test_get_source_deltas, test_get_target_deltas, test_get_head_deltas, + test_get_inclusion_delay_deltas, + test_get_inactivity_penalty_deltas, ) from gen_base import gen_runner, gen_typing from gen_from_tests.gen import generate_from_tests @@ -41,4 +43,8 @@ if __name__ == "__main__": create_provider('get_target_deltas', test_get_target_deltas, 'mainnet'), create_provider('get_head_deltas', test_get_head_deltas, 'minimal'), create_provider('get_head_deltas', test_get_head_deltas, 'mainnet'), + create_provider('get_inclusion_delay_deltas', test_get_inclusion_delay_deltas, 'minimal'), + create_provider('get_inclusion_delay_deltas', test_get_inclusion_delay_deltas, 'mainnet'), + create_provider('get_inactivity_penalty_deltas', test_get_inactivity_penalty_deltas, 'minimal'), + create_provider('get_inactivity_penalty_deltas', test_get_inactivity_penalty_deltas, 'mainnet'), ]) From 3f250f7dd32296ae50a417424665b4279f9f588e Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 4 May 2020 21:05:10 -0600 Subject: [PATCH 36/53] PR feedback --- specs/phase0/beacon-chain.md | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 95f06f125..82fbb2856 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1364,9 +1364,12 @@ def get_eligible_validator_indices(state: BeaconState) -> Sequence[ValidatorInde ``` ```python -def compute_attestation_component_deltas(state: BeaconState, - attestations: Sequence[PendingAttestation] - ) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: +def get_attestation_component_deltas(state: BeaconState, + attestations: Sequence[PendingAttestation] + ) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Helper with shared logic for use by get source, target, and head deltas functions + """ rewards = [Gwei(0) for _ in range(len(state.validators))] penalties = [Gwei(0) for _ in range(len(state.validators))] total_balance = get_total_active_balance(state) @@ -1390,7 +1393,7 @@ def get_source_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei Return attester micro-rewards/penalties for source-vote for each validator. """ matching_source_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) - return compute_attestation_component_deltas(state, matching_source_attestations) + return get_attestation_component_deltas(state, matching_source_attestations) ``` ```python @@ -1399,7 +1402,7 @@ def get_target_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei Return attester micro-rewards/penalties for target-vote for each validator. """ matching_target_attestations = get_matching_target_attestations(state, get_previous_epoch(state)) - return compute_attestation_component_deltas(state, matching_target_attestations) + return get_attestation_component_deltas(state, matching_target_attestations) ``` ```python @@ -1408,7 +1411,7 @@ def get_head_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]] Return attester micro-rewards/penalties for head-vote for each validator. """ matching_head_attestations = get_matching_head_attestations(state, get_previous_epoch(state)) - return compute_attestation_component_deltas(state, matching_head_attestations) + return get_attestation_component_deltas(state, matching_head_attestations) ``` ```python @@ -1465,18 +1468,12 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence _, inactivity_penalties = get_inactivity_penalty_deltas(state) rewards = [ - source_rewards[i] - + target_rewards[i] - + head_rewards[i] - + inclusion_delay_rewards[i] + source_rewards[i] + target_rewards[i] + head_rewards[i] + inclusion_delay_rewards[i] for i in range(len(state.validators)) ] penalties = [ - source_penalties[i] - + target_penalties[i] - + head_penalties[i] - + inactivity_penalties[i] + source_penalties[i] + target_penalties[i] + head_penalties[i] + inactivity_penalties[i] for i in range(len(state.validators)) ] From f35106d9eec822dd02ca768dc5bc5918a6ad05ba Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 5 May 2020 09:43:25 -0600 Subject: [PATCH 37/53] add comment for helper -- has_enouh_for_reward --- tests/core/pyspec/eth2spec/test/helpers/rewards.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index 09c99c3ac..1b428891b 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -10,6 +10,12 @@ class Deltas(Container): def has_enough_for_reward(spec, state, index): + """ + Check if base_reward will be non-zero. + + At very low balances, it is possible for a validator have a positive effective_balance + but a zero base reward. + """ return ( state.validators[index].effective_balance * spec.BASE_REWARD_FACTOR > spec.integer_squareroot(spec.get_total_active_balance(state)) // spec.BASE_REWARDS_PER_EPOCH From f60f8ca332a55b5512179d96267f23ae86849b24 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 6 May 2020 02:10:43 +0800 Subject: [PATCH 38/53] Fix README spec links --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fed65eedb..16f0dc97a 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Core specifications for Eth2 clients be found in [specs](specs/). These are divi ### Phase 0 * [The Beacon Chain](specs/phase0/beacon-chain.md) -* [Fork Choice](specs/phase0/fork-choice.md) +* [Beacon Chain Fork Choice](specs/phase0/fork-choice.md) * [Deposit Contract](specs/phase0/deposit-contract.md) * [Honest Validator](specs/phase0/validator.md) * [P2P Networking](specs/phase0/p2p-interface.md) @@ -22,8 +22,9 @@ Core specifications for Eth2 clients be found in [specs](specs/). These are divi * [From Phase 0 to Phase 1](specs/phase1/phase1-fork.md) * [The Beacon Chain for Shards](specs/phase1/beacon-chain.md) * [Custody Game](specs/phase1/custody-game.md) -* [Shard Transition and Fraud Proofs](specs/phase1/fraud-proofs.md) +* [Shard Transition and Fraud Proofs](specs/phase1/shard-transition.md) * [Light client syncing protocol](specs/phase1/light-client-sync.md) +* [Beacon Chain Fork Choice for Shards](specs/phase1/fork-choice.md) ### Phase 2 From d26cfd2e599abc0f8f2691d21406fdaddf9de300 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 5 May 2020 13:08:41 -0600 Subject: [PATCH 39/53] Apply suggestions from code review from @hwwhww Co-authored-by: Hsiao-Wei Wang --- tests/core/pyspec/eth2spec/test/helpers/rewards.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index 1b428891b..17849e7d1 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -99,7 +99,7 @@ def test_half_full(spec, state, runner): def test_one_attestation_one_correct(spec, state, runner): prepare_state_with_full_attestations(spec, state) - # Remove half of attestations + # Remove all attestations except for the first one state.previous_epoch_attestations = state.previous_epoch_attestations[:1] yield from runner(spec, state) @@ -120,11 +120,9 @@ def test_some_very_low_effective_balances_that_attested(spec, state, runner): prepare_state_with_full_attestations(spec, state) # Set some balances to be very low (including 0) - state.validators[0].effective_balance = 0 - state.validators[1].effective_balance = 2 - state.validators[2].effective_balance = 10 - state.validators[3].effective_balance = 100 - state.validators[4].effective_balance = 1000 + assert len(state.validators) >= 5: + for i, index in enumerate(5): + state.validators[index].effective_balance = i yield from runner(spec, state) @@ -135,7 +133,7 @@ def test_some_very_low_effective_balances_that_did_not_attest(spec, state, runne # Remove attestation attestation = state.previous_epoch_attestations[0] state.previous_epoch_attestations = state.previous_epoch_attestations[1:] - # Set removed indices effective balance to zero + # Set removed indices effective balance to very low amount indices = spec.get_unslashed_attesting_indices(state, [attestation]) for i, index in enumerate(indices): state.validators[index].effective_balance = i From b2dfb6cebe9e40e2f2bef2cb4e03397893d76e1a Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 5 May 2020 13:33:44 -0600 Subject: [PATCH 40/53] PR feedback from @hwwhww --- .../pyspec/eth2spec/test/helpers/rewards.py | 32 +++++++-------- .../phase_0/rewards/test_get_head_deltas.py | 26 ++++++------ .../test_get_inactivity_penalty_deltas.py | 40 +++++++++---------- .../test_get_inclusion_delay_deltas.py | 19 ++++----- .../phase_0/rewards/test_get_source_deltas.py | 30 ++++++++------ .../phase_0/rewards/test_get_target_deltas.py | 30 ++++++++------ 6 files changed, 93 insertions(+), 84 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index 17849e7d1..deee1b120 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -61,19 +61,19 @@ def run_attestation_component_deltas(spec, state, component_delta_fn, matching_a assert penalties[index] == 0 -def test_empty(spec, state, runner): +def run_test_empty(spec, state, runner): # Do not add any attestations to state yield from runner(spec, state) -def test_full_all_correct(spec, state, runner): +def run_test_full_all_correct(spec, state, runner): prepare_state_with_full_attestations(spec, state) yield from runner(spec, state) -def test_full_but_partial_participation(spec, state, runner, rng=Random(5522)): +def run_test_full_but_partial_participation(spec, state, runner, rng=Random(5522)): prepare_state_with_full_attestations(spec, state) for a in state.previous_epoch_attestations: @@ -82,7 +82,7 @@ def test_full_but_partial_participation(spec, state, runner, rng=Random(5522)): yield from runner(spec, state) -def test_partial(spec, state, fraction_filled, runner): +def run_test_partial(spec, state, fraction_filled, runner): prepare_state_with_full_attestations(spec, state) # Remove portion of attestations @@ -92,11 +92,11 @@ def test_partial(spec, state, fraction_filled, runner): yield from runner(spec, state) -def test_half_full(spec, state, runner): - yield from test_partial(spec, state, 0.5, runner) +def run_test_half_full(spec, state, runner): + yield from run_test_partial(spec, state, 0.5, runner) -def test_one_attestation_one_correct(spec, state, runner): +def run_test_one_attestation_one_correct(spec, state, runner): prepare_state_with_full_attestations(spec, state) # Remove all attestations except for the first one @@ -105,7 +105,7 @@ def test_one_attestation_one_correct(spec, state, runner): yield from runner(spec, state) -def test_with_slashed_validators(spec, state, runner): +def run_test_with_slashed_validators(spec, state, runner): prepare_state_with_full_attestations(spec, state) # Slash half of validators @@ -115,19 +115,19 @@ def test_with_slashed_validators(spec, state, runner): yield from runner(spec, state) -def test_some_very_low_effective_balances_that_attested(spec, state, runner): +def run_test_some_very_low_effective_balances_that_attested(spec, state, runner): state.balances prepare_state_with_full_attestations(spec, state) # Set some balances to be very low (including 0) - assert len(state.validators) >= 5: - for i, index in enumerate(5): + assert len(state.validators) >= 5 + for i, index in enumerate(range(5)): state.validators[index].effective_balance = i yield from runner(spec, state) -def test_some_very_low_effective_balances_that_did_not_attest(spec, state, runner): +def run_test_some_very_low_effective_balances_that_did_not_attest(spec, state, runner): prepare_state_with_full_attestations(spec, state) # Remove attestation @@ -141,7 +141,7 @@ def test_some_very_low_effective_balances_that_did_not_attest(spec, state, runne yield from runner(spec, state) -def test_full_fraction_incorrect(spec, state, correct_target, correct_head, fraction_incorrect, runner): +def run_test_full_fraction_incorrect(spec, state, correct_target, correct_head, fraction_incorrect, runner): prepare_state_with_full_attestations(spec, state) # Make fraction_incorrect of pending attestations have bad target/head as specified @@ -155,14 +155,14 @@ def test_full_fraction_incorrect(spec, state, correct_target, correct_head, frac yield from runner(spec, state) -def test_full_random(spec, state, runner, rng=Random(8020)): +def run_test_full_random(spec, state, runner, rng=Random(8020)): prepare_state_with_full_attestations(spec, state) for pending_attestation in state.previous_epoch_attestations: - # 1/3 have bad target + # ~1/3 have bad target if rng.randint(0, 2) == 0: pending_attestation.data.target.root = b'\x55' * 32 - # 1/3 have bad head + # ~1/3 have bad head if rng.randint(0, 2) == 0: pending_attestation.data.beacon_block_root = b'\x66' * 32 # ~50% participation diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py index 237920732..19a3e7191 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py @@ -22,49 +22,49 @@ def run_get_head_deltas(spec, state): @with_all_phases @spec_state_test def test_empty(spec, state): - yield from rewards_helpers.test_empty(spec, state, run_get_head_deltas) + yield from rewards_helpers.run_test_empty(spec, state, run_get_head_deltas) @with_all_phases @spec_state_test def test_full_all_correct(spec, state): - yield from rewards_helpers.test_full_all_correct(spec, state, run_get_head_deltas) + yield from rewards_helpers.run_test_full_all_correct(spec, state, run_get_head_deltas) @with_all_phases @spec_state_test def test_half_full(spec, state): - yield from rewards_helpers.test_half_full(spec, state, run_get_head_deltas) + yield from rewards_helpers.run_test_half_full(spec, state, run_get_head_deltas) @with_all_phases @spec_state_test def test_full_but_partial_participation(spec, state): - yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_head_deltas) + yield from rewards_helpers.run_test_full_but_partial_participation(spec, state, run_get_head_deltas) @with_all_phases @spec_state_test def test_one_attestation_one_correct(spec, state): - yield from rewards_helpers.test_one_attestation_one_correct(spec, state, run_get_head_deltas) + yield from rewards_helpers.run_test_one_attestation_one_correct(spec, state, run_get_head_deltas) @with_all_phases @spec_state_test def test_with_slashed_validators(spec, state): - yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_head_deltas) + yield from rewards_helpers.run_test_with_slashed_validators(spec, state, run_get_head_deltas) @with_all_phases @spec_state_test def test_some_very_low_effective_balances_that_attested(spec, state): - yield from rewards_helpers.test_some_very_low_effective_balances_that_attested(spec, state, run_get_head_deltas) + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_attested(spec, state, run_get_head_deltas) @with_all_phases @spec_state_test def test_some_very_low_effective_balances_that_did_not_attest(spec, state): - yield from rewards_helpers.test_some_very_low_effective_balances_that_did_not_attest( + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_did_not_attest( spec, state, run_get_head_deltas, @@ -74,7 +74,7 @@ def test_some_very_low_effective_balances_that_did_not_attest(spec, state): @with_all_phases @spec_state_test def test_full_half_correct_target_incorrect_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=True, correct_head=False, @@ -86,7 +86,7 @@ def test_full_half_correct_target_incorrect_head(spec, state): @with_all_phases @spec_state_test def test_full_correct_target_incorrect_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=True, correct_head=False, @@ -98,7 +98,7 @@ def test_full_correct_target_incorrect_head(spec, state): @with_all_phases @spec_state_test def test_full_half_incorrect_target_incorrect_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=False, correct_head=False, @@ -110,7 +110,7 @@ def test_full_half_incorrect_target_incorrect_head(spec, state): @with_all_phases @spec_state_test def test_full_half_incorrect_target_correct_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=False, correct_head=True, @@ -122,4 +122,4 @@ def test_full_half_incorrect_target_correct_head(spec, state): @with_all_phases @spec_state_test def test_full_random(spec, state): - yield from rewards_helpers.test_full_random(spec, state, run_get_head_deltas) + yield from rewards_helpers.run_test_full_random(spec, state, run_get_head_deltas) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py index a81451e38..fb24e09d8 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py @@ -60,85 +60,85 @@ def transition_state_to_leak(spec, state, epochs=None): @with_all_phases @spec_state_test def test_empty_no_leak(spec, state): - yield from rewards_helpers.test_empty(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_empty(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_empty_leak(spec, state): transition_state_to_leak(spec, state) - yield from rewards_helpers.test_empty(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_empty(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_full_no_leak(spec, state): - yield from rewards_helpers.test_full_all_correct(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_full_all_correct(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_full_leak(spec, state): transition_state_to_leak(spec, state) - yield from rewards_helpers.test_full_all_correct(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_full_all_correct(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_half_full_no_leak(spec, state): - yield from rewards_helpers.test_half_full(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_half_full(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_half_full_leak(spec, state): transition_state_to_leak(spec, state) - yield from rewards_helpers.test_half_full(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_half_full(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_quarter_full_no_leak(spec, state): - yield from rewards_helpers.test_partial(spec, state, 0.25, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_partial(spec, state, 0.25, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_quarter_full_leak(spec, state): transition_state_to_leak(spec, state) - yield from rewards_helpers.test_partial(spec, state, 0.25, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_partial(spec, state, 0.25, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_full_but_partial_participation_no_leak(spec, state): - yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_full_but_partial_participation(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_full_but_partial_participation_leak(spec, state): transition_state_to_leak(spec, state) - yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_full_but_partial_participation(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_with_slashed_validators_no_leak(spec, state): - yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_with_slashed_validators(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_with_slashed_validators_leak(spec, state): transition_state_to_leak(spec, state) - yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_with_slashed_validators(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_some_very_low_effective_balances_that_attested_no_leak(spec, state): - yield from rewards_helpers.test_some_very_low_effective_balances_that_attested( + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_attested( spec, state, run_get_inactivity_penalty_deltas, @@ -149,7 +149,7 @@ def test_some_very_low_effective_balances_that_attested_no_leak(spec, state): @spec_state_test def test_some_very_low_effective_balances_that_attested_leak(spec, state): transition_state_to_leak(spec, state) - yield from rewards_helpers.test_some_very_low_effective_balances_that_attested( + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_attested( spec, state, run_get_inactivity_penalty_deltas, @@ -159,7 +159,7 @@ def test_some_very_low_effective_balances_that_attested_leak(spec, state): @with_all_phases @spec_state_test def test_some_very_low_effective_balances_that_did_not_attest_no_leak(spec, state): - yield from rewards_helpers.test_some_very_low_effective_balances_that_did_not_attest( + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_did_not_attest( spec, state, run_get_inactivity_penalty_deltas, @@ -170,7 +170,7 @@ def test_some_very_low_effective_balances_that_did_not_attest_no_leak(spec, stat @spec_state_test def test_some_very_low_effective_balances_that_did_not_attest_leak(spec, state): transition_state_to_leak(spec, state) - yield from rewards_helpers.test_some_very_low_effective_balances_that_did_not_attest( + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_did_not_attest( spec, state, run_get_inactivity_penalty_deltas, @@ -180,25 +180,25 @@ def test_some_very_low_effective_balances_that_did_not_attest_leak(spec, state): @with_all_phases @spec_state_test def test_full_random_no_leak(spec, state): - yield from rewards_helpers.test_full_random(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_full_random(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_full_random_leak(spec, state): transition_state_to_leak(spec, state) - yield from rewards_helpers.test_full_random(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_full_random(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_full_random_five_epoch_leak(spec, state): transition_state_to_leak(spec, state, epochs=5) - yield from rewards_helpers.test_full_random(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_full_random(spec, state, run_get_inactivity_penalty_deltas) @with_all_phases @spec_state_test def test_full_random_ten_epoch_leak(spec, state): transition_state_to_leak(spec, state, epochs=10) - yield from rewards_helpers.test_full_random(spec, state, run_get_inactivity_penalty_deltas) + yield from rewards_helpers.run_test_full_random(spec, state, run_get_inactivity_penalty_deltas) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py index b9ae9882c..9eda75290 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py @@ -64,43 +64,43 @@ def run_get_inclusion_delay_deltas(spec, state): @with_all_phases @spec_state_test def test_empty(spec, state): - yield from rewards_helpers.test_empty(spec, state, run_get_inclusion_delay_deltas) + yield from rewards_helpers.run_test_empty(spec, state, run_get_inclusion_delay_deltas) @with_all_phases @spec_state_test def test_full(spec, state): - yield from rewards_helpers.test_full_all_correct(spec, state, run_get_inclusion_delay_deltas) + yield from rewards_helpers.run_test_full_all_correct(spec, state, run_get_inclusion_delay_deltas) @with_all_phases @spec_state_test def test_half_full(spec, state): - yield from rewards_helpers.test_half_full(spec, state, run_get_inclusion_delay_deltas) + yield from rewards_helpers.run_test_half_full(spec, state, run_get_inclusion_delay_deltas) @with_all_phases @spec_state_test def test_quarter_full(spec, state): - yield from rewards_helpers.test_partial(spec, state, 0.25, run_get_inclusion_delay_deltas) + yield from rewards_helpers.run_test_partial(spec, state, 0.25, run_get_inclusion_delay_deltas) @with_all_phases @spec_state_test def test_full_but_partial_participation(spec, state): - yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_inclusion_delay_deltas) + yield from rewards_helpers.run_test_full_but_partial_participation(spec, state, run_get_inclusion_delay_deltas) @with_all_phases @spec_state_test def test_with_slashed_validators(spec, state): - yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_inclusion_delay_deltas) + yield from rewards_helpers.run_test_with_slashed_validators(spec, state, run_get_inclusion_delay_deltas) @with_all_phases @spec_state_test def test_some_very_low_effective_balances_that_attested(spec, state): - yield from rewards_helpers.test_some_very_low_effective_balances_that_attested( + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_attested( spec, state, run_get_inclusion_delay_deltas @@ -110,7 +110,7 @@ def test_some_very_low_effective_balances_that_attested(spec, state): @with_all_phases @spec_state_test def test_full_random(spec, state): - yield from rewards_helpers.test_full_random(spec, state, run_get_inclusion_delay_deltas) + yield from rewards_helpers.run_test_full_random(spec, state, run_get_inclusion_delay_deltas) @with_all_phases @@ -179,7 +179,8 @@ def test_duplicate_attestations_at_later_slots(spec, state): max_slot = max([a.data.slot + a.inclusion_delay for a in state.previous_epoch_attestations]) later_attestations = [] for a in state.previous_epoch_attestations: - # Do not create later duplicate if goes into next epoch + # Only have proposers for previous epoch so do not create later + # duplicate if slot exceeds the max slot in previous_epoch_attestations if a.data.slot + a.inclusion_delay >= max_slot: continue later_a = a.copy() diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py index 842728db8..efdc1fcb0 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py @@ -22,49 +22,53 @@ def run_get_source_deltas(spec, state): @with_all_phases @spec_state_test def test_empty(spec, state): - yield from rewards_helpers.test_empty(spec, state, run_get_source_deltas) + yield from rewards_helpers.run_test_empty(spec, state, run_get_source_deltas) @with_all_phases @spec_state_test def test_full_all_correct(spec, state): - yield from rewards_helpers.test_full_all_correct(spec, state, run_get_source_deltas) + yield from rewards_helpers.run_test_full_all_correct(spec, state, run_get_source_deltas) @with_all_phases @spec_state_test def test_half_full(spec, state): - yield from rewards_helpers.test_half_full(spec, state, run_get_source_deltas) + yield from rewards_helpers.run_test_half_full(spec, state, run_get_source_deltas) @with_all_phases @spec_state_test def test_full_but_partial_participation(spec, state): - yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_source_deltas) + yield from rewards_helpers.run_test_full_but_partial_participation(spec, state, run_get_source_deltas) @with_all_phases @spec_state_test def test_one_attestation_one_correct(spec, state): - yield from rewards_helpers.test_one_attestation_one_correct(spec, state, run_get_source_deltas) + yield from rewards_helpers.run_test_one_attestation_one_correct(spec, state, run_get_source_deltas) @with_all_phases @spec_state_test def test_with_slashed_validators(spec, state): - yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_source_deltas) + yield from rewards_helpers.run_test_with_slashed_validators(spec, state, run_get_source_deltas) @with_all_phases @spec_state_test def test_some_very_low_effective_balances_that_attested(spec, state): - yield from rewards_helpers.test_some_very_low_effective_balances_that_attested(spec, state, run_get_source_deltas) + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_attested( + spec, + state, + run_get_source_deltas + ) @with_all_phases @spec_state_test def test_some_very_low_effective_balances_that_did_not_attest(spec, state): - yield from rewards_helpers.test_some_very_low_effective_balances_that_did_not_attest( + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_did_not_attest( spec, state, run_get_source_deltas, @@ -81,7 +85,7 @@ def test_some_very_low_effective_balances_that_did_not_attest(spec, state): @with_all_phases @spec_state_test def test_full_half_correct_target_incorrect_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=True, correct_head=False, @@ -93,7 +97,7 @@ def test_full_half_correct_target_incorrect_head(spec, state): @with_all_phases @spec_state_test def test_full_correct_target_incorrect_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=True, correct_head=False, @@ -105,7 +109,7 @@ def test_full_correct_target_incorrect_head(spec, state): @with_all_phases @spec_state_test def test_full_half_incorrect_target_incorrect_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=False, correct_head=False, @@ -117,7 +121,7 @@ def test_full_half_incorrect_target_incorrect_head(spec, state): @with_all_phases @spec_state_test def test_full_half_incorrect_target_correct_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=False, correct_head=True, @@ -129,4 +133,4 @@ def test_full_half_incorrect_target_correct_head(spec, state): @with_all_phases @spec_state_test def test_full_random(spec, state): - yield from rewards_helpers.test_full_random(spec, state, run_get_source_deltas) + yield from rewards_helpers.run_test_full_random(spec, state, run_get_source_deltas) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py index ad2a65d2a..ad7198c84 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py @@ -22,49 +22,53 @@ def run_get_target_deltas(spec, state): @with_all_phases @spec_state_test def test_empty(spec, state): - yield from rewards_helpers.test_empty(spec, state, run_get_target_deltas) + yield from rewards_helpers.run_test_empty(spec, state, run_get_target_deltas) @with_all_phases @spec_state_test def test_full_all_correct(spec, state): - yield from rewards_helpers.test_full_all_correct(spec, state, run_get_target_deltas) + yield from rewards_helpers.run_test_full_all_correct(spec, state, run_get_target_deltas) @with_all_phases @spec_state_test def test_half_full(spec, state): - yield from rewards_helpers.test_half_full(spec, state, run_get_target_deltas) + yield from rewards_helpers.run_test_half_full(spec, state, run_get_target_deltas) @with_all_phases @spec_state_test def test_full_but_partial_participation(spec, state): - yield from rewards_helpers.test_full_but_partial_participation(spec, state, run_get_target_deltas) + yield from rewards_helpers.run_test_full_but_partial_participation(spec, state, run_get_target_deltas) @with_all_phases @spec_state_test def test_one_attestation_one_correct(spec, state): - yield from rewards_helpers.test_one_attestation_one_correct(spec, state, run_get_target_deltas) + yield from rewards_helpers.run_test_one_attestation_one_correct(spec, state, run_get_target_deltas) @with_all_phases @spec_state_test def test_with_slashed_validators(spec, state): - yield from rewards_helpers.test_with_slashed_validators(spec, state, run_get_target_deltas) + yield from rewards_helpers.run_test_with_slashed_validators(spec, state, run_get_target_deltas) @with_all_phases @spec_state_test def test_some_very_low_effective_balances_that_attested(spec, state): - yield from rewards_helpers.test_some_very_low_effective_balances_that_attested(spec, state, run_get_target_deltas) + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_attested( + spec, + state, + run_get_target_deltas + ) @with_all_phases @spec_state_test def test_some_very_low_effective_balances_that_did_not_attest(spec, state): - yield from rewards_helpers.test_some_very_low_effective_balances_that_did_not_attest( + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_did_not_attest( spec, state, run_get_target_deltas, @@ -74,7 +78,7 @@ def test_some_very_low_effective_balances_that_did_not_attest(spec, state): @with_all_phases @spec_state_test def test_full_half_correct_target_incorrect_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=True, correct_head=False, @@ -86,7 +90,7 @@ def test_full_half_correct_target_incorrect_head(spec, state): @with_all_phases @spec_state_test def test_full_correct_target_incorrect_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=True, correct_head=False, @@ -98,7 +102,7 @@ def test_full_correct_target_incorrect_head(spec, state): @with_all_phases @spec_state_test def test_full_half_incorrect_target_incorrect_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=False, correct_head=False, @@ -110,7 +114,7 @@ def test_full_half_incorrect_target_incorrect_head(spec, state): @with_all_phases @spec_state_test def test_full_half_incorrect_target_correct_head(spec, state): - yield from rewards_helpers.test_full_fraction_incorrect( + yield from rewards_helpers.run_test_full_fraction_incorrect( spec, state, correct_target=False, correct_head=True, @@ -122,4 +126,4 @@ def test_full_half_incorrect_target_correct_head(spec, state): @with_all_phases @spec_state_test def test_full_random(spec, state): - yield from rewards_helpers.test_full_random(spec, state, run_get_target_deltas) + yield from rewards_helpers.run_test_full_random(spec, state, run_get_target_deltas) From 4f401133e14fac50ea9fce42264b3c09f6ac39b0 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 5 May 2020 15:37:14 -0600 Subject: [PATCH 41/53] address PR feedback from @protolambda --- specs/phase0/beacon-chain.md | 4 +-- .../pyspec/eth2spec/test/helpers/rewards.py | 8 +++--- .../test_get_inactivity_penalty_deltas.py | 10 ++----- .../test_get_inclusion_delay_deltas.py | 11 ++------ tests/formats/rewards/README.md | 27 +++++++++++-------- 5 files changed, 26 insertions(+), 34 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 82fbb2856..fe8fcc9bd 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1370,8 +1370,8 @@ def get_attestation_component_deltas(state: BeaconState, """ Helper with shared logic for use by get source, target, and head deltas functions """ - rewards = [Gwei(0) for _ in range(len(state.validators))] - penalties = [Gwei(0) for _ in range(len(state.validators))] + rewards = [Gwei(0)] * len(state.validators) + penalties = [Gwei(0)] * len(state.validators) total_balance = get_total_active_balance(state) unslashed_attesting_indices = get_unslashed_attesting_indices(state, attestations) attesting_balance = get_total_balance(state, unslashed_attesting_indices) diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index deee1b120..6e470d9e9 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -1,12 +1,13 @@ from random import Random +from eth2spec.phase0 import spec as spec_phase0 from eth2spec.test.helpers.attestations import prepare_state_with_full_attestations from eth2spec.utils.ssz.ssz_typing import Container, uint64, List -# HACK to get the generators outputting correctly class Deltas(Container): - delta_list: List[uint64, 2**30] + rewards: List[uint64, spec_phase0.VALIDATOR_REGISTRY_LIMIT] + penalties: List[uint64, spec_phase0.VALIDATOR_REGISTRY_LIMIT] def has_enough_for_reward(spec, state, index): @@ -33,8 +34,7 @@ def run_attestation_component_deltas(spec, state, component_delta_fn, matching_a rewards, penalties = component_delta_fn(state) - yield 'rewards', Deltas(delta_list=rewards) - yield 'penalties', Deltas(delta_list=penalties) + yield 'deltas', Deltas(rewards=rewards, penalties=penalties) matching_attestations = matching_att_fn(state, spec.get_previous_epoch(state)) matching_indices = spec.get_unslashed_attesting_indices(state, matching_attestations) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py index fb24e09d8..657acbdbb 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py @@ -1,13 +1,8 @@ from eth2spec.test.context import with_all_phases, spec_state_test from eth2spec.test.helpers.rewards import has_enough_for_reward from eth2spec.test.helpers.state import next_epoch +from eth2spec.test.helpers.rewards import Deltas import eth2spec.test.helpers.rewards as rewards_helpers -from eth2spec.utils.ssz.ssz_typing import Container, uint64, List - - -# HACK to get the generators outputting correctly -class Deltas(Container): - delta_list: List[uint64, 2**30] def run_get_inactivity_penalty_deltas(spec, state): @@ -22,8 +17,7 @@ def run_get_inactivity_penalty_deltas(spec, state): rewards, penalties = spec.get_inactivity_penalty_deltas(state) - yield 'rewards', Deltas(delta_list=rewards) - yield 'penalties', Deltas(delta_list=penalties) + yield 'deltas', Deltas(rewards=rewards, penalties=penalties) matching_attestations = spec.get_matching_target_attestations(state, spec.get_previous_epoch(state)) matching_attesting_indices = spec.get_unslashed_attesting_indices(state, matching_attestations) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py index 9eda75290..320f59a9d 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py @@ -2,14 +2,8 @@ from random import Random from eth2spec.test.context import with_all_phases, spec_state_test from eth2spec.test.helpers.attestations import prepare_state_with_full_attestations -from eth2spec.test.helpers.rewards import has_enough_for_reward +from eth2spec.test.helpers.rewards import Deltas, has_enough_for_reward import eth2spec.test.helpers.rewards as rewards_helpers -from eth2spec.utils.ssz.ssz_typing import Container, uint64, List - - -# HACK to get the generators outputting correctly -class Deltas(Container): - delta_list: List[uint64, 2**30] def run_get_inclusion_delay_deltas(spec, state): @@ -24,8 +18,7 @@ def run_get_inclusion_delay_deltas(spec, state): rewards, penalties = spec.get_inclusion_delay_deltas(state) - yield 'rewards', Deltas(delta_list=rewards) - yield 'penalties', Deltas(delta_list=penalties) + yield 'deltas', Deltas(rewards=rewards, penalties=penalties) eligible_attestations = spec.get_matching_source_attestations(state, spec.get_previous_epoch(state)) attesting_indices = spec.get_unslashed_attesting_indices(state, eligible_attestations) diff --git a/tests/formats/rewards/README.md b/tests/formats/rewards/README.md index dce2b5ac8..bffe2e7de 100644 --- a/tests/formats/rewards/README.md +++ b/tests/formats/rewards/README.md @@ -4,7 +4,6 @@ The different rewards deltas sub-functions are testing individually with the tes There is no "change" factor, the rewards/penalties outputs are pure functions with just the pre-state as input. Hence, the format is shared between each test-handler. (See test condition documentation on how to run the tests.) - ## Test case format ### `meta.yaml` @@ -12,23 +11,29 @@ Hence, the format is shared between each test-handler. (See test condition docum ```yaml description: string -- Optional description of test case, purely for debugging purposes. Tests should use the directory name of the test case as identifier, not the description. -bls_setting: int -- see general test-format spec. ``` +_Note_: No signature verification happens within rewards sub-functions. These + tests can safely be run with or without BLS enabled. + ### `pre.yaml` A YAML-encoded `BeaconState`, the state before running the rewards sub-function. Also available as `pre.ssz`. +### `deltas.yaml` -### `rewards.yaml` +A YAML-encoded `Deltas` representing the rewards and penalties returned by the rewards sub-function -A YAML-encoded list of integers representing the 0th item in the return value (i.e. the rewards deltas) +Where `Deltas` is defined as: +```python +class Deltas(Container): + rewards: List[uint64, VALIDATOR_REGISTRY_LIMIT] + penalties: List[uint64, VALIDATOR_REGISTRY_LIMIT] +``` -### `penalties.yaml` - -A YAML-encoded list of integers representing the 1st item in the return value (i.e. the penalties deltas) +Also available as `rewards.ssz`. ## Condition @@ -38,10 +43,10 @@ This excludes all other parts of `process_rewards_and_penalties` The provided pre-state is ready to be input into the designated handler. -The resulting `rewards`/`penalties` should match the return values of the -handler. Specifically the following must hold true: +The provided `deltas` should match the return values of the + handler. Specifically the following must hold true: ```python - rewards == handler(state)[0] - penalties == handler(state)[1] + deltas.rewards == handler(state)[0] + deltas.penalties == handler(state)[1] ``` From f0742b2f2def1de421469919384faea05db953c5 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 5 May 2020 16:28:29 -0600 Subject: [PATCH 42/53] add exited tests for rewards. make some valiators exited/withdrawable in slashed tests --- .../pyspec/eth2spec/test/helpers/rewards.py | 44 +++++++++++++++++-- .../phase_0/rewards/test_get_head_deltas.py | 6 +++ .../test_get_inactivity_penalty_deltas.py | 13 ++++++ .../test_get_inclusion_delay_deltas.py | 6 +++ .../phase_0/rewards/test_get_source_deltas.py | 6 +++ 5 files changed, 71 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index 6e470d9e9..fa041dae4 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -2,6 +2,7 @@ from random import Random from eth2spec.phase0 import spec as spec_phase0 from eth2spec.test.helpers.attestations import prepare_state_with_full_attestations +from eth2spec.test.helpers.state import next_epoch from eth2spec.utils.ssz.ssz_typing import Container, uint64, List @@ -61,6 +62,32 @@ def run_attestation_component_deltas(spec, state, component_delta_fn, matching_a assert penalties[index] == 0 +def exit_random_validators(spec, state, rng): + if spec.get_current_epoch(state) < 5: + # Move epochs forward to allow for some validators already exited/withdrawable + for _ in range(5): + next_epoch(spec, state) + + current_epoch = spec.get_current_epoch(state) + # Exit ~1/2 of validators + for validator in state.validators: + if rng.choice([True, False]): + continue + + validator.exit_epoch = rng.choice([current_epoch - 1, current_epoch - 2, current_epoch - 3]) + # ~1/2 are withdrawable + if rng.choice([True, False]): + validator.withdrawable_epoch = current_epoch + else: + validator.withdrawable_epoch = current_epoch + 1 + + +def slash_random_validators(spec, state, rng): + # Slash ~1/2 of validators + for validator in state.validators: + validator.slashed = rng.choice([True, False]) + + def run_test_empty(spec, state, runner): # Do not add any attestations to state @@ -105,12 +132,18 @@ def run_test_one_attestation_one_correct(spec, state, runner): yield from runner(spec, state) -def run_test_with_slashed_validators(spec, state, runner): +def run_test_with_exited_validators(spec, state, runner, rng=Random(1337)): + exit_random_validators(spec, state, rng) prepare_state_with_full_attestations(spec, state) - # Slash half of validators - for validator in state.validators[:len(state.validators) // 2]: - validator.slashed = True + yield from runner(spec, state) + + +def run_test_with_slashed_validators(spec, state, runner, rng=Random(3322)): + exit_random_validators(spec, state, rng) + slash_random_validators(spec, state, rng) + + prepare_state_with_full_attestations(spec, state) yield from runner(spec, state) @@ -156,6 +189,9 @@ def run_test_full_fraction_incorrect(spec, state, correct_target, correct_head, def run_test_full_random(spec, state, runner, rng=Random(8020)): + exit_random_validators(spec, state, rng) + slash_random_validators(spec, state, rng) + prepare_state_with_full_attestations(spec, state) for pending_attestation in state.previous_epoch_attestations: diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py index 19a3e7191..ffaad15af 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py @@ -49,6 +49,12 @@ def test_one_attestation_one_correct(spec, state): yield from rewards_helpers.run_test_one_attestation_one_correct(spec, state, run_get_head_deltas) +@with_all_phases +@spec_state_test +def test_with_exited_validators(spec, state): + yield from rewards_helpers.run_test_with_exited_validators(spec, state, run_get_head_deltas) + + @with_all_phases @spec_state_test def test_with_slashed_validators(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py index 657acbdbb..52ad631ca 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py @@ -116,6 +116,19 @@ def test_full_but_partial_participation_leak(spec, state): yield from rewards_helpers.run_test_full_but_partial_participation(spec, state, run_get_inactivity_penalty_deltas) +@with_all_phases +@spec_state_test +def test_with_exited_validators_no_leak(spec, state): + yield from rewards_helpers.run_test_with_exited_validators(spec, state, run_get_inactivity_penalty_deltas) + + +@with_all_phases +@spec_state_test +def test_with_exited_validators_leak(spec, state): + transition_state_to_leak(spec, state) + yield from rewards_helpers.run_test_with_exited_validators(spec, state, run_get_inactivity_penalty_deltas) + + @with_all_phases @spec_state_test def test_with_slashed_validators_no_leak(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py index 320f59a9d..29ac10131 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py @@ -84,6 +84,12 @@ def test_full_but_partial_participation(spec, state): yield from rewards_helpers.run_test_full_but_partial_participation(spec, state, run_get_inclusion_delay_deltas) +@with_all_phases +@spec_state_test +def test_with_exited_validators(spec, state): + yield from rewards_helpers.run_test_with_exited_validators(spec, state, run_get_inclusion_delay_deltas) + + @with_all_phases @spec_state_test def test_with_slashed_validators(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py index efdc1fcb0..120260d82 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py @@ -49,6 +49,12 @@ def test_one_attestation_one_correct(spec, state): yield from rewards_helpers.run_test_one_attestation_one_correct(spec, state, run_get_source_deltas) +@with_all_phases +@spec_state_test +def test_with_exited_validators(spec, state): + yield from rewards_helpers.run_test_with_exited_validators(spec, state, run_get_source_deltas) + + @with_all_phases @spec_state_test def test_with_slashed_validators(spec, state): From 4ffa0dba60a4d8670f1a50ddead4f7bce2a66351 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 5 May 2020 16:31:40 -0600 Subject: [PATCH 43/53] Apply suggestions from code review "rewards/penalties" -> "deltas" in throughout test comments/descriptions Co-authored-by: Diederik Loerakker --- tests/core/pyspec/eth2spec/test/helpers/rewards.py | 3 +-- .../eth2spec/test/phase_0/rewards/test_get_head_deltas.py | 3 +-- .../test/phase_0/rewards/test_get_inactivity_penalty_deltas.py | 3 +-- .../test/phase_0/rewards/test_get_inclusion_delay_deltas.py | 3 +-- .../eth2spec/test/phase_0/rewards/test_get_source_deltas.py | 3 +-- .../eth2spec/test/phase_0/rewards/test_get_target_deltas.py | 3 +-- tests/formats/rewards/README.md | 2 +- 7 files changed, 7 insertions(+), 13 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index fa041dae4..eaeb9252e 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -28,8 +28,7 @@ def run_attestation_component_deltas(spec, state, component_delta_fn, matching_a """ Run ``component_delta_fn``, yielding: - pre-state ('pre') - - rewards ('rewards') - - penalties ('penalties') + - deltas ('deltas') """ yield 'pre', state diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py index ffaad15af..2e4b9dbbc 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_head_deltas.py @@ -7,8 +7,7 @@ def run_get_head_deltas(spec, state): """ Run ``get_head_deltas``, yielding: - pre-state ('pre') - - rewards ('rewards') - - penalties ('penalties') + - deltas ('deltas') """ yield from run_attestation_component_deltas( diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py index 52ad631ca..4940cdc63 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inactivity_penalty_deltas.py @@ -9,8 +9,7 @@ def run_get_inactivity_penalty_deltas(spec, state): """ Run ``get_inactivity_penalty_deltas``, yielding: - pre-state ('pre') - - rewards ('rewards') - - penalties ('penalties') + - deltas ('deltas') """ yield 'pre', state diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py index 29ac10131..526d135ed 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_inclusion_delay_deltas.py @@ -10,8 +10,7 @@ def run_get_inclusion_delay_deltas(spec, state): """ Run ``get_inclusion_delay_deltas``, yielding: - pre-state ('pre') - - rewards ('rewards') - - penalties ('penalties') + - deltas ('deltas') """ yield 'pre', state diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py index 120260d82..54f8f3b5d 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_source_deltas.py @@ -7,8 +7,7 @@ def run_get_source_deltas(spec, state): """ Run ``get_source_deltas``, yielding: - pre-state ('pre') - - rewards ('rewards') - - penalties ('penalties') + - deltas ('deltas') """ yield from run_attestation_component_deltas( diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py index ad7198c84..0ae985086 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_get_target_deltas.py @@ -7,8 +7,7 @@ def run_get_target_deltas(spec, state): """ Run ``get_target_deltas``, yielding: - pre-state ('pre') - - rewards ('rewards') - - penalties ('penalties') + - deltas ('deltas') """ yield from run_attestation_component_deltas( diff --git a/tests/formats/rewards/README.md b/tests/formats/rewards/README.md index bffe2e7de..f70a20f9c 100644 --- a/tests/formats/rewards/README.md +++ b/tests/formats/rewards/README.md @@ -33,7 +33,7 @@ class Deltas(Container): penalties: List[uint64, VALIDATOR_REGISTRY_LIMIT] ``` -Also available as `rewards.ssz`. +Also available as `deltas.ssz`. ## Condition From c8b13c320cf1bacc1177b8c21e1fdd7255850620 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 6 May 2020 18:03:48 +0800 Subject: [PATCH 44/53] Add release & pypi badges --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 16f0dc97a..13c644a77 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ This repository hosts the current Eth2 specifications. Discussions about design ## Specs +[![GitHub release](https://img.shields.io/github/v/release/ethereum/eth2.0-specs)](https://github.com/ethereum/eth2.0-specs/releases/) [![PyPI version](https://badge.fury.io/py/eth2spec.svg)](https://badge.fury.io/py/eth2spec) + + Core specifications for Eth2 clients be found in [specs](specs/). These are divided into phases. Each subsequent phase depends upon the prior. The current phases specified are: ### Phase 0 From ee7d11d18f0fe1bcdab20d279441e782d71b2519 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 7 May 2020 11:58:52 -0600 Subject: [PATCH 45/53] clean up proposer slashing tests and add a couple --- .../test/helpers/proposer_slashings.py | 41 ++++++++++-- .../test_process_proposer_slashing.py | 35 +++++++---- .../eth2spec/test/sanity/test_blocks.py | 62 ++++++++++++++----- 3 files changed, 103 insertions(+), 35 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py b/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py index d753d55dd..8b4b04879 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py +++ b/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py @@ -1,18 +1,49 @@ from eth2spec.test.helpers.block_header import sign_block_header from eth2spec.test.helpers.keys import pubkey_to_privkey +from eth2spec.test.helpers.state import get_balance + + +def check_proposer_slashing_effect(spec, pre_state, state, slashed_index): + slashed_validator = state.validators[slashed_index] + assert slashed_validator.slashed + assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH + assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + proposer_index = spec.get_beacon_proposer_index(state) + slash_penalty = state.validators[slashed_index].effective_balance // spec.MIN_SLASHING_PENALTY_QUOTIENT + whistleblower_reward = state.validators[slashed_index].effective_balance // spec.WHISTLEBLOWER_REWARD_QUOTIENT + if proposer_index != slashed_index: + # slashed validator lost initial slash penalty + assert ( + get_balance(state, slashed_index) + == get_balance(pre_state, slashed_index) - slash_penalty + ) + # block proposer gained whistleblower reward + # >= becase proposer could have reported multiple + assert ( + get_balance(state, proposer_index) + >= get_balance(pre_state, proposer_index) + whistleblower_reward + ) + else: + # proposer reported themself so get penalty and reward + # >= becase proposer could have reported multiple + assert ( + get_balance(state, slashed_index) + >= get_balance(pre_state, slashed_index) - slash_penalty + whistleblower_reward + ) def get_valid_proposer_slashing(spec, state, random_root=b'\x99' * 32, - validator_index=None, signed_1=False, signed_2=False): - if validator_index is None: + slashed_index=None, signed_1=False, signed_2=False): + if slashed_index is None: current_epoch = spec.get_current_epoch(state) - validator_index = spec.get_active_validator_indices(state, current_epoch)[-1] - privkey = pubkey_to_privkey[state.validators[validator_index].pubkey] + slashed_index = spec.get_active_validator_indices(state, current_epoch)[-1] + privkey = pubkey_to_privkey[state.validators[slashed_index].pubkey] slot = state.slot header_1 = spec.BeaconBlockHeader( slot=slot, - proposer_index=validator_index, + proposer_index=slashed_index, parent_root=b'\x33' * 32, state_root=b'\x44' * 32, body_root=b'\x55' * 32, diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_proposer_slashing.py b/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_proposer_slashing.py index 7657518fc..e2a6a3fe0 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_proposer_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_proposer_slashing.py @@ -1,8 +1,9 @@ from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls, with_all_phases +from eth2spec.test.helpers.block import build_empty_block_for_next_slot from eth2spec.test.helpers.block_header import sign_block_header from eth2spec.test.helpers.keys import privkeys -from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing -from eth2spec.test.helpers.state import get_balance, next_epoch +from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing, check_proposer_slashing_effect +from eth2spec.test.helpers.state import next_epoch def run_proposer_slashing_processing(spec, state, proposer_slashing, valid=True): @@ -14,6 +15,8 @@ def run_proposer_slashing_processing(spec, state, proposer_slashing, valid=True) If ``valid == False``, run expecting ``AssertionError`` """ + pre_state = state.copy() + yield 'pre', state yield 'proposer_slashing', proposer_slashing @@ -22,25 +25,31 @@ def run_proposer_slashing_processing(spec, state, proposer_slashing, valid=True) yield 'post', None return - proposer_index = proposer_slashing.signed_header_1.message.proposer_index - pre_proposer_balance = get_balance(state, proposer_index) - spec.process_proposer_slashing(state, proposer_slashing) yield 'post', state - # check if slashed - slashed_validator = state.validators[proposer_index] - 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(state, proposer_index) < pre_proposer_balance + slashed_proposer_index = proposer_slashing.signed_header_1.message.proposer_index + check_proposer_slashing_effect(spec, pre_state, state, slashed_proposer_index) @with_all_phases @spec_state_test def test_success(spec, state): + # Get proposer for next slot + block = build_empty_block_for_next_slot(spec, state) + proposer_index = block.proposer_index + + # Create slashing for same proposer + proposer_slashing = get_valid_proposer_slashing(spec, state, + slashed_index=proposer_index, + signed_1=True, signed_2=True) + + yield from run_proposer_slashing_processing(spec, state, proposer_slashing) + + +@with_all_phases +@spec_state_test +def test_success_slashed_and_proposer_index_the_same(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) yield from run_proposer_slashing_processing(spec, state, proposer_slashing) diff --git a/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py index 3347d4f8b..ddb884ae7 100644 --- a/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py @@ -5,7 +5,7 @@ from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_e transition_unsigned_block from eth2spec.test.helpers.keys import privkeys, pubkeys from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing, get_indexed_attestation_participants -from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing +from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing, check_proposer_slashing_effect from eth2spec.test.helpers.attestations import get_valid_attestation from eth2spec.test.helpers.deposits import prepare_state_and_deposit @@ -228,9 +228,9 @@ def test_proposer_slashing(spec, state): # copy for later balance lookups. pre_state = state.copy() proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) - validator_index = proposer_slashing.signed_header_1.message.proposer_index + slashed_index = proposer_slashing.signed_header_1.message.proposer_index - assert not state.validators[validator_index].slashed + assert not state.validators[slashed_index].slashed yield 'pre', state @@ -245,21 +245,15 @@ def test_proposer_slashing(spec, state): yield 'blocks', [signed_block] yield 'post', state - # check if slashed - slashed_validator = state.validators[validator_index] - 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(state, validator_index) < get_balance(pre_state, validator_index) + check_proposer_slashing_effect(spec, pre_state, state, slashed_index) @with_all_phases @spec_state_test def test_double_same_proposer_slashings_same_block(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) - validator_index = proposer_slashing.signed_header_1.message.proposer_index - assert not state.validators[validator_index].slashed + slashed_index = proposer_slashing.signed_header_1.message.proposer_index + assert not state.validators[slashed_index].slashed yield 'pre', state @@ -274,16 +268,16 @@ def test_double_same_proposer_slashings_same_block(spec, state): @with_all_phases @spec_state_test def test_double_similar_proposer_slashings_same_block(spec, state): - validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] + slashed_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] # Same validator, but different slashable offences in the same block proposer_slashing_1 = get_valid_proposer_slashing(spec, state, random_root=b'\xaa' * 32, - validator_index=validator_index, + slashed_index=slashed_index, signed_1=True, signed_2=True) proposer_slashing_2 = get_valid_proposer_slashing(spec, state, random_root=b'\xbb' * 32, - validator_index=validator_index, + slashed_index=slashed_index, signed_1=True, signed_2=True) - assert not state.validators[validator_index].slashed + assert not state.validators[slashed_index].slashed yield 'pre', state @@ -295,6 +289,40 @@ def test_double_similar_proposer_slashings_same_block(spec, state): yield 'post', None +@with_all_phases +@spec_state_test +def test_multiple_different_proposer_slashings_same_block(spec, state): + pre_state = state.copy() + + num_slashings = 3 + proposer_slashings = [] + for i in range(num_slashings): + slashed_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[i] + assert not state.validators[slashed_index].slashed + + proposer_slashing = get_valid_proposer_slashing(spec, state, + slashed_index=slashed_index, + signed_1=True, signed_2=True) + proposer_slashings.append(proposer_slashing) + + yield 'pre', state + + # + # Add to state via block transition + # + block = build_empty_block_for_next_slot(spec, state) + block.body.proposer_slashings = proposer_slashings + + signed_block = state_transition_and_sign_block(spec, state, block) + + yield 'blocks', [signed_block] + yield 'post', state + + for proposer_slashing in proposer_slashings: + slashed_index = proposer_slashing.signed_header_1.message.proposer_index + check_proposer_slashing_effect(spec, pre_state, state, slashed_index) + + def check_attester_slashing_effect(spec, pre_state, state, validator_index): slashed_validator = state.validators[validator_index] assert slashed_validator.slashed @@ -335,7 +363,7 @@ def test_attester_slashing(spec, state): check_attester_slashing_effect(spec, pre_state, state, validator_index) # TODO: currently mainnet limits attester-slashings per block to 1. -# When this is increased, it should be tested to cover varrious combinations +# When this is increased, it should be tested to cover various combinations # of duplicate slashings and overlaps of slashed attestations within the same block From 4ad3d65d100e7118250ea04a7ea0d066c2410ed5 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 7 May 2020 12:23:37 -0600 Subject: [PATCH 46/53] add multiple exits block sanity test --- .../test/helpers/proposer_slashings.py | 4 +-- .../eth2spec/test/sanity/test_blocks.py | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py b/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py index 8b4b04879..87b4f5ca0 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py +++ b/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py @@ -19,14 +19,14 @@ def check_proposer_slashing_effect(spec, pre_state, state, slashed_index): == get_balance(pre_state, slashed_index) - slash_penalty ) # block proposer gained whistleblower reward - # >= becase proposer could have reported multiple + # >= because proposer could have reported multiple assert ( get_balance(state, proposer_index) >= get_balance(pre_state, proposer_index) + whistleblower_reward ) else: # proposer reported themself so get penalty and reward - # >= becase proposer could have reported multiple + # >= because proposer could have reported multiple assert ( get_balance(state, slashed_index) >= get_balance(pre_state, slashed_index) - slash_penalty + whistleblower_reward diff --git a/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py index ddb884ae7..9faeb1f98 100644 --- a/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py @@ -582,6 +582,38 @@ def test_double_validator_exit_same_block(spec, state): yield 'post', None +@with_phases(['phase0']) +@spec_state_test +def test_multiple_different_validator_exits_same_block(spec, state): + validator_indices = [ + spec.get_active_validator_indices(state, spec.get_current_epoch(state))[i] + for i in range(3) + ] + # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + signed_exits = prepare_signed_exits(spec, state, validator_indices) + yield 'pre', state + + # Add to state via block transition + initiate_exit_block = build_empty_block_for_next_slot(spec, state) + initiate_exit_block.body.voluntary_exits = signed_exits + signed_initiate_exit_block = state_transition_and_sign_block(spec, state, initiate_exit_block) + + for index in validator_indices: + assert state.validators[index].exit_epoch < spec.FAR_FUTURE_EPOCH + + # Process within epoch transition + exit_block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH) + signed_exit_block = state_transition_and_sign_block(spec, state, exit_block) + + yield 'blocks', [signed_initiate_exit_block, signed_exit_block] + yield 'post', state + + for index in validator_indices: + assert state.validators[index].exit_epoch < spec.FAR_FUTURE_EPOCH + + @with_all_phases @spec_state_test def test_balance_driven_status_transitions(spec, state): From 38a5f41c30743ac97794c8a442ea28032b5d3319 Mon Sep 17 00:00:00 2001 From: Nathaniel Jensen Date: Fri, 8 May 2020 20:12:44 +1000 Subject: [PATCH 47/53] [Minor] Fix config example to not assign a return value. --- tests/core/pyspec/eth2spec/config/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/config/README.md b/tests/core/pyspec/eth2spec/config/README.md index ea2b2ccd8..314e68aae 100644 --- a/tests/core/pyspec/eth2spec/config/README.md +++ b/tests/core/pyspec/eth2spec/config/README.md @@ -12,7 +12,7 @@ configs_path = 'configs/' from eth2spec.config import config_util from eth2spec.phase0 import spec from importlib import reload -my_presets = config_util.prepare_config(configs_path, 'mainnet') +config_util.prepare_config(configs_path, 'mainnet') # reload spec to make loaded config effective reload(spec) ``` From 7a130606ac73b72d033b6764285c3a617e322a67 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 8 May 2020 10:50:05 -0600 Subject: [PATCH 48/53] hww feedback --- specs/phase0/beacon-chain.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index fe8fcc9bd..5f04269e1 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1420,7 +1420,6 @@ def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequ Return proposer and inclusion delay micro-rewards/penalties for each validator. """ rewards = [Gwei(0) for _ in range(len(state.validators))] - penalties = [Gwei(0) for _ in range(len(state.validators))] matching_source_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) for index in get_unslashed_attesting_indices(state, matching_source_attestations): attestation = min([ @@ -1431,6 +1430,9 @@ def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequ rewards[attestation.proposer_index] += proposer_reward max_attester_reward = get_base_reward(state, index) - proposer_reward rewards[index] += Gwei(max_attester_reward // attestation.inclusion_delay) + + # No penalties associated with inclusion delay + penalties = [Gwei(0) for _ in range(len(state.validators))] return rewards, penalties ``` @@ -1439,7 +1441,6 @@ def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], S """ Return inactivity reward/penalty deltas for each validator. """ - rewards = [Gwei(0) for _ in range(len(state.validators))] penalties = [Gwei(0) for _ in range(len(state.validators))] finality_delay = get_previous_epoch(state) - state.finalized_checkpoint.epoch @@ -1451,6 +1452,9 @@ def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], S if index not in matching_target_attesting_indices: effective_balance = state.validators[index].effective_balance penalties[index] += Gwei(effective_balance * finality_delay // INACTIVITY_PENALTY_QUOTIENT) + + # No rewards associated with inactivity penalties + rewards = [Gwei(0) for _ in range(len(state.validators))] return rewards, penalties ``` From aef564733af3644801ed0b6449ac67f5f56ac326 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Wed, 13 May 2020 10:56:52 -0700 Subject: [PATCH 49/53] Update beacon-chain.md --- specs/phase1/beacon-chain.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 35f6a6425..fccf157aa 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -476,7 +476,7 @@ def get_online_validator_indices(state: BeaconState) -> Set[ValidatorIndex]: ```python def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) -> Sequence[ValidatorIndex]: source_epoch = epoch - epoch % SHARD_COMMITTEE_PERIOD - if source_epoch > 0: + if source_epoch > SHARD_COMMITTEE_PERIOD: source_epoch -= SHARD_COMMITTEE_PERIOD active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) seed = get_seed(beacon_state, source_epoch, DOMAIN_SHARD_COMMITTEE) @@ -494,7 +494,7 @@ def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) - ```python def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: source_epoch = epoch - epoch % LIGHT_CLIENT_COMMITTEE_PERIOD - if source_epoch > 0: + if source_epoch > LIGHT_CLIENT_COMMITTEE_PERIOD: source_epoch -= LIGHT_CLIENT_COMMITTEE_PERIOD active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) seed = get_seed(beacon_state, source_epoch, DOMAIN_LIGHT_CLIENT) From f3448e51dd1faed2e769b26c48ed695b7e2d395c Mon Sep 17 00:00:00 2001 From: terence tsao Date: Thu, 14 May 2020 06:21:36 -0700 Subject: [PATCH 50/53] Update specs/phase1/beacon-chain.md Co-authored-by: Hsiao-Wei Wang --- specs/phase1/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index fccf157aa..4a75cb076 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -476,7 +476,7 @@ def get_online_validator_indices(state: BeaconState) -> Set[ValidatorIndex]: ```python def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) -> Sequence[ValidatorIndex]: source_epoch = epoch - epoch % SHARD_COMMITTEE_PERIOD - if source_epoch > SHARD_COMMITTEE_PERIOD: + if source_epoch >= SHARD_COMMITTEE_PERIOD: source_epoch -= SHARD_COMMITTEE_PERIOD active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) seed = get_seed(beacon_state, source_epoch, DOMAIN_SHARD_COMMITTEE) From 23e2b83e20683802ce3a0f1627df88182cf3d7e1 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Thu, 14 May 2020 06:21:44 -0700 Subject: [PATCH 51/53] Update specs/phase1/beacon-chain.md Co-authored-by: Hsiao-Wei Wang --- specs/phase1/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 4a75cb076..b81f12b91 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -494,7 +494,7 @@ def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) - ```python def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: source_epoch = epoch - epoch % LIGHT_CLIENT_COMMITTEE_PERIOD - if source_epoch > LIGHT_CLIENT_COMMITTEE_PERIOD: + if source_epoch => LIGHT_CLIENT_COMMITTEE_PERIOD: source_epoch -= LIGHT_CLIENT_COMMITTEE_PERIOD active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) seed = get_seed(beacon_state, source_epoch, DOMAIN_LIGHT_CLIENT) From 183b19788863174e6cb5ffb6a23e2d48e3cec5af Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 14 May 2020 21:25:36 +0800 Subject: [PATCH 52/53] Update specs/phase1/beacon-chain.md --- specs/phase1/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index b81f12b91..03794fa2a 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -494,7 +494,7 @@ def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) - ```python def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: source_epoch = epoch - epoch % LIGHT_CLIENT_COMMITTEE_PERIOD - if source_epoch => LIGHT_CLIENT_COMMITTEE_PERIOD: + if source_epoch >= LIGHT_CLIENT_COMMITTEE_PERIOD: source_epoch -= LIGHT_CLIENT_COMMITTEE_PERIOD active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) seed = get_seed(beacon_state, source_epoch, DOMAIN_LIGHT_CLIENT) From 6f5cbe6182d9e4b35fb29e876e69d9a96aa388a7 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Thu, 14 May 2020 14:55:29 -0700 Subject: [PATCH 53/53] Update shard-transition.md --- specs/phase1/shard-transition.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index a8de508fb..e9d97f598 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -260,7 +260,7 @@ def get_shard_state_transition_result( ### Make attestations -Suppose you are a committee member on shard `shard` at slot `current_slot` and you have received shard blocks `shard_blocks` since the latest successful crosslink for `shard` into the beacon chain. Let `state` be the head beacon state you are building on, and let `QUARTER_PERIOD = SECONDS_PER_SLOT // 4`. `2 * QUARTER_PERIOD` seconds into slot `current_slot`, run `get_shard_transition(beacon_state, shard, shard_blocks)` to get `shard_transition`. +Suppose you are a committee member on shard `shard` at slot `current_slot` and you have received shard blocks `shard_blocks` since the latest successful crosslink for `shard` into the beacon chain. Let `beacon_state` be the head beacon state you are building on, and let `QUARTER_PERIOD = SECONDS_PER_SLOT // 4`. `2 * QUARTER_PERIOD` seconds into slot `current_slot`, run `get_shard_transition(beacon_state, shard, shard_blocks)` to get `shard_transition`. ```python def get_shard_transition(beacon_state: BeaconState,