diff --git a/specs/lightclient/beacon-chain.md b/specs/lightclient/beacon-chain.md index d27def8d9..2773e1760 100644 --- a/specs/lightclient/beacon-chain.md +++ b/specs/lightclient/beacon-chain.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 Light Client Support +# Ethereum 2.0 HF1 ## Table of contents @@ -7,10 +7,13 @@ - [Introduction](#introduction) +- [Custom types](#custom-types) - [Constants](#constants) -- [Configuration](#configuration) - - [Constants](#constants-1) + - [Validator action flags](#validator-action-flags) + - [Participation rewards](#participation-rewards) - [Misc](#misc) +- [Configuration](#configuration) + - [Misc](#misc-1) - [Time parameters](#time-parameters) - [Domain types](#domain-types) - [Containers](#containers) @@ -22,36 +25,78 @@ - [Helper functions](#helper-functions) - [`Predicates`](#predicates) - [`eth2_fast_aggregate_verify`](#eth2_fast_aggregate_verify) + - [Misc](#misc-2) + - [`flags_and_numerators`](#flags_and_numerators) - [Beacon state accessors](#beacon-state-accessors) - [`get_sync_committee_indices`](#get_sync_committee_indices) - [`get_sync_committee`](#get_sync_committee) + - [`get_base_reward`](#get_base_reward) + - [`get_unslashed_participating_indices`](#get_unslashed_participating_indices) + - [`get_flag_deltas`](#get_flag_deltas) + - [New `get_inactivity_penalty_deltas`](#new-get_inactivity_penalty_deltas) - [Block processing](#block-processing) + - [New `process_attestation`](#new-process_attestation) + - [New `process_deposit`](#new-process_deposit) - [Sync committee processing](#sync-committee-processing) - [Epoch processing](#epoch-processing) - - [Components of attestation deltas](#components-of-attestation-deltas) + - [New `process_justification_and_finalization`](#new-process_justification_and_finalization) + - [New `process_rewards_and_penalties`](#new-process_rewards_and_penalties) - [Sync committee updates](#sync-committee-updates) + - [Participation flags updates](#participation-flags-updates) ## Introduction -This is a standalone beacon chain patch adding light client support via sync committees. +This is a patch implementing the first hard fork to the beacon chain, tentatively named HF1 pending a permanent name. It has three main features: + +* Light client support via sync committees +* Incentive accounting reforms, reducing spec complexity + and [TODO] reducing the cost of processing chains that have very little or zero participation for a long span of epochs +* Fork choice rule changes to address weaknesses recently discovered in the existing fork choice + +## Custom types + +| Name | SSZ equivalent | Description | +| - | - | - | +| `ValidatorFlag` | `uint8` | Bitflags to track validator actions with | ## Constants +### Validator action flags + +This is formatted as an enum, with values `2**i` that can be combined as bit-flags. +The `0` value is reserved as default. Remaining bits in `ValidatorFlag` may be used in future hardforks. + +**Note**: Unlike Phase0, a `TIMELY_TARGET_FLAG` does not necessarily imply a `TIMELY_SOURCE_FLAG` +due to the varying slot delay requirements of each. + | Name | Value | -| - | - | -| `BASE_REWARDS_PER_EPOCH` | `uint64(5)` | +| - | - | +| `TIMELY_HEAD_FLAG` | `ValidatorFlag(2**0)` (= 1) | +| `TIMELY_SOURCE_FLAG` | `ValidatorFlag(2**1)` (= 2) | +| `TIMELY_TARGET_FLAG` | `ValidatorFlag(2**2)` (= 4) | -## Configuration +### Participation rewards -### Constants +| Name | Value | +| - | - | +| `TIMELY_HEAD_NUMERATOR` | `12` | +| `TIMELY_SOURCE_NUMERATOR` | `12` | +| `TIMELY_TARGET_NUMERATOR` | `32` | +| `REWARD_DENOMINATOR` | `64` | + +The reward fractions add up to 7/8, leaving the remaining 1/8 for proposer rewards and other future micro-rewards. + +### Misc | Name | Value | | - | - | | `G2_POINT_AT_INFINITY` | `BLSSignature(b'\xc0' + b'\x00' * 95)` | +## Configuration + ### Misc | Name | Value | @@ -90,8 +135,37 @@ class BeaconBlockBody(phase0.BeaconBlockBody): #### `BeaconState` ```python -class BeaconState(phase0.BeaconState): - # Sync committees +class BeaconState(Container): + # Versioning + genesis_time: uint64 + genesis_validators_root: Root + slot: Slot + fork: Fork + # History + latest_block_header: BeaconBlockHeader + block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] + # Eth1 + eth1_data: Eth1Data + eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH] + eth1_deposit_index: uint64 + # Registry + validators: List[Validator, VALIDATOR_REGISTRY_LIMIT] + balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT] + # Randomness + randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR] + # Slashings + slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances + # Participation + previous_epoch_participation: List[ValidatorFlag, VALIDATOR_REGISTRY_LIMIT] + current_epoch_participation: List[ValidatorFlag, VALIDATOR_REGISTRY_LIMIT] + # Finality + justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch + previous_justified_checkpoint: Checkpoint + current_justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + # Light client sync committees current_sync_committee: SyncCommittee next_sync_committee: SyncCommittee ``` @@ -122,6 +196,29 @@ def eth2_fast_aggregate_verify(pubkeys: Sequence[BLSPubkey], message: Bytes32, s return bls.FastAggregateVerify(pubkeys, message, signature) ``` +### Misc + +#### `flags_and_numerators` + +```python +def get_flags_and_numerators() -> Sequence[Tuple[ValidatorFlag, int]]: + return ( + (TIMELY_HEAD_FLAG, TIMELY_HEAD_NUMERATOR), + (TIMELY_SOURCE_FLAG, TIMELY_SOURCE_NUMERATOR), + (TIMELY_TARGET_FLAG, TIMELY_TARGET_NUMERATOR) + ) +``` + +```python +def add_validator_flags(flags: ValidatorFlag, add: ValidatorFlag) -> ValidatorFlag: + return flags | add +``` + +```python +def has_validator_flags(flags: ValidatorFlag, has: ValidatorFlag) -> bool: + return flags & has == has +``` + ### Beacon state accessors #### `get_sync_committee_indices` @@ -166,6 +263,97 @@ def get_sync_committee(state: BeaconState, epoch: Epoch) -> SyncCommittee: return SyncCommittee(pubkeys=pubkeys, pubkey_aggregates=aggregates) ``` +#### `get_base_reward` + +*Note*: The function `get_base_reward` is modified with the removal of `BASE_REWARDS_PER_EPOCH`. + +```python +def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: + total_balance = get_total_active_balance(state) + effective_balance = state.validators[index].effective_balance + return Gwei(effective_balance * BASE_REWARD_FACTOR // integer_squareroot(total_balance)) +``` + +#### `get_unslashed_participating_indices` + +```python +def get_unslashed_participating_indices(state: BeaconState, flags: ValidatorFlag, epoch: Epoch) -> Set[ValidatorIndex]: + """ + Retrieve the active validator indices of the given epoch, which are not slashed, and have all of the given flags. + """ + assert epoch in (get_previous_epoch(state), get_current_epoch(state)) + if epoch == get_current_epoch(state): + epoch_participation = state.current_epoch_participation + else: + epoch_participation = state.previous_epoch_participation + participating_indices = [ + index for index in get_active_validator_indices(state, epoch) + if has_validator_flags(epoch_participation[index], flags) + ] + return set(filter(lambda index: not state.validators[index].slashed, participating_indices)) +``` + +#### `get_flag_deltas` + +```python +def get_flag_deltas(state: BeaconState, + flag: ValidatorFlag, + numerator: uint64) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Compute the rewards and penalties associated with a particular duty, by scanning through the participation + flags to determine who participated and who did not and assigning them the appropriate rewards and penalties. + """ + rewards = [Gwei(0)] * len(state.validators) + penalties = [Gwei(0)] * len(state.validators) + + unslashed_participating_indices = get_unslashed_participating_indices(state, flag, get_previous_epoch(state)) + increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from balances to avoid uint64 overflow + unslashed_participating_increments = get_total_balance(state, unslashed_participating_indices) // increment + active_increments = get_total_active_balance(state) // increment + for index in get_eligible_validator_indices(state): + base_reward = get_base_reward(state, index) + if index in unslashed_participating_indices: + if is_in_inactivity_leak(state): + # Optimal participatition is fully rewarded to cancel the inactivity penalty + rewards[index] = base_reward * numerator // REWARD_DENOMINATOR + else: + rewards[index] = ( + (base_reward * numerator * unslashed_participating_increments) + // (active_increments * REWARD_DENOMINATOR) + ) + else: + penalties[index] = base_reward * numerator // REWARD_DENOMINATOR + return rewards, penalties +``` + +#### New `get_inactivity_penalty_deltas` + +*Note*: The function `get_inactivity_penalty_deltas` is modified in the selection of matching target indices and the removal of `BASE_REWARDS_PER_EPOCH`. + +```python +def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Compute the penalties associated with the inactivity leak, by scanning through the participation + flags to determine who participated and who did not, applying the leak penalty globally and applying + compensatory rewards to participants. + """ + penalties = [Gwei(0) for _ in range(len(state.validators))] + if is_in_inactivity_leak(state): + reward_numerator_sum = sum(numerator for (_, numerator) in get_flags_and_numerators()) + matching_target_attesting_indices = get_unslashed_participating_indices( + state, TIMELY_TARGET_FLAG, get_previous_epoch(state) + ) + for index in get_eligible_validator_indices(state): + # If validator is performing optimally this cancels all attestation rewards for a neutral balance + penalties[index] += Gwei(get_base_reward(state, index) * reward_numerator_sum // REWARD_DENOMINATOR) + if index not in matching_target_attesting_indices: + effective_balance = state.validators[index].effective_balance + penalties[index] += Gwei(effective_balance * get_finality_delay(state) // INACTIVITY_PENALTY_QUOTIENT) + + rewards = [Gwei(0) for _ in range(len(state.validators))] + return rewards, penalties +``` + ### Block processing ```python @@ -178,6 +366,105 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: process_sync_committee(state, block.body) ``` +#### New `process_attestation` + +*Note*: The function `process_attestation` is modified to do incentive accounting with epoch participation flags. + +```python +def process_attestation(state: BeaconState, attestation: Attestation) -> None: + data = attestation.data + assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state)) + assert data.target.epoch == compute_epoch_at_slot(data.slot) + assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= data.slot + SLOTS_PER_EPOCH + assert data.index < get_committee_count_per_slot(state, data.target.epoch) + + committee = get_beacon_committee(state, data.slot, data.index) + assert len(attestation.aggregation_bits) == len(committee) + + if data.target.epoch == get_current_epoch(state): + epoch_participation = state.current_epoch_participation + justified_checkpoint = state.current_justified_checkpoint + else: + epoch_participation = state.previous_epoch_participation + justified_checkpoint = state.previous_justified_checkpoint + + # Matching roots + is_matching_head = data.beacon_block_root == get_block_root_at_slot(state, data.slot) + is_matching_source = data.source == justified_checkpoint + is_matching_target = data.target.root == get_block_root(state, data.target.epoch) + assert is_matching_source + + # Verify signature + assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) + + # Participation flags + participation_flags = [] + if is_matching_head and is_matching_target and state.slot <= data.slot + MIN_ATTESTATION_INCLUSION_DELAY: + participation_flags.append(TIMELY_HEAD_FLAG) + if is_matching_source and state.slot <= data.slot + integer_squareroot(SLOTS_PER_EPOCH): + participation_flags.append(TIMELY_SOURCE_FLAG) + if is_matching_target and state.slot <= data.slot + SLOTS_PER_EPOCH: + participation_flags.append(TIMELY_TARGET_FLAG) + + # Update epoch participation flags + proposer_reward_numerator = 0 + for index in get_attesting_indices(state, data, attestation.aggregation_bits): + for flag, numerator in get_flags_and_numerators(): + if flag in participation_flags and not has_validator_flags(epoch_participation[index], flag): + epoch_participation[index] = add_validator_flags(epoch_participation[index], flag) + proposer_reward_numerator += get_base_reward(state, index) * numerator + + # Reward proposer + proposer_reward = Gwei(proposer_reward_numerator // (REWARD_DENOMINATOR * PROPOSER_REWARD_QUOTIENT)) + increase_balance(state, get_beacon_proposer_index(state), proposer_reward) +``` + + +#### New `process_deposit` + +*Note*: The function `process_deposit` is modified to initialize `previous_epoch_participation` and `current_epoch_participation`. + +```python +def process_deposit(state: BeaconState, deposit: Deposit) -> None: + # Verify the Merkle branch + assert is_valid_merkle_branch( + leaf=hash_tree_root(deposit.data), + branch=deposit.proof, + depth=DEPOSIT_CONTRACT_TREE_DEPTH + 1, # Add 1 for the List length mix-in + index=state.eth1_deposit_index, + root=state.eth1_data.deposit_root, + ) + + # Deposits must be processed in order + state.eth1_deposit_index += 1 + + pubkey = deposit.data.pubkey + amount = deposit.data.amount + validator_pubkeys = [v.pubkey for v in state.validators] + if pubkey not in validator_pubkeys: + # Verify the deposit signature (proof of possession) which is not checked by the deposit contract + deposit_message = DepositMessage( + pubkey=deposit.data.pubkey, + withdrawal_credentials=deposit.data.withdrawal_credentials, + amount=deposit.data.amount, + ) + domain = compute_domain(DOMAIN_DEPOSIT) # Fork-agnostic domain since deposits are valid across forks + signing_root = compute_signing_root(deposit_message, domain) + if not bls.Verify(pubkey, signing_root, deposit.data.signature): + return + + # Add validator and balance entries + state.validators.append(get_validator_from_deposit(state, deposit)) + state.balances.append(amount) + # [Added in hf-1] Initialize empty participation flags for new validator + state.previous_epoch_participation.append(ValidatorFlag(0)) + state.current_epoch_participation.append(ValidatorFlag(0)) + else: + # Increase balance by deposit amount + index = ValidatorIndex(validator_pubkeys.index(pubkey)) + increase_balance(state, index, amount) +``` + #### Sync committee processing ```python @@ -211,8 +498,8 @@ def process_sync_committee(state: BeaconState, body: BeaconBlockBody) -> None: ```python def process_epoch(state: BeaconState) -> None: - process_justification_and_finalization(state) - process_rewards_and_penalties(state) + process_justification_and_finalization(state) # [Updated in HF1] + process_rewards_and_penalties(state) # [Updated in HF1] process_registry_updates(state) process_slashings(state) process_eth1_data_reset(state) @@ -220,43 +507,95 @@ def process_epoch(state: BeaconState) -> None: process_slashings_reset(state) process_randao_mixes_reset(state) process_historical_roots_update(state) - process_participation_record_updates(state) - # Light client patch + # [Removed in HF1] -- process_participation_record_updates(state) + # [Added in HF1] + process_participation_flag_updates(state) process_sync_committee_updates(state) ``` -#### Components of attestation deltas +#### New `process_justification_and_finalization` -*Note*: The function `get_inactivity_penalty_deltas` is modified with `BASE_REWARDS_PER_EPOCH` replaced by `BASE_REWARDS_PER_EPOCH - 1`. +*Note*: The function `process_justification_and_finalization` is modified with `matching_target_attestations` replaced by `matching_target_indices`. ```python -def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: - """ - Return inactivity reward/penalty deltas for each validator. - """ - penalties = [Gwei(0) for _ in range(len(state.validators))] - if is_in_inactivity_leak(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): - # Penalize validator so that optimal attestation performance is rewarded with one base reward per epoch - base_reward = get_base_reward(state, index) - penalties[index] += Gwei((BASE_REWARDS_PER_EPOCH - 1) * base_reward - get_proposer_reward(state, index)) - if index not in matching_target_attesting_indices: - effective_balance = state.validators[index].effective_balance - penalties[index] += Gwei(effective_balance * get_finality_delay(state) // INACTIVITY_PENALTY_QUOTIENT) +def process_justification_and_finalization(state: BeaconState) -> None: + # Initial FFG checkpoint values have a `0x00` stub for `root`. + # Skip FFG updates in the first two epochs to avoid corner cases that might result in modifying this stub. + if get_current_epoch(state) <= GENESIS_EPOCH + 1: + return + previous_epoch = get_previous_epoch(state) + current_epoch = get_current_epoch(state) + old_previous_justified_checkpoint = state.previous_justified_checkpoint + old_current_justified_checkpoint = state.current_justified_checkpoint - # No rewards associated with inactivity penalties - rewards = [Gwei(0) for _ in range(len(state.validators))] - return rewards, penalties + # Process justifications + state.previous_justified_checkpoint = state.current_justified_checkpoint + state.justification_bits[1:] = state.justification_bits[:JUSTIFICATION_BITS_LENGTH - 1] + state.justification_bits[0] = 0b0 + matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG, previous_epoch) + if get_total_balance(state, matching_target_indices) * 3 >= get_total_active_balance(state) * 2: + state.current_justified_checkpoint = Checkpoint(epoch=previous_epoch, + root=get_block_root(state, previous_epoch)) + state.justification_bits[1] = 0b1 + matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG, current_epoch) + if get_total_balance(state, matching_target_indices) * 3 >= get_total_active_balance(state) * 2: + state.current_justified_checkpoint = Checkpoint(epoch=current_epoch, + root=get_block_root(state, current_epoch)) + state.justification_bits[0] = 0b1 + + # Process finalizations + bits = state.justification_bits + # The 2nd/3rd/4th most recent epochs are justified, the 2nd using the 4th as source + if all(bits[1:4]) and old_previous_justified_checkpoint.epoch + 3 == current_epoch: + state.finalized_checkpoint = old_previous_justified_checkpoint + # The 2nd/3rd most recent epochs are justified, the 2nd using the 3rd as source + if all(bits[1:3]) and old_previous_justified_checkpoint.epoch + 2 == current_epoch: + state.finalized_checkpoint = old_previous_justified_checkpoint + # The 1st/2nd/3rd most recent epochs are justified, the 1st using the 3rd as source + if all(bits[0:3]) and old_current_justified_checkpoint.epoch + 2 == current_epoch: + state.finalized_checkpoint = old_current_justified_checkpoint + # The 1st/2nd most recent epochs are justified, the 1st using the 2nd as source + if all(bits[0:2]) and old_current_justified_checkpoint.epoch + 1 == current_epoch: + state.finalized_checkpoint = old_current_justified_checkpoint +``` + +#### New `process_rewards_and_penalties` + +*Note*: The function `process_rewards_and_penalties` is modified to use participation flag deltas. + +```python +def process_rewards_and_penalties(state: BeaconState) -> None: + # No rewards are applied at the end of `GENESIS_EPOCH` because rewards are for work done in the previous epoch + if get_current_epoch(state) == GENESIS_EPOCH: + return + flag_deltas = [get_flag_deltas(state, flag, numerator) for (flag, numerator) in get_flags_and_numerators()] + deltas = flag_deltas + [get_inactivity_penalty_deltas(state)] + for (rewards, penalties) in deltas: + for index in range(len(state.validators)): + increase_balance(state, ValidatorIndex(index), rewards[index]) + decrease_balance(state, ValidatorIndex(index), penalties[index]) ``` #### Sync committee updates ```python def process_sync_committee_updates(state: BeaconState) -> None: + """ + Call to ``proces_sync_committee_updates`` added to ``process_epoch`` in HF1 + """ next_epoch = get_current_epoch(state) + Epoch(1) if next_epoch % EPOCHS_PER_SYNC_COMMITTEE_PERIOD == 0: state.current_sync_committee = state.next_sync_committee state.next_sync_committee = get_sync_committee(state, next_epoch + EPOCHS_PER_SYNC_COMMITTEE_PERIOD) ``` + +#### Participation flags updates + +```python +def process_participation_flag_updates(state: BeaconState) -> None: + """ + Call to ``process_participation_flag_updates`` added to ``process_epoch`` in HF1 + """ + state.previous_epoch_participation = state.current_epoch_participation + state.current_epoch_participation = [ValidatorFlag(0) for _ in range(len(state.validators))] +``` diff --git a/specs/lightclient/lightclient-fork.md b/specs/lightclient/lightclient-fork.md index 568a9793b..aa0171b86 100644 --- a/specs/lightclient/lightclient-fork.md +++ b/specs/lightclient/lightclient-fork.md @@ -43,6 +43,7 @@ def upgrade_to_lightclient_patch(pre: phase0.BeaconState) -> BeaconState: epoch = get_current_epoch(pre) post = BeaconState( genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, slot=pre.slot, fork=Fork( previous_version=pre.fork.current_version, @@ -66,10 +67,8 @@ def upgrade_to_lightclient_patch(pre: phase0.BeaconState) -> BeaconState: # Slashings slashings=pre.slashings, # Attestations - # previous_epoch_attestations is cleared on upgrade. - previous_epoch_attestations=List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH](), - # empty in pre state, since the upgrade is performed just after an epoch boundary. - current_epoch_attestations=List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH](), + previous_epoch_participation=[ValidatorFlag(0) for _ in range(len(pre.validators))], + current_epoch_participation=[ValidatorFlag(0) for _ in range(len(pre.validators))], # Finality justification_bits=pre.justification_bits, previous_justified_checkpoint=pre.previous_justified_checkpoint, diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index d19547477..4f07a790a 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -390,3 +390,11 @@ def only_full_crosslink(fn): return None return fn(*args, spec=spec, state=state, **kw) return wrapper + + +def is_post_lightclient_patch(spec): + if spec.fork in [PHASE0, PHASE1]: + # TODO: PHASE1 fork is temporarily parallel to LIGHTCLIENT_PATCH. + # Will make PHASE1 fork inherit LIGHTCLIENT_PATCH later. + return False + return True diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index b924da378..571e19fef 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -2,7 +2,7 @@ from lru import LRU from typing import List -from eth2spec.test.context import expect_assertion_error, PHASE1 +from eth2spec.test.context import expect_assertion_error, PHASE1, is_post_lightclient_patch 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.shard_transitions import get_shard_transition_of_committee @@ -30,17 +30,22 @@ def run_attestation_processing(spec, state, attestation, valid=True): yield 'post', None return - current_epoch_count = len(state.current_epoch_attestations) - previous_epoch_count = len(state.previous_epoch_attestations) + if not is_post_lightclient_patch(spec): + current_epoch_count = len(state.current_epoch_attestations) + previous_epoch_count = len(state.previous_epoch_attestations) # process attestation spec.process_attestation(state, attestation) # Make sure the attestation has been processed - if attestation.data.target.epoch == spec.get_current_epoch(state): - assert len(state.current_epoch_attestations) == current_epoch_count + 1 + if not is_post_lightclient_patch(spec): + if attestation.data.target.epoch == spec.get_current_epoch(state): + assert len(state.current_epoch_attestations) == current_epoch_count + 1 + else: + assert len(state.previous_epoch_attestations) == previous_epoch_count + 1 else: - assert len(state.previous_epoch_attestations) == previous_epoch_count + 1 + # After accounting reform, there are cases when processing an attestation does not result in any flag updates + pass # yield post-state yield 'post', state @@ -315,7 +320,8 @@ def prepare_state_with_attestations(spec, state, participation_fn=None): next_slot(spec, state) assert state.slot == next_epoch_start_slot + spec.MIN_ATTESTATION_INCLUSION_DELAY - assert len(state.previous_epoch_attestations) == len(attestations) + if not is_post_lightclient_patch(spec): + assert len(state.previous_epoch_attestations) == len(attestations) return attestations diff --git a/tests/core/pyspec/eth2spec/test/helpers/block.py b/tests/core/pyspec/eth2spec/test/helpers/block.py index c4d8f1931..7501c8268 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/block.py @@ -1,4 +1,4 @@ -from eth2spec.test.context import LIGHTCLIENT_PATCH +from eth2spec.test.context import is_post_lightclient_patch from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls from eth2spec.utils.bls import only_with_bls @@ -91,7 +91,7 @@ def build_empty_block(spec, state, slot=None): empty_block.body.eth1_data.deposit_count = state.eth1_deposit_index empty_block.parent_root = parent_block_root - if spec.fork == LIGHTCLIENT_PATCH: + if is_post_lightclient_patch(spec): empty_block.body.sync_committee_signature = spec.G2_POINT_AT_INFINITY apply_randao_reveal(spec, state, empty_block) diff --git a/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py b/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py index b1ebb5d49..52479cfeb 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py +++ b/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py @@ -1,23 +1,29 @@ -process_calls = [ - # PHASE0 - 'process_justification_and_finalization', - 'process_rewards_and_penalties', - 'process_registry_updates', - 'process_reveal_deadlines', - 'process_challenge_deadlines', - 'process_slashings', - 'process_eth1_data_reset', - 'process_effective_balance_updates', - 'process_slashings_reset', - 'process_randao_mixes_reset', - 'process_historical_roots_update', - 'process_participation_record_updates', - # LIGHTCLIENT_PATCH - 'process_sync_committee_updates', - # PHASE1 - 'process_phase_1_final_updates', -] +from eth2spec.test.context import is_post_lightclient_patch + + +def get_process_calls(spec): + return [ + # PHASE0 + 'process_justification_and_finalization', + 'process_rewards_and_penalties', + 'process_registry_updates', + 'process_reveal_deadlines', + 'process_challenge_deadlines', + 'process_slashings', + 'process_eth1_data_reset', + 'process_effective_balance_updates', + 'process_slashings_reset', + 'process_randao_mixes_reset', + 'process_historical_roots_update', + # HF1 replaced `process_participation_record_updates` with `process_participation_flag_updates` + 'process_participation_flag_updates' if is_post_lightclient_patch(spec) else ( + 'process_participation_record_updates' + ), + 'process_sync_committee_updates', + # PHASE1 + 'process_phase_1_final_updates', + ] def run_epoch_processing_to(spec, state, process_name: str): @@ -34,7 +40,7 @@ def run_epoch_processing_to(spec, state, process_name: str): spec.process_slot(state) # process components of epoch transition before final-updates - for name in process_calls: + for name in get_process_calls(spec): if name == process_name: break # only run when present. Later phases introduce more to the epoch-processing. diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index dc355972f..2499bcffe 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -2,7 +2,7 @@ from random import Random from lru import LRU from eth2spec.phase0 import spec as spec_phase0 -from eth2spec.test.context import LIGHTCLIENT_PATCH +from eth2spec.test.context import is_post_lightclient_patch from eth2spec.test.helpers.attestations import cached_prepare_state_with_attestations from eth2spec.test.helpers.deposits import mock_deposit from eth2spec.test.helpers.state import next_epoch @@ -38,24 +38,35 @@ def run_deltas(spec, state): - inactivity penalty deltas ('inactivity_penalty_deltas') """ yield 'pre', state + + if is_post_lightclient_patch(spec): + def get_source_deltas(state): + return spec.get_flag_deltas(state, spec.TIMELY_SOURCE_FLAG, spec.TIMELY_SOURCE_NUMERATOR) + + def get_head_deltas(state): + return spec.get_flag_deltas(state, spec.TIMELY_HEAD_FLAG, spec.TIMELY_HEAD_NUMERATOR) + + def get_target_deltas(state): + return spec.get_flag_deltas(state, spec.TIMELY_TARGET_FLAG, spec.TIMELY_TARGET_NUMERATOR) + yield from run_attestation_component_deltas( spec, state, - spec.get_source_deltas, + spec.get_source_deltas if not is_post_lightclient_patch(spec) else get_source_deltas, spec.get_matching_source_attestations, 'source_deltas', ) yield from run_attestation_component_deltas( spec, state, - spec.get_target_deltas, + spec.get_target_deltas if not is_post_lightclient_patch(spec) else get_target_deltas, spec.get_matching_target_attestations, 'target_deltas', ) yield from run_attestation_component_deltas( spec, state, - spec.get_head_deltas, + spec.get_head_deltas if not is_post_lightclient_patch(spec) else get_head_deltas, spec.get_matching_head_attestations, 'head_deltas', ) @@ -63,6 +74,16 @@ def run_deltas(spec, state): yield from run_get_inactivity_penalty_deltas(spec, state) +def deltas_name_to_flag(spec, deltas_name): + if 'source' in deltas_name: + return spec.TIMELY_SOURCE_FLAG + elif 'head' in deltas_name: + return spec.TIMELY_HEAD_FLAG + elif 'target' in deltas_name: + return spec.TIMELY_TARGET_FLAG + raise ValueError("Wrong deltas_name %s" % deltas_name) + + def run_attestation_component_deltas(spec, state, component_delta_fn, matching_att_fn, deltas_name): """ Run ``component_delta_fn``, yielding: @@ -72,8 +93,14 @@ def run_attestation_component_deltas(spec, state, component_delta_fn, matching_a yield deltas_name, 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) + if not is_post_lightclient_patch(spec): + matching_attestations = matching_att_fn(state, spec.get_previous_epoch(state)) + matching_indices = spec.get_unslashed_attesting_indices(state, matching_attestations) + else: + matching_indices = spec.get_unslashed_participating_indices( + state, deltas_name_to_flag(spec, deltas_name), spec.get_previous_epoch(state) + ) + eligible_indices = spec.get_eligible_validator_indices(state) for index in range(len(state.validators)): if index not in eligible_indices: @@ -102,6 +129,12 @@ def run_get_inclusion_delay_deltas(spec, state): Run ``get_inclusion_delay_deltas``, yielding: - inclusion delay deltas ('inclusion_delay_deltas') """ + if is_post_lightclient_patch(spec): + # No inclusion_delay_deltas + yield 'inclusion_delay_deltas', Deltas(rewards=[0] * len(state.validators), + penalties=[0] * len(state.validators)) + return + rewards, penalties = spec.get_inclusion_delay_deltas(state) yield 'inclusion_delay_deltas', Deltas(rewards=rewards, penalties=penalties) @@ -149,8 +182,14 @@ def run_get_inactivity_penalty_deltas(spec, state): yield 'inactivity_penalty_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) + if not is_post_lightclient_patch(spec): + matching_attestations = spec.get_matching_target_attestations(state, spec.get_previous_epoch(state)) + matching_attesting_indices = spec.get_unslashed_attesting_indices(state, matching_attestations) + else: + matching_attesting_indices = spec.get_unslashed_participating_indices( + state, spec.TIMELY_TARGET_FLAG, spec.get_previous_epoch(state) + ) + reward_numerator_sum = sum(numerator for (_, numerator) in spec.get_flags_and_numerators()) eligible_indices = spec.get_eligible_validator_indices(state) for index in range(len(state.validators)): @@ -160,12 +199,14 @@ def run_get_inactivity_penalty_deltas(spec, state): continue if spec.is_in_inactivity_leak(state): - if spec.fork == LIGHTCLIENT_PATCH: - cancel_base_rewards_per_epoch = spec.BASE_REWARDS_PER_EPOCH - 1 - else: + # Compute base_penalty + if not is_post_lightclient_patch(spec): cancel_base_rewards_per_epoch = spec.BASE_REWARDS_PER_EPOCH - base_reward = spec.get_base_reward(state, index) - base_penalty = cancel_base_rewards_per_epoch * base_reward - spec.get_proposer_reward(state, index) + base_reward = spec.get_base_reward(state, index) + base_penalty = cancel_base_rewards_per_epoch * base_reward - spec.get_proposer_reward(state, index) + else: + base_penalty = spec.get_base_reward(state, index) * reward_numerator_sum // spec.REWARD_DENOMINATOR + if not has_enough_for_reward(spec, state, index): assert penalties[index] == 0 elif index in matching_attesting_indices: @@ -267,8 +308,13 @@ def run_test_full_all_correct(spec, state): def run_test_full_but_partial_participation(spec, state, rng=Random(5522)): cached_prepare_state_with_attestations(spec, state) - for a in state.previous_epoch_attestations: - a.aggregation_bits = [rng.choice([True, False]) for _ in a.aggregation_bits] + if not is_post_lightclient_patch(spec): + for a in state.previous_epoch_attestations: + a.aggregation_bits = [rng.choice([True, False]) for _ in a.aggregation_bits] + else: + for index in range(len(state.validators)): + if rng.choice([True, False]): + state.previous_epoch_participation[index] = spec.ValidatorFlag(0) yield from run_deltas(spec, state) @@ -277,8 +323,12 @@ def run_test_partial(spec, state, fraction_filled): cached_prepare_state_with_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] + if not is_post_lightclient_patch(spec): + num_attestations = int(len(state.previous_epoch_attestations) * fraction_filled) + state.previous_epoch_attestations = state.previous_epoch_attestations[:num_attestations] + else: + for index in range(int(len(state.validators) * fraction_filled)): + state.previous_epoch_participation[index] = spec.ValidatorFlag(0) yield from run_deltas(spec, state) @@ -333,13 +383,18 @@ def run_test_some_very_low_effective_balances_that_attested(spec, state): def run_test_some_very_low_effective_balances_that_did_not_attest(spec, state): cached_prepare_state_with_attestations(spec, state) - # Remove attestation - attestation = state.previous_epoch_attestations[0] - state.previous_epoch_attestations = state.previous_epoch_attestations[1:] - # 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 + if not is_post_lightclient_patch(spec): + # Remove attestation + attestation = state.previous_epoch_attestations[0] + state.previous_epoch_attestations = state.previous_epoch_attestations[1:] + # 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 + else: + index = 0 + state.validators[index].effective_balance = 1 + state.previous_epoch_participation[index] = spec.ValidatorFlag(0) yield from run_deltas(spec, state) @@ -447,16 +502,42 @@ def run_test_full_random(spec, state, rng=Random(8020)): cached_prepare_state_with_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) + if not is_post_lightclient_patch(spec): + 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) + else: + for index in range(len(state.validators)): + # ~1/3 have bad head or bad target or not timely enough + is_timely_correct_head = rng.randint(0, 2) != 0 + flags = state.previous_epoch_participation[index] + def set_flag(f, v): + nonlocal flags + if v: + flags |= f + else: + flags &= 0xff ^ f + + set_flag(spec.TIMELY_HEAD_FLAG, is_timely_correct_head) + if is_timely_correct_head: + # If timely head, then must be timely target + set_flag(spec.TIMELY_TARGET_FLAG, True) + # If timely head, then must be timely source + set_flag(spec.TIMELY_SOURCE_FLAG, True) + else: + # ~50% of remaining have bad target or not timely enough + set_flag(spec.TIMELY_TARGET_FLAG, rng.choice([True, False])) + # ~50% of remaining have bad source or not timely enough + set_flag(spec.TIMELY_SOURCE_FLAG, rng.choice([True, False])) + state.previous_epoch_participation[index] = flags yield from run_deltas(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/lightclient_patch/block_processing/test_process_sync_committee.py b/tests/core/pyspec/eth2spec/test/lightclient_patch/block_processing/test_process_sync_committee.py index a8be1af63..430c59b10 100644 --- a/tests/core/pyspec/eth2spec/test/lightclient_patch/block_processing/test_process_sync_committee.py +++ b/tests/core/pyspec/eth2spec/test/lightclient_patch/block_processing/test_process_sync_committee.py @@ -18,6 +18,7 @@ from eth2spec.test.context import ( with_all_phases_except, with_configs, spec_state_test, + always_bls, ) from eth2spec.utils.hash_function import hash @@ -196,6 +197,7 @@ def test_sync_committee_rewards_duplicate_committee(spec, state): @with_all_phases_except([PHASE0, PHASE1]) @spec_state_test +@always_bls def test_invalid_signature_past_block(spec, state): committee = spec.get_sync_committee_indices(state, spec.get_current_epoch(state)) @@ -237,6 +239,7 @@ def test_invalid_signature_past_block(spec, state): @with_all_phases_except([PHASE0, PHASE1]) @with_configs([MINIMAL], reason="to produce different committee sets") @spec_state_test +@always_bls def test_invalid_signature_previous_committee(spec, state): # NOTE: the `state` provided is at genesis and the process to select # sync committees currently returns the same committee for the first and second diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py index b31cf167c..99a82879d 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py @@ -2,10 +2,13 @@ from eth2spec.test.context import ( spec_state_test, always_bls, never_bls, with_all_phases, + with_all_phases_except, spec_test, low_balances, with_custom_state, - single_phase) + single_phase, + PHASE1, +) from eth2spec.test.helpers.attestations import ( run_attestation_processing, get_valid_attestation, @@ -329,3 +332,212 @@ def test_too_few_aggregation_bits(spec, state): attestation.aggregation_bits = attestation.aggregation_bits[:-1] yield from run_attestation_processing(spec, state, attestation, False) + + +# +# Full correct atttestation contents at different slot inclusions +# + +@with_all_phases +@spec_state_test +def test_correct_min_inclusion_delay(spec, state): + attestation = get_valid_attestation(spec, state, signed=True) + next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) + + yield from run_attestation_processing(spec, state, attestation) + + +@with_all_phases +@spec_state_test +def test_correct_sqrt_epoch_delay(spec, state): + attestation = get_valid_attestation(spec, state, signed=True, on_time=False) + next_slots(spec, state, spec.integer_squareroot(spec.SLOTS_PER_EPOCH)) + + yield from run_attestation_processing(spec, state, attestation) + + +@with_all_phases +@spec_state_test +def test_correct_epoch_delay(spec, state): + attestation = get_valid_attestation(spec, state, signed=True, on_time=False) + next_slots(spec, state, spec.SLOTS_PER_EPOCH) + + yield from run_attestation_processing(spec, state, attestation) + + +@with_all_phases +@spec_state_test +def test_correct_after_epoch_delay(spec, state): + attestation = get_valid_attestation(spec, state, signed=True, on_time=False) + + # increment past latest inclusion slot + next_slots(spec, state, spec.SLOTS_PER_EPOCH + 1) + + yield from run_attestation_processing(spec, state, attestation, False) + + +# +# Incorrect head but correct source/target at different slot inclusions +# + +@with_all_phases_except([PHASE1]) +@spec_state_test +def test_incorrect_head_min_inclusion_delay(spec, state): + attestation = get_valid_attestation(spec, state, signed=False) + next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) + + attestation.data.beacon_block_root = b'\x42' * 32 + sign_attestation(spec, state, attestation) + + yield from run_attestation_processing(spec, state, attestation) + + +@with_all_phases +@spec_state_test +def test_incorrect_head_sqrt_epoch_delay(spec, state): + attestation = get_valid_attestation(spec, state, signed=False, on_time=False) + next_slots(spec, state, spec.integer_squareroot(spec.SLOTS_PER_EPOCH)) + + attestation.data.beacon_block_root = b'\x42' * 32 + sign_attestation(spec, state, attestation) + + yield from run_attestation_processing(spec, state, attestation) + + +@with_all_phases +@spec_state_test +def test_incorrect_head_epoch_delay(spec, state): + attestation = get_valid_attestation(spec, state, signed=False, on_time=False) + next_slots(spec, state, spec.SLOTS_PER_EPOCH) + + attestation.data.beacon_block_root = b'\x42' * 32 + sign_attestation(spec, state, attestation) + + yield from run_attestation_processing(spec, state, attestation) + + +@with_all_phases +@spec_state_test +def test_incorrect_head_after_epoch_delay(spec, state): + attestation = get_valid_attestation(spec, state, signed=False, on_time=False) + + # increment past latest inclusion slot + next_slots(spec, state, spec.SLOTS_PER_EPOCH + 1) + + attestation.data.beacon_block_root = b'\x42' * 32 + sign_attestation(spec, state, attestation) + + yield from run_attestation_processing(spec, state, attestation, False) + + +# +# Incorrect head and target but correct source at different slot inclusions +# + +# Note: current phase 1 spec checks +# `assert data.beacon_block_root == get_block_root_at_slot(state, compute_previous_slot(state.slot))` +# so this test can't pass that until phase 1 refactor is merged +@with_all_phases_except([PHASE1]) +@spec_state_test +def test_incorrect_head_and_target_min_inclusion_delay(spec, state): + attestation = get_valid_attestation(spec, state, signed=False) + next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) + + attestation.data.beacon_block_root = b'\x42' * 32 + attestation.data.target.root = b'\x42' * 32 + sign_attestation(spec, state, attestation) + + yield from run_attestation_processing(spec, state, attestation) + + +@with_all_phases +@spec_state_test +def test_incorrect_head_and_target_sqrt_epoch_delay(spec, state): + attestation = get_valid_attestation(spec, state, signed=False, on_time=False) + next_slots(spec, state, spec.integer_squareroot(spec.SLOTS_PER_EPOCH)) + + attestation.data.beacon_block_root = b'\x42' * 32 + attestation.data.target.root = b'\x42' * 32 + sign_attestation(spec, state, attestation) + + yield from run_attestation_processing(spec, state, attestation) + + +@with_all_phases +@spec_state_test +def test_incorrect_head_and_target_epoch_delay(spec, state): + attestation = get_valid_attestation(spec, state, signed=False, on_time=False) + next_slots(spec, state, spec.SLOTS_PER_EPOCH) + + attestation.data.beacon_block_root = b'\x42' * 32 + attestation.data.target.root = b'\x42' * 32 + sign_attestation(spec, state, attestation) + + yield from run_attestation_processing(spec, state, attestation) + + +@with_all_phases +@spec_state_test +def test_incorrect_head_and_target_after_epoch_delay(spec, state): + attestation = get_valid_attestation(spec, state, signed=False, on_time=False) + # increment past latest inclusion slot + next_slots(spec, state, spec.SLOTS_PER_EPOCH + 1) + + attestation.data.beacon_block_root = b'\x42' * 32 + attestation.data.target.root = b'\x42' * 32 + sign_attestation(spec, state, attestation) + + yield from run_attestation_processing(spec, state, attestation, False) + + +# +# Correct head and source but incorrect target at different slot inclusions +# + +@with_all_phases_except([PHASE1]) +@spec_state_test +def test_incorrect_target_min_inclusion_delay(spec, state): + attestation = get_valid_attestation(spec, state, signed=False) + next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) + + attestation.data.target.root = b'\x42' * 32 + sign_attestation(spec, state, attestation) + + yield from run_attestation_processing(spec, state, attestation) + + +@with_all_phases +@spec_state_test +def test_incorrect_target_sqrt_epoch_delay(spec, state): + attestation = get_valid_attestation(spec, state, signed=False, on_time=False) + next_slots(spec, state, spec.integer_squareroot(spec.SLOTS_PER_EPOCH)) + + attestation.data.target.root = b'\x42' * 32 + sign_attestation(spec, state, attestation) + + yield from run_attestation_processing(spec, state, attestation) + + +@with_all_phases +@spec_state_test +def test_incorrect_target_epoch_delay(spec, state): + attestation = get_valid_attestation(spec, state, signed=False, on_time=False) + next_slots(spec, state, spec.SLOTS_PER_EPOCH) + + attestation.data.target.root = b'\x42' * 32 + sign_attestation(spec, state, attestation) + + yield from run_attestation_processing(spec, state, attestation) + + +@with_all_phases +@spec_state_test +def test_incorrect_target_after_epoch_delay(spec, state): + attestation = get_valid_attestation(spec, state, signed=False, on_time=False) + # increment past latest inclusion slot + next_slots(spec, state, spec.SLOTS_PER_EPOCH + 1) + + attestation.data.target.root = b'\x42' * 32 + sign_attestation(spec, state, attestation) + + yield from run_attestation_processing(spec, state, attestation, False) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py index 921289f5b..89783f987 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py @@ -1,6 +1,6 @@ -from eth2spec.test.context import spec_state_test, with_all_phases +from eth2spec.test.context import is_post_lightclient_patch, spec_state_test, with_all_phases from eth2spec.test.helpers.epoch_processing import ( - run_epoch_processing_with + run_epoch_processing_with, ) from eth2spec.test.helpers.state import transition_to @@ -16,12 +16,20 @@ def add_mock_attestations(spec, state, epoch, source, target, sufficient_support previous_epoch = spec.get_previous_epoch(state) current_epoch = spec.get_current_epoch(state) - if current_epoch == epoch: - attestations = state.current_epoch_attestations - elif previous_epoch == epoch: - attestations = state.previous_epoch_attestations + if not is_post_lightclient_patch(spec): + if current_epoch == epoch: + attestations = state.current_epoch_attestations + elif previous_epoch == epoch: + attestations = state.previous_epoch_attestations + else: + raise Exception(f"cannot include attestations in epoch ${epoch} from epoch ${current_epoch}") else: - raise Exception(f"cannot include attestations in epoch ${epoch} from epoch ${current_epoch}") + if current_epoch == epoch: + epoch_participation = state.current_epoch_participation + elif previous_epoch == epoch: + epoch_participation = state.previous_epoch_participation + else: + raise Exception(f"cannot include attestations in epoch ${epoch} from epoch ${current_epoch}") total_balance = spec.get_total_active_balance(state) remaining_balance = int(total_balance * 2 // 3) # can become negative @@ -52,19 +60,28 @@ def add_mock_attestations(spec, state, epoch, source, target, sufficient_support for i in range(max(len(committee) // 5, 1)): aggregation_bits[i] = 0 - attestations.append(spec.PendingAttestation( - aggregation_bits=aggregation_bits, - data=spec.AttestationData( - slot=slot, - beacon_block_root=b'\xff' * 32, # irrelevant to testing - source=source, - target=target, - index=index, - ), - inclusion_delay=1, - )) - if messed_up_target: - attestations[len(attestations) - 1].data.target.root = b'\x99' * 32 + # Update state + if not is_post_lightclient_patch(spec): + attestations.append(spec.PendingAttestation( + aggregation_bits=aggregation_bits, + data=spec.AttestationData( + slot=slot, + beacon_block_root=b'\xff' * 32, # irrelevant to testing + source=source, + target=target, + index=index, + ), + inclusion_delay=1, + )) + if messed_up_target: + attestations[len(attestations) - 1].data.target.root = b'\x99' * 32 + else: + for i, index in enumerate(committee): + if aggregation_bits[i]: + epoch_participation[index] |= spec.TIMELY_HEAD_FLAG + epoch_participation[index] |= spec.TIMELY_SOURCE_FLAG + if not messed_up_target: + epoch_participation[index] |= spec.TIMELY_TARGET_FLAG def get_checkpoints(spec, epoch): diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_participation_record_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_participation_record_updates.py index f5e1513e3..978ef5739 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_participation_record_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_participation_record_updates.py @@ -1,4 +1,4 @@ -from eth2spec.test.context import spec_state_test, with_all_phases +from eth2spec.test.context import PHASE0, spec_state_test, with_phases from eth2spec.test.helpers.epoch_processing import ( run_epoch_processing_with ) @@ -8,7 +8,7 @@ def run_process_participation_record_updates(spec, state): yield from run_epoch_processing_with(spec, state, 'process_participation_record_updates') -@with_all_phases +@with_phases([PHASE0]) @spec_state_test def test_updated_participation_record(spec, state): state.previous_epoch_attestations = [spec.PendingAttestation(proposer_index=100)] diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py index 8560b66a8..7bb86b45e 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py @@ -1,11 +1,11 @@ from eth2spec.test.context import ( - LIGHTCLIENT_PATCH, spec_state_test, spec_test, with_all_phases, single_phase, - with_phases, PHASE0, + with_phases, PHASE0, PHASE1, with_custom_state, zero_activation_threshold, misc_balances, low_single_balance, + is_post_lightclient_patch, ) from eth2spec.test.helpers.state import ( next_epoch, @@ -66,7 +66,7 @@ def test_genesis_epoch_full_attestations_no_rewards(spec, state): assert state.balances[index] == pre_state.balances[index] -@with_all_phases +@with_phases([PHASE0, PHASE1]) @spec_state_test def test_full_attestations_random_incorrect_fields(spec, state): attestations = prepare_state_with_attestations(spec, state) @@ -159,11 +159,11 @@ def run_with_participation(spec, state, participation_fn): return att_participants attestations = prepare_state_with_attestations(spec, state, participation_fn=participation_tracker) - proposer_indices = [a.proposer_index for a in state.previous_epoch_attestations] - pre_state = state.copy() - if spec.fork == LIGHTCLIENT_PATCH: + if not is_post_lightclient_patch(spec): + proposer_indices = [a.proposer_index for a in state.previous_epoch_attestations] + else: sync_committee_indices = spec.get_sync_committee_indices(state, spec.get_current_epoch(state)) yield from run_process_rewards_and_penalties(spec, state) @@ -173,11 +173,11 @@ def run_with_participation(spec, state, participation_fn): for index in range(len(pre_state.validators)): if spec.is_in_inactivity_leak(state): - # Proposers can still make money during a leak - if index in proposer_indices and index in participated: + # Proposers can still make money during a leak before LIGHTCLIENT_PATCH + if not is_post_lightclient_patch(spec) and index in proposer_indices and index in participated: assert state.balances[index] > pre_state.balances[index] elif index in attesting_indices: - if spec.fork == LIGHTCLIENT_PATCH and index in sync_committee_indices: + if is_post_lightclient_patch(spec) and index in sync_committee_indices: # The sync committee reward has not been canceled, so the sync committee participants still earn it assert state.balances[index] >= pre_state.balances[index] else: @@ -428,7 +428,8 @@ def test_attestations_some_slashed(spec, state): for i in range(spec.MIN_PER_EPOCH_CHURN_LIMIT): spec.slash_validator(state, attesting_indices_before_slashings[i]) - assert len(state.previous_epoch_attestations) == len(attestations) + if not is_post_lightclient_patch(spec): + assert len(state.previous_epoch_attestations) == len(attestations) pre_state = state.copy() diff --git a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_basic.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_basic.py index 92277fdd7..7871d3fcf 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_basic.py +++ b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_basic.py @@ -1,4 +1,4 @@ -from eth2spec.test.context import with_all_phases, spec_state_test +from eth2spec.test.context import PHASE0, PHASE1, with_all_phases, with_phases, spec_state_test import eth2spec.test.helpers.rewards as rewards_helpers @@ -32,7 +32,7 @@ def test_full_but_partial_participation(spec, state): yield from rewards_helpers.run_test_full_but_partial_participation(spec, state) -@with_all_phases +@with_phases([PHASE0, PHASE1]) @spec_state_test def test_one_attestation_one_correct(spec, state): yield from rewards_helpers.run_test_one_attestation_one_correct(spec, state) @@ -75,7 +75,7 @@ def test_some_very_low_effective_balances_that_did_not_attest(spec, state): # -@with_all_phases +@with_phases([PHASE0, PHASE1]) @spec_state_test def test_full_half_correct_target_incorrect_head(spec, state): yield from rewards_helpers.run_test_full_fraction_incorrect( @@ -86,7 +86,7 @@ def test_full_half_correct_target_incorrect_head(spec, state): ) -@with_all_phases +@with_phases([PHASE0, PHASE1]) @spec_state_test def test_full_correct_target_incorrect_head(spec, state): yield from rewards_helpers.run_test_full_fraction_incorrect( @@ -97,7 +97,7 @@ def test_full_correct_target_incorrect_head(spec, state): ) -@with_all_phases +@with_phases([PHASE0, PHASE1]) @spec_state_test def test_full_half_incorrect_target_incorrect_head(spec, state): yield from rewards_helpers.run_test_full_fraction_incorrect( @@ -108,7 +108,7 @@ def test_full_half_incorrect_target_incorrect_head(spec, state): ) -@with_all_phases +@with_phases([PHASE0, PHASE1]) @spec_state_test def test_full_half_incorrect_target_correct_head(spec, state): yield from rewards_helpers.run_test_full_fraction_incorrect( @@ -119,31 +119,31 @@ def test_full_half_incorrect_target_correct_head(spec, state): ) -@with_all_phases +@with_phases([PHASE0, PHASE1]) @spec_state_test def test_full_delay_one_slot(spec, state): yield from rewards_helpers.run_test_full_delay_one_slot(spec, state) -@with_all_phases +@with_phases([PHASE0, PHASE1]) @spec_state_test def test_full_delay_max_slots(spec, state): yield from rewards_helpers.run_test_full_delay_max_slots(spec, state) -@with_all_phases +@with_phases([PHASE0, PHASE1]) @spec_state_test def test_full_mixed_delay(spec, state): yield from rewards_helpers.run_test_full_mixed_delay(spec, state) -@with_all_phases +@with_phases([PHASE0, PHASE1]) @spec_state_test def test_proposer_not_in_attestations(spec, state): yield from rewards_helpers.run_test_proposer_not_in_attestations(spec, state) -@with_all_phases +@with_phases([PHASE0, PHASE1]) @spec_state_test def test_duplicate_attestations_at_later_slots(spec, state): yield from rewards_helpers.run_test_duplicate_attestations_at_later_slots(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_leak.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_leak.py index b0f9767b2..b2ed6f5d8 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_leak.py +++ b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_leak.py @@ -1,4 +1,4 @@ -from eth2spec.test.context import with_all_phases, spec_state_test +from eth2spec.test.context import PHASE0, PHASE1, with_all_phases, with_phases, spec_state_test from eth2spec.test.helpers.rewards import leaking import eth2spec.test.helpers.rewards as rewards_helpers @@ -38,7 +38,7 @@ def test_full_but_partial_participation_leak(spec, state): yield from rewards_helpers.run_test_full_but_partial_participation(spec, state) -@with_all_phases +@with_phases([PHASE0, PHASE1]) @spec_state_test @leaking() def test_one_attestation_one_correct_leak(spec, state): @@ -87,7 +87,7 @@ def test_some_very_low_effective_balances_that_did_not_attest_leak(spec, state): # -@with_all_phases +@with_phases([PHASE0, PHASE1]) @spec_state_test @leaking() def test_full_half_correct_target_incorrect_head_leak(spec, state): @@ -99,7 +99,7 @@ def test_full_half_correct_target_incorrect_head_leak(spec, state): ) -@with_all_phases +@with_phases([PHASE0, PHASE1]) @spec_state_test @leaking() def test_full_correct_target_incorrect_head_leak(spec, state): @@ -111,7 +111,7 @@ def test_full_correct_target_incorrect_head_leak(spec, state): ) -@with_all_phases +@with_phases([PHASE0, PHASE1]) @spec_state_test @leaking() def test_full_half_incorrect_target_incorrect_head_leak(spec, state): @@ -123,7 +123,7 @@ def test_full_half_incorrect_target_incorrect_head_leak(spec, state): ) -@with_all_phases +@with_phases([PHASE0, PHASE1]) @spec_state_test @leaking() def test_full_half_incorrect_target_correct_head_leak(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py index 83c7f7905..ae44c6640 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py @@ -29,6 +29,12 @@ def test_full_random_2(spec, state): yield from rewards_helpers.run_test_full_random(spec, state, rng=Random(3030)) +@with_all_phases +@spec_state_test +def test_full_random_3(spec, state): + yield from rewards_helpers.run_test_full_random(spec, state, rng=Random(4040)) + + @with_all_phases @with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.EJECTION_BALANCE) @spec_test diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py index 358fd5211..1834b290f 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py @@ -35,6 +35,7 @@ from eth2spec.test.context import ( with_configs, with_custom_state, large_validator_set, + is_post_lightclient_patch, ) @@ -780,15 +781,19 @@ def test_attestation(spec, state): spec, state, shard_transition=shard_transition, index=index, signed=True, on_time=True ) + if not is_post_lightclient_patch(spec): + pre_current_attestations_len = len(state.current_epoch_attestations) + # Add to state via block transition - pre_current_attestations_len = len(state.current_epoch_attestations) attestation_block.body.attestations.append(attestation) signed_attestation_block = state_transition_and_sign_block(spec, state, attestation_block) - assert len(state.current_epoch_attestations) == pre_current_attestations_len + 1 - - # Epoch transition should move to previous_epoch_attestations - pre_current_attestations_root = spec.hash_tree_root(state.current_epoch_attestations) + if not is_post_lightclient_patch(spec): + assert len(state.current_epoch_attestations) == pre_current_attestations_len + 1 + # Epoch transition should move to previous_epoch_attestations + pre_current_attestations_root = spec.hash_tree_root(state.current_epoch_attestations) + else: + pre_current_epoch_participation_root = spec.hash_tree_root(state.current_epoch_participation) epoch_block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH) signed_epoch_block = state_transition_and_sign_block(spec, state, epoch_block) @@ -796,8 +801,13 @@ def test_attestation(spec, state): yield 'blocks', [signed_attestation_block, signed_epoch_block] yield 'post', state - assert len(state.current_epoch_attestations) == 0 - assert spec.hash_tree_root(state.previous_epoch_attestations) == pre_current_attestations_root + if not is_post_lightclient_patch(spec): + assert len(state.current_epoch_attestations) == 0 + assert spec.hash_tree_root(state.previous_epoch_attestations) == pre_current_attestations_root + else: + for index in range(len(state.validators)): + assert state.current_epoch_participation[index] == 0 + assert spec.hash_tree_root(state.previous_epoch_participation) == pre_current_epoch_participation_root # In phase1 a committee is computed for SHARD_COMMITTEE_PERIOD slots ago,