From 4f24fe455b13a05728cf62b7efde70ccee4482f8 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 1 Dec 2021 11:37:30 -0700 Subject: [PATCH 01/98] basic capella withdrawal in place --- Makefile | 2 +- configs/minimal.yaml | 7 +- setup.py | 45 +- specs/capella/beacon-chain.md | 163 + specs/capella/fork.md | 90 + specs/capella/p2p-interface.md | 0 specs/capella/validator.md | 0 .../core/pyspec/eth2spec/capella/__init__.py | 1 + .../capella/fork/test_capella_fork_basic.py | 82 + .../capella/fork/test_capella_fork_random.py | 0 tests/core/pyspec/eth2spec/capella/mainnet.py | 3072 ++++++++++++++++ tests/core/pyspec/eth2spec/capella/minimal.py | 3074 +++++++++++++++++ tests/core/pyspec/eth2spec/test/context.py | 18 +- .../eth2spec/test/helpers/capella/fork.py | 47 + .../pyspec/eth2spec/test/helpers/constants.py | 5 +- .../pyspec/eth2spec/withdrawals/__init__.py | 1 + .../pyspec/eth2spec/withdrawals/mainnet.py | 3018 ++++++++++++++++ .../pyspec/eth2spec/withdrawals/minimal.py | 3018 ++++++++++++++++ 18 files changed, 12634 insertions(+), 9 deletions(-) create mode 100644 specs/capella/beacon-chain.md create mode 100644 specs/capella/fork.md create mode 100644 specs/capella/p2p-interface.md create mode 100644 specs/capella/validator.md create mode 100644 tests/core/pyspec/eth2spec/capella/__init__.py create mode 100644 tests/core/pyspec/eth2spec/capella/fork/test_capella_fork_basic.py create mode 100644 tests/core/pyspec/eth2spec/capella/fork/test_capella_fork_random.py create mode 100644 tests/core/pyspec/eth2spec/capella/mainnet.py create mode 100644 tests/core/pyspec/eth2spec/capella/minimal.py create mode 100644 tests/core/pyspec/eth2spec/test/helpers/capella/fork.py create mode 100644 tests/core/pyspec/eth2spec/withdrawals/__init__.py create mode 100644 tests/core/pyspec/eth2spec/withdrawals/mainnet.py create mode 100644 tests/core/pyspec/eth2spec/withdrawals/minimal.py diff --git a/Makefile b/Makefile index 7810ff19e2..3ef1246779 100644 --- a/Makefile +++ b/Makefile @@ -133,7 +133,7 @@ codespell: lint: pyspec . venv/bin/activate; cd $(PY_SPEC_DIR); \ flake8 --config $(LINTER_CONFIG_FILE) ./eth2spec \ - && mypy --config-file $(LINTER_CONFIG_FILE) -p eth2spec.phase0 -p eth2spec.altair -p eth2spec.merge + && mypy --config-file $(LINTER_CONFIG_FILE) -p eth2spec.phase0 -p eth2spec.altair -p eth2spec.merge -p eth2spec.capella lint_generators: pyspec . venv/bin/activate; cd $(TEST_GENERATORS_DIR); \ diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 1b5433ec3a..efa58b57d1 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -36,11 +36,16 @@ ALTAIR_FORK_EPOCH: 18446744073709551615 # Merge MERGE_FORK_VERSION: 0x02000001 MERGE_FORK_EPOCH: 18446744073709551615 +# Capella +CAPELLA_FORK_VERSION: 0x03000001 +CAPELLA_FORK_EPOCH: 18446744073709551615 # Sharding -SHARDING_FORK_VERSION: 0x03000001 +SHARDING_FORK_VERSION: 0x04000001 SHARDING_FORK_EPOCH: 18446744073709551615 + + # Time parameters # --------------------------------------------------------------- # [customized] Faster for testing purposes diff --git a/setup.py b/setup.py index 0ced87be2e..e5d5b4ca27 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ def installPackage(package: str): PHASE0 = 'phase0' ALTAIR = 'altair' MERGE = 'merge' +CAPELLA = 'capella' # The helper functions that are used when defining constants CONSTANT_DEP_SUNDRY_CONSTANTS_FUNCTIONS = ''' @@ -548,9 +549,38 @@ def hardcoded_custom_type_dep_constants(cls) -> str: return {**super().hardcoded_custom_type_dep_constants(), **constants} +# +# CapellaSpecBuilder +# +class CapellaSpecBuilder(MergeSpecBuilder): + fork: str = CAPELLA + + @classmethod + def imports(cls, preset_name: str): + return super().imports(preset_name) + f''' +from eth2spec.merge import {preset_name} as merge +''' + + @classmethod + def preparations(cls): + return super().preparations() + + @classmethod + def sundry_functions(cls) -> str: + return super().sundry_functions() + + @classmethod + def hardcoded_ssz_dep_constants(cls) -> Dict[str, str]: + constants = { + 'FINALIZED_ROOT_INDEX': 'GeneralizedIndex(105)', + 'NEXT_SYNC_COMMITTEE_INDEX': 'GeneralizedIndex(55)', + } + return {**super().hardcoded_ssz_dep_constants(), **constants} + + spec_builders = { builder.fork: builder - for builder in (Phase0SpecBuilder, AltairSpecBuilder, MergeSpecBuilder) + for builder in (Phase0SpecBuilder, AltairSpecBuilder, MergeSpecBuilder, CapellaSpecBuilder) } @@ -845,14 +875,14 @@ def finalize_options(self): if len(self.md_doc_paths) == 0: print("no paths were specified, using default markdown file paths for pyspec" " build (spec fork: %s)" % self.spec_fork) - if self.spec_fork in (PHASE0, ALTAIR, MERGE): + if self.spec_fork in (PHASE0, ALTAIR, MERGE, CAPELLA): self.md_doc_paths = """ specs/phase0/beacon-chain.md specs/phase0/fork-choice.md specs/phase0/validator.md specs/phase0/weak-subjectivity.md """ - if self.spec_fork in (ALTAIR, MERGE): + if self.spec_fork in (ALTAIR, MERGE, CAPELLA): self.md_doc_paths += """ specs/altair/beacon-chain.md specs/altair/bls.md @@ -861,13 +891,20 @@ def finalize_options(self): specs/altair/p2p-interface.md specs/altair/sync-protocol.md """ - if self.spec_fork == MERGE: + if self.spec_fork in (MERGE, CAPELLA): self.md_doc_paths += """ specs/merge/beacon-chain.md specs/merge/fork.md specs/merge/fork-choice.md specs/merge/validator.md """ + if self.spec_fork == CAPELLA: + self.md_doc_paths += """ + specs/capella/beacon-chain.md + specs/capella/fork.md + specs/capella/validator.md + specs/capella/p2p-interface.md + """ if len(self.md_doc_paths) == 0: raise Exception('no markdown files specified, and spec fork "%s" is unknown', self.spec_fork) diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md new file mode 100644 index 0000000000..2ab014daa5 --- /dev/null +++ b/specs/capella/beacon-chain.md @@ -0,0 +1,163 @@ +# Cappela -- The Beacon Chain + +## Table of contents + + + + + + + + +## Introduction + +Cappela is a consensus-layer upgrade containin a number of features related +to validator withdrawals. Including: +* Automatic withdrawals of `withdrawable` validators +* Partial withdrawals during block proposal +* Operation to change from `BLS_WITHDRAWAL_PREFIX` to + `ETH1_ADDRESS_WITHDRAWAL_PREFIX` versioned withdrawal credentials to enable withdrawals for a validator + +## Custom types + +| Name | SSZ equivalent | Description | +| - | - | - | +| `WithdrawalReceiptIndex` | `uint64` | a withdrawal receipt index | + +## Constants + +## Preset + +### State list lengths + +| Name | Value | Unit | Duration | +| - | - | :-: | :-: | +| `WITHDRAWAL_RECEIPT_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | withdrawal receipts| + +## Configuration + +## Containers + +### Extended Containers + +#### `BeaconState` + +```python +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[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + current_epoch_participation: List[ParticipationFlags, 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 + # Inactivity + inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] + # Sync + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + # Execution + latest_execution_payload_header: ExecutionPayloadHeader + # Withdrawals + withdrawal_receipts: List[WithdrawalReceipt, WITHDRAWAL_RECEIPT_LIMIT] # [New in Cappela] +``` + +### New containers + +#### `WithdrawalReceipt` + +```python +class WithdrawalReceipt(Container): + index: WithdrawalReceiptIndex + address: ExecutionAddress + amount: Gwei +``` + +## Helpers + +### Beacon state mutators + +#### `withdraw` + +```python +def withdraw(state: BeaconState, index: ValidatorIndex, amount: Gwei) -> None: + # Decrease the validator's balance + decrease_balance(state, index, amount) + # Create a corresponding withdrawal receipt + receipt = WithdrawalReceipt( + index=WithdrawalReceiptIndex(len(state.withdrawal_receipts)), + address=state.validators[index].withdrawal_credentials[12:], + amount=amount, + ) + state.withdrawal_receipts.append(receipt) +``` + +### Predicates + +#### `is_withdrawable_validator` + +```python +def is_withdrawable_validator(validator: Validator, epoch: Epoch) -> bool: + """ + Check if ``validator`` is withdrawable. + """ + return validator.withdrawable_epoch <= epoch +``` + +## Beacon chain state transition function + +### Epoch processing + +```python +def process_epoch(state: BeaconState) -> None: + process_justification_and_finalization(state) + process_inactivity_updates(state) + process_rewards_and_penalties(state) + process_registry_updates(state) + process_slashings(state) + process_eth1_data_reset(state) + process_effective_balance_updates(state) + process_slashings_reset(state) + process_randao_mixes_reset(state) + process_historical_roots_update(state) + process_participation_flag_updates(state) + process_sync_committee_updates(state) + process_withdrawals(state) # [New in Cappela] +``` + +#### Withdrawals + +*Note*: The function `process_inactivity_updates` is new. + +```python +def process_withdrawals(state: BeaconState) -> None: + current_epoch = get_current_epoch(state) + for index, validator in enumerate(state.validators): + balance = state.balances[index] + is_balance_nonzero = state.balances[index] == 0 + is_eth1_withdrawal_prefix = validator.withdrawal_credentials[0] != ETH1_ADDRESS_WITHDRAWAL_PREFIX + if is_balance_nonzero and is_eth1_withdrawal_prefix and is_withdrawable_validator(validator, current_epoch): + withdraw(state, ValidatorIndex(index), balance) +``` diff --git a/specs/capella/fork.md b/specs/capella/fork.md new file mode 100644 index 0000000000..e4c6549f74 --- /dev/null +++ b/specs/capella/fork.md @@ -0,0 +1,90 @@ +# Capella -- Fork Logic + +## Table of contents + + + + + + +## Introduction + +This document describes the process of the Capella upgrade. + +## Configuration + +Warning: this configuration is not definitive. + +| Name | Value | +| - | - | +| `CAPELLA_FORK_VERSION` | `Version('0x03000000')` | +| `CAPELLA_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** | + + +## Fork to Capella + +### Fork trigger + +The fork is triggered at epoch `CAPELLA_FORK_EPOCH`. + +Note that for the pure Capella networks, we don't apply `upgrade_to_capella` since it starts with Capella version logic. + +### Upgrading the state + +If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == CAPELLA_FORK_EPOCH`, +an irregular state change is made to upgrade to Capella. + +The upgrade occurs after the completion of the inner loop of `process_slots` that sets `state.slot` equal to `CAPELLA_FORK_EPOCH * SLOTS_PER_EPOCH`. +Care must be taken when transitioning through the fork boundary as implementations will need a modified [state transition function](../phase0/beacon-chain.md#beacon-chain-state-transition-function) that deviates from the Phase 0 document. +In particular, the outer `state_transition` function defined in the Phase 0 document will not expose the precise fork slot to execute the upgrade in the presence of skipped slots at the fork boundary. Instead the logic must be within `process_slots`. + +```python +def upgrade_to_capella(pre: merge.BeaconState) -> BeaconState: + epoch = merge.get_current_epoch(pre) + post = BeaconState( + # Versioning + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + current_version=CAPELLA_FORK_VERSION, + epoch=epoch, + ), + # History + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + # Eth1 + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + # Registry + validators=pre.validators, + balances=pre.balances, + # Randomness + randao_mixes=pre.randao_mixes, + # Slashings + slashings=pre.slashings, + # Participation + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + # Finality + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + # Inactivity + inactivity_scores=pre.inactivity_scores, + # Sync + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + # Execution-layer + latest_execution_payload_header=pre.latest_execution_payload_header, + # Withdrawals + withdrawal_receipts=[], + ) + + return post +``` diff --git a/specs/capella/p2p-interface.md b/specs/capella/p2p-interface.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/specs/capella/validator.md b/specs/capella/validator.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/capella/__init__.py b/tests/core/pyspec/eth2spec/capella/__init__.py new file mode 100644 index 0000000000..91b4a68537 --- /dev/null +++ b/tests/core/pyspec/eth2spec/capella/__init__.py @@ -0,0 +1 @@ +from . import mainnet as spec # noqa:F401 diff --git a/tests/core/pyspec/eth2spec/capella/fork/test_capella_fork_basic.py b/tests/core/pyspec/eth2spec/capella/fork/test_capella_fork_basic.py new file mode 100644 index 0000000000..73ccbb13b9 --- /dev/null +++ b/tests/core/pyspec/eth2spec/capella/fork/test_capella_fork_basic.py @@ -0,0 +1,82 @@ +from eth2spec.test.context import ( + with_phases, + with_custom_state, + with_presets, + spec_test, with_state, + low_balances, misc_balances, large_validator_set, +) +from eth2spec.test.utils import with_meta_tags +from eth2spec.test.helpers.constants import ( + MERGE, CAPELLA, + MINIMAL, +) +from eth2spec.test.helpers.state import ( + next_epoch, + next_epoch_via_block, +) +from eth2spec.test.helpers.capella.fork import ( + CAPELLA_FORK_TEST_META_TAGS, + run_fork_test, +) + + +@with_phases(phases=[MERGE], other_phases=[CAPELLA]) +@spec_test +@with_state +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_fork_base_state(spec, phases, state): + yield from run_fork_test(phases[CAPELLA], state) + + +@with_phases(phases=[MERGE], other_phases=[CAPELLA]) +@spec_test +@with_state +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_fork_next_epoch(spec, phases, state): + next_epoch(spec, state) + yield from run_fork_test(phases[CAPELLA], state) + + +@with_phases(phases=[MERGE], other_phases=[CAPELLA]) +@spec_test +@with_state +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_fork_next_epoch_with_block(spec, phases, state): + next_epoch_via_block(spec, state) + yield from run_fork_test(phases[CAPELLA], state) + + +@with_phases(phases=[MERGE], other_phases=[CAPELLA]) +@spec_test +@with_state +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_fork_many_next_epoch(spec, phases, state): + for _ in range(3): + next_epoch(spec, state) + yield from run_fork_test(phases[CAPELLA], state) + + +@with_phases(phases=[MERGE], other_phases=[CAPELLA]) +@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@spec_test +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_fork_random_low_balances(spec, phases, state): + yield from run_fork_test(phases[CAPELLA], state) + + +@with_phases(phases=[MERGE], other_phases=[CAPELLA]) +@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@spec_test +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_fork_random_misc_balances(spec, phases, state): + yield from run_fork_test(phases[CAPELLA], state) + + +@with_phases(phases=[MERGE], other_phases=[CAPELLA]) +@with_presets([MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") +@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@spec_test +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_fork_random_large_validator_set(spec, phases, state): + yield from run_fork_test(phases[CAPELLA], state) diff --git a/tests/core/pyspec/eth2spec/capella/fork/test_capella_fork_random.py b/tests/core/pyspec/eth2spec/capella/fork/test_capella_fork_random.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/capella/mainnet.py b/tests/core/pyspec/eth2spec/capella/mainnet.py new file mode 100644 index 0000000000..e7ec0b4bd4 --- /dev/null +++ b/tests/core/pyspec/eth2spec/capella/mainnet.py @@ -0,0 +1,3072 @@ +from lru import LRU +from dataclasses import ( + dataclass, + field, +) +from typing import ( + Any, Callable, Dict, Set, Sequence, Tuple, Optional, TypeVar, NamedTuple +) + +from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy, uint_to_bytes +from eth2spec.utils.ssz.ssz_typing import ( + View, boolean, Container, List, Vector, uint8, uint32, uint64, + Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist) +from eth2spec.utils.ssz.ssz_typing import Bitvector # noqa: F401 +from eth2spec.utils import bls +from eth2spec.utils.hash_function import hash + + +from typing import NewType, Union as PyUnion + +from eth2spec.phase0 import mainnet as phase0 +from eth2spec.utils.ssz.ssz_typing import Path + +from typing import Protocol +from eth2spec.altair import mainnet as altair +from eth2spec.utils.ssz.ssz_typing import Bytes8, Bytes20, ByteList, ByteVector, uint256 + +from eth2spec.merge import mainnet as merge + +SSZObject = TypeVar('SSZObject', bound=View) + + +SSZVariableName = str +GeneralizedIndex = NewType('GeneralizedIndex', int) + + +fork = 'capella' + + +MAX_BYTES_PER_TRANSACTION = uint64(2**30) + + +class Slot(uint64): + pass + + +class Epoch(uint64): + pass + + +class CommitteeIndex(uint64): + pass + + +class ValidatorIndex(uint64): + pass + + +class Gwei(uint64): + pass + + +class Root(Bytes32): + pass + + +class Hash32(Bytes32): + pass + + +class Version(Bytes4): + pass + + +class DomainType(Bytes4): + pass + + +class ForkDigest(Bytes4): + pass + + +class Domain(Bytes32): + pass + + +class BLSPubkey(Bytes48): + pass + + +class BLSSignature(Bytes96): + pass + + +class Ether(uint64): + pass + + +class ParticipationFlags(uint8): + pass + + +class ExecutionAddress(Bytes20): + pass + + +class PayloadId(Bytes8): + pass + + +class WithdrawalReceiptIndex(uint64): + pass + + +Transaction = ByteList[MAX_BYTES_PER_TRANSACTION] + + +def ceillog2(x: int) -> uint64: + if x < 1: + raise ValueError(f"ceillog2 accepts only positive values, x={x}") + return uint64((x - 1).bit_length()) + + +def floorlog2(x: int) -> uint64: + if x < 1: + raise ValueError(f"floorlog2 accepts only positive values, x={x}") + return uint64(x.bit_length() - 1) + + +FINALIZED_ROOT_INDEX = GeneralizedIndex(105) +NEXT_SYNC_COMMITTEE_INDEX = GeneralizedIndex(55) + +# Constant vars +GENESIS_SLOT = Slot(0) +GENESIS_EPOCH = Epoch(0) +FAR_FUTURE_EPOCH = Epoch(2**64 - 1) +BASE_REWARDS_PER_EPOCH = uint64(4) +DEPOSIT_CONTRACT_TREE_DEPTH = uint64(2**5) +JUSTIFICATION_BITS_LENGTH = uint64(4) +ENDIANNESS = 'little' +BLS_WITHDRAWAL_PREFIX = Bytes1('0x00') +ETH1_ADDRESS_WITHDRAWAL_PREFIX = Bytes1('0x01') +DOMAIN_BEACON_PROPOSER = DomainType('0x00000000') +DOMAIN_BEACON_ATTESTER = DomainType('0x01000000') +DOMAIN_RANDAO = DomainType('0x02000000') +DOMAIN_DEPOSIT = DomainType('0x03000000') +DOMAIN_VOLUNTARY_EXIT = DomainType('0x04000000') +DOMAIN_SELECTION_PROOF = DomainType('0x05000000') +DOMAIN_AGGREGATE_AND_PROOF = DomainType('0x06000000') +INTERVALS_PER_SLOT = uint64(3) +TARGET_AGGREGATORS_PER_COMMITTEE = 2**4 +RANDOM_SUBNETS_PER_VALIDATOR = 2**0 +EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION = 2**8 +ATTESTATION_SUBNET_COUNT = 64 +ETH_TO_GWEI = uint64(10**9) +SAFETY_DECAY = uint64(10) +TIMELY_SOURCE_FLAG_INDEX = 0 +TIMELY_TARGET_FLAG_INDEX = 1 +TIMELY_HEAD_FLAG_INDEX = 2 +TIMELY_SOURCE_WEIGHT = uint64(14) +TIMELY_TARGET_WEIGHT = uint64(26) +TIMELY_HEAD_WEIGHT = uint64(14) +SYNC_REWARD_WEIGHT = uint64(2) +PROPOSER_WEIGHT = uint64(8) +WEIGHT_DENOMINATOR = uint64(64) +DOMAIN_SYNC_COMMITTEE = DomainType('0x07000000') +DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF = DomainType('0x08000000') +DOMAIN_CONTRIBUTION_AND_PROOF = DomainType('0x09000000') +PARTICIPATION_FLAG_WEIGHTS = [TIMELY_SOURCE_WEIGHT, TIMELY_TARGET_WEIGHT, TIMELY_HEAD_WEIGHT] +G2_POINT_AT_INFINITY = BLSSignature(b'\xc0' + b'\x00' * 95) +TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE = 2**4 +SYNC_COMMITTEE_SUBNET_COUNT = 4 +WITHDRAWAL_RECEIPT_LIMIT = uint64(2**40) +CAPELLA_FORK_VERSION = Version('0x03000000') +CAPELLA_FORK_EPOCH = Epoch(18446744073709551615) + +# Preset vars +MAX_COMMITTEES_PER_SLOT = uint64(64) +TARGET_COMMITTEE_SIZE = uint64(128) +MAX_VALIDATORS_PER_COMMITTEE = uint64(2048) +SHUFFLE_ROUND_COUNT = uint64(90) +HYSTERESIS_QUOTIENT = uint64(4) +HYSTERESIS_DOWNWARD_MULTIPLIER = uint64(1) +HYSTERESIS_UPWARD_MULTIPLIER = uint64(5) +MIN_DEPOSIT_AMOUNT = Gwei(1000000000) +MAX_EFFECTIVE_BALANCE = Gwei(32000000000) +EFFECTIVE_BALANCE_INCREMENT = Gwei(1000000000) +MIN_ATTESTATION_INCLUSION_DELAY = uint64(1) +SLOTS_PER_EPOCH = uint64(32) +MIN_SEED_LOOKAHEAD = uint64(1) +MAX_SEED_LOOKAHEAD = uint64(4) +MIN_EPOCHS_TO_INACTIVITY_PENALTY = uint64(4) +EPOCHS_PER_ETH1_VOTING_PERIOD = uint64(64) +SLOTS_PER_HISTORICAL_ROOT = uint64(8192) +EPOCHS_PER_HISTORICAL_VECTOR = uint64(65536) +EPOCHS_PER_SLASHINGS_VECTOR = uint64(8192) +HISTORICAL_ROOTS_LIMIT = uint64(16777216) +VALIDATOR_REGISTRY_LIMIT = uint64(1099511627776) +BASE_REWARD_FACTOR = uint64(64) +WHISTLEBLOWER_REWARD_QUOTIENT = uint64(512) +PROPOSER_REWARD_QUOTIENT = uint64(8) +INACTIVITY_PENALTY_QUOTIENT = uint64(67108864) +MIN_SLASHING_PENALTY_QUOTIENT = uint64(128) +PROPORTIONAL_SLASHING_MULTIPLIER = uint64(1) +MAX_PROPOSER_SLASHINGS = 16 +MAX_ATTESTER_SLASHINGS = 2 +MAX_ATTESTATIONS = 128 +MAX_DEPOSITS = 16 +MAX_VOLUNTARY_EXITS = 16 +SAFE_SLOTS_TO_UPDATE_JUSTIFIED = 8 +INACTIVITY_PENALTY_QUOTIENT_ALTAIR = uint64(50331648) +MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR = uint64(64) +PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR = uint64(2) +SYNC_COMMITTEE_SIZE = uint64(512) +EPOCHS_PER_SYNC_COMMITTEE_PERIOD = uint64(256) +MIN_SYNC_COMMITTEE_PARTICIPANTS = 1 +MAX_BYTES_PER_TRANSACTION = uint64(1073741824) +MAX_TRANSACTIONS_PER_PAYLOAD = uint64(1048576) +BYTES_PER_LOGS_BLOOM = uint64(256) +MAX_EXTRA_DATA_BYTES = 32 +INACTIVITY_PENALTY_QUOTIENT_MERGE = uint64(16777216) +MIN_SLASHING_PENALTY_QUOTIENT_MERGE = uint64(32) +PROPORTIONAL_SLASHING_MULTIPLIER_MERGE = uint64(3) + + +class Configuration(NamedTuple): + PRESET_BASE: str + MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: uint64 + MIN_GENESIS_TIME: uint64 + GENESIS_FORK_VERSION: Version + GENESIS_DELAY: uint64 + SECONDS_PER_SLOT: uint64 + SECONDS_PER_ETH1_BLOCK: uint64 + MIN_VALIDATOR_WITHDRAWABILITY_DELAY: uint64 + SHARD_COMMITTEE_PERIOD: uint64 + ETH1_FOLLOW_DISTANCE: uint64 + EJECTION_BALANCE: Gwei + MIN_PER_EPOCH_CHURN_LIMIT: uint64 + CHURN_LIMIT_QUOTIENT: uint64 + PROPOSER_SCORE_BOOST: uint64 + INACTIVITY_SCORE_BIAS: uint64 + INACTIVITY_SCORE_RECOVERY_RATE: uint64 + ALTAIR_FORK_VERSION: Version + ALTAIR_FORK_EPOCH: Epoch + TERMINAL_TOTAL_DIFFICULTY: int + TERMINAL_BLOCK_HASH: Hash32 + TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: int + MERGE_FORK_VERSION: Version + MERGE_FORK_EPOCH: Epoch + + +config = Configuration( + PRESET_BASE="mainnet", + MIN_GENESIS_ACTIVE_VALIDATOR_COUNT=uint64(16384), + MIN_GENESIS_TIME=uint64(1606824000), + GENESIS_FORK_VERSION=Version('0x00000000'), + GENESIS_DELAY=uint64(604800), + SECONDS_PER_SLOT=uint64(12), + SECONDS_PER_ETH1_BLOCK=uint64(14), + MIN_VALIDATOR_WITHDRAWABILITY_DELAY=uint64(256), + SHARD_COMMITTEE_PERIOD=uint64(256), + ETH1_FOLLOW_DISTANCE=uint64(2048), + EJECTION_BALANCE=Gwei(16000000000), + MIN_PER_EPOCH_CHURN_LIMIT=uint64(4), + CHURN_LIMIT_QUOTIENT=uint64(65536), + PROPOSER_SCORE_BOOST=uint64(70), + INACTIVITY_SCORE_BIAS=uint64(4), + INACTIVITY_SCORE_RECOVERY_RATE=uint64(16), + ALTAIR_FORK_VERSION=Version('0x01000000'), + ALTAIR_FORK_EPOCH=Epoch(74240), + TERMINAL_TOTAL_DIFFICULTY=115792089237316195423570985008687907853269984665640564039457584007913129638912, + TERMINAL_BLOCK_HASH=Hash32('0x0000000000000000000000000000000000000000000000000000000000000000'), + TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH=18446744073709551615, + MERGE_FORK_VERSION=Version('0x02000000'), + MERGE_FORK_EPOCH=Epoch(18446744073709551615), +) + + +class Fork(Container): + previous_version: Version + current_version: Version + epoch: Epoch # Epoch of latest fork + + +class ForkData(Container): + current_version: Version + genesis_validators_root: Root + + +class Checkpoint(Container): + epoch: Epoch + root: Root + + +class Validator(Container): + pubkey: BLSPubkey + withdrawal_credentials: Bytes32 # Commitment to pubkey for withdrawals + effective_balance: Gwei # Balance at stake + slashed: boolean + # Status epochs + activation_eligibility_epoch: Epoch # When criteria for activation were met + activation_epoch: Epoch + exit_epoch: Epoch + withdrawable_epoch: Epoch # When validator can withdraw funds + + +class AttestationData(Container): + slot: Slot + index: CommitteeIndex + # LMD GHOST vote + beacon_block_root: Root + # FFG vote + source: Checkpoint + target: Checkpoint + + +class IndexedAttestation(Container): + attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] + data: AttestationData + signature: BLSSignature + + +class PendingAttestation(Container): + aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] + data: AttestationData + inclusion_delay: Slot + proposer_index: ValidatorIndex + + +class Eth1Data(Container): + deposit_root: Root + deposit_count: uint64 + block_hash: Hash32 + + +class HistoricalBatch(Container): + block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + + +class DepositMessage(Container): + pubkey: BLSPubkey + withdrawal_credentials: Bytes32 + amount: Gwei + + +class DepositData(Container): + pubkey: BLSPubkey + withdrawal_credentials: Bytes32 + amount: Gwei + signature: BLSSignature # Signing over DepositMessage + + +class BeaconBlockHeader(Container): + slot: Slot + proposer_index: ValidatorIndex + parent_root: Root + state_root: Root + body_root: Root + + +class SigningData(Container): + object_root: Root + domain: Domain + + +class AttesterSlashing(Container): + attestation_1: IndexedAttestation + attestation_2: IndexedAttestation + + +class Attestation(Container): + aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] + data: AttestationData + signature: BLSSignature + + +class Deposit(Container): + proof: Vector[Bytes32, DEPOSIT_CONTRACT_TREE_DEPTH + 1] # Merkle path to deposit root + data: DepositData + + +class VoluntaryExit(Container): + epoch: Epoch # Earliest epoch when voluntary exit can be processed + validator_index: ValidatorIndex + + +class SignedVoluntaryExit(Container): + message: VoluntaryExit + signature: BLSSignature + + +class SignedBeaconBlockHeader(Container): + message: BeaconBlockHeader + signature: BLSSignature + + +class ProposerSlashing(Container): + signed_header_1: SignedBeaconBlockHeader + signed_header_2: SignedBeaconBlockHeader + + +class Eth1Block(Container): + timestamp: uint64 + deposit_root: Root + deposit_count: uint64 + # All other eth1 block fields + + +class AggregateAndProof(Container): + aggregator_index: ValidatorIndex + aggregate: Attestation + selection_proof: BLSSignature + + +class SignedAggregateAndProof(Container): + message: AggregateAndProof + signature: BLSSignature + + +class SyncAggregate(Container): + sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] + sync_committee_signature: BLSSignature + + +class SyncCommittee(Container): + pubkeys: Vector[BLSPubkey, SYNC_COMMITTEE_SIZE] + aggregate_pubkey: BLSPubkey + + +class SyncCommitteeMessage(Container): + # Slot to which this contribution pertains + slot: Slot + # Block root for this signature + beacon_block_root: Root + # Index of the validator that produced this signature + validator_index: ValidatorIndex + # Signature by the validator over the block root of `slot` + signature: BLSSignature + + +class SyncCommitteeContribution(Container): + # Slot to which this contribution pertains + slot: Slot + # Block root for this contribution + beacon_block_root: Root + # The subcommittee this contribution pertains to out of the broader sync committee + subcommittee_index: uint64 + # A bit is set if a signature from the validator at the corresponding + # index in the subcommittee is present in the aggregate `signature`. + aggregation_bits: Bitvector[SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT] + # Signature by the validator(s) over the block root of `slot` + signature: BLSSignature + + +class ContributionAndProof(Container): + aggregator_index: ValidatorIndex + contribution: SyncCommitteeContribution + selection_proof: BLSSignature + + +class SignedContributionAndProof(Container): + message: ContributionAndProof + signature: BLSSignature + + +class SyncAggregatorSelectionData(Container): + slot: Slot + subcommittee_index: uint64 + + +class LightClientSnapshot(Container): + # Beacon block header + header: BeaconBlockHeader + # Sync committees corresponding to the header + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + + +class LightClientUpdate(Container): + # Update beacon block header + header: BeaconBlockHeader + # Next sync committee corresponding to the header + next_sync_committee: SyncCommittee + next_sync_committee_branch: Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_INDEX)] + # Finality proof for the update header + finality_header: BeaconBlockHeader + finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX)] + # Sync committee aggregate signature + sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] + sync_committee_signature: BLSSignature + # Fork version for the aggregate signature + fork_version: Version + + +class ExecutionPayload(Container): + # Execution block header fields + parent_hash: Hash32 + fee_recipient: ExecutionAddress # 'beneficiary' in the yellow paper + state_root: Bytes32 + receipt_root: Bytes32 # 'receipts root' in the yellow paper + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + random: Bytes32 # 'difficulty' in the yellow paper + block_number: uint64 # 'number' in the yellow paper + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + # Extra payload fields + block_hash: Hash32 # Hash of execution block + transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] + + +class BeaconBlockBody(Container): + randao_reveal: BLSSignature + eth1_data: Eth1Data # Eth1 data vote + graffiti: Bytes32 # Arbitrary data + # Operations + proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] + attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] + attestations: List[Attestation, MAX_ATTESTATIONS] + deposits: List[Deposit, MAX_DEPOSITS] + voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] + sync_aggregate: SyncAggregate + # Execution + execution_payload: ExecutionPayload # [New in Merge] + + +class BeaconBlock(Container): + slot: Slot + proposer_index: ValidatorIndex + parent_root: Root + state_root: Root + body: BeaconBlockBody + + +class SignedBeaconBlock(Container): + message: BeaconBlock + signature: BLSSignature + + +class ExecutionPayloadHeader(Container): + # Execution block header fields + parent_hash: Hash32 + fee_recipient: ExecutionAddress + state_root: Bytes32 + receipt_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + random: Bytes32 + block_number: uint64 + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + # Extra payload fields + block_hash: Hash32 # Hash of execution block + transactions_root: Root + + +class PowBlock(Container): + block_hash: Hash32 + parent_hash: Hash32 + total_difficulty: uint256 + + +class WithdrawalReceipt(Container): + index: WithdrawalReceiptIndex + address: ExecutionAddress + amount: Gwei + + +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[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + current_epoch_participation: List[ParticipationFlags, 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 + # Inactivity + inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] + # Sync + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + # Execution + latest_execution_payload_header: ExecutionPayloadHeader + # Withdrawals + withdrawal_receipts: List[WithdrawalReceipt, WITHDRAWAL_RECEIPT_LIMIT] # [New in Cappela] + + +@dataclass(eq=True, frozen=True) +class LatestMessage(object): + epoch: Epoch + root: Root + + +@dataclass +class Store(object): + time: uint64 + genesis_time: uint64 + justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + best_justified_checkpoint: Checkpoint + proposer_boost_root: Root + blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) + block_states: Dict[Root, BeaconState] = field(default_factory=dict) + checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict) + latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict) + + +@dataclass +class LightClientStore(object): + snapshot: LightClientSnapshot + valid_updates: Set[LightClientUpdate] + + +@dataclass +class PayloadAttributes(object): + timestamp: uint64 + random: Bytes32 + suggested_fee_recipient: ExecutionAddress + + +class ExecutionEngine(Protocol): + + def execute_payload(self, execution_payload: ExecutionPayload) -> bool: + """ + Return ``True`` if and only if ``execution_payload`` is valid with respect to ``self.execution_state``. + """ + ... + + def notify_forkchoice_updated(self, + head_block_hash: Hash32, + finalized_block_hash: Hash32, + payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: + ... + + def get_payload(self, payload_id: PayloadId) -> ExecutionPayload: + """ + Return ``execution_payload`` object. + """ + ... + + +def integer_squareroot(n: uint64) -> uint64: + """ + Return the largest integer ``x`` such that ``x**2 <= n``. + """ + x = n + y = (x + 1) // 2 + while y < x: + x = y + y = (x + n // x) // 2 + return x + + +def xor(bytes_1: Bytes32, bytes_2: Bytes32) -> Bytes32: + """ + Return the exclusive-or of two 32-byte strings. + """ + return Bytes32(a ^ b for a, b in zip(bytes_1, bytes_2)) + + +def bytes_to_uint64(data: bytes) -> uint64: + """ + Return the integer deserialization of ``data`` interpreted as ``ENDIANNESS``-endian. + """ + return uint64(int.from_bytes(data, ENDIANNESS)) + + +def is_active_validator(validator: Validator, epoch: Epoch) -> bool: + """ + Check if ``validator`` is active. + """ + return validator.activation_epoch <= epoch < validator.exit_epoch + + +def is_eligible_for_activation_queue(validator: Validator) -> bool: + """ + Check if ``validator`` is eligible to be placed into the activation queue. + """ + return ( + validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH + and validator.effective_balance == MAX_EFFECTIVE_BALANCE + ) + + +def is_eligible_for_activation(state: BeaconState, validator: Validator) -> bool: + """ + Check if ``validator`` is eligible for activation. + """ + return ( + # Placement in queue is finalized + validator.activation_eligibility_epoch <= state.finalized_checkpoint.epoch + # Has not yet been activated + and validator.activation_epoch == FAR_FUTURE_EPOCH + ) + + +def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool: + """ + Check if ``validator`` is slashable. + """ + return (not validator.slashed) and (validator.activation_epoch <= epoch < validator.withdrawable_epoch) + + +def is_slashable_attestation_data(data_1: AttestationData, data_2: AttestationData) -> bool: + """ + Check if ``data_1`` and ``data_2`` are slashable according to Casper FFG rules. + """ + return ( + # Double vote + (data_1 != data_2 and data_1.target.epoch == data_2.target.epoch) or + # Surround vote + (data_1.source.epoch < data_2.source.epoch and data_2.target.epoch < data_1.target.epoch) + ) + + +def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool: + """ + Check if ``indexed_attestation`` is not empty, has sorted and unique indices and has a valid aggregate signature. + """ + # Verify indices are sorted and unique + indices = indexed_attestation.attesting_indices + if len(indices) == 0 or not indices == sorted(set(indices)): + return False + # Verify aggregate signature + pubkeys = [state.validators[i].pubkey for i in indices] + domain = get_domain(state, DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch) + signing_root = compute_signing_root(indexed_attestation.data, domain) + return bls.FastAggregateVerify(pubkeys, signing_root, indexed_attestation.signature) + + +def is_valid_merkle_branch(leaf: Bytes32, branch: Sequence[Bytes32], depth: uint64, index: uint64, root: Root) -> bool: + """ + Check if ``leaf`` at ``index`` verifies against the Merkle ``root`` and ``branch``. + """ + value = leaf + for i in range(depth): + if index // (2**i) % 2: + value = hash(branch[i] + value) + else: + value = hash(value + branch[i]) + return value == root + + +def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) -> uint64: + """ + Return the shuffled index corresponding to ``seed`` (and ``index_count``). + """ + assert index < index_count + + # Swap or not (https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf) + # See the 'generalized domain' algorithm on page 3 + for current_round in range(SHUFFLE_ROUND_COUNT): + pivot = bytes_to_uint64(hash(seed + uint_to_bytes(uint8(current_round)))[0:8]) % index_count + flip = (pivot + index_count - index) % index_count + position = max(index, flip) + source = hash( + seed + + uint_to_bytes(uint8(current_round)) + + uint_to_bytes(uint32(position // 256)) + ) + byte = uint8(source[(position % 256) // 8]) + bit = (byte >> (position % 8)) % 2 + index = flip if bit else index + + return index + + +def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex], seed: Bytes32) -> ValidatorIndex: + """ + Return from ``indices`` a random index sampled by effective balance. + """ + assert len(indices) > 0 + MAX_RANDOM_BYTE = 2**8 - 1 + i = uint64(0) + total = uint64(len(indices)) + while True: + candidate_index = indices[compute_shuffled_index(i % total, total, seed)] + random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] + effective_balance = state.validators[candidate_index].effective_balance + if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: + return candidate_index + i += 1 + + +def compute_committee(indices: Sequence[ValidatorIndex], + seed: Bytes32, + index: uint64, + count: uint64) -> Sequence[ValidatorIndex]: + """ + Return the committee corresponding to ``indices``, ``seed``, ``index``, and committee ``count``. + """ + start = (len(indices) * index) // count + end = (len(indices) * uint64(index + 1)) // count + return [indices[compute_shuffled_index(uint64(i), uint64(len(indices)), seed)] for i in range(start, end)] + + +def compute_epoch_at_slot(slot: Slot) -> Epoch: + """ + Return the epoch number at ``slot``. + """ + return Epoch(slot // SLOTS_PER_EPOCH) + + +def compute_start_slot_at_epoch(epoch: Epoch) -> Slot: + """ + Return the start slot of ``epoch``. + """ + return Slot(epoch * SLOTS_PER_EPOCH) + + +def compute_activation_exit_epoch(epoch: Epoch) -> Epoch: + """ + Return the epoch during which validator activations and exits initiated in ``epoch`` take effect. + """ + return Epoch(epoch + 1 + MAX_SEED_LOOKAHEAD) + + +def compute_fork_data_root(current_version: Version, genesis_validators_root: Root) -> Root: + """ + Return the 32-byte fork data root for the ``current_version`` and ``genesis_validators_root``. + This is used primarily in signature domains to avoid collisions across forks/chains. + """ + return hash_tree_root(ForkData( + current_version=current_version, + genesis_validators_root=genesis_validators_root, + )) + + +def compute_fork_digest(current_version: Version, genesis_validators_root: Root) -> ForkDigest: + """ + Return the 4-byte fork digest for the ``current_version`` and ``genesis_validators_root``. + This is a digest primarily used for domain separation on the p2p layer. + 4-bytes suffices for practical separation of forks/chains. + """ + return ForkDigest(compute_fork_data_root(current_version, genesis_validators_root)[:4]) + + +def compute_domain(domain_type: DomainType, fork_version: Version=None, genesis_validators_root: Root=None) -> Domain: + """ + Return the domain for the ``domain_type`` and ``fork_version``. + """ + if fork_version is None: + fork_version = config.GENESIS_FORK_VERSION + if genesis_validators_root is None: + genesis_validators_root = Root() # all bytes zero by default + fork_data_root = compute_fork_data_root(fork_version, genesis_validators_root) + return Domain(domain_type + fork_data_root[:28]) + + +def compute_signing_root(ssz_object: SSZObject, domain: Domain) -> Root: + """ + Return the signing root for the corresponding signing data. + """ + return hash_tree_root(SigningData( + object_root=hash_tree_root(ssz_object), + domain=domain, + )) + + +def get_current_epoch(state: BeaconState) -> Epoch: + """ + Return the current epoch. + """ + return compute_epoch_at_slot(state.slot) + + +def get_previous_epoch(state: BeaconState) -> Epoch: + """` + Return the previous epoch (unless the current epoch is ``GENESIS_EPOCH``). + """ + current_epoch = get_current_epoch(state) + return GENESIS_EPOCH if current_epoch == GENESIS_EPOCH else Epoch(current_epoch - 1) + + +def get_block_root(state: BeaconState, epoch: Epoch) -> Root: + """ + Return the block root at the start of a recent ``epoch``. + """ + return get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch)) + + +def get_block_root_at_slot(state: BeaconState, slot: Slot) -> Root: + """ + Return the block root at a recent ``slot``. + """ + assert slot < state.slot <= slot + SLOTS_PER_HISTORICAL_ROOT + return state.block_roots[slot % SLOTS_PER_HISTORICAL_ROOT] + + +def get_randao_mix(state: BeaconState, epoch: Epoch) -> Bytes32: + """ + Return the randao mix at a recent ``epoch``. + """ + return state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR] + + +def get_active_validator_indices(state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: + """ + Return the sequence of active validator indices at ``epoch``. + """ + return [ValidatorIndex(i) for i, v in enumerate(state.validators) if is_active_validator(v, epoch)] + + +def get_validator_churn_limit(state: BeaconState) -> uint64: + """ + Return the validator churn limit for the current epoch. + """ + active_validator_indices = get_active_validator_indices(state, get_current_epoch(state)) + return max(config.MIN_PER_EPOCH_CHURN_LIMIT, uint64(len(active_validator_indices)) // config.CHURN_LIMIT_QUOTIENT) + + +def get_seed(state: BeaconState, epoch: Epoch, domain_type: DomainType) -> Bytes32: + """ + Return the seed at ``epoch``. + """ + mix = get_randao_mix(state, Epoch(epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD - 1)) # Avoid underflow + return hash(domain_type + uint_to_bytes(epoch) + mix) + + +def get_committee_count_per_slot(state: BeaconState, epoch: Epoch) -> uint64: + """ + Return the number of committees in each slot for the given ``epoch``. + """ + return max(uint64(1), min( + MAX_COMMITTEES_PER_SLOT, + uint64(len(get_active_validator_indices(state, epoch))) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE, + )) + + +def get_beacon_committee(state: BeaconState, slot: Slot, index: CommitteeIndex) -> Sequence[ValidatorIndex]: + """ + Return the beacon committee at ``slot`` for ``index``. + """ + epoch = compute_epoch_at_slot(slot) + committees_per_slot = get_committee_count_per_slot(state, epoch) + return compute_committee( + indices=get_active_validator_indices(state, epoch), + seed=get_seed(state, epoch, DOMAIN_BEACON_ATTESTER), + index=(slot % SLOTS_PER_EPOCH) * committees_per_slot + index, + count=committees_per_slot * SLOTS_PER_EPOCH, + ) + + +def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: + """ + Return the beacon proposer index at the current slot. + """ + epoch = get_current_epoch(state) + seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + uint_to_bytes(state.slot)) + indices = get_active_validator_indices(state, epoch) + return compute_proposer_index(state, indices, seed) + + +def get_total_balance(state: BeaconState, indices: Set[ValidatorIndex]) -> Gwei: + """ + Return the combined effective balance of the ``indices``. + ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. + Math safe up to ~10B ETH, afterwhich this overflows uint64. + """ + return Gwei(max(EFFECTIVE_BALANCE_INCREMENT, sum([state.validators[index].effective_balance for index in indices]))) + + +def get_total_active_balance(state: BeaconState) -> Gwei: + """ + Return the combined effective balance of the active validators. + Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. + """ + return get_total_balance(state, set(get_active_validator_indices(state, get_current_epoch(state)))) + + +def get_domain(state: BeaconState, domain_type: DomainType, epoch: Epoch=None) -> Domain: + """ + Return the signature domain (fork version concatenated with domain type) of a message. + """ + epoch = get_current_epoch(state) if epoch is None else epoch + fork_version = state.fork.previous_version if epoch < state.fork.epoch else state.fork.current_version + return compute_domain(domain_type, fork_version, state.genesis_validators_root) + + +def get_indexed_attestation(state: BeaconState, attestation: Attestation) -> IndexedAttestation: + """ + Return the indexed attestation corresponding to ``attestation``. + """ + attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) + + return IndexedAttestation( + attesting_indices=sorted(attesting_indices), + data=attestation.data, + signature=attestation.signature, + ) + + +def get_attesting_indices(state: BeaconState, + data: AttestationData, + bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Set[ValidatorIndex]: + """ + Return the set of attesting indices corresponding to ``data`` and ``bits``. + """ + committee = get_beacon_committee(state, data.slot, data.index) + return set(index for i, index in enumerate(committee) if bits[i]) + + +def increase_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: + """ + Increase the validator balance at index ``index`` by ``delta``. + """ + state.balances[index] += delta + + +def decrease_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: + """ + Decrease the validator balance at index ``index`` by ``delta``, with underflow protection. + """ + state.balances[index] = 0 if delta > state.balances[index] else state.balances[index] - delta + + +def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: + """ + Initiate the exit of the validator with index ``index``. + """ + # Return if validator already initiated exit + validator = state.validators[index] + if validator.exit_epoch != FAR_FUTURE_EPOCH: + return + + # Compute exit queue epoch + exit_epochs = [v.exit_epoch for v in state.validators if v.exit_epoch != FAR_FUTURE_EPOCH] + exit_queue_epoch = max(exit_epochs + [compute_activation_exit_epoch(get_current_epoch(state))]) + exit_queue_churn = len([v for v in state.validators if v.exit_epoch == exit_queue_epoch]) + if exit_queue_churn >= get_validator_churn_limit(state): + exit_queue_epoch += Epoch(1) + + # Set validator exit epoch and withdrawable epoch + validator.exit_epoch = exit_queue_epoch + validator.withdrawable_epoch = Epoch(validator.exit_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY) + + +def slash_validator(state: BeaconState, + slashed_index: ValidatorIndex, + whistleblower_index: ValidatorIndex=None) -> None: + """ + Slash the validator with index ``slashed_index``. + """ + epoch = get_current_epoch(state) + initiate_validator_exit(state, slashed_index) + validator = state.validators[slashed_index] + validator.slashed = True + validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR)) + state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance + slashing_penalty = validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_MERGE # [Modified in Merge] + decrease_balance(state, slashed_index, slashing_penalty) + + # Apply proposer and whistleblower rewards + proposer_index = get_beacon_proposer_index(state) + if whistleblower_index is None: + whistleblower_index = proposer_index + whistleblower_reward = Gwei(validator.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT) + proposer_reward = Gwei(whistleblower_reward * PROPOSER_WEIGHT // WEIGHT_DENOMINATOR) + increase_balance(state, proposer_index, proposer_reward) + increase_balance(state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward)) + + +def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, + eth1_timestamp: uint64, + deposits: Sequence[Deposit], + execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader() + ) -> BeaconState: + fork = Fork( + previous_version=config.MERGE_FORK_VERSION, # [Modified in Merge] for testing only + current_version=config.MERGE_FORK_VERSION, # [Modified in Merge] + epoch=GENESIS_EPOCH, + ) + state = BeaconState( + genesis_time=eth1_timestamp + config.GENESIS_DELAY, + fork=fork, + eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))), + latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), + randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy + ) + + # Process deposits + leaves = list(map(lambda deposit: deposit.data, deposits)) + for index, deposit in enumerate(deposits): + deposit_data_list = List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[:index + 1]) + state.eth1_data.deposit_root = hash_tree_root(deposit_data_list) + process_deposit(state, deposit) + + # Process activations + for index, validator in enumerate(state.validators): + balance = state.balances[index] + validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) + if validator.effective_balance == MAX_EFFECTIVE_BALANCE: + validator.activation_eligibility_epoch = GENESIS_EPOCH + validator.activation_epoch = GENESIS_EPOCH + + # Set genesis validators root for domain separation and chain versioning + state.genesis_validators_root = hash_tree_root(state.validators) + + # Fill in sync committees + # Note: A duplicate committee is assigned for the current and next committee at genesis + state.current_sync_committee = get_next_sync_committee(state) + state.next_sync_committee = get_next_sync_committee(state) + + # [New in Merge] Initialize the execution payload header + # If empty, will initialize a chain that has not yet gone through the Merge transition + state.latest_execution_payload_header = execution_payload_header + + return state + + +def is_valid_genesis_state(state: BeaconState) -> bool: + if state.genesis_time < config.MIN_GENESIS_TIME: + return False + if len(get_active_validator_indices(state, GENESIS_EPOCH)) < config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: + return False + return True + + +def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool=True) -> None: + block = signed_block.message + # Process slots (including those with no blocks) since block + process_slots(state, block.slot) + # Verify signature + if validate_result: + assert verify_block_signature(state, signed_block) + # Process block + process_block(state, block) + # Verify state root + if validate_result: + assert block.state_root == hash_tree_root(state) + + +def verify_block_signature(state: BeaconState, signed_block: SignedBeaconBlock) -> bool: + proposer = state.validators[signed_block.message.proposer_index] + signing_root = compute_signing_root(signed_block.message, get_domain(state, DOMAIN_BEACON_PROPOSER)) + return bls.Verify(proposer.pubkey, signing_root, signed_block.signature) + + +def process_slots(state: BeaconState, slot: Slot) -> None: + assert state.slot < slot + while state.slot < slot: + process_slot(state) + # Process epoch on the start slot of the next epoch + if (state.slot + 1) % SLOTS_PER_EPOCH == 0: + process_epoch(state) + state.slot = Slot(state.slot + 1) + + +def process_slot(state: BeaconState) -> None: + # Cache state root + previous_state_root = hash_tree_root(state) + state.state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_state_root + # Cache latest block header state root + if state.latest_block_header.state_root == Bytes32(): + state.latest_block_header.state_root = previous_state_root + # Cache block root + previous_block_root = hash_tree_root(state.latest_block_header) + state.block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_block_root + + +def process_epoch(state: BeaconState) -> None: + process_justification_and_finalization(state) + process_inactivity_updates(state) + process_rewards_and_penalties(state) + process_registry_updates(state) + process_slashings(state) + process_eth1_data_reset(state) + process_effective_balance_updates(state) + process_slashings_reset(state) + process_randao_mixes_reset(state) + process_historical_roots_update(state) + process_participation_flag_updates(state) + process_sync_committee_updates(state) + process_withdrawals(state) # [New in Cappela] + + +def get_matching_source_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: + assert epoch in (get_previous_epoch(state), get_current_epoch(state)) + return state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations + + +def get_matching_target_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: + return [ + a for a in get_matching_source_attestations(state, epoch) + if a.data.target.root == get_block_root(state, epoch) + ] + + +def get_matching_head_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: + return [ + a for a in get_matching_target_attestations(state, epoch) + if a.data.beacon_block_root == get_block_root_at_slot(state, a.data.slot) + ] + + +def get_unslashed_attesting_indices(state: BeaconState, + attestations: Sequence[PendingAttestation]) -> Set[ValidatorIndex]: + output = set() # type: Set[ValidatorIndex] + for a in attestations: + output = output.union(get_attesting_indices(state, a.data, a.aggregation_bits)) + return set(filter(lambda index: not state.validators[index].slashed, output)) + + +def get_attesting_balance(state: BeaconState, attestations: Sequence[PendingAttestation]) -> Gwei: + """ + Return the combined effective balance of the set of unslashed validators participating in ``attestations``. + Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. + """ + return get_total_balance(state, get_unslashed_attesting_indices(state, attestations)) + + +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_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)) + current_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_current_epoch(state)) + total_active_balance = get_total_active_balance(state) + previous_target_balance = get_total_balance(state, previous_indices) + current_target_balance = get_total_balance(state, current_indices) + weigh_justification_and_finalization(state, total_active_balance, previous_target_balance, current_target_balance) + + +def weigh_justification_and_finalization(state: BeaconState, + total_active_balance: Gwei, + previous_epoch_target_balance: Gwei, + current_epoch_target_balance: Gwei) -> None: + 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 + + # 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 + if previous_epoch_target_balance * 3 >= total_active_balance * 2: + state.current_justified_checkpoint = Checkpoint(epoch=previous_epoch, + root=get_block_root(state, previous_epoch)) + state.justification_bits[1] = 0b1 + if current_epoch_target_balance * 3 >= total_active_balance * 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 + + +def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: + """ + Return the base reward for the validator defined by ``index`` with respect to the current ``state``. + """ + increments = state.validators[index].effective_balance // EFFECTIVE_BALANCE_INCREMENT + return Gwei(increments * get_base_reward_per_increment(state)) + + +def get_proposer_reward(state: BeaconState, attesting_index: ValidatorIndex) -> Gwei: + return Gwei(get_base_reward(state, attesting_index) // PROPOSER_REWARD_QUOTIENT) + + +def get_finality_delay(state: BeaconState) -> uint64: + return get_previous_epoch(state) - state.finalized_checkpoint.epoch + + +def is_in_inactivity_leak(state: BeaconState) -> bool: + return get_finality_delay(state) > MIN_EPOCHS_TO_INACTIVITY_PENALTY + + +def get_eligible_validator_indices(state: BeaconState) -> Sequence[ValidatorIndex]: + previous_epoch = get_previous_epoch(state) + 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) + ] + + +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)] * 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) + 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 + if is_in_inactivity_leak(state): + # Since full base reward will be canceled out by inactivity penalty deltas, + # optimal participation receives full base reward compensation here. + rewards[index] += get_base_reward(state, index) + else: + 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 + + +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 get_attestation_component_deltas(state, matching_source_attestations) + + +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 get_attestation_component_deltas(state, matching_target_attestations) + + +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 get_attestation_component_deltas(state, matching_head_attestations) + + +def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return proposer and inclusion delay micro-rewards/penalties for each validator. + """ + rewards = [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 + if index in get_attesting_indices(state, a.data, a.aggregation_bits) + ], key=lambda a: a.inclusion_delay) + rewards[attestation.proposer_index] += get_proposer_reward(state, index) + max_attester_reward = Gwei(get_base_reward(state, index) - get_proposer_reward(state, index)) + 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 + + +def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return the inactivity penalty deltas by considering timely target participation flags and inactivity scores. + """ + rewards = [Gwei(0) for _ in range(len(state.validators))] + penalties = [Gwei(0) for _ in range(len(state.validators))] + previous_epoch = get_previous_epoch(state) + matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, previous_epoch) + for index in get_eligible_validator_indices(state): + if index not in matching_target_indices: + penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index] + # [Modified in Merge] + penalty_denominator = config.INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_MERGE + penalties[index] += Gwei(penalty_numerator // penalty_denominator) + return rewards, penalties + + +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) + 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 + + +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_index_deltas(state, flag_index) for flag_index in range(len(PARTICIPATION_FLAG_WEIGHTS))] + 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]) + + +def process_registry_updates(state: BeaconState) -> None: + # Process activation eligibility and ejections + for index, validator in enumerate(state.validators): + if is_eligible_for_activation_queue(validator): + validator.activation_eligibility_epoch = get_current_epoch(state) + 1 + + if ( + is_active_validator(validator, get_current_epoch(state)) + and validator.effective_balance <= config.EJECTION_BALANCE + ): + initiate_validator_exit(state, ValidatorIndex(index)) + + # Queue validators eligible for activation and not yet dequeued for activation + activation_queue = sorted([ + index for index, validator in enumerate(state.validators) + if is_eligible_for_activation(state, validator) + # Order by the sequence of activation_eligibility_epoch setting and then index + ], key=lambda index: (state.validators[index].activation_eligibility_epoch, index)) + # Dequeued validators for activation up to churn limit + for index in activation_queue[:get_validator_churn_limit(state)]: + validator = state.validators[index] + validator.activation_epoch = compute_activation_exit_epoch(get_current_epoch(state)) + + +def process_slashings(state: BeaconState) -> None: + epoch = get_current_epoch(state) + total_balance = get_total_active_balance(state) + adjusted_total_slashing_balance = min( + sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_MERGE, # [Modified in Merge] + total_balance + ) + for index, validator in enumerate(state.validators): + if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch: + increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow + penalty_numerator = validator.effective_balance // increment * adjusted_total_slashing_balance + penalty = penalty_numerator // total_balance * increment + decrease_balance(state, ValidatorIndex(index), penalty) + + +def process_eth1_data_reset(state: BeaconState) -> None: + next_epoch = Epoch(get_current_epoch(state) + 1) + # Reset eth1 data votes + if next_epoch % EPOCHS_PER_ETH1_VOTING_PERIOD == 0: + state.eth1_data_votes = [] + + +def process_effective_balance_updates(state: BeaconState) -> None: + # Update effective balances with hysteresis + for index, validator in enumerate(state.validators): + balance = state.balances[index] + HYSTERESIS_INCREMENT = uint64(EFFECTIVE_BALANCE_INCREMENT // HYSTERESIS_QUOTIENT) + DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER + UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER + if ( + balance + DOWNWARD_THRESHOLD < validator.effective_balance + or validator.effective_balance + UPWARD_THRESHOLD < balance + ): + validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) + + +def process_slashings_reset(state: BeaconState) -> None: + next_epoch = Epoch(get_current_epoch(state) + 1) + # Reset slashings + state.slashings[next_epoch % EPOCHS_PER_SLASHINGS_VECTOR] = Gwei(0) + + +def process_randao_mixes_reset(state: BeaconState) -> None: + current_epoch = get_current_epoch(state) + next_epoch = Epoch(current_epoch + 1) + # Set randao mix + state.randao_mixes[next_epoch % EPOCHS_PER_HISTORICAL_VECTOR] = get_randao_mix(state, current_epoch) + + +def process_historical_roots_update(state: BeaconState) -> None: + # Set historical root accumulator + next_epoch = Epoch(get_current_epoch(state) + 1) + if next_epoch % (SLOTS_PER_HISTORICAL_ROOT // SLOTS_PER_EPOCH) == 0: + historical_batch = HistoricalBatch(block_roots=state.block_roots, state_roots=state.state_roots) + state.historical_roots.append(hash_tree_root(historical_batch)) + + +def process_participation_record_updates(state: BeaconState) -> None: + # Rotate current/previous epoch attestations + state.previous_epoch_attestations = state.current_epoch_attestations + state.current_epoch_attestations = [] + + +def process_block(state: BeaconState, block: BeaconBlock) -> None: + process_block_header(state, block) + if is_execution_enabled(state, block.body): + process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [New in Merge] + process_randao(state, block.body) + process_eth1_data(state, block.body) + process_operations(state, block.body) + process_sync_aggregate(state, block.body.sync_aggregate) + + +def process_block_header(state: BeaconState, block: BeaconBlock) -> None: + # Verify that the slots match + assert block.slot == state.slot + # Verify that the block is newer than latest block header + assert block.slot > state.latest_block_header.slot + # Verify that proposer index is the correct index + assert block.proposer_index == get_beacon_proposer_index(state) + # Verify that the parent matches + assert block.parent_root == hash_tree_root(state.latest_block_header) + # Cache current block as the new latest block + state.latest_block_header = BeaconBlockHeader( + slot=block.slot, + proposer_index=block.proposer_index, + parent_root=block.parent_root, + state_root=Bytes32(), # Overwritten in the next process_slot call + body_root=hash_tree_root(block.body), + ) + + # Verify proposer is not slashed + proposer = state.validators[block.proposer_index] + assert not proposer.slashed + + +def process_randao(state: BeaconState, body: BeaconBlockBody) -> None: + epoch = get_current_epoch(state) + # Verify RANDAO reveal + proposer = state.validators[get_beacon_proposer_index(state)] + signing_root = compute_signing_root(epoch, get_domain(state, DOMAIN_RANDAO)) + assert bls.Verify(proposer.pubkey, signing_root, body.randao_reveal) + # Mix in RANDAO reveal + mix = xor(get_randao_mix(state, epoch), hash(body.randao_reveal)) + state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR] = mix + + +def process_eth1_data(state: BeaconState, body: BeaconBlockBody) -> None: + state.eth1_data_votes.append(body.eth1_data) + if state.eth1_data_votes.count(body.eth1_data) * 2 > EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH: + state.eth1_data = body.eth1_data + + +def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: + # Verify that outstanding deposits are processed up to the maximum number of deposits + assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index) + + def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: + for operation in operations: + fn(state, operation) + + for_ops(body.proposer_slashings, process_proposer_slashing) + for_ops(body.attester_slashings, process_attester_slashing) + for_ops(body.attestations, process_attestation) + for_ops(body.deposits, process_deposit) + for_ops(body.voluntary_exits, process_voluntary_exit) + + +def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None: + header_1 = proposer_slashing.signed_header_1.message + header_2 = proposer_slashing.signed_header_2.message + + # Verify header slots match + assert header_1.slot == header_2.slot + # Verify header proposer indices match + assert header_1.proposer_index == header_2.proposer_index + # Verify the headers are different + assert header_1 != header_2 + # Verify the proposer is slashable + proposer = state.validators[header_1.proposer_index] + assert is_slashable_validator(proposer, get_current_epoch(state)) + # Verify signatures + for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2): + domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(signed_header.message.slot)) + signing_root = compute_signing_root(signed_header.message, domain) + assert bls.Verify(proposer.pubkey, signing_root, signed_header.signature) + + slash_validator(state, header_1.proposer_index) + + +def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSlashing) -> None: + attestation_1 = attester_slashing.attestation_1 + attestation_2 = attester_slashing.attestation_2 + assert is_slashable_attestation_data(attestation_1.data, attestation_2.data) + assert is_valid_indexed_attestation(state, attestation_1) + assert is_valid_indexed_attestation(state, attestation_2) + + slashed_any = False + indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices) + for index in sorted(indices): + if is_slashable_validator(state.validators[index], get_current_epoch(state)): + slash_validator(state, index) + slashed_any = True + assert slashed_any + + +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) + + # Participation flag indices + participation_flag_indices = get_attestation_participation_flag_indices(state, data, state.slot - data.slot) + + # Verify signature + assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) + + # Update epoch participation flags + if data.target.epoch == get_current_epoch(state): + epoch_participation = state.current_epoch_participation + else: + epoch_participation = state.previous_epoch_participation + + proposer_reward_numerator = 0 + for index in get_attesting_indices(state, data, attestation.aggregation_bits): + for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS): + if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index): + epoch_participation[index] = add_flag(epoch_participation[index], flag_index) + proposer_reward_numerator += get_base_reward(state, index) * weight + + # Reward proposer + proposer_reward_denominator = (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT + proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator) + increase_balance(state, get_beacon_proposer_index(state), proposer_reward) + + +def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: + amount = deposit.data.amount + effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) + + return Validator( + pubkey=deposit.data.pubkey, + withdrawal_credentials=deposit.data.withdrawal_credentials, + activation_eligibility_epoch=FAR_FUTURE_EPOCH, + activation_epoch=FAR_FUTURE_EPOCH, + exit_epoch=FAR_FUTURE_EPOCH, + withdrawable_epoch=FAR_FUTURE_EPOCH, + effective_balance=effective_balance, + ) + + +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 = [validator.pubkey for validator 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) + # Initialize validator if the deposit signature is valid + if bls.Verify(pubkey, signing_root, deposit.data.signature): + state.validators.append(get_validator_from_deposit(state, deposit)) + state.balances.append(amount) + state.previous_epoch_participation.append(ParticipationFlags(0b0000_0000)) + state.current_epoch_participation.append(ParticipationFlags(0b0000_0000)) + state.inactivity_scores.append(uint64(0)) + else: + # Increase balance by deposit amount + index = ValidatorIndex(validator_pubkeys.index(pubkey)) + increase_balance(state, index, amount) + + +def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVoluntaryExit) -> None: + voluntary_exit = signed_voluntary_exit.message + validator = state.validators[voluntary_exit.validator_index] + # Verify the validator is active + assert is_active_validator(validator, get_current_epoch(state)) + # Verify exit has not been initiated + assert validator.exit_epoch == FAR_FUTURE_EPOCH + # Exits must specify an epoch when they become valid; they are not valid before then + assert get_current_epoch(state) >= voluntary_exit.epoch + # Verify the validator has been active long enough + assert get_current_epoch(state) >= validator.activation_epoch + config.SHARD_COMMITTEE_PERIOD + # Verify signature + domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch) + signing_root = compute_signing_root(voluntary_exit, domain) + assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature) + # Initiate exit + initiate_validator_exit(state, voluntary_exit.validator_index) + + +def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store: + assert anchor_block.state_root == hash_tree_root(anchor_state) + anchor_root = hash_tree_root(anchor_block) + anchor_epoch = get_current_epoch(anchor_state) + justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) + finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) + proposer_boost_root = Root() + return Store( + time=uint64(anchor_state.genesis_time + config.SECONDS_PER_SLOT * anchor_state.slot), + genesis_time=anchor_state.genesis_time, + justified_checkpoint=justified_checkpoint, + finalized_checkpoint=finalized_checkpoint, + best_justified_checkpoint=justified_checkpoint, + proposer_boost_root=proposer_boost_root, + blocks={anchor_root: copy(anchor_block)}, + block_states={anchor_root: copy(anchor_state)}, + checkpoint_states={justified_checkpoint: copy(anchor_state)}, + ) + + +def get_slots_since_genesis(store: Store) -> int: + return (store.time - store.genesis_time) // config.SECONDS_PER_SLOT + + +def get_current_slot(store: Store) -> Slot: + return Slot(GENESIS_SLOT + get_slots_since_genesis(store)) + + +def compute_slots_since_epoch_start(slot: Slot) -> int: + return slot - compute_start_slot_at_epoch(compute_epoch_at_slot(slot)) + + +def get_ancestor(store: Store, root: Root, slot: Slot) -> Root: + block = store.blocks[root] + if block.slot > slot: + return get_ancestor(store, block.parent_root, slot) + elif block.slot == slot: + return root + else: + # root is older than queried slot, thus a skip slot. Return most recent root prior to slot + return root + + +def get_latest_attesting_balance(store: Store, root: Root) -> Gwei: + state = store.checkpoint_states[store.justified_checkpoint] + active_indices = get_active_validator_indices(state, get_current_epoch(state)) + attestation_score = Gwei(sum( + state.validators[i].effective_balance for i in active_indices + if (i in store.latest_messages + and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root) + )) + proposer_score = Gwei(0) + if store.proposer_boost_root != Root(): + block = store.blocks[root] + if get_ancestor(store, root, block.slot) == store.proposer_boost_root: + num_validators = len(get_active_validator_indices(state, get_current_epoch(state))) + avg_balance = get_total_active_balance(state) // num_validators + committee_size = num_validators // SLOTS_PER_EPOCH + committee_weight = committee_size * avg_balance + proposer_score = (committee_weight * config.PROPOSER_SCORE_BOOST) // 100 + return attestation_score + proposer_score + + +def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconBlock]) -> bool: + block = store.blocks[block_root] + children = [ + root for root in store.blocks.keys() + if store.blocks[root].parent_root == block_root + ] + + # If any children branches contain expected finalized/justified checkpoints, + # add to filtered block-tree and signal viability to parent. + if any(children): + filter_block_tree_result = [filter_block_tree(store, child, blocks) for child in children] + if any(filter_block_tree_result): + blocks[block_root] = block + return True + return False + + # If leaf block, check finalized/justified checkpoints as matching latest. + head_state = store.block_states[block_root] + + correct_justified = ( + store.justified_checkpoint.epoch == GENESIS_EPOCH + or head_state.current_justified_checkpoint == store.justified_checkpoint + ) + correct_finalized = ( + store.finalized_checkpoint.epoch == GENESIS_EPOCH + or head_state.finalized_checkpoint == store.finalized_checkpoint + ) + # If expected finalized/justified, add to viable block-tree and signal viability to parent. + if correct_justified and correct_finalized: + blocks[block_root] = block + return True + + # Otherwise, branch not viable + return False + + +def get_filtered_block_tree(store: Store) -> Dict[Root, BeaconBlock]: + """ + Retrieve a filtered block tree from ``store``, only returning branches + whose leaf state's justified/finalized info agrees with that in ``store``. + """ + base = store.justified_checkpoint.root + blocks: Dict[Root, BeaconBlock] = {} + filter_block_tree(store, base, blocks) + return blocks + + +def get_head(store: Store) -> Root: + # Get filtered block tree that only includes viable branches + blocks = get_filtered_block_tree(store) + # Execute the LMD-GHOST fork choice + head = store.justified_checkpoint.root + while True: + children = [ + root for root in blocks.keys() + if blocks[root].parent_root == head + ] + if len(children) == 0: + return head + # Sort by latest attesting balance with ties broken lexicographically + head = max(children, key=lambda root: (get_latest_attesting_balance(store, root), root)) + + +def should_update_justified_checkpoint(store: Store, new_justified_checkpoint: Checkpoint) -> bool: + """ + To address the bouncing attack, only update conflicting justified + checkpoints in the fork choice if in the early slots of the epoch. + Otherwise, delay incorporation of new justified checkpoint until next epoch boundary. + + See https://ethresear.ch/t/prevention-of-bouncing-attack-on-ffg/6114 for more detailed analysis and discussion. + """ + if compute_slots_since_epoch_start(get_current_slot(store)) < SAFE_SLOTS_TO_UPDATE_JUSTIFIED: + return True + + justified_slot = compute_start_slot_at_epoch(store.justified_checkpoint.epoch) + if not get_ancestor(store, new_justified_checkpoint.root, justified_slot) == store.justified_checkpoint.root: + return False + + return True + + +def validate_target_epoch_against_current_time(store: Store, attestation: Attestation) -> None: + target = attestation.data.target + + # Attestations must be from the current or previous epoch + 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] + + +def validate_on_attestation(store: Store, attestation: Attestation, is_from_block: bool) -> None: + target = attestation.data.target + + # If the given attestation is not from a beacon block message, we have to check the target epoch scope. + if not is_from_block: + validate_target_epoch_against_current_time(store, attestation) + + # Check that the epoch number and slot number are matching + assert target.epoch == compute_epoch_at_slot(attestation.data.slot) + + # 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 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 + # Attestations must not be for blocks in the future. If not, the attestation should not be considered + assert store.blocks[attestation.data.beacon_block_root].slot <= attestation.data.slot + + # LMD vote must be consistent with FFG vote target + target_slot = compute_start_slot_at_epoch(target.epoch) + assert target.root == get_ancestor(store, attestation.data.beacon_block_root, target_slot) + + # Attestations can only affect the fork choice of subsequent slots. + # Delay consideration in the fork choice until their slot is in the past. + assert get_current_slot(store) >= attestation.data.slot + 1 + + +def store_target_checkpoint_state(store: Store, target: Checkpoint) -> None: + # Store target checkpoint state if not yet seen + if target not in store.checkpoint_states: + base_state = copy(store.block_states[target.root]) + if base_state.slot < compute_start_slot_at_epoch(target.epoch): + process_slots(base_state, compute_start_slot_at_epoch(target.epoch)) + store.checkpoint_states[target] = base_state + + +def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None: + target = attestation.data.target + beacon_block_root = attestation.data.beacon_block_root + for i in attesting_indices: + if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch: + store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root) + + +def on_tick(store: Store, time: uint64) -> None: + previous_slot = get_current_slot(store) + + # update store time + store.time = time + + current_slot = get_current_slot(store) + + # Reset store.proposer_boost_root if this is a new slot + if current_slot > previous_slot: + store.proposer_boost_root = Root() + + # Not a new epoch, return + if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0): + return + + # Update store.justified_checkpoint if a better checkpoint on the store.finalized_checkpoint chain + if store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch: + finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + ancestor_at_finalized_slot = get_ancestor(store, store.best_justified_checkpoint.root, finalized_slot) + if ancestor_at_finalized_slot == store.finalized_checkpoint.root: + store.justified_checkpoint = store.best_justified_checkpoint + + +def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: + """ + Run ``on_block`` upon receiving a new block. + + A block that is asserted as invalid due to unavailable PoW block may be valid at a later time, + consider scheduling it for later processing in such case. + """ + block = signed_block.message + # Parent block must be known + assert block.parent_root in store.block_states + # Make a copy of the state to avoid mutability issues + pre_state = copy(store.block_states[block.parent_root]) + # Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past. + assert get_current_slot(store) >= block.slot + + # Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) + finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + assert block.slot > finalized_slot + # Check block is a descendant of the finalized block at the checkpoint finalized slot + assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root + + # Check the block is valid and compute the post-state + state = pre_state.copy() + state_transition(state, signed_block, True) + + # [New in Merge] + if is_merge_transition_block(pre_state, block.body): + validate_merge_block(block) + + # Add new block to the store + store.blocks[hash_tree_root(block)] = block + # Add new state for this block to the store + store.block_states[hash_tree_root(block)] = state + + # Add proposer score boost if the block is timely + time_into_slot = (store.time - store.genesis_time) % config.SECONDS_PER_SLOT + is_before_attesting_interval = time_into_slot < config.SECONDS_PER_SLOT // INTERVALS_PER_SLOT + if get_current_slot(store) == block.slot and is_before_attesting_interval: + store.proposer_boost_root = hash_tree_root(block) + + # Update justified checkpoint + if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: + if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch: + store.best_justified_checkpoint = state.current_justified_checkpoint + if should_update_justified_checkpoint(store, state.current_justified_checkpoint): + store.justified_checkpoint = state.current_justified_checkpoint + + # Update finalized checkpoint + if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch: + store.finalized_checkpoint = state.finalized_checkpoint + store.justified_checkpoint = state.current_justified_checkpoint + + +def on_attestation(store: Store, attestation: Attestation, is_from_block: bool=False) -> None: + """ + Run ``on_attestation`` upon receiving a new ``attestation`` from either within a block or directly on the wire. + + An ``attestation`` that is asserted as invalid may be valid at a later time, + consider scheduling it for later processing in such case. + """ + validate_on_attestation(store, attestation, is_from_block) + + store_target_checkpoint_state(store, attestation.data.target) + + # Get state at the `target` to fully validate attestation + target_state = store.checkpoint_states[attestation.data.target] + indexed_attestation = get_indexed_attestation(target_state, attestation) + assert is_valid_indexed_attestation(target_state, indexed_attestation) + + # Update latest messages for attesting indices + update_latest_messages(store, indexed_attestation.attesting_indices, attestation) + + +def check_if_validator_active(state: BeaconState, validator_index: ValidatorIndex) -> bool: + validator = state.validators[validator_index] + return is_active_validator(validator, get_current_epoch(state)) + + +def get_committee_assignment(state: BeaconState, + epoch: Epoch, + validator_index: ValidatorIndex + ) -> Optional[Tuple[Sequence[ValidatorIndex], CommitteeIndex, Slot]]: + """ + Return the committee assignment in the ``epoch`` for ``validator_index``. + ``assignment`` returned is a tuple of the following form: + * ``assignment[0]`` is the list of validators in the committee + * ``assignment[1]`` is the index to which the committee is assigned + * ``assignment[2]`` is the slot at which the committee is assigned + Return None if no assignment. + """ + next_epoch = Epoch(get_current_epoch(state) + 1) + assert epoch <= next_epoch + + start_slot = compute_start_slot_at_epoch(epoch) + committee_count_per_slot = get_committee_count_per_slot(state, epoch) + for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH): + for index in range(committee_count_per_slot): + committee = get_beacon_committee(state, Slot(slot), CommitteeIndex(index)) + if validator_index in committee: + return committee, CommitteeIndex(index), Slot(slot) + return None + + +def is_proposer(state: BeaconState, validator_index: ValidatorIndex) -> bool: + return get_beacon_proposer_index(state) == validator_index + + +def get_epoch_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_RANDAO, compute_epoch_at_slot(block.slot)) + signing_root = compute_signing_root(compute_epoch_at_slot(block.slot), domain) + return bls.Sign(privkey, signing_root) + + +def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64: + return uint64(state.genesis_time + slot * config.SECONDS_PER_SLOT) + + +def voting_period_start_time(state: BeaconState) -> uint64: + eth1_voting_period_start_slot = Slot(state.slot - state.slot % (EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH)) + return compute_time_at_slot(state, eth1_voting_period_start_slot) + + +def is_candidate_block(block: Eth1Block, period_start: uint64) -> bool: + return ( + block.timestamp + config.SECONDS_PER_ETH1_BLOCK * config.ETH1_FOLLOW_DISTANCE <= period_start + and block.timestamp + config.SECONDS_PER_ETH1_BLOCK * config.ETH1_FOLLOW_DISTANCE * 2 >= period_start + ) + + +def get_eth1_vote(state: BeaconState, eth1_chain: Sequence[Eth1Block]) -> Eth1Data: + period_start = voting_period_start_time(state) + # `eth1_chain` abstractly represents all blocks in the eth1 chain sorted by ascending block height + votes_to_consider = [ + get_eth1_data(block) for block in eth1_chain + if ( + is_candidate_block(block, period_start) + # Ensure cannot move back to earlier deposit contract states + and get_eth1_data(block).deposit_count >= state.eth1_data.deposit_count + ) + ] + + # Valid votes already cast during this period + valid_votes = [vote for vote in state.eth1_data_votes if vote in votes_to_consider] + + # Default vote on latest eth1 block data in the period range unless eth1 chain is not live + # Non-substantive casting for linter + state_eth1_data: Eth1Data = state.eth1_data + default_vote = votes_to_consider[len(votes_to_consider) - 1] if any(votes_to_consider) else state_eth1_data + + return max( + valid_votes, + key=lambda v: (valid_votes.count(v), -valid_votes.index(v)), # Tiebreak by smallest distance + default=default_vote + ) + + +def compute_new_state_root(state: BeaconState, block: BeaconBlock) -> Root: + temp_state: BeaconState = state.copy() + signed_block = SignedBeaconBlock(message=block) + state_transition(temp_state, signed_block, validate_result=False) + return hash_tree_root(temp_state) + + +def get_block_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(block.slot)) + signing_root = compute_signing_root(block, domain) + return bls.Sign(privkey, signing_root) + + +def get_attestation_signature(state: BeaconState, attestation_data: AttestationData, privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch) + signing_root = compute_signing_root(attestation_data, domain) + return bls.Sign(privkey, signing_root) + + +def compute_subnet_for_attestation(committees_per_slot: uint64, slot: Slot, committee_index: CommitteeIndex) -> uint64: + """ + Compute the correct subnet for an attestation for Phase 0. + Note, this mimics expected future behavior where attestations will be mapped to their shard subnet. + """ + slots_since_epoch_start = uint64(slot % SLOTS_PER_EPOCH) + committees_since_epoch_start = committees_per_slot * slots_since_epoch_start + + return uint64((committees_since_epoch_start + committee_index) % ATTESTATION_SUBNET_COUNT) + + +def get_slot_signature(state: BeaconState, slot: Slot, privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_SELECTION_PROOF, compute_epoch_at_slot(slot)) + signing_root = compute_signing_root(slot, domain) + return bls.Sign(privkey, signing_root) + + +def is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex, slot_signature: BLSSignature) -> bool: + committee = get_beacon_committee(state, slot, index) + modulo = max(1, len(committee) // TARGET_AGGREGATORS_PER_COMMITTEE) + return bytes_to_uint64(hash(slot_signature)[0:8]) % modulo == 0 + + +def get_aggregate_signature(attestations: Sequence[Attestation]) -> BLSSignature: + signatures = [attestation.signature for attestation in attestations] + return bls.Aggregate(signatures) + + +def get_aggregate_and_proof(state: BeaconState, + aggregator_index: ValidatorIndex, + aggregate: Attestation, + privkey: int) -> AggregateAndProof: + return AggregateAndProof( + aggregator_index=aggregator_index, + aggregate=aggregate, + selection_proof=get_slot_signature(state, aggregate.data.slot, privkey), + ) + + +def get_aggregate_and_proof_signature(state: BeaconState, + aggregate_and_proof: AggregateAndProof, + privkey: int) -> BLSSignature: + aggregate = aggregate_and_proof.aggregate + domain = get_domain(state, DOMAIN_AGGREGATE_AND_PROOF, compute_epoch_at_slot(aggregate.data.slot)) + signing_root = compute_signing_root(aggregate_and_proof, domain) + return bls.Sign(privkey, signing_root) + + +def compute_weak_subjectivity_period(state: BeaconState) -> uint64: + """ + Returns the weak subjectivity period for the current ``state``. + This computation takes into account the effect of: + - validator set churn (bounded by ``get_validator_churn_limit()`` per epoch), and + - validator balance top-ups (bounded by ``MAX_DEPOSITS * SLOTS_PER_EPOCH`` per epoch). + A detailed calculation can be found at: + https://github.com/runtimeverification/beacon-chain-verification/blob/master/weak-subjectivity/weak-subjectivity-analysis.pdf + """ + ws_period = config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + N = len(get_active_validator_indices(state, get_current_epoch(state))) + t = get_total_active_balance(state) // N // ETH_TO_GWEI + T = MAX_EFFECTIVE_BALANCE // ETH_TO_GWEI + delta = get_validator_churn_limit(state) + Delta = MAX_DEPOSITS * SLOTS_PER_EPOCH + D = SAFETY_DECAY + + if T * (200 + 3 * D) < t * (200 + 12 * D): + epochs_for_validator_set_churn = ( + N * (t * (200 + 12 * D) - T * (200 + 3 * D)) // (600 * delta * (2 * t + T)) + ) + epochs_for_balance_top_ups = ( + N * (200 + 3 * D) // (600 * Delta) + ) + ws_period += max(epochs_for_validator_set_churn, epochs_for_balance_top_ups) + else: + ws_period += ( + 3 * N * D * t // (200 * Delta * (T - t)) + ) + + return ws_period + + +def is_within_weak_subjectivity_period(store: Store, ws_state: BeaconState, ws_checkpoint: Checkpoint) -> bool: + # Clients may choose to validate the input state against the input Weak Subjectivity Checkpoint + assert ws_state.latest_block_header.state_root == ws_checkpoint.root + assert compute_epoch_at_slot(ws_state.slot) == ws_checkpoint.epoch + + ws_period = compute_weak_subjectivity_period(ws_state) + ws_state_epoch = compute_epoch_at_slot(ws_state.slot) + current_epoch = compute_epoch_at_slot(get_current_slot(store)) + return current_epoch <= ws_state_epoch + ws_period + + +def add_flag(flags: ParticipationFlags, flag_index: int) -> ParticipationFlags: + """ + Return a new ``ParticipationFlags`` adding ``flag_index`` to ``flags``. + """ + flag = ParticipationFlags(2**flag_index) + return flags | flag + + +def has_flag(flags: ParticipationFlags, flag_index: int) -> bool: + """ + Return whether ``flags`` has ``flag_index`` set. + """ + flag = ParticipationFlags(2**flag_index) + return flags & flag == flag + + +def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorIndex]: + """ + Return the sync committee indices, with possible duplicates, for the next sync committee. + """ + epoch = Epoch(get_current_epoch(state) + 1) + + MAX_RANDOM_BYTE = 2**8 - 1 + active_validator_indices = get_active_validator_indices(state, epoch) + active_validator_count = uint64(len(active_validator_indices)) + seed = get_seed(state, epoch, DOMAIN_SYNC_COMMITTEE) + i = 0 + sync_committee_indices: List[ValidatorIndex] = [] + while len(sync_committee_indices) < SYNC_COMMITTEE_SIZE: + shuffled_index = compute_shuffled_index(uint64(i % active_validator_count), active_validator_count, seed) + candidate_index = active_validator_indices[shuffled_index] + random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] + effective_balance = state.validators[candidate_index].effective_balance + if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: + sync_committee_indices.append(candidate_index) + i += 1 + return sync_committee_indices + + +def get_next_sync_committee(state: BeaconState) -> SyncCommittee: + """ + Return the next sync committee, with possible pubkey duplicates. + """ + indices = get_next_sync_committee_indices(state) + pubkeys = [state.validators[index].pubkey for index in indices] + aggregate_pubkey = eth_aggregate_pubkeys(pubkeys) + return SyncCommittee(pubkeys=pubkeys, aggregate_pubkey=aggregate_pubkey) + + +def get_base_reward_per_increment(state: BeaconState) -> Gwei: + return Gwei(EFFECTIVE_BALANCE_INCREMENT * BASE_REWARD_FACTOR // integer_squareroot(get_total_active_balance(state))) + + +def get_unslashed_participating_indices(state: BeaconState, flag_index: int, epoch: Epoch) -> Set[ValidatorIndex]: + """ + Return the set of validator indices that are both active and unslashed for the given ``flag_index`` and ``epoch``. + """ + 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 + active_validator_indices = get_active_validator_indices(state, epoch) + participating_indices = [i for i in active_validator_indices if has_flag(epoch_participation[i], flag_index)] + return set(filter(lambda index: not state.validators[index].slashed, participating_indices)) + + +def get_attestation_participation_flag_indices(state: BeaconState, + data: AttestationData, + inclusion_delay: uint64) -> Sequence[int]: + """ + Return the flag indices that are satisfied by an attestation. + """ + if data.target.epoch == get_current_epoch(state): + justified_checkpoint = state.current_justified_checkpoint + else: + justified_checkpoint = state.previous_justified_checkpoint + + # Matching roots + is_matching_source = data.source == justified_checkpoint + is_matching_target = is_matching_source and data.target.root == get_block_root(state, data.target.epoch) + is_matching_head = is_matching_target and data.beacon_block_root == get_block_root_at_slot(state, data.slot) + assert is_matching_source + + participation_flag_indices = [] + if is_matching_source and inclusion_delay <= integer_squareroot(SLOTS_PER_EPOCH): + participation_flag_indices.append(TIMELY_SOURCE_FLAG_INDEX) + if is_matching_target and inclusion_delay <= SLOTS_PER_EPOCH: + participation_flag_indices.append(TIMELY_TARGET_FLAG_INDEX) + if is_matching_head and inclusion_delay == MIN_ATTESTATION_INCLUSION_DELAY: + participation_flag_indices.append(TIMELY_HEAD_FLAG_INDEX) + + return participation_flag_indices + + +def get_flag_index_deltas(state: BeaconState, flag_index: int) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return the deltas for a given ``flag_index`` by scanning through the participation flags. + """ + rewards = [Gwei(0)] * len(state.validators) + penalties = [Gwei(0)] * len(state.validators) + previous_epoch = get_previous_epoch(state) + unslashed_participating_indices = get_unslashed_participating_indices(state, flag_index, previous_epoch) + weight = PARTICIPATION_FLAG_WEIGHTS[flag_index] + unslashed_participating_balance = get_total_balance(state, unslashed_participating_indices) + unslashed_participating_increments = unslashed_participating_balance // EFFECTIVE_BALANCE_INCREMENT + active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT + for index in get_eligible_validator_indices(state): + base_reward = get_base_reward(state, index) + if index in unslashed_participating_indices: + if not is_in_inactivity_leak(state): + reward_numerator = base_reward * weight * unslashed_participating_increments + rewards[index] += Gwei(reward_numerator // (active_increments * WEIGHT_DENOMINATOR)) + elif flag_index != TIMELY_HEAD_FLAG_INDEX: + penalties[index] += Gwei(base_reward * weight // WEIGHT_DENOMINATOR) + return rewards, penalties + + +def process_sync_aggregate(state: BeaconState, sync_aggregate: SyncAggregate) -> None: + # Verify sync committee aggregate signature signing over the previous slot block root + committee_pubkeys = state.current_sync_committee.pubkeys + participant_pubkeys = [pubkey for pubkey, bit in zip(committee_pubkeys, sync_aggregate.sync_committee_bits) if bit] + previous_slot = max(state.slot, Slot(1)) - Slot(1) + domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, compute_epoch_at_slot(previous_slot)) + signing_root = compute_signing_root(get_block_root_at_slot(state, previous_slot), domain) + assert eth_fast_aggregate_verify(participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature) + + # Compute participant and proposer rewards + total_active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT + total_base_rewards = Gwei(get_base_reward_per_increment(state) * total_active_increments) + max_participant_rewards = Gwei(total_base_rewards * SYNC_REWARD_WEIGHT // WEIGHT_DENOMINATOR // SLOTS_PER_EPOCH) + participant_reward = Gwei(max_participant_rewards // SYNC_COMMITTEE_SIZE) + proposer_reward = Gwei(participant_reward * PROPOSER_WEIGHT // (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)) + + # Apply participant and proposer rewards + all_pubkeys = [v.pubkey for v in state.validators] + committee_indices = [ValidatorIndex(all_pubkeys.index(pubkey)) for pubkey in state.current_sync_committee.pubkeys] + for participant_index, participation_bit in zip(committee_indices, sync_aggregate.sync_committee_bits): + if participation_bit: + increase_balance(state, participant_index, participant_reward) + increase_balance(state, get_beacon_proposer_index(state), proposer_reward) + else: + decrease_balance(state, participant_index, participant_reward) + + +def process_inactivity_updates(state: BeaconState) -> None: + # Skip the genesis epoch as score updates are based on the previous epoch participation + if get_current_epoch(state) == GENESIS_EPOCH: + return + + for index in get_eligible_validator_indices(state): + # Increase the inactivity score of inactive validators + if index in get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)): + state.inactivity_scores[index] -= min(1, state.inactivity_scores[index]) + else: + state.inactivity_scores[index] += config.INACTIVITY_SCORE_BIAS + # Decrease the inactivity score of all eligible validators during a leak-free epoch + if not is_in_inactivity_leak(state): + state.inactivity_scores[index] -= min(config.INACTIVITY_SCORE_RECOVERY_RATE, state.inactivity_scores[index]) + + +def process_participation_flag_updates(state: BeaconState) -> None: + state.previous_epoch_participation = state.current_epoch_participation + state.current_epoch_participation = [ParticipationFlags(0b0000_0000) for _ in range(len(state.validators))] + + +def process_sync_committee_updates(state: BeaconState) -> None: + 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_next_sync_committee(state) + + +def eth_aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey: + return bls.AggregatePKs(pubkeys) + + +def eth_fast_aggregate_verify(pubkeys: Sequence[BLSPubkey], message: Bytes32, signature: BLSSignature) -> bool: + """ + Wrapper to ``bls.FastAggregateVerify`` accepting the ``G2_POINT_AT_INFINITY`` signature when ``pubkeys`` is empty. + """ + if len(pubkeys) == 0 and signature == G2_POINT_AT_INFINITY: + return True + return bls.FastAggregateVerify(pubkeys, message, signature) + + +def translate_participation(state: BeaconState, pending_attestations: Sequence[phase0.PendingAttestation]) -> None: + for attestation in pending_attestations: + data = attestation.data + inclusion_delay = attestation.inclusion_delay + # Translate attestation inclusion info to flag indices + participation_flag_indices = get_attestation_participation_flag_indices(state, data, inclusion_delay) + + # Apply flags to all attesting validators + epoch_participation = state.previous_epoch_participation + for index in get_attesting_indices(state, data, attestation.aggregation_bits): + for flag_index in participation_flag_indices: + epoch_participation[index] = add_flag(epoch_participation[index], flag_index) + + +def upgrade_to_altair(pre: phase0.BeaconState) -> BeaconState: + epoch = phase0.get_current_epoch(pre) + post = BeaconState( + # Versioning + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + current_version=config.ALTAIR_FORK_VERSION, + epoch=epoch, + ), + # History + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + # Eth1 + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + # Registry + validators=pre.validators, + balances=pre.balances, + # Randomness + randao_mixes=pre.randao_mixes, + # Slashings + slashings=pre.slashings, + # Participation + previous_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))], + current_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))], + # Finality + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + # Inactivity + inactivity_scores=[uint64(0) for _ in range(len(pre.validators))], + ) + # Fill in previous epoch participation from the pre state's pending attestations + translate_participation(post, pre.previous_epoch_attestations) + + # Fill in sync committees + # Note: A duplicate committee is assigned for the current and next committee at the fork boundary + post.current_sync_committee = get_next_sync_committee(post) + post.next_sync_committee = get_next_sync_committee(post) + return post + + +def compute_sync_committee_period(epoch: Epoch) -> uint64: + return epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + + +def is_assigned_to_sync_committee(state: BeaconState, + epoch: Epoch, + validator_index: ValidatorIndex) -> bool: + sync_committee_period = compute_sync_committee_period(epoch) + current_epoch = get_current_epoch(state) + current_sync_committee_period = compute_sync_committee_period(current_epoch) + next_sync_committee_period = current_sync_committee_period + 1 + assert sync_committee_period in (current_sync_committee_period, next_sync_committee_period) + + pubkey = state.validators[validator_index].pubkey + if sync_committee_period == current_sync_committee_period: + return pubkey in state.current_sync_committee.pubkeys + else: # sync_committee_period == next_sync_committee_period + return pubkey in state.next_sync_committee.pubkeys + + +def process_sync_committee_contributions(block: BeaconBlock, + contributions: Set[SyncCommitteeContribution]) -> None: + sync_aggregate = SyncAggregate() + signatures = [] + sync_subcommittee_size = SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT + + for contribution in contributions: + subcommittee_index = contribution.subcommittee_index + for index, participated in enumerate(contribution.aggregation_bits): + if participated: + participant_index = sync_subcommittee_size * subcommittee_index + index + sync_aggregate.sync_committee_bits[participant_index] = True + signatures.append(contribution.signature) + + sync_aggregate.sync_committee_signature = bls.Aggregate(signatures) + + block.body.sync_aggregate = sync_aggregate + + +def get_sync_committee_message(state: BeaconState, + block_root: Root, + validator_index: ValidatorIndex, + privkey: int) -> SyncCommitteeMessage: + epoch = get_current_epoch(state) + domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, epoch) + signing_root = compute_signing_root(block_root, domain) + signature = bls.Sign(privkey, signing_root) + + return SyncCommitteeMessage( + slot=state.slot, + beacon_block_root=block_root, + validator_index=validator_index, + signature=signature, + ) + + +def compute_subnets_for_sync_committee(state: BeaconState, validator_index: ValidatorIndex) -> Set[uint64]: + next_slot_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) + if compute_sync_committee_period(get_current_epoch(state)) == compute_sync_committee_period(next_slot_epoch): + sync_committee = state.current_sync_committee + else: + sync_committee = state.next_sync_committee + + target_pubkey = state.validators[validator_index].pubkey + sync_committee_indices = [index for index, pubkey in enumerate(sync_committee.pubkeys) if pubkey == target_pubkey] + return set([ + uint64(index // (SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT)) + for index in sync_committee_indices + ]) + + +def get_sync_committee_selection_proof(state: BeaconState, + slot: Slot, + subcommittee_index: uint64, + privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF, compute_epoch_at_slot(slot)) + signing_data = SyncAggregatorSelectionData( + slot=slot, + subcommittee_index=subcommittee_index, + ) + signing_root = compute_signing_root(signing_data, domain) + return bls.Sign(privkey, signing_root) + + +def is_sync_committee_aggregator(signature: BLSSignature) -> bool: + modulo = max(1, SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT // TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE) + return bytes_to_uint64(hash(signature)[0:8]) % modulo == 0 + + +def get_contribution_and_proof(state: BeaconState, + aggregator_index: ValidatorIndex, + contribution: SyncCommitteeContribution, + privkey: int) -> ContributionAndProof: + selection_proof = get_sync_committee_selection_proof( + state, + contribution.slot, + contribution.subcommittee_index, + privkey, + ) + return ContributionAndProof( + aggregator_index=aggregator_index, + contribution=contribution, + selection_proof=selection_proof, + ) + + +def get_contribution_and_proof_signature(state: BeaconState, + contribution_and_proof: ContributionAndProof, + privkey: int) -> BLSSignature: + contribution = contribution_and_proof.contribution + domain = get_domain(state, DOMAIN_CONTRIBUTION_AND_PROOF, compute_epoch_at_slot(contribution.slot)) + signing_root = compute_signing_root(contribution_and_proof, domain) + return bls.Sign(privkey, signing_root) + + +def get_sync_subcommittee_pubkeys(state: BeaconState, subcommittee_index: uint64) -> Sequence[BLSPubkey]: + # Committees assigned to `slot` sign for `slot - 1` + # This creates the exceptional logic below when transitioning between sync committee periods + next_slot_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) + if compute_sync_committee_period(get_current_epoch(state)) == compute_sync_committee_period(next_slot_epoch): + sync_committee = state.current_sync_committee + else: + sync_committee = state.next_sync_committee + + # Return pubkeys for the subcommittee index + sync_subcommittee_size = SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT + i = subcommittee_index * sync_subcommittee_size + return sync_committee.pubkeys[i:i + sync_subcommittee_size] + + +def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: + return uint64(generalized_index % 2**(floorlog2(generalized_index))) + + +def validate_light_client_update(snapshot: LightClientSnapshot, + update: LightClientUpdate, + genesis_validators_root: Root) -> None: + # Verify update slot is larger than snapshot slot + assert update.header.slot > snapshot.header.slot + + # Verify update does not skip a sync committee period + snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + assert update_period in (snapshot_period, snapshot_period + 1) + + # Verify update header root is the finalized root of the finality header, if specified + if update.finality_header == BeaconBlockHeader(): + signed_header = update.header + assert update.finality_branch == [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))] + else: + signed_header = update.finality_header + assert is_valid_merkle_branch( + leaf=hash_tree_root(update.header), + branch=update.finality_branch, + depth=floorlog2(FINALIZED_ROOT_INDEX), + index=get_subtree_index(FINALIZED_ROOT_INDEX), + root=update.finality_header.state_root, + ) + + # Verify update next sync committee if the update period incremented + if update_period == snapshot_period: + sync_committee = snapshot.current_sync_committee + assert update.next_sync_committee_branch == [Bytes32() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))] + else: + sync_committee = snapshot.next_sync_committee + assert is_valid_merkle_branch( + leaf=hash_tree_root(update.next_sync_committee), + branch=update.next_sync_committee_branch, + depth=floorlog2(NEXT_SYNC_COMMITTEE_INDEX), + index=get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX), + root=update.header.state_root, + ) + + # Verify sync committee has sufficient participants + assert sum(update.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS + + # Verify sync committee aggregate signature + participant_pubkeys = [pubkey for (bit, pubkey) in zip(update.sync_committee_bits, sync_committee.pubkeys) if bit] + domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version, genesis_validators_root) + signing_root = compute_signing_root(signed_header, domain) + assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature) + + +def apply_light_client_update(snapshot: LightClientSnapshot, update: LightClientUpdate) -> None: + snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + if update_period == snapshot_period + 1: + snapshot.current_sync_committee = snapshot.next_sync_committee + snapshot.next_sync_committee = update.next_sync_committee + snapshot.header = update.header + + +def process_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot, + genesis_validators_root: Root) -> None: + validate_light_client_update(store.snapshot, update, genesis_validators_root) + store.valid_updates.add(update) + + update_timeout = SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD + if ( + sum(update.sync_committee_bits) * 3 >= len(update.sync_committee_bits) * 2 + and update.finality_header != BeaconBlockHeader() + ): + # Apply update if (1) 2/3 quorum is reached and (2) we have a finality proof. + # Note that (2) means that the current light client design needs finality. + # It may be changed to re-organizable light client design. See the on-going issue consensus-specs#2182. + apply_light_client_update(store.snapshot, update) + store.valid_updates = set() + elif current_slot > store.snapshot.header.slot + update_timeout: + # Forced best update when the update timeout has elapsed + apply_light_client_update(store.snapshot, + max(store.valid_updates, key=lambda update: sum(update.sync_committee_bits))) + store.valid_updates = set() + + +def is_merge_transition_complete(state: BeaconState) -> bool: + return state.latest_execution_payload_header != ExecutionPayloadHeader() + + +def is_merge_transition_block(state: BeaconState, body: BeaconBlockBody) -> bool: + return not is_merge_transition_complete(state) and body.execution_payload != ExecutionPayload() + + +def is_execution_enabled(state: BeaconState, body: BeaconBlockBody) -> bool: + return is_merge_transition_block(state, body) or is_merge_transition_complete(state) + + +def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64: + slots_since_genesis = slot - GENESIS_SLOT + return uint64(state.genesis_time + slots_since_genesis * config.SECONDS_PER_SLOT) + + +def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None: + # Verify consistency of the parent hash with respect to the previous execution payload header + if is_merge_transition_complete(state): + assert payload.parent_hash == state.latest_execution_payload_header.block_hash + # Verify random + assert payload.random == get_randao_mix(state, get_current_epoch(state)) + # Verify timestamp + assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) + # Verify the execution payload is valid + assert execution_engine.execute_payload(payload) + # Cache execution payload header + state.latest_execution_payload_header = ExecutionPayloadHeader( + parent_hash=payload.parent_hash, + fee_recipient=payload.fee_recipient, + state_root=payload.state_root, + receipt_root=payload.receipt_root, + logs_bloom=payload.logs_bloom, + random=payload.random, + block_number=payload.block_number, + gas_limit=payload.gas_limit, + gas_used=payload.gas_used, + timestamp=payload.timestamp, + extra_data=payload.extra_data, + base_fee_per_gas=payload.base_fee_per_gas, + block_hash=payload.block_hash, + transactions_root=hash_tree_root(payload.transactions), + ) + + +def upgrade_to_merge(pre: altair.BeaconState) -> BeaconState: + epoch = altair.get_current_epoch(pre) + post = BeaconState( + # Versioning + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + current_version=config.MERGE_FORK_VERSION, + epoch=epoch, + ), + # History + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + # Eth1 + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + # Registry + validators=pre.validators, + balances=pre.balances, + # Randomness + randao_mixes=pre.randao_mixes, + # Slashings + slashings=pre.slashings, + # Participation + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + # Finality + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + # Inactivity + inactivity_scores=pre.inactivity_scores, + # Sync + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + # Execution-layer + latest_execution_payload_header=ExecutionPayloadHeader(), + ) + + return post + + +def is_valid_terminal_pow_block(block: PowBlock, parent: PowBlock) -> bool: + is_total_difficulty_reached = block.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY + is_parent_total_difficulty_valid = parent.total_difficulty < config.TERMINAL_TOTAL_DIFFICULTY + return is_total_difficulty_reached and is_parent_total_difficulty_valid + + +def validate_merge_block(block: BeaconBlock) -> None: + """ + Check the parent PoW block of execution payload is a valid terminal PoW block. + + Note: Unavailable PoW block(s) may later become available, + and a client software MAY delay a call to ``validate_merge_block`` + until the PoW block(s) become available. + """ + if config.TERMINAL_BLOCK_HASH != Hash32(): + # If `config.TERMINAL_BLOCK_HASH` is used as an override, the activation epoch must be reached. + assert compute_epoch_at_slot(block.slot) >= config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH + assert block.body.execution_payload.parent_hash == config.TERMINAL_BLOCK_HASH + return + + pow_block = get_pow_block(block.body.execution_payload.parent_hash) + # Check if `pow_block` is available + assert pow_block is not None + pow_parent = get_pow_block(pow_block.parent_hash) + # Check if `pow_parent` is available + assert pow_parent is not None + # Check if `pow_block` is a valid terminal PoW block + assert is_valid_terminal_pow_block(pow_block, pow_parent) + + +def get_pow_block_at_terminal_total_difficulty(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlock]: + # `pow_chain` abstractly represents all blocks in the PoW chain + for block in pow_chain.values(): + block_reached_ttd = block.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY + if block_reached_ttd: + # If genesis block, no parent exists so reaching TTD alone qualifies as valid terminal block + if block.parent_hash == Hash32(): + return block + parent = pow_chain[block.parent_hash] + parent_reached_ttd = parent.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY + if not parent_reached_ttd: + return block + + return None + + +def get_terminal_pow_block(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlock]: + if config.TERMINAL_BLOCK_HASH != Hash32(): + # Terminal block hash override takes precedence over terminal total difficulty + if config.TERMINAL_BLOCK_HASH in pow_chain: + return pow_chain[config.TERMINAL_BLOCK_HASH] + else: + return None + + return get_pow_block_at_terminal_total_difficulty(pow_chain) + + +def prepare_execution_payload(state: BeaconState, + pow_chain: Dict[Hash32, PowBlock], + finalized_block_hash: Hash32, + suggested_fee_recipient: ExecutionAddress, + execution_engine: ExecutionEngine) -> Optional[PayloadId]: + if not is_merge_transition_complete(state): + is_terminal_block_hash_set = config.TERMINAL_BLOCK_HASH != Hash32() + is_activation_epoch_reached = get_current_epoch(state) >= config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH + if is_terminal_block_hash_set and not is_activation_epoch_reached: + # Terminal block hash is set but activation epoch is not yet reached, no prepare payload call is needed + return None + + terminal_pow_block = get_terminal_pow_block(pow_chain) + if terminal_pow_block is None: + # Pre-merge, no prepare payload call is needed + return None + # Signify merge via producing on top of the terminal PoW block + parent_hash = terminal_pow_block.block_hash + else: + # Post-merge, normal payload + parent_hash = state.latest_execution_payload_header.block_hash + + # Set the forkchoice head and initiate the payload build process + payload_attributes = PayloadAttributes( + timestamp=compute_timestamp_at_slot(state, state.slot), + random=get_randao_mix(state, get_current_epoch(state)), + suggested_fee_recipient=suggested_fee_recipient, + ) + return execution_engine.notify_forkchoice_updated(parent_hash, finalized_block_hash, payload_attributes) + + +def get_execution_payload(payload_id: Optional[PayloadId], execution_engine: ExecutionEngine) -> ExecutionPayload: + if payload_id is None: + # Pre-merge, empty payload + return ExecutionPayload() + else: + return execution_engine.get_payload(payload_id) + + +def withdraw(state: BeaconState, index: ValidatorIndex, amount: Gwei) -> None: + # Decrease the validator's balance + decrease_balance(state, index, amount) + # Create a corresponding withdrawal receipt + receipt = WithdrawalReceipt( + index=WithdrawalReceiptIndex(len(state.withdrawal_receipts)), + address=state.validators[index].withdrawal_credentials[12:], + amount=amount, + ) + state.withdrawal_receipts.append(receipt) + + +def is_withdrawable_validator(validator: Validator, epoch: Epoch) -> bool: + """ + Check if ``validator`` is withdrawable. + """ + return validator.withdrawable_epoch <= epoch + + +def process_withdrawals(state: BeaconState) -> None: + current_epoch = get_current_epoch(state) + for index, validator in enumerate(state.validators): + balance = state.balances[index] + is_balance_nonzero = state.balances[index] == 0 + is_eth1_withdrawal_prefix = validator.withdrawal_credentials[0] != ETH1_ADDRESS_WITHDRAWAL_PREFIX + if is_balance_nonzero and is_eth1_withdrawal_prefix and is_withdrawable_validator(validator, current_epoch): + withdraw(state, ValidatorIndex(index), balance) + + +def upgrade_to_capella(pre: merge.BeaconState) -> BeaconState: + epoch = merge.get_current_epoch(pre) + post = BeaconState( + # Versioning + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + current_version=CAPELLA_FORK_VERSION, + epoch=epoch, + ), + # History + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + # Eth1 + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + # Registry + validators=pre.validators, + balances=pre.balances, + # Randomness + randao_mixes=pre.randao_mixes, + # Slashings + slashings=pre.slashings, + # Participation + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + # Finality + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + # Inactivity + inactivity_scores=pre.inactivity_scores, + # Sync + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + # Execution-layer + latest_execution_payload_header=pre.latest_execution_payload_header, + # Withdrawals + withdrawal_receipts=[], + ) + + return post + + +def get_eth1_data(block: Eth1Block) -> Eth1Data: + """ + A stub function return mocking Eth1Data. + """ + return Eth1Data( + deposit_root=block.deposit_root, + deposit_count=block.deposit_count, + block_hash=hash_tree_root(block)) + + +def cache_this(key_fn, value_fn, lru_size): # type: ignore + cache_dict = LRU(size=lru_size) + + def wrapper(*args, **kw): # type: ignore + key = key_fn(*args, **kw) + nonlocal cache_dict + if key not in cache_dict: + cache_dict[key] = value_fn(*args, **kw) + return cache_dict[key] + return wrapper + + +_compute_shuffled_index = compute_shuffled_index +compute_shuffled_index = cache_this( + lambda index, index_count, seed: (index, index_count, seed), + _compute_shuffled_index, lru_size=SLOTS_PER_EPOCH * 3) + +_get_total_active_balance = get_total_active_balance +get_total_active_balance = cache_this( + lambda state: (state.validators.hash_tree_root(), compute_epoch_at_slot(state.slot)), + _get_total_active_balance, lru_size=10) + +_get_base_reward = get_base_reward +get_base_reward = cache_this( + lambda state, index: (state.validators.hash_tree_root(), state.slot, index), + _get_base_reward, lru_size=2048) + +_get_committee_count_per_slot = get_committee_count_per_slot +get_committee_count_per_slot = cache_this( + lambda state, epoch: (state.validators.hash_tree_root(), epoch), + _get_committee_count_per_slot, lru_size=SLOTS_PER_EPOCH * 3) + +_get_active_validator_indices = get_active_validator_indices +get_active_validator_indices = cache_this( + lambda state, epoch: (state.validators.hash_tree_root(), epoch), + _get_active_validator_indices, lru_size=3) + +_get_beacon_committee = get_beacon_committee +get_beacon_committee = cache_this( + lambda state, slot, index: (state.validators.hash_tree_root(), state.randao_mixes.hash_tree_root(), slot, index), + _get_beacon_committee, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3) + +_get_matching_target_attestations = get_matching_target_attestations +get_matching_target_attestations = cache_this( + lambda state, epoch: (state.hash_tree_root(), epoch), + _get_matching_target_attestations, lru_size=10) + +_get_matching_head_attestations = get_matching_head_attestations +get_matching_head_attestations = cache_this( + lambda state, epoch: (state.hash_tree_root(), epoch), + _get_matching_head_attestations, lru_size=10) + +_get_attesting_indices = get_attesting_indices +get_attesting_indices = cache_this( + lambda state, data, bits: ( + state.randao_mixes.hash_tree_root(), + state.validators.hash_tree_root(), data.hash_tree_root(), bits.hash_tree_root() + ), + _get_attesting_indices, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3) + + +def get_generalized_index(ssz_class: Any, *path: Sequence[PyUnion[int, SSZVariableName]]) -> GeneralizedIndex: + ssz_path = Path(ssz_class) + for item in path: + ssz_path = ssz_path / item + return GeneralizedIndex(ssz_path.gindex()) + + +ExecutionState = Any + + +def get_pow_block(hash: Bytes32) -> Optional[PowBlock]: + return PowBlock(block_hash=hash, parent_hash=Bytes32(), total_difficulty=uint256(0)) + + +def get_execution_state(execution_state_root: Bytes32) -> ExecutionState: + pass + + +def get_pow_chain_head() -> PowBlock: + pass + + +class NoopExecutionEngine(ExecutionEngine): + + def execute_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: + return True + + def notify_forkchoice_updated(self: ExecutionEngine, + head_block_hash: Hash32, + finalized_block_hash: Hash32, + payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: + pass + + def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayload: + raise NotImplementedError("no default block production") + + +EXECUTION_ENGINE = NoopExecutionEngine() + + +assert FINALIZED_ROOT_INDEX == get_generalized_index(BeaconState, 'finalized_checkpoint', 'root') +assert NEXT_SYNC_COMMITTEE_INDEX == get_generalized_index(BeaconState, 'next_sync_committee') diff --git a/tests/core/pyspec/eth2spec/capella/minimal.py b/tests/core/pyspec/eth2spec/capella/minimal.py new file mode 100644 index 0000000000..8f0a5f67aa --- /dev/null +++ b/tests/core/pyspec/eth2spec/capella/minimal.py @@ -0,0 +1,3074 @@ +from lru import LRU +from dataclasses import ( + dataclass, + field, +) +from typing import ( + Any, Callable, Dict, Set, Sequence, Tuple, Optional, TypeVar, NamedTuple +) + +from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy, uint_to_bytes +from eth2spec.utils.ssz.ssz_typing import ( + View, boolean, Container, List, Vector, uint8, uint32, uint64, + Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist) +from eth2spec.utils.ssz.ssz_typing import Bitvector # noqa: F401 +from eth2spec.utils import bls +from eth2spec.utils.hash_function import hash + + +from typing import NewType, Union as PyUnion + +from eth2spec.phase0 import minimal as phase0 +from eth2spec.utils.ssz.ssz_typing import Path + +from typing import Protocol +from eth2spec.altair import minimal as altair +from eth2spec.utils.ssz.ssz_typing import Bytes8, Bytes20, ByteList, ByteVector, uint256 + +from eth2spec.merge import minimal as merge + +SSZObject = TypeVar('SSZObject', bound=View) + + +SSZVariableName = str +GeneralizedIndex = NewType('GeneralizedIndex', int) + + +fork = 'capella' + + +MAX_BYTES_PER_TRANSACTION = uint64(2**30) + + +class Slot(uint64): + pass + + +class Epoch(uint64): + pass + + +class CommitteeIndex(uint64): + pass + + +class ValidatorIndex(uint64): + pass + + +class Gwei(uint64): + pass + + +class Root(Bytes32): + pass + + +class Hash32(Bytes32): + pass + + +class Version(Bytes4): + pass + + +class DomainType(Bytes4): + pass + + +class ForkDigest(Bytes4): + pass + + +class Domain(Bytes32): + pass + + +class BLSPubkey(Bytes48): + pass + + +class BLSSignature(Bytes96): + pass + + +class Ether(uint64): + pass + + +class ParticipationFlags(uint8): + pass + + +class ExecutionAddress(Bytes20): + pass + + +class PayloadId(Bytes8): + pass + + +class WithdrawalReceiptIndex(uint64): + pass + + +Transaction = ByteList[MAX_BYTES_PER_TRANSACTION] + + +def ceillog2(x: int) -> uint64: + if x < 1: + raise ValueError(f"ceillog2 accepts only positive values, x={x}") + return uint64((x - 1).bit_length()) + + +def floorlog2(x: int) -> uint64: + if x < 1: + raise ValueError(f"floorlog2 accepts only positive values, x={x}") + return uint64(x.bit_length() - 1) + + +FINALIZED_ROOT_INDEX = GeneralizedIndex(105) +NEXT_SYNC_COMMITTEE_INDEX = GeneralizedIndex(55) + +# Constant vars +GENESIS_SLOT = Slot(0) +GENESIS_EPOCH = Epoch(0) +FAR_FUTURE_EPOCH = Epoch(2**64 - 1) +BASE_REWARDS_PER_EPOCH = uint64(4) +DEPOSIT_CONTRACT_TREE_DEPTH = uint64(2**5) +JUSTIFICATION_BITS_LENGTH = uint64(4) +ENDIANNESS = 'little' +BLS_WITHDRAWAL_PREFIX = Bytes1('0x00') +ETH1_ADDRESS_WITHDRAWAL_PREFIX = Bytes1('0x01') +DOMAIN_BEACON_PROPOSER = DomainType('0x00000000') +DOMAIN_BEACON_ATTESTER = DomainType('0x01000000') +DOMAIN_RANDAO = DomainType('0x02000000') +DOMAIN_DEPOSIT = DomainType('0x03000000') +DOMAIN_VOLUNTARY_EXIT = DomainType('0x04000000') +DOMAIN_SELECTION_PROOF = DomainType('0x05000000') +DOMAIN_AGGREGATE_AND_PROOF = DomainType('0x06000000') +INTERVALS_PER_SLOT = uint64(3) +TARGET_AGGREGATORS_PER_COMMITTEE = 2**4 +RANDOM_SUBNETS_PER_VALIDATOR = 2**0 +EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION = 2**8 +ATTESTATION_SUBNET_COUNT = 64 +ETH_TO_GWEI = uint64(10**9) +SAFETY_DECAY = uint64(10) +TIMELY_SOURCE_FLAG_INDEX = 0 +TIMELY_TARGET_FLAG_INDEX = 1 +TIMELY_HEAD_FLAG_INDEX = 2 +TIMELY_SOURCE_WEIGHT = uint64(14) +TIMELY_TARGET_WEIGHT = uint64(26) +TIMELY_HEAD_WEIGHT = uint64(14) +SYNC_REWARD_WEIGHT = uint64(2) +PROPOSER_WEIGHT = uint64(8) +WEIGHT_DENOMINATOR = uint64(64) +DOMAIN_SYNC_COMMITTEE = DomainType('0x07000000') +DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF = DomainType('0x08000000') +DOMAIN_CONTRIBUTION_AND_PROOF = DomainType('0x09000000') +PARTICIPATION_FLAG_WEIGHTS = [TIMELY_SOURCE_WEIGHT, TIMELY_TARGET_WEIGHT, TIMELY_HEAD_WEIGHT] +G2_POINT_AT_INFINITY = BLSSignature(b'\xc0' + b'\x00' * 95) +TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE = 2**4 +SYNC_COMMITTEE_SUBNET_COUNT = 4 +WITHDRAWAL_RECEIPT_LIMIT = uint64(2**40) + +# Preset vars +MAX_COMMITTEES_PER_SLOT = uint64(4) +TARGET_COMMITTEE_SIZE = uint64(4) +MAX_VALIDATORS_PER_COMMITTEE = uint64(2048) +SHUFFLE_ROUND_COUNT = uint64(10) +HYSTERESIS_QUOTIENT = uint64(4) +HYSTERESIS_DOWNWARD_MULTIPLIER = uint64(1) +HYSTERESIS_UPWARD_MULTIPLIER = uint64(5) +MIN_DEPOSIT_AMOUNT = Gwei(1000000000) +MAX_EFFECTIVE_BALANCE = Gwei(32000000000) +EFFECTIVE_BALANCE_INCREMENT = Gwei(1000000000) +MIN_ATTESTATION_INCLUSION_DELAY = uint64(1) +SLOTS_PER_EPOCH = uint64(8) +MIN_SEED_LOOKAHEAD = uint64(1) +MAX_SEED_LOOKAHEAD = uint64(4) +MIN_EPOCHS_TO_INACTIVITY_PENALTY = uint64(4) +EPOCHS_PER_ETH1_VOTING_PERIOD = uint64(4) +SLOTS_PER_HISTORICAL_ROOT = uint64(64) +EPOCHS_PER_HISTORICAL_VECTOR = uint64(64) +EPOCHS_PER_SLASHINGS_VECTOR = uint64(64) +HISTORICAL_ROOTS_LIMIT = uint64(16777216) +VALIDATOR_REGISTRY_LIMIT = uint64(1099511627776) +BASE_REWARD_FACTOR = uint64(64) +WHISTLEBLOWER_REWARD_QUOTIENT = uint64(512) +PROPOSER_REWARD_QUOTIENT = uint64(8) +INACTIVITY_PENALTY_QUOTIENT = uint64(33554432) +MIN_SLASHING_PENALTY_QUOTIENT = uint64(64) +PROPORTIONAL_SLASHING_MULTIPLIER = uint64(2) +MAX_PROPOSER_SLASHINGS = 16 +MAX_ATTESTER_SLASHINGS = 2 +MAX_ATTESTATIONS = 128 +MAX_DEPOSITS = 16 +MAX_VOLUNTARY_EXITS = 16 +SAFE_SLOTS_TO_UPDATE_JUSTIFIED = 2 +INACTIVITY_PENALTY_QUOTIENT_ALTAIR = uint64(50331648) +MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR = uint64(64) +PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR = uint64(2) +SYNC_COMMITTEE_SIZE = uint64(32) +EPOCHS_PER_SYNC_COMMITTEE_PERIOD = uint64(8) +MIN_SYNC_COMMITTEE_PARTICIPANTS = 1 +MAX_BYTES_PER_TRANSACTION = uint64(1073741824) +MAX_TRANSACTIONS_PER_PAYLOAD = uint64(1048576) +BYTES_PER_LOGS_BLOOM = uint64(256) +MAX_EXTRA_DATA_BYTES = 32 +INACTIVITY_PENALTY_QUOTIENT_MERGE = uint64(16777216) +MIN_SLASHING_PENALTY_QUOTIENT_MERGE = uint64(32) +PROPORTIONAL_SLASHING_MULTIPLIER_MERGE = uint64(3) + + +class Configuration(NamedTuple): + PRESET_BASE: str + MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: uint64 + MIN_GENESIS_TIME: uint64 + GENESIS_FORK_VERSION: Version + GENESIS_DELAY: uint64 + SECONDS_PER_SLOT: uint64 + SECONDS_PER_ETH1_BLOCK: uint64 + MIN_VALIDATOR_WITHDRAWABILITY_DELAY: uint64 + SHARD_COMMITTEE_PERIOD: uint64 + ETH1_FOLLOW_DISTANCE: uint64 + EJECTION_BALANCE: Gwei + MIN_PER_EPOCH_CHURN_LIMIT: uint64 + CHURN_LIMIT_QUOTIENT: uint64 + PROPOSER_SCORE_BOOST: uint64 + INACTIVITY_SCORE_BIAS: uint64 + INACTIVITY_SCORE_RECOVERY_RATE: uint64 + ALTAIR_FORK_VERSION: Version + ALTAIR_FORK_EPOCH: Epoch + TERMINAL_TOTAL_DIFFICULTY: int + TERMINAL_BLOCK_HASH: Hash32 + TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: int + MERGE_FORK_VERSION: Version + MERGE_FORK_EPOCH: Epoch + CAPELLA_FORK_VERSION: Version + CAPELLA_FORK_EPOCH: Epoch + + +config = Configuration( + PRESET_BASE="minimal", + MIN_GENESIS_ACTIVE_VALIDATOR_COUNT=uint64(64), + MIN_GENESIS_TIME=uint64(1578009600), + GENESIS_FORK_VERSION=Version('0x00000001'), + GENESIS_DELAY=uint64(300), + SECONDS_PER_SLOT=uint64(6), + SECONDS_PER_ETH1_BLOCK=uint64(14), + MIN_VALIDATOR_WITHDRAWABILITY_DELAY=uint64(256), + SHARD_COMMITTEE_PERIOD=uint64(64), + ETH1_FOLLOW_DISTANCE=uint64(16), + EJECTION_BALANCE=Gwei(16000000000), + MIN_PER_EPOCH_CHURN_LIMIT=uint64(4), + CHURN_LIMIT_QUOTIENT=uint64(32), + PROPOSER_SCORE_BOOST=uint64(70), + INACTIVITY_SCORE_BIAS=uint64(4), + INACTIVITY_SCORE_RECOVERY_RATE=uint64(16), + ALTAIR_FORK_VERSION=Version('0x01000001'), + ALTAIR_FORK_EPOCH=Epoch(18446744073709551615), + TERMINAL_TOTAL_DIFFICULTY=115792089237316195423570985008687907853269984665640564039457584007913129638912, + TERMINAL_BLOCK_HASH=Hash32('0x0000000000000000000000000000000000000000000000000000000000000000'), + TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH=18446744073709551615, + MERGE_FORK_VERSION=Version('0x02000001'), + MERGE_FORK_EPOCH=Epoch(18446744073709551615), + CAPELLA_FORK_VERSION=Version('0x03000001'), + CAPELLA_FORK_EPOCH=Epoch(18446744073709551615), +) + + +class Fork(Container): + previous_version: Version + current_version: Version + epoch: Epoch # Epoch of latest fork + + +class ForkData(Container): + current_version: Version + genesis_validators_root: Root + + +class Checkpoint(Container): + epoch: Epoch + root: Root + + +class Validator(Container): + pubkey: BLSPubkey + withdrawal_credentials: Bytes32 # Commitment to pubkey for withdrawals + effective_balance: Gwei # Balance at stake + slashed: boolean + # Status epochs + activation_eligibility_epoch: Epoch # When criteria for activation were met + activation_epoch: Epoch + exit_epoch: Epoch + withdrawable_epoch: Epoch # When validator can withdraw funds + + +class AttestationData(Container): + slot: Slot + index: CommitteeIndex + # LMD GHOST vote + beacon_block_root: Root + # FFG vote + source: Checkpoint + target: Checkpoint + + +class IndexedAttestation(Container): + attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] + data: AttestationData + signature: BLSSignature + + +class PendingAttestation(Container): + aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] + data: AttestationData + inclusion_delay: Slot + proposer_index: ValidatorIndex + + +class Eth1Data(Container): + deposit_root: Root + deposit_count: uint64 + block_hash: Hash32 + + +class HistoricalBatch(Container): + block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + + +class DepositMessage(Container): + pubkey: BLSPubkey + withdrawal_credentials: Bytes32 + amount: Gwei + + +class DepositData(Container): + pubkey: BLSPubkey + withdrawal_credentials: Bytes32 + amount: Gwei + signature: BLSSignature # Signing over DepositMessage + + +class BeaconBlockHeader(Container): + slot: Slot + proposer_index: ValidatorIndex + parent_root: Root + state_root: Root + body_root: Root + + +class SigningData(Container): + object_root: Root + domain: Domain + + +class AttesterSlashing(Container): + attestation_1: IndexedAttestation + attestation_2: IndexedAttestation + + +class Attestation(Container): + aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] + data: AttestationData + signature: BLSSignature + + +class Deposit(Container): + proof: Vector[Bytes32, DEPOSIT_CONTRACT_TREE_DEPTH + 1] # Merkle path to deposit root + data: DepositData + + +class VoluntaryExit(Container): + epoch: Epoch # Earliest epoch when voluntary exit can be processed + validator_index: ValidatorIndex + + +class SignedVoluntaryExit(Container): + message: VoluntaryExit + signature: BLSSignature + + +class SignedBeaconBlockHeader(Container): + message: BeaconBlockHeader + signature: BLSSignature + + +class ProposerSlashing(Container): + signed_header_1: SignedBeaconBlockHeader + signed_header_2: SignedBeaconBlockHeader + + +class Eth1Block(Container): + timestamp: uint64 + deposit_root: Root + deposit_count: uint64 + # All other eth1 block fields + + +class AggregateAndProof(Container): + aggregator_index: ValidatorIndex + aggregate: Attestation + selection_proof: BLSSignature + + +class SignedAggregateAndProof(Container): + message: AggregateAndProof + signature: BLSSignature + + +class SyncAggregate(Container): + sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] + sync_committee_signature: BLSSignature + + +class SyncCommittee(Container): + pubkeys: Vector[BLSPubkey, SYNC_COMMITTEE_SIZE] + aggregate_pubkey: BLSPubkey + + +class SyncCommitteeMessage(Container): + # Slot to which this contribution pertains + slot: Slot + # Block root for this signature + beacon_block_root: Root + # Index of the validator that produced this signature + validator_index: ValidatorIndex + # Signature by the validator over the block root of `slot` + signature: BLSSignature + + +class SyncCommitteeContribution(Container): + # Slot to which this contribution pertains + slot: Slot + # Block root for this contribution + beacon_block_root: Root + # The subcommittee this contribution pertains to out of the broader sync committee + subcommittee_index: uint64 + # A bit is set if a signature from the validator at the corresponding + # index in the subcommittee is present in the aggregate `signature`. + aggregation_bits: Bitvector[SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT] + # Signature by the validator(s) over the block root of `slot` + signature: BLSSignature + + +class ContributionAndProof(Container): + aggregator_index: ValidatorIndex + contribution: SyncCommitteeContribution + selection_proof: BLSSignature + + +class SignedContributionAndProof(Container): + message: ContributionAndProof + signature: BLSSignature + + +class SyncAggregatorSelectionData(Container): + slot: Slot + subcommittee_index: uint64 + + +class LightClientSnapshot(Container): + # Beacon block header + header: BeaconBlockHeader + # Sync committees corresponding to the header + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + + +class LightClientUpdate(Container): + # Update beacon block header + header: BeaconBlockHeader + # Next sync committee corresponding to the header + next_sync_committee: SyncCommittee + next_sync_committee_branch: Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_INDEX)] + # Finality proof for the update header + finality_header: BeaconBlockHeader + finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX)] + # Sync committee aggregate signature + sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] + sync_committee_signature: BLSSignature + # Fork version for the aggregate signature + fork_version: Version + + +class ExecutionPayload(Container): + # Execution block header fields + parent_hash: Hash32 + fee_recipient: ExecutionAddress # 'beneficiary' in the yellow paper + state_root: Bytes32 + receipt_root: Bytes32 # 'receipts root' in the yellow paper + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + random: Bytes32 # 'difficulty' in the yellow paper + block_number: uint64 # 'number' in the yellow paper + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + # Extra payload fields + block_hash: Hash32 # Hash of execution block + transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] + + +class BeaconBlockBody(Container): + randao_reveal: BLSSignature + eth1_data: Eth1Data # Eth1 data vote + graffiti: Bytes32 # Arbitrary data + # Operations + proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] + attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] + attestations: List[Attestation, MAX_ATTESTATIONS] + deposits: List[Deposit, MAX_DEPOSITS] + voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] + sync_aggregate: SyncAggregate + # Execution + execution_payload: ExecutionPayload # [New in Merge] + + +class BeaconBlock(Container): + slot: Slot + proposer_index: ValidatorIndex + parent_root: Root + state_root: Root + body: BeaconBlockBody + + +class SignedBeaconBlock(Container): + message: BeaconBlock + signature: BLSSignature + + +class ExecutionPayloadHeader(Container): + # Execution block header fields + parent_hash: Hash32 + fee_recipient: ExecutionAddress + state_root: Bytes32 + receipt_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + random: Bytes32 + block_number: uint64 + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + # Extra payload fields + block_hash: Hash32 # Hash of execution block + transactions_root: Root + + +class PowBlock(Container): + block_hash: Hash32 + parent_hash: Hash32 + total_difficulty: uint256 + + +class WithdrawalReceipt(Container): + index: WithdrawalReceiptIndex + address: ExecutionAddress + amount: Gwei + + +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[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + current_epoch_participation: List[ParticipationFlags, 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 + # Inactivity + inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] + # Sync + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + # Execution + latest_execution_payload_header: ExecutionPayloadHeader + # Withdrawals + withdrawal_receipts: List[WithdrawalReceipt, WITHDRAWAL_RECEIPT_LIMIT] # [New in Cappela] + + +@dataclass(eq=True, frozen=True) +class LatestMessage(object): + epoch: Epoch + root: Root + + +@dataclass +class Store(object): + time: uint64 + genesis_time: uint64 + justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + best_justified_checkpoint: Checkpoint + proposer_boost_root: Root + blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) + block_states: Dict[Root, BeaconState] = field(default_factory=dict) + checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict) + latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict) + + +@dataclass +class LightClientStore(object): + snapshot: LightClientSnapshot + valid_updates: Set[LightClientUpdate] + + +@dataclass +class PayloadAttributes(object): + timestamp: uint64 + random: Bytes32 + suggested_fee_recipient: ExecutionAddress + + +class ExecutionEngine(Protocol): + + def execute_payload(self, execution_payload: ExecutionPayload) -> bool: + """ + Return ``True`` if and only if ``execution_payload`` is valid with respect to ``self.execution_state``. + """ + ... + + def notify_forkchoice_updated(self, + head_block_hash: Hash32, + finalized_block_hash: Hash32, + payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: + ... + + def get_payload(self, payload_id: PayloadId) -> ExecutionPayload: + """ + Return ``execution_payload`` object. + """ + ... + + +def integer_squareroot(n: uint64) -> uint64: + """ + Return the largest integer ``x`` such that ``x**2 <= n``. + """ + x = n + y = (x + 1) // 2 + while y < x: + x = y + y = (x + n // x) // 2 + return x + + +def xor(bytes_1: Bytes32, bytes_2: Bytes32) -> Bytes32: + """ + Return the exclusive-or of two 32-byte strings. + """ + return Bytes32(a ^ b for a, b in zip(bytes_1, bytes_2)) + + +def bytes_to_uint64(data: bytes) -> uint64: + """ + Return the integer deserialization of ``data`` interpreted as ``ENDIANNESS``-endian. + """ + return uint64(int.from_bytes(data, ENDIANNESS)) + + +def is_active_validator(validator: Validator, epoch: Epoch) -> bool: + """ + Check if ``validator`` is active. + """ + return validator.activation_epoch <= epoch < validator.exit_epoch + + +def is_eligible_for_activation_queue(validator: Validator) -> bool: + """ + Check if ``validator`` is eligible to be placed into the activation queue. + """ + return ( + validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH + and validator.effective_balance == MAX_EFFECTIVE_BALANCE + ) + + +def is_eligible_for_activation(state: BeaconState, validator: Validator) -> bool: + """ + Check if ``validator`` is eligible for activation. + """ + return ( + # Placement in queue is finalized + validator.activation_eligibility_epoch <= state.finalized_checkpoint.epoch + # Has not yet been activated + and validator.activation_epoch == FAR_FUTURE_EPOCH + ) + + +def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool: + """ + Check if ``validator`` is slashable. + """ + return (not validator.slashed) and (validator.activation_epoch <= epoch < validator.withdrawable_epoch) + + +def is_slashable_attestation_data(data_1: AttestationData, data_2: AttestationData) -> bool: + """ + Check if ``data_1`` and ``data_2`` are slashable according to Casper FFG rules. + """ + return ( + # Double vote + (data_1 != data_2 and data_1.target.epoch == data_2.target.epoch) or + # Surround vote + (data_1.source.epoch < data_2.source.epoch and data_2.target.epoch < data_1.target.epoch) + ) + + +def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool: + """ + Check if ``indexed_attestation`` is not empty, has sorted and unique indices and has a valid aggregate signature. + """ + # Verify indices are sorted and unique + indices = indexed_attestation.attesting_indices + if len(indices) == 0 or not indices == sorted(set(indices)): + return False + # Verify aggregate signature + pubkeys = [state.validators[i].pubkey for i in indices] + domain = get_domain(state, DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch) + signing_root = compute_signing_root(indexed_attestation.data, domain) + return bls.FastAggregateVerify(pubkeys, signing_root, indexed_attestation.signature) + + +def is_valid_merkle_branch(leaf: Bytes32, branch: Sequence[Bytes32], depth: uint64, index: uint64, root: Root) -> bool: + """ + Check if ``leaf`` at ``index`` verifies against the Merkle ``root`` and ``branch``. + """ + value = leaf + for i in range(depth): + if index // (2**i) % 2: + value = hash(branch[i] + value) + else: + value = hash(value + branch[i]) + return value == root + + +def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) -> uint64: + """ + Return the shuffled index corresponding to ``seed`` (and ``index_count``). + """ + assert index < index_count + + # Swap or not (https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf) + # See the 'generalized domain' algorithm on page 3 + for current_round in range(SHUFFLE_ROUND_COUNT): + pivot = bytes_to_uint64(hash(seed + uint_to_bytes(uint8(current_round)))[0:8]) % index_count + flip = (pivot + index_count - index) % index_count + position = max(index, flip) + source = hash( + seed + + uint_to_bytes(uint8(current_round)) + + uint_to_bytes(uint32(position // 256)) + ) + byte = uint8(source[(position % 256) // 8]) + bit = (byte >> (position % 8)) % 2 + index = flip if bit else index + + return index + + +def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex], seed: Bytes32) -> ValidatorIndex: + """ + Return from ``indices`` a random index sampled by effective balance. + """ + assert len(indices) > 0 + MAX_RANDOM_BYTE = 2**8 - 1 + i = uint64(0) + total = uint64(len(indices)) + while True: + candidate_index = indices[compute_shuffled_index(i % total, total, seed)] + random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] + effective_balance = state.validators[candidate_index].effective_balance + if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: + return candidate_index + i += 1 + + +def compute_committee(indices: Sequence[ValidatorIndex], + seed: Bytes32, + index: uint64, + count: uint64) -> Sequence[ValidatorIndex]: + """ + Return the committee corresponding to ``indices``, ``seed``, ``index``, and committee ``count``. + """ + start = (len(indices) * index) // count + end = (len(indices) * uint64(index + 1)) // count + return [indices[compute_shuffled_index(uint64(i), uint64(len(indices)), seed)] for i in range(start, end)] + + +def compute_epoch_at_slot(slot: Slot) -> Epoch: + """ + Return the epoch number at ``slot``. + """ + return Epoch(slot // SLOTS_PER_EPOCH) + + +def compute_start_slot_at_epoch(epoch: Epoch) -> Slot: + """ + Return the start slot of ``epoch``. + """ + return Slot(epoch * SLOTS_PER_EPOCH) + + +def compute_activation_exit_epoch(epoch: Epoch) -> Epoch: + """ + Return the epoch during which validator activations and exits initiated in ``epoch`` take effect. + """ + return Epoch(epoch + 1 + MAX_SEED_LOOKAHEAD) + + +def compute_fork_data_root(current_version: Version, genesis_validators_root: Root) -> Root: + """ + Return the 32-byte fork data root for the ``current_version`` and ``genesis_validators_root``. + This is used primarily in signature domains to avoid collisions across forks/chains. + """ + return hash_tree_root(ForkData( + current_version=current_version, + genesis_validators_root=genesis_validators_root, + )) + + +def compute_fork_digest(current_version: Version, genesis_validators_root: Root) -> ForkDigest: + """ + Return the 4-byte fork digest for the ``current_version`` and ``genesis_validators_root``. + This is a digest primarily used for domain separation on the p2p layer. + 4-bytes suffices for practical separation of forks/chains. + """ + return ForkDigest(compute_fork_data_root(current_version, genesis_validators_root)[:4]) + + +def compute_domain(domain_type: DomainType, fork_version: Version=None, genesis_validators_root: Root=None) -> Domain: + """ + Return the domain for the ``domain_type`` and ``fork_version``. + """ + if fork_version is None: + fork_version = config.GENESIS_FORK_VERSION + if genesis_validators_root is None: + genesis_validators_root = Root() # all bytes zero by default + fork_data_root = compute_fork_data_root(fork_version, genesis_validators_root) + return Domain(domain_type + fork_data_root[:28]) + + +def compute_signing_root(ssz_object: SSZObject, domain: Domain) -> Root: + """ + Return the signing root for the corresponding signing data. + """ + return hash_tree_root(SigningData( + object_root=hash_tree_root(ssz_object), + domain=domain, + )) + + +def get_current_epoch(state: BeaconState) -> Epoch: + """ + Return the current epoch. + """ + return compute_epoch_at_slot(state.slot) + + +def get_previous_epoch(state: BeaconState) -> Epoch: + """` + Return the previous epoch (unless the current epoch is ``GENESIS_EPOCH``). + """ + current_epoch = get_current_epoch(state) + return GENESIS_EPOCH if current_epoch == GENESIS_EPOCH else Epoch(current_epoch - 1) + + +def get_block_root(state: BeaconState, epoch: Epoch) -> Root: + """ + Return the block root at the start of a recent ``epoch``. + """ + return get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch)) + + +def get_block_root_at_slot(state: BeaconState, slot: Slot) -> Root: + """ + Return the block root at a recent ``slot``. + """ + assert slot < state.slot <= slot + SLOTS_PER_HISTORICAL_ROOT + return state.block_roots[slot % SLOTS_PER_HISTORICAL_ROOT] + + +def get_randao_mix(state: BeaconState, epoch: Epoch) -> Bytes32: + """ + Return the randao mix at a recent ``epoch``. + """ + return state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR] + + +def get_active_validator_indices(state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: + """ + Return the sequence of active validator indices at ``epoch``. + """ + return [ValidatorIndex(i) for i, v in enumerate(state.validators) if is_active_validator(v, epoch)] + + +def get_validator_churn_limit(state: BeaconState) -> uint64: + """ + Return the validator churn limit for the current epoch. + """ + active_validator_indices = get_active_validator_indices(state, get_current_epoch(state)) + return max(config.MIN_PER_EPOCH_CHURN_LIMIT, uint64(len(active_validator_indices)) // config.CHURN_LIMIT_QUOTIENT) + + +def get_seed(state: BeaconState, epoch: Epoch, domain_type: DomainType) -> Bytes32: + """ + Return the seed at ``epoch``. + """ + mix = get_randao_mix(state, Epoch(epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD - 1)) # Avoid underflow + return hash(domain_type + uint_to_bytes(epoch) + mix) + + +def get_committee_count_per_slot(state: BeaconState, epoch: Epoch) -> uint64: + """ + Return the number of committees in each slot for the given ``epoch``. + """ + return max(uint64(1), min( + MAX_COMMITTEES_PER_SLOT, + uint64(len(get_active_validator_indices(state, epoch))) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE, + )) + + +def get_beacon_committee(state: BeaconState, slot: Slot, index: CommitteeIndex) -> Sequence[ValidatorIndex]: + """ + Return the beacon committee at ``slot`` for ``index``. + """ + epoch = compute_epoch_at_slot(slot) + committees_per_slot = get_committee_count_per_slot(state, epoch) + return compute_committee( + indices=get_active_validator_indices(state, epoch), + seed=get_seed(state, epoch, DOMAIN_BEACON_ATTESTER), + index=(slot % SLOTS_PER_EPOCH) * committees_per_slot + index, + count=committees_per_slot * SLOTS_PER_EPOCH, + ) + + +def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: + """ + Return the beacon proposer index at the current slot. + """ + epoch = get_current_epoch(state) + seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + uint_to_bytes(state.slot)) + indices = get_active_validator_indices(state, epoch) + return compute_proposer_index(state, indices, seed) + + +def get_total_balance(state: BeaconState, indices: Set[ValidatorIndex]) -> Gwei: + """ + Return the combined effective balance of the ``indices``. + ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. + Math safe up to ~10B ETH, afterwhich this overflows uint64. + """ + return Gwei(max(EFFECTIVE_BALANCE_INCREMENT, sum([state.validators[index].effective_balance for index in indices]))) + + +def get_total_active_balance(state: BeaconState) -> Gwei: + """ + Return the combined effective balance of the active validators. + Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. + """ + return get_total_balance(state, set(get_active_validator_indices(state, get_current_epoch(state)))) + + +def get_domain(state: BeaconState, domain_type: DomainType, epoch: Epoch=None) -> Domain: + """ + Return the signature domain (fork version concatenated with domain type) of a message. + """ + epoch = get_current_epoch(state) if epoch is None else epoch + fork_version = state.fork.previous_version if epoch < state.fork.epoch else state.fork.current_version + return compute_domain(domain_type, fork_version, state.genesis_validators_root) + + +def get_indexed_attestation(state: BeaconState, attestation: Attestation) -> IndexedAttestation: + """ + Return the indexed attestation corresponding to ``attestation``. + """ + attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) + + return IndexedAttestation( + attesting_indices=sorted(attesting_indices), + data=attestation.data, + signature=attestation.signature, + ) + + +def get_attesting_indices(state: BeaconState, + data: AttestationData, + bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Set[ValidatorIndex]: + """ + Return the set of attesting indices corresponding to ``data`` and ``bits``. + """ + committee = get_beacon_committee(state, data.slot, data.index) + return set(index for i, index in enumerate(committee) if bits[i]) + + +def increase_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: + """ + Increase the validator balance at index ``index`` by ``delta``. + """ + state.balances[index] += delta + + +def decrease_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: + """ + Decrease the validator balance at index ``index`` by ``delta``, with underflow protection. + """ + state.balances[index] = 0 if delta > state.balances[index] else state.balances[index] - delta + + +def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: + """ + Initiate the exit of the validator with index ``index``. + """ + # Return if validator already initiated exit + validator = state.validators[index] + if validator.exit_epoch != FAR_FUTURE_EPOCH: + return + + # Compute exit queue epoch + exit_epochs = [v.exit_epoch for v in state.validators if v.exit_epoch != FAR_FUTURE_EPOCH] + exit_queue_epoch = max(exit_epochs + [compute_activation_exit_epoch(get_current_epoch(state))]) + exit_queue_churn = len([v for v in state.validators if v.exit_epoch == exit_queue_epoch]) + if exit_queue_churn >= get_validator_churn_limit(state): + exit_queue_epoch += Epoch(1) + + # Set validator exit epoch and withdrawable epoch + validator.exit_epoch = exit_queue_epoch + validator.withdrawable_epoch = Epoch(validator.exit_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY) + + +def slash_validator(state: BeaconState, + slashed_index: ValidatorIndex, + whistleblower_index: ValidatorIndex=None) -> None: + """ + Slash the validator with index ``slashed_index``. + """ + epoch = get_current_epoch(state) + initiate_validator_exit(state, slashed_index) + validator = state.validators[slashed_index] + validator.slashed = True + validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR)) + state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance + slashing_penalty = validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_MERGE # [Modified in Merge] + decrease_balance(state, slashed_index, slashing_penalty) + + # Apply proposer and whistleblower rewards + proposer_index = get_beacon_proposer_index(state) + if whistleblower_index is None: + whistleblower_index = proposer_index + whistleblower_reward = Gwei(validator.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT) + proposer_reward = Gwei(whistleblower_reward * PROPOSER_WEIGHT // WEIGHT_DENOMINATOR) + increase_balance(state, proposer_index, proposer_reward) + increase_balance(state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward)) + + +def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, + eth1_timestamp: uint64, + deposits: Sequence[Deposit], + execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader() + ) -> BeaconState: + fork = Fork( + previous_version=config.MERGE_FORK_VERSION, # [Modified in Merge] for testing only + current_version=config.MERGE_FORK_VERSION, # [Modified in Merge] + epoch=GENESIS_EPOCH, + ) + state = BeaconState( + genesis_time=eth1_timestamp + config.GENESIS_DELAY, + fork=fork, + eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))), + latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), + randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy + ) + + # Process deposits + leaves = list(map(lambda deposit: deposit.data, deposits)) + for index, deposit in enumerate(deposits): + deposit_data_list = List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[:index + 1]) + state.eth1_data.deposit_root = hash_tree_root(deposit_data_list) + process_deposit(state, deposit) + + # Process activations + for index, validator in enumerate(state.validators): + balance = state.balances[index] + validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) + if validator.effective_balance == MAX_EFFECTIVE_BALANCE: + validator.activation_eligibility_epoch = GENESIS_EPOCH + validator.activation_epoch = GENESIS_EPOCH + + # Set genesis validators root for domain separation and chain versioning + state.genesis_validators_root = hash_tree_root(state.validators) + + # Fill in sync committees + # Note: A duplicate committee is assigned for the current and next committee at genesis + state.current_sync_committee = get_next_sync_committee(state) + state.next_sync_committee = get_next_sync_committee(state) + + # [New in Merge] Initialize the execution payload header + # If empty, will initialize a chain that has not yet gone through the Merge transition + state.latest_execution_payload_header = execution_payload_header + + return state + + +def is_valid_genesis_state(state: BeaconState) -> bool: + if state.genesis_time < config.MIN_GENESIS_TIME: + return False + if len(get_active_validator_indices(state, GENESIS_EPOCH)) < config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: + return False + return True + + +def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool=True) -> None: + block = signed_block.message + # Process slots (including those with no blocks) since block + process_slots(state, block.slot) + # Verify signature + if validate_result: + assert verify_block_signature(state, signed_block) + # Process block + process_block(state, block) + # Verify state root + if validate_result: + assert block.state_root == hash_tree_root(state) + + +def verify_block_signature(state: BeaconState, signed_block: SignedBeaconBlock) -> bool: + proposer = state.validators[signed_block.message.proposer_index] + signing_root = compute_signing_root(signed_block.message, get_domain(state, DOMAIN_BEACON_PROPOSER)) + return bls.Verify(proposer.pubkey, signing_root, signed_block.signature) + + +def process_slots(state: BeaconState, slot: Slot) -> None: + assert state.slot < slot + while state.slot < slot: + process_slot(state) + # Process epoch on the start slot of the next epoch + if (state.slot + 1) % SLOTS_PER_EPOCH == 0: + process_epoch(state) + state.slot = Slot(state.slot + 1) + + +def process_slot(state: BeaconState) -> None: + # Cache state root + previous_state_root = hash_tree_root(state) + state.state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_state_root + # Cache latest block header state root + if state.latest_block_header.state_root == Bytes32(): + state.latest_block_header.state_root = previous_state_root + # Cache block root + previous_block_root = hash_tree_root(state.latest_block_header) + state.block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_block_root + + +def process_epoch(state: BeaconState) -> None: + process_justification_and_finalization(state) + process_inactivity_updates(state) + process_rewards_and_penalties(state) + process_registry_updates(state) + process_slashings(state) + process_eth1_data_reset(state) + process_effective_balance_updates(state) + process_slashings_reset(state) + process_randao_mixes_reset(state) + process_historical_roots_update(state) + process_participation_flag_updates(state) + process_sync_committee_updates(state) + process_withdrawals(state) # [New in Cappela] + + +def get_matching_source_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: + assert epoch in (get_previous_epoch(state), get_current_epoch(state)) + return state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations + + +def get_matching_target_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: + return [ + a for a in get_matching_source_attestations(state, epoch) + if a.data.target.root == get_block_root(state, epoch) + ] + + +def get_matching_head_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: + return [ + a for a in get_matching_target_attestations(state, epoch) + if a.data.beacon_block_root == get_block_root_at_slot(state, a.data.slot) + ] + + +def get_unslashed_attesting_indices(state: BeaconState, + attestations: Sequence[PendingAttestation]) -> Set[ValidatorIndex]: + output = set() # type: Set[ValidatorIndex] + for a in attestations: + output = output.union(get_attesting_indices(state, a.data, a.aggregation_bits)) + return set(filter(lambda index: not state.validators[index].slashed, output)) + + +def get_attesting_balance(state: BeaconState, attestations: Sequence[PendingAttestation]) -> Gwei: + """ + Return the combined effective balance of the set of unslashed validators participating in ``attestations``. + Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. + """ + return get_total_balance(state, get_unslashed_attesting_indices(state, attestations)) + + +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_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)) + current_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_current_epoch(state)) + total_active_balance = get_total_active_balance(state) + previous_target_balance = get_total_balance(state, previous_indices) + current_target_balance = get_total_balance(state, current_indices) + weigh_justification_and_finalization(state, total_active_balance, previous_target_balance, current_target_balance) + + +def weigh_justification_and_finalization(state: BeaconState, + total_active_balance: Gwei, + previous_epoch_target_balance: Gwei, + current_epoch_target_balance: Gwei) -> None: + 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 + + # 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 + if previous_epoch_target_balance * 3 >= total_active_balance * 2: + state.current_justified_checkpoint = Checkpoint(epoch=previous_epoch, + root=get_block_root(state, previous_epoch)) + state.justification_bits[1] = 0b1 + if current_epoch_target_balance * 3 >= total_active_balance * 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 + + +def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: + """ + Return the base reward for the validator defined by ``index`` with respect to the current ``state``. + """ + increments = state.validators[index].effective_balance // EFFECTIVE_BALANCE_INCREMENT + return Gwei(increments * get_base_reward_per_increment(state)) + + +def get_proposer_reward(state: BeaconState, attesting_index: ValidatorIndex) -> Gwei: + return Gwei(get_base_reward(state, attesting_index) // PROPOSER_REWARD_QUOTIENT) + + +def get_finality_delay(state: BeaconState) -> uint64: + return get_previous_epoch(state) - state.finalized_checkpoint.epoch + + +def is_in_inactivity_leak(state: BeaconState) -> bool: + return get_finality_delay(state) > MIN_EPOCHS_TO_INACTIVITY_PENALTY + + +def get_eligible_validator_indices(state: BeaconState) -> Sequence[ValidatorIndex]: + previous_epoch = get_previous_epoch(state) + 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) + ] + + +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)] * 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) + 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 + if is_in_inactivity_leak(state): + # Since full base reward will be canceled out by inactivity penalty deltas, + # optimal participation receives full base reward compensation here. + rewards[index] += get_base_reward(state, index) + else: + 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 + + +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 get_attestation_component_deltas(state, matching_source_attestations) + + +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 get_attestation_component_deltas(state, matching_target_attestations) + + +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 get_attestation_component_deltas(state, matching_head_attestations) + + +def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return proposer and inclusion delay micro-rewards/penalties for each validator. + """ + rewards = [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 + if index in get_attesting_indices(state, a.data, a.aggregation_bits) + ], key=lambda a: a.inclusion_delay) + rewards[attestation.proposer_index] += get_proposer_reward(state, index) + max_attester_reward = Gwei(get_base_reward(state, index) - get_proposer_reward(state, index)) + 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 + + +def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return the inactivity penalty deltas by considering timely target participation flags and inactivity scores. + """ + rewards = [Gwei(0) for _ in range(len(state.validators))] + penalties = [Gwei(0) for _ in range(len(state.validators))] + previous_epoch = get_previous_epoch(state) + matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, previous_epoch) + for index in get_eligible_validator_indices(state): + if index not in matching_target_indices: + penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index] + # [Modified in Merge] + penalty_denominator = config.INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_MERGE + penalties[index] += Gwei(penalty_numerator // penalty_denominator) + return rewards, penalties + + +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) + 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 + + +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_index_deltas(state, flag_index) for flag_index in range(len(PARTICIPATION_FLAG_WEIGHTS))] + 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]) + + +def process_registry_updates(state: BeaconState) -> None: + # Process activation eligibility and ejections + for index, validator in enumerate(state.validators): + if is_eligible_for_activation_queue(validator): + validator.activation_eligibility_epoch = get_current_epoch(state) + 1 + + if ( + is_active_validator(validator, get_current_epoch(state)) + and validator.effective_balance <= config.EJECTION_BALANCE + ): + initiate_validator_exit(state, ValidatorIndex(index)) + + # Queue validators eligible for activation and not yet dequeued for activation + activation_queue = sorted([ + index for index, validator in enumerate(state.validators) + if is_eligible_for_activation(state, validator) + # Order by the sequence of activation_eligibility_epoch setting and then index + ], key=lambda index: (state.validators[index].activation_eligibility_epoch, index)) + # Dequeued validators for activation up to churn limit + for index in activation_queue[:get_validator_churn_limit(state)]: + validator = state.validators[index] + validator.activation_epoch = compute_activation_exit_epoch(get_current_epoch(state)) + + +def process_slashings(state: BeaconState) -> None: + epoch = get_current_epoch(state) + total_balance = get_total_active_balance(state) + adjusted_total_slashing_balance = min( + sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_MERGE, # [Modified in Merge] + total_balance + ) + for index, validator in enumerate(state.validators): + if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch: + increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow + penalty_numerator = validator.effective_balance // increment * adjusted_total_slashing_balance + penalty = penalty_numerator // total_balance * increment + decrease_balance(state, ValidatorIndex(index), penalty) + + +def process_eth1_data_reset(state: BeaconState) -> None: + next_epoch = Epoch(get_current_epoch(state) + 1) + # Reset eth1 data votes + if next_epoch % EPOCHS_PER_ETH1_VOTING_PERIOD == 0: + state.eth1_data_votes = [] + + +def process_effective_balance_updates(state: BeaconState) -> None: + # Update effective balances with hysteresis + for index, validator in enumerate(state.validators): + balance = state.balances[index] + HYSTERESIS_INCREMENT = uint64(EFFECTIVE_BALANCE_INCREMENT // HYSTERESIS_QUOTIENT) + DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER + UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER + if ( + balance + DOWNWARD_THRESHOLD < validator.effective_balance + or validator.effective_balance + UPWARD_THRESHOLD < balance + ): + validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) + + +def process_slashings_reset(state: BeaconState) -> None: + next_epoch = Epoch(get_current_epoch(state) + 1) + # Reset slashings + state.slashings[next_epoch % EPOCHS_PER_SLASHINGS_VECTOR] = Gwei(0) + + +def process_randao_mixes_reset(state: BeaconState) -> None: + current_epoch = get_current_epoch(state) + next_epoch = Epoch(current_epoch + 1) + # Set randao mix + state.randao_mixes[next_epoch % EPOCHS_PER_HISTORICAL_VECTOR] = get_randao_mix(state, current_epoch) + + +def process_historical_roots_update(state: BeaconState) -> None: + # Set historical root accumulator + next_epoch = Epoch(get_current_epoch(state) + 1) + if next_epoch % (SLOTS_PER_HISTORICAL_ROOT // SLOTS_PER_EPOCH) == 0: + historical_batch = HistoricalBatch(block_roots=state.block_roots, state_roots=state.state_roots) + state.historical_roots.append(hash_tree_root(historical_batch)) + + +def process_participation_record_updates(state: BeaconState) -> None: + # Rotate current/previous epoch attestations + state.previous_epoch_attestations = state.current_epoch_attestations + state.current_epoch_attestations = [] + + +def process_block(state: BeaconState, block: BeaconBlock) -> None: + process_block_header(state, block) + if is_execution_enabled(state, block.body): + process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [New in Merge] + process_randao(state, block.body) + process_eth1_data(state, block.body) + process_operations(state, block.body) + process_sync_aggregate(state, block.body.sync_aggregate) + + +def process_block_header(state: BeaconState, block: BeaconBlock) -> None: + # Verify that the slots match + assert block.slot == state.slot + # Verify that the block is newer than latest block header + assert block.slot > state.latest_block_header.slot + # Verify that proposer index is the correct index + assert block.proposer_index == get_beacon_proposer_index(state) + # Verify that the parent matches + assert block.parent_root == hash_tree_root(state.latest_block_header) + # Cache current block as the new latest block + state.latest_block_header = BeaconBlockHeader( + slot=block.slot, + proposer_index=block.proposer_index, + parent_root=block.parent_root, + state_root=Bytes32(), # Overwritten in the next process_slot call + body_root=hash_tree_root(block.body), + ) + + # Verify proposer is not slashed + proposer = state.validators[block.proposer_index] + assert not proposer.slashed + + +def process_randao(state: BeaconState, body: BeaconBlockBody) -> None: + epoch = get_current_epoch(state) + # Verify RANDAO reveal + proposer = state.validators[get_beacon_proposer_index(state)] + signing_root = compute_signing_root(epoch, get_domain(state, DOMAIN_RANDAO)) + assert bls.Verify(proposer.pubkey, signing_root, body.randao_reveal) + # Mix in RANDAO reveal + mix = xor(get_randao_mix(state, epoch), hash(body.randao_reveal)) + state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR] = mix + + +def process_eth1_data(state: BeaconState, body: BeaconBlockBody) -> None: + state.eth1_data_votes.append(body.eth1_data) + if state.eth1_data_votes.count(body.eth1_data) * 2 > EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH: + state.eth1_data = body.eth1_data + + +def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: + # Verify that outstanding deposits are processed up to the maximum number of deposits + assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index) + + def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: + for operation in operations: + fn(state, operation) + + for_ops(body.proposer_slashings, process_proposer_slashing) + for_ops(body.attester_slashings, process_attester_slashing) + for_ops(body.attestations, process_attestation) + for_ops(body.deposits, process_deposit) + for_ops(body.voluntary_exits, process_voluntary_exit) + + +def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None: + header_1 = proposer_slashing.signed_header_1.message + header_2 = proposer_slashing.signed_header_2.message + + # Verify header slots match + assert header_1.slot == header_2.slot + # Verify header proposer indices match + assert header_1.proposer_index == header_2.proposer_index + # Verify the headers are different + assert header_1 != header_2 + # Verify the proposer is slashable + proposer = state.validators[header_1.proposer_index] + assert is_slashable_validator(proposer, get_current_epoch(state)) + # Verify signatures + for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2): + domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(signed_header.message.slot)) + signing_root = compute_signing_root(signed_header.message, domain) + assert bls.Verify(proposer.pubkey, signing_root, signed_header.signature) + + slash_validator(state, header_1.proposer_index) + + +def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSlashing) -> None: + attestation_1 = attester_slashing.attestation_1 + attestation_2 = attester_slashing.attestation_2 + assert is_slashable_attestation_data(attestation_1.data, attestation_2.data) + assert is_valid_indexed_attestation(state, attestation_1) + assert is_valid_indexed_attestation(state, attestation_2) + + slashed_any = False + indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices) + for index in sorted(indices): + if is_slashable_validator(state.validators[index], get_current_epoch(state)): + slash_validator(state, index) + slashed_any = True + assert slashed_any + + +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) + + # Participation flag indices + participation_flag_indices = get_attestation_participation_flag_indices(state, data, state.slot - data.slot) + + # Verify signature + assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) + + # Update epoch participation flags + if data.target.epoch == get_current_epoch(state): + epoch_participation = state.current_epoch_participation + else: + epoch_participation = state.previous_epoch_participation + + proposer_reward_numerator = 0 + for index in get_attesting_indices(state, data, attestation.aggregation_bits): + for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS): + if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index): + epoch_participation[index] = add_flag(epoch_participation[index], flag_index) + proposer_reward_numerator += get_base_reward(state, index) * weight + + # Reward proposer + proposer_reward_denominator = (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT + proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator) + increase_balance(state, get_beacon_proposer_index(state), proposer_reward) + + +def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: + amount = deposit.data.amount + effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) + + return Validator( + pubkey=deposit.data.pubkey, + withdrawal_credentials=deposit.data.withdrawal_credentials, + activation_eligibility_epoch=FAR_FUTURE_EPOCH, + activation_epoch=FAR_FUTURE_EPOCH, + exit_epoch=FAR_FUTURE_EPOCH, + withdrawable_epoch=FAR_FUTURE_EPOCH, + effective_balance=effective_balance, + ) + + +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 = [validator.pubkey for validator 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) + # Initialize validator if the deposit signature is valid + if bls.Verify(pubkey, signing_root, deposit.data.signature): + state.validators.append(get_validator_from_deposit(state, deposit)) + state.balances.append(amount) + state.previous_epoch_participation.append(ParticipationFlags(0b0000_0000)) + state.current_epoch_participation.append(ParticipationFlags(0b0000_0000)) + state.inactivity_scores.append(uint64(0)) + else: + # Increase balance by deposit amount + index = ValidatorIndex(validator_pubkeys.index(pubkey)) + increase_balance(state, index, amount) + + +def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVoluntaryExit) -> None: + voluntary_exit = signed_voluntary_exit.message + validator = state.validators[voluntary_exit.validator_index] + # Verify the validator is active + assert is_active_validator(validator, get_current_epoch(state)) + # Verify exit has not been initiated + assert validator.exit_epoch == FAR_FUTURE_EPOCH + # Exits must specify an epoch when they become valid; they are not valid before then + assert get_current_epoch(state) >= voluntary_exit.epoch + # Verify the validator has been active long enough + assert get_current_epoch(state) >= validator.activation_epoch + config.SHARD_COMMITTEE_PERIOD + # Verify signature + domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch) + signing_root = compute_signing_root(voluntary_exit, domain) + assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature) + # Initiate exit + initiate_validator_exit(state, voluntary_exit.validator_index) + + +def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store: + assert anchor_block.state_root == hash_tree_root(anchor_state) + anchor_root = hash_tree_root(anchor_block) + anchor_epoch = get_current_epoch(anchor_state) + justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) + finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) + proposer_boost_root = Root() + return Store( + time=uint64(anchor_state.genesis_time + config.SECONDS_PER_SLOT * anchor_state.slot), + genesis_time=anchor_state.genesis_time, + justified_checkpoint=justified_checkpoint, + finalized_checkpoint=finalized_checkpoint, + best_justified_checkpoint=justified_checkpoint, + proposer_boost_root=proposer_boost_root, + blocks={anchor_root: copy(anchor_block)}, + block_states={anchor_root: copy(anchor_state)}, + checkpoint_states={justified_checkpoint: copy(anchor_state)}, + ) + + +def get_slots_since_genesis(store: Store) -> int: + return (store.time - store.genesis_time) // config.SECONDS_PER_SLOT + + +def get_current_slot(store: Store) -> Slot: + return Slot(GENESIS_SLOT + get_slots_since_genesis(store)) + + +def compute_slots_since_epoch_start(slot: Slot) -> int: + return slot - compute_start_slot_at_epoch(compute_epoch_at_slot(slot)) + + +def get_ancestor(store: Store, root: Root, slot: Slot) -> Root: + block = store.blocks[root] + if block.slot > slot: + return get_ancestor(store, block.parent_root, slot) + elif block.slot == slot: + return root + else: + # root is older than queried slot, thus a skip slot. Return most recent root prior to slot + return root + + +def get_latest_attesting_balance(store: Store, root: Root) -> Gwei: + state = store.checkpoint_states[store.justified_checkpoint] + active_indices = get_active_validator_indices(state, get_current_epoch(state)) + attestation_score = Gwei(sum( + state.validators[i].effective_balance for i in active_indices + if (i in store.latest_messages + and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root) + )) + proposer_score = Gwei(0) + if store.proposer_boost_root != Root(): + block = store.blocks[root] + if get_ancestor(store, root, block.slot) == store.proposer_boost_root: + num_validators = len(get_active_validator_indices(state, get_current_epoch(state))) + avg_balance = get_total_active_balance(state) // num_validators + committee_size = num_validators // SLOTS_PER_EPOCH + committee_weight = committee_size * avg_balance + proposer_score = (committee_weight * config.PROPOSER_SCORE_BOOST) // 100 + return attestation_score + proposer_score + + +def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconBlock]) -> bool: + block = store.blocks[block_root] + children = [ + root for root in store.blocks.keys() + if store.blocks[root].parent_root == block_root + ] + + # If any children branches contain expected finalized/justified checkpoints, + # add to filtered block-tree and signal viability to parent. + if any(children): + filter_block_tree_result = [filter_block_tree(store, child, blocks) for child in children] + if any(filter_block_tree_result): + blocks[block_root] = block + return True + return False + + # If leaf block, check finalized/justified checkpoints as matching latest. + head_state = store.block_states[block_root] + + correct_justified = ( + store.justified_checkpoint.epoch == GENESIS_EPOCH + or head_state.current_justified_checkpoint == store.justified_checkpoint + ) + correct_finalized = ( + store.finalized_checkpoint.epoch == GENESIS_EPOCH + or head_state.finalized_checkpoint == store.finalized_checkpoint + ) + # If expected finalized/justified, add to viable block-tree and signal viability to parent. + if correct_justified and correct_finalized: + blocks[block_root] = block + return True + + # Otherwise, branch not viable + return False + + +def get_filtered_block_tree(store: Store) -> Dict[Root, BeaconBlock]: + """ + Retrieve a filtered block tree from ``store``, only returning branches + whose leaf state's justified/finalized info agrees with that in ``store``. + """ + base = store.justified_checkpoint.root + blocks: Dict[Root, BeaconBlock] = {} + filter_block_tree(store, base, blocks) + return blocks + + +def get_head(store: Store) -> Root: + # Get filtered block tree that only includes viable branches + blocks = get_filtered_block_tree(store) + # Execute the LMD-GHOST fork choice + head = store.justified_checkpoint.root + while True: + children = [ + root for root in blocks.keys() + if blocks[root].parent_root == head + ] + if len(children) == 0: + return head + # Sort by latest attesting balance with ties broken lexicographically + head = max(children, key=lambda root: (get_latest_attesting_balance(store, root), root)) + + +def should_update_justified_checkpoint(store: Store, new_justified_checkpoint: Checkpoint) -> bool: + """ + To address the bouncing attack, only update conflicting justified + checkpoints in the fork choice if in the early slots of the epoch. + Otherwise, delay incorporation of new justified checkpoint until next epoch boundary. + + See https://ethresear.ch/t/prevention-of-bouncing-attack-on-ffg/6114 for more detailed analysis and discussion. + """ + if compute_slots_since_epoch_start(get_current_slot(store)) < SAFE_SLOTS_TO_UPDATE_JUSTIFIED: + return True + + justified_slot = compute_start_slot_at_epoch(store.justified_checkpoint.epoch) + if not get_ancestor(store, new_justified_checkpoint.root, justified_slot) == store.justified_checkpoint.root: + return False + + return True + + +def validate_target_epoch_against_current_time(store: Store, attestation: Attestation) -> None: + target = attestation.data.target + + # Attestations must be from the current or previous epoch + 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] + + +def validate_on_attestation(store: Store, attestation: Attestation, is_from_block: bool) -> None: + target = attestation.data.target + + # If the given attestation is not from a beacon block message, we have to check the target epoch scope. + if not is_from_block: + validate_target_epoch_against_current_time(store, attestation) + + # Check that the epoch number and slot number are matching + assert target.epoch == compute_epoch_at_slot(attestation.data.slot) + + # 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 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 + # Attestations must not be for blocks in the future. If not, the attestation should not be considered + assert store.blocks[attestation.data.beacon_block_root].slot <= attestation.data.slot + + # LMD vote must be consistent with FFG vote target + target_slot = compute_start_slot_at_epoch(target.epoch) + assert target.root == get_ancestor(store, attestation.data.beacon_block_root, target_slot) + + # Attestations can only affect the fork choice of subsequent slots. + # Delay consideration in the fork choice until their slot is in the past. + assert get_current_slot(store) >= attestation.data.slot + 1 + + +def store_target_checkpoint_state(store: Store, target: Checkpoint) -> None: + # Store target checkpoint state if not yet seen + if target not in store.checkpoint_states: + base_state = copy(store.block_states[target.root]) + if base_state.slot < compute_start_slot_at_epoch(target.epoch): + process_slots(base_state, compute_start_slot_at_epoch(target.epoch)) + store.checkpoint_states[target] = base_state + + +def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None: + target = attestation.data.target + beacon_block_root = attestation.data.beacon_block_root + for i in attesting_indices: + if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch: + store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root) + + +def on_tick(store: Store, time: uint64) -> None: + previous_slot = get_current_slot(store) + + # update store time + store.time = time + + current_slot = get_current_slot(store) + + # Reset store.proposer_boost_root if this is a new slot + if current_slot > previous_slot: + store.proposer_boost_root = Root() + + # Not a new epoch, return + if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0): + return + + # Update store.justified_checkpoint if a better checkpoint on the store.finalized_checkpoint chain + if store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch: + finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + ancestor_at_finalized_slot = get_ancestor(store, store.best_justified_checkpoint.root, finalized_slot) + if ancestor_at_finalized_slot == store.finalized_checkpoint.root: + store.justified_checkpoint = store.best_justified_checkpoint + + +def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: + """ + Run ``on_block`` upon receiving a new block. + + A block that is asserted as invalid due to unavailable PoW block may be valid at a later time, + consider scheduling it for later processing in such case. + """ + block = signed_block.message + # Parent block must be known + assert block.parent_root in store.block_states + # Make a copy of the state to avoid mutability issues + pre_state = copy(store.block_states[block.parent_root]) + # Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past. + assert get_current_slot(store) >= block.slot + + # Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) + finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + assert block.slot > finalized_slot + # Check block is a descendant of the finalized block at the checkpoint finalized slot + assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root + + # Check the block is valid and compute the post-state + state = pre_state.copy() + state_transition(state, signed_block, True) + + # [New in Merge] + if is_merge_transition_block(pre_state, block.body): + validate_merge_block(block) + + # Add new block to the store + store.blocks[hash_tree_root(block)] = block + # Add new state for this block to the store + store.block_states[hash_tree_root(block)] = state + + # Add proposer score boost if the block is timely + time_into_slot = (store.time - store.genesis_time) % config.SECONDS_PER_SLOT + is_before_attesting_interval = time_into_slot < config.SECONDS_PER_SLOT // INTERVALS_PER_SLOT + if get_current_slot(store) == block.slot and is_before_attesting_interval: + store.proposer_boost_root = hash_tree_root(block) + + # Update justified checkpoint + if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: + if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch: + store.best_justified_checkpoint = state.current_justified_checkpoint + if should_update_justified_checkpoint(store, state.current_justified_checkpoint): + store.justified_checkpoint = state.current_justified_checkpoint + + # Update finalized checkpoint + if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch: + store.finalized_checkpoint = state.finalized_checkpoint + store.justified_checkpoint = state.current_justified_checkpoint + + +def on_attestation(store: Store, attestation: Attestation, is_from_block: bool=False) -> None: + """ + Run ``on_attestation`` upon receiving a new ``attestation`` from either within a block or directly on the wire. + + An ``attestation`` that is asserted as invalid may be valid at a later time, + consider scheduling it for later processing in such case. + """ + validate_on_attestation(store, attestation, is_from_block) + + store_target_checkpoint_state(store, attestation.data.target) + + # Get state at the `target` to fully validate attestation + target_state = store.checkpoint_states[attestation.data.target] + indexed_attestation = get_indexed_attestation(target_state, attestation) + assert is_valid_indexed_attestation(target_state, indexed_attestation) + + # Update latest messages for attesting indices + update_latest_messages(store, indexed_attestation.attesting_indices, attestation) + + +def check_if_validator_active(state: BeaconState, validator_index: ValidatorIndex) -> bool: + validator = state.validators[validator_index] + return is_active_validator(validator, get_current_epoch(state)) + + +def get_committee_assignment(state: BeaconState, + epoch: Epoch, + validator_index: ValidatorIndex + ) -> Optional[Tuple[Sequence[ValidatorIndex], CommitteeIndex, Slot]]: + """ + Return the committee assignment in the ``epoch`` for ``validator_index``. + ``assignment`` returned is a tuple of the following form: + * ``assignment[0]`` is the list of validators in the committee + * ``assignment[1]`` is the index to which the committee is assigned + * ``assignment[2]`` is the slot at which the committee is assigned + Return None if no assignment. + """ + next_epoch = Epoch(get_current_epoch(state) + 1) + assert epoch <= next_epoch + + start_slot = compute_start_slot_at_epoch(epoch) + committee_count_per_slot = get_committee_count_per_slot(state, epoch) + for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH): + for index in range(committee_count_per_slot): + committee = get_beacon_committee(state, Slot(slot), CommitteeIndex(index)) + if validator_index in committee: + return committee, CommitteeIndex(index), Slot(slot) + return None + + +def is_proposer(state: BeaconState, validator_index: ValidatorIndex) -> bool: + return get_beacon_proposer_index(state) == validator_index + + +def get_epoch_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_RANDAO, compute_epoch_at_slot(block.slot)) + signing_root = compute_signing_root(compute_epoch_at_slot(block.slot), domain) + return bls.Sign(privkey, signing_root) + + +def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64: + return uint64(state.genesis_time + slot * config.SECONDS_PER_SLOT) + + +def voting_period_start_time(state: BeaconState) -> uint64: + eth1_voting_period_start_slot = Slot(state.slot - state.slot % (EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH)) + return compute_time_at_slot(state, eth1_voting_period_start_slot) + + +def is_candidate_block(block: Eth1Block, period_start: uint64) -> bool: + return ( + block.timestamp + config.SECONDS_PER_ETH1_BLOCK * config.ETH1_FOLLOW_DISTANCE <= period_start + and block.timestamp + config.SECONDS_PER_ETH1_BLOCK * config.ETH1_FOLLOW_DISTANCE * 2 >= period_start + ) + + +def get_eth1_vote(state: BeaconState, eth1_chain: Sequence[Eth1Block]) -> Eth1Data: + period_start = voting_period_start_time(state) + # `eth1_chain` abstractly represents all blocks in the eth1 chain sorted by ascending block height + votes_to_consider = [ + get_eth1_data(block) for block in eth1_chain + if ( + is_candidate_block(block, period_start) + # Ensure cannot move back to earlier deposit contract states + and get_eth1_data(block).deposit_count >= state.eth1_data.deposit_count + ) + ] + + # Valid votes already cast during this period + valid_votes = [vote for vote in state.eth1_data_votes if vote in votes_to_consider] + + # Default vote on latest eth1 block data in the period range unless eth1 chain is not live + # Non-substantive casting for linter + state_eth1_data: Eth1Data = state.eth1_data + default_vote = votes_to_consider[len(votes_to_consider) - 1] if any(votes_to_consider) else state_eth1_data + + return max( + valid_votes, + key=lambda v: (valid_votes.count(v), -valid_votes.index(v)), # Tiebreak by smallest distance + default=default_vote + ) + + +def compute_new_state_root(state: BeaconState, block: BeaconBlock) -> Root: + temp_state: BeaconState = state.copy() + signed_block = SignedBeaconBlock(message=block) + state_transition(temp_state, signed_block, validate_result=False) + return hash_tree_root(temp_state) + + +def get_block_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(block.slot)) + signing_root = compute_signing_root(block, domain) + return bls.Sign(privkey, signing_root) + + +def get_attestation_signature(state: BeaconState, attestation_data: AttestationData, privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch) + signing_root = compute_signing_root(attestation_data, domain) + return bls.Sign(privkey, signing_root) + + +def compute_subnet_for_attestation(committees_per_slot: uint64, slot: Slot, committee_index: CommitteeIndex) -> uint64: + """ + Compute the correct subnet for an attestation for Phase 0. + Note, this mimics expected future behavior where attestations will be mapped to their shard subnet. + """ + slots_since_epoch_start = uint64(slot % SLOTS_PER_EPOCH) + committees_since_epoch_start = committees_per_slot * slots_since_epoch_start + + return uint64((committees_since_epoch_start + committee_index) % ATTESTATION_SUBNET_COUNT) + + +def get_slot_signature(state: BeaconState, slot: Slot, privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_SELECTION_PROOF, compute_epoch_at_slot(slot)) + signing_root = compute_signing_root(slot, domain) + return bls.Sign(privkey, signing_root) + + +def is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex, slot_signature: BLSSignature) -> bool: + committee = get_beacon_committee(state, slot, index) + modulo = max(1, len(committee) // TARGET_AGGREGATORS_PER_COMMITTEE) + return bytes_to_uint64(hash(slot_signature)[0:8]) % modulo == 0 + + +def get_aggregate_signature(attestations: Sequence[Attestation]) -> BLSSignature: + signatures = [attestation.signature for attestation in attestations] + return bls.Aggregate(signatures) + + +def get_aggregate_and_proof(state: BeaconState, + aggregator_index: ValidatorIndex, + aggregate: Attestation, + privkey: int) -> AggregateAndProof: + return AggregateAndProof( + aggregator_index=aggregator_index, + aggregate=aggregate, + selection_proof=get_slot_signature(state, aggregate.data.slot, privkey), + ) + + +def get_aggregate_and_proof_signature(state: BeaconState, + aggregate_and_proof: AggregateAndProof, + privkey: int) -> BLSSignature: + aggregate = aggregate_and_proof.aggregate + domain = get_domain(state, DOMAIN_AGGREGATE_AND_PROOF, compute_epoch_at_slot(aggregate.data.slot)) + signing_root = compute_signing_root(aggregate_and_proof, domain) + return bls.Sign(privkey, signing_root) + + +def compute_weak_subjectivity_period(state: BeaconState) -> uint64: + """ + Returns the weak subjectivity period for the current ``state``. + This computation takes into account the effect of: + - validator set churn (bounded by ``get_validator_churn_limit()`` per epoch), and + - validator balance top-ups (bounded by ``MAX_DEPOSITS * SLOTS_PER_EPOCH`` per epoch). + A detailed calculation can be found at: + https://github.com/runtimeverification/beacon-chain-verification/blob/master/weak-subjectivity/weak-subjectivity-analysis.pdf + """ + ws_period = config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + N = len(get_active_validator_indices(state, get_current_epoch(state))) + t = get_total_active_balance(state) // N // ETH_TO_GWEI + T = MAX_EFFECTIVE_BALANCE // ETH_TO_GWEI + delta = get_validator_churn_limit(state) + Delta = MAX_DEPOSITS * SLOTS_PER_EPOCH + D = SAFETY_DECAY + + if T * (200 + 3 * D) < t * (200 + 12 * D): + epochs_for_validator_set_churn = ( + N * (t * (200 + 12 * D) - T * (200 + 3 * D)) // (600 * delta * (2 * t + T)) + ) + epochs_for_balance_top_ups = ( + N * (200 + 3 * D) // (600 * Delta) + ) + ws_period += max(epochs_for_validator_set_churn, epochs_for_balance_top_ups) + else: + ws_period += ( + 3 * N * D * t // (200 * Delta * (T - t)) + ) + + return ws_period + + +def is_within_weak_subjectivity_period(store: Store, ws_state: BeaconState, ws_checkpoint: Checkpoint) -> bool: + # Clients may choose to validate the input state against the input Weak Subjectivity Checkpoint + assert ws_state.latest_block_header.state_root == ws_checkpoint.root + assert compute_epoch_at_slot(ws_state.slot) == ws_checkpoint.epoch + + ws_period = compute_weak_subjectivity_period(ws_state) + ws_state_epoch = compute_epoch_at_slot(ws_state.slot) + current_epoch = compute_epoch_at_slot(get_current_slot(store)) + return current_epoch <= ws_state_epoch + ws_period + + +def add_flag(flags: ParticipationFlags, flag_index: int) -> ParticipationFlags: + """ + Return a new ``ParticipationFlags`` adding ``flag_index`` to ``flags``. + """ + flag = ParticipationFlags(2**flag_index) + return flags | flag + + +def has_flag(flags: ParticipationFlags, flag_index: int) -> bool: + """ + Return whether ``flags`` has ``flag_index`` set. + """ + flag = ParticipationFlags(2**flag_index) + return flags & flag == flag + + +def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorIndex]: + """ + Return the sync committee indices, with possible duplicates, for the next sync committee. + """ + epoch = Epoch(get_current_epoch(state) + 1) + + MAX_RANDOM_BYTE = 2**8 - 1 + active_validator_indices = get_active_validator_indices(state, epoch) + active_validator_count = uint64(len(active_validator_indices)) + seed = get_seed(state, epoch, DOMAIN_SYNC_COMMITTEE) + i = 0 + sync_committee_indices: List[ValidatorIndex] = [] + while len(sync_committee_indices) < SYNC_COMMITTEE_SIZE: + shuffled_index = compute_shuffled_index(uint64(i % active_validator_count), active_validator_count, seed) + candidate_index = active_validator_indices[shuffled_index] + random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] + effective_balance = state.validators[candidate_index].effective_balance + if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: + sync_committee_indices.append(candidate_index) + i += 1 + return sync_committee_indices + + +def get_next_sync_committee(state: BeaconState) -> SyncCommittee: + """ + Return the next sync committee, with possible pubkey duplicates. + """ + indices = get_next_sync_committee_indices(state) + pubkeys = [state.validators[index].pubkey for index in indices] + aggregate_pubkey = eth_aggregate_pubkeys(pubkeys) + return SyncCommittee(pubkeys=pubkeys, aggregate_pubkey=aggregate_pubkey) + + +def get_base_reward_per_increment(state: BeaconState) -> Gwei: + return Gwei(EFFECTIVE_BALANCE_INCREMENT * BASE_REWARD_FACTOR // integer_squareroot(get_total_active_balance(state))) + + +def get_unslashed_participating_indices(state: BeaconState, flag_index: int, epoch: Epoch) -> Set[ValidatorIndex]: + """ + Return the set of validator indices that are both active and unslashed for the given ``flag_index`` and ``epoch``. + """ + 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 + active_validator_indices = get_active_validator_indices(state, epoch) + participating_indices = [i for i in active_validator_indices if has_flag(epoch_participation[i], flag_index)] + return set(filter(lambda index: not state.validators[index].slashed, participating_indices)) + + +def get_attestation_participation_flag_indices(state: BeaconState, + data: AttestationData, + inclusion_delay: uint64) -> Sequence[int]: + """ + Return the flag indices that are satisfied by an attestation. + """ + if data.target.epoch == get_current_epoch(state): + justified_checkpoint = state.current_justified_checkpoint + else: + justified_checkpoint = state.previous_justified_checkpoint + + # Matching roots + is_matching_source = data.source == justified_checkpoint + is_matching_target = is_matching_source and data.target.root == get_block_root(state, data.target.epoch) + is_matching_head = is_matching_target and data.beacon_block_root == get_block_root_at_slot(state, data.slot) + assert is_matching_source + + participation_flag_indices = [] + if is_matching_source and inclusion_delay <= integer_squareroot(SLOTS_PER_EPOCH): + participation_flag_indices.append(TIMELY_SOURCE_FLAG_INDEX) + if is_matching_target and inclusion_delay <= SLOTS_PER_EPOCH: + participation_flag_indices.append(TIMELY_TARGET_FLAG_INDEX) + if is_matching_head and inclusion_delay == MIN_ATTESTATION_INCLUSION_DELAY: + participation_flag_indices.append(TIMELY_HEAD_FLAG_INDEX) + + return participation_flag_indices + + +def get_flag_index_deltas(state: BeaconState, flag_index: int) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return the deltas for a given ``flag_index`` by scanning through the participation flags. + """ + rewards = [Gwei(0)] * len(state.validators) + penalties = [Gwei(0)] * len(state.validators) + previous_epoch = get_previous_epoch(state) + unslashed_participating_indices = get_unslashed_participating_indices(state, flag_index, previous_epoch) + weight = PARTICIPATION_FLAG_WEIGHTS[flag_index] + unslashed_participating_balance = get_total_balance(state, unslashed_participating_indices) + unslashed_participating_increments = unslashed_participating_balance // EFFECTIVE_BALANCE_INCREMENT + active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT + for index in get_eligible_validator_indices(state): + base_reward = get_base_reward(state, index) + if index in unslashed_participating_indices: + if not is_in_inactivity_leak(state): + reward_numerator = base_reward * weight * unslashed_participating_increments + rewards[index] += Gwei(reward_numerator // (active_increments * WEIGHT_DENOMINATOR)) + elif flag_index != TIMELY_HEAD_FLAG_INDEX: + penalties[index] += Gwei(base_reward * weight // WEIGHT_DENOMINATOR) + return rewards, penalties + + +def process_sync_aggregate(state: BeaconState, sync_aggregate: SyncAggregate) -> None: + # Verify sync committee aggregate signature signing over the previous slot block root + committee_pubkeys = state.current_sync_committee.pubkeys + participant_pubkeys = [pubkey for pubkey, bit in zip(committee_pubkeys, sync_aggregate.sync_committee_bits) if bit] + previous_slot = max(state.slot, Slot(1)) - Slot(1) + domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, compute_epoch_at_slot(previous_slot)) + signing_root = compute_signing_root(get_block_root_at_slot(state, previous_slot), domain) + assert eth_fast_aggregate_verify(participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature) + + # Compute participant and proposer rewards + total_active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT + total_base_rewards = Gwei(get_base_reward_per_increment(state) * total_active_increments) + max_participant_rewards = Gwei(total_base_rewards * SYNC_REWARD_WEIGHT // WEIGHT_DENOMINATOR // SLOTS_PER_EPOCH) + participant_reward = Gwei(max_participant_rewards // SYNC_COMMITTEE_SIZE) + proposer_reward = Gwei(participant_reward * PROPOSER_WEIGHT // (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)) + + # Apply participant and proposer rewards + all_pubkeys = [v.pubkey for v in state.validators] + committee_indices = [ValidatorIndex(all_pubkeys.index(pubkey)) for pubkey in state.current_sync_committee.pubkeys] + for participant_index, participation_bit in zip(committee_indices, sync_aggregate.sync_committee_bits): + if participation_bit: + increase_balance(state, participant_index, participant_reward) + increase_balance(state, get_beacon_proposer_index(state), proposer_reward) + else: + decrease_balance(state, participant_index, participant_reward) + + +def process_inactivity_updates(state: BeaconState) -> None: + # Skip the genesis epoch as score updates are based on the previous epoch participation + if get_current_epoch(state) == GENESIS_EPOCH: + return + + for index in get_eligible_validator_indices(state): + # Increase the inactivity score of inactive validators + if index in get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)): + state.inactivity_scores[index] -= min(1, state.inactivity_scores[index]) + else: + state.inactivity_scores[index] += config.INACTIVITY_SCORE_BIAS + # Decrease the inactivity score of all eligible validators during a leak-free epoch + if not is_in_inactivity_leak(state): + state.inactivity_scores[index] -= min(config.INACTIVITY_SCORE_RECOVERY_RATE, state.inactivity_scores[index]) + + +def process_participation_flag_updates(state: BeaconState) -> None: + state.previous_epoch_participation = state.current_epoch_participation + state.current_epoch_participation = [ParticipationFlags(0b0000_0000) for _ in range(len(state.validators))] + + +def process_sync_committee_updates(state: BeaconState) -> None: + 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_next_sync_committee(state) + + +def eth_aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey: + return bls.AggregatePKs(pubkeys) + + +def eth_fast_aggregate_verify(pubkeys: Sequence[BLSPubkey], message: Bytes32, signature: BLSSignature) -> bool: + """ + Wrapper to ``bls.FastAggregateVerify`` accepting the ``G2_POINT_AT_INFINITY`` signature when ``pubkeys`` is empty. + """ + if len(pubkeys) == 0 and signature == G2_POINT_AT_INFINITY: + return True + return bls.FastAggregateVerify(pubkeys, message, signature) + + +def translate_participation(state: BeaconState, pending_attestations: Sequence[phase0.PendingAttestation]) -> None: + for attestation in pending_attestations: + data = attestation.data + inclusion_delay = attestation.inclusion_delay + # Translate attestation inclusion info to flag indices + participation_flag_indices = get_attestation_participation_flag_indices(state, data, inclusion_delay) + + # Apply flags to all attesting validators + epoch_participation = state.previous_epoch_participation + for index in get_attesting_indices(state, data, attestation.aggregation_bits): + for flag_index in participation_flag_indices: + epoch_participation[index] = add_flag(epoch_participation[index], flag_index) + + +def upgrade_to_altair(pre: phase0.BeaconState) -> BeaconState: + epoch = phase0.get_current_epoch(pre) + post = BeaconState( + # Versioning + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + current_version=config.ALTAIR_FORK_VERSION, + epoch=epoch, + ), + # History + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + # Eth1 + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + # Registry + validators=pre.validators, + balances=pre.balances, + # Randomness + randao_mixes=pre.randao_mixes, + # Slashings + slashings=pre.slashings, + # Participation + previous_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))], + current_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))], + # Finality + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + # Inactivity + inactivity_scores=[uint64(0) for _ in range(len(pre.validators))], + ) + # Fill in previous epoch participation from the pre state's pending attestations + translate_participation(post, pre.previous_epoch_attestations) + + # Fill in sync committees + # Note: A duplicate committee is assigned for the current and next committee at the fork boundary + post.current_sync_committee = get_next_sync_committee(post) + post.next_sync_committee = get_next_sync_committee(post) + return post + + +def compute_sync_committee_period(epoch: Epoch) -> uint64: + return epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + + +def is_assigned_to_sync_committee(state: BeaconState, + epoch: Epoch, + validator_index: ValidatorIndex) -> bool: + sync_committee_period = compute_sync_committee_period(epoch) + current_epoch = get_current_epoch(state) + current_sync_committee_period = compute_sync_committee_period(current_epoch) + next_sync_committee_period = current_sync_committee_period + 1 + assert sync_committee_period in (current_sync_committee_period, next_sync_committee_period) + + pubkey = state.validators[validator_index].pubkey + if sync_committee_period == current_sync_committee_period: + return pubkey in state.current_sync_committee.pubkeys + else: # sync_committee_period == next_sync_committee_period + return pubkey in state.next_sync_committee.pubkeys + + +def process_sync_committee_contributions(block: BeaconBlock, + contributions: Set[SyncCommitteeContribution]) -> None: + sync_aggregate = SyncAggregate() + signatures = [] + sync_subcommittee_size = SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT + + for contribution in contributions: + subcommittee_index = contribution.subcommittee_index + for index, participated in enumerate(contribution.aggregation_bits): + if participated: + participant_index = sync_subcommittee_size * subcommittee_index + index + sync_aggregate.sync_committee_bits[participant_index] = True + signatures.append(contribution.signature) + + sync_aggregate.sync_committee_signature = bls.Aggregate(signatures) + + block.body.sync_aggregate = sync_aggregate + + +def get_sync_committee_message(state: BeaconState, + block_root: Root, + validator_index: ValidatorIndex, + privkey: int) -> SyncCommitteeMessage: + epoch = get_current_epoch(state) + domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, epoch) + signing_root = compute_signing_root(block_root, domain) + signature = bls.Sign(privkey, signing_root) + + return SyncCommitteeMessage( + slot=state.slot, + beacon_block_root=block_root, + validator_index=validator_index, + signature=signature, + ) + + +def compute_subnets_for_sync_committee(state: BeaconState, validator_index: ValidatorIndex) -> Set[uint64]: + next_slot_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) + if compute_sync_committee_period(get_current_epoch(state)) == compute_sync_committee_period(next_slot_epoch): + sync_committee = state.current_sync_committee + else: + sync_committee = state.next_sync_committee + + target_pubkey = state.validators[validator_index].pubkey + sync_committee_indices = [index for index, pubkey in enumerate(sync_committee.pubkeys) if pubkey == target_pubkey] + return set([ + uint64(index // (SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT)) + for index in sync_committee_indices + ]) + + +def get_sync_committee_selection_proof(state: BeaconState, + slot: Slot, + subcommittee_index: uint64, + privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF, compute_epoch_at_slot(slot)) + signing_data = SyncAggregatorSelectionData( + slot=slot, + subcommittee_index=subcommittee_index, + ) + signing_root = compute_signing_root(signing_data, domain) + return bls.Sign(privkey, signing_root) + + +def is_sync_committee_aggregator(signature: BLSSignature) -> bool: + modulo = max(1, SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT // TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE) + return bytes_to_uint64(hash(signature)[0:8]) % modulo == 0 + + +def get_contribution_and_proof(state: BeaconState, + aggregator_index: ValidatorIndex, + contribution: SyncCommitteeContribution, + privkey: int) -> ContributionAndProof: + selection_proof = get_sync_committee_selection_proof( + state, + contribution.slot, + contribution.subcommittee_index, + privkey, + ) + return ContributionAndProof( + aggregator_index=aggregator_index, + contribution=contribution, + selection_proof=selection_proof, + ) + + +def get_contribution_and_proof_signature(state: BeaconState, + contribution_and_proof: ContributionAndProof, + privkey: int) -> BLSSignature: + contribution = contribution_and_proof.contribution + domain = get_domain(state, DOMAIN_CONTRIBUTION_AND_PROOF, compute_epoch_at_slot(contribution.slot)) + signing_root = compute_signing_root(contribution_and_proof, domain) + return bls.Sign(privkey, signing_root) + + +def get_sync_subcommittee_pubkeys(state: BeaconState, subcommittee_index: uint64) -> Sequence[BLSPubkey]: + # Committees assigned to `slot` sign for `slot - 1` + # This creates the exceptional logic below when transitioning between sync committee periods + next_slot_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) + if compute_sync_committee_period(get_current_epoch(state)) == compute_sync_committee_period(next_slot_epoch): + sync_committee = state.current_sync_committee + else: + sync_committee = state.next_sync_committee + + # Return pubkeys for the subcommittee index + sync_subcommittee_size = SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT + i = subcommittee_index * sync_subcommittee_size + return sync_committee.pubkeys[i:i + sync_subcommittee_size] + + +def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: + return uint64(generalized_index % 2**(floorlog2(generalized_index))) + + +def validate_light_client_update(snapshot: LightClientSnapshot, + update: LightClientUpdate, + genesis_validators_root: Root) -> None: + # Verify update slot is larger than snapshot slot + assert update.header.slot > snapshot.header.slot + + # Verify update does not skip a sync committee period + snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + assert update_period in (snapshot_period, snapshot_period + 1) + + # Verify update header root is the finalized root of the finality header, if specified + if update.finality_header == BeaconBlockHeader(): + signed_header = update.header + assert update.finality_branch == [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))] + else: + signed_header = update.finality_header + assert is_valid_merkle_branch( + leaf=hash_tree_root(update.header), + branch=update.finality_branch, + depth=floorlog2(FINALIZED_ROOT_INDEX), + index=get_subtree_index(FINALIZED_ROOT_INDEX), + root=update.finality_header.state_root, + ) + + # Verify update next sync committee if the update period incremented + if update_period == snapshot_period: + sync_committee = snapshot.current_sync_committee + assert update.next_sync_committee_branch == [Bytes32() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))] + else: + sync_committee = snapshot.next_sync_committee + assert is_valid_merkle_branch( + leaf=hash_tree_root(update.next_sync_committee), + branch=update.next_sync_committee_branch, + depth=floorlog2(NEXT_SYNC_COMMITTEE_INDEX), + index=get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX), + root=update.header.state_root, + ) + + # Verify sync committee has sufficient participants + assert sum(update.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS + + # Verify sync committee aggregate signature + participant_pubkeys = [pubkey for (bit, pubkey) in zip(update.sync_committee_bits, sync_committee.pubkeys) if bit] + domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version, genesis_validators_root) + signing_root = compute_signing_root(signed_header, domain) + assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature) + + +def apply_light_client_update(snapshot: LightClientSnapshot, update: LightClientUpdate) -> None: + snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + if update_period == snapshot_period + 1: + snapshot.current_sync_committee = snapshot.next_sync_committee + snapshot.next_sync_committee = update.next_sync_committee + snapshot.header = update.header + + +def process_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot, + genesis_validators_root: Root) -> None: + validate_light_client_update(store.snapshot, update, genesis_validators_root) + store.valid_updates.add(update) + + update_timeout = SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD + if ( + sum(update.sync_committee_bits) * 3 >= len(update.sync_committee_bits) * 2 + and update.finality_header != BeaconBlockHeader() + ): + # Apply update if (1) 2/3 quorum is reached and (2) we have a finality proof. + # Note that (2) means that the current light client design needs finality. + # It may be changed to re-organizable light client design. See the on-going issue consensus-specs#2182. + apply_light_client_update(store.snapshot, update) + store.valid_updates = set() + elif current_slot > store.snapshot.header.slot + update_timeout: + # Forced best update when the update timeout has elapsed + apply_light_client_update(store.snapshot, + max(store.valid_updates, key=lambda update: sum(update.sync_committee_bits))) + store.valid_updates = set() + + +def is_merge_transition_complete(state: BeaconState) -> bool: + return state.latest_execution_payload_header != ExecutionPayloadHeader() + + +def is_merge_transition_block(state: BeaconState, body: BeaconBlockBody) -> bool: + return not is_merge_transition_complete(state) and body.execution_payload != ExecutionPayload() + + +def is_execution_enabled(state: BeaconState, body: BeaconBlockBody) -> bool: + return is_merge_transition_block(state, body) or is_merge_transition_complete(state) + + +def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64: + slots_since_genesis = slot - GENESIS_SLOT + return uint64(state.genesis_time + slots_since_genesis * config.SECONDS_PER_SLOT) + + +def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None: + # Verify consistency of the parent hash with respect to the previous execution payload header + if is_merge_transition_complete(state): + assert payload.parent_hash == state.latest_execution_payload_header.block_hash + # Verify random + assert payload.random == get_randao_mix(state, get_current_epoch(state)) + # Verify timestamp + assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) + # Verify the execution payload is valid + assert execution_engine.execute_payload(payload) + # Cache execution payload header + state.latest_execution_payload_header = ExecutionPayloadHeader( + parent_hash=payload.parent_hash, + fee_recipient=payload.fee_recipient, + state_root=payload.state_root, + receipt_root=payload.receipt_root, + logs_bloom=payload.logs_bloom, + random=payload.random, + block_number=payload.block_number, + gas_limit=payload.gas_limit, + gas_used=payload.gas_used, + timestamp=payload.timestamp, + extra_data=payload.extra_data, + base_fee_per_gas=payload.base_fee_per_gas, + block_hash=payload.block_hash, + transactions_root=hash_tree_root(payload.transactions), + ) + + +def upgrade_to_merge(pre: altair.BeaconState) -> BeaconState: + epoch = altair.get_current_epoch(pre) + post = BeaconState( + # Versioning + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + current_version=config.MERGE_FORK_VERSION, + epoch=epoch, + ), + # History + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + # Eth1 + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + # Registry + validators=pre.validators, + balances=pre.balances, + # Randomness + randao_mixes=pre.randao_mixes, + # Slashings + slashings=pre.slashings, + # Participation + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + # Finality + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + # Inactivity + inactivity_scores=pre.inactivity_scores, + # Sync + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + # Execution-layer + latest_execution_payload_header=ExecutionPayloadHeader(), + ) + + return post + + +def is_valid_terminal_pow_block(block: PowBlock, parent: PowBlock) -> bool: + is_total_difficulty_reached = block.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY + is_parent_total_difficulty_valid = parent.total_difficulty < config.TERMINAL_TOTAL_DIFFICULTY + return is_total_difficulty_reached and is_parent_total_difficulty_valid + + +def validate_merge_block(block: BeaconBlock) -> None: + """ + Check the parent PoW block of execution payload is a valid terminal PoW block. + + Note: Unavailable PoW block(s) may later become available, + and a client software MAY delay a call to ``validate_merge_block`` + until the PoW block(s) become available. + """ + if config.TERMINAL_BLOCK_HASH != Hash32(): + # If `config.TERMINAL_BLOCK_HASH` is used as an override, the activation epoch must be reached. + assert compute_epoch_at_slot(block.slot) >= config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH + assert block.body.execution_payload.parent_hash == config.TERMINAL_BLOCK_HASH + return + + pow_block = get_pow_block(block.body.execution_payload.parent_hash) + # Check if `pow_block` is available + assert pow_block is not None + pow_parent = get_pow_block(pow_block.parent_hash) + # Check if `pow_parent` is available + assert pow_parent is not None + # Check if `pow_block` is a valid terminal PoW block + assert is_valid_terminal_pow_block(pow_block, pow_parent) + + +def get_pow_block_at_terminal_total_difficulty(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlock]: + # `pow_chain` abstractly represents all blocks in the PoW chain + for block in pow_chain.values(): + block_reached_ttd = block.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY + if block_reached_ttd: + # If genesis block, no parent exists so reaching TTD alone qualifies as valid terminal block + if block.parent_hash == Hash32(): + return block + parent = pow_chain[block.parent_hash] + parent_reached_ttd = parent.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY + if not parent_reached_ttd: + return block + + return None + + +def get_terminal_pow_block(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlock]: + if config.TERMINAL_BLOCK_HASH != Hash32(): + # Terminal block hash override takes precedence over terminal total difficulty + if config.TERMINAL_BLOCK_HASH in pow_chain: + return pow_chain[config.TERMINAL_BLOCK_HASH] + else: + return None + + return get_pow_block_at_terminal_total_difficulty(pow_chain) + + +def prepare_execution_payload(state: BeaconState, + pow_chain: Dict[Hash32, PowBlock], + finalized_block_hash: Hash32, + suggested_fee_recipient: ExecutionAddress, + execution_engine: ExecutionEngine) -> Optional[PayloadId]: + if not is_merge_transition_complete(state): + is_terminal_block_hash_set = config.TERMINAL_BLOCK_HASH != Hash32() + is_activation_epoch_reached = get_current_epoch(state) >= config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH + if is_terminal_block_hash_set and not is_activation_epoch_reached: + # Terminal block hash is set but activation epoch is not yet reached, no prepare payload call is needed + return None + + terminal_pow_block = get_terminal_pow_block(pow_chain) + if terminal_pow_block is None: + # Pre-merge, no prepare payload call is needed + return None + # Signify merge via producing on top of the terminal PoW block + parent_hash = terminal_pow_block.block_hash + else: + # Post-merge, normal payload + parent_hash = state.latest_execution_payload_header.block_hash + + # Set the forkchoice head and initiate the payload build process + payload_attributes = PayloadAttributes( + timestamp=compute_timestamp_at_slot(state, state.slot), + random=get_randao_mix(state, get_current_epoch(state)), + suggested_fee_recipient=suggested_fee_recipient, + ) + return execution_engine.notify_forkchoice_updated(parent_hash, finalized_block_hash, payload_attributes) + + +def get_execution_payload(payload_id: Optional[PayloadId], execution_engine: ExecutionEngine) -> ExecutionPayload: + if payload_id is None: + # Pre-merge, empty payload + return ExecutionPayload() + else: + return execution_engine.get_payload(payload_id) + + +def withdraw(state: BeaconState, index: ValidatorIndex, amount: Gwei) -> None: + # Decrease the validator's balance + decrease_balance(state, index, amount) + # Create a corresponding withdrawal receipt + receipt = WithdrawalReceipt( + index=WithdrawalReceiptIndex(len(state.withdrawal_receipts)), + address=state.validators[index].withdrawal_credentials[12:], + amount=amount, + ) + state.withdrawal_receipts.append(receipt) + + +def is_withdrawable_validator(validator: Validator, epoch: Epoch) -> bool: + """ + Check if ``validator`` is withdrawable. + """ + return validator.withdrawable_epoch <= epoch + + +def process_withdrawals(state: BeaconState) -> None: + current_epoch = get_current_epoch(state) + for index, validator in enumerate(state.validators): + balance = state.balances[index] + is_balance_nonzero = state.balances[index] == 0 + is_eth1_withdrawal_prefix = validator.withdrawal_credentials[0] != ETH1_ADDRESS_WITHDRAWAL_PREFIX + if is_balance_nonzero and is_eth1_withdrawal_prefix and is_withdrawable_validator(validator, current_epoch): + withdraw(state, ValidatorIndex(index), balance) + + +def upgrade_to_capella(pre: merge.BeaconState) -> BeaconState: + epoch = merge.get_current_epoch(pre) + post = BeaconState( + # Versioning + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + current_version=config.CAPELLA_FORK_VERSION, + epoch=epoch, + ), + # History + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + # Eth1 + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + # Registry + validators=pre.validators, + balances=pre.balances, + # Randomness + randao_mixes=pre.randao_mixes, + # Slashings + slashings=pre.slashings, + # Participation + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + # Finality + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + # Inactivity + inactivity_scores=pre.inactivity_scores, + # Sync + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + # Execution-layer + latest_execution_payload_header=pre.latest_execution_payload_header, + # Withdrawals + withdrawal_receipts=[], + ) + + return post + + +def get_eth1_data(block: Eth1Block) -> Eth1Data: + """ + A stub function return mocking Eth1Data. + """ + return Eth1Data( + deposit_root=block.deposit_root, + deposit_count=block.deposit_count, + block_hash=hash_tree_root(block)) + + +def cache_this(key_fn, value_fn, lru_size): # type: ignore + cache_dict = LRU(size=lru_size) + + def wrapper(*args, **kw): # type: ignore + key = key_fn(*args, **kw) + nonlocal cache_dict + if key not in cache_dict: + cache_dict[key] = value_fn(*args, **kw) + return cache_dict[key] + return wrapper + + +_compute_shuffled_index = compute_shuffled_index +compute_shuffled_index = cache_this( + lambda index, index_count, seed: (index, index_count, seed), + _compute_shuffled_index, lru_size=SLOTS_PER_EPOCH * 3) + +_get_total_active_balance = get_total_active_balance +get_total_active_balance = cache_this( + lambda state: (state.validators.hash_tree_root(), compute_epoch_at_slot(state.slot)), + _get_total_active_balance, lru_size=10) + +_get_base_reward = get_base_reward +get_base_reward = cache_this( + lambda state, index: (state.validators.hash_tree_root(), state.slot, index), + _get_base_reward, lru_size=2048) + +_get_committee_count_per_slot = get_committee_count_per_slot +get_committee_count_per_slot = cache_this( + lambda state, epoch: (state.validators.hash_tree_root(), epoch), + _get_committee_count_per_slot, lru_size=SLOTS_PER_EPOCH * 3) + +_get_active_validator_indices = get_active_validator_indices +get_active_validator_indices = cache_this( + lambda state, epoch: (state.validators.hash_tree_root(), epoch), + _get_active_validator_indices, lru_size=3) + +_get_beacon_committee = get_beacon_committee +get_beacon_committee = cache_this( + lambda state, slot, index: (state.validators.hash_tree_root(), state.randao_mixes.hash_tree_root(), slot, index), + _get_beacon_committee, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3) + +_get_matching_target_attestations = get_matching_target_attestations +get_matching_target_attestations = cache_this( + lambda state, epoch: (state.hash_tree_root(), epoch), + _get_matching_target_attestations, lru_size=10) + +_get_matching_head_attestations = get_matching_head_attestations +get_matching_head_attestations = cache_this( + lambda state, epoch: (state.hash_tree_root(), epoch), + _get_matching_head_attestations, lru_size=10) + +_get_attesting_indices = get_attesting_indices +get_attesting_indices = cache_this( + lambda state, data, bits: ( + state.randao_mixes.hash_tree_root(), + state.validators.hash_tree_root(), data.hash_tree_root(), bits.hash_tree_root() + ), + _get_attesting_indices, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3) + + +def get_generalized_index(ssz_class: Any, *path: Sequence[PyUnion[int, SSZVariableName]]) -> GeneralizedIndex: + ssz_path = Path(ssz_class) + for item in path: + ssz_path = ssz_path / item + return GeneralizedIndex(ssz_path.gindex()) + + +ExecutionState = Any + + +def get_pow_block(hash: Bytes32) -> Optional[PowBlock]: + return PowBlock(block_hash=hash, parent_hash=Bytes32(), total_difficulty=uint256(0)) + + +def get_execution_state(execution_state_root: Bytes32) -> ExecutionState: + pass + + +def get_pow_chain_head() -> PowBlock: + pass + + +class NoopExecutionEngine(ExecutionEngine): + + def execute_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: + return True + + def notify_forkchoice_updated(self: ExecutionEngine, + head_block_hash: Hash32, + finalized_block_hash: Hash32, + payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: + pass + + def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayload: + raise NotImplementedError("no default block production") + + +EXECUTION_ENGINE = NoopExecutionEngine() + + +assert FINALIZED_ROOT_INDEX == get_generalized_index(BeaconState, 'finalized_checkpoint', 'root') +assert NEXT_SYNC_COMMITTEE_INDEX == get_generalized_index(BeaconState, 'next_sync_committee') diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 184c0d6098..7fbd3ff76d 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -5,12 +5,14 @@ from eth2spec.phase0 import mainnet as spec_phase0_mainnet, minimal as spec_phase0_minimal from eth2spec.altair import mainnet as spec_altair_mainnet, minimal as spec_altair_minimal from eth2spec.merge import mainnet as spec_merge_mainnet, minimal as spec_merge_minimal +from eth2spec.capella import mainnet as spec_capella_mainnet, minimal as spec_capella_minimal from eth2spec.utils import bls from .exceptions import SkippedTest from .helpers.constants import ( - PHASE0, ALTAIR, MERGE, MINIMAL, MAINNET, - ALL_PHASES, FORKS_BEFORE_ALTAIR, FORKS_BEFORE_MERGE, + PHASE0, ALTAIR, MERGE, CAPELLA, + MINIMAL, MAINNET, + ALL_PHASES, FORKS_BEFORE_ALTAIR, FORKS_BEFORE_MERGE, FORKS_BEFORE_CAPELLA, ALL_FORK_UPGRADES, ) from .helpers.typing import SpecForkName, PresetBaseName @@ -56,6 +58,10 @@ class SpecMerge(Spec): ... +class SpecCapella(Spec): + ... + + @dataclass(frozen=True) class ForkMeta: pre_fork_name: str @@ -68,11 +74,13 @@ class ForkMeta: PHASE0: spec_phase0_minimal, ALTAIR: spec_altair_minimal, MERGE: spec_merge_minimal, + CAPELLA: spec_capella_minimal, }, MAINNET: { PHASE0: spec_phase0_mainnet, ALTAIR: spec_altair_mainnet, MERGE: spec_merge_mainnet, + CAPELLA: spec_capella_mainnet, }, } @@ -81,6 +89,7 @@ class SpecForks(TypedDict, total=False): PHASE0: SpecPhase0 ALTAIR: SpecAltair MERGE: SpecMerge + CAPELLA: SpecCapella def _prepare_state(balances_fn: Callable[[Any], Sequence[int]], threshold_fn: Callable[[Any], int], @@ -511,8 +520,13 @@ def is_post_merge(spec): return spec.fork not in FORKS_BEFORE_MERGE +def is_post_capella(spec): + return spec.fork not in FORKS_BEFORE_CAPELLA + + with_altair_and_later = with_all_phases_except([PHASE0]) with_merge_and_later = with_all_phases_except([PHASE0, ALTAIR]) +with_capella_and_later = with_all_phases_except([PHASE0, ALTAIR, MERGE]) def only_generator(reason): diff --git a/tests/core/pyspec/eth2spec/test/helpers/capella/fork.py b/tests/core/pyspec/eth2spec/test/helpers/capella/fork.py new file mode 100644 index 0000000000..f5905178e1 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/capella/fork.py @@ -0,0 +1,47 @@ +CAPELLA_FORK_TEST_META_TAGS = { + 'fork': 'capella', +} + + +def run_fork_test(post_spec, pre_state): + yield 'pre', pre_state + + post_state = post_spec.upgrade_to_capella(pre_state) + + # Stable fields + stable_fields = [ + 'genesis_time', 'genesis_validators_root', 'slot', + # History + 'latest_block_header', 'block_roots', 'state_roots', 'historical_roots', + # Eth1 + 'eth1_data', 'eth1_data_votes', 'eth1_deposit_index', + # Registry + 'validators', 'balances', + # Randomness + 'randao_mixes', + # Slashings + 'slashings', + # Participation + 'previous_epoch_participation', 'current_epoch_participation', + # Finality + 'justification_bits', 'previous_justified_checkpoint', 'current_justified_checkpoint', 'finalized_checkpoint', + # Inactivity + 'inactivity_scores', + # Sync + 'current_sync_committee', 'next_sync_committee', + # Execution + 'latest_execution_payload_header', + ] + for field in stable_fields: + assert getattr(pre_state, field) == getattr(post_state, field) + + # Modified fields + modified_fields = ['fork'] + for field in modified_fields: + assert getattr(pre_state, field) != getattr(post_state, field) + + assert pre_state.fork.current_version == post_state.fork.previous_version + assert post_state.fork.current_version == post_spec.config.CAPELLA_FORK_VERSION + assert post_state.fork.epoch == post_spec.get_current_epoch(post_state) + + yield 'post', post_state diff --git a/tests/core/pyspec/eth2spec/test/helpers/constants.py b/tests/core/pyspec/eth2spec/test/helpers/constants.py index bb8f49cbc9..47a008773c 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/constants.py +++ b/tests/core/pyspec/eth2spec/test/helpers/constants.py @@ -8,6 +8,7 @@ PHASE0 = SpecForkName('phase0') ALTAIR = SpecForkName('altair') MERGE = SpecForkName('merge') +CAPELLA = SpecForkName('capella') # Experimental phases (not included in default "ALL_PHASES"): SHARDING = SpecForkName('sharding') @@ -15,16 +16,18 @@ DAS = SpecForkName('das') # The forks that pytest runs with. -ALL_PHASES = (PHASE0, ALTAIR, MERGE) +ALL_PHASES = (PHASE0, ALTAIR, MERGE, CAPELLA) # The forks that output to the test vectors. TESTGEN_FORKS = (PHASE0, ALTAIR, MERGE) FORKS_BEFORE_ALTAIR = (PHASE0,) FORKS_BEFORE_MERGE = (PHASE0, ALTAIR) +FORKS_BEFORE_CAPELLA = (PHASE0, ALTAIR, MERGE) ALL_FORK_UPGRADES = { # pre_fork_name: post_fork_name PHASE0: ALTAIR, ALTAIR: MERGE, + MERGE: CAPELLA, } ALL_PRE_POST_FORKS = ALL_FORK_UPGRADES.items() AFTER_MERGE_UPGRADES = {key: value for key, value in ALL_FORK_UPGRADES.items() if key not in FORKS_BEFORE_ALTAIR} diff --git a/tests/core/pyspec/eth2spec/withdrawals/__init__.py b/tests/core/pyspec/eth2spec/withdrawals/__init__.py new file mode 100644 index 0000000000..91b4a68537 --- /dev/null +++ b/tests/core/pyspec/eth2spec/withdrawals/__init__.py @@ -0,0 +1 @@ +from . import mainnet as spec # noqa:F401 diff --git a/tests/core/pyspec/eth2spec/withdrawals/mainnet.py b/tests/core/pyspec/eth2spec/withdrawals/mainnet.py new file mode 100644 index 0000000000..d7c9ce43f8 --- /dev/null +++ b/tests/core/pyspec/eth2spec/withdrawals/mainnet.py @@ -0,0 +1,3018 @@ +from lru import LRU +from dataclasses import ( + dataclass, + field, +) +from typing import ( + Any, Callable, Dict, Set, Sequence, Tuple, Optional, TypeVar, NamedTuple +) + +from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy, uint_to_bytes +from eth2spec.utils.ssz.ssz_typing import ( + View, boolean, Container, List, Vector, uint8, uint32, uint64, + Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist) +from eth2spec.utils.ssz.ssz_typing import Bitvector # noqa: F401 +from eth2spec.utils import bls +from eth2spec.utils.hash_function import hash + + +from typing import NewType, Union as PyUnion + +from eth2spec.phase0 import mainnet as phase0 +from eth2spec.utils.ssz.ssz_typing import Path + +from typing import Protocol +from eth2spec.altair import mainnet as altair +from eth2spec.utils.ssz.ssz_typing import Bytes8, Bytes20, ByteList, ByteVector, uint256 + +SSZObject = TypeVar('SSZObject', bound=View) + + +SSZVariableName = str +GeneralizedIndex = NewType('GeneralizedIndex', int) + + +fork = 'withdrawals' + + +MAX_BYTES_PER_TRANSACTION = uint64(2**30) + + +class Slot(uint64): + pass + + +class Epoch(uint64): + pass + + +class CommitteeIndex(uint64): + pass + + +class ValidatorIndex(uint64): + pass + + +class Gwei(uint64): + pass + + +class Root(Bytes32): + pass + + +class Hash32(Bytes32): + pass + + +class Version(Bytes4): + pass + + +class DomainType(Bytes4): + pass + + +class ForkDigest(Bytes4): + pass + + +class Domain(Bytes32): + pass + + +class BLSPubkey(Bytes48): + pass + + +class BLSSignature(Bytes96): + pass + + +class Ether(uint64): + pass + + +class ParticipationFlags(uint8): + pass + + +class ExecutionAddress(Bytes20): + pass + + +class PayloadId(Bytes8): + pass + + +class WithdrawalReceiptIndex(uint64): + pass + + +Transaction = ByteList[MAX_BYTES_PER_TRANSACTION] + + +def ceillog2(x: int) -> uint64: + if x < 1: + raise ValueError(f"ceillog2 accepts only positive values, x={x}") + return uint64((x - 1).bit_length()) + + +def floorlog2(x: int) -> uint64: + if x < 1: + raise ValueError(f"floorlog2 accepts only positive values, x={x}") + return uint64(x.bit_length() - 1) + + +FINALIZED_ROOT_INDEX = GeneralizedIndex(105) +NEXT_SYNC_COMMITTEE_INDEX = GeneralizedIndex(55) + +# Constant vars +GENESIS_SLOT = Slot(0) +GENESIS_EPOCH = Epoch(0) +FAR_FUTURE_EPOCH = Epoch(2**64 - 1) +BASE_REWARDS_PER_EPOCH = uint64(4) +DEPOSIT_CONTRACT_TREE_DEPTH = uint64(2**5) +JUSTIFICATION_BITS_LENGTH = uint64(4) +ENDIANNESS = 'little' +BLS_WITHDRAWAL_PREFIX = Bytes1('0x00') +ETH1_ADDRESS_WITHDRAWAL_PREFIX = Bytes1('0x01') +DOMAIN_BEACON_PROPOSER = DomainType('0x00000000') +DOMAIN_BEACON_ATTESTER = DomainType('0x01000000') +DOMAIN_RANDAO = DomainType('0x02000000') +DOMAIN_DEPOSIT = DomainType('0x03000000') +DOMAIN_VOLUNTARY_EXIT = DomainType('0x04000000') +DOMAIN_SELECTION_PROOF = DomainType('0x05000000') +DOMAIN_AGGREGATE_AND_PROOF = DomainType('0x06000000') +INTERVALS_PER_SLOT = uint64(3) +TARGET_AGGREGATORS_PER_COMMITTEE = 2**4 +RANDOM_SUBNETS_PER_VALIDATOR = 2**0 +EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION = 2**8 +ATTESTATION_SUBNET_COUNT = 64 +ETH_TO_GWEI = uint64(10**9) +SAFETY_DECAY = uint64(10) +TIMELY_SOURCE_FLAG_INDEX = 0 +TIMELY_TARGET_FLAG_INDEX = 1 +TIMELY_HEAD_FLAG_INDEX = 2 +TIMELY_SOURCE_WEIGHT = uint64(14) +TIMELY_TARGET_WEIGHT = uint64(26) +TIMELY_HEAD_WEIGHT = uint64(14) +SYNC_REWARD_WEIGHT = uint64(2) +PROPOSER_WEIGHT = uint64(8) +WEIGHT_DENOMINATOR = uint64(64) +DOMAIN_SYNC_COMMITTEE = DomainType('0x07000000') +DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF = DomainType('0x08000000') +DOMAIN_CONTRIBUTION_AND_PROOF = DomainType('0x09000000') +PARTICIPATION_FLAG_WEIGHTS = [TIMELY_SOURCE_WEIGHT, TIMELY_TARGET_WEIGHT, TIMELY_HEAD_WEIGHT] +G2_POINT_AT_INFINITY = BLSSignature(b'\xc0' + b'\x00' * 95) +TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE = 2**4 +SYNC_COMMITTEE_SUBNET_COUNT = 4 +WITHDRAWAL_RECEIPT_LIMIT = uint64(2**40) + +# Preset vars +MAX_COMMITTEES_PER_SLOT = uint64(64) +TARGET_COMMITTEE_SIZE = uint64(128) +MAX_VALIDATORS_PER_COMMITTEE = uint64(2048) +SHUFFLE_ROUND_COUNT = uint64(90) +HYSTERESIS_QUOTIENT = uint64(4) +HYSTERESIS_DOWNWARD_MULTIPLIER = uint64(1) +HYSTERESIS_UPWARD_MULTIPLIER = uint64(5) +MIN_DEPOSIT_AMOUNT = Gwei(1000000000) +MAX_EFFECTIVE_BALANCE = Gwei(32000000000) +EFFECTIVE_BALANCE_INCREMENT = Gwei(1000000000) +MIN_ATTESTATION_INCLUSION_DELAY = uint64(1) +SLOTS_PER_EPOCH = uint64(32) +MIN_SEED_LOOKAHEAD = uint64(1) +MAX_SEED_LOOKAHEAD = uint64(4) +MIN_EPOCHS_TO_INACTIVITY_PENALTY = uint64(4) +EPOCHS_PER_ETH1_VOTING_PERIOD = uint64(64) +SLOTS_PER_HISTORICAL_ROOT = uint64(8192) +EPOCHS_PER_HISTORICAL_VECTOR = uint64(65536) +EPOCHS_PER_SLASHINGS_VECTOR = uint64(8192) +HISTORICAL_ROOTS_LIMIT = uint64(16777216) +VALIDATOR_REGISTRY_LIMIT = uint64(1099511627776) +BASE_REWARD_FACTOR = uint64(64) +WHISTLEBLOWER_REWARD_QUOTIENT = uint64(512) +PROPOSER_REWARD_QUOTIENT = uint64(8) +INACTIVITY_PENALTY_QUOTIENT = uint64(67108864) +MIN_SLASHING_PENALTY_QUOTIENT = uint64(128) +PROPORTIONAL_SLASHING_MULTIPLIER = uint64(1) +MAX_PROPOSER_SLASHINGS = 16 +MAX_ATTESTER_SLASHINGS = 2 +MAX_ATTESTATIONS = 128 +MAX_DEPOSITS = 16 +MAX_VOLUNTARY_EXITS = 16 +SAFE_SLOTS_TO_UPDATE_JUSTIFIED = 8 +INACTIVITY_PENALTY_QUOTIENT_ALTAIR = uint64(50331648) +MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR = uint64(64) +PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR = uint64(2) +SYNC_COMMITTEE_SIZE = uint64(512) +EPOCHS_PER_SYNC_COMMITTEE_PERIOD = uint64(256) +MIN_SYNC_COMMITTEE_PARTICIPANTS = 1 +MAX_BYTES_PER_TRANSACTION = uint64(1073741824) +MAX_TRANSACTIONS_PER_PAYLOAD = uint64(1048576) +BYTES_PER_LOGS_BLOOM = uint64(256) +MAX_EXTRA_DATA_BYTES = 32 +INACTIVITY_PENALTY_QUOTIENT_MERGE = uint64(16777216) +MIN_SLASHING_PENALTY_QUOTIENT_MERGE = uint64(32) +PROPORTIONAL_SLASHING_MULTIPLIER_MERGE = uint64(3) + + +class Configuration(NamedTuple): + PRESET_BASE: str + MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: uint64 + MIN_GENESIS_TIME: uint64 + GENESIS_FORK_VERSION: Version + GENESIS_DELAY: uint64 + SECONDS_PER_SLOT: uint64 + SECONDS_PER_ETH1_BLOCK: uint64 + MIN_VALIDATOR_WITHDRAWABILITY_DELAY: uint64 + SHARD_COMMITTEE_PERIOD: uint64 + ETH1_FOLLOW_DISTANCE: uint64 + EJECTION_BALANCE: Gwei + MIN_PER_EPOCH_CHURN_LIMIT: uint64 + CHURN_LIMIT_QUOTIENT: uint64 + PROPOSER_SCORE_BOOST: uint64 + INACTIVITY_SCORE_BIAS: uint64 + INACTIVITY_SCORE_RECOVERY_RATE: uint64 + ALTAIR_FORK_VERSION: Version + ALTAIR_FORK_EPOCH: Epoch + TERMINAL_TOTAL_DIFFICULTY: int + TERMINAL_BLOCK_HASH: Hash32 + TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: int + MERGE_FORK_VERSION: Version + MERGE_FORK_EPOCH: Epoch + + +config = Configuration( + PRESET_BASE="mainnet", + MIN_GENESIS_ACTIVE_VALIDATOR_COUNT=uint64(16384), + MIN_GENESIS_TIME=uint64(1606824000), + GENESIS_FORK_VERSION=Version('0x00000000'), + GENESIS_DELAY=uint64(604800), + SECONDS_PER_SLOT=uint64(12), + SECONDS_PER_ETH1_BLOCK=uint64(14), + MIN_VALIDATOR_WITHDRAWABILITY_DELAY=uint64(256), + SHARD_COMMITTEE_PERIOD=uint64(256), + ETH1_FOLLOW_DISTANCE=uint64(2048), + EJECTION_BALANCE=Gwei(16000000000), + MIN_PER_EPOCH_CHURN_LIMIT=uint64(4), + CHURN_LIMIT_QUOTIENT=uint64(65536), + PROPOSER_SCORE_BOOST=uint64(70), + INACTIVITY_SCORE_BIAS=uint64(4), + INACTIVITY_SCORE_RECOVERY_RATE=uint64(16), + ALTAIR_FORK_VERSION=Version('0x01000000'), + ALTAIR_FORK_EPOCH=Epoch(74240), + TERMINAL_TOTAL_DIFFICULTY=115792089237316195423570985008687907853269984665640564039457584007913129638912, + TERMINAL_BLOCK_HASH=Hash32('0x0000000000000000000000000000000000000000000000000000000000000000'), + TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH=18446744073709551615, + MERGE_FORK_VERSION=Version('0x02000000'), + MERGE_FORK_EPOCH=Epoch(18446744073709551615), +) + + +class Fork(Container): + previous_version: Version + current_version: Version + epoch: Epoch # Epoch of latest fork + + +class ForkData(Container): + current_version: Version + genesis_validators_root: Root + + +class Checkpoint(Container): + epoch: Epoch + root: Root + + +class Validator(Container): + pubkey: BLSPubkey + withdrawal_credentials: Bytes32 # Commitment to pubkey for withdrawals + effective_balance: Gwei # Balance at stake + slashed: boolean + # Status epochs + activation_eligibility_epoch: Epoch # When criteria for activation were met + activation_epoch: Epoch + exit_epoch: Epoch + withdrawable_epoch: Epoch # When validator can withdraw funds + + +class AttestationData(Container): + slot: Slot + index: CommitteeIndex + # LMD GHOST vote + beacon_block_root: Root + # FFG vote + source: Checkpoint + target: Checkpoint + + +class IndexedAttestation(Container): + attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] + data: AttestationData + signature: BLSSignature + + +class PendingAttestation(Container): + aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] + data: AttestationData + inclusion_delay: Slot + proposer_index: ValidatorIndex + + +class Eth1Data(Container): + deposit_root: Root + deposit_count: uint64 + block_hash: Hash32 + + +class HistoricalBatch(Container): + block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + + +class DepositMessage(Container): + pubkey: BLSPubkey + withdrawal_credentials: Bytes32 + amount: Gwei + + +class DepositData(Container): + pubkey: BLSPubkey + withdrawal_credentials: Bytes32 + amount: Gwei + signature: BLSSignature # Signing over DepositMessage + + +class BeaconBlockHeader(Container): + slot: Slot + proposer_index: ValidatorIndex + parent_root: Root + state_root: Root + body_root: Root + + +class SigningData(Container): + object_root: Root + domain: Domain + + +class AttesterSlashing(Container): + attestation_1: IndexedAttestation + attestation_2: IndexedAttestation + + +class Attestation(Container): + aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] + data: AttestationData + signature: BLSSignature + + +class Deposit(Container): + proof: Vector[Bytes32, DEPOSIT_CONTRACT_TREE_DEPTH + 1] # Merkle path to deposit root + data: DepositData + + +class VoluntaryExit(Container): + epoch: Epoch # Earliest epoch when voluntary exit can be processed + validator_index: ValidatorIndex + + +class SignedVoluntaryExit(Container): + message: VoluntaryExit + signature: BLSSignature + + +class SignedBeaconBlockHeader(Container): + message: BeaconBlockHeader + signature: BLSSignature + + +class ProposerSlashing(Container): + signed_header_1: SignedBeaconBlockHeader + signed_header_2: SignedBeaconBlockHeader + + +class Eth1Block(Container): + timestamp: uint64 + deposit_root: Root + deposit_count: uint64 + # All other eth1 block fields + + +class AggregateAndProof(Container): + aggregator_index: ValidatorIndex + aggregate: Attestation + selection_proof: BLSSignature + + +class SignedAggregateAndProof(Container): + message: AggregateAndProof + signature: BLSSignature + + +class SyncAggregate(Container): + sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] + sync_committee_signature: BLSSignature + + +class SyncCommittee(Container): + pubkeys: Vector[BLSPubkey, SYNC_COMMITTEE_SIZE] + aggregate_pubkey: BLSPubkey + + +class SyncCommitteeMessage(Container): + # Slot to which this contribution pertains + slot: Slot + # Block root for this signature + beacon_block_root: Root + # Index of the validator that produced this signature + validator_index: ValidatorIndex + # Signature by the validator over the block root of `slot` + signature: BLSSignature + + +class SyncCommitteeContribution(Container): + # Slot to which this contribution pertains + slot: Slot + # Block root for this contribution + beacon_block_root: Root + # The subcommittee this contribution pertains to out of the broader sync committee + subcommittee_index: uint64 + # A bit is set if a signature from the validator at the corresponding + # index in the subcommittee is present in the aggregate `signature`. + aggregation_bits: Bitvector[SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT] + # Signature by the validator(s) over the block root of `slot` + signature: BLSSignature + + +class ContributionAndProof(Container): + aggregator_index: ValidatorIndex + contribution: SyncCommitteeContribution + selection_proof: BLSSignature + + +class SignedContributionAndProof(Container): + message: ContributionAndProof + signature: BLSSignature + + +class SyncAggregatorSelectionData(Container): + slot: Slot + subcommittee_index: uint64 + + +class LightClientSnapshot(Container): + # Beacon block header + header: BeaconBlockHeader + # Sync committees corresponding to the header + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + + +class LightClientUpdate(Container): + # Update beacon block header + header: BeaconBlockHeader + # Next sync committee corresponding to the header + next_sync_committee: SyncCommittee + next_sync_committee_branch: Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_INDEX)] + # Finality proof for the update header + finality_header: BeaconBlockHeader + finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX)] + # Sync committee aggregate signature + sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] + sync_committee_signature: BLSSignature + # Fork version for the aggregate signature + fork_version: Version + + +class ExecutionPayload(Container): + # Execution block header fields + parent_hash: Hash32 + fee_recipient: ExecutionAddress # 'beneficiary' in the yellow paper + state_root: Bytes32 + receipt_root: Bytes32 # 'receipts root' in the yellow paper + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + random: Bytes32 # 'difficulty' in the yellow paper + block_number: uint64 # 'number' in the yellow paper + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + # Extra payload fields + block_hash: Hash32 # Hash of execution block + transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] + + +class BeaconBlockBody(Container): + randao_reveal: BLSSignature + eth1_data: Eth1Data # Eth1 data vote + graffiti: Bytes32 # Arbitrary data + # Operations + proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] + attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] + attestations: List[Attestation, MAX_ATTESTATIONS] + deposits: List[Deposit, MAX_DEPOSITS] + voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] + sync_aggregate: SyncAggregate + # Execution + execution_payload: ExecutionPayload # [New in Merge] + + +class BeaconBlock(Container): + slot: Slot + proposer_index: ValidatorIndex + parent_root: Root + state_root: Root + body: BeaconBlockBody + + +class SignedBeaconBlock(Container): + message: BeaconBlock + signature: BLSSignature + + +class ExecutionPayloadHeader(Container): + # Execution block header fields + parent_hash: Hash32 + fee_recipient: ExecutionAddress + state_root: Bytes32 + receipt_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + random: Bytes32 + block_number: uint64 + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + # Extra payload fields + block_hash: Hash32 # Hash of execution block + transactions_root: Root + + +class PowBlock(Container): + block_hash: Hash32 + parent_hash: Hash32 + total_difficulty: uint256 + + +class WithdrawalReceipt(Container): + index: WithdrawalReceiptIndex + address: ExecutionAddress + amount: Gwei + + +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[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + current_epoch_participation: List[ParticipationFlags, 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 + # Inactivity + inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] + # Sync + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + # Execution + latest_execution_payload_header: ExecutionPayloadHeader + # Withdrawals + withdrawal_receipts: List[WithdrawalReceipt, WITHDRAWAL_RECEIPT_LIMIT] # [New in Withdrawals] + + +@dataclass(eq=True, frozen=True) +class LatestMessage(object): + epoch: Epoch + root: Root + + +@dataclass +class Store(object): + time: uint64 + genesis_time: uint64 + justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + best_justified_checkpoint: Checkpoint + proposer_boost_root: Root + blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) + block_states: Dict[Root, BeaconState] = field(default_factory=dict) + checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict) + latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict) + + +@dataclass +class LightClientStore(object): + snapshot: LightClientSnapshot + valid_updates: Set[LightClientUpdate] + + +@dataclass +class PayloadAttributes(object): + timestamp: uint64 + random: Bytes32 + suggested_fee_recipient: ExecutionAddress + + +class ExecutionEngine(Protocol): + + def execute_payload(self, execution_payload: ExecutionPayload) -> bool: + """ + Return ``True`` if and only if ``execution_payload`` is valid with respect to ``self.execution_state``. + """ + ... + + def notify_forkchoice_updated(self, + head_block_hash: Hash32, + finalized_block_hash: Hash32, + payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: + ... + + def get_payload(self, payload_id: PayloadId) -> ExecutionPayload: + """ + Return ``execution_payload`` object. + """ + ... + + +def integer_squareroot(n: uint64) -> uint64: + """ + Return the largest integer ``x`` such that ``x**2 <= n``. + """ + x = n + y = (x + 1) // 2 + while y < x: + x = y + y = (x + n // x) // 2 + return x + + +def xor(bytes_1: Bytes32, bytes_2: Bytes32) -> Bytes32: + """ + Return the exclusive-or of two 32-byte strings. + """ + return Bytes32(a ^ b for a, b in zip(bytes_1, bytes_2)) + + +def bytes_to_uint64(data: bytes) -> uint64: + """ + Return the integer deserialization of ``data`` interpreted as ``ENDIANNESS``-endian. + """ + return uint64(int.from_bytes(data, ENDIANNESS)) + + +def is_active_validator(validator: Validator, epoch: Epoch) -> bool: + """ + Check if ``validator`` is active. + """ + return validator.activation_epoch <= epoch < validator.exit_epoch + + +def is_eligible_for_activation_queue(validator: Validator) -> bool: + """ + Check if ``validator`` is eligible to be placed into the activation queue. + """ + return ( + validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH + and validator.effective_balance == MAX_EFFECTIVE_BALANCE + ) + + +def is_eligible_for_activation(state: BeaconState, validator: Validator) -> bool: + """ + Check if ``validator`` is eligible for activation. + """ + return ( + # Placement in queue is finalized + validator.activation_eligibility_epoch <= state.finalized_checkpoint.epoch + # Has not yet been activated + and validator.activation_epoch == FAR_FUTURE_EPOCH + ) + + +def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool: + """ + Check if ``validator`` is slashable. + """ + return (not validator.slashed) and (validator.activation_epoch <= epoch < validator.withdrawable_epoch) + + +def is_slashable_attestation_data(data_1: AttestationData, data_2: AttestationData) -> bool: + """ + Check if ``data_1`` and ``data_2`` are slashable according to Casper FFG rules. + """ + return ( + # Double vote + (data_1 != data_2 and data_1.target.epoch == data_2.target.epoch) or + # Surround vote + (data_1.source.epoch < data_2.source.epoch and data_2.target.epoch < data_1.target.epoch) + ) + + +def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool: + """ + Check if ``indexed_attestation`` is not empty, has sorted and unique indices and has a valid aggregate signature. + """ + # Verify indices are sorted and unique + indices = indexed_attestation.attesting_indices + if len(indices) == 0 or not indices == sorted(set(indices)): + return False + # Verify aggregate signature + pubkeys = [state.validators[i].pubkey for i in indices] + domain = get_domain(state, DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch) + signing_root = compute_signing_root(indexed_attestation.data, domain) + return bls.FastAggregateVerify(pubkeys, signing_root, indexed_attestation.signature) + + +def is_valid_merkle_branch(leaf: Bytes32, branch: Sequence[Bytes32], depth: uint64, index: uint64, root: Root) -> bool: + """ + Check if ``leaf`` at ``index`` verifies against the Merkle ``root`` and ``branch``. + """ + value = leaf + for i in range(depth): + if index // (2**i) % 2: + value = hash(branch[i] + value) + else: + value = hash(value + branch[i]) + return value == root + + +def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) -> uint64: + """ + Return the shuffled index corresponding to ``seed`` (and ``index_count``). + """ + assert index < index_count + + # Swap or not (https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf) + # See the 'generalized domain' algorithm on page 3 + for current_round in range(SHUFFLE_ROUND_COUNT): + pivot = bytes_to_uint64(hash(seed + uint_to_bytes(uint8(current_round)))[0:8]) % index_count + flip = (pivot + index_count - index) % index_count + position = max(index, flip) + source = hash( + seed + + uint_to_bytes(uint8(current_round)) + + uint_to_bytes(uint32(position // 256)) + ) + byte = uint8(source[(position % 256) // 8]) + bit = (byte >> (position % 8)) % 2 + index = flip if bit else index + + return index + + +def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex], seed: Bytes32) -> ValidatorIndex: + """ + Return from ``indices`` a random index sampled by effective balance. + """ + assert len(indices) > 0 + MAX_RANDOM_BYTE = 2**8 - 1 + i = uint64(0) + total = uint64(len(indices)) + while True: + candidate_index = indices[compute_shuffled_index(i % total, total, seed)] + random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] + effective_balance = state.validators[candidate_index].effective_balance + if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: + return candidate_index + i += 1 + + +def compute_committee(indices: Sequence[ValidatorIndex], + seed: Bytes32, + index: uint64, + count: uint64) -> Sequence[ValidatorIndex]: + """ + Return the committee corresponding to ``indices``, ``seed``, ``index``, and committee ``count``. + """ + start = (len(indices) * index) // count + end = (len(indices) * uint64(index + 1)) // count + return [indices[compute_shuffled_index(uint64(i), uint64(len(indices)), seed)] for i in range(start, end)] + + +def compute_epoch_at_slot(slot: Slot) -> Epoch: + """ + Return the epoch number at ``slot``. + """ + return Epoch(slot // SLOTS_PER_EPOCH) + + +def compute_start_slot_at_epoch(epoch: Epoch) -> Slot: + """ + Return the start slot of ``epoch``. + """ + return Slot(epoch * SLOTS_PER_EPOCH) + + +def compute_activation_exit_epoch(epoch: Epoch) -> Epoch: + """ + Return the epoch during which validator activations and exits initiated in ``epoch`` take effect. + """ + return Epoch(epoch + 1 + MAX_SEED_LOOKAHEAD) + + +def compute_fork_data_root(current_version: Version, genesis_validators_root: Root) -> Root: + """ + Return the 32-byte fork data root for the ``current_version`` and ``genesis_validators_root``. + This is used primarily in signature domains to avoid collisions across forks/chains. + """ + return hash_tree_root(ForkData( + current_version=current_version, + genesis_validators_root=genesis_validators_root, + )) + + +def compute_fork_digest(current_version: Version, genesis_validators_root: Root) -> ForkDigest: + """ + Return the 4-byte fork digest for the ``current_version`` and ``genesis_validators_root``. + This is a digest primarily used for domain separation on the p2p layer. + 4-bytes suffices for practical separation of forks/chains. + """ + return ForkDigest(compute_fork_data_root(current_version, genesis_validators_root)[:4]) + + +def compute_domain(domain_type: DomainType, fork_version: Version=None, genesis_validators_root: Root=None) -> Domain: + """ + Return the domain for the ``domain_type`` and ``fork_version``. + """ + if fork_version is None: + fork_version = config.GENESIS_FORK_VERSION + if genesis_validators_root is None: + genesis_validators_root = Root() # all bytes zero by default + fork_data_root = compute_fork_data_root(fork_version, genesis_validators_root) + return Domain(domain_type + fork_data_root[:28]) + + +def compute_signing_root(ssz_object: SSZObject, domain: Domain) -> Root: + """ + Return the signing root for the corresponding signing data. + """ + return hash_tree_root(SigningData( + object_root=hash_tree_root(ssz_object), + domain=domain, + )) + + +def get_current_epoch(state: BeaconState) -> Epoch: + """ + Return the current epoch. + """ + return compute_epoch_at_slot(state.slot) + + +def get_previous_epoch(state: BeaconState) -> Epoch: + """` + Return the previous epoch (unless the current epoch is ``GENESIS_EPOCH``). + """ + current_epoch = get_current_epoch(state) + return GENESIS_EPOCH if current_epoch == GENESIS_EPOCH else Epoch(current_epoch - 1) + + +def get_block_root(state: BeaconState, epoch: Epoch) -> Root: + """ + Return the block root at the start of a recent ``epoch``. + """ + return get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch)) + + +def get_block_root_at_slot(state: BeaconState, slot: Slot) -> Root: + """ + Return the block root at a recent ``slot``. + """ + assert slot < state.slot <= slot + SLOTS_PER_HISTORICAL_ROOT + return state.block_roots[slot % SLOTS_PER_HISTORICAL_ROOT] + + +def get_randao_mix(state: BeaconState, epoch: Epoch) -> Bytes32: + """ + Return the randao mix at a recent ``epoch``. + """ + return state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR] + + +def get_active_validator_indices(state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: + """ + Return the sequence of active validator indices at ``epoch``. + """ + return [ValidatorIndex(i) for i, v in enumerate(state.validators) if is_active_validator(v, epoch)] + + +def get_validator_churn_limit(state: BeaconState) -> uint64: + """ + Return the validator churn limit for the current epoch. + """ + active_validator_indices = get_active_validator_indices(state, get_current_epoch(state)) + return max(config.MIN_PER_EPOCH_CHURN_LIMIT, uint64(len(active_validator_indices)) // config.CHURN_LIMIT_QUOTIENT) + + +def get_seed(state: BeaconState, epoch: Epoch, domain_type: DomainType) -> Bytes32: + """ + Return the seed at ``epoch``. + """ + mix = get_randao_mix(state, Epoch(epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD - 1)) # Avoid underflow + return hash(domain_type + uint_to_bytes(epoch) + mix) + + +def get_committee_count_per_slot(state: BeaconState, epoch: Epoch) -> uint64: + """ + Return the number of committees in each slot for the given ``epoch``. + """ + return max(uint64(1), min( + MAX_COMMITTEES_PER_SLOT, + uint64(len(get_active_validator_indices(state, epoch))) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE, + )) + + +def get_beacon_committee(state: BeaconState, slot: Slot, index: CommitteeIndex) -> Sequence[ValidatorIndex]: + """ + Return the beacon committee at ``slot`` for ``index``. + """ + epoch = compute_epoch_at_slot(slot) + committees_per_slot = get_committee_count_per_slot(state, epoch) + return compute_committee( + indices=get_active_validator_indices(state, epoch), + seed=get_seed(state, epoch, DOMAIN_BEACON_ATTESTER), + index=(slot % SLOTS_PER_EPOCH) * committees_per_slot + index, + count=committees_per_slot * SLOTS_PER_EPOCH, + ) + + +def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: + """ + Return the beacon proposer index at the current slot. + """ + epoch = get_current_epoch(state) + seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + uint_to_bytes(state.slot)) + indices = get_active_validator_indices(state, epoch) + return compute_proposer_index(state, indices, seed) + + +def get_total_balance(state: BeaconState, indices: Set[ValidatorIndex]) -> Gwei: + """ + Return the combined effective balance of the ``indices``. + ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. + Math safe up to ~10B ETH, afterwhich this overflows uint64. + """ + return Gwei(max(EFFECTIVE_BALANCE_INCREMENT, sum([state.validators[index].effective_balance for index in indices]))) + + +def get_total_active_balance(state: BeaconState) -> Gwei: + """ + Return the combined effective balance of the active validators. + Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. + """ + return get_total_balance(state, set(get_active_validator_indices(state, get_current_epoch(state)))) + + +def get_domain(state: BeaconState, domain_type: DomainType, epoch: Epoch=None) -> Domain: + """ + Return the signature domain (fork version concatenated with domain type) of a message. + """ + epoch = get_current_epoch(state) if epoch is None else epoch + fork_version = state.fork.previous_version if epoch < state.fork.epoch else state.fork.current_version + return compute_domain(domain_type, fork_version, state.genesis_validators_root) + + +def get_indexed_attestation(state: BeaconState, attestation: Attestation) -> IndexedAttestation: + """ + Return the indexed attestation corresponding to ``attestation``. + """ + attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) + + return IndexedAttestation( + attesting_indices=sorted(attesting_indices), + data=attestation.data, + signature=attestation.signature, + ) + + +def get_attesting_indices(state: BeaconState, + data: AttestationData, + bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Set[ValidatorIndex]: + """ + Return the set of attesting indices corresponding to ``data`` and ``bits``. + """ + committee = get_beacon_committee(state, data.slot, data.index) + return set(index for i, index in enumerate(committee) if bits[i]) + + +def increase_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: + """ + Increase the validator balance at index ``index`` by ``delta``. + """ + state.balances[index] += delta + + +def decrease_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: + """ + Decrease the validator balance at index ``index`` by ``delta``, with underflow protection. + """ + state.balances[index] = 0 if delta > state.balances[index] else state.balances[index] - delta + + +def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: + """ + Initiate the exit of the validator with index ``index``. + """ + # Return if validator already initiated exit + validator = state.validators[index] + if validator.exit_epoch != FAR_FUTURE_EPOCH: + return + + # Compute exit queue epoch + exit_epochs = [v.exit_epoch for v in state.validators if v.exit_epoch != FAR_FUTURE_EPOCH] + exit_queue_epoch = max(exit_epochs + [compute_activation_exit_epoch(get_current_epoch(state))]) + exit_queue_churn = len([v for v in state.validators if v.exit_epoch == exit_queue_epoch]) + if exit_queue_churn >= get_validator_churn_limit(state): + exit_queue_epoch += Epoch(1) + + # Set validator exit epoch and withdrawable epoch + validator.exit_epoch = exit_queue_epoch + validator.withdrawable_epoch = Epoch(validator.exit_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY) + + +def slash_validator(state: BeaconState, + slashed_index: ValidatorIndex, + whistleblower_index: ValidatorIndex=None) -> None: + """ + Slash the validator with index ``slashed_index``. + """ + epoch = get_current_epoch(state) + initiate_validator_exit(state, slashed_index) + validator = state.validators[slashed_index] + validator.slashed = True + validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR)) + state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance + slashing_penalty = validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_MERGE # [Modified in Merge] + decrease_balance(state, slashed_index, slashing_penalty) + + # Apply proposer and whistleblower rewards + proposer_index = get_beacon_proposer_index(state) + if whistleblower_index is None: + whistleblower_index = proposer_index + whistleblower_reward = Gwei(validator.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT) + proposer_reward = Gwei(whistleblower_reward * PROPOSER_WEIGHT // WEIGHT_DENOMINATOR) + increase_balance(state, proposer_index, proposer_reward) + increase_balance(state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward)) + + +def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, + eth1_timestamp: uint64, + deposits: Sequence[Deposit], + execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader() + ) -> BeaconState: + fork = Fork( + previous_version=config.MERGE_FORK_VERSION, # [Modified in Merge] for testing only + current_version=config.MERGE_FORK_VERSION, # [Modified in Merge] + epoch=GENESIS_EPOCH, + ) + state = BeaconState( + genesis_time=eth1_timestamp + config.GENESIS_DELAY, + fork=fork, + eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))), + latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), + randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy + ) + + # Process deposits + leaves = list(map(lambda deposit: deposit.data, deposits)) + for index, deposit in enumerate(deposits): + deposit_data_list = List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[:index + 1]) + state.eth1_data.deposit_root = hash_tree_root(deposit_data_list) + process_deposit(state, deposit) + + # Process activations + for index, validator in enumerate(state.validators): + balance = state.balances[index] + validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) + if validator.effective_balance == MAX_EFFECTIVE_BALANCE: + validator.activation_eligibility_epoch = GENESIS_EPOCH + validator.activation_epoch = GENESIS_EPOCH + + # Set genesis validators root for domain separation and chain versioning + state.genesis_validators_root = hash_tree_root(state.validators) + + # Fill in sync committees + # Note: A duplicate committee is assigned for the current and next committee at genesis + state.current_sync_committee = get_next_sync_committee(state) + state.next_sync_committee = get_next_sync_committee(state) + + # [New in Merge] Initialize the execution payload header + # If empty, will initialize a chain that has not yet gone through the Merge transition + state.latest_execution_payload_header = execution_payload_header + + return state + + +def is_valid_genesis_state(state: BeaconState) -> bool: + if state.genesis_time < config.MIN_GENESIS_TIME: + return False + if len(get_active_validator_indices(state, GENESIS_EPOCH)) < config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: + return False + return True + + +def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool=True) -> None: + block = signed_block.message + # Process slots (including those with no blocks) since block + process_slots(state, block.slot) + # Verify signature + if validate_result: + assert verify_block_signature(state, signed_block) + # Process block + process_block(state, block) + # Verify state root + if validate_result: + assert block.state_root == hash_tree_root(state) + + +def verify_block_signature(state: BeaconState, signed_block: SignedBeaconBlock) -> bool: + proposer = state.validators[signed_block.message.proposer_index] + signing_root = compute_signing_root(signed_block.message, get_domain(state, DOMAIN_BEACON_PROPOSER)) + return bls.Verify(proposer.pubkey, signing_root, signed_block.signature) + + +def process_slots(state: BeaconState, slot: Slot) -> None: + assert state.slot < slot + while state.slot < slot: + process_slot(state) + # Process epoch on the start slot of the next epoch + if (state.slot + 1) % SLOTS_PER_EPOCH == 0: + process_epoch(state) + state.slot = Slot(state.slot + 1) + + +def process_slot(state: BeaconState) -> None: + # Cache state root + previous_state_root = hash_tree_root(state) + state.state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_state_root + # Cache latest block header state root + if state.latest_block_header.state_root == Bytes32(): + state.latest_block_header.state_root = previous_state_root + # Cache block root + previous_block_root = hash_tree_root(state.latest_block_header) + state.block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_block_root + + +def process_epoch(state: BeaconState) -> None: + process_justification_and_finalization(state) + process_inactivity_updates(state) + process_rewards_and_penalties(state) + process_registry_updates(state) + process_slashings(state) + process_eth1_data_reset(state) + process_effective_balance_updates(state) + process_slashings_reset(state) + process_randao_mixes_reset(state) + process_historical_roots_update(state) + process_participation_flag_updates(state) + process_sync_committee_updates(state) + process_withdrawals(state) # [New in Withdrawals] + + +def get_matching_source_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: + assert epoch in (get_previous_epoch(state), get_current_epoch(state)) + return state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations + + +def get_matching_target_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: + return [ + a for a in get_matching_source_attestations(state, epoch) + if a.data.target.root == get_block_root(state, epoch) + ] + + +def get_matching_head_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: + return [ + a for a in get_matching_target_attestations(state, epoch) + if a.data.beacon_block_root == get_block_root_at_slot(state, a.data.slot) + ] + + +def get_unslashed_attesting_indices(state: BeaconState, + attestations: Sequence[PendingAttestation]) -> Set[ValidatorIndex]: + output = set() # type: Set[ValidatorIndex] + for a in attestations: + output = output.union(get_attesting_indices(state, a.data, a.aggregation_bits)) + return set(filter(lambda index: not state.validators[index].slashed, output)) + + +def get_attesting_balance(state: BeaconState, attestations: Sequence[PendingAttestation]) -> Gwei: + """ + Return the combined effective balance of the set of unslashed validators participating in ``attestations``. + Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. + """ + return get_total_balance(state, get_unslashed_attesting_indices(state, attestations)) + + +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_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)) + current_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_current_epoch(state)) + total_active_balance = get_total_active_balance(state) + previous_target_balance = get_total_balance(state, previous_indices) + current_target_balance = get_total_balance(state, current_indices) + weigh_justification_and_finalization(state, total_active_balance, previous_target_balance, current_target_balance) + + +def weigh_justification_and_finalization(state: BeaconState, + total_active_balance: Gwei, + previous_epoch_target_balance: Gwei, + current_epoch_target_balance: Gwei) -> None: + 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 + + # 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 + if previous_epoch_target_balance * 3 >= total_active_balance * 2: + state.current_justified_checkpoint = Checkpoint(epoch=previous_epoch, + root=get_block_root(state, previous_epoch)) + state.justification_bits[1] = 0b1 + if current_epoch_target_balance * 3 >= total_active_balance * 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 + + +def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: + """ + Return the base reward for the validator defined by ``index`` with respect to the current ``state``. + """ + increments = state.validators[index].effective_balance // EFFECTIVE_BALANCE_INCREMENT + return Gwei(increments * get_base_reward_per_increment(state)) + + +def get_proposer_reward(state: BeaconState, attesting_index: ValidatorIndex) -> Gwei: + return Gwei(get_base_reward(state, attesting_index) // PROPOSER_REWARD_QUOTIENT) + + +def get_finality_delay(state: BeaconState) -> uint64: + return get_previous_epoch(state) - state.finalized_checkpoint.epoch + + +def is_in_inactivity_leak(state: BeaconState) -> bool: + return get_finality_delay(state) > MIN_EPOCHS_TO_INACTIVITY_PENALTY + + +def get_eligible_validator_indices(state: BeaconState) -> Sequence[ValidatorIndex]: + previous_epoch = get_previous_epoch(state) + 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) + ] + + +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)] * 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) + 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 + if is_in_inactivity_leak(state): + # Since full base reward will be canceled out by inactivity penalty deltas, + # optimal participation receives full base reward compensation here. + rewards[index] += get_base_reward(state, index) + else: + 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 + + +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 get_attestation_component_deltas(state, matching_source_attestations) + + +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 get_attestation_component_deltas(state, matching_target_attestations) + + +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 get_attestation_component_deltas(state, matching_head_attestations) + + +def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return proposer and inclusion delay micro-rewards/penalties for each validator. + """ + rewards = [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 + if index in get_attesting_indices(state, a.data, a.aggregation_bits) + ], key=lambda a: a.inclusion_delay) + rewards[attestation.proposer_index] += get_proposer_reward(state, index) + max_attester_reward = Gwei(get_base_reward(state, index) - get_proposer_reward(state, index)) + 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 + + +def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return the inactivity penalty deltas by considering timely target participation flags and inactivity scores. + """ + rewards = [Gwei(0) for _ in range(len(state.validators))] + penalties = [Gwei(0) for _ in range(len(state.validators))] + previous_epoch = get_previous_epoch(state) + matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, previous_epoch) + for index in get_eligible_validator_indices(state): + if index not in matching_target_indices: + penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index] + # [Modified in Merge] + penalty_denominator = config.INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_MERGE + penalties[index] += Gwei(penalty_numerator // penalty_denominator) + return rewards, penalties + + +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) + 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 + + +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_index_deltas(state, flag_index) for flag_index in range(len(PARTICIPATION_FLAG_WEIGHTS))] + 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]) + + +def process_registry_updates(state: BeaconState) -> None: + # Process activation eligibility and ejections + for index, validator in enumerate(state.validators): + if is_eligible_for_activation_queue(validator): + validator.activation_eligibility_epoch = get_current_epoch(state) + 1 + + if ( + is_active_validator(validator, get_current_epoch(state)) + and validator.effective_balance <= config.EJECTION_BALANCE + ): + initiate_validator_exit(state, ValidatorIndex(index)) + + # Queue validators eligible for activation and not yet dequeued for activation + activation_queue = sorted([ + index for index, validator in enumerate(state.validators) + if is_eligible_for_activation(state, validator) + # Order by the sequence of activation_eligibility_epoch setting and then index + ], key=lambda index: (state.validators[index].activation_eligibility_epoch, index)) + # Dequeued validators for activation up to churn limit + for index in activation_queue[:get_validator_churn_limit(state)]: + validator = state.validators[index] + validator.activation_epoch = compute_activation_exit_epoch(get_current_epoch(state)) + + +def process_slashings(state: BeaconState) -> None: + epoch = get_current_epoch(state) + total_balance = get_total_active_balance(state) + adjusted_total_slashing_balance = min( + sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_MERGE, # [Modified in Merge] + total_balance + ) + for index, validator in enumerate(state.validators): + if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch: + increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow + penalty_numerator = validator.effective_balance // increment * adjusted_total_slashing_balance + penalty = penalty_numerator // total_balance * increment + decrease_balance(state, ValidatorIndex(index), penalty) + + +def process_eth1_data_reset(state: BeaconState) -> None: + next_epoch = Epoch(get_current_epoch(state) + 1) + # Reset eth1 data votes + if next_epoch % EPOCHS_PER_ETH1_VOTING_PERIOD == 0: + state.eth1_data_votes = [] + + +def process_effective_balance_updates(state: BeaconState) -> None: + # Update effective balances with hysteresis + for index, validator in enumerate(state.validators): + balance = state.balances[index] + HYSTERESIS_INCREMENT = uint64(EFFECTIVE_BALANCE_INCREMENT // HYSTERESIS_QUOTIENT) + DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER + UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER + if ( + balance + DOWNWARD_THRESHOLD < validator.effective_balance + or validator.effective_balance + UPWARD_THRESHOLD < balance + ): + validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) + + +def process_slashings_reset(state: BeaconState) -> None: + next_epoch = Epoch(get_current_epoch(state) + 1) + # Reset slashings + state.slashings[next_epoch % EPOCHS_PER_SLASHINGS_VECTOR] = Gwei(0) + + +def process_randao_mixes_reset(state: BeaconState) -> None: + current_epoch = get_current_epoch(state) + next_epoch = Epoch(current_epoch + 1) + # Set randao mix + state.randao_mixes[next_epoch % EPOCHS_PER_HISTORICAL_VECTOR] = get_randao_mix(state, current_epoch) + + +def process_historical_roots_update(state: BeaconState) -> None: + # Set historical root accumulator + next_epoch = Epoch(get_current_epoch(state) + 1) + if next_epoch % (SLOTS_PER_HISTORICAL_ROOT // SLOTS_PER_EPOCH) == 0: + historical_batch = HistoricalBatch(block_roots=state.block_roots, state_roots=state.state_roots) + state.historical_roots.append(hash_tree_root(historical_batch)) + + +def process_participation_record_updates(state: BeaconState) -> None: + # Rotate current/previous epoch attestations + state.previous_epoch_attestations = state.current_epoch_attestations + state.current_epoch_attestations = [] + + +def process_block(state: BeaconState, block: BeaconBlock) -> None: + process_block_header(state, block) + if is_execution_enabled(state, block.body): + process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [New in Merge] + process_randao(state, block.body) + process_eth1_data(state, block.body) + process_operations(state, block.body) + process_sync_aggregate(state, block.body.sync_aggregate) + + +def process_block_header(state: BeaconState, block: BeaconBlock) -> None: + # Verify that the slots match + assert block.slot == state.slot + # Verify that the block is newer than latest block header + assert block.slot > state.latest_block_header.slot + # Verify that proposer index is the correct index + assert block.proposer_index == get_beacon_proposer_index(state) + # Verify that the parent matches + assert block.parent_root == hash_tree_root(state.latest_block_header) + # Cache current block as the new latest block + state.latest_block_header = BeaconBlockHeader( + slot=block.slot, + proposer_index=block.proposer_index, + parent_root=block.parent_root, + state_root=Bytes32(), # Overwritten in the next process_slot call + body_root=hash_tree_root(block.body), + ) + + # Verify proposer is not slashed + proposer = state.validators[block.proposer_index] + assert not proposer.slashed + + +def process_randao(state: BeaconState, body: BeaconBlockBody) -> None: + epoch = get_current_epoch(state) + # Verify RANDAO reveal + proposer = state.validators[get_beacon_proposer_index(state)] + signing_root = compute_signing_root(epoch, get_domain(state, DOMAIN_RANDAO)) + assert bls.Verify(proposer.pubkey, signing_root, body.randao_reveal) + # Mix in RANDAO reveal + mix = xor(get_randao_mix(state, epoch), hash(body.randao_reveal)) + state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR] = mix + + +def process_eth1_data(state: BeaconState, body: BeaconBlockBody) -> None: + state.eth1_data_votes.append(body.eth1_data) + if state.eth1_data_votes.count(body.eth1_data) * 2 > EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH: + state.eth1_data = body.eth1_data + + +def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: + # Verify that outstanding deposits are processed up to the maximum number of deposits + assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index) + + def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: + for operation in operations: + fn(state, operation) + + for_ops(body.proposer_slashings, process_proposer_slashing) + for_ops(body.attester_slashings, process_attester_slashing) + for_ops(body.attestations, process_attestation) + for_ops(body.deposits, process_deposit) + for_ops(body.voluntary_exits, process_voluntary_exit) + + +def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None: + header_1 = proposer_slashing.signed_header_1.message + header_2 = proposer_slashing.signed_header_2.message + + # Verify header slots match + assert header_1.slot == header_2.slot + # Verify header proposer indices match + assert header_1.proposer_index == header_2.proposer_index + # Verify the headers are different + assert header_1 != header_2 + # Verify the proposer is slashable + proposer = state.validators[header_1.proposer_index] + assert is_slashable_validator(proposer, get_current_epoch(state)) + # Verify signatures + for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2): + domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(signed_header.message.slot)) + signing_root = compute_signing_root(signed_header.message, domain) + assert bls.Verify(proposer.pubkey, signing_root, signed_header.signature) + + slash_validator(state, header_1.proposer_index) + + +def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSlashing) -> None: + attestation_1 = attester_slashing.attestation_1 + attestation_2 = attester_slashing.attestation_2 + assert is_slashable_attestation_data(attestation_1.data, attestation_2.data) + assert is_valid_indexed_attestation(state, attestation_1) + assert is_valid_indexed_attestation(state, attestation_2) + + slashed_any = False + indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices) + for index in sorted(indices): + if is_slashable_validator(state.validators[index], get_current_epoch(state)): + slash_validator(state, index) + slashed_any = True + assert slashed_any + + +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) + + # Participation flag indices + participation_flag_indices = get_attestation_participation_flag_indices(state, data, state.slot - data.slot) + + # Verify signature + assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) + + # Update epoch participation flags + if data.target.epoch == get_current_epoch(state): + epoch_participation = state.current_epoch_participation + else: + epoch_participation = state.previous_epoch_participation + + proposer_reward_numerator = 0 + for index in get_attesting_indices(state, data, attestation.aggregation_bits): + for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS): + if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index): + epoch_participation[index] = add_flag(epoch_participation[index], flag_index) + proposer_reward_numerator += get_base_reward(state, index) * weight + + # Reward proposer + proposer_reward_denominator = (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT + proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator) + increase_balance(state, get_beacon_proposer_index(state), proposer_reward) + + +def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: + amount = deposit.data.amount + effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) + + return Validator( + pubkey=deposit.data.pubkey, + withdrawal_credentials=deposit.data.withdrawal_credentials, + activation_eligibility_epoch=FAR_FUTURE_EPOCH, + activation_epoch=FAR_FUTURE_EPOCH, + exit_epoch=FAR_FUTURE_EPOCH, + withdrawable_epoch=FAR_FUTURE_EPOCH, + effective_balance=effective_balance, + ) + + +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 = [validator.pubkey for validator 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) + # Initialize validator if the deposit signature is valid + if bls.Verify(pubkey, signing_root, deposit.data.signature): + state.validators.append(get_validator_from_deposit(state, deposit)) + state.balances.append(amount) + state.previous_epoch_participation.append(ParticipationFlags(0b0000_0000)) + state.current_epoch_participation.append(ParticipationFlags(0b0000_0000)) + state.inactivity_scores.append(uint64(0)) + else: + # Increase balance by deposit amount + index = ValidatorIndex(validator_pubkeys.index(pubkey)) + increase_balance(state, index, amount) + + +def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVoluntaryExit) -> None: + voluntary_exit = signed_voluntary_exit.message + validator = state.validators[voluntary_exit.validator_index] + # Verify the validator is active + assert is_active_validator(validator, get_current_epoch(state)) + # Verify exit has not been initiated + assert validator.exit_epoch == FAR_FUTURE_EPOCH + # Exits must specify an epoch when they become valid; they are not valid before then + assert get_current_epoch(state) >= voluntary_exit.epoch + # Verify the validator has been active long enough + assert get_current_epoch(state) >= validator.activation_epoch + config.SHARD_COMMITTEE_PERIOD + # Verify signature + domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch) + signing_root = compute_signing_root(voluntary_exit, domain) + assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature) + # Initiate exit + initiate_validator_exit(state, voluntary_exit.validator_index) + + +def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store: + assert anchor_block.state_root == hash_tree_root(anchor_state) + anchor_root = hash_tree_root(anchor_block) + anchor_epoch = get_current_epoch(anchor_state) + justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) + finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) + proposer_boost_root = Root() + return Store( + time=uint64(anchor_state.genesis_time + config.SECONDS_PER_SLOT * anchor_state.slot), + genesis_time=anchor_state.genesis_time, + justified_checkpoint=justified_checkpoint, + finalized_checkpoint=finalized_checkpoint, + best_justified_checkpoint=justified_checkpoint, + proposer_boost_root=proposer_boost_root, + blocks={anchor_root: copy(anchor_block)}, + block_states={anchor_root: copy(anchor_state)}, + checkpoint_states={justified_checkpoint: copy(anchor_state)}, + ) + + +def get_slots_since_genesis(store: Store) -> int: + return (store.time - store.genesis_time) // config.SECONDS_PER_SLOT + + +def get_current_slot(store: Store) -> Slot: + return Slot(GENESIS_SLOT + get_slots_since_genesis(store)) + + +def compute_slots_since_epoch_start(slot: Slot) -> int: + return slot - compute_start_slot_at_epoch(compute_epoch_at_slot(slot)) + + +def get_ancestor(store: Store, root: Root, slot: Slot) -> Root: + block = store.blocks[root] + if block.slot > slot: + return get_ancestor(store, block.parent_root, slot) + elif block.slot == slot: + return root + else: + # root is older than queried slot, thus a skip slot. Return most recent root prior to slot + return root + + +def get_latest_attesting_balance(store: Store, root: Root) -> Gwei: + state = store.checkpoint_states[store.justified_checkpoint] + active_indices = get_active_validator_indices(state, get_current_epoch(state)) + attestation_score = Gwei(sum( + state.validators[i].effective_balance for i in active_indices + if (i in store.latest_messages + and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root) + )) + proposer_score = Gwei(0) + if store.proposer_boost_root != Root(): + block = store.blocks[root] + if get_ancestor(store, root, block.slot) == store.proposer_boost_root: + num_validators = len(get_active_validator_indices(state, get_current_epoch(state))) + avg_balance = get_total_active_balance(state) // num_validators + committee_size = num_validators // SLOTS_PER_EPOCH + committee_weight = committee_size * avg_balance + proposer_score = (committee_weight * config.PROPOSER_SCORE_BOOST) // 100 + return attestation_score + proposer_score + + +def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconBlock]) -> bool: + block = store.blocks[block_root] + children = [ + root for root in store.blocks.keys() + if store.blocks[root].parent_root == block_root + ] + + # If any children branches contain expected finalized/justified checkpoints, + # add to filtered block-tree and signal viability to parent. + if any(children): + filter_block_tree_result = [filter_block_tree(store, child, blocks) for child in children] + if any(filter_block_tree_result): + blocks[block_root] = block + return True + return False + + # If leaf block, check finalized/justified checkpoints as matching latest. + head_state = store.block_states[block_root] + + correct_justified = ( + store.justified_checkpoint.epoch == GENESIS_EPOCH + or head_state.current_justified_checkpoint == store.justified_checkpoint + ) + correct_finalized = ( + store.finalized_checkpoint.epoch == GENESIS_EPOCH + or head_state.finalized_checkpoint == store.finalized_checkpoint + ) + # If expected finalized/justified, add to viable block-tree and signal viability to parent. + if correct_justified and correct_finalized: + blocks[block_root] = block + return True + + # Otherwise, branch not viable + return False + + +def get_filtered_block_tree(store: Store) -> Dict[Root, BeaconBlock]: + """ + Retrieve a filtered block tree from ``store``, only returning branches + whose leaf state's justified/finalized info agrees with that in ``store``. + """ + base = store.justified_checkpoint.root + blocks: Dict[Root, BeaconBlock] = {} + filter_block_tree(store, base, blocks) + return blocks + + +def get_head(store: Store) -> Root: + # Get filtered block tree that only includes viable branches + blocks = get_filtered_block_tree(store) + # Execute the LMD-GHOST fork choice + head = store.justified_checkpoint.root + while True: + children = [ + root for root in blocks.keys() + if blocks[root].parent_root == head + ] + if len(children) == 0: + return head + # Sort by latest attesting balance with ties broken lexicographically + head = max(children, key=lambda root: (get_latest_attesting_balance(store, root), root)) + + +def should_update_justified_checkpoint(store: Store, new_justified_checkpoint: Checkpoint) -> bool: + """ + To address the bouncing attack, only update conflicting justified + checkpoints in the fork choice if in the early slots of the epoch. + Otherwise, delay incorporation of new justified checkpoint until next epoch boundary. + + See https://ethresear.ch/t/prevention-of-bouncing-attack-on-ffg/6114 for more detailed analysis and discussion. + """ + if compute_slots_since_epoch_start(get_current_slot(store)) < SAFE_SLOTS_TO_UPDATE_JUSTIFIED: + return True + + justified_slot = compute_start_slot_at_epoch(store.justified_checkpoint.epoch) + if not get_ancestor(store, new_justified_checkpoint.root, justified_slot) == store.justified_checkpoint.root: + return False + + return True + + +def validate_target_epoch_against_current_time(store: Store, attestation: Attestation) -> None: + target = attestation.data.target + + # Attestations must be from the current or previous epoch + 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] + + +def validate_on_attestation(store: Store, attestation: Attestation, is_from_block: bool) -> None: + target = attestation.data.target + + # If the given attestation is not from a beacon block message, we have to check the target epoch scope. + if not is_from_block: + validate_target_epoch_against_current_time(store, attestation) + + # Check that the epoch number and slot number are matching + assert target.epoch == compute_epoch_at_slot(attestation.data.slot) + + # 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 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 + # Attestations must not be for blocks in the future. If not, the attestation should not be considered + assert store.blocks[attestation.data.beacon_block_root].slot <= attestation.data.slot + + # LMD vote must be consistent with FFG vote target + target_slot = compute_start_slot_at_epoch(target.epoch) + assert target.root == get_ancestor(store, attestation.data.beacon_block_root, target_slot) + + # Attestations can only affect the fork choice of subsequent slots. + # Delay consideration in the fork choice until their slot is in the past. + assert get_current_slot(store) >= attestation.data.slot + 1 + + +def store_target_checkpoint_state(store: Store, target: Checkpoint) -> None: + # Store target checkpoint state if not yet seen + if target not in store.checkpoint_states: + base_state = copy(store.block_states[target.root]) + if base_state.slot < compute_start_slot_at_epoch(target.epoch): + process_slots(base_state, compute_start_slot_at_epoch(target.epoch)) + store.checkpoint_states[target] = base_state + + +def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None: + target = attestation.data.target + beacon_block_root = attestation.data.beacon_block_root + for i in attesting_indices: + if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch: + store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root) + + +def on_tick(store: Store, time: uint64) -> None: + previous_slot = get_current_slot(store) + + # update store time + store.time = time + + current_slot = get_current_slot(store) + + # Reset store.proposer_boost_root if this is a new slot + if current_slot > previous_slot: + store.proposer_boost_root = Root() + + # Not a new epoch, return + if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0): + return + + # Update store.justified_checkpoint if a better checkpoint on the store.finalized_checkpoint chain + if store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch: + finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + ancestor_at_finalized_slot = get_ancestor(store, store.best_justified_checkpoint.root, finalized_slot) + if ancestor_at_finalized_slot == store.finalized_checkpoint.root: + store.justified_checkpoint = store.best_justified_checkpoint + + +def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: + """ + Run ``on_block`` upon receiving a new block. + + A block that is asserted as invalid due to unavailable PoW block may be valid at a later time, + consider scheduling it for later processing in such case. + """ + block = signed_block.message + # Parent block must be known + assert block.parent_root in store.block_states + # Make a copy of the state to avoid mutability issues + pre_state = copy(store.block_states[block.parent_root]) + # Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past. + assert get_current_slot(store) >= block.slot + + # Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) + finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + assert block.slot > finalized_slot + # Check block is a descendant of the finalized block at the checkpoint finalized slot + assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root + + # Check the block is valid and compute the post-state + state = pre_state.copy() + state_transition(state, signed_block, True) + + # [New in Merge] + if is_merge_transition_block(pre_state, block.body): + validate_merge_block(block) + + # Add new block to the store + store.blocks[hash_tree_root(block)] = block + # Add new state for this block to the store + store.block_states[hash_tree_root(block)] = state + + # Add proposer score boost if the block is timely + time_into_slot = (store.time - store.genesis_time) % config.SECONDS_PER_SLOT + is_before_attesting_interval = time_into_slot < config.SECONDS_PER_SLOT // INTERVALS_PER_SLOT + if get_current_slot(store) == block.slot and is_before_attesting_interval: + store.proposer_boost_root = hash_tree_root(block) + + # Update justified checkpoint + if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: + if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch: + store.best_justified_checkpoint = state.current_justified_checkpoint + if should_update_justified_checkpoint(store, state.current_justified_checkpoint): + store.justified_checkpoint = state.current_justified_checkpoint + + # Update finalized checkpoint + if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch: + store.finalized_checkpoint = state.finalized_checkpoint + store.justified_checkpoint = state.current_justified_checkpoint + + +def on_attestation(store: Store, attestation: Attestation, is_from_block: bool=False) -> None: + """ + Run ``on_attestation`` upon receiving a new ``attestation`` from either within a block or directly on the wire. + + An ``attestation`` that is asserted as invalid may be valid at a later time, + consider scheduling it for later processing in such case. + """ + validate_on_attestation(store, attestation, is_from_block) + + store_target_checkpoint_state(store, attestation.data.target) + + # Get state at the `target` to fully validate attestation + target_state = store.checkpoint_states[attestation.data.target] + indexed_attestation = get_indexed_attestation(target_state, attestation) + assert is_valid_indexed_attestation(target_state, indexed_attestation) + + # Update latest messages for attesting indices + update_latest_messages(store, indexed_attestation.attesting_indices, attestation) + + +def check_if_validator_active(state: BeaconState, validator_index: ValidatorIndex) -> bool: + validator = state.validators[validator_index] + return is_active_validator(validator, get_current_epoch(state)) + + +def get_committee_assignment(state: BeaconState, + epoch: Epoch, + validator_index: ValidatorIndex + ) -> Optional[Tuple[Sequence[ValidatorIndex], CommitteeIndex, Slot]]: + """ + Return the committee assignment in the ``epoch`` for ``validator_index``. + ``assignment`` returned is a tuple of the following form: + * ``assignment[0]`` is the list of validators in the committee + * ``assignment[1]`` is the index to which the committee is assigned + * ``assignment[2]`` is the slot at which the committee is assigned + Return None if no assignment. + """ + next_epoch = Epoch(get_current_epoch(state) + 1) + assert epoch <= next_epoch + + start_slot = compute_start_slot_at_epoch(epoch) + committee_count_per_slot = get_committee_count_per_slot(state, epoch) + for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH): + for index in range(committee_count_per_slot): + committee = get_beacon_committee(state, Slot(slot), CommitteeIndex(index)) + if validator_index in committee: + return committee, CommitteeIndex(index), Slot(slot) + return None + + +def is_proposer(state: BeaconState, validator_index: ValidatorIndex) -> bool: + return get_beacon_proposer_index(state) == validator_index + + +def get_epoch_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_RANDAO, compute_epoch_at_slot(block.slot)) + signing_root = compute_signing_root(compute_epoch_at_slot(block.slot), domain) + return bls.Sign(privkey, signing_root) + + +def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64: + return uint64(state.genesis_time + slot * config.SECONDS_PER_SLOT) + + +def voting_period_start_time(state: BeaconState) -> uint64: + eth1_voting_period_start_slot = Slot(state.slot - state.slot % (EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH)) + return compute_time_at_slot(state, eth1_voting_period_start_slot) + + +def is_candidate_block(block: Eth1Block, period_start: uint64) -> bool: + return ( + block.timestamp + config.SECONDS_PER_ETH1_BLOCK * config.ETH1_FOLLOW_DISTANCE <= period_start + and block.timestamp + config.SECONDS_PER_ETH1_BLOCK * config.ETH1_FOLLOW_DISTANCE * 2 >= period_start + ) + + +def get_eth1_vote(state: BeaconState, eth1_chain: Sequence[Eth1Block]) -> Eth1Data: + period_start = voting_period_start_time(state) + # `eth1_chain` abstractly represents all blocks in the eth1 chain sorted by ascending block height + votes_to_consider = [ + get_eth1_data(block) for block in eth1_chain + if ( + is_candidate_block(block, period_start) + # Ensure cannot move back to earlier deposit contract states + and get_eth1_data(block).deposit_count >= state.eth1_data.deposit_count + ) + ] + + # Valid votes already cast during this period + valid_votes = [vote for vote in state.eth1_data_votes if vote in votes_to_consider] + + # Default vote on latest eth1 block data in the period range unless eth1 chain is not live + # Non-substantive casting for linter + state_eth1_data: Eth1Data = state.eth1_data + default_vote = votes_to_consider[len(votes_to_consider) - 1] if any(votes_to_consider) else state_eth1_data + + return max( + valid_votes, + key=lambda v: (valid_votes.count(v), -valid_votes.index(v)), # Tiebreak by smallest distance + default=default_vote + ) + + +def compute_new_state_root(state: BeaconState, block: BeaconBlock) -> Root: + temp_state: BeaconState = state.copy() + signed_block = SignedBeaconBlock(message=block) + state_transition(temp_state, signed_block, validate_result=False) + return hash_tree_root(temp_state) + + +def get_block_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(block.slot)) + signing_root = compute_signing_root(block, domain) + return bls.Sign(privkey, signing_root) + + +def get_attestation_signature(state: BeaconState, attestation_data: AttestationData, privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch) + signing_root = compute_signing_root(attestation_data, domain) + return bls.Sign(privkey, signing_root) + + +def compute_subnet_for_attestation(committees_per_slot: uint64, slot: Slot, committee_index: CommitteeIndex) -> uint64: + """ + Compute the correct subnet for an attestation for Phase 0. + Note, this mimics expected future behavior where attestations will be mapped to their shard subnet. + """ + slots_since_epoch_start = uint64(slot % SLOTS_PER_EPOCH) + committees_since_epoch_start = committees_per_slot * slots_since_epoch_start + + return uint64((committees_since_epoch_start + committee_index) % ATTESTATION_SUBNET_COUNT) + + +def get_slot_signature(state: BeaconState, slot: Slot, privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_SELECTION_PROOF, compute_epoch_at_slot(slot)) + signing_root = compute_signing_root(slot, domain) + return bls.Sign(privkey, signing_root) + + +def is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex, slot_signature: BLSSignature) -> bool: + committee = get_beacon_committee(state, slot, index) + modulo = max(1, len(committee) // TARGET_AGGREGATORS_PER_COMMITTEE) + return bytes_to_uint64(hash(slot_signature)[0:8]) % modulo == 0 + + +def get_aggregate_signature(attestations: Sequence[Attestation]) -> BLSSignature: + signatures = [attestation.signature for attestation in attestations] + return bls.Aggregate(signatures) + + +def get_aggregate_and_proof(state: BeaconState, + aggregator_index: ValidatorIndex, + aggregate: Attestation, + privkey: int) -> AggregateAndProof: + return AggregateAndProof( + aggregator_index=aggregator_index, + aggregate=aggregate, + selection_proof=get_slot_signature(state, aggregate.data.slot, privkey), + ) + + +def get_aggregate_and_proof_signature(state: BeaconState, + aggregate_and_proof: AggregateAndProof, + privkey: int) -> BLSSignature: + aggregate = aggregate_and_proof.aggregate + domain = get_domain(state, DOMAIN_AGGREGATE_AND_PROOF, compute_epoch_at_slot(aggregate.data.slot)) + signing_root = compute_signing_root(aggregate_and_proof, domain) + return bls.Sign(privkey, signing_root) + + +def compute_weak_subjectivity_period(state: BeaconState) -> uint64: + """ + Returns the weak subjectivity period for the current ``state``. + This computation takes into account the effect of: + - validator set churn (bounded by ``get_validator_churn_limit()`` per epoch), and + - validator balance top-ups (bounded by ``MAX_DEPOSITS * SLOTS_PER_EPOCH`` per epoch). + A detailed calculation can be found at: + https://github.com/runtimeverification/beacon-chain-verification/blob/master/weak-subjectivity/weak-subjectivity-analysis.pdf + """ + ws_period = config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + N = len(get_active_validator_indices(state, get_current_epoch(state))) + t = get_total_active_balance(state) // N // ETH_TO_GWEI + T = MAX_EFFECTIVE_BALANCE // ETH_TO_GWEI + delta = get_validator_churn_limit(state) + Delta = MAX_DEPOSITS * SLOTS_PER_EPOCH + D = SAFETY_DECAY + + if T * (200 + 3 * D) < t * (200 + 12 * D): + epochs_for_validator_set_churn = ( + N * (t * (200 + 12 * D) - T * (200 + 3 * D)) // (600 * delta * (2 * t + T)) + ) + epochs_for_balance_top_ups = ( + N * (200 + 3 * D) // (600 * Delta) + ) + ws_period += max(epochs_for_validator_set_churn, epochs_for_balance_top_ups) + else: + ws_period += ( + 3 * N * D * t // (200 * Delta * (T - t)) + ) + + return ws_period + + +def is_within_weak_subjectivity_period(store: Store, ws_state: BeaconState, ws_checkpoint: Checkpoint) -> bool: + # Clients may choose to validate the input state against the input Weak Subjectivity Checkpoint + assert ws_state.latest_block_header.state_root == ws_checkpoint.root + assert compute_epoch_at_slot(ws_state.slot) == ws_checkpoint.epoch + + ws_period = compute_weak_subjectivity_period(ws_state) + ws_state_epoch = compute_epoch_at_slot(ws_state.slot) + current_epoch = compute_epoch_at_slot(get_current_slot(store)) + return current_epoch <= ws_state_epoch + ws_period + + +def add_flag(flags: ParticipationFlags, flag_index: int) -> ParticipationFlags: + """ + Return a new ``ParticipationFlags`` adding ``flag_index`` to ``flags``. + """ + flag = ParticipationFlags(2**flag_index) + return flags | flag + + +def has_flag(flags: ParticipationFlags, flag_index: int) -> bool: + """ + Return whether ``flags`` has ``flag_index`` set. + """ + flag = ParticipationFlags(2**flag_index) + return flags & flag == flag + + +def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorIndex]: + """ + Return the sync committee indices, with possible duplicates, for the next sync committee. + """ + epoch = Epoch(get_current_epoch(state) + 1) + + MAX_RANDOM_BYTE = 2**8 - 1 + active_validator_indices = get_active_validator_indices(state, epoch) + active_validator_count = uint64(len(active_validator_indices)) + seed = get_seed(state, epoch, DOMAIN_SYNC_COMMITTEE) + i = 0 + sync_committee_indices: List[ValidatorIndex] = [] + while len(sync_committee_indices) < SYNC_COMMITTEE_SIZE: + shuffled_index = compute_shuffled_index(uint64(i % active_validator_count), active_validator_count, seed) + candidate_index = active_validator_indices[shuffled_index] + random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] + effective_balance = state.validators[candidate_index].effective_balance + if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: + sync_committee_indices.append(candidate_index) + i += 1 + return sync_committee_indices + + +def get_next_sync_committee(state: BeaconState) -> SyncCommittee: + """ + Return the next sync committee, with possible pubkey duplicates. + """ + indices = get_next_sync_committee_indices(state) + pubkeys = [state.validators[index].pubkey for index in indices] + aggregate_pubkey = eth_aggregate_pubkeys(pubkeys) + return SyncCommittee(pubkeys=pubkeys, aggregate_pubkey=aggregate_pubkey) + + +def get_base_reward_per_increment(state: BeaconState) -> Gwei: + return Gwei(EFFECTIVE_BALANCE_INCREMENT * BASE_REWARD_FACTOR // integer_squareroot(get_total_active_balance(state))) + + +def get_unslashed_participating_indices(state: BeaconState, flag_index: int, epoch: Epoch) -> Set[ValidatorIndex]: + """ + Return the set of validator indices that are both active and unslashed for the given ``flag_index`` and ``epoch``. + """ + 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 + active_validator_indices = get_active_validator_indices(state, epoch) + participating_indices = [i for i in active_validator_indices if has_flag(epoch_participation[i], flag_index)] + return set(filter(lambda index: not state.validators[index].slashed, participating_indices)) + + +def get_attestation_participation_flag_indices(state: BeaconState, + data: AttestationData, + inclusion_delay: uint64) -> Sequence[int]: + """ + Return the flag indices that are satisfied by an attestation. + """ + if data.target.epoch == get_current_epoch(state): + justified_checkpoint = state.current_justified_checkpoint + else: + justified_checkpoint = state.previous_justified_checkpoint + + # Matching roots + is_matching_source = data.source == justified_checkpoint + is_matching_target = is_matching_source and data.target.root == get_block_root(state, data.target.epoch) + is_matching_head = is_matching_target and data.beacon_block_root == get_block_root_at_slot(state, data.slot) + assert is_matching_source + + participation_flag_indices = [] + if is_matching_source and inclusion_delay <= integer_squareroot(SLOTS_PER_EPOCH): + participation_flag_indices.append(TIMELY_SOURCE_FLAG_INDEX) + if is_matching_target and inclusion_delay <= SLOTS_PER_EPOCH: + participation_flag_indices.append(TIMELY_TARGET_FLAG_INDEX) + if is_matching_head and inclusion_delay == MIN_ATTESTATION_INCLUSION_DELAY: + participation_flag_indices.append(TIMELY_HEAD_FLAG_INDEX) + + return participation_flag_indices + + +def get_flag_index_deltas(state: BeaconState, flag_index: int) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return the deltas for a given ``flag_index`` by scanning through the participation flags. + """ + rewards = [Gwei(0)] * len(state.validators) + penalties = [Gwei(0)] * len(state.validators) + previous_epoch = get_previous_epoch(state) + unslashed_participating_indices = get_unslashed_participating_indices(state, flag_index, previous_epoch) + weight = PARTICIPATION_FLAG_WEIGHTS[flag_index] + unslashed_participating_balance = get_total_balance(state, unslashed_participating_indices) + unslashed_participating_increments = unslashed_participating_balance // EFFECTIVE_BALANCE_INCREMENT + active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT + for index in get_eligible_validator_indices(state): + base_reward = get_base_reward(state, index) + if index in unslashed_participating_indices: + if not is_in_inactivity_leak(state): + reward_numerator = base_reward * weight * unslashed_participating_increments + rewards[index] += Gwei(reward_numerator // (active_increments * WEIGHT_DENOMINATOR)) + elif flag_index != TIMELY_HEAD_FLAG_INDEX: + penalties[index] += Gwei(base_reward * weight // WEIGHT_DENOMINATOR) + return rewards, penalties + + +def process_sync_aggregate(state: BeaconState, sync_aggregate: SyncAggregate) -> None: + # Verify sync committee aggregate signature signing over the previous slot block root + committee_pubkeys = state.current_sync_committee.pubkeys + participant_pubkeys = [pubkey for pubkey, bit in zip(committee_pubkeys, sync_aggregate.sync_committee_bits) if bit] + previous_slot = max(state.slot, Slot(1)) - Slot(1) + domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, compute_epoch_at_slot(previous_slot)) + signing_root = compute_signing_root(get_block_root_at_slot(state, previous_slot), domain) + assert eth_fast_aggregate_verify(participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature) + + # Compute participant and proposer rewards + total_active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT + total_base_rewards = Gwei(get_base_reward_per_increment(state) * total_active_increments) + max_participant_rewards = Gwei(total_base_rewards * SYNC_REWARD_WEIGHT // WEIGHT_DENOMINATOR // SLOTS_PER_EPOCH) + participant_reward = Gwei(max_participant_rewards // SYNC_COMMITTEE_SIZE) + proposer_reward = Gwei(participant_reward * PROPOSER_WEIGHT // (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)) + + # Apply participant and proposer rewards + all_pubkeys = [v.pubkey for v in state.validators] + committee_indices = [ValidatorIndex(all_pubkeys.index(pubkey)) for pubkey in state.current_sync_committee.pubkeys] + for participant_index, participation_bit in zip(committee_indices, sync_aggregate.sync_committee_bits): + if participation_bit: + increase_balance(state, participant_index, participant_reward) + increase_balance(state, get_beacon_proposer_index(state), proposer_reward) + else: + decrease_balance(state, participant_index, participant_reward) + + +def process_inactivity_updates(state: BeaconState) -> None: + # Skip the genesis epoch as score updates are based on the previous epoch participation + if get_current_epoch(state) == GENESIS_EPOCH: + return + + for index in get_eligible_validator_indices(state): + # Increase the inactivity score of inactive validators + if index in get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)): + state.inactivity_scores[index] -= min(1, state.inactivity_scores[index]) + else: + state.inactivity_scores[index] += config.INACTIVITY_SCORE_BIAS + # Decrease the inactivity score of all eligible validators during a leak-free epoch + if not is_in_inactivity_leak(state): + state.inactivity_scores[index] -= min(config.INACTIVITY_SCORE_RECOVERY_RATE, state.inactivity_scores[index]) + + +def process_participation_flag_updates(state: BeaconState) -> None: + state.previous_epoch_participation = state.current_epoch_participation + state.current_epoch_participation = [ParticipationFlags(0b0000_0000) for _ in range(len(state.validators))] + + +def process_sync_committee_updates(state: BeaconState) -> None: + 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_next_sync_committee(state) + + +def eth_aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey: + return bls.AggregatePKs(pubkeys) + + +def eth_fast_aggregate_verify(pubkeys: Sequence[BLSPubkey], message: Bytes32, signature: BLSSignature) -> bool: + """ + Wrapper to ``bls.FastAggregateVerify`` accepting the ``G2_POINT_AT_INFINITY`` signature when ``pubkeys`` is empty. + """ + if len(pubkeys) == 0 and signature == G2_POINT_AT_INFINITY: + return True + return bls.FastAggregateVerify(pubkeys, message, signature) + + +def translate_participation(state: BeaconState, pending_attestations: Sequence[phase0.PendingAttestation]) -> None: + for attestation in pending_attestations: + data = attestation.data + inclusion_delay = attestation.inclusion_delay + # Translate attestation inclusion info to flag indices + participation_flag_indices = get_attestation_participation_flag_indices(state, data, inclusion_delay) + + # Apply flags to all attesting validators + epoch_participation = state.previous_epoch_participation + for index in get_attesting_indices(state, data, attestation.aggregation_bits): + for flag_index in participation_flag_indices: + epoch_participation[index] = add_flag(epoch_participation[index], flag_index) + + +def upgrade_to_altair(pre: phase0.BeaconState) -> BeaconState: + epoch = phase0.get_current_epoch(pre) + post = BeaconState( + # Versioning + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + current_version=config.ALTAIR_FORK_VERSION, + epoch=epoch, + ), + # History + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + # Eth1 + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + # Registry + validators=pre.validators, + balances=pre.balances, + # Randomness + randao_mixes=pre.randao_mixes, + # Slashings + slashings=pre.slashings, + # Participation + previous_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))], + current_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))], + # Finality + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + # Inactivity + inactivity_scores=[uint64(0) for _ in range(len(pre.validators))], + ) + # Fill in previous epoch participation from the pre state's pending attestations + translate_participation(post, pre.previous_epoch_attestations) + + # Fill in sync committees + # Note: A duplicate committee is assigned for the current and next committee at the fork boundary + post.current_sync_committee = get_next_sync_committee(post) + post.next_sync_committee = get_next_sync_committee(post) + return post + + +def compute_sync_committee_period(epoch: Epoch) -> uint64: + return epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + + +def is_assigned_to_sync_committee(state: BeaconState, + epoch: Epoch, + validator_index: ValidatorIndex) -> bool: + sync_committee_period = compute_sync_committee_period(epoch) + current_epoch = get_current_epoch(state) + current_sync_committee_period = compute_sync_committee_period(current_epoch) + next_sync_committee_period = current_sync_committee_period + 1 + assert sync_committee_period in (current_sync_committee_period, next_sync_committee_period) + + pubkey = state.validators[validator_index].pubkey + if sync_committee_period == current_sync_committee_period: + return pubkey in state.current_sync_committee.pubkeys + else: # sync_committee_period == next_sync_committee_period + return pubkey in state.next_sync_committee.pubkeys + + +def process_sync_committee_contributions(block: BeaconBlock, + contributions: Set[SyncCommitteeContribution]) -> None: + sync_aggregate = SyncAggregate() + signatures = [] + sync_subcommittee_size = SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT + + for contribution in contributions: + subcommittee_index = contribution.subcommittee_index + for index, participated in enumerate(contribution.aggregation_bits): + if participated: + participant_index = sync_subcommittee_size * subcommittee_index + index + sync_aggregate.sync_committee_bits[participant_index] = True + signatures.append(contribution.signature) + + sync_aggregate.sync_committee_signature = bls.Aggregate(signatures) + + block.body.sync_aggregate = sync_aggregate + + +def get_sync_committee_message(state: BeaconState, + block_root: Root, + validator_index: ValidatorIndex, + privkey: int) -> SyncCommitteeMessage: + epoch = get_current_epoch(state) + domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, epoch) + signing_root = compute_signing_root(block_root, domain) + signature = bls.Sign(privkey, signing_root) + + return SyncCommitteeMessage( + slot=state.slot, + beacon_block_root=block_root, + validator_index=validator_index, + signature=signature, + ) + + +def compute_subnets_for_sync_committee(state: BeaconState, validator_index: ValidatorIndex) -> Set[uint64]: + next_slot_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) + if compute_sync_committee_period(get_current_epoch(state)) == compute_sync_committee_period(next_slot_epoch): + sync_committee = state.current_sync_committee + else: + sync_committee = state.next_sync_committee + + target_pubkey = state.validators[validator_index].pubkey + sync_committee_indices = [index for index, pubkey in enumerate(sync_committee.pubkeys) if pubkey == target_pubkey] + return set([ + uint64(index // (SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT)) + for index in sync_committee_indices + ]) + + +def get_sync_committee_selection_proof(state: BeaconState, + slot: Slot, + subcommittee_index: uint64, + privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF, compute_epoch_at_slot(slot)) + signing_data = SyncAggregatorSelectionData( + slot=slot, + subcommittee_index=subcommittee_index, + ) + signing_root = compute_signing_root(signing_data, domain) + return bls.Sign(privkey, signing_root) + + +def is_sync_committee_aggregator(signature: BLSSignature) -> bool: + modulo = max(1, SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT // TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE) + return bytes_to_uint64(hash(signature)[0:8]) % modulo == 0 + + +def get_contribution_and_proof(state: BeaconState, + aggregator_index: ValidatorIndex, + contribution: SyncCommitteeContribution, + privkey: int) -> ContributionAndProof: + selection_proof = get_sync_committee_selection_proof( + state, + contribution.slot, + contribution.subcommittee_index, + privkey, + ) + return ContributionAndProof( + aggregator_index=aggregator_index, + contribution=contribution, + selection_proof=selection_proof, + ) + + +def get_contribution_and_proof_signature(state: BeaconState, + contribution_and_proof: ContributionAndProof, + privkey: int) -> BLSSignature: + contribution = contribution_and_proof.contribution + domain = get_domain(state, DOMAIN_CONTRIBUTION_AND_PROOF, compute_epoch_at_slot(contribution.slot)) + signing_root = compute_signing_root(contribution_and_proof, domain) + return bls.Sign(privkey, signing_root) + + +def get_sync_subcommittee_pubkeys(state: BeaconState, subcommittee_index: uint64) -> Sequence[BLSPubkey]: + # Committees assigned to `slot` sign for `slot - 1` + # This creates the exceptional logic below when transitioning between sync committee periods + next_slot_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) + if compute_sync_committee_period(get_current_epoch(state)) == compute_sync_committee_period(next_slot_epoch): + sync_committee = state.current_sync_committee + else: + sync_committee = state.next_sync_committee + + # Return pubkeys for the subcommittee index + sync_subcommittee_size = SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT + i = subcommittee_index * sync_subcommittee_size + return sync_committee.pubkeys[i:i + sync_subcommittee_size] + + +def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: + return uint64(generalized_index % 2**(floorlog2(generalized_index))) + + +def validate_light_client_update(snapshot: LightClientSnapshot, + update: LightClientUpdate, + genesis_validators_root: Root) -> None: + # Verify update slot is larger than snapshot slot + assert update.header.slot > snapshot.header.slot + + # Verify update does not skip a sync committee period + snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + assert update_period in (snapshot_period, snapshot_period + 1) + + # Verify update header root is the finalized root of the finality header, if specified + if update.finality_header == BeaconBlockHeader(): + signed_header = update.header + assert update.finality_branch == [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))] + else: + signed_header = update.finality_header + assert is_valid_merkle_branch( + leaf=hash_tree_root(update.header), + branch=update.finality_branch, + depth=floorlog2(FINALIZED_ROOT_INDEX), + index=get_subtree_index(FINALIZED_ROOT_INDEX), + root=update.finality_header.state_root, + ) + + # Verify update next sync committee if the update period incremented + if update_period == snapshot_period: + sync_committee = snapshot.current_sync_committee + assert update.next_sync_committee_branch == [Bytes32() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))] + else: + sync_committee = snapshot.next_sync_committee + assert is_valid_merkle_branch( + leaf=hash_tree_root(update.next_sync_committee), + branch=update.next_sync_committee_branch, + depth=floorlog2(NEXT_SYNC_COMMITTEE_INDEX), + index=get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX), + root=update.header.state_root, + ) + + # Verify sync committee has sufficient participants + assert sum(update.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS + + # Verify sync committee aggregate signature + participant_pubkeys = [pubkey for (bit, pubkey) in zip(update.sync_committee_bits, sync_committee.pubkeys) if bit] + domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version, genesis_validators_root) + signing_root = compute_signing_root(signed_header, domain) + assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature) + + +def apply_light_client_update(snapshot: LightClientSnapshot, update: LightClientUpdate) -> None: + snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + if update_period == snapshot_period + 1: + snapshot.current_sync_committee = snapshot.next_sync_committee + snapshot.next_sync_committee = update.next_sync_committee + snapshot.header = update.header + + +def process_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot, + genesis_validators_root: Root) -> None: + validate_light_client_update(store.snapshot, update, genesis_validators_root) + store.valid_updates.add(update) + + update_timeout = SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD + if ( + sum(update.sync_committee_bits) * 3 >= len(update.sync_committee_bits) * 2 + and update.finality_header != BeaconBlockHeader() + ): + # Apply update if (1) 2/3 quorum is reached and (2) we have a finality proof. + # Note that (2) means that the current light client design needs finality. + # It may be changed to re-organizable light client design. See the on-going issue consensus-specs#2182. + apply_light_client_update(store.snapshot, update) + store.valid_updates = set() + elif current_slot > store.snapshot.header.slot + update_timeout: + # Forced best update when the update timeout has elapsed + apply_light_client_update(store.snapshot, + max(store.valid_updates, key=lambda update: sum(update.sync_committee_bits))) + store.valid_updates = set() + + +def is_merge_transition_complete(state: BeaconState) -> bool: + return state.latest_execution_payload_header != ExecutionPayloadHeader() + + +def is_merge_transition_block(state: BeaconState, body: BeaconBlockBody) -> bool: + return not is_merge_transition_complete(state) and body.execution_payload != ExecutionPayload() + + +def is_execution_enabled(state: BeaconState, body: BeaconBlockBody) -> bool: + return is_merge_transition_block(state, body) or is_merge_transition_complete(state) + + +def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64: + slots_since_genesis = slot - GENESIS_SLOT + return uint64(state.genesis_time + slots_since_genesis * config.SECONDS_PER_SLOT) + + +def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None: + # Verify consistency of the parent hash with respect to the previous execution payload header + if is_merge_transition_complete(state): + assert payload.parent_hash == state.latest_execution_payload_header.block_hash + # Verify random + assert payload.random == get_randao_mix(state, get_current_epoch(state)) + # Verify timestamp + assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) + # Verify the execution payload is valid + assert execution_engine.execute_payload(payload) + # Cache execution payload header + state.latest_execution_payload_header = ExecutionPayloadHeader( + parent_hash=payload.parent_hash, + fee_recipient=payload.fee_recipient, + state_root=payload.state_root, + receipt_root=payload.receipt_root, + logs_bloom=payload.logs_bloom, + random=payload.random, + block_number=payload.block_number, + gas_limit=payload.gas_limit, + gas_used=payload.gas_used, + timestamp=payload.timestamp, + extra_data=payload.extra_data, + base_fee_per_gas=payload.base_fee_per_gas, + block_hash=payload.block_hash, + transactions_root=hash_tree_root(payload.transactions), + ) + + +def upgrade_to_merge(pre: altair.BeaconState) -> BeaconState: + epoch = altair.get_current_epoch(pre) + post = BeaconState( + # Versioning + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + current_version=config.MERGE_FORK_VERSION, + epoch=epoch, + ), + # History + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + # Eth1 + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + # Registry + validators=pre.validators, + balances=pre.balances, + # Randomness + randao_mixes=pre.randao_mixes, + # Slashings + slashings=pre.slashings, + # Participation + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + # Finality + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + # Inactivity + inactivity_scores=pre.inactivity_scores, + # Sync + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + # Execution-layer + latest_execution_payload_header=ExecutionPayloadHeader(), + ) + + return post + + +def is_valid_terminal_pow_block(block: PowBlock, parent: PowBlock) -> bool: + is_total_difficulty_reached = block.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY + is_parent_total_difficulty_valid = parent.total_difficulty < config.TERMINAL_TOTAL_DIFFICULTY + return is_total_difficulty_reached and is_parent_total_difficulty_valid + + +def validate_merge_block(block: BeaconBlock) -> None: + """ + Check the parent PoW block of execution payload is a valid terminal PoW block. + + Note: Unavailable PoW block(s) may later become available, + and a client software MAY delay a call to ``validate_merge_block`` + until the PoW block(s) become available. + """ + if config.TERMINAL_BLOCK_HASH != Hash32(): + # If `config.TERMINAL_BLOCK_HASH` is used as an override, the activation epoch must be reached. + assert compute_epoch_at_slot(block.slot) >= config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH + assert block.body.execution_payload.parent_hash == config.TERMINAL_BLOCK_HASH + return + + pow_block = get_pow_block(block.body.execution_payload.parent_hash) + # Check if `pow_block` is available + assert pow_block is not None + pow_parent = get_pow_block(pow_block.parent_hash) + # Check if `pow_parent` is available + assert pow_parent is not None + # Check if `pow_block` is a valid terminal PoW block + assert is_valid_terminal_pow_block(pow_block, pow_parent) + + +def get_pow_block_at_terminal_total_difficulty(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlock]: + # `pow_chain` abstractly represents all blocks in the PoW chain + for block in pow_chain.values(): + block_reached_ttd = block.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY + if block_reached_ttd: + # If genesis block, no parent exists so reaching TTD alone qualifies as valid terminal block + if block.parent_hash == Hash32(): + return block + parent = pow_chain[block.parent_hash] + parent_reached_ttd = parent.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY + if not parent_reached_ttd: + return block + + return None + + +def get_terminal_pow_block(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlock]: + if config.TERMINAL_BLOCK_HASH != Hash32(): + # Terminal block hash override takes precedence over terminal total difficulty + if config.TERMINAL_BLOCK_HASH in pow_chain: + return pow_chain[config.TERMINAL_BLOCK_HASH] + else: + return None + + return get_pow_block_at_terminal_total_difficulty(pow_chain) + + +def prepare_execution_payload(state: BeaconState, + pow_chain: Dict[Hash32, PowBlock], + finalized_block_hash: Hash32, + suggested_fee_recipient: ExecutionAddress, + execution_engine: ExecutionEngine) -> Optional[PayloadId]: + if not is_merge_transition_complete(state): + is_terminal_block_hash_set = config.TERMINAL_BLOCK_HASH != Hash32() + is_activation_epoch_reached = get_current_epoch(state) >= config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH + if is_terminal_block_hash_set and not is_activation_epoch_reached: + # Terminal block hash is set but activation epoch is not yet reached, no prepare payload call is needed + return None + + terminal_pow_block = get_terminal_pow_block(pow_chain) + if terminal_pow_block is None: + # Pre-merge, no prepare payload call is needed + return None + # Signify merge via producing on top of the terminal PoW block + parent_hash = terminal_pow_block.block_hash + else: + # Post-merge, normal payload + parent_hash = state.latest_execution_payload_header.block_hash + + # Set the forkchoice head and initiate the payload build process + payload_attributes = PayloadAttributes( + timestamp=compute_timestamp_at_slot(state, state.slot), + random=get_randao_mix(state, get_current_epoch(state)), + suggested_fee_recipient=suggested_fee_recipient, + ) + return execution_engine.notify_forkchoice_updated(parent_hash, finalized_block_hash, payload_attributes) + + +def get_execution_payload(payload_id: Optional[PayloadId], execution_engine: ExecutionEngine) -> ExecutionPayload: + if payload_id is None: + # Pre-merge, empty payload + return ExecutionPayload() + else: + return execution_engine.get_payload(payload_id) + + +def withdraw(state: BeaconState, index: ValidatorIndex, amount: Gwei) -> None: + # Decrease the validator's balance + decrease_balance(state, index, amount) + # Create a corresponding withdrawal receipt + receipt = WithdrawalReceipt( + index=WithdrawalReceiptIndex(len(state.withdrawal_receipts)), + address=state.validators[index].withdrawal_credentials[12:], + amount=amount, + ) + state.withdrawal_receipts.append(receipt) + + +def is_withdrawable_validator(validator: Validator, epoch: Epoch) -> bool: + """ + Check if ``validator`` is withdrawable. + """ + return validator.withdrawable_epoch <= epoch + + +def process_withdrawals(state: BeaconState) -> None: + current_epoch = get_current_epoch(state) + for index, validator in enumerate(state.validators): + balance = state.balances[index] + is_balance_nonzero = state.balances[index] == 0 + is_eth1_withdrawal_prefix = validator.withdrawal_credentials[0] != ETH1_ADDRESS_WITHDRAWAL_PREFIX + if is_balance_nonzero and is_eth1_withdrawal_prefix and is_withdrawable_validator(validator, current_epoch): + withdraw(state, index, balance) + + +def get_eth1_data(block: Eth1Block) -> Eth1Data: + """ + A stub function return mocking Eth1Data. + """ + return Eth1Data( + deposit_root=block.deposit_root, + deposit_count=block.deposit_count, + block_hash=hash_tree_root(block)) + + +def cache_this(key_fn, value_fn, lru_size): # type: ignore + cache_dict = LRU(size=lru_size) + + def wrapper(*args, **kw): # type: ignore + key = key_fn(*args, **kw) + nonlocal cache_dict + if key not in cache_dict: + cache_dict[key] = value_fn(*args, **kw) + return cache_dict[key] + return wrapper + + +_compute_shuffled_index = compute_shuffled_index +compute_shuffled_index = cache_this( + lambda index, index_count, seed: (index, index_count, seed), + _compute_shuffled_index, lru_size=SLOTS_PER_EPOCH * 3) + +_get_total_active_balance = get_total_active_balance +get_total_active_balance = cache_this( + lambda state: (state.validators.hash_tree_root(), compute_epoch_at_slot(state.slot)), + _get_total_active_balance, lru_size=10) + +_get_base_reward = get_base_reward +get_base_reward = cache_this( + lambda state, index: (state.validators.hash_tree_root(), state.slot, index), + _get_base_reward, lru_size=2048) + +_get_committee_count_per_slot = get_committee_count_per_slot +get_committee_count_per_slot = cache_this( + lambda state, epoch: (state.validators.hash_tree_root(), epoch), + _get_committee_count_per_slot, lru_size=SLOTS_PER_EPOCH * 3) + +_get_active_validator_indices = get_active_validator_indices +get_active_validator_indices = cache_this( + lambda state, epoch: (state.validators.hash_tree_root(), epoch), + _get_active_validator_indices, lru_size=3) + +_get_beacon_committee = get_beacon_committee +get_beacon_committee = cache_this( + lambda state, slot, index: (state.validators.hash_tree_root(), state.randao_mixes.hash_tree_root(), slot, index), + _get_beacon_committee, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3) + +_get_matching_target_attestations = get_matching_target_attestations +get_matching_target_attestations = cache_this( + lambda state, epoch: (state.hash_tree_root(), epoch), + _get_matching_target_attestations, lru_size=10) + +_get_matching_head_attestations = get_matching_head_attestations +get_matching_head_attestations = cache_this( + lambda state, epoch: (state.hash_tree_root(), epoch), + _get_matching_head_attestations, lru_size=10) + +_get_attesting_indices = get_attesting_indices +get_attesting_indices = cache_this( + lambda state, data, bits: ( + state.randao_mixes.hash_tree_root(), + state.validators.hash_tree_root(), data.hash_tree_root(), bits.hash_tree_root() + ), + _get_attesting_indices, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3) + + +def get_generalized_index(ssz_class: Any, *path: Sequence[PyUnion[int, SSZVariableName]]) -> GeneralizedIndex: + ssz_path = Path(ssz_class) + for item in path: + ssz_path = ssz_path / item + return GeneralizedIndex(ssz_path.gindex()) + + +ExecutionState = Any + + +def get_pow_block(hash: Bytes32) -> Optional[PowBlock]: + return PowBlock(block_hash=hash, parent_hash=Bytes32(), total_difficulty=uint256(0)) + + +def get_execution_state(execution_state_root: Bytes32) -> ExecutionState: + pass + + +def get_pow_chain_head() -> PowBlock: + pass + + +class NoopExecutionEngine(ExecutionEngine): + + def execute_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: + return True + + def notify_forkchoice_updated(self: ExecutionEngine, + head_block_hash: Hash32, + finalized_block_hash: Hash32, + payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: + pass + + def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayload: + raise NotImplementedError("no default block production") + + +EXECUTION_ENGINE = NoopExecutionEngine() + + +assert FINALIZED_ROOT_INDEX == get_generalized_index(BeaconState, 'finalized_checkpoint', 'root') +assert NEXT_SYNC_COMMITTEE_INDEX == get_generalized_index(BeaconState, 'next_sync_committee') diff --git a/tests/core/pyspec/eth2spec/withdrawals/minimal.py b/tests/core/pyspec/eth2spec/withdrawals/minimal.py new file mode 100644 index 0000000000..820d3703c6 --- /dev/null +++ b/tests/core/pyspec/eth2spec/withdrawals/minimal.py @@ -0,0 +1,3018 @@ +from lru import LRU +from dataclasses import ( + dataclass, + field, +) +from typing import ( + Any, Callable, Dict, Set, Sequence, Tuple, Optional, TypeVar, NamedTuple +) + +from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy, uint_to_bytes +from eth2spec.utils.ssz.ssz_typing import ( + View, boolean, Container, List, Vector, uint8, uint32, uint64, + Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist) +from eth2spec.utils.ssz.ssz_typing import Bitvector # noqa: F401 +from eth2spec.utils import bls +from eth2spec.utils.hash_function import hash + + +from typing import NewType, Union as PyUnion + +from eth2spec.phase0 import minimal as phase0 +from eth2spec.utils.ssz.ssz_typing import Path + +from typing import Protocol +from eth2spec.altair import minimal as altair +from eth2spec.utils.ssz.ssz_typing import Bytes8, Bytes20, ByteList, ByteVector, uint256 + +SSZObject = TypeVar('SSZObject', bound=View) + + +SSZVariableName = str +GeneralizedIndex = NewType('GeneralizedIndex', int) + + +fork = 'withdrawals' + + +MAX_BYTES_PER_TRANSACTION = uint64(2**30) + + +class Slot(uint64): + pass + + +class Epoch(uint64): + pass + + +class CommitteeIndex(uint64): + pass + + +class ValidatorIndex(uint64): + pass + + +class Gwei(uint64): + pass + + +class Root(Bytes32): + pass + + +class Hash32(Bytes32): + pass + + +class Version(Bytes4): + pass + + +class DomainType(Bytes4): + pass + + +class ForkDigest(Bytes4): + pass + + +class Domain(Bytes32): + pass + + +class BLSPubkey(Bytes48): + pass + + +class BLSSignature(Bytes96): + pass + + +class Ether(uint64): + pass + + +class ParticipationFlags(uint8): + pass + + +class ExecutionAddress(Bytes20): + pass + + +class PayloadId(Bytes8): + pass + + +class WithdrawalReceiptIndex(uint64): + pass + + +Transaction = ByteList[MAX_BYTES_PER_TRANSACTION] + + +def ceillog2(x: int) -> uint64: + if x < 1: + raise ValueError(f"ceillog2 accepts only positive values, x={x}") + return uint64((x - 1).bit_length()) + + +def floorlog2(x: int) -> uint64: + if x < 1: + raise ValueError(f"floorlog2 accepts only positive values, x={x}") + return uint64(x.bit_length() - 1) + + +FINALIZED_ROOT_INDEX = GeneralizedIndex(105) +NEXT_SYNC_COMMITTEE_INDEX = GeneralizedIndex(55) + +# Constant vars +GENESIS_SLOT = Slot(0) +GENESIS_EPOCH = Epoch(0) +FAR_FUTURE_EPOCH = Epoch(2**64 - 1) +BASE_REWARDS_PER_EPOCH = uint64(4) +DEPOSIT_CONTRACT_TREE_DEPTH = uint64(2**5) +JUSTIFICATION_BITS_LENGTH = uint64(4) +ENDIANNESS = 'little' +BLS_WITHDRAWAL_PREFIX = Bytes1('0x00') +ETH1_ADDRESS_WITHDRAWAL_PREFIX = Bytes1('0x01') +DOMAIN_BEACON_PROPOSER = DomainType('0x00000000') +DOMAIN_BEACON_ATTESTER = DomainType('0x01000000') +DOMAIN_RANDAO = DomainType('0x02000000') +DOMAIN_DEPOSIT = DomainType('0x03000000') +DOMAIN_VOLUNTARY_EXIT = DomainType('0x04000000') +DOMAIN_SELECTION_PROOF = DomainType('0x05000000') +DOMAIN_AGGREGATE_AND_PROOF = DomainType('0x06000000') +INTERVALS_PER_SLOT = uint64(3) +TARGET_AGGREGATORS_PER_COMMITTEE = 2**4 +RANDOM_SUBNETS_PER_VALIDATOR = 2**0 +EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION = 2**8 +ATTESTATION_SUBNET_COUNT = 64 +ETH_TO_GWEI = uint64(10**9) +SAFETY_DECAY = uint64(10) +TIMELY_SOURCE_FLAG_INDEX = 0 +TIMELY_TARGET_FLAG_INDEX = 1 +TIMELY_HEAD_FLAG_INDEX = 2 +TIMELY_SOURCE_WEIGHT = uint64(14) +TIMELY_TARGET_WEIGHT = uint64(26) +TIMELY_HEAD_WEIGHT = uint64(14) +SYNC_REWARD_WEIGHT = uint64(2) +PROPOSER_WEIGHT = uint64(8) +WEIGHT_DENOMINATOR = uint64(64) +DOMAIN_SYNC_COMMITTEE = DomainType('0x07000000') +DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF = DomainType('0x08000000') +DOMAIN_CONTRIBUTION_AND_PROOF = DomainType('0x09000000') +PARTICIPATION_FLAG_WEIGHTS = [TIMELY_SOURCE_WEIGHT, TIMELY_TARGET_WEIGHT, TIMELY_HEAD_WEIGHT] +G2_POINT_AT_INFINITY = BLSSignature(b'\xc0' + b'\x00' * 95) +TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE = 2**4 +SYNC_COMMITTEE_SUBNET_COUNT = 4 +WITHDRAWAL_RECEIPT_LIMIT = uint64(2**40) + +# Preset vars +MAX_COMMITTEES_PER_SLOT = uint64(4) +TARGET_COMMITTEE_SIZE = uint64(4) +MAX_VALIDATORS_PER_COMMITTEE = uint64(2048) +SHUFFLE_ROUND_COUNT = uint64(10) +HYSTERESIS_QUOTIENT = uint64(4) +HYSTERESIS_DOWNWARD_MULTIPLIER = uint64(1) +HYSTERESIS_UPWARD_MULTIPLIER = uint64(5) +MIN_DEPOSIT_AMOUNT = Gwei(1000000000) +MAX_EFFECTIVE_BALANCE = Gwei(32000000000) +EFFECTIVE_BALANCE_INCREMENT = Gwei(1000000000) +MIN_ATTESTATION_INCLUSION_DELAY = uint64(1) +SLOTS_PER_EPOCH = uint64(8) +MIN_SEED_LOOKAHEAD = uint64(1) +MAX_SEED_LOOKAHEAD = uint64(4) +MIN_EPOCHS_TO_INACTIVITY_PENALTY = uint64(4) +EPOCHS_PER_ETH1_VOTING_PERIOD = uint64(4) +SLOTS_PER_HISTORICAL_ROOT = uint64(64) +EPOCHS_PER_HISTORICAL_VECTOR = uint64(64) +EPOCHS_PER_SLASHINGS_VECTOR = uint64(64) +HISTORICAL_ROOTS_LIMIT = uint64(16777216) +VALIDATOR_REGISTRY_LIMIT = uint64(1099511627776) +BASE_REWARD_FACTOR = uint64(64) +WHISTLEBLOWER_REWARD_QUOTIENT = uint64(512) +PROPOSER_REWARD_QUOTIENT = uint64(8) +INACTIVITY_PENALTY_QUOTIENT = uint64(33554432) +MIN_SLASHING_PENALTY_QUOTIENT = uint64(64) +PROPORTIONAL_SLASHING_MULTIPLIER = uint64(2) +MAX_PROPOSER_SLASHINGS = 16 +MAX_ATTESTER_SLASHINGS = 2 +MAX_ATTESTATIONS = 128 +MAX_DEPOSITS = 16 +MAX_VOLUNTARY_EXITS = 16 +SAFE_SLOTS_TO_UPDATE_JUSTIFIED = 2 +INACTIVITY_PENALTY_QUOTIENT_ALTAIR = uint64(50331648) +MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR = uint64(64) +PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR = uint64(2) +SYNC_COMMITTEE_SIZE = uint64(32) +EPOCHS_PER_SYNC_COMMITTEE_PERIOD = uint64(8) +MIN_SYNC_COMMITTEE_PARTICIPANTS = 1 +MAX_BYTES_PER_TRANSACTION = uint64(1073741824) +MAX_TRANSACTIONS_PER_PAYLOAD = uint64(1048576) +BYTES_PER_LOGS_BLOOM = uint64(256) +MAX_EXTRA_DATA_BYTES = 32 +INACTIVITY_PENALTY_QUOTIENT_MERGE = uint64(16777216) +MIN_SLASHING_PENALTY_QUOTIENT_MERGE = uint64(32) +PROPORTIONAL_SLASHING_MULTIPLIER_MERGE = uint64(3) + + +class Configuration(NamedTuple): + PRESET_BASE: str + MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: uint64 + MIN_GENESIS_TIME: uint64 + GENESIS_FORK_VERSION: Version + GENESIS_DELAY: uint64 + SECONDS_PER_SLOT: uint64 + SECONDS_PER_ETH1_BLOCK: uint64 + MIN_VALIDATOR_WITHDRAWABILITY_DELAY: uint64 + SHARD_COMMITTEE_PERIOD: uint64 + ETH1_FOLLOW_DISTANCE: uint64 + EJECTION_BALANCE: Gwei + MIN_PER_EPOCH_CHURN_LIMIT: uint64 + CHURN_LIMIT_QUOTIENT: uint64 + PROPOSER_SCORE_BOOST: uint64 + INACTIVITY_SCORE_BIAS: uint64 + INACTIVITY_SCORE_RECOVERY_RATE: uint64 + ALTAIR_FORK_VERSION: Version + ALTAIR_FORK_EPOCH: Epoch + TERMINAL_TOTAL_DIFFICULTY: int + TERMINAL_BLOCK_HASH: Hash32 + TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: int + MERGE_FORK_VERSION: Version + MERGE_FORK_EPOCH: Epoch + + +config = Configuration( + PRESET_BASE="minimal", + MIN_GENESIS_ACTIVE_VALIDATOR_COUNT=uint64(64), + MIN_GENESIS_TIME=uint64(1578009600), + GENESIS_FORK_VERSION=Version('0x00000001'), + GENESIS_DELAY=uint64(300), + SECONDS_PER_SLOT=uint64(6), + SECONDS_PER_ETH1_BLOCK=uint64(14), + MIN_VALIDATOR_WITHDRAWABILITY_DELAY=uint64(256), + SHARD_COMMITTEE_PERIOD=uint64(64), + ETH1_FOLLOW_DISTANCE=uint64(16), + EJECTION_BALANCE=Gwei(16000000000), + MIN_PER_EPOCH_CHURN_LIMIT=uint64(4), + CHURN_LIMIT_QUOTIENT=uint64(32), + PROPOSER_SCORE_BOOST=uint64(70), + INACTIVITY_SCORE_BIAS=uint64(4), + INACTIVITY_SCORE_RECOVERY_RATE=uint64(16), + ALTAIR_FORK_VERSION=Version('0x01000001'), + ALTAIR_FORK_EPOCH=Epoch(18446744073709551615), + TERMINAL_TOTAL_DIFFICULTY=115792089237316195423570985008687907853269984665640564039457584007913129638912, + TERMINAL_BLOCK_HASH=Hash32('0x0000000000000000000000000000000000000000000000000000000000000000'), + TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH=18446744073709551615, + MERGE_FORK_VERSION=Version('0x02000001'), + MERGE_FORK_EPOCH=Epoch(18446744073709551615), +) + + +class Fork(Container): + previous_version: Version + current_version: Version + epoch: Epoch # Epoch of latest fork + + +class ForkData(Container): + current_version: Version + genesis_validators_root: Root + + +class Checkpoint(Container): + epoch: Epoch + root: Root + + +class Validator(Container): + pubkey: BLSPubkey + withdrawal_credentials: Bytes32 # Commitment to pubkey for withdrawals + effective_balance: Gwei # Balance at stake + slashed: boolean + # Status epochs + activation_eligibility_epoch: Epoch # When criteria for activation were met + activation_epoch: Epoch + exit_epoch: Epoch + withdrawable_epoch: Epoch # When validator can withdraw funds + + +class AttestationData(Container): + slot: Slot + index: CommitteeIndex + # LMD GHOST vote + beacon_block_root: Root + # FFG vote + source: Checkpoint + target: Checkpoint + + +class IndexedAttestation(Container): + attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] + data: AttestationData + signature: BLSSignature + + +class PendingAttestation(Container): + aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] + data: AttestationData + inclusion_delay: Slot + proposer_index: ValidatorIndex + + +class Eth1Data(Container): + deposit_root: Root + deposit_count: uint64 + block_hash: Hash32 + + +class HistoricalBatch(Container): + block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + + +class DepositMessage(Container): + pubkey: BLSPubkey + withdrawal_credentials: Bytes32 + amount: Gwei + + +class DepositData(Container): + pubkey: BLSPubkey + withdrawal_credentials: Bytes32 + amount: Gwei + signature: BLSSignature # Signing over DepositMessage + + +class BeaconBlockHeader(Container): + slot: Slot + proposer_index: ValidatorIndex + parent_root: Root + state_root: Root + body_root: Root + + +class SigningData(Container): + object_root: Root + domain: Domain + + +class AttesterSlashing(Container): + attestation_1: IndexedAttestation + attestation_2: IndexedAttestation + + +class Attestation(Container): + aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] + data: AttestationData + signature: BLSSignature + + +class Deposit(Container): + proof: Vector[Bytes32, DEPOSIT_CONTRACT_TREE_DEPTH + 1] # Merkle path to deposit root + data: DepositData + + +class VoluntaryExit(Container): + epoch: Epoch # Earliest epoch when voluntary exit can be processed + validator_index: ValidatorIndex + + +class SignedVoluntaryExit(Container): + message: VoluntaryExit + signature: BLSSignature + + +class SignedBeaconBlockHeader(Container): + message: BeaconBlockHeader + signature: BLSSignature + + +class ProposerSlashing(Container): + signed_header_1: SignedBeaconBlockHeader + signed_header_2: SignedBeaconBlockHeader + + +class Eth1Block(Container): + timestamp: uint64 + deposit_root: Root + deposit_count: uint64 + # All other eth1 block fields + + +class AggregateAndProof(Container): + aggregator_index: ValidatorIndex + aggregate: Attestation + selection_proof: BLSSignature + + +class SignedAggregateAndProof(Container): + message: AggregateAndProof + signature: BLSSignature + + +class SyncAggregate(Container): + sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] + sync_committee_signature: BLSSignature + + +class SyncCommittee(Container): + pubkeys: Vector[BLSPubkey, SYNC_COMMITTEE_SIZE] + aggregate_pubkey: BLSPubkey + + +class SyncCommitteeMessage(Container): + # Slot to which this contribution pertains + slot: Slot + # Block root for this signature + beacon_block_root: Root + # Index of the validator that produced this signature + validator_index: ValidatorIndex + # Signature by the validator over the block root of `slot` + signature: BLSSignature + + +class SyncCommitteeContribution(Container): + # Slot to which this contribution pertains + slot: Slot + # Block root for this contribution + beacon_block_root: Root + # The subcommittee this contribution pertains to out of the broader sync committee + subcommittee_index: uint64 + # A bit is set if a signature from the validator at the corresponding + # index in the subcommittee is present in the aggregate `signature`. + aggregation_bits: Bitvector[SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT] + # Signature by the validator(s) over the block root of `slot` + signature: BLSSignature + + +class ContributionAndProof(Container): + aggregator_index: ValidatorIndex + contribution: SyncCommitteeContribution + selection_proof: BLSSignature + + +class SignedContributionAndProof(Container): + message: ContributionAndProof + signature: BLSSignature + + +class SyncAggregatorSelectionData(Container): + slot: Slot + subcommittee_index: uint64 + + +class LightClientSnapshot(Container): + # Beacon block header + header: BeaconBlockHeader + # Sync committees corresponding to the header + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + + +class LightClientUpdate(Container): + # Update beacon block header + header: BeaconBlockHeader + # Next sync committee corresponding to the header + next_sync_committee: SyncCommittee + next_sync_committee_branch: Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_INDEX)] + # Finality proof for the update header + finality_header: BeaconBlockHeader + finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX)] + # Sync committee aggregate signature + sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] + sync_committee_signature: BLSSignature + # Fork version for the aggregate signature + fork_version: Version + + +class ExecutionPayload(Container): + # Execution block header fields + parent_hash: Hash32 + fee_recipient: ExecutionAddress # 'beneficiary' in the yellow paper + state_root: Bytes32 + receipt_root: Bytes32 # 'receipts root' in the yellow paper + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + random: Bytes32 # 'difficulty' in the yellow paper + block_number: uint64 # 'number' in the yellow paper + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + # Extra payload fields + block_hash: Hash32 # Hash of execution block + transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] + + +class BeaconBlockBody(Container): + randao_reveal: BLSSignature + eth1_data: Eth1Data # Eth1 data vote + graffiti: Bytes32 # Arbitrary data + # Operations + proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] + attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] + attestations: List[Attestation, MAX_ATTESTATIONS] + deposits: List[Deposit, MAX_DEPOSITS] + voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] + sync_aggregate: SyncAggregate + # Execution + execution_payload: ExecutionPayload # [New in Merge] + + +class BeaconBlock(Container): + slot: Slot + proposer_index: ValidatorIndex + parent_root: Root + state_root: Root + body: BeaconBlockBody + + +class SignedBeaconBlock(Container): + message: BeaconBlock + signature: BLSSignature + + +class ExecutionPayloadHeader(Container): + # Execution block header fields + parent_hash: Hash32 + fee_recipient: ExecutionAddress + state_root: Bytes32 + receipt_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + random: Bytes32 + block_number: uint64 + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + # Extra payload fields + block_hash: Hash32 # Hash of execution block + transactions_root: Root + + +class PowBlock(Container): + block_hash: Hash32 + parent_hash: Hash32 + total_difficulty: uint256 + + +class WithdrawalReceipt(Container): + index: WithdrawalReceiptIndex + address: ExecutionAddress + amount: Gwei + + +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[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + current_epoch_participation: List[ParticipationFlags, 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 + # Inactivity + inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] + # Sync + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + # Execution + latest_execution_payload_header: ExecutionPayloadHeader + # Withdrawals + withdrawal_receipts: List[WithdrawalReceipt, WITHDRAWAL_RECEIPT_LIMIT] # [New in Withdrawals] + + +@dataclass(eq=True, frozen=True) +class LatestMessage(object): + epoch: Epoch + root: Root + + +@dataclass +class Store(object): + time: uint64 + genesis_time: uint64 + justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + best_justified_checkpoint: Checkpoint + proposer_boost_root: Root + blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) + block_states: Dict[Root, BeaconState] = field(default_factory=dict) + checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict) + latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict) + + +@dataclass +class LightClientStore(object): + snapshot: LightClientSnapshot + valid_updates: Set[LightClientUpdate] + + +@dataclass +class PayloadAttributes(object): + timestamp: uint64 + random: Bytes32 + suggested_fee_recipient: ExecutionAddress + + +class ExecutionEngine(Protocol): + + def execute_payload(self, execution_payload: ExecutionPayload) -> bool: + """ + Return ``True`` if and only if ``execution_payload`` is valid with respect to ``self.execution_state``. + """ + ... + + def notify_forkchoice_updated(self, + head_block_hash: Hash32, + finalized_block_hash: Hash32, + payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: + ... + + def get_payload(self, payload_id: PayloadId) -> ExecutionPayload: + """ + Return ``execution_payload`` object. + """ + ... + + +def integer_squareroot(n: uint64) -> uint64: + """ + Return the largest integer ``x`` such that ``x**2 <= n``. + """ + x = n + y = (x + 1) // 2 + while y < x: + x = y + y = (x + n // x) // 2 + return x + + +def xor(bytes_1: Bytes32, bytes_2: Bytes32) -> Bytes32: + """ + Return the exclusive-or of two 32-byte strings. + """ + return Bytes32(a ^ b for a, b in zip(bytes_1, bytes_2)) + + +def bytes_to_uint64(data: bytes) -> uint64: + """ + Return the integer deserialization of ``data`` interpreted as ``ENDIANNESS``-endian. + """ + return uint64(int.from_bytes(data, ENDIANNESS)) + + +def is_active_validator(validator: Validator, epoch: Epoch) -> bool: + """ + Check if ``validator`` is active. + """ + return validator.activation_epoch <= epoch < validator.exit_epoch + + +def is_eligible_for_activation_queue(validator: Validator) -> bool: + """ + Check if ``validator`` is eligible to be placed into the activation queue. + """ + return ( + validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH + and validator.effective_balance == MAX_EFFECTIVE_BALANCE + ) + + +def is_eligible_for_activation(state: BeaconState, validator: Validator) -> bool: + """ + Check if ``validator`` is eligible for activation. + """ + return ( + # Placement in queue is finalized + validator.activation_eligibility_epoch <= state.finalized_checkpoint.epoch + # Has not yet been activated + and validator.activation_epoch == FAR_FUTURE_EPOCH + ) + + +def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool: + """ + Check if ``validator`` is slashable. + """ + return (not validator.slashed) and (validator.activation_epoch <= epoch < validator.withdrawable_epoch) + + +def is_slashable_attestation_data(data_1: AttestationData, data_2: AttestationData) -> bool: + """ + Check if ``data_1`` and ``data_2`` are slashable according to Casper FFG rules. + """ + return ( + # Double vote + (data_1 != data_2 and data_1.target.epoch == data_2.target.epoch) or + # Surround vote + (data_1.source.epoch < data_2.source.epoch and data_2.target.epoch < data_1.target.epoch) + ) + + +def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool: + """ + Check if ``indexed_attestation`` is not empty, has sorted and unique indices and has a valid aggregate signature. + """ + # Verify indices are sorted and unique + indices = indexed_attestation.attesting_indices + if len(indices) == 0 or not indices == sorted(set(indices)): + return False + # Verify aggregate signature + pubkeys = [state.validators[i].pubkey for i in indices] + domain = get_domain(state, DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch) + signing_root = compute_signing_root(indexed_attestation.data, domain) + return bls.FastAggregateVerify(pubkeys, signing_root, indexed_attestation.signature) + + +def is_valid_merkle_branch(leaf: Bytes32, branch: Sequence[Bytes32], depth: uint64, index: uint64, root: Root) -> bool: + """ + Check if ``leaf`` at ``index`` verifies against the Merkle ``root`` and ``branch``. + """ + value = leaf + for i in range(depth): + if index // (2**i) % 2: + value = hash(branch[i] + value) + else: + value = hash(value + branch[i]) + return value == root + + +def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) -> uint64: + """ + Return the shuffled index corresponding to ``seed`` (and ``index_count``). + """ + assert index < index_count + + # Swap or not (https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf) + # See the 'generalized domain' algorithm on page 3 + for current_round in range(SHUFFLE_ROUND_COUNT): + pivot = bytes_to_uint64(hash(seed + uint_to_bytes(uint8(current_round)))[0:8]) % index_count + flip = (pivot + index_count - index) % index_count + position = max(index, flip) + source = hash( + seed + + uint_to_bytes(uint8(current_round)) + + uint_to_bytes(uint32(position // 256)) + ) + byte = uint8(source[(position % 256) // 8]) + bit = (byte >> (position % 8)) % 2 + index = flip if bit else index + + return index + + +def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex], seed: Bytes32) -> ValidatorIndex: + """ + Return from ``indices`` a random index sampled by effective balance. + """ + assert len(indices) > 0 + MAX_RANDOM_BYTE = 2**8 - 1 + i = uint64(0) + total = uint64(len(indices)) + while True: + candidate_index = indices[compute_shuffled_index(i % total, total, seed)] + random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] + effective_balance = state.validators[candidate_index].effective_balance + if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: + return candidate_index + i += 1 + + +def compute_committee(indices: Sequence[ValidatorIndex], + seed: Bytes32, + index: uint64, + count: uint64) -> Sequence[ValidatorIndex]: + """ + Return the committee corresponding to ``indices``, ``seed``, ``index``, and committee ``count``. + """ + start = (len(indices) * index) // count + end = (len(indices) * uint64(index + 1)) // count + return [indices[compute_shuffled_index(uint64(i), uint64(len(indices)), seed)] for i in range(start, end)] + + +def compute_epoch_at_slot(slot: Slot) -> Epoch: + """ + Return the epoch number at ``slot``. + """ + return Epoch(slot // SLOTS_PER_EPOCH) + + +def compute_start_slot_at_epoch(epoch: Epoch) -> Slot: + """ + Return the start slot of ``epoch``. + """ + return Slot(epoch * SLOTS_PER_EPOCH) + + +def compute_activation_exit_epoch(epoch: Epoch) -> Epoch: + """ + Return the epoch during which validator activations and exits initiated in ``epoch`` take effect. + """ + return Epoch(epoch + 1 + MAX_SEED_LOOKAHEAD) + + +def compute_fork_data_root(current_version: Version, genesis_validators_root: Root) -> Root: + """ + Return the 32-byte fork data root for the ``current_version`` and ``genesis_validators_root``. + This is used primarily in signature domains to avoid collisions across forks/chains. + """ + return hash_tree_root(ForkData( + current_version=current_version, + genesis_validators_root=genesis_validators_root, + )) + + +def compute_fork_digest(current_version: Version, genesis_validators_root: Root) -> ForkDigest: + """ + Return the 4-byte fork digest for the ``current_version`` and ``genesis_validators_root``. + This is a digest primarily used for domain separation on the p2p layer. + 4-bytes suffices for practical separation of forks/chains. + """ + return ForkDigest(compute_fork_data_root(current_version, genesis_validators_root)[:4]) + + +def compute_domain(domain_type: DomainType, fork_version: Version=None, genesis_validators_root: Root=None) -> Domain: + """ + Return the domain for the ``domain_type`` and ``fork_version``. + """ + if fork_version is None: + fork_version = config.GENESIS_FORK_VERSION + if genesis_validators_root is None: + genesis_validators_root = Root() # all bytes zero by default + fork_data_root = compute_fork_data_root(fork_version, genesis_validators_root) + return Domain(domain_type + fork_data_root[:28]) + + +def compute_signing_root(ssz_object: SSZObject, domain: Domain) -> Root: + """ + Return the signing root for the corresponding signing data. + """ + return hash_tree_root(SigningData( + object_root=hash_tree_root(ssz_object), + domain=domain, + )) + + +def get_current_epoch(state: BeaconState) -> Epoch: + """ + Return the current epoch. + """ + return compute_epoch_at_slot(state.slot) + + +def get_previous_epoch(state: BeaconState) -> Epoch: + """` + Return the previous epoch (unless the current epoch is ``GENESIS_EPOCH``). + """ + current_epoch = get_current_epoch(state) + return GENESIS_EPOCH if current_epoch == GENESIS_EPOCH else Epoch(current_epoch - 1) + + +def get_block_root(state: BeaconState, epoch: Epoch) -> Root: + """ + Return the block root at the start of a recent ``epoch``. + """ + return get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch)) + + +def get_block_root_at_slot(state: BeaconState, slot: Slot) -> Root: + """ + Return the block root at a recent ``slot``. + """ + assert slot < state.slot <= slot + SLOTS_PER_HISTORICAL_ROOT + return state.block_roots[slot % SLOTS_PER_HISTORICAL_ROOT] + + +def get_randao_mix(state: BeaconState, epoch: Epoch) -> Bytes32: + """ + Return the randao mix at a recent ``epoch``. + """ + return state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR] + + +def get_active_validator_indices(state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: + """ + Return the sequence of active validator indices at ``epoch``. + """ + return [ValidatorIndex(i) for i, v in enumerate(state.validators) if is_active_validator(v, epoch)] + + +def get_validator_churn_limit(state: BeaconState) -> uint64: + """ + Return the validator churn limit for the current epoch. + """ + active_validator_indices = get_active_validator_indices(state, get_current_epoch(state)) + return max(config.MIN_PER_EPOCH_CHURN_LIMIT, uint64(len(active_validator_indices)) // config.CHURN_LIMIT_QUOTIENT) + + +def get_seed(state: BeaconState, epoch: Epoch, domain_type: DomainType) -> Bytes32: + """ + Return the seed at ``epoch``. + """ + mix = get_randao_mix(state, Epoch(epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD - 1)) # Avoid underflow + return hash(domain_type + uint_to_bytes(epoch) + mix) + + +def get_committee_count_per_slot(state: BeaconState, epoch: Epoch) -> uint64: + """ + Return the number of committees in each slot for the given ``epoch``. + """ + return max(uint64(1), min( + MAX_COMMITTEES_PER_SLOT, + uint64(len(get_active_validator_indices(state, epoch))) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE, + )) + + +def get_beacon_committee(state: BeaconState, slot: Slot, index: CommitteeIndex) -> Sequence[ValidatorIndex]: + """ + Return the beacon committee at ``slot`` for ``index``. + """ + epoch = compute_epoch_at_slot(slot) + committees_per_slot = get_committee_count_per_slot(state, epoch) + return compute_committee( + indices=get_active_validator_indices(state, epoch), + seed=get_seed(state, epoch, DOMAIN_BEACON_ATTESTER), + index=(slot % SLOTS_PER_EPOCH) * committees_per_slot + index, + count=committees_per_slot * SLOTS_PER_EPOCH, + ) + + +def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: + """ + Return the beacon proposer index at the current slot. + """ + epoch = get_current_epoch(state) + seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + uint_to_bytes(state.slot)) + indices = get_active_validator_indices(state, epoch) + return compute_proposer_index(state, indices, seed) + + +def get_total_balance(state: BeaconState, indices: Set[ValidatorIndex]) -> Gwei: + """ + Return the combined effective balance of the ``indices``. + ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. + Math safe up to ~10B ETH, afterwhich this overflows uint64. + """ + return Gwei(max(EFFECTIVE_BALANCE_INCREMENT, sum([state.validators[index].effective_balance for index in indices]))) + + +def get_total_active_balance(state: BeaconState) -> Gwei: + """ + Return the combined effective balance of the active validators. + Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. + """ + return get_total_balance(state, set(get_active_validator_indices(state, get_current_epoch(state)))) + + +def get_domain(state: BeaconState, domain_type: DomainType, epoch: Epoch=None) -> Domain: + """ + Return the signature domain (fork version concatenated with domain type) of a message. + """ + epoch = get_current_epoch(state) if epoch is None else epoch + fork_version = state.fork.previous_version if epoch < state.fork.epoch else state.fork.current_version + return compute_domain(domain_type, fork_version, state.genesis_validators_root) + + +def get_indexed_attestation(state: BeaconState, attestation: Attestation) -> IndexedAttestation: + """ + Return the indexed attestation corresponding to ``attestation``. + """ + attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) + + return IndexedAttestation( + attesting_indices=sorted(attesting_indices), + data=attestation.data, + signature=attestation.signature, + ) + + +def get_attesting_indices(state: BeaconState, + data: AttestationData, + bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Set[ValidatorIndex]: + """ + Return the set of attesting indices corresponding to ``data`` and ``bits``. + """ + committee = get_beacon_committee(state, data.slot, data.index) + return set(index for i, index in enumerate(committee) if bits[i]) + + +def increase_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: + """ + Increase the validator balance at index ``index`` by ``delta``. + """ + state.balances[index] += delta + + +def decrease_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: + """ + Decrease the validator balance at index ``index`` by ``delta``, with underflow protection. + """ + state.balances[index] = 0 if delta > state.balances[index] else state.balances[index] - delta + + +def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: + """ + Initiate the exit of the validator with index ``index``. + """ + # Return if validator already initiated exit + validator = state.validators[index] + if validator.exit_epoch != FAR_FUTURE_EPOCH: + return + + # Compute exit queue epoch + exit_epochs = [v.exit_epoch for v in state.validators if v.exit_epoch != FAR_FUTURE_EPOCH] + exit_queue_epoch = max(exit_epochs + [compute_activation_exit_epoch(get_current_epoch(state))]) + exit_queue_churn = len([v for v in state.validators if v.exit_epoch == exit_queue_epoch]) + if exit_queue_churn >= get_validator_churn_limit(state): + exit_queue_epoch += Epoch(1) + + # Set validator exit epoch and withdrawable epoch + validator.exit_epoch = exit_queue_epoch + validator.withdrawable_epoch = Epoch(validator.exit_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY) + + +def slash_validator(state: BeaconState, + slashed_index: ValidatorIndex, + whistleblower_index: ValidatorIndex=None) -> None: + """ + Slash the validator with index ``slashed_index``. + """ + epoch = get_current_epoch(state) + initiate_validator_exit(state, slashed_index) + validator = state.validators[slashed_index] + validator.slashed = True + validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR)) + state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance + slashing_penalty = validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_MERGE # [Modified in Merge] + decrease_balance(state, slashed_index, slashing_penalty) + + # Apply proposer and whistleblower rewards + proposer_index = get_beacon_proposer_index(state) + if whistleblower_index is None: + whistleblower_index = proposer_index + whistleblower_reward = Gwei(validator.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT) + proposer_reward = Gwei(whistleblower_reward * PROPOSER_WEIGHT // WEIGHT_DENOMINATOR) + increase_balance(state, proposer_index, proposer_reward) + increase_balance(state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward)) + + +def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, + eth1_timestamp: uint64, + deposits: Sequence[Deposit], + execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader() + ) -> BeaconState: + fork = Fork( + previous_version=config.MERGE_FORK_VERSION, # [Modified in Merge] for testing only + current_version=config.MERGE_FORK_VERSION, # [Modified in Merge] + epoch=GENESIS_EPOCH, + ) + state = BeaconState( + genesis_time=eth1_timestamp + config.GENESIS_DELAY, + fork=fork, + eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))), + latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), + randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy + ) + + # Process deposits + leaves = list(map(lambda deposit: deposit.data, deposits)) + for index, deposit in enumerate(deposits): + deposit_data_list = List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[:index + 1]) + state.eth1_data.deposit_root = hash_tree_root(deposit_data_list) + process_deposit(state, deposit) + + # Process activations + for index, validator in enumerate(state.validators): + balance = state.balances[index] + validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) + if validator.effective_balance == MAX_EFFECTIVE_BALANCE: + validator.activation_eligibility_epoch = GENESIS_EPOCH + validator.activation_epoch = GENESIS_EPOCH + + # Set genesis validators root for domain separation and chain versioning + state.genesis_validators_root = hash_tree_root(state.validators) + + # Fill in sync committees + # Note: A duplicate committee is assigned for the current and next committee at genesis + state.current_sync_committee = get_next_sync_committee(state) + state.next_sync_committee = get_next_sync_committee(state) + + # [New in Merge] Initialize the execution payload header + # If empty, will initialize a chain that has not yet gone through the Merge transition + state.latest_execution_payload_header = execution_payload_header + + return state + + +def is_valid_genesis_state(state: BeaconState) -> bool: + if state.genesis_time < config.MIN_GENESIS_TIME: + return False + if len(get_active_validator_indices(state, GENESIS_EPOCH)) < config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: + return False + return True + + +def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool=True) -> None: + block = signed_block.message + # Process slots (including those with no blocks) since block + process_slots(state, block.slot) + # Verify signature + if validate_result: + assert verify_block_signature(state, signed_block) + # Process block + process_block(state, block) + # Verify state root + if validate_result: + assert block.state_root == hash_tree_root(state) + + +def verify_block_signature(state: BeaconState, signed_block: SignedBeaconBlock) -> bool: + proposer = state.validators[signed_block.message.proposer_index] + signing_root = compute_signing_root(signed_block.message, get_domain(state, DOMAIN_BEACON_PROPOSER)) + return bls.Verify(proposer.pubkey, signing_root, signed_block.signature) + + +def process_slots(state: BeaconState, slot: Slot) -> None: + assert state.slot < slot + while state.slot < slot: + process_slot(state) + # Process epoch on the start slot of the next epoch + if (state.slot + 1) % SLOTS_PER_EPOCH == 0: + process_epoch(state) + state.slot = Slot(state.slot + 1) + + +def process_slot(state: BeaconState) -> None: + # Cache state root + previous_state_root = hash_tree_root(state) + state.state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_state_root + # Cache latest block header state root + if state.latest_block_header.state_root == Bytes32(): + state.latest_block_header.state_root = previous_state_root + # Cache block root + previous_block_root = hash_tree_root(state.latest_block_header) + state.block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_block_root + + +def process_epoch(state: BeaconState) -> None: + process_justification_and_finalization(state) + process_inactivity_updates(state) + process_rewards_and_penalties(state) + process_registry_updates(state) + process_slashings(state) + process_eth1_data_reset(state) + process_effective_balance_updates(state) + process_slashings_reset(state) + process_randao_mixes_reset(state) + process_historical_roots_update(state) + process_participation_flag_updates(state) + process_sync_committee_updates(state) + process_withdrawals(state) # [New in Withdrawals] + + +def get_matching_source_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: + assert epoch in (get_previous_epoch(state), get_current_epoch(state)) + return state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations + + +def get_matching_target_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: + return [ + a for a in get_matching_source_attestations(state, epoch) + if a.data.target.root == get_block_root(state, epoch) + ] + + +def get_matching_head_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: + return [ + a for a in get_matching_target_attestations(state, epoch) + if a.data.beacon_block_root == get_block_root_at_slot(state, a.data.slot) + ] + + +def get_unslashed_attesting_indices(state: BeaconState, + attestations: Sequence[PendingAttestation]) -> Set[ValidatorIndex]: + output = set() # type: Set[ValidatorIndex] + for a in attestations: + output = output.union(get_attesting_indices(state, a.data, a.aggregation_bits)) + return set(filter(lambda index: not state.validators[index].slashed, output)) + + +def get_attesting_balance(state: BeaconState, attestations: Sequence[PendingAttestation]) -> Gwei: + """ + Return the combined effective balance of the set of unslashed validators participating in ``attestations``. + Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. + """ + return get_total_balance(state, get_unslashed_attesting_indices(state, attestations)) + + +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_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)) + current_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_current_epoch(state)) + total_active_balance = get_total_active_balance(state) + previous_target_balance = get_total_balance(state, previous_indices) + current_target_balance = get_total_balance(state, current_indices) + weigh_justification_and_finalization(state, total_active_balance, previous_target_balance, current_target_balance) + + +def weigh_justification_and_finalization(state: BeaconState, + total_active_balance: Gwei, + previous_epoch_target_balance: Gwei, + current_epoch_target_balance: Gwei) -> None: + 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 + + # 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 + if previous_epoch_target_balance * 3 >= total_active_balance * 2: + state.current_justified_checkpoint = Checkpoint(epoch=previous_epoch, + root=get_block_root(state, previous_epoch)) + state.justification_bits[1] = 0b1 + if current_epoch_target_balance * 3 >= total_active_balance * 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 + + +def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: + """ + Return the base reward for the validator defined by ``index`` with respect to the current ``state``. + """ + increments = state.validators[index].effective_balance // EFFECTIVE_BALANCE_INCREMENT + return Gwei(increments * get_base_reward_per_increment(state)) + + +def get_proposer_reward(state: BeaconState, attesting_index: ValidatorIndex) -> Gwei: + return Gwei(get_base_reward(state, attesting_index) // PROPOSER_REWARD_QUOTIENT) + + +def get_finality_delay(state: BeaconState) -> uint64: + return get_previous_epoch(state) - state.finalized_checkpoint.epoch + + +def is_in_inactivity_leak(state: BeaconState) -> bool: + return get_finality_delay(state) > MIN_EPOCHS_TO_INACTIVITY_PENALTY + + +def get_eligible_validator_indices(state: BeaconState) -> Sequence[ValidatorIndex]: + previous_epoch = get_previous_epoch(state) + 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) + ] + + +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)] * 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) + 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 + if is_in_inactivity_leak(state): + # Since full base reward will be canceled out by inactivity penalty deltas, + # optimal participation receives full base reward compensation here. + rewards[index] += get_base_reward(state, index) + else: + 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 + + +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 get_attestation_component_deltas(state, matching_source_attestations) + + +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 get_attestation_component_deltas(state, matching_target_attestations) + + +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 get_attestation_component_deltas(state, matching_head_attestations) + + +def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return proposer and inclusion delay micro-rewards/penalties for each validator. + """ + rewards = [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 + if index in get_attesting_indices(state, a.data, a.aggregation_bits) + ], key=lambda a: a.inclusion_delay) + rewards[attestation.proposer_index] += get_proposer_reward(state, index) + max_attester_reward = Gwei(get_base_reward(state, index) - get_proposer_reward(state, index)) + 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 + + +def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return the inactivity penalty deltas by considering timely target participation flags and inactivity scores. + """ + rewards = [Gwei(0) for _ in range(len(state.validators))] + penalties = [Gwei(0) for _ in range(len(state.validators))] + previous_epoch = get_previous_epoch(state) + matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, previous_epoch) + for index in get_eligible_validator_indices(state): + if index not in matching_target_indices: + penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index] + # [Modified in Merge] + penalty_denominator = config.INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_MERGE + penalties[index] += Gwei(penalty_numerator // penalty_denominator) + return rewards, penalties + + +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) + 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 + + +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_index_deltas(state, flag_index) for flag_index in range(len(PARTICIPATION_FLAG_WEIGHTS))] + 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]) + + +def process_registry_updates(state: BeaconState) -> None: + # Process activation eligibility and ejections + for index, validator in enumerate(state.validators): + if is_eligible_for_activation_queue(validator): + validator.activation_eligibility_epoch = get_current_epoch(state) + 1 + + if ( + is_active_validator(validator, get_current_epoch(state)) + and validator.effective_balance <= config.EJECTION_BALANCE + ): + initiate_validator_exit(state, ValidatorIndex(index)) + + # Queue validators eligible for activation and not yet dequeued for activation + activation_queue = sorted([ + index for index, validator in enumerate(state.validators) + if is_eligible_for_activation(state, validator) + # Order by the sequence of activation_eligibility_epoch setting and then index + ], key=lambda index: (state.validators[index].activation_eligibility_epoch, index)) + # Dequeued validators for activation up to churn limit + for index in activation_queue[:get_validator_churn_limit(state)]: + validator = state.validators[index] + validator.activation_epoch = compute_activation_exit_epoch(get_current_epoch(state)) + + +def process_slashings(state: BeaconState) -> None: + epoch = get_current_epoch(state) + total_balance = get_total_active_balance(state) + adjusted_total_slashing_balance = min( + sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_MERGE, # [Modified in Merge] + total_balance + ) + for index, validator in enumerate(state.validators): + if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch: + increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow + penalty_numerator = validator.effective_balance // increment * adjusted_total_slashing_balance + penalty = penalty_numerator // total_balance * increment + decrease_balance(state, ValidatorIndex(index), penalty) + + +def process_eth1_data_reset(state: BeaconState) -> None: + next_epoch = Epoch(get_current_epoch(state) + 1) + # Reset eth1 data votes + if next_epoch % EPOCHS_PER_ETH1_VOTING_PERIOD == 0: + state.eth1_data_votes = [] + + +def process_effective_balance_updates(state: BeaconState) -> None: + # Update effective balances with hysteresis + for index, validator in enumerate(state.validators): + balance = state.balances[index] + HYSTERESIS_INCREMENT = uint64(EFFECTIVE_BALANCE_INCREMENT // HYSTERESIS_QUOTIENT) + DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER + UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER + if ( + balance + DOWNWARD_THRESHOLD < validator.effective_balance + or validator.effective_balance + UPWARD_THRESHOLD < balance + ): + validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) + + +def process_slashings_reset(state: BeaconState) -> None: + next_epoch = Epoch(get_current_epoch(state) + 1) + # Reset slashings + state.slashings[next_epoch % EPOCHS_PER_SLASHINGS_VECTOR] = Gwei(0) + + +def process_randao_mixes_reset(state: BeaconState) -> None: + current_epoch = get_current_epoch(state) + next_epoch = Epoch(current_epoch + 1) + # Set randao mix + state.randao_mixes[next_epoch % EPOCHS_PER_HISTORICAL_VECTOR] = get_randao_mix(state, current_epoch) + + +def process_historical_roots_update(state: BeaconState) -> None: + # Set historical root accumulator + next_epoch = Epoch(get_current_epoch(state) + 1) + if next_epoch % (SLOTS_PER_HISTORICAL_ROOT // SLOTS_PER_EPOCH) == 0: + historical_batch = HistoricalBatch(block_roots=state.block_roots, state_roots=state.state_roots) + state.historical_roots.append(hash_tree_root(historical_batch)) + + +def process_participation_record_updates(state: BeaconState) -> None: + # Rotate current/previous epoch attestations + state.previous_epoch_attestations = state.current_epoch_attestations + state.current_epoch_attestations = [] + + +def process_block(state: BeaconState, block: BeaconBlock) -> None: + process_block_header(state, block) + if is_execution_enabled(state, block.body): + process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [New in Merge] + process_randao(state, block.body) + process_eth1_data(state, block.body) + process_operations(state, block.body) + process_sync_aggregate(state, block.body.sync_aggregate) + + +def process_block_header(state: BeaconState, block: BeaconBlock) -> None: + # Verify that the slots match + assert block.slot == state.slot + # Verify that the block is newer than latest block header + assert block.slot > state.latest_block_header.slot + # Verify that proposer index is the correct index + assert block.proposer_index == get_beacon_proposer_index(state) + # Verify that the parent matches + assert block.parent_root == hash_tree_root(state.latest_block_header) + # Cache current block as the new latest block + state.latest_block_header = BeaconBlockHeader( + slot=block.slot, + proposer_index=block.proposer_index, + parent_root=block.parent_root, + state_root=Bytes32(), # Overwritten in the next process_slot call + body_root=hash_tree_root(block.body), + ) + + # Verify proposer is not slashed + proposer = state.validators[block.proposer_index] + assert not proposer.slashed + + +def process_randao(state: BeaconState, body: BeaconBlockBody) -> None: + epoch = get_current_epoch(state) + # Verify RANDAO reveal + proposer = state.validators[get_beacon_proposer_index(state)] + signing_root = compute_signing_root(epoch, get_domain(state, DOMAIN_RANDAO)) + assert bls.Verify(proposer.pubkey, signing_root, body.randao_reveal) + # Mix in RANDAO reveal + mix = xor(get_randao_mix(state, epoch), hash(body.randao_reveal)) + state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR] = mix + + +def process_eth1_data(state: BeaconState, body: BeaconBlockBody) -> None: + state.eth1_data_votes.append(body.eth1_data) + if state.eth1_data_votes.count(body.eth1_data) * 2 > EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH: + state.eth1_data = body.eth1_data + + +def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: + # Verify that outstanding deposits are processed up to the maximum number of deposits + assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index) + + def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: + for operation in operations: + fn(state, operation) + + for_ops(body.proposer_slashings, process_proposer_slashing) + for_ops(body.attester_slashings, process_attester_slashing) + for_ops(body.attestations, process_attestation) + for_ops(body.deposits, process_deposit) + for_ops(body.voluntary_exits, process_voluntary_exit) + + +def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None: + header_1 = proposer_slashing.signed_header_1.message + header_2 = proposer_slashing.signed_header_2.message + + # Verify header slots match + assert header_1.slot == header_2.slot + # Verify header proposer indices match + assert header_1.proposer_index == header_2.proposer_index + # Verify the headers are different + assert header_1 != header_2 + # Verify the proposer is slashable + proposer = state.validators[header_1.proposer_index] + assert is_slashable_validator(proposer, get_current_epoch(state)) + # Verify signatures + for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2): + domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(signed_header.message.slot)) + signing_root = compute_signing_root(signed_header.message, domain) + assert bls.Verify(proposer.pubkey, signing_root, signed_header.signature) + + slash_validator(state, header_1.proposer_index) + + +def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSlashing) -> None: + attestation_1 = attester_slashing.attestation_1 + attestation_2 = attester_slashing.attestation_2 + assert is_slashable_attestation_data(attestation_1.data, attestation_2.data) + assert is_valid_indexed_attestation(state, attestation_1) + assert is_valid_indexed_attestation(state, attestation_2) + + slashed_any = False + indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices) + for index in sorted(indices): + if is_slashable_validator(state.validators[index], get_current_epoch(state)): + slash_validator(state, index) + slashed_any = True + assert slashed_any + + +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) + + # Participation flag indices + participation_flag_indices = get_attestation_participation_flag_indices(state, data, state.slot - data.slot) + + # Verify signature + assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) + + # Update epoch participation flags + if data.target.epoch == get_current_epoch(state): + epoch_participation = state.current_epoch_participation + else: + epoch_participation = state.previous_epoch_participation + + proposer_reward_numerator = 0 + for index in get_attesting_indices(state, data, attestation.aggregation_bits): + for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS): + if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index): + epoch_participation[index] = add_flag(epoch_participation[index], flag_index) + proposer_reward_numerator += get_base_reward(state, index) * weight + + # Reward proposer + proposer_reward_denominator = (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT + proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator) + increase_balance(state, get_beacon_proposer_index(state), proposer_reward) + + +def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: + amount = deposit.data.amount + effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) + + return Validator( + pubkey=deposit.data.pubkey, + withdrawal_credentials=deposit.data.withdrawal_credentials, + activation_eligibility_epoch=FAR_FUTURE_EPOCH, + activation_epoch=FAR_FUTURE_EPOCH, + exit_epoch=FAR_FUTURE_EPOCH, + withdrawable_epoch=FAR_FUTURE_EPOCH, + effective_balance=effective_balance, + ) + + +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 = [validator.pubkey for validator 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) + # Initialize validator if the deposit signature is valid + if bls.Verify(pubkey, signing_root, deposit.data.signature): + state.validators.append(get_validator_from_deposit(state, deposit)) + state.balances.append(amount) + state.previous_epoch_participation.append(ParticipationFlags(0b0000_0000)) + state.current_epoch_participation.append(ParticipationFlags(0b0000_0000)) + state.inactivity_scores.append(uint64(0)) + else: + # Increase balance by deposit amount + index = ValidatorIndex(validator_pubkeys.index(pubkey)) + increase_balance(state, index, amount) + + +def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVoluntaryExit) -> None: + voluntary_exit = signed_voluntary_exit.message + validator = state.validators[voluntary_exit.validator_index] + # Verify the validator is active + assert is_active_validator(validator, get_current_epoch(state)) + # Verify exit has not been initiated + assert validator.exit_epoch == FAR_FUTURE_EPOCH + # Exits must specify an epoch when they become valid; they are not valid before then + assert get_current_epoch(state) >= voluntary_exit.epoch + # Verify the validator has been active long enough + assert get_current_epoch(state) >= validator.activation_epoch + config.SHARD_COMMITTEE_PERIOD + # Verify signature + domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch) + signing_root = compute_signing_root(voluntary_exit, domain) + assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature) + # Initiate exit + initiate_validator_exit(state, voluntary_exit.validator_index) + + +def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store: + assert anchor_block.state_root == hash_tree_root(anchor_state) + anchor_root = hash_tree_root(anchor_block) + anchor_epoch = get_current_epoch(anchor_state) + justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) + finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) + proposer_boost_root = Root() + return Store( + time=uint64(anchor_state.genesis_time + config.SECONDS_PER_SLOT * anchor_state.slot), + genesis_time=anchor_state.genesis_time, + justified_checkpoint=justified_checkpoint, + finalized_checkpoint=finalized_checkpoint, + best_justified_checkpoint=justified_checkpoint, + proposer_boost_root=proposer_boost_root, + blocks={anchor_root: copy(anchor_block)}, + block_states={anchor_root: copy(anchor_state)}, + checkpoint_states={justified_checkpoint: copy(anchor_state)}, + ) + + +def get_slots_since_genesis(store: Store) -> int: + return (store.time - store.genesis_time) // config.SECONDS_PER_SLOT + + +def get_current_slot(store: Store) -> Slot: + return Slot(GENESIS_SLOT + get_slots_since_genesis(store)) + + +def compute_slots_since_epoch_start(slot: Slot) -> int: + return slot - compute_start_slot_at_epoch(compute_epoch_at_slot(slot)) + + +def get_ancestor(store: Store, root: Root, slot: Slot) -> Root: + block = store.blocks[root] + if block.slot > slot: + return get_ancestor(store, block.parent_root, slot) + elif block.slot == slot: + return root + else: + # root is older than queried slot, thus a skip slot. Return most recent root prior to slot + return root + + +def get_latest_attesting_balance(store: Store, root: Root) -> Gwei: + state = store.checkpoint_states[store.justified_checkpoint] + active_indices = get_active_validator_indices(state, get_current_epoch(state)) + attestation_score = Gwei(sum( + state.validators[i].effective_balance for i in active_indices + if (i in store.latest_messages + and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root) + )) + proposer_score = Gwei(0) + if store.proposer_boost_root != Root(): + block = store.blocks[root] + if get_ancestor(store, root, block.slot) == store.proposer_boost_root: + num_validators = len(get_active_validator_indices(state, get_current_epoch(state))) + avg_balance = get_total_active_balance(state) // num_validators + committee_size = num_validators // SLOTS_PER_EPOCH + committee_weight = committee_size * avg_balance + proposer_score = (committee_weight * config.PROPOSER_SCORE_BOOST) // 100 + return attestation_score + proposer_score + + +def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconBlock]) -> bool: + block = store.blocks[block_root] + children = [ + root for root in store.blocks.keys() + if store.blocks[root].parent_root == block_root + ] + + # If any children branches contain expected finalized/justified checkpoints, + # add to filtered block-tree and signal viability to parent. + if any(children): + filter_block_tree_result = [filter_block_tree(store, child, blocks) for child in children] + if any(filter_block_tree_result): + blocks[block_root] = block + return True + return False + + # If leaf block, check finalized/justified checkpoints as matching latest. + head_state = store.block_states[block_root] + + correct_justified = ( + store.justified_checkpoint.epoch == GENESIS_EPOCH + or head_state.current_justified_checkpoint == store.justified_checkpoint + ) + correct_finalized = ( + store.finalized_checkpoint.epoch == GENESIS_EPOCH + or head_state.finalized_checkpoint == store.finalized_checkpoint + ) + # If expected finalized/justified, add to viable block-tree and signal viability to parent. + if correct_justified and correct_finalized: + blocks[block_root] = block + return True + + # Otherwise, branch not viable + return False + + +def get_filtered_block_tree(store: Store) -> Dict[Root, BeaconBlock]: + """ + Retrieve a filtered block tree from ``store``, only returning branches + whose leaf state's justified/finalized info agrees with that in ``store``. + """ + base = store.justified_checkpoint.root + blocks: Dict[Root, BeaconBlock] = {} + filter_block_tree(store, base, blocks) + return blocks + + +def get_head(store: Store) -> Root: + # Get filtered block tree that only includes viable branches + blocks = get_filtered_block_tree(store) + # Execute the LMD-GHOST fork choice + head = store.justified_checkpoint.root + while True: + children = [ + root for root in blocks.keys() + if blocks[root].parent_root == head + ] + if len(children) == 0: + return head + # Sort by latest attesting balance with ties broken lexicographically + head = max(children, key=lambda root: (get_latest_attesting_balance(store, root), root)) + + +def should_update_justified_checkpoint(store: Store, new_justified_checkpoint: Checkpoint) -> bool: + """ + To address the bouncing attack, only update conflicting justified + checkpoints in the fork choice if in the early slots of the epoch. + Otherwise, delay incorporation of new justified checkpoint until next epoch boundary. + + See https://ethresear.ch/t/prevention-of-bouncing-attack-on-ffg/6114 for more detailed analysis and discussion. + """ + if compute_slots_since_epoch_start(get_current_slot(store)) < SAFE_SLOTS_TO_UPDATE_JUSTIFIED: + return True + + justified_slot = compute_start_slot_at_epoch(store.justified_checkpoint.epoch) + if not get_ancestor(store, new_justified_checkpoint.root, justified_slot) == store.justified_checkpoint.root: + return False + + return True + + +def validate_target_epoch_against_current_time(store: Store, attestation: Attestation) -> None: + target = attestation.data.target + + # Attestations must be from the current or previous epoch + 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] + + +def validate_on_attestation(store: Store, attestation: Attestation, is_from_block: bool) -> None: + target = attestation.data.target + + # If the given attestation is not from a beacon block message, we have to check the target epoch scope. + if not is_from_block: + validate_target_epoch_against_current_time(store, attestation) + + # Check that the epoch number and slot number are matching + assert target.epoch == compute_epoch_at_slot(attestation.data.slot) + + # 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 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 + # Attestations must not be for blocks in the future. If not, the attestation should not be considered + assert store.blocks[attestation.data.beacon_block_root].slot <= attestation.data.slot + + # LMD vote must be consistent with FFG vote target + target_slot = compute_start_slot_at_epoch(target.epoch) + assert target.root == get_ancestor(store, attestation.data.beacon_block_root, target_slot) + + # Attestations can only affect the fork choice of subsequent slots. + # Delay consideration in the fork choice until their slot is in the past. + assert get_current_slot(store) >= attestation.data.slot + 1 + + +def store_target_checkpoint_state(store: Store, target: Checkpoint) -> None: + # Store target checkpoint state if not yet seen + if target not in store.checkpoint_states: + base_state = copy(store.block_states[target.root]) + if base_state.slot < compute_start_slot_at_epoch(target.epoch): + process_slots(base_state, compute_start_slot_at_epoch(target.epoch)) + store.checkpoint_states[target] = base_state + + +def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None: + target = attestation.data.target + beacon_block_root = attestation.data.beacon_block_root + for i in attesting_indices: + if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch: + store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root) + + +def on_tick(store: Store, time: uint64) -> None: + previous_slot = get_current_slot(store) + + # update store time + store.time = time + + current_slot = get_current_slot(store) + + # Reset store.proposer_boost_root if this is a new slot + if current_slot > previous_slot: + store.proposer_boost_root = Root() + + # Not a new epoch, return + if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0): + return + + # Update store.justified_checkpoint if a better checkpoint on the store.finalized_checkpoint chain + if store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch: + finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + ancestor_at_finalized_slot = get_ancestor(store, store.best_justified_checkpoint.root, finalized_slot) + if ancestor_at_finalized_slot == store.finalized_checkpoint.root: + store.justified_checkpoint = store.best_justified_checkpoint + + +def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: + """ + Run ``on_block`` upon receiving a new block. + + A block that is asserted as invalid due to unavailable PoW block may be valid at a later time, + consider scheduling it for later processing in such case. + """ + block = signed_block.message + # Parent block must be known + assert block.parent_root in store.block_states + # Make a copy of the state to avoid mutability issues + pre_state = copy(store.block_states[block.parent_root]) + # Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past. + assert get_current_slot(store) >= block.slot + + # Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) + finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + assert block.slot > finalized_slot + # Check block is a descendant of the finalized block at the checkpoint finalized slot + assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root + + # Check the block is valid and compute the post-state + state = pre_state.copy() + state_transition(state, signed_block, True) + + # [New in Merge] + if is_merge_transition_block(pre_state, block.body): + validate_merge_block(block) + + # Add new block to the store + store.blocks[hash_tree_root(block)] = block + # Add new state for this block to the store + store.block_states[hash_tree_root(block)] = state + + # Add proposer score boost if the block is timely + time_into_slot = (store.time - store.genesis_time) % config.SECONDS_PER_SLOT + is_before_attesting_interval = time_into_slot < config.SECONDS_PER_SLOT // INTERVALS_PER_SLOT + if get_current_slot(store) == block.slot and is_before_attesting_interval: + store.proposer_boost_root = hash_tree_root(block) + + # Update justified checkpoint + if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: + if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch: + store.best_justified_checkpoint = state.current_justified_checkpoint + if should_update_justified_checkpoint(store, state.current_justified_checkpoint): + store.justified_checkpoint = state.current_justified_checkpoint + + # Update finalized checkpoint + if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch: + store.finalized_checkpoint = state.finalized_checkpoint + store.justified_checkpoint = state.current_justified_checkpoint + + +def on_attestation(store: Store, attestation: Attestation, is_from_block: bool=False) -> None: + """ + Run ``on_attestation`` upon receiving a new ``attestation`` from either within a block or directly on the wire. + + An ``attestation`` that is asserted as invalid may be valid at a later time, + consider scheduling it for later processing in such case. + """ + validate_on_attestation(store, attestation, is_from_block) + + store_target_checkpoint_state(store, attestation.data.target) + + # Get state at the `target` to fully validate attestation + target_state = store.checkpoint_states[attestation.data.target] + indexed_attestation = get_indexed_attestation(target_state, attestation) + assert is_valid_indexed_attestation(target_state, indexed_attestation) + + # Update latest messages for attesting indices + update_latest_messages(store, indexed_attestation.attesting_indices, attestation) + + +def check_if_validator_active(state: BeaconState, validator_index: ValidatorIndex) -> bool: + validator = state.validators[validator_index] + return is_active_validator(validator, get_current_epoch(state)) + + +def get_committee_assignment(state: BeaconState, + epoch: Epoch, + validator_index: ValidatorIndex + ) -> Optional[Tuple[Sequence[ValidatorIndex], CommitteeIndex, Slot]]: + """ + Return the committee assignment in the ``epoch`` for ``validator_index``. + ``assignment`` returned is a tuple of the following form: + * ``assignment[0]`` is the list of validators in the committee + * ``assignment[1]`` is the index to which the committee is assigned + * ``assignment[2]`` is the slot at which the committee is assigned + Return None if no assignment. + """ + next_epoch = Epoch(get_current_epoch(state) + 1) + assert epoch <= next_epoch + + start_slot = compute_start_slot_at_epoch(epoch) + committee_count_per_slot = get_committee_count_per_slot(state, epoch) + for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH): + for index in range(committee_count_per_slot): + committee = get_beacon_committee(state, Slot(slot), CommitteeIndex(index)) + if validator_index in committee: + return committee, CommitteeIndex(index), Slot(slot) + return None + + +def is_proposer(state: BeaconState, validator_index: ValidatorIndex) -> bool: + return get_beacon_proposer_index(state) == validator_index + + +def get_epoch_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_RANDAO, compute_epoch_at_slot(block.slot)) + signing_root = compute_signing_root(compute_epoch_at_slot(block.slot), domain) + return bls.Sign(privkey, signing_root) + + +def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64: + return uint64(state.genesis_time + slot * config.SECONDS_PER_SLOT) + + +def voting_period_start_time(state: BeaconState) -> uint64: + eth1_voting_period_start_slot = Slot(state.slot - state.slot % (EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH)) + return compute_time_at_slot(state, eth1_voting_period_start_slot) + + +def is_candidate_block(block: Eth1Block, period_start: uint64) -> bool: + return ( + block.timestamp + config.SECONDS_PER_ETH1_BLOCK * config.ETH1_FOLLOW_DISTANCE <= period_start + and block.timestamp + config.SECONDS_PER_ETH1_BLOCK * config.ETH1_FOLLOW_DISTANCE * 2 >= period_start + ) + + +def get_eth1_vote(state: BeaconState, eth1_chain: Sequence[Eth1Block]) -> Eth1Data: + period_start = voting_period_start_time(state) + # `eth1_chain` abstractly represents all blocks in the eth1 chain sorted by ascending block height + votes_to_consider = [ + get_eth1_data(block) for block in eth1_chain + if ( + is_candidate_block(block, period_start) + # Ensure cannot move back to earlier deposit contract states + and get_eth1_data(block).deposit_count >= state.eth1_data.deposit_count + ) + ] + + # Valid votes already cast during this period + valid_votes = [vote for vote in state.eth1_data_votes if vote in votes_to_consider] + + # Default vote on latest eth1 block data in the period range unless eth1 chain is not live + # Non-substantive casting for linter + state_eth1_data: Eth1Data = state.eth1_data + default_vote = votes_to_consider[len(votes_to_consider) - 1] if any(votes_to_consider) else state_eth1_data + + return max( + valid_votes, + key=lambda v: (valid_votes.count(v), -valid_votes.index(v)), # Tiebreak by smallest distance + default=default_vote + ) + + +def compute_new_state_root(state: BeaconState, block: BeaconBlock) -> Root: + temp_state: BeaconState = state.copy() + signed_block = SignedBeaconBlock(message=block) + state_transition(temp_state, signed_block, validate_result=False) + return hash_tree_root(temp_state) + + +def get_block_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(block.slot)) + signing_root = compute_signing_root(block, domain) + return bls.Sign(privkey, signing_root) + + +def get_attestation_signature(state: BeaconState, attestation_data: AttestationData, privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch) + signing_root = compute_signing_root(attestation_data, domain) + return bls.Sign(privkey, signing_root) + + +def compute_subnet_for_attestation(committees_per_slot: uint64, slot: Slot, committee_index: CommitteeIndex) -> uint64: + """ + Compute the correct subnet for an attestation for Phase 0. + Note, this mimics expected future behavior where attestations will be mapped to their shard subnet. + """ + slots_since_epoch_start = uint64(slot % SLOTS_PER_EPOCH) + committees_since_epoch_start = committees_per_slot * slots_since_epoch_start + + return uint64((committees_since_epoch_start + committee_index) % ATTESTATION_SUBNET_COUNT) + + +def get_slot_signature(state: BeaconState, slot: Slot, privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_SELECTION_PROOF, compute_epoch_at_slot(slot)) + signing_root = compute_signing_root(slot, domain) + return bls.Sign(privkey, signing_root) + + +def is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex, slot_signature: BLSSignature) -> bool: + committee = get_beacon_committee(state, slot, index) + modulo = max(1, len(committee) // TARGET_AGGREGATORS_PER_COMMITTEE) + return bytes_to_uint64(hash(slot_signature)[0:8]) % modulo == 0 + + +def get_aggregate_signature(attestations: Sequence[Attestation]) -> BLSSignature: + signatures = [attestation.signature for attestation in attestations] + return bls.Aggregate(signatures) + + +def get_aggregate_and_proof(state: BeaconState, + aggregator_index: ValidatorIndex, + aggregate: Attestation, + privkey: int) -> AggregateAndProof: + return AggregateAndProof( + aggregator_index=aggregator_index, + aggregate=aggregate, + selection_proof=get_slot_signature(state, aggregate.data.slot, privkey), + ) + + +def get_aggregate_and_proof_signature(state: BeaconState, + aggregate_and_proof: AggregateAndProof, + privkey: int) -> BLSSignature: + aggregate = aggregate_and_proof.aggregate + domain = get_domain(state, DOMAIN_AGGREGATE_AND_PROOF, compute_epoch_at_slot(aggregate.data.slot)) + signing_root = compute_signing_root(aggregate_and_proof, domain) + return bls.Sign(privkey, signing_root) + + +def compute_weak_subjectivity_period(state: BeaconState) -> uint64: + """ + Returns the weak subjectivity period for the current ``state``. + This computation takes into account the effect of: + - validator set churn (bounded by ``get_validator_churn_limit()`` per epoch), and + - validator balance top-ups (bounded by ``MAX_DEPOSITS * SLOTS_PER_EPOCH`` per epoch). + A detailed calculation can be found at: + https://github.com/runtimeverification/beacon-chain-verification/blob/master/weak-subjectivity/weak-subjectivity-analysis.pdf + """ + ws_period = config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + N = len(get_active_validator_indices(state, get_current_epoch(state))) + t = get_total_active_balance(state) // N // ETH_TO_GWEI + T = MAX_EFFECTIVE_BALANCE // ETH_TO_GWEI + delta = get_validator_churn_limit(state) + Delta = MAX_DEPOSITS * SLOTS_PER_EPOCH + D = SAFETY_DECAY + + if T * (200 + 3 * D) < t * (200 + 12 * D): + epochs_for_validator_set_churn = ( + N * (t * (200 + 12 * D) - T * (200 + 3 * D)) // (600 * delta * (2 * t + T)) + ) + epochs_for_balance_top_ups = ( + N * (200 + 3 * D) // (600 * Delta) + ) + ws_period += max(epochs_for_validator_set_churn, epochs_for_balance_top_ups) + else: + ws_period += ( + 3 * N * D * t // (200 * Delta * (T - t)) + ) + + return ws_period + + +def is_within_weak_subjectivity_period(store: Store, ws_state: BeaconState, ws_checkpoint: Checkpoint) -> bool: + # Clients may choose to validate the input state against the input Weak Subjectivity Checkpoint + assert ws_state.latest_block_header.state_root == ws_checkpoint.root + assert compute_epoch_at_slot(ws_state.slot) == ws_checkpoint.epoch + + ws_period = compute_weak_subjectivity_period(ws_state) + ws_state_epoch = compute_epoch_at_slot(ws_state.slot) + current_epoch = compute_epoch_at_slot(get_current_slot(store)) + return current_epoch <= ws_state_epoch + ws_period + + +def add_flag(flags: ParticipationFlags, flag_index: int) -> ParticipationFlags: + """ + Return a new ``ParticipationFlags`` adding ``flag_index`` to ``flags``. + """ + flag = ParticipationFlags(2**flag_index) + return flags | flag + + +def has_flag(flags: ParticipationFlags, flag_index: int) -> bool: + """ + Return whether ``flags`` has ``flag_index`` set. + """ + flag = ParticipationFlags(2**flag_index) + return flags & flag == flag + + +def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorIndex]: + """ + Return the sync committee indices, with possible duplicates, for the next sync committee. + """ + epoch = Epoch(get_current_epoch(state) + 1) + + MAX_RANDOM_BYTE = 2**8 - 1 + active_validator_indices = get_active_validator_indices(state, epoch) + active_validator_count = uint64(len(active_validator_indices)) + seed = get_seed(state, epoch, DOMAIN_SYNC_COMMITTEE) + i = 0 + sync_committee_indices: List[ValidatorIndex] = [] + while len(sync_committee_indices) < SYNC_COMMITTEE_SIZE: + shuffled_index = compute_shuffled_index(uint64(i % active_validator_count), active_validator_count, seed) + candidate_index = active_validator_indices[shuffled_index] + random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] + effective_balance = state.validators[candidate_index].effective_balance + if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: + sync_committee_indices.append(candidate_index) + i += 1 + return sync_committee_indices + + +def get_next_sync_committee(state: BeaconState) -> SyncCommittee: + """ + Return the next sync committee, with possible pubkey duplicates. + """ + indices = get_next_sync_committee_indices(state) + pubkeys = [state.validators[index].pubkey for index in indices] + aggregate_pubkey = eth_aggregate_pubkeys(pubkeys) + return SyncCommittee(pubkeys=pubkeys, aggregate_pubkey=aggregate_pubkey) + + +def get_base_reward_per_increment(state: BeaconState) -> Gwei: + return Gwei(EFFECTIVE_BALANCE_INCREMENT * BASE_REWARD_FACTOR // integer_squareroot(get_total_active_balance(state))) + + +def get_unslashed_participating_indices(state: BeaconState, flag_index: int, epoch: Epoch) -> Set[ValidatorIndex]: + """ + Return the set of validator indices that are both active and unslashed for the given ``flag_index`` and ``epoch``. + """ + 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 + active_validator_indices = get_active_validator_indices(state, epoch) + participating_indices = [i for i in active_validator_indices if has_flag(epoch_participation[i], flag_index)] + return set(filter(lambda index: not state.validators[index].slashed, participating_indices)) + + +def get_attestation_participation_flag_indices(state: BeaconState, + data: AttestationData, + inclusion_delay: uint64) -> Sequence[int]: + """ + Return the flag indices that are satisfied by an attestation. + """ + if data.target.epoch == get_current_epoch(state): + justified_checkpoint = state.current_justified_checkpoint + else: + justified_checkpoint = state.previous_justified_checkpoint + + # Matching roots + is_matching_source = data.source == justified_checkpoint + is_matching_target = is_matching_source and data.target.root == get_block_root(state, data.target.epoch) + is_matching_head = is_matching_target and data.beacon_block_root == get_block_root_at_slot(state, data.slot) + assert is_matching_source + + participation_flag_indices = [] + if is_matching_source and inclusion_delay <= integer_squareroot(SLOTS_PER_EPOCH): + participation_flag_indices.append(TIMELY_SOURCE_FLAG_INDEX) + if is_matching_target and inclusion_delay <= SLOTS_PER_EPOCH: + participation_flag_indices.append(TIMELY_TARGET_FLAG_INDEX) + if is_matching_head and inclusion_delay == MIN_ATTESTATION_INCLUSION_DELAY: + participation_flag_indices.append(TIMELY_HEAD_FLAG_INDEX) + + return participation_flag_indices + + +def get_flag_index_deltas(state: BeaconState, flag_index: int) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return the deltas for a given ``flag_index`` by scanning through the participation flags. + """ + rewards = [Gwei(0)] * len(state.validators) + penalties = [Gwei(0)] * len(state.validators) + previous_epoch = get_previous_epoch(state) + unslashed_participating_indices = get_unslashed_participating_indices(state, flag_index, previous_epoch) + weight = PARTICIPATION_FLAG_WEIGHTS[flag_index] + unslashed_participating_balance = get_total_balance(state, unslashed_participating_indices) + unslashed_participating_increments = unslashed_participating_balance // EFFECTIVE_BALANCE_INCREMENT + active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT + for index in get_eligible_validator_indices(state): + base_reward = get_base_reward(state, index) + if index in unslashed_participating_indices: + if not is_in_inactivity_leak(state): + reward_numerator = base_reward * weight * unslashed_participating_increments + rewards[index] += Gwei(reward_numerator // (active_increments * WEIGHT_DENOMINATOR)) + elif flag_index != TIMELY_HEAD_FLAG_INDEX: + penalties[index] += Gwei(base_reward * weight // WEIGHT_DENOMINATOR) + return rewards, penalties + + +def process_sync_aggregate(state: BeaconState, sync_aggregate: SyncAggregate) -> None: + # Verify sync committee aggregate signature signing over the previous slot block root + committee_pubkeys = state.current_sync_committee.pubkeys + participant_pubkeys = [pubkey for pubkey, bit in zip(committee_pubkeys, sync_aggregate.sync_committee_bits) if bit] + previous_slot = max(state.slot, Slot(1)) - Slot(1) + domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, compute_epoch_at_slot(previous_slot)) + signing_root = compute_signing_root(get_block_root_at_slot(state, previous_slot), domain) + assert eth_fast_aggregate_verify(participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature) + + # Compute participant and proposer rewards + total_active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT + total_base_rewards = Gwei(get_base_reward_per_increment(state) * total_active_increments) + max_participant_rewards = Gwei(total_base_rewards * SYNC_REWARD_WEIGHT // WEIGHT_DENOMINATOR // SLOTS_PER_EPOCH) + participant_reward = Gwei(max_participant_rewards // SYNC_COMMITTEE_SIZE) + proposer_reward = Gwei(participant_reward * PROPOSER_WEIGHT // (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)) + + # Apply participant and proposer rewards + all_pubkeys = [v.pubkey for v in state.validators] + committee_indices = [ValidatorIndex(all_pubkeys.index(pubkey)) for pubkey in state.current_sync_committee.pubkeys] + for participant_index, participation_bit in zip(committee_indices, sync_aggregate.sync_committee_bits): + if participation_bit: + increase_balance(state, participant_index, participant_reward) + increase_balance(state, get_beacon_proposer_index(state), proposer_reward) + else: + decrease_balance(state, participant_index, participant_reward) + + +def process_inactivity_updates(state: BeaconState) -> None: + # Skip the genesis epoch as score updates are based on the previous epoch participation + if get_current_epoch(state) == GENESIS_EPOCH: + return + + for index in get_eligible_validator_indices(state): + # Increase the inactivity score of inactive validators + if index in get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)): + state.inactivity_scores[index] -= min(1, state.inactivity_scores[index]) + else: + state.inactivity_scores[index] += config.INACTIVITY_SCORE_BIAS + # Decrease the inactivity score of all eligible validators during a leak-free epoch + if not is_in_inactivity_leak(state): + state.inactivity_scores[index] -= min(config.INACTIVITY_SCORE_RECOVERY_RATE, state.inactivity_scores[index]) + + +def process_participation_flag_updates(state: BeaconState) -> None: + state.previous_epoch_participation = state.current_epoch_participation + state.current_epoch_participation = [ParticipationFlags(0b0000_0000) for _ in range(len(state.validators))] + + +def process_sync_committee_updates(state: BeaconState) -> None: + 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_next_sync_committee(state) + + +def eth_aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey: + return bls.AggregatePKs(pubkeys) + + +def eth_fast_aggregate_verify(pubkeys: Sequence[BLSPubkey], message: Bytes32, signature: BLSSignature) -> bool: + """ + Wrapper to ``bls.FastAggregateVerify`` accepting the ``G2_POINT_AT_INFINITY`` signature when ``pubkeys`` is empty. + """ + if len(pubkeys) == 0 and signature == G2_POINT_AT_INFINITY: + return True + return bls.FastAggregateVerify(pubkeys, message, signature) + + +def translate_participation(state: BeaconState, pending_attestations: Sequence[phase0.PendingAttestation]) -> None: + for attestation in pending_attestations: + data = attestation.data + inclusion_delay = attestation.inclusion_delay + # Translate attestation inclusion info to flag indices + participation_flag_indices = get_attestation_participation_flag_indices(state, data, inclusion_delay) + + # Apply flags to all attesting validators + epoch_participation = state.previous_epoch_participation + for index in get_attesting_indices(state, data, attestation.aggregation_bits): + for flag_index in participation_flag_indices: + epoch_participation[index] = add_flag(epoch_participation[index], flag_index) + + +def upgrade_to_altair(pre: phase0.BeaconState) -> BeaconState: + epoch = phase0.get_current_epoch(pre) + post = BeaconState( + # Versioning + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + current_version=config.ALTAIR_FORK_VERSION, + epoch=epoch, + ), + # History + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + # Eth1 + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + # Registry + validators=pre.validators, + balances=pre.balances, + # Randomness + randao_mixes=pre.randao_mixes, + # Slashings + slashings=pre.slashings, + # Participation + previous_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))], + current_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))], + # Finality + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + # Inactivity + inactivity_scores=[uint64(0) for _ in range(len(pre.validators))], + ) + # Fill in previous epoch participation from the pre state's pending attestations + translate_participation(post, pre.previous_epoch_attestations) + + # Fill in sync committees + # Note: A duplicate committee is assigned for the current and next committee at the fork boundary + post.current_sync_committee = get_next_sync_committee(post) + post.next_sync_committee = get_next_sync_committee(post) + return post + + +def compute_sync_committee_period(epoch: Epoch) -> uint64: + return epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + + +def is_assigned_to_sync_committee(state: BeaconState, + epoch: Epoch, + validator_index: ValidatorIndex) -> bool: + sync_committee_period = compute_sync_committee_period(epoch) + current_epoch = get_current_epoch(state) + current_sync_committee_period = compute_sync_committee_period(current_epoch) + next_sync_committee_period = current_sync_committee_period + 1 + assert sync_committee_period in (current_sync_committee_period, next_sync_committee_period) + + pubkey = state.validators[validator_index].pubkey + if sync_committee_period == current_sync_committee_period: + return pubkey in state.current_sync_committee.pubkeys + else: # sync_committee_period == next_sync_committee_period + return pubkey in state.next_sync_committee.pubkeys + + +def process_sync_committee_contributions(block: BeaconBlock, + contributions: Set[SyncCommitteeContribution]) -> None: + sync_aggregate = SyncAggregate() + signatures = [] + sync_subcommittee_size = SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT + + for contribution in contributions: + subcommittee_index = contribution.subcommittee_index + for index, participated in enumerate(contribution.aggregation_bits): + if participated: + participant_index = sync_subcommittee_size * subcommittee_index + index + sync_aggregate.sync_committee_bits[participant_index] = True + signatures.append(contribution.signature) + + sync_aggregate.sync_committee_signature = bls.Aggregate(signatures) + + block.body.sync_aggregate = sync_aggregate + + +def get_sync_committee_message(state: BeaconState, + block_root: Root, + validator_index: ValidatorIndex, + privkey: int) -> SyncCommitteeMessage: + epoch = get_current_epoch(state) + domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, epoch) + signing_root = compute_signing_root(block_root, domain) + signature = bls.Sign(privkey, signing_root) + + return SyncCommitteeMessage( + slot=state.slot, + beacon_block_root=block_root, + validator_index=validator_index, + signature=signature, + ) + + +def compute_subnets_for_sync_committee(state: BeaconState, validator_index: ValidatorIndex) -> Set[uint64]: + next_slot_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) + if compute_sync_committee_period(get_current_epoch(state)) == compute_sync_committee_period(next_slot_epoch): + sync_committee = state.current_sync_committee + else: + sync_committee = state.next_sync_committee + + target_pubkey = state.validators[validator_index].pubkey + sync_committee_indices = [index for index, pubkey in enumerate(sync_committee.pubkeys) if pubkey == target_pubkey] + return set([ + uint64(index // (SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT)) + for index in sync_committee_indices + ]) + + +def get_sync_committee_selection_proof(state: BeaconState, + slot: Slot, + subcommittee_index: uint64, + privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF, compute_epoch_at_slot(slot)) + signing_data = SyncAggregatorSelectionData( + slot=slot, + subcommittee_index=subcommittee_index, + ) + signing_root = compute_signing_root(signing_data, domain) + return bls.Sign(privkey, signing_root) + + +def is_sync_committee_aggregator(signature: BLSSignature) -> bool: + modulo = max(1, SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT // TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE) + return bytes_to_uint64(hash(signature)[0:8]) % modulo == 0 + + +def get_contribution_and_proof(state: BeaconState, + aggregator_index: ValidatorIndex, + contribution: SyncCommitteeContribution, + privkey: int) -> ContributionAndProof: + selection_proof = get_sync_committee_selection_proof( + state, + contribution.slot, + contribution.subcommittee_index, + privkey, + ) + return ContributionAndProof( + aggregator_index=aggregator_index, + contribution=contribution, + selection_proof=selection_proof, + ) + + +def get_contribution_and_proof_signature(state: BeaconState, + contribution_and_proof: ContributionAndProof, + privkey: int) -> BLSSignature: + contribution = contribution_and_proof.contribution + domain = get_domain(state, DOMAIN_CONTRIBUTION_AND_PROOF, compute_epoch_at_slot(contribution.slot)) + signing_root = compute_signing_root(contribution_and_proof, domain) + return bls.Sign(privkey, signing_root) + + +def get_sync_subcommittee_pubkeys(state: BeaconState, subcommittee_index: uint64) -> Sequence[BLSPubkey]: + # Committees assigned to `slot` sign for `slot - 1` + # This creates the exceptional logic below when transitioning between sync committee periods + next_slot_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) + if compute_sync_committee_period(get_current_epoch(state)) == compute_sync_committee_period(next_slot_epoch): + sync_committee = state.current_sync_committee + else: + sync_committee = state.next_sync_committee + + # Return pubkeys for the subcommittee index + sync_subcommittee_size = SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT + i = subcommittee_index * sync_subcommittee_size + return sync_committee.pubkeys[i:i + sync_subcommittee_size] + + +def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: + return uint64(generalized_index % 2**(floorlog2(generalized_index))) + + +def validate_light_client_update(snapshot: LightClientSnapshot, + update: LightClientUpdate, + genesis_validators_root: Root) -> None: + # Verify update slot is larger than snapshot slot + assert update.header.slot > snapshot.header.slot + + # Verify update does not skip a sync committee period + snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + assert update_period in (snapshot_period, snapshot_period + 1) + + # Verify update header root is the finalized root of the finality header, if specified + if update.finality_header == BeaconBlockHeader(): + signed_header = update.header + assert update.finality_branch == [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))] + else: + signed_header = update.finality_header + assert is_valid_merkle_branch( + leaf=hash_tree_root(update.header), + branch=update.finality_branch, + depth=floorlog2(FINALIZED_ROOT_INDEX), + index=get_subtree_index(FINALIZED_ROOT_INDEX), + root=update.finality_header.state_root, + ) + + # Verify update next sync committee if the update period incremented + if update_period == snapshot_period: + sync_committee = snapshot.current_sync_committee + assert update.next_sync_committee_branch == [Bytes32() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))] + else: + sync_committee = snapshot.next_sync_committee + assert is_valid_merkle_branch( + leaf=hash_tree_root(update.next_sync_committee), + branch=update.next_sync_committee_branch, + depth=floorlog2(NEXT_SYNC_COMMITTEE_INDEX), + index=get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX), + root=update.header.state_root, + ) + + # Verify sync committee has sufficient participants + assert sum(update.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS + + # Verify sync committee aggregate signature + participant_pubkeys = [pubkey for (bit, pubkey) in zip(update.sync_committee_bits, sync_committee.pubkeys) if bit] + domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version, genesis_validators_root) + signing_root = compute_signing_root(signed_header, domain) + assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature) + + +def apply_light_client_update(snapshot: LightClientSnapshot, update: LightClientUpdate) -> None: + snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + if update_period == snapshot_period + 1: + snapshot.current_sync_committee = snapshot.next_sync_committee + snapshot.next_sync_committee = update.next_sync_committee + snapshot.header = update.header + + +def process_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot, + genesis_validators_root: Root) -> None: + validate_light_client_update(store.snapshot, update, genesis_validators_root) + store.valid_updates.add(update) + + update_timeout = SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD + if ( + sum(update.sync_committee_bits) * 3 >= len(update.sync_committee_bits) * 2 + and update.finality_header != BeaconBlockHeader() + ): + # Apply update if (1) 2/3 quorum is reached and (2) we have a finality proof. + # Note that (2) means that the current light client design needs finality. + # It may be changed to re-organizable light client design. See the on-going issue consensus-specs#2182. + apply_light_client_update(store.snapshot, update) + store.valid_updates = set() + elif current_slot > store.snapshot.header.slot + update_timeout: + # Forced best update when the update timeout has elapsed + apply_light_client_update(store.snapshot, + max(store.valid_updates, key=lambda update: sum(update.sync_committee_bits))) + store.valid_updates = set() + + +def is_merge_transition_complete(state: BeaconState) -> bool: + return state.latest_execution_payload_header != ExecutionPayloadHeader() + + +def is_merge_transition_block(state: BeaconState, body: BeaconBlockBody) -> bool: + return not is_merge_transition_complete(state) and body.execution_payload != ExecutionPayload() + + +def is_execution_enabled(state: BeaconState, body: BeaconBlockBody) -> bool: + return is_merge_transition_block(state, body) or is_merge_transition_complete(state) + + +def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64: + slots_since_genesis = slot - GENESIS_SLOT + return uint64(state.genesis_time + slots_since_genesis * config.SECONDS_PER_SLOT) + + +def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None: + # Verify consistency of the parent hash with respect to the previous execution payload header + if is_merge_transition_complete(state): + assert payload.parent_hash == state.latest_execution_payload_header.block_hash + # Verify random + assert payload.random == get_randao_mix(state, get_current_epoch(state)) + # Verify timestamp + assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) + # Verify the execution payload is valid + assert execution_engine.execute_payload(payload) + # Cache execution payload header + state.latest_execution_payload_header = ExecutionPayloadHeader( + parent_hash=payload.parent_hash, + fee_recipient=payload.fee_recipient, + state_root=payload.state_root, + receipt_root=payload.receipt_root, + logs_bloom=payload.logs_bloom, + random=payload.random, + block_number=payload.block_number, + gas_limit=payload.gas_limit, + gas_used=payload.gas_used, + timestamp=payload.timestamp, + extra_data=payload.extra_data, + base_fee_per_gas=payload.base_fee_per_gas, + block_hash=payload.block_hash, + transactions_root=hash_tree_root(payload.transactions), + ) + + +def upgrade_to_merge(pre: altair.BeaconState) -> BeaconState: + epoch = altair.get_current_epoch(pre) + post = BeaconState( + # Versioning + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + current_version=config.MERGE_FORK_VERSION, + epoch=epoch, + ), + # History + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + # Eth1 + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + # Registry + validators=pre.validators, + balances=pre.balances, + # Randomness + randao_mixes=pre.randao_mixes, + # Slashings + slashings=pre.slashings, + # Participation + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + # Finality + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + # Inactivity + inactivity_scores=pre.inactivity_scores, + # Sync + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + # Execution-layer + latest_execution_payload_header=ExecutionPayloadHeader(), + ) + + return post + + +def is_valid_terminal_pow_block(block: PowBlock, parent: PowBlock) -> bool: + is_total_difficulty_reached = block.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY + is_parent_total_difficulty_valid = parent.total_difficulty < config.TERMINAL_TOTAL_DIFFICULTY + return is_total_difficulty_reached and is_parent_total_difficulty_valid + + +def validate_merge_block(block: BeaconBlock) -> None: + """ + Check the parent PoW block of execution payload is a valid terminal PoW block. + + Note: Unavailable PoW block(s) may later become available, + and a client software MAY delay a call to ``validate_merge_block`` + until the PoW block(s) become available. + """ + if config.TERMINAL_BLOCK_HASH != Hash32(): + # If `config.TERMINAL_BLOCK_HASH` is used as an override, the activation epoch must be reached. + assert compute_epoch_at_slot(block.slot) >= config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH + assert block.body.execution_payload.parent_hash == config.TERMINAL_BLOCK_HASH + return + + pow_block = get_pow_block(block.body.execution_payload.parent_hash) + # Check if `pow_block` is available + assert pow_block is not None + pow_parent = get_pow_block(pow_block.parent_hash) + # Check if `pow_parent` is available + assert pow_parent is not None + # Check if `pow_block` is a valid terminal PoW block + assert is_valid_terminal_pow_block(pow_block, pow_parent) + + +def get_pow_block_at_terminal_total_difficulty(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlock]: + # `pow_chain` abstractly represents all blocks in the PoW chain + for block in pow_chain.values(): + block_reached_ttd = block.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY + if block_reached_ttd: + # If genesis block, no parent exists so reaching TTD alone qualifies as valid terminal block + if block.parent_hash == Hash32(): + return block + parent = pow_chain[block.parent_hash] + parent_reached_ttd = parent.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY + if not parent_reached_ttd: + return block + + return None + + +def get_terminal_pow_block(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlock]: + if config.TERMINAL_BLOCK_HASH != Hash32(): + # Terminal block hash override takes precedence over terminal total difficulty + if config.TERMINAL_BLOCK_HASH in pow_chain: + return pow_chain[config.TERMINAL_BLOCK_HASH] + else: + return None + + return get_pow_block_at_terminal_total_difficulty(pow_chain) + + +def prepare_execution_payload(state: BeaconState, + pow_chain: Dict[Hash32, PowBlock], + finalized_block_hash: Hash32, + suggested_fee_recipient: ExecutionAddress, + execution_engine: ExecutionEngine) -> Optional[PayloadId]: + if not is_merge_transition_complete(state): + is_terminal_block_hash_set = config.TERMINAL_BLOCK_HASH != Hash32() + is_activation_epoch_reached = get_current_epoch(state) >= config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH + if is_terminal_block_hash_set and not is_activation_epoch_reached: + # Terminal block hash is set but activation epoch is not yet reached, no prepare payload call is needed + return None + + terminal_pow_block = get_terminal_pow_block(pow_chain) + if terminal_pow_block is None: + # Pre-merge, no prepare payload call is needed + return None + # Signify merge via producing on top of the terminal PoW block + parent_hash = terminal_pow_block.block_hash + else: + # Post-merge, normal payload + parent_hash = state.latest_execution_payload_header.block_hash + + # Set the forkchoice head and initiate the payload build process + payload_attributes = PayloadAttributes( + timestamp=compute_timestamp_at_slot(state, state.slot), + random=get_randao_mix(state, get_current_epoch(state)), + suggested_fee_recipient=suggested_fee_recipient, + ) + return execution_engine.notify_forkchoice_updated(parent_hash, finalized_block_hash, payload_attributes) + + +def get_execution_payload(payload_id: Optional[PayloadId], execution_engine: ExecutionEngine) -> ExecutionPayload: + if payload_id is None: + # Pre-merge, empty payload + return ExecutionPayload() + else: + return execution_engine.get_payload(payload_id) + + +def withdraw(state: BeaconState, index: ValidatorIndex, amount: Gwei) -> None: + # Decrease the validator's balance + decrease_balance(state, index, amount) + # Create a corresponding withdrawal receipt + receipt = WithdrawalReceipt( + index=WithdrawalReceiptIndex(len(state.withdrawal_receipts)), + address=state.validators[index].withdrawal_credentials[12:], + amount=amount, + ) + state.withdrawal_receipts.append(receipt) + + +def is_withdrawable_validator(validator: Validator, epoch: Epoch) -> bool: + """ + Check if ``validator`` is withdrawable. + """ + return validator.withdrawable_epoch <= epoch + + +def process_withdrawals(state: BeaconState) -> None: + current_epoch = get_current_epoch(state) + for index, validator in enumerate(state.validators): + balance = state.balances[index] + is_balance_nonzero = state.balances[index] == 0 + is_eth1_withdrawal_prefix = validator.withdrawal_credentials[0] != ETH1_ADDRESS_WITHDRAWAL_PREFIX + if is_balance_nonzero and is_eth1_withdrawal_prefix and is_withdrawable_validator(validator, current_epoch): + withdraw(state, index, balance) + + +def get_eth1_data(block: Eth1Block) -> Eth1Data: + """ + A stub function return mocking Eth1Data. + """ + return Eth1Data( + deposit_root=block.deposit_root, + deposit_count=block.deposit_count, + block_hash=hash_tree_root(block)) + + +def cache_this(key_fn, value_fn, lru_size): # type: ignore + cache_dict = LRU(size=lru_size) + + def wrapper(*args, **kw): # type: ignore + key = key_fn(*args, **kw) + nonlocal cache_dict + if key not in cache_dict: + cache_dict[key] = value_fn(*args, **kw) + return cache_dict[key] + return wrapper + + +_compute_shuffled_index = compute_shuffled_index +compute_shuffled_index = cache_this( + lambda index, index_count, seed: (index, index_count, seed), + _compute_shuffled_index, lru_size=SLOTS_PER_EPOCH * 3) + +_get_total_active_balance = get_total_active_balance +get_total_active_balance = cache_this( + lambda state: (state.validators.hash_tree_root(), compute_epoch_at_slot(state.slot)), + _get_total_active_balance, lru_size=10) + +_get_base_reward = get_base_reward +get_base_reward = cache_this( + lambda state, index: (state.validators.hash_tree_root(), state.slot, index), + _get_base_reward, lru_size=2048) + +_get_committee_count_per_slot = get_committee_count_per_slot +get_committee_count_per_slot = cache_this( + lambda state, epoch: (state.validators.hash_tree_root(), epoch), + _get_committee_count_per_slot, lru_size=SLOTS_PER_EPOCH * 3) + +_get_active_validator_indices = get_active_validator_indices +get_active_validator_indices = cache_this( + lambda state, epoch: (state.validators.hash_tree_root(), epoch), + _get_active_validator_indices, lru_size=3) + +_get_beacon_committee = get_beacon_committee +get_beacon_committee = cache_this( + lambda state, slot, index: (state.validators.hash_tree_root(), state.randao_mixes.hash_tree_root(), slot, index), + _get_beacon_committee, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3) + +_get_matching_target_attestations = get_matching_target_attestations +get_matching_target_attestations = cache_this( + lambda state, epoch: (state.hash_tree_root(), epoch), + _get_matching_target_attestations, lru_size=10) + +_get_matching_head_attestations = get_matching_head_attestations +get_matching_head_attestations = cache_this( + lambda state, epoch: (state.hash_tree_root(), epoch), + _get_matching_head_attestations, lru_size=10) + +_get_attesting_indices = get_attesting_indices +get_attesting_indices = cache_this( + lambda state, data, bits: ( + state.randao_mixes.hash_tree_root(), + state.validators.hash_tree_root(), data.hash_tree_root(), bits.hash_tree_root() + ), + _get_attesting_indices, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3) + + +def get_generalized_index(ssz_class: Any, *path: Sequence[PyUnion[int, SSZVariableName]]) -> GeneralizedIndex: + ssz_path = Path(ssz_class) + for item in path: + ssz_path = ssz_path / item + return GeneralizedIndex(ssz_path.gindex()) + + +ExecutionState = Any + + +def get_pow_block(hash: Bytes32) -> Optional[PowBlock]: + return PowBlock(block_hash=hash, parent_hash=Bytes32(), total_difficulty=uint256(0)) + + +def get_execution_state(execution_state_root: Bytes32) -> ExecutionState: + pass + + +def get_pow_chain_head() -> PowBlock: + pass + + +class NoopExecutionEngine(ExecutionEngine): + + def execute_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: + return True + + def notify_forkchoice_updated(self: ExecutionEngine, + head_block_hash: Hash32, + finalized_block_hash: Hash32, + payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: + pass + + def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayload: + raise NotImplementedError("no default block production") + + +EXECUTION_ENGINE = NoopExecutionEngine() + + +assert FINALIZED_ROOT_INDEX == get_generalized_index(BeaconState, 'finalized_checkpoint', 'root') +assert NEXT_SYNC_COMMITTEE_INDEX == get_generalized_index(BeaconState, 'next_sync_committee') From 155863d86d6b176906c28d862db0a726ac121b66 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 1 Dec 2021 11:40:54 -0700 Subject: [PATCH 02/98] add capella to circleci build --- .circleci/config.yml | 14 ++++++++++++++ configs/mainnet.yaml | 7 ++++++- configs/minimal.yaml | 2 -- presets/mainnet/capella.yaml | 1 + presets/minimal/capella.yaml | 1 + 5 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 presets/mainnet/capella.yaml create mode 100644 presets/minimal/capella.yaml diff --git a/.circleci/config.yml b/.circleci/config.yml index bcce74bd91..beb0f26a4d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -129,6 +129,20 @@ jobs: command: make citest fork=merge - store_test_results: path: tests/core/pyspec/test-reports + test-capella: + docker: + - image: circleci/python:3.8 + working_directory: ~/specs-repo + steps: + - restore_cache: + key: v3-specs-repo-{{ .Branch }}-{{ .Revision }} + - restore_pyspec_cached_venv + - run: + name: Run py-tests + command: make citest fork=capella + - store_test_results: + path: tests/core/pyspec/test-reports + table_of_contents: docker: - image: circleci/node:10.16.3 diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 395e8d7177..6499ddbfb1 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -37,11 +37,16 @@ ALTAIR_FORK_EPOCH: 74240 # Oct 27, 2021, 10:56:23am UTC # Merge MERGE_FORK_VERSION: 0x02000000 MERGE_FORK_EPOCH: 18446744073709551615 +# Capella +CAPELLA_FORK_VERSION: 0x03000000 +CAPELLA_FORK_EPOCH: 18446744073709551615 # Sharding -SHARDING_FORK_VERSION: 0x03000000 +SHARDING_FORK_VERSION: 0x04000000 SHARDING_FORK_EPOCH: 18446744073709551615 + + # Time parameters # --------------------------------------------------------------- # 12 seconds diff --git a/configs/minimal.yaml b/configs/minimal.yaml index efa58b57d1..74315f48b0 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -44,8 +44,6 @@ SHARDING_FORK_VERSION: 0x04000001 SHARDING_FORK_EPOCH: 18446744073709551615 - - # Time parameters # --------------------------------------------------------------- # [customized] Faster for testing purposes diff --git a/presets/mainnet/capella.yaml b/presets/mainnet/capella.yaml new file mode 100644 index 0000000000..7d9da914ee --- /dev/null +++ b/presets/mainnet/capella.yaml @@ -0,0 +1 @@ +# Minimal preset - Sharding diff --git a/presets/minimal/capella.yaml b/presets/minimal/capella.yaml new file mode 100644 index 0000000000..7d9da914ee --- /dev/null +++ b/presets/minimal/capella.yaml @@ -0,0 +1 @@ +# Minimal preset - Sharding From b7308e0953287956908e667f29a150d7493a0a86 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 1 Dec 2021 11:43:32 -0700 Subject: [PATCH 03/98] remove capella built files --- .gitignore | 1 + tests/core/pyspec/eth2spec/capella/mainnet.py | 3072 ---------------- tests/core/pyspec/eth2spec/capella/minimal.py | 3074 ----------------- .../pyspec/eth2spec/withdrawals/mainnet.py | 3018 ---------------- .../pyspec/eth2spec/withdrawals/minimal.py | 3018 ---------------- 5 files changed, 1 insertion(+), 12182 deletions(-) delete mode 100644 tests/core/pyspec/eth2spec/capella/mainnet.py delete mode 100644 tests/core/pyspec/eth2spec/capella/minimal.py delete mode 100644 tests/core/pyspec/eth2spec/withdrawals/mainnet.py delete mode 100644 tests/core/pyspec/eth2spec/withdrawals/minimal.py diff --git a/.gitignore b/.gitignore index 76fe21ddde..56d7dfafc7 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ consensus-spec-tests/ tests/core/pyspec/eth2spec/phase0/ tests/core/pyspec/eth2spec/altair/ tests/core/pyspec/eth2spec/merge/ +tests/core/pyspec/eth2spec/capella/ # coverage reports .htmlcov diff --git a/tests/core/pyspec/eth2spec/capella/mainnet.py b/tests/core/pyspec/eth2spec/capella/mainnet.py deleted file mode 100644 index e7ec0b4bd4..0000000000 --- a/tests/core/pyspec/eth2spec/capella/mainnet.py +++ /dev/null @@ -1,3072 +0,0 @@ -from lru import LRU -from dataclasses import ( - dataclass, - field, -) -from typing import ( - Any, Callable, Dict, Set, Sequence, Tuple, Optional, TypeVar, NamedTuple -) - -from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy, uint_to_bytes -from eth2spec.utils.ssz.ssz_typing import ( - View, boolean, Container, List, Vector, uint8, uint32, uint64, - Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist) -from eth2spec.utils.ssz.ssz_typing import Bitvector # noqa: F401 -from eth2spec.utils import bls -from eth2spec.utils.hash_function import hash - - -from typing import NewType, Union as PyUnion - -from eth2spec.phase0 import mainnet as phase0 -from eth2spec.utils.ssz.ssz_typing import Path - -from typing import Protocol -from eth2spec.altair import mainnet as altair -from eth2spec.utils.ssz.ssz_typing import Bytes8, Bytes20, ByteList, ByteVector, uint256 - -from eth2spec.merge import mainnet as merge - -SSZObject = TypeVar('SSZObject', bound=View) - - -SSZVariableName = str -GeneralizedIndex = NewType('GeneralizedIndex', int) - - -fork = 'capella' - - -MAX_BYTES_PER_TRANSACTION = uint64(2**30) - - -class Slot(uint64): - pass - - -class Epoch(uint64): - pass - - -class CommitteeIndex(uint64): - pass - - -class ValidatorIndex(uint64): - pass - - -class Gwei(uint64): - pass - - -class Root(Bytes32): - pass - - -class Hash32(Bytes32): - pass - - -class Version(Bytes4): - pass - - -class DomainType(Bytes4): - pass - - -class ForkDigest(Bytes4): - pass - - -class Domain(Bytes32): - pass - - -class BLSPubkey(Bytes48): - pass - - -class BLSSignature(Bytes96): - pass - - -class Ether(uint64): - pass - - -class ParticipationFlags(uint8): - pass - - -class ExecutionAddress(Bytes20): - pass - - -class PayloadId(Bytes8): - pass - - -class WithdrawalReceiptIndex(uint64): - pass - - -Transaction = ByteList[MAX_BYTES_PER_TRANSACTION] - - -def ceillog2(x: int) -> uint64: - if x < 1: - raise ValueError(f"ceillog2 accepts only positive values, x={x}") - return uint64((x - 1).bit_length()) - - -def floorlog2(x: int) -> uint64: - if x < 1: - raise ValueError(f"floorlog2 accepts only positive values, x={x}") - return uint64(x.bit_length() - 1) - - -FINALIZED_ROOT_INDEX = GeneralizedIndex(105) -NEXT_SYNC_COMMITTEE_INDEX = GeneralizedIndex(55) - -# Constant vars -GENESIS_SLOT = Slot(0) -GENESIS_EPOCH = Epoch(0) -FAR_FUTURE_EPOCH = Epoch(2**64 - 1) -BASE_REWARDS_PER_EPOCH = uint64(4) -DEPOSIT_CONTRACT_TREE_DEPTH = uint64(2**5) -JUSTIFICATION_BITS_LENGTH = uint64(4) -ENDIANNESS = 'little' -BLS_WITHDRAWAL_PREFIX = Bytes1('0x00') -ETH1_ADDRESS_WITHDRAWAL_PREFIX = Bytes1('0x01') -DOMAIN_BEACON_PROPOSER = DomainType('0x00000000') -DOMAIN_BEACON_ATTESTER = DomainType('0x01000000') -DOMAIN_RANDAO = DomainType('0x02000000') -DOMAIN_DEPOSIT = DomainType('0x03000000') -DOMAIN_VOLUNTARY_EXIT = DomainType('0x04000000') -DOMAIN_SELECTION_PROOF = DomainType('0x05000000') -DOMAIN_AGGREGATE_AND_PROOF = DomainType('0x06000000') -INTERVALS_PER_SLOT = uint64(3) -TARGET_AGGREGATORS_PER_COMMITTEE = 2**4 -RANDOM_SUBNETS_PER_VALIDATOR = 2**0 -EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION = 2**8 -ATTESTATION_SUBNET_COUNT = 64 -ETH_TO_GWEI = uint64(10**9) -SAFETY_DECAY = uint64(10) -TIMELY_SOURCE_FLAG_INDEX = 0 -TIMELY_TARGET_FLAG_INDEX = 1 -TIMELY_HEAD_FLAG_INDEX = 2 -TIMELY_SOURCE_WEIGHT = uint64(14) -TIMELY_TARGET_WEIGHT = uint64(26) -TIMELY_HEAD_WEIGHT = uint64(14) -SYNC_REWARD_WEIGHT = uint64(2) -PROPOSER_WEIGHT = uint64(8) -WEIGHT_DENOMINATOR = uint64(64) -DOMAIN_SYNC_COMMITTEE = DomainType('0x07000000') -DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF = DomainType('0x08000000') -DOMAIN_CONTRIBUTION_AND_PROOF = DomainType('0x09000000') -PARTICIPATION_FLAG_WEIGHTS = [TIMELY_SOURCE_WEIGHT, TIMELY_TARGET_WEIGHT, TIMELY_HEAD_WEIGHT] -G2_POINT_AT_INFINITY = BLSSignature(b'\xc0' + b'\x00' * 95) -TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE = 2**4 -SYNC_COMMITTEE_SUBNET_COUNT = 4 -WITHDRAWAL_RECEIPT_LIMIT = uint64(2**40) -CAPELLA_FORK_VERSION = Version('0x03000000') -CAPELLA_FORK_EPOCH = Epoch(18446744073709551615) - -# Preset vars -MAX_COMMITTEES_PER_SLOT = uint64(64) -TARGET_COMMITTEE_SIZE = uint64(128) -MAX_VALIDATORS_PER_COMMITTEE = uint64(2048) -SHUFFLE_ROUND_COUNT = uint64(90) -HYSTERESIS_QUOTIENT = uint64(4) -HYSTERESIS_DOWNWARD_MULTIPLIER = uint64(1) -HYSTERESIS_UPWARD_MULTIPLIER = uint64(5) -MIN_DEPOSIT_AMOUNT = Gwei(1000000000) -MAX_EFFECTIVE_BALANCE = Gwei(32000000000) -EFFECTIVE_BALANCE_INCREMENT = Gwei(1000000000) -MIN_ATTESTATION_INCLUSION_DELAY = uint64(1) -SLOTS_PER_EPOCH = uint64(32) -MIN_SEED_LOOKAHEAD = uint64(1) -MAX_SEED_LOOKAHEAD = uint64(4) -MIN_EPOCHS_TO_INACTIVITY_PENALTY = uint64(4) -EPOCHS_PER_ETH1_VOTING_PERIOD = uint64(64) -SLOTS_PER_HISTORICAL_ROOT = uint64(8192) -EPOCHS_PER_HISTORICAL_VECTOR = uint64(65536) -EPOCHS_PER_SLASHINGS_VECTOR = uint64(8192) -HISTORICAL_ROOTS_LIMIT = uint64(16777216) -VALIDATOR_REGISTRY_LIMIT = uint64(1099511627776) -BASE_REWARD_FACTOR = uint64(64) -WHISTLEBLOWER_REWARD_QUOTIENT = uint64(512) -PROPOSER_REWARD_QUOTIENT = uint64(8) -INACTIVITY_PENALTY_QUOTIENT = uint64(67108864) -MIN_SLASHING_PENALTY_QUOTIENT = uint64(128) -PROPORTIONAL_SLASHING_MULTIPLIER = uint64(1) -MAX_PROPOSER_SLASHINGS = 16 -MAX_ATTESTER_SLASHINGS = 2 -MAX_ATTESTATIONS = 128 -MAX_DEPOSITS = 16 -MAX_VOLUNTARY_EXITS = 16 -SAFE_SLOTS_TO_UPDATE_JUSTIFIED = 8 -INACTIVITY_PENALTY_QUOTIENT_ALTAIR = uint64(50331648) -MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR = uint64(64) -PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR = uint64(2) -SYNC_COMMITTEE_SIZE = uint64(512) -EPOCHS_PER_SYNC_COMMITTEE_PERIOD = uint64(256) -MIN_SYNC_COMMITTEE_PARTICIPANTS = 1 -MAX_BYTES_PER_TRANSACTION = uint64(1073741824) -MAX_TRANSACTIONS_PER_PAYLOAD = uint64(1048576) -BYTES_PER_LOGS_BLOOM = uint64(256) -MAX_EXTRA_DATA_BYTES = 32 -INACTIVITY_PENALTY_QUOTIENT_MERGE = uint64(16777216) -MIN_SLASHING_PENALTY_QUOTIENT_MERGE = uint64(32) -PROPORTIONAL_SLASHING_MULTIPLIER_MERGE = uint64(3) - - -class Configuration(NamedTuple): - PRESET_BASE: str - MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: uint64 - MIN_GENESIS_TIME: uint64 - GENESIS_FORK_VERSION: Version - GENESIS_DELAY: uint64 - SECONDS_PER_SLOT: uint64 - SECONDS_PER_ETH1_BLOCK: uint64 - MIN_VALIDATOR_WITHDRAWABILITY_DELAY: uint64 - SHARD_COMMITTEE_PERIOD: uint64 - ETH1_FOLLOW_DISTANCE: uint64 - EJECTION_BALANCE: Gwei - MIN_PER_EPOCH_CHURN_LIMIT: uint64 - CHURN_LIMIT_QUOTIENT: uint64 - PROPOSER_SCORE_BOOST: uint64 - INACTIVITY_SCORE_BIAS: uint64 - INACTIVITY_SCORE_RECOVERY_RATE: uint64 - ALTAIR_FORK_VERSION: Version - ALTAIR_FORK_EPOCH: Epoch - TERMINAL_TOTAL_DIFFICULTY: int - TERMINAL_BLOCK_HASH: Hash32 - TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: int - MERGE_FORK_VERSION: Version - MERGE_FORK_EPOCH: Epoch - - -config = Configuration( - PRESET_BASE="mainnet", - MIN_GENESIS_ACTIVE_VALIDATOR_COUNT=uint64(16384), - MIN_GENESIS_TIME=uint64(1606824000), - GENESIS_FORK_VERSION=Version('0x00000000'), - GENESIS_DELAY=uint64(604800), - SECONDS_PER_SLOT=uint64(12), - SECONDS_PER_ETH1_BLOCK=uint64(14), - MIN_VALIDATOR_WITHDRAWABILITY_DELAY=uint64(256), - SHARD_COMMITTEE_PERIOD=uint64(256), - ETH1_FOLLOW_DISTANCE=uint64(2048), - EJECTION_BALANCE=Gwei(16000000000), - MIN_PER_EPOCH_CHURN_LIMIT=uint64(4), - CHURN_LIMIT_QUOTIENT=uint64(65536), - PROPOSER_SCORE_BOOST=uint64(70), - INACTIVITY_SCORE_BIAS=uint64(4), - INACTIVITY_SCORE_RECOVERY_RATE=uint64(16), - ALTAIR_FORK_VERSION=Version('0x01000000'), - ALTAIR_FORK_EPOCH=Epoch(74240), - TERMINAL_TOTAL_DIFFICULTY=115792089237316195423570985008687907853269984665640564039457584007913129638912, - TERMINAL_BLOCK_HASH=Hash32('0x0000000000000000000000000000000000000000000000000000000000000000'), - TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH=18446744073709551615, - MERGE_FORK_VERSION=Version('0x02000000'), - MERGE_FORK_EPOCH=Epoch(18446744073709551615), -) - - -class Fork(Container): - previous_version: Version - current_version: Version - epoch: Epoch # Epoch of latest fork - - -class ForkData(Container): - current_version: Version - genesis_validators_root: Root - - -class Checkpoint(Container): - epoch: Epoch - root: Root - - -class Validator(Container): - pubkey: BLSPubkey - withdrawal_credentials: Bytes32 # Commitment to pubkey for withdrawals - effective_balance: Gwei # Balance at stake - slashed: boolean - # Status epochs - activation_eligibility_epoch: Epoch # When criteria for activation were met - activation_epoch: Epoch - exit_epoch: Epoch - withdrawable_epoch: Epoch # When validator can withdraw funds - - -class AttestationData(Container): - slot: Slot - index: CommitteeIndex - # LMD GHOST vote - beacon_block_root: Root - # FFG vote - source: Checkpoint - target: Checkpoint - - -class IndexedAttestation(Container): - attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] - data: AttestationData - signature: BLSSignature - - -class PendingAttestation(Container): - aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] - data: AttestationData - inclusion_delay: Slot - proposer_index: ValidatorIndex - - -class Eth1Data(Container): - deposit_root: Root - deposit_count: uint64 - block_hash: Hash32 - - -class HistoricalBatch(Container): - block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] - state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] - - -class DepositMessage(Container): - pubkey: BLSPubkey - withdrawal_credentials: Bytes32 - amount: Gwei - - -class DepositData(Container): - pubkey: BLSPubkey - withdrawal_credentials: Bytes32 - amount: Gwei - signature: BLSSignature # Signing over DepositMessage - - -class BeaconBlockHeader(Container): - slot: Slot - proposer_index: ValidatorIndex - parent_root: Root - state_root: Root - body_root: Root - - -class SigningData(Container): - object_root: Root - domain: Domain - - -class AttesterSlashing(Container): - attestation_1: IndexedAttestation - attestation_2: IndexedAttestation - - -class Attestation(Container): - aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] - data: AttestationData - signature: BLSSignature - - -class Deposit(Container): - proof: Vector[Bytes32, DEPOSIT_CONTRACT_TREE_DEPTH + 1] # Merkle path to deposit root - data: DepositData - - -class VoluntaryExit(Container): - epoch: Epoch # Earliest epoch when voluntary exit can be processed - validator_index: ValidatorIndex - - -class SignedVoluntaryExit(Container): - message: VoluntaryExit - signature: BLSSignature - - -class SignedBeaconBlockHeader(Container): - message: BeaconBlockHeader - signature: BLSSignature - - -class ProposerSlashing(Container): - signed_header_1: SignedBeaconBlockHeader - signed_header_2: SignedBeaconBlockHeader - - -class Eth1Block(Container): - timestamp: uint64 - deposit_root: Root - deposit_count: uint64 - # All other eth1 block fields - - -class AggregateAndProof(Container): - aggregator_index: ValidatorIndex - aggregate: Attestation - selection_proof: BLSSignature - - -class SignedAggregateAndProof(Container): - message: AggregateAndProof - signature: BLSSignature - - -class SyncAggregate(Container): - sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] - sync_committee_signature: BLSSignature - - -class SyncCommittee(Container): - pubkeys: Vector[BLSPubkey, SYNC_COMMITTEE_SIZE] - aggregate_pubkey: BLSPubkey - - -class SyncCommitteeMessage(Container): - # Slot to which this contribution pertains - slot: Slot - # Block root for this signature - beacon_block_root: Root - # Index of the validator that produced this signature - validator_index: ValidatorIndex - # Signature by the validator over the block root of `slot` - signature: BLSSignature - - -class SyncCommitteeContribution(Container): - # Slot to which this contribution pertains - slot: Slot - # Block root for this contribution - beacon_block_root: Root - # The subcommittee this contribution pertains to out of the broader sync committee - subcommittee_index: uint64 - # A bit is set if a signature from the validator at the corresponding - # index in the subcommittee is present in the aggregate `signature`. - aggregation_bits: Bitvector[SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT] - # Signature by the validator(s) over the block root of `slot` - signature: BLSSignature - - -class ContributionAndProof(Container): - aggregator_index: ValidatorIndex - contribution: SyncCommitteeContribution - selection_proof: BLSSignature - - -class SignedContributionAndProof(Container): - message: ContributionAndProof - signature: BLSSignature - - -class SyncAggregatorSelectionData(Container): - slot: Slot - subcommittee_index: uint64 - - -class LightClientSnapshot(Container): - # Beacon block header - header: BeaconBlockHeader - # Sync committees corresponding to the header - current_sync_committee: SyncCommittee - next_sync_committee: SyncCommittee - - -class LightClientUpdate(Container): - # Update beacon block header - header: BeaconBlockHeader - # Next sync committee corresponding to the header - next_sync_committee: SyncCommittee - next_sync_committee_branch: Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_INDEX)] - # Finality proof for the update header - finality_header: BeaconBlockHeader - finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX)] - # Sync committee aggregate signature - sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] - sync_committee_signature: BLSSignature - # Fork version for the aggregate signature - fork_version: Version - - -class ExecutionPayload(Container): - # Execution block header fields - parent_hash: Hash32 - fee_recipient: ExecutionAddress # 'beneficiary' in the yellow paper - state_root: Bytes32 - receipt_root: Bytes32 # 'receipts root' in the yellow paper - logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - random: Bytes32 # 'difficulty' in the yellow paper - block_number: uint64 # 'number' in the yellow paper - gas_limit: uint64 - gas_used: uint64 - timestamp: uint64 - extra_data: ByteList[MAX_EXTRA_DATA_BYTES] - base_fee_per_gas: uint256 - # Extra payload fields - block_hash: Hash32 # Hash of execution block - transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] - - -class BeaconBlockBody(Container): - randao_reveal: BLSSignature - eth1_data: Eth1Data # Eth1 data vote - graffiti: Bytes32 # Arbitrary data - # Operations - proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] - attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] - attestations: List[Attestation, MAX_ATTESTATIONS] - deposits: List[Deposit, MAX_DEPOSITS] - voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] - sync_aggregate: SyncAggregate - # Execution - execution_payload: ExecutionPayload # [New in Merge] - - -class BeaconBlock(Container): - slot: Slot - proposer_index: ValidatorIndex - parent_root: Root - state_root: Root - body: BeaconBlockBody - - -class SignedBeaconBlock(Container): - message: BeaconBlock - signature: BLSSignature - - -class ExecutionPayloadHeader(Container): - # Execution block header fields - parent_hash: Hash32 - fee_recipient: ExecutionAddress - state_root: Bytes32 - receipt_root: Bytes32 - logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - random: Bytes32 - block_number: uint64 - gas_limit: uint64 - gas_used: uint64 - timestamp: uint64 - extra_data: ByteList[MAX_EXTRA_DATA_BYTES] - base_fee_per_gas: uint256 - # Extra payload fields - block_hash: Hash32 # Hash of execution block - transactions_root: Root - - -class PowBlock(Container): - block_hash: Hash32 - parent_hash: Hash32 - total_difficulty: uint256 - - -class WithdrawalReceipt(Container): - index: WithdrawalReceiptIndex - address: ExecutionAddress - amount: Gwei - - -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[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] - current_epoch_participation: List[ParticipationFlags, 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 - # Inactivity - inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] - # Sync - current_sync_committee: SyncCommittee - next_sync_committee: SyncCommittee - # Execution - latest_execution_payload_header: ExecutionPayloadHeader - # Withdrawals - withdrawal_receipts: List[WithdrawalReceipt, WITHDRAWAL_RECEIPT_LIMIT] # [New in Cappela] - - -@dataclass(eq=True, frozen=True) -class LatestMessage(object): - epoch: Epoch - root: Root - - -@dataclass -class Store(object): - time: uint64 - genesis_time: uint64 - justified_checkpoint: Checkpoint - finalized_checkpoint: Checkpoint - best_justified_checkpoint: Checkpoint - proposer_boost_root: Root - blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) - block_states: Dict[Root, BeaconState] = field(default_factory=dict) - checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict) - latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict) - - -@dataclass -class LightClientStore(object): - snapshot: LightClientSnapshot - valid_updates: Set[LightClientUpdate] - - -@dataclass -class PayloadAttributes(object): - timestamp: uint64 - random: Bytes32 - suggested_fee_recipient: ExecutionAddress - - -class ExecutionEngine(Protocol): - - def execute_payload(self, execution_payload: ExecutionPayload) -> bool: - """ - Return ``True`` if and only if ``execution_payload`` is valid with respect to ``self.execution_state``. - """ - ... - - def notify_forkchoice_updated(self, - head_block_hash: Hash32, - finalized_block_hash: Hash32, - payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: - ... - - def get_payload(self, payload_id: PayloadId) -> ExecutionPayload: - """ - Return ``execution_payload`` object. - """ - ... - - -def integer_squareroot(n: uint64) -> uint64: - """ - Return the largest integer ``x`` such that ``x**2 <= n``. - """ - x = n - y = (x + 1) // 2 - while y < x: - x = y - y = (x + n // x) // 2 - return x - - -def xor(bytes_1: Bytes32, bytes_2: Bytes32) -> Bytes32: - """ - Return the exclusive-or of two 32-byte strings. - """ - return Bytes32(a ^ b for a, b in zip(bytes_1, bytes_2)) - - -def bytes_to_uint64(data: bytes) -> uint64: - """ - Return the integer deserialization of ``data`` interpreted as ``ENDIANNESS``-endian. - """ - return uint64(int.from_bytes(data, ENDIANNESS)) - - -def is_active_validator(validator: Validator, epoch: Epoch) -> bool: - """ - Check if ``validator`` is active. - """ - return validator.activation_epoch <= epoch < validator.exit_epoch - - -def is_eligible_for_activation_queue(validator: Validator) -> bool: - """ - Check if ``validator`` is eligible to be placed into the activation queue. - """ - return ( - validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH - and validator.effective_balance == MAX_EFFECTIVE_BALANCE - ) - - -def is_eligible_for_activation(state: BeaconState, validator: Validator) -> bool: - """ - Check if ``validator`` is eligible for activation. - """ - return ( - # Placement in queue is finalized - validator.activation_eligibility_epoch <= state.finalized_checkpoint.epoch - # Has not yet been activated - and validator.activation_epoch == FAR_FUTURE_EPOCH - ) - - -def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool: - """ - Check if ``validator`` is slashable. - """ - return (not validator.slashed) and (validator.activation_epoch <= epoch < validator.withdrawable_epoch) - - -def is_slashable_attestation_data(data_1: AttestationData, data_2: AttestationData) -> bool: - """ - Check if ``data_1`` and ``data_2`` are slashable according to Casper FFG rules. - """ - return ( - # Double vote - (data_1 != data_2 and data_1.target.epoch == data_2.target.epoch) or - # Surround vote - (data_1.source.epoch < data_2.source.epoch and data_2.target.epoch < data_1.target.epoch) - ) - - -def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool: - """ - Check if ``indexed_attestation`` is not empty, has sorted and unique indices and has a valid aggregate signature. - """ - # Verify indices are sorted and unique - indices = indexed_attestation.attesting_indices - if len(indices) == 0 or not indices == sorted(set(indices)): - return False - # Verify aggregate signature - pubkeys = [state.validators[i].pubkey for i in indices] - domain = get_domain(state, DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch) - signing_root = compute_signing_root(indexed_attestation.data, domain) - return bls.FastAggregateVerify(pubkeys, signing_root, indexed_attestation.signature) - - -def is_valid_merkle_branch(leaf: Bytes32, branch: Sequence[Bytes32], depth: uint64, index: uint64, root: Root) -> bool: - """ - Check if ``leaf`` at ``index`` verifies against the Merkle ``root`` and ``branch``. - """ - value = leaf - for i in range(depth): - if index // (2**i) % 2: - value = hash(branch[i] + value) - else: - value = hash(value + branch[i]) - return value == root - - -def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) -> uint64: - """ - Return the shuffled index corresponding to ``seed`` (and ``index_count``). - """ - assert index < index_count - - # Swap or not (https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf) - # See the 'generalized domain' algorithm on page 3 - for current_round in range(SHUFFLE_ROUND_COUNT): - pivot = bytes_to_uint64(hash(seed + uint_to_bytes(uint8(current_round)))[0:8]) % index_count - flip = (pivot + index_count - index) % index_count - position = max(index, flip) - source = hash( - seed - + uint_to_bytes(uint8(current_round)) - + uint_to_bytes(uint32(position // 256)) - ) - byte = uint8(source[(position % 256) // 8]) - bit = (byte >> (position % 8)) % 2 - index = flip if bit else index - - return index - - -def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex], seed: Bytes32) -> ValidatorIndex: - """ - Return from ``indices`` a random index sampled by effective balance. - """ - assert len(indices) > 0 - MAX_RANDOM_BYTE = 2**8 - 1 - i = uint64(0) - total = uint64(len(indices)) - while True: - candidate_index = indices[compute_shuffled_index(i % total, total, seed)] - random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] - effective_balance = state.validators[candidate_index].effective_balance - if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: - return candidate_index - i += 1 - - -def compute_committee(indices: Sequence[ValidatorIndex], - seed: Bytes32, - index: uint64, - count: uint64) -> Sequence[ValidatorIndex]: - """ - Return the committee corresponding to ``indices``, ``seed``, ``index``, and committee ``count``. - """ - start = (len(indices) * index) // count - end = (len(indices) * uint64(index + 1)) // count - return [indices[compute_shuffled_index(uint64(i), uint64(len(indices)), seed)] for i in range(start, end)] - - -def compute_epoch_at_slot(slot: Slot) -> Epoch: - """ - Return the epoch number at ``slot``. - """ - return Epoch(slot // SLOTS_PER_EPOCH) - - -def compute_start_slot_at_epoch(epoch: Epoch) -> Slot: - """ - Return the start slot of ``epoch``. - """ - return Slot(epoch * SLOTS_PER_EPOCH) - - -def compute_activation_exit_epoch(epoch: Epoch) -> Epoch: - """ - Return the epoch during which validator activations and exits initiated in ``epoch`` take effect. - """ - return Epoch(epoch + 1 + MAX_SEED_LOOKAHEAD) - - -def compute_fork_data_root(current_version: Version, genesis_validators_root: Root) -> Root: - """ - Return the 32-byte fork data root for the ``current_version`` and ``genesis_validators_root``. - This is used primarily in signature domains to avoid collisions across forks/chains. - """ - return hash_tree_root(ForkData( - current_version=current_version, - genesis_validators_root=genesis_validators_root, - )) - - -def compute_fork_digest(current_version: Version, genesis_validators_root: Root) -> ForkDigest: - """ - Return the 4-byte fork digest for the ``current_version`` and ``genesis_validators_root``. - This is a digest primarily used for domain separation on the p2p layer. - 4-bytes suffices for practical separation of forks/chains. - """ - return ForkDigest(compute_fork_data_root(current_version, genesis_validators_root)[:4]) - - -def compute_domain(domain_type: DomainType, fork_version: Version=None, genesis_validators_root: Root=None) -> Domain: - """ - Return the domain for the ``domain_type`` and ``fork_version``. - """ - if fork_version is None: - fork_version = config.GENESIS_FORK_VERSION - if genesis_validators_root is None: - genesis_validators_root = Root() # all bytes zero by default - fork_data_root = compute_fork_data_root(fork_version, genesis_validators_root) - return Domain(domain_type + fork_data_root[:28]) - - -def compute_signing_root(ssz_object: SSZObject, domain: Domain) -> Root: - """ - Return the signing root for the corresponding signing data. - """ - return hash_tree_root(SigningData( - object_root=hash_tree_root(ssz_object), - domain=domain, - )) - - -def get_current_epoch(state: BeaconState) -> Epoch: - """ - Return the current epoch. - """ - return compute_epoch_at_slot(state.slot) - - -def get_previous_epoch(state: BeaconState) -> Epoch: - """` - Return the previous epoch (unless the current epoch is ``GENESIS_EPOCH``). - """ - current_epoch = get_current_epoch(state) - return GENESIS_EPOCH if current_epoch == GENESIS_EPOCH else Epoch(current_epoch - 1) - - -def get_block_root(state: BeaconState, epoch: Epoch) -> Root: - """ - Return the block root at the start of a recent ``epoch``. - """ - return get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch)) - - -def get_block_root_at_slot(state: BeaconState, slot: Slot) -> Root: - """ - Return the block root at a recent ``slot``. - """ - assert slot < state.slot <= slot + SLOTS_PER_HISTORICAL_ROOT - return state.block_roots[slot % SLOTS_PER_HISTORICAL_ROOT] - - -def get_randao_mix(state: BeaconState, epoch: Epoch) -> Bytes32: - """ - Return the randao mix at a recent ``epoch``. - """ - return state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR] - - -def get_active_validator_indices(state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: - """ - Return the sequence of active validator indices at ``epoch``. - """ - return [ValidatorIndex(i) for i, v in enumerate(state.validators) if is_active_validator(v, epoch)] - - -def get_validator_churn_limit(state: BeaconState) -> uint64: - """ - Return the validator churn limit for the current epoch. - """ - active_validator_indices = get_active_validator_indices(state, get_current_epoch(state)) - return max(config.MIN_PER_EPOCH_CHURN_LIMIT, uint64(len(active_validator_indices)) // config.CHURN_LIMIT_QUOTIENT) - - -def get_seed(state: BeaconState, epoch: Epoch, domain_type: DomainType) -> Bytes32: - """ - Return the seed at ``epoch``. - """ - mix = get_randao_mix(state, Epoch(epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD - 1)) # Avoid underflow - return hash(domain_type + uint_to_bytes(epoch) + mix) - - -def get_committee_count_per_slot(state: BeaconState, epoch: Epoch) -> uint64: - """ - Return the number of committees in each slot for the given ``epoch``. - """ - return max(uint64(1), min( - MAX_COMMITTEES_PER_SLOT, - uint64(len(get_active_validator_indices(state, epoch))) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE, - )) - - -def get_beacon_committee(state: BeaconState, slot: Slot, index: CommitteeIndex) -> Sequence[ValidatorIndex]: - """ - Return the beacon committee at ``slot`` for ``index``. - """ - epoch = compute_epoch_at_slot(slot) - committees_per_slot = get_committee_count_per_slot(state, epoch) - return compute_committee( - indices=get_active_validator_indices(state, epoch), - seed=get_seed(state, epoch, DOMAIN_BEACON_ATTESTER), - index=(slot % SLOTS_PER_EPOCH) * committees_per_slot + index, - count=committees_per_slot * SLOTS_PER_EPOCH, - ) - - -def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: - """ - Return the beacon proposer index at the current slot. - """ - epoch = get_current_epoch(state) - seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + uint_to_bytes(state.slot)) - indices = get_active_validator_indices(state, epoch) - return compute_proposer_index(state, indices, seed) - - -def get_total_balance(state: BeaconState, indices: Set[ValidatorIndex]) -> Gwei: - """ - Return the combined effective balance of the ``indices``. - ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. - Math safe up to ~10B ETH, afterwhich this overflows uint64. - """ - return Gwei(max(EFFECTIVE_BALANCE_INCREMENT, sum([state.validators[index].effective_balance for index in indices]))) - - -def get_total_active_balance(state: BeaconState) -> Gwei: - """ - Return the combined effective balance of the active validators. - Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. - """ - return get_total_balance(state, set(get_active_validator_indices(state, get_current_epoch(state)))) - - -def get_domain(state: BeaconState, domain_type: DomainType, epoch: Epoch=None) -> Domain: - """ - Return the signature domain (fork version concatenated with domain type) of a message. - """ - epoch = get_current_epoch(state) if epoch is None else epoch - fork_version = state.fork.previous_version if epoch < state.fork.epoch else state.fork.current_version - return compute_domain(domain_type, fork_version, state.genesis_validators_root) - - -def get_indexed_attestation(state: BeaconState, attestation: Attestation) -> IndexedAttestation: - """ - Return the indexed attestation corresponding to ``attestation``. - """ - attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) - - return IndexedAttestation( - attesting_indices=sorted(attesting_indices), - data=attestation.data, - signature=attestation.signature, - ) - - -def get_attesting_indices(state: BeaconState, - data: AttestationData, - bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Set[ValidatorIndex]: - """ - Return the set of attesting indices corresponding to ``data`` and ``bits``. - """ - committee = get_beacon_committee(state, data.slot, data.index) - return set(index for i, index in enumerate(committee) if bits[i]) - - -def increase_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: - """ - Increase the validator balance at index ``index`` by ``delta``. - """ - state.balances[index] += delta - - -def decrease_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: - """ - Decrease the validator balance at index ``index`` by ``delta``, with underflow protection. - """ - state.balances[index] = 0 if delta > state.balances[index] else state.balances[index] - delta - - -def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: - """ - Initiate the exit of the validator with index ``index``. - """ - # Return if validator already initiated exit - validator = state.validators[index] - if validator.exit_epoch != FAR_FUTURE_EPOCH: - return - - # Compute exit queue epoch - exit_epochs = [v.exit_epoch for v in state.validators if v.exit_epoch != FAR_FUTURE_EPOCH] - exit_queue_epoch = max(exit_epochs + [compute_activation_exit_epoch(get_current_epoch(state))]) - exit_queue_churn = len([v for v in state.validators if v.exit_epoch == exit_queue_epoch]) - if exit_queue_churn >= get_validator_churn_limit(state): - exit_queue_epoch += Epoch(1) - - # Set validator exit epoch and withdrawable epoch - validator.exit_epoch = exit_queue_epoch - validator.withdrawable_epoch = Epoch(validator.exit_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY) - - -def slash_validator(state: BeaconState, - slashed_index: ValidatorIndex, - whistleblower_index: ValidatorIndex=None) -> None: - """ - Slash the validator with index ``slashed_index``. - """ - epoch = get_current_epoch(state) - initiate_validator_exit(state, slashed_index) - validator = state.validators[slashed_index] - validator.slashed = True - validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR)) - state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance - slashing_penalty = validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_MERGE # [Modified in Merge] - decrease_balance(state, slashed_index, slashing_penalty) - - # Apply proposer and whistleblower rewards - proposer_index = get_beacon_proposer_index(state) - if whistleblower_index is None: - whistleblower_index = proposer_index - whistleblower_reward = Gwei(validator.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT) - proposer_reward = Gwei(whistleblower_reward * PROPOSER_WEIGHT // WEIGHT_DENOMINATOR) - increase_balance(state, proposer_index, proposer_reward) - increase_balance(state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward)) - - -def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, - eth1_timestamp: uint64, - deposits: Sequence[Deposit], - execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader() - ) -> BeaconState: - fork = Fork( - previous_version=config.MERGE_FORK_VERSION, # [Modified in Merge] for testing only - current_version=config.MERGE_FORK_VERSION, # [Modified in Merge] - epoch=GENESIS_EPOCH, - ) - state = BeaconState( - genesis_time=eth1_timestamp + config.GENESIS_DELAY, - fork=fork, - eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))), - latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), - randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy - ) - - # Process deposits - leaves = list(map(lambda deposit: deposit.data, deposits)) - for index, deposit in enumerate(deposits): - deposit_data_list = List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[:index + 1]) - state.eth1_data.deposit_root = hash_tree_root(deposit_data_list) - process_deposit(state, deposit) - - # Process activations - for index, validator in enumerate(state.validators): - balance = state.balances[index] - validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) - if validator.effective_balance == MAX_EFFECTIVE_BALANCE: - validator.activation_eligibility_epoch = GENESIS_EPOCH - validator.activation_epoch = GENESIS_EPOCH - - # Set genesis validators root for domain separation and chain versioning - state.genesis_validators_root = hash_tree_root(state.validators) - - # Fill in sync committees - # Note: A duplicate committee is assigned for the current and next committee at genesis - state.current_sync_committee = get_next_sync_committee(state) - state.next_sync_committee = get_next_sync_committee(state) - - # [New in Merge] Initialize the execution payload header - # If empty, will initialize a chain that has not yet gone through the Merge transition - state.latest_execution_payload_header = execution_payload_header - - return state - - -def is_valid_genesis_state(state: BeaconState) -> bool: - if state.genesis_time < config.MIN_GENESIS_TIME: - return False - if len(get_active_validator_indices(state, GENESIS_EPOCH)) < config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: - return False - return True - - -def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool=True) -> None: - block = signed_block.message - # Process slots (including those with no blocks) since block - process_slots(state, block.slot) - # Verify signature - if validate_result: - assert verify_block_signature(state, signed_block) - # Process block - process_block(state, block) - # Verify state root - if validate_result: - assert block.state_root == hash_tree_root(state) - - -def verify_block_signature(state: BeaconState, signed_block: SignedBeaconBlock) -> bool: - proposer = state.validators[signed_block.message.proposer_index] - signing_root = compute_signing_root(signed_block.message, get_domain(state, DOMAIN_BEACON_PROPOSER)) - return bls.Verify(proposer.pubkey, signing_root, signed_block.signature) - - -def process_slots(state: BeaconState, slot: Slot) -> None: - assert state.slot < slot - while state.slot < slot: - process_slot(state) - # Process epoch on the start slot of the next epoch - if (state.slot + 1) % SLOTS_PER_EPOCH == 0: - process_epoch(state) - state.slot = Slot(state.slot + 1) - - -def process_slot(state: BeaconState) -> None: - # Cache state root - previous_state_root = hash_tree_root(state) - state.state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_state_root - # Cache latest block header state root - if state.latest_block_header.state_root == Bytes32(): - state.latest_block_header.state_root = previous_state_root - # Cache block root - previous_block_root = hash_tree_root(state.latest_block_header) - state.block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_block_root - - -def process_epoch(state: BeaconState) -> None: - process_justification_and_finalization(state) - process_inactivity_updates(state) - process_rewards_and_penalties(state) - process_registry_updates(state) - process_slashings(state) - process_eth1_data_reset(state) - process_effective_balance_updates(state) - process_slashings_reset(state) - process_randao_mixes_reset(state) - process_historical_roots_update(state) - process_participation_flag_updates(state) - process_sync_committee_updates(state) - process_withdrawals(state) # [New in Cappela] - - -def get_matching_source_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: - assert epoch in (get_previous_epoch(state), get_current_epoch(state)) - return state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations - - -def get_matching_target_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: - return [ - a for a in get_matching_source_attestations(state, epoch) - if a.data.target.root == get_block_root(state, epoch) - ] - - -def get_matching_head_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: - return [ - a for a in get_matching_target_attestations(state, epoch) - if a.data.beacon_block_root == get_block_root_at_slot(state, a.data.slot) - ] - - -def get_unslashed_attesting_indices(state: BeaconState, - attestations: Sequence[PendingAttestation]) -> Set[ValidatorIndex]: - output = set() # type: Set[ValidatorIndex] - for a in attestations: - output = output.union(get_attesting_indices(state, a.data, a.aggregation_bits)) - return set(filter(lambda index: not state.validators[index].slashed, output)) - - -def get_attesting_balance(state: BeaconState, attestations: Sequence[PendingAttestation]) -> Gwei: - """ - Return the combined effective balance of the set of unslashed validators participating in ``attestations``. - Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. - """ - return get_total_balance(state, get_unslashed_attesting_indices(state, attestations)) - - -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_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)) - current_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_current_epoch(state)) - total_active_balance = get_total_active_balance(state) - previous_target_balance = get_total_balance(state, previous_indices) - current_target_balance = get_total_balance(state, current_indices) - weigh_justification_and_finalization(state, total_active_balance, previous_target_balance, current_target_balance) - - -def weigh_justification_and_finalization(state: BeaconState, - total_active_balance: Gwei, - previous_epoch_target_balance: Gwei, - current_epoch_target_balance: Gwei) -> None: - 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 - - # 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 - if previous_epoch_target_balance * 3 >= total_active_balance * 2: - state.current_justified_checkpoint = Checkpoint(epoch=previous_epoch, - root=get_block_root(state, previous_epoch)) - state.justification_bits[1] = 0b1 - if current_epoch_target_balance * 3 >= total_active_balance * 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 - - -def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: - """ - Return the base reward for the validator defined by ``index`` with respect to the current ``state``. - """ - increments = state.validators[index].effective_balance // EFFECTIVE_BALANCE_INCREMENT - return Gwei(increments * get_base_reward_per_increment(state)) - - -def get_proposer_reward(state: BeaconState, attesting_index: ValidatorIndex) -> Gwei: - return Gwei(get_base_reward(state, attesting_index) // PROPOSER_REWARD_QUOTIENT) - - -def get_finality_delay(state: BeaconState) -> uint64: - return get_previous_epoch(state) - state.finalized_checkpoint.epoch - - -def is_in_inactivity_leak(state: BeaconState) -> bool: - return get_finality_delay(state) > MIN_EPOCHS_TO_INACTIVITY_PENALTY - - -def get_eligible_validator_indices(state: BeaconState) -> Sequence[ValidatorIndex]: - previous_epoch = get_previous_epoch(state) - 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) - ] - - -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)] * 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) - 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 - if is_in_inactivity_leak(state): - # Since full base reward will be canceled out by inactivity penalty deltas, - # optimal participation receives full base reward compensation here. - rewards[index] += get_base_reward(state, index) - else: - 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 - - -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 get_attestation_component_deltas(state, matching_source_attestations) - - -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 get_attestation_component_deltas(state, matching_target_attestations) - - -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 get_attestation_component_deltas(state, matching_head_attestations) - - -def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: - """ - Return proposer and inclusion delay micro-rewards/penalties for each validator. - """ - rewards = [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 - if index in get_attesting_indices(state, a.data, a.aggregation_bits) - ], key=lambda a: a.inclusion_delay) - rewards[attestation.proposer_index] += get_proposer_reward(state, index) - max_attester_reward = Gwei(get_base_reward(state, index) - get_proposer_reward(state, index)) - 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 - - -def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: - """ - Return the inactivity penalty deltas by considering timely target participation flags and inactivity scores. - """ - rewards = [Gwei(0) for _ in range(len(state.validators))] - penalties = [Gwei(0) for _ in range(len(state.validators))] - previous_epoch = get_previous_epoch(state) - matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, previous_epoch) - for index in get_eligible_validator_indices(state): - if index not in matching_target_indices: - penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index] - # [Modified in Merge] - penalty_denominator = config.INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_MERGE - penalties[index] += Gwei(penalty_numerator // penalty_denominator) - return rewards, penalties - - -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) - 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 - - -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_index_deltas(state, flag_index) for flag_index in range(len(PARTICIPATION_FLAG_WEIGHTS))] - 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]) - - -def process_registry_updates(state: BeaconState) -> None: - # Process activation eligibility and ejections - for index, validator in enumerate(state.validators): - if is_eligible_for_activation_queue(validator): - validator.activation_eligibility_epoch = get_current_epoch(state) + 1 - - if ( - is_active_validator(validator, get_current_epoch(state)) - and validator.effective_balance <= config.EJECTION_BALANCE - ): - initiate_validator_exit(state, ValidatorIndex(index)) - - # Queue validators eligible for activation and not yet dequeued for activation - activation_queue = sorted([ - index for index, validator in enumerate(state.validators) - if is_eligible_for_activation(state, validator) - # Order by the sequence of activation_eligibility_epoch setting and then index - ], key=lambda index: (state.validators[index].activation_eligibility_epoch, index)) - # Dequeued validators for activation up to churn limit - for index in activation_queue[:get_validator_churn_limit(state)]: - validator = state.validators[index] - validator.activation_epoch = compute_activation_exit_epoch(get_current_epoch(state)) - - -def process_slashings(state: BeaconState) -> None: - epoch = get_current_epoch(state) - total_balance = get_total_active_balance(state) - adjusted_total_slashing_balance = min( - sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_MERGE, # [Modified in Merge] - total_balance - ) - for index, validator in enumerate(state.validators): - if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch: - increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow - penalty_numerator = validator.effective_balance // increment * adjusted_total_slashing_balance - penalty = penalty_numerator // total_balance * increment - decrease_balance(state, ValidatorIndex(index), penalty) - - -def process_eth1_data_reset(state: BeaconState) -> None: - next_epoch = Epoch(get_current_epoch(state) + 1) - # Reset eth1 data votes - if next_epoch % EPOCHS_PER_ETH1_VOTING_PERIOD == 0: - state.eth1_data_votes = [] - - -def process_effective_balance_updates(state: BeaconState) -> None: - # Update effective balances with hysteresis - for index, validator in enumerate(state.validators): - balance = state.balances[index] - HYSTERESIS_INCREMENT = uint64(EFFECTIVE_BALANCE_INCREMENT // HYSTERESIS_QUOTIENT) - DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER - UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER - if ( - balance + DOWNWARD_THRESHOLD < validator.effective_balance - or validator.effective_balance + UPWARD_THRESHOLD < balance - ): - validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) - - -def process_slashings_reset(state: BeaconState) -> None: - next_epoch = Epoch(get_current_epoch(state) + 1) - # Reset slashings - state.slashings[next_epoch % EPOCHS_PER_SLASHINGS_VECTOR] = Gwei(0) - - -def process_randao_mixes_reset(state: BeaconState) -> None: - current_epoch = get_current_epoch(state) - next_epoch = Epoch(current_epoch + 1) - # Set randao mix - state.randao_mixes[next_epoch % EPOCHS_PER_HISTORICAL_VECTOR] = get_randao_mix(state, current_epoch) - - -def process_historical_roots_update(state: BeaconState) -> None: - # Set historical root accumulator - next_epoch = Epoch(get_current_epoch(state) + 1) - if next_epoch % (SLOTS_PER_HISTORICAL_ROOT // SLOTS_PER_EPOCH) == 0: - historical_batch = HistoricalBatch(block_roots=state.block_roots, state_roots=state.state_roots) - state.historical_roots.append(hash_tree_root(historical_batch)) - - -def process_participation_record_updates(state: BeaconState) -> None: - # Rotate current/previous epoch attestations - state.previous_epoch_attestations = state.current_epoch_attestations - state.current_epoch_attestations = [] - - -def process_block(state: BeaconState, block: BeaconBlock) -> None: - process_block_header(state, block) - if is_execution_enabled(state, block.body): - process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [New in Merge] - process_randao(state, block.body) - process_eth1_data(state, block.body) - process_operations(state, block.body) - process_sync_aggregate(state, block.body.sync_aggregate) - - -def process_block_header(state: BeaconState, block: BeaconBlock) -> None: - # Verify that the slots match - assert block.slot == state.slot - # Verify that the block is newer than latest block header - assert block.slot > state.latest_block_header.slot - # Verify that proposer index is the correct index - assert block.proposer_index == get_beacon_proposer_index(state) - # Verify that the parent matches - assert block.parent_root == hash_tree_root(state.latest_block_header) - # Cache current block as the new latest block - state.latest_block_header = BeaconBlockHeader( - slot=block.slot, - proposer_index=block.proposer_index, - parent_root=block.parent_root, - state_root=Bytes32(), # Overwritten in the next process_slot call - body_root=hash_tree_root(block.body), - ) - - # Verify proposer is not slashed - proposer = state.validators[block.proposer_index] - assert not proposer.slashed - - -def process_randao(state: BeaconState, body: BeaconBlockBody) -> None: - epoch = get_current_epoch(state) - # Verify RANDAO reveal - proposer = state.validators[get_beacon_proposer_index(state)] - signing_root = compute_signing_root(epoch, get_domain(state, DOMAIN_RANDAO)) - assert bls.Verify(proposer.pubkey, signing_root, body.randao_reveal) - # Mix in RANDAO reveal - mix = xor(get_randao_mix(state, epoch), hash(body.randao_reveal)) - state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR] = mix - - -def process_eth1_data(state: BeaconState, body: BeaconBlockBody) -> None: - state.eth1_data_votes.append(body.eth1_data) - if state.eth1_data_votes.count(body.eth1_data) * 2 > EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH: - state.eth1_data = body.eth1_data - - -def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: - # Verify that outstanding deposits are processed up to the maximum number of deposits - assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index) - - def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: - for operation in operations: - fn(state, operation) - - for_ops(body.proposer_slashings, process_proposer_slashing) - for_ops(body.attester_slashings, process_attester_slashing) - for_ops(body.attestations, process_attestation) - for_ops(body.deposits, process_deposit) - for_ops(body.voluntary_exits, process_voluntary_exit) - - -def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None: - header_1 = proposer_slashing.signed_header_1.message - header_2 = proposer_slashing.signed_header_2.message - - # Verify header slots match - assert header_1.slot == header_2.slot - # Verify header proposer indices match - assert header_1.proposer_index == header_2.proposer_index - # Verify the headers are different - assert header_1 != header_2 - # Verify the proposer is slashable - proposer = state.validators[header_1.proposer_index] - assert is_slashable_validator(proposer, get_current_epoch(state)) - # Verify signatures - for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2): - domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(signed_header.message.slot)) - signing_root = compute_signing_root(signed_header.message, domain) - assert bls.Verify(proposer.pubkey, signing_root, signed_header.signature) - - slash_validator(state, header_1.proposer_index) - - -def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSlashing) -> None: - attestation_1 = attester_slashing.attestation_1 - attestation_2 = attester_slashing.attestation_2 - assert is_slashable_attestation_data(attestation_1.data, attestation_2.data) - assert is_valid_indexed_attestation(state, attestation_1) - assert is_valid_indexed_attestation(state, attestation_2) - - slashed_any = False - indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices) - for index in sorted(indices): - if is_slashable_validator(state.validators[index], get_current_epoch(state)): - slash_validator(state, index) - slashed_any = True - assert slashed_any - - -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) - - # Participation flag indices - participation_flag_indices = get_attestation_participation_flag_indices(state, data, state.slot - data.slot) - - # Verify signature - assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) - - # Update epoch participation flags - if data.target.epoch == get_current_epoch(state): - epoch_participation = state.current_epoch_participation - else: - epoch_participation = state.previous_epoch_participation - - proposer_reward_numerator = 0 - for index in get_attesting_indices(state, data, attestation.aggregation_bits): - for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS): - if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index): - epoch_participation[index] = add_flag(epoch_participation[index], flag_index) - proposer_reward_numerator += get_base_reward(state, index) * weight - - # Reward proposer - proposer_reward_denominator = (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT - proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator) - increase_balance(state, get_beacon_proposer_index(state), proposer_reward) - - -def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: - amount = deposit.data.amount - effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) - - return Validator( - pubkey=deposit.data.pubkey, - withdrawal_credentials=deposit.data.withdrawal_credentials, - activation_eligibility_epoch=FAR_FUTURE_EPOCH, - activation_epoch=FAR_FUTURE_EPOCH, - exit_epoch=FAR_FUTURE_EPOCH, - withdrawable_epoch=FAR_FUTURE_EPOCH, - effective_balance=effective_balance, - ) - - -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 = [validator.pubkey for validator 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) - # Initialize validator if the deposit signature is valid - if bls.Verify(pubkey, signing_root, deposit.data.signature): - state.validators.append(get_validator_from_deposit(state, deposit)) - state.balances.append(amount) - state.previous_epoch_participation.append(ParticipationFlags(0b0000_0000)) - state.current_epoch_participation.append(ParticipationFlags(0b0000_0000)) - state.inactivity_scores.append(uint64(0)) - else: - # Increase balance by deposit amount - index = ValidatorIndex(validator_pubkeys.index(pubkey)) - increase_balance(state, index, amount) - - -def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVoluntaryExit) -> None: - voluntary_exit = signed_voluntary_exit.message - validator = state.validators[voluntary_exit.validator_index] - # Verify the validator is active - assert is_active_validator(validator, get_current_epoch(state)) - # Verify exit has not been initiated - assert validator.exit_epoch == FAR_FUTURE_EPOCH - # Exits must specify an epoch when they become valid; they are not valid before then - assert get_current_epoch(state) >= voluntary_exit.epoch - # Verify the validator has been active long enough - assert get_current_epoch(state) >= validator.activation_epoch + config.SHARD_COMMITTEE_PERIOD - # Verify signature - domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch) - signing_root = compute_signing_root(voluntary_exit, domain) - assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature) - # Initiate exit - initiate_validator_exit(state, voluntary_exit.validator_index) - - -def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store: - assert anchor_block.state_root == hash_tree_root(anchor_state) - anchor_root = hash_tree_root(anchor_block) - anchor_epoch = get_current_epoch(anchor_state) - justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) - finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) - proposer_boost_root = Root() - return Store( - time=uint64(anchor_state.genesis_time + config.SECONDS_PER_SLOT * anchor_state.slot), - genesis_time=anchor_state.genesis_time, - justified_checkpoint=justified_checkpoint, - finalized_checkpoint=finalized_checkpoint, - best_justified_checkpoint=justified_checkpoint, - proposer_boost_root=proposer_boost_root, - blocks={anchor_root: copy(anchor_block)}, - block_states={anchor_root: copy(anchor_state)}, - checkpoint_states={justified_checkpoint: copy(anchor_state)}, - ) - - -def get_slots_since_genesis(store: Store) -> int: - return (store.time - store.genesis_time) // config.SECONDS_PER_SLOT - - -def get_current_slot(store: Store) -> Slot: - return Slot(GENESIS_SLOT + get_slots_since_genesis(store)) - - -def compute_slots_since_epoch_start(slot: Slot) -> int: - return slot - compute_start_slot_at_epoch(compute_epoch_at_slot(slot)) - - -def get_ancestor(store: Store, root: Root, slot: Slot) -> Root: - block = store.blocks[root] - if block.slot > slot: - return get_ancestor(store, block.parent_root, slot) - elif block.slot == slot: - return root - else: - # root is older than queried slot, thus a skip slot. Return most recent root prior to slot - return root - - -def get_latest_attesting_balance(store: Store, root: Root) -> Gwei: - state = store.checkpoint_states[store.justified_checkpoint] - active_indices = get_active_validator_indices(state, get_current_epoch(state)) - attestation_score = Gwei(sum( - state.validators[i].effective_balance for i in active_indices - if (i in store.latest_messages - and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root) - )) - proposer_score = Gwei(0) - if store.proposer_boost_root != Root(): - block = store.blocks[root] - if get_ancestor(store, root, block.slot) == store.proposer_boost_root: - num_validators = len(get_active_validator_indices(state, get_current_epoch(state))) - avg_balance = get_total_active_balance(state) // num_validators - committee_size = num_validators // SLOTS_PER_EPOCH - committee_weight = committee_size * avg_balance - proposer_score = (committee_weight * config.PROPOSER_SCORE_BOOST) // 100 - return attestation_score + proposer_score - - -def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconBlock]) -> bool: - block = store.blocks[block_root] - children = [ - root for root in store.blocks.keys() - if store.blocks[root].parent_root == block_root - ] - - # If any children branches contain expected finalized/justified checkpoints, - # add to filtered block-tree and signal viability to parent. - if any(children): - filter_block_tree_result = [filter_block_tree(store, child, blocks) for child in children] - if any(filter_block_tree_result): - blocks[block_root] = block - return True - return False - - # If leaf block, check finalized/justified checkpoints as matching latest. - head_state = store.block_states[block_root] - - correct_justified = ( - store.justified_checkpoint.epoch == GENESIS_EPOCH - or head_state.current_justified_checkpoint == store.justified_checkpoint - ) - correct_finalized = ( - store.finalized_checkpoint.epoch == GENESIS_EPOCH - or head_state.finalized_checkpoint == store.finalized_checkpoint - ) - # If expected finalized/justified, add to viable block-tree and signal viability to parent. - if correct_justified and correct_finalized: - blocks[block_root] = block - return True - - # Otherwise, branch not viable - return False - - -def get_filtered_block_tree(store: Store) -> Dict[Root, BeaconBlock]: - """ - Retrieve a filtered block tree from ``store``, only returning branches - whose leaf state's justified/finalized info agrees with that in ``store``. - """ - base = store.justified_checkpoint.root - blocks: Dict[Root, BeaconBlock] = {} - filter_block_tree(store, base, blocks) - return blocks - - -def get_head(store: Store) -> Root: - # Get filtered block tree that only includes viable branches - blocks = get_filtered_block_tree(store) - # Execute the LMD-GHOST fork choice - head = store.justified_checkpoint.root - while True: - children = [ - root for root in blocks.keys() - if blocks[root].parent_root == head - ] - if len(children) == 0: - return head - # Sort by latest attesting balance with ties broken lexicographically - head = max(children, key=lambda root: (get_latest_attesting_balance(store, root), root)) - - -def should_update_justified_checkpoint(store: Store, new_justified_checkpoint: Checkpoint) -> bool: - """ - To address the bouncing attack, only update conflicting justified - checkpoints in the fork choice if in the early slots of the epoch. - Otherwise, delay incorporation of new justified checkpoint until next epoch boundary. - - See https://ethresear.ch/t/prevention-of-bouncing-attack-on-ffg/6114 for more detailed analysis and discussion. - """ - if compute_slots_since_epoch_start(get_current_slot(store)) < SAFE_SLOTS_TO_UPDATE_JUSTIFIED: - return True - - justified_slot = compute_start_slot_at_epoch(store.justified_checkpoint.epoch) - if not get_ancestor(store, new_justified_checkpoint.root, justified_slot) == store.justified_checkpoint.root: - return False - - return True - - -def validate_target_epoch_against_current_time(store: Store, attestation: Attestation) -> None: - target = attestation.data.target - - # Attestations must be from the current or previous epoch - 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] - - -def validate_on_attestation(store: Store, attestation: Attestation, is_from_block: bool) -> None: - target = attestation.data.target - - # If the given attestation is not from a beacon block message, we have to check the target epoch scope. - if not is_from_block: - validate_target_epoch_against_current_time(store, attestation) - - # Check that the epoch number and slot number are matching - assert target.epoch == compute_epoch_at_slot(attestation.data.slot) - - # 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 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 - # Attestations must not be for blocks in the future. If not, the attestation should not be considered - assert store.blocks[attestation.data.beacon_block_root].slot <= attestation.data.slot - - # LMD vote must be consistent with FFG vote target - target_slot = compute_start_slot_at_epoch(target.epoch) - assert target.root == get_ancestor(store, attestation.data.beacon_block_root, target_slot) - - # Attestations can only affect the fork choice of subsequent slots. - # Delay consideration in the fork choice until their slot is in the past. - assert get_current_slot(store) >= attestation.data.slot + 1 - - -def store_target_checkpoint_state(store: Store, target: Checkpoint) -> None: - # Store target checkpoint state if not yet seen - if target not in store.checkpoint_states: - base_state = copy(store.block_states[target.root]) - if base_state.slot < compute_start_slot_at_epoch(target.epoch): - process_slots(base_state, compute_start_slot_at_epoch(target.epoch)) - store.checkpoint_states[target] = base_state - - -def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None: - target = attestation.data.target - beacon_block_root = attestation.data.beacon_block_root - for i in attesting_indices: - if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch: - store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root) - - -def on_tick(store: Store, time: uint64) -> None: - previous_slot = get_current_slot(store) - - # update store time - store.time = time - - current_slot = get_current_slot(store) - - # Reset store.proposer_boost_root if this is a new slot - if current_slot > previous_slot: - store.proposer_boost_root = Root() - - # Not a new epoch, return - if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0): - return - - # Update store.justified_checkpoint if a better checkpoint on the store.finalized_checkpoint chain - if store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch: - finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) - ancestor_at_finalized_slot = get_ancestor(store, store.best_justified_checkpoint.root, finalized_slot) - if ancestor_at_finalized_slot == store.finalized_checkpoint.root: - store.justified_checkpoint = store.best_justified_checkpoint - - -def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: - """ - Run ``on_block`` upon receiving a new block. - - A block that is asserted as invalid due to unavailable PoW block may be valid at a later time, - consider scheduling it for later processing in such case. - """ - block = signed_block.message - # Parent block must be known - assert block.parent_root in store.block_states - # Make a copy of the state to avoid mutability issues - pre_state = copy(store.block_states[block.parent_root]) - # Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past. - assert get_current_slot(store) >= block.slot - - # Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) - finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) - assert block.slot > finalized_slot - # Check block is a descendant of the finalized block at the checkpoint finalized slot - assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root - - # Check the block is valid and compute the post-state - state = pre_state.copy() - state_transition(state, signed_block, True) - - # [New in Merge] - if is_merge_transition_block(pre_state, block.body): - validate_merge_block(block) - - # Add new block to the store - store.blocks[hash_tree_root(block)] = block - # Add new state for this block to the store - store.block_states[hash_tree_root(block)] = state - - # Add proposer score boost if the block is timely - time_into_slot = (store.time - store.genesis_time) % config.SECONDS_PER_SLOT - is_before_attesting_interval = time_into_slot < config.SECONDS_PER_SLOT // INTERVALS_PER_SLOT - if get_current_slot(store) == block.slot and is_before_attesting_interval: - store.proposer_boost_root = hash_tree_root(block) - - # Update justified checkpoint - if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: - if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch: - store.best_justified_checkpoint = state.current_justified_checkpoint - if should_update_justified_checkpoint(store, state.current_justified_checkpoint): - store.justified_checkpoint = state.current_justified_checkpoint - - # Update finalized checkpoint - if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch: - store.finalized_checkpoint = state.finalized_checkpoint - store.justified_checkpoint = state.current_justified_checkpoint - - -def on_attestation(store: Store, attestation: Attestation, is_from_block: bool=False) -> None: - """ - Run ``on_attestation`` upon receiving a new ``attestation`` from either within a block or directly on the wire. - - An ``attestation`` that is asserted as invalid may be valid at a later time, - consider scheduling it for later processing in such case. - """ - validate_on_attestation(store, attestation, is_from_block) - - store_target_checkpoint_state(store, attestation.data.target) - - # Get state at the `target` to fully validate attestation - target_state = store.checkpoint_states[attestation.data.target] - indexed_attestation = get_indexed_attestation(target_state, attestation) - assert is_valid_indexed_attestation(target_state, indexed_attestation) - - # Update latest messages for attesting indices - update_latest_messages(store, indexed_attestation.attesting_indices, attestation) - - -def check_if_validator_active(state: BeaconState, validator_index: ValidatorIndex) -> bool: - validator = state.validators[validator_index] - return is_active_validator(validator, get_current_epoch(state)) - - -def get_committee_assignment(state: BeaconState, - epoch: Epoch, - validator_index: ValidatorIndex - ) -> Optional[Tuple[Sequence[ValidatorIndex], CommitteeIndex, Slot]]: - """ - Return the committee assignment in the ``epoch`` for ``validator_index``. - ``assignment`` returned is a tuple of the following form: - * ``assignment[0]`` is the list of validators in the committee - * ``assignment[1]`` is the index to which the committee is assigned - * ``assignment[2]`` is the slot at which the committee is assigned - Return None if no assignment. - """ - next_epoch = Epoch(get_current_epoch(state) + 1) - assert epoch <= next_epoch - - start_slot = compute_start_slot_at_epoch(epoch) - committee_count_per_slot = get_committee_count_per_slot(state, epoch) - for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH): - for index in range(committee_count_per_slot): - committee = get_beacon_committee(state, Slot(slot), CommitteeIndex(index)) - if validator_index in committee: - return committee, CommitteeIndex(index), Slot(slot) - return None - - -def is_proposer(state: BeaconState, validator_index: ValidatorIndex) -> bool: - return get_beacon_proposer_index(state) == validator_index - - -def get_epoch_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_RANDAO, compute_epoch_at_slot(block.slot)) - signing_root = compute_signing_root(compute_epoch_at_slot(block.slot), domain) - return bls.Sign(privkey, signing_root) - - -def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64: - return uint64(state.genesis_time + slot * config.SECONDS_PER_SLOT) - - -def voting_period_start_time(state: BeaconState) -> uint64: - eth1_voting_period_start_slot = Slot(state.slot - state.slot % (EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH)) - return compute_time_at_slot(state, eth1_voting_period_start_slot) - - -def is_candidate_block(block: Eth1Block, period_start: uint64) -> bool: - return ( - block.timestamp + config.SECONDS_PER_ETH1_BLOCK * config.ETH1_FOLLOW_DISTANCE <= period_start - and block.timestamp + config.SECONDS_PER_ETH1_BLOCK * config.ETH1_FOLLOW_DISTANCE * 2 >= period_start - ) - - -def get_eth1_vote(state: BeaconState, eth1_chain: Sequence[Eth1Block]) -> Eth1Data: - period_start = voting_period_start_time(state) - # `eth1_chain` abstractly represents all blocks in the eth1 chain sorted by ascending block height - votes_to_consider = [ - get_eth1_data(block) for block in eth1_chain - if ( - is_candidate_block(block, period_start) - # Ensure cannot move back to earlier deposit contract states - and get_eth1_data(block).deposit_count >= state.eth1_data.deposit_count - ) - ] - - # Valid votes already cast during this period - valid_votes = [vote for vote in state.eth1_data_votes if vote in votes_to_consider] - - # Default vote on latest eth1 block data in the period range unless eth1 chain is not live - # Non-substantive casting for linter - state_eth1_data: Eth1Data = state.eth1_data - default_vote = votes_to_consider[len(votes_to_consider) - 1] if any(votes_to_consider) else state_eth1_data - - return max( - valid_votes, - key=lambda v: (valid_votes.count(v), -valid_votes.index(v)), # Tiebreak by smallest distance - default=default_vote - ) - - -def compute_new_state_root(state: BeaconState, block: BeaconBlock) -> Root: - temp_state: BeaconState = state.copy() - signed_block = SignedBeaconBlock(message=block) - state_transition(temp_state, signed_block, validate_result=False) - return hash_tree_root(temp_state) - - -def get_block_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(block.slot)) - signing_root = compute_signing_root(block, domain) - return bls.Sign(privkey, signing_root) - - -def get_attestation_signature(state: BeaconState, attestation_data: AttestationData, privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch) - signing_root = compute_signing_root(attestation_data, domain) - return bls.Sign(privkey, signing_root) - - -def compute_subnet_for_attestation(committees_per_slot: uint64, slot: Slot, committee_index: CommitteeIndex) -> uint64: - """ - Compute the correct subnet for an attestation for Phase 0. - Note, this mimics expected future behavior where attestations will be mapped to their shard subnet. - """ - slots_since_epoch_start = uint64(slot % SLOTS_PER_EPOCH) - committees_since_epoch_start = committees_per_slot * slots_since_epoch_start - - return uint64((committees_since_epoch_start + committee_index) % ATTESTATION_SUBNET_COUNT) - - -def get_slot_signature(state: BeaconState, slot: Slot, privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_SELECTION_PROOF, compute_epoch_at_slot(slot)) - signing_root = compute_signing_root(slot, domain) - return bls.Sign(privkey, signing_root) - - -def is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex, slot_signature: BLSSignature) -> bool: - committee = get_beacon_committee(state, slot, index) - modulo = max(1, len(committee) // TARGET_AGGREGATORS_PER_COMMITTEE) - return bytes_to_uint64(hash(slot_signature)[0:8]) % modulo == 0 - - -def get_aggregate_signature(attestations: Sequence[Attestation]) -> BLSSignature: - signatures = [attestation.signature for attestation in attestations] - return bls.Aggregate(signatures) - - -def get_aggregate_and_proof(state: BeaconState, - aggregator_index: ValidatorIndex, - aggregate: Attestation, - privkey: int) -> AggregateAndProof: - return AggregateAndProof( - aggregator_index=aggregator_index, - aggregate=aggregate, - selection_proof=get_slot_signature(state, aggregate.data.slot, privkey), - ) - - -def get_aggregate_and_proof_signature(state: BeaconState, - aggregate_and_proof: AggregateAndProof, - privkey: int) -> BLSSignature: - aggregate = aggregate_and_proof.aggregate - domain = get_domain(state, DOMAIN_AGGREGATE_AND_PROOF, compute_epoch_at_slot(aggregate.data.slot)) - signing_root = compute_signing_root(aggregate_and_proof, domain) - return bls.Sign(privkey, signing_root) - - -def compute_weak_subjectivity_period(state: BeaconState) -> uint64: - """ - Returns the weak subjectivity period for the current ``state``. - This computation takes into account the effect of: - - validator set churn (bounded by ``get_validator_churn_limit()`` per epoch), and - - validator balance top-ups (bounded by ``MAX_DEPOSITS * SLOTS_PER_EPOCH`` per epoch). - A detailed calculation can be found at: - https://github.com/runtimeverification/beacon-chain-verification/blob/master/weak-subjectivity/weak-subjectivity-analysis.pdf - """ - ws_period = config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY - N = len(get_active_validator_indices(state, get_current_epoch(state))) - t = get_total_active_balance(state) // N // ETH_TO_GWEI - T = MAX_EFFECTIVE_BALANCE // ETH_TO_GWEI - delta = get_validator_churn_limit(state) - Delta = MAX_DEPOSITS * SLOTS_PER_EPOCH - D = SAFETY_DECAY - - if T * (200 + 3 * D) < t * (200 + 12 * D): - epochs_for_validator_set_churn = ( - N * (t * (200 + 12 * D) - T * (200 + 3 * D)) // (600 * delta * (2 * t + T)) - ) - epochs_for_balance_top_ups = ( - N * (200 + 3 * D) // (600 * Delta) - ) - ws_period += max(epochs_for_validator_set_churn, epochs_for_balance_top_ups) - else: - ws_period += ( - 3 * N * D * t // (200 * Delta * (T - t)) - ) - - return ws_period - - -def is_within_weak_subjectivity_period(store: Store, ws_state: BeaconState, ws_checkpoint: Checkpoint) -> bool: - # Clients may choose to validate the input state against the input Weak Subjectivity Checkpoint - assert ws_state.latest_block_header.state_root == ws_checkpoint.root - assert compute_epoch_at_slot(ws_state.slot) == ws_checkpoint.epoch - - ws_period = compute_weak_subjectivity_period(ws_state) - ws_state_epoch = compute_epoch_at_slot(ws_state.slot) - current_epoch = compute_epoch_at_slot(get_current_slot(store)) - return current_epoch <= ws_state_epoch + ws_period - - -def add_flag(flags: ParticipationFlags, flag_index: int) -> ParticipationFlags: - """ - Return a new ``ParticipationFlags`` adding ``flag_index`` to ``flags``. - """ - flag = ParticipationFlags(2**flag_index) - return flags | flag - - -def has_flag(flags: ParticipationFlags, flag_index: int) -> bool: - """ - Return whether ``flags`` has ``flag_index`` set. - """ - flag = ParticipationFlags(2**flag_index) - return flags & flag == flag - - -def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorIndex]: - """ - Return the sync committee indices, with possible duplicates, for the next sync committee. - """ - epoch = Epoch(get_current_epoch(state) + 1) - - MAX_RANDOM_BYTE = 2**8 - 1 - active_validator_indices = get_active_validator_indices(state, epoch) - active_validator_count = uint64(len(active_validator_indices)) - seed = get_seed(state, epoch, DOMAIN_SYNC_COMMITTEE) - i = 0 - sync_committee_indices: List[ValidatorIndex] = [] - while len(sync_committee_indices) < SYNC_COMMITTEE_SIZE: - shuffled_index = compute_shuffled_index(uint64(i % active_validator_count), active_validator_count, seed) - candidate_index = active_validator_indices[shuffled_index] - random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] - effective_balance = state.validators[candidate_index].effective_balance - if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: - sync_committee_indices.append(candidate_index) - i += 1 - return sync_committee_indices - - -def get_next_sync_committee(state: BeaconState) -> SyncCommittee: - """ - Return the next sync committee, with possible pubkey duplicates. - """ - indices = get_next_sync_committee_indices(state) - pubkeys = [state.validators[index].pubkey for index in indices] - aggregate_pubkey = eth_aggregate_pubkeys(pubkeys) - return SyncCommittee(pubkeys=pubkeys, aggregate_pubkey=aggregate_pubkey) - - -def get_base_reward_per_increment(state: BeaconState) -> Gwei: - return Gwei(EFFECTIVE_BALANCE_INCREMENT * BASE_REWARD_FACTOR // integer_squareroot(get_total_active_balance(state))) - - -def get_unslashed_participating_indices(state: BeaconState, flag_index: int, epoch: Epoch) -> Set[ValidatorIndex]: - """ - Return the set of validator indices that are both active and unslashed for the given ``flag_index`` and ``epoch``. - """ - 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 - active_validator_indices = get_active_validator_indices(state, epoch) - participating_indices = [i for i in active_validator_indices if has_flag(epoch_participation[i], flag_index)] - return set(filter(lambda index: not state.validators[index].slashed, participating_indices)) - - -def get_attestation_participation_flag_indices(state: BeaconState, - data: AttestationData, - inclusion_delay: uint64) -> Sequence[int]: - """ - Return the flag indices that are satisfied by an attestation. - """ - if data.target.epoch == get_current_epoch(state): - justified_checkpoint = state.current_justified_checkpoint - else: - justified_checkpoint = state.previous_justified_checkpoint - - # Matching roots - is_matching_source = data.source == justified_checkpoint - is_matching_target = is_matching_source and data.target.root == get_block_root(state, data.target.epoch) - is_matching_head = is_matching_target and data.beacon_block_root == get_block_root_at_slot(state, data.slot) - assert is_matching_source - - participation_flag_indices = [] - if is_matching_source and inclusion_delay <= integer_squareroot(SLOTS_PER_EPOCH): - participation_flag_indices.append(TIMELY_SOURCE_FLAG_INDEX) - if is_matching_target and inclusion_delay <= SLOTS_PER_EPOCH: - participation_flag_indices.append(TIMELY_TARGET_FLAG_INDEX) - if is_matching_head and inclusion_delay == MIN_ATTESTATION_INCLUSION_DELAY: - participation_flag_indices.append(TIMELY_HEAD_FLAG_INDEX) - - return participation_flag_indices - - -def get_flag_index_deltas(state: BeaconState, flag_index: int) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: - """ - Return the deltas for a given ``flag_index`` by scanning through the participation flags. - """ - rewards = [Gwei(0)] * len(state.validators) - penalties = [Gwei(0)] * len(state.validators) - previous_epoch = get_previous_epoch(state) - unslashed_participating_indices = get_unslashed_participating_indices(state, flag_index, previous_epoch) - weight = PARTICIPATION_FLAG_WEIGHTS[flag_index] - unslashed_participating_balance = get_total_balance(state, unslashed_participating_indices) - unslashed_participating_increments = unslashed_participating_balance // EFFECTIVE_BALANCE_INCREMENT - active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT - for index in get_eligible_validator_indices(state): - base_reward = get_base_reward(state, index) - if index in unslashed_participating_indices: - if not is_in_inactivity_leak(state): - reward_numerator = base_reward * weight * unslashed_participating_increments - rewards[index] += Gwei(reward_numerator // (active_increments * WEIGHT_DENOMINATOR)) - elif flag_index != TIMELY_HEAD_FLAG_INDEX: - penalties[index] += Gwei(base_reward * weight // WEIGHT_DENOMINATOR) - return rewards, penalties - - -def process_sync_aggregate(state: BeaconState, sync_aggregate: SyncAggregate) -> None: - # Verify sync committee aggregate signature signing over the previous slot block root - committee_pubkeys = state.current_sync_committee.pubkeys - participant_pubkeys = [pubkey for pubkey, bit in zip(committee_pubkeys, sync_aggregate.sync_committee_bits) if bit] - previous_slot = max(state.slot, Slot(1)) - Slot(1) - domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, compute_epoch_at_slot(previous_slot)) - signing_root = compute_signing_root(get_block_root_at_slot(state, previous_slot), domain) - assert eth_fast_aggregate_verify(participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature) - - # Compute participant and proposer rewards - total_active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT - total_base_rewards = Gwei(get_base_reward_per_increment(state) * total_active_increments) - max_participant_rewards = Gwei(total_base_rewards * SYNC_REWARD_WEIGHT // WEIGHT_DENOMINATOR // SLOTS_PER_EPOCH) - participant_reward = Gwei(max_participant_rewards // SYNC_COMMITTEE_SIZE) - proposer_reward = Gwei(participant_reward * PROPOSER_WEIGHT // (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)) - - # Apply participant and proposer rewards - all_pubkeys = [v.pubkey for v in state.validators] - committee_indices = [ValidatorIndex(all_pubkeys.index(pubkey)) for pubkey in state.current_sync_committee.pubkeys] - for participant_index, participation_bit in zip(committee_indices, sync_aggregate.sync_committee_bits): - if participation_bit: - increase_balance(state, participant_index, participant_reward) - increase_balance(state, get_beacon_proposer_index(state), proposer_reward) - else: - decrease_balance(state, participant_index, participant_reward) - - -def process_inactivity_updates(state: BeaconState) -> None: - # Skip the genesis epoch as score updates are based on the previous epoch participation - if get_current_epoch(state) == GENESIS_EPOCH: - return - - for index in get_eligible_validator_indices(state): - # Increase the inactivity score of inactive validators - if index in get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)): - state.inactivity_scores[index] -= min(1, state.inactivity_scores[index]) - else: - state.inactivity_scores[index] += config.INACTIVITY_SCORE_BIAS - # Decrease the inactivity score of all eligible validators during a leak-free epoch - if not is_in_inactivity_leak(state): - state.inactivity_scores[index] -= min(config.INACTIVITY_SCORE_RECOVERY_RATE, state.inactivity_scores[index]) - - -def process_participation_flag_updates(state: BeaconState) -> None: - state.previous_epoch_participation = state.current_epoch_participation - state.current_epoch_participation = [ParticipationFlags(0b0000_0000) for _ in range(len(state.validators))] - - -def process_sync_committee_updates(state: BeaconState) -> None: - 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_next_sync_committee(state) - - -def eth_aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey: - return bls.AggregatePKs(pubkeys) - - -def eth_fast_aggregate_verify(pubkeys: Sequence[BLSPubkey], message: Bytes32, signature: BLSSignature) -> bool: - """ - Wrapper to ``bls.FastAggregateVerify`` accepting the ``G2_POINT_AT_INFINITY`` signature when ``pubkeys`` is empty. - """ - if len(pubkeys) == 0 and signature == G2_POINT_AT_INFINITY: - return True - return bls.FastAggregateVerify(pubkeys, message, signature) - - -def translate_participation(state: BeaconState, pending_attestations: Sequence[phase0.PendingAttestation]) -> None: - for attestation in pending_attestations: - data = attestation.data - inclusion_delay = attestation.inclusion_delay - # Translate attestation inclusion info to flag indices - participation_flag_indices = get_attestation_participation_flag_indices(state, data, inclusion_delay) - - # Apply flags to all attesting validators - epoch_participation = state.previous_epoch_participation - for index in get_attesting_indices(state, data, attestation.aggregation_bits): - for flag_index in participation_flag_indices: - epoch_participation[index] = add_flag(epoch_participation[index], flag_index) - - -def upgrade_to_altair(pre: phase0.BeaconState) -> BeaconState: - epoch = phase0.get_current_epoch(pre) - post = BeaconState( - # Versioning - genesis_time=pre.genesis_time, - genesis_validators_root=pre.genesis_validators_root, - slot=pre.slot, - fork=Fork( - previous_version=pre.fork.current_version, - current_version=config.ALTAIR_FORK_VERSION, - epoch=epoch, - ), - # History - latest_block_header=pre.latest_block_header, - block_roots=pre.block_roots, - state_roots=pre.state_roots, - historical_roots=pre.historical_roots, - # Eth1 - eth1_data=pre.eth1_data, - eth1_data_votes=pre.eth1_data_votes, - eth1_deposit_index=pre.eth1_deposit_index, - # Registry - validators=pre.validators, - balances=pre.balances, - # Randomness - randao_mixes=pre.randao_mixes, - # Slashings - slashings=pre.slashings, - # Participation - previous_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))], - current_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))], - # Finality - justification_bits=pre.justification_bits, - previous_justified_checkpoint=pre.previous_justified_checkpoint, - current_justified_checkpoint=pre.current_justified_checkpoint, - finalized_checkpoint=pre.finalized_checkpoint, - # Inactivity - inactivity_scores=[uint64(0) for _ in range(len(pre.validators))], - ) - # Fill in previous epoch participation from the pre state's pending attestations - translate_participation(post, pre.previous_epoch_attestations) - - # Fill in sync committees - # Note: A duplicate committee is assigned for the current and next committee at the fork boundary - post.current_sync_committee = get_next_sync_committee(post) - post.next_sync_committee = get_next_sync_committee(post) - return post - - -def compute_sync_committee_period(epoch: Epoch) -> uint64: - return epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - - -def is_assigned_to_sync_committee(state: BeaconState, - epoch: Epoch, - validator_index: ValidatorIndex) -> bool: - sync_committee_period = compute_sync_committee_period(epoch) - current_epoch = get_current_epoch(state) - current_sync_committee_period = compute_sync_committee_period(current_epoch) - next_sync_committee_period = current_sync_committee_period + 1 - assert sync_committee_period in (current_sync_committee_period, next_sync_committee_period) - - pubkey = state.validators[validator_index].pubkey - if sync_committee_period == current_sync_committee_period: - return pubkey in state.current_sync_committee.pubkeys - else: # sync_committee_period == next_sync_committee_period - return pubkey in state.next_sync_committee.pubkeys - - -def process_sync_committee_contributions(block: BeaconBlock, - contributions: Set[SyncCommitteeContribution]) -> None: - sync_aggregate = SyncAggregate() - signatures = [] - sync_subcommittee_size = SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT - - for contribution in contributions: - subcommittee_index = contribution.subcommittee_index - for index, participated in enumerate(contribution.aggregation_bits): - if participated: - participant_index = sync_subcommittee_size * subcommittee_index + index - sync_aggregate.sync_committee_bits[participant_index] = True - signatures.append(contribution.signature) - - sync_aggregate.sync_committee_signature = bls.Aggregate(signatures) - - block.body.sync_aggregate = sync_aggregate - - -def get_sync_committee_message(state: BeaconState, - block_root: Root, - validator_index: ValidatorIndex, - privkey: int) -> SyncCommitteeMessage: - epoch = get_current_epoch(state) - domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, epoch) - signing_root = compute_signing_root(block_root, domain) - signature = bls.Sign(privkey, signing_root) - - return SyncCommitteeMessage( - slot=state.slot, - beacon_block_root=block_root, - validator_index=validator_index, - signature=signature, - ) - - -def compute_subnets_for_sync_committee(state: BeaconState, validator_index: ValidatorIndex) -> Set[uint64]: - next_slot_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) - if compute_sync_committee_period(get_current_epoch(state)) == compute_sync_committee_period(next_slot_epoch): - sync_committee = state.current_sync_committee - else: - sync_committee = state.next_sync_committee - - target_pubkey = state.validators[validator_index].pubkey - sync_committee_indices = [index for index, pubkey in enumerate(sync_committee.pubkeys) if pubkey == target_pubkey] - return set([ - uint64(index // (SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT)) - for index in sync_committee_indices - ]) - - -def get_sync_committee_selection_proof(state: BeaconState, - slot: Slot, - subcommittee_index: uint64, - privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF, compute_epoch_at_slot(slot)) - signing_data = SyncAggregatorSelectionData( - slot=slot, - subcommittee_index=subcommittee_index, - ) - signing_root = compute_signing_root(signing_data, domain) - return bls.Sign(privkey, signing_root) - - -def is_sync_committee_aggregator(signature: BLSSignature) -> bool: - modulo = max(1, SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT // TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE) - return bytes_to_uint64(hash(signature)[0:8]) % modulo == 0 - - -def get_contribution_and_proof(state: BeaconState, - aggregator_index: ValidatorIndex, - contribution: SyncCommitteeContribution, - privkey: int) -> ContributionAndProof: - selection_proof = get_sync_committee_selection_proof( - state, - contribution.slot, - contribution.subcommittee_index, - privkey, - ) - return ContributionAndProof( - aggregator_index=aggregator_index, - contribution=contribution, - selection_proof=selection_proof, - ) - - -def get_contribution_and_proof_signature(state: BeaconState, - contribution_and_proof: ContributionAndProof, - privkey: int) -> BLSSignature: - contribution = contribution_and_proof.contribution - domain = get_domain(state, DOMAIN_CONTRIBUTION_AND_PROOF, compute_epoch_at_slot(contribution.slot)) - signing_root = compute_signing_root(contribution_and_proof, domain) - return bls.Sign(privkey, signing_root) - - -def get_sync_subcommittee_pubkeys(state: BeaconState, subcommittee_index: uint64) -> Sequence[BLSPubkey]: - # Committees assigned to `slot` sign for `slot - 1` - # This creates the exceptional logic below when transitioning between sync committee periods - next_slot_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) - if compute_sync_committee_period(get_current_epoch(state)) == compute_sync_committee_period(next_slot_epoch): - sync_committee = state.current_sync_committee - else: - sync_committee = state.next_sync_committee - - # Return pubkeys for the subcommittee index - sync_subcommittee_size = SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT - i = subcommittee_index * sync_subcommittee_size - return sync_committee.pubkeys[i:i + sync_subcommittee_size] - - -def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: - return uint64(generalized_index % 2**(floorlog2(generalized_index))) - - -def validate_light_client_update(snapshot: LightClientSnapshot, - update: LightClientUpdate, - genesis_validators_root: Root) -> None: - # Verify update slot is larger than snapshot slot - assert update.header.slot > snapshot.header.slot - - # Verify update does not skip a sync committee period - snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - assert update_period in (snapshot_period, snapshot_period + 1) - - # Verify update header root is the finalized root of the finality header, if specified - if update.finality_header == BeaconBlockHeader(): - signed_header = update.header - assert update.finality_branch == [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))] - else: - signed_header = update.finality_header - assert is_valid_merkle_branch( - leaf=hash_tree_root(update.header), - branch=update.finality_branch, - depth=floorlog2(FINALIZED_ROOT_INDEX), - index=get_subtree_index(FINALIZED_ROOT_INDEX), - root=update.finality_header.state_root, - ) - - # Verify update next sync committee if the update period incremented - if update_period == snapshot_period: - sync_committee = snapshot.current_sync_committee - assert update.next_sync_committee_branch == [Bytes32() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))] - else: - sync_committee = snapshot.next_sync_committee - assert is_valid_merkle_branch( - leaf=hash_tree_root(update.next_sync_committee), - branch=update.next_sync_committee_branch, - depth=floorlog2(NEXT_SYNC_COMMITTEE_INDEX), - index=get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX), - root=update.header.state_root, - ) - - # Verify sync committee has sufficient participants - assert sum(update.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS - - # Verify sync committee aggregate signature - participant_pubkeys = [pubkey for (bit, pubkey) in zip(update.sync_committee_bits, sync_committee.pubkeys) if bit] - domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version, genesis_validators_root) - signing_root = compute_signing_root(signed_header, domain) - assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature) - - -def apply_light_client_update(snapshot: LightClientSnapshot, update: LightClientUpdate) -> None: - snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - if update_period == snapshot_period + 1: - snapshot.current_sync_committee = snapshot.next_sync_committee - snapshot.next_sync_committee = update.next_sync_committee - snapshot.header = update.header - - -def process_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot, - genesis_validators_root: Root) -> None: - validate_light_client_update(store.snapshot, update, genesis_validators_root) - store.valid_updates.add(update) - - update_timeout = SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD - if ( - sum(update.sync_committee_bits) * 3 >= len(update.sync_committee_bits) * 2 - and update.finality_header != BeaconBlockHeader() - ): - # Apply update if (1) 2/3 quorum is reached and (2) we have a finality proof. - # Note that (2) means that the current light client design needs finality. - # It may be changed to re-organizable light client design. See the on-going issue consensus-specs#2182. - apply_light_client_update(store.snapshot, update) - store.valid_updates = set() - elif current_slot > store.snapshot.header.slot + update_timeout: - # Forced best update when the update timeout has elapsed - apply_light_client_update(store.snapshot, - max(store.valid_updates, key=lambda update: sum(update.sync_committee_bits))) - store.valid_updates = set() - - -def is_merge_transition_complete(state: BeaconState) -> bool: - return state.latest_execution_payload_header != ExecutionPayloadHeader() - - -def is_merge_transition_block(state: BeaconState, body: BeaconBlockBody) -> bool: - return not is_merge_transition_complete(state) and body.execution_payload != ExecutionPayload() - - -def is_execution_enabled(state: BeaconState, body: BeaconBlockBody) -> bool: - return is_merge_transition_block(state, body) or is_merge_transition_complete(state) - - -def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64: - slots_since_genesis = slot - GENESIS_SLOT - return uint64(state.genesis_time + slots_since_genesis * config.SECONDS_PER_SLOT) - - -def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None: - # Verify consistency of the parent hash with respect to the previous execution payload header - if is_merge_transition_complete(state): - assert payload.parent_hash == state.latest_execution_payload_header.block_hash - # Verify random - assert payload.random == get_randao_mix(state, get_current_epoch(state)) - # Verify timestamp - assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) - # Verify the execution payload is valid - assert execution_engine.execute_payload(payload) - # Cache execution payload header - state.latest_execution_payload_header = ExecutionPayloadHeader( - parent_hash=payload.parent_hash, - fee_recipient=payload.fee_recipient, - state_root=payload.state_root, - receipt_root=payload.receipt_root, - logs_bloom=payload.logs_bloom, - random=payload.random, - block_number=payload.block_number, - gas_limit=payload.gas_limit, - gas_used=payload.gas_used, - timestamp=payload.timestamp, - extra_data=payload.extra_data, - base_fee_per_gas=payload.base_fee_per_gas, - block_hash=payload.block_hash, - transactions_root=hash_tree_root(payload.transactions), - ) - - -def upgrade_to_merge(pre: altair.BeaconState) -> BeaconState: - epoch = altair.get_current_epoch(pre) - post = BeaconState( - # Versioning - genesis_time=pre.genesis_time, - genesis_validators_root=pre.genesis_validators_root, - slot=pre.slot, - fork=Fork( - previous_version=pre.fork.current_version, - current_version=config.MERGE_FORK_VERSION, - epoch=epoch, - ), - # History - latest_block_header=pre.latest_block_header, - block_roots=pre.block_roots, - state_roots=pre.state_roots, - historical_roots=pre.historical_roots, - # Eth1 - eth1_data=pre.eth1_data, - eth1_data_votes=pre.eth1_data_votes, - eth1_deposit_index=pre.eth1_deposit_index, - # Registry - validators=pre.validators, - balances=pre.balances, - # Randomness - randao_mixes=pre.randao_mixes, - # Slashings - slashings=pre.slashings, - # Participation - previous_epoch_participation=pre.previous_epoch_participation, - current_epoch_participation=pre.current_epoch_participation, - # Finality - justification_bits=pre.justification_bits, - previous_justified_checkpoint=pre.previous_justified_checkpoint, - current_justified_checkpoint=pre.current_justified_checkpoint, - finalized_checkpoint=pre.finalized_checkpoint, - # Inactivity - inactivity_scores=pre.inactivity_scores, - # Sync - current_sync_committee=pre.current_sync_committee, - next_sync_committee=pre.next_sync_committee, - # Execution-layer - latest_execution_payload_header=ExecutionPayloadHeader(), - ) - - return post - - -def is_valid_terminal_pow_block(block: PowBlock, parent: PowBlock) -> bool: - is_total_difficulty_reached = block.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY - is_parent_total_difficulty_valid = parent.total_difficulty < config.TERMINAL_TOTAL_DIFFICULTY - return is_total_difficulty_reached and is_parent_total_difficulty_valid - - -def validate_merge_block(block: BeaconBlock) -> None: - """ - Check the parent PoW block of execution payload is a valid terminal PoW block. - - Note: Unavailable PoW block(s) may later become available, - and a client software MAY delay a call to ``validate_merge_block`` - until the PoW block(s) become available. - """ - if config.TERMINAL_BLOCK_HASH != Hash32(): - # If `config.TERMINAL_BLOCK_HASH` is used as an override, the activation epoch must be reached. - assert compute_epoch_at_slot(block.slot) >= config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH - assert block.body.execution_payload.parent_hash == config.TERMINAL_BLOCK_HASH - return - - pow_block = get_pow_block(block.body.execution_payload.parent_hash) - # Check if `pow_block` is available - assert pow_block is not None - pow_parent = get_pow_block(pow_block.parent_hash) - # Check if `pow_parent` is available - assert pow_parent is not None - # Check if `pow_block` is a valid terminal PoW block - assert is_valid_terminal_pow_block(pow_block, pow_parent) - - -def get_pow_block_at_terminal_total_difficulty(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlock]: - # `pow_chain` abstractly represents all blocks in the PoW chain - for block in pow_chain.values(): - block_reached_ttd = block.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY - if block_reached_ttd: - # If genesis block, no parent exists so reaching TTD alone qualifies as valid terminal block - if block.parent_hash == Hash32(): - return block - parent = pow_chain[block.parent_hash] - parent_reached_ttd = parent.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY - if not parent_reached_ttd: - return block - - return None - - -def get_terminal_pow_block(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlock]: - if config.TERMINAL_BLOCK_HASH != Hash32(): - # Terminal block hash override takes precedence over terminal total difficulty - if config.TERMINAL_BLOCK_HASH in pow_chain: - return pow_chain[config.TERMINAL_BLOCK_HASH] - else: - return None - - return get_pow_block_at_terminal_total_difficulty(pow_chain) - - -def prepare_execution_payload(state: BeaconState, - pow_chain: Dict[Hash32, PowBlock], - finalized_block_hash: Hash32, - suggested_fee_recipient: ExecutionAddress, - execution_engine: ExecutionEngine) -> Optional[PayloadId]: - if not is_merge_transition_complete(state): - is_terminal_block_hash_set = config.TERMINAL_BLOCK_HASH != Hash32() - is_activation_epoch_reached = get_current_epoch(state) >= config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH - if is_terminal_block_hash_set and not is_activation_epoch_reached: - # Terminal block hash is set but activation epoch is not yet reached, no prepare payload call is needed - return None - - terminal_pow_block = get_terminal_pow_block(pow_chain) - if terminal_pow_block is None: - # Pre-merge, no prepare payload call is needed - return None - # Signify merge via producing on top of the terminal PoW block - parent_hash = terminal_pow_block.block_hash - else: - # Post-merge, normal payload - parent_hash = state.latest_execution_payload_header.block_hash - - # Set the forkchoice head and initiate the payload build process - payload_attributes = PayloadAttributes( - timestamp=compute_timestamp_at_slot(state, state.slot), - random=get_randao_mix(state, get_current_epoch(state)), - suggested_fee_recipient=suggested_fee_recipient, - ) - return execution_engine.notify_forkchoice_updated(parent_hash, finalized_block_hash, payload_attributes) - - -def get_execution_payload(payload_id: Optional[PayloadId], execution_engine: ExecutionEngine) -> ExecutionPayload: - if payload_id is None: - # Pre-merge, empty payload - return ExecutionPayload() - else: - return execution_engine.get_payload(payload_id) - - -def withdraw(state: BeaconState, index: ValidatorIndex, amount: Gwei) -> None: - # Decrease the validator's balance - decrease_balance(state, index, amount) - # Create a corresponding withdrawal receipt - receipt = WithdrawalReceipt( - index=WithdrawalReceiptIndex(len(state.withdrawal_receipts)), - address=state.validators[index].withdrawal_credentials[12:], - amount=amount, - ) - state.withdrawal_receipts.append(receipt) - - -def is_withdrawable_validator(validator: Validator, epoch: Epoch) -> bool: - """ - Check if ``validator`` is withdrawable. - """ - return validator.withdrawable_epoch <= epoch - - -def process_withdrawals(state: BeaconState) -> None: - current_epoch = get_current_epoch(state) - for index, validator in enumerate(state.validators): - balance = state.balances[index] - is_balance_nonzero = state.balances[index] == 0 - is_eth1_withdrawal_prefix = validator.withdrawal_credentials[0] != ETH1_ADDRESS_WITHDRAWAL_PREFIX - if is_balance_nonzero and is_eth1_withdrawal_prefix and is_withdrawable_validator(validator, current_epoch): - withdraw(state, ValidatorIndex(index), balance) - - -def upgrade_to_capella(pre: merge.BeaconState) -> BeaconState: - epoch = merge.get_current_epoch(pre) - post = BeaconState( - # Versioning - genesis_time=pre.genesis_time, - genesis_validators_root=pre.genesis_validators_root, - slot=pre.slot, - fork=Fork( - previous_version=pre.fork.current_version, - current_version=CAPELLA_FORK_VERSION, - epoch=epoch, - ), - # History - latest_block_header=pre.latest_block_header, - block_roots=pre.block_roots, - state_roots=pre.state_roots, - historical_roots=pre.historical_roots, - # Eth1 - eth1_data=pre.eth1_data, - eth1_data_votes=pre.eth1_data_votes, - eth1_deposit_index=pre.eth1_deposit_index, - # Registry - validators=pre.validators, - balances=pre.balances, - # Randomness - randao_mixes=pre.randao_mixes, - # Slashings - slashings=pre.slashings, - # Participation - previous_epoch_participation=pre.previous_epoch_participation, - current_epoch_participation=pre.current_epoch_participation, - # Finality - justification_bits=pre.justification_bits, - previous_justified_checkpoint=pre.previous_justified_checkpoint, - current_justified_checkpoint=pre.current_justified_checkpoint, - finalized_checkpoint=pre.finalized_checkpoint, - # Inactivity - inactivity_scores=pre.inactivity_scores, - # Sync - current_sync_committee=pre.current_sync_committee, - next_sync_committee=pre.next_sync_committee, - # Execution-layer - latest_execution_payload_header=pre.latest_execution_payload_header, - # Withdrawals - withdrawal_receipts=[], - ) - - return post - - -def get_eth1_data(block: Eth1Block) -> Eth1Data: - """ - A stub function return mocking Eth1Data. - """ - return Eth1Data( - deposit_root=block.deposit_root, - deposit_count=block.deposit_count, - block_hash=hash_tree_root(block)) - - -def cache_this(key_fn, value_fn, lru_size): # type: ignore - cache_dict = LRU(size=lru_size) - - def wrapper(*args, **kw): # type: ignore - key = key_fn(*args, **kw) - nonlocal cache_dict - if key not in cache_dict: - cache_dict[key] = value_fn(*args, **kw) - return cache_dict[key] - return wrapper - - -_compute_shuffled_index = compute_shuffled_index -compute_shuffled_index = cache_this( - lambda index, index_count, seed: (index, index_count, seed), - _compute_shuffled_index, lru_size=SLOTS_PER_EPOCH * 3) - -_get_total_active_balance = get_total_active_balance -get_total_active_balance = cache_this( - lambda state: (state.validators.hash_tree_root(), compute_epoch_at_slot(state.slot)), - _get_total_active_balance, lru_size=10) - -_get_base_reward = get_base_reward -get_base_reward = cache_this( - lambda state, index: (state.validators.hash_tree_root(), state.slot, index), - _get_base_reward, lru_size=2048) - -_get_committee_count_per_slot = get_committee_count_per_slot -get_committee_count_per_slot = cache_this( - lambda state, epoch: (state.validators.hash_tree_root(), epoch), - _get_committee_count_per_slot, lru_size=SLOTS_PER_EPOCH * 3) - -_get_active_validator_indices = get_active_validator_indices -get_active_validator_indices = cache_this( - lambda state, epoch: (state.validators.hash_tree_root(), epoch), - _get_active_validator_indices, lru_size=3) - -_get_beacon_committee = get_beacon_committee -get_beacon_committee = cache_this( - lambda state, slot, index: (state.validators.hash_tree_root(), state.randao_mixes.hash_tree_root(), slot, index), - _get_beacon_committee, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3) - -_get_matching_target_attestations = get_matching_target_attestations -get_matching_target_attestations = cache_this( - lambda state, epoch: (state.hash_tree_root(), epoch), - _get_matching_target_attestations, lru_size=10) - -_get_matching_head_attestations = get_matching_head_attestations -get_matching_head_attestations = cache_this( - lambda state, epoch: (state.hash_tree_root(), epoch), - _get_matching_head_attestations, lru_size=10) - -_get_attesting_indices = get_attesting_indices -get_attesting_indices = cache_this( - lambda state, data, bits: ( - state.randao_mixes.hash_tree_root(), - state.validators.hash_tree_root(), data.hash_tree_root(), bits.hash_tree_root() - ), - _get_attesting_indices, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3) - - -def get_generalized_index(ssz_class: Any, *path: Sequence[PyUnion[int, SSZVariableName]]) -> GeneralizedIndex: - ssz_path = Path(ssz_class) - for item in path: - ssz_path = ssz_path / item - return GeneralizedIndex(ssz_path.gindex()) - - -ExecutionState = Any - - -def get_pow_block(hash: Bytes32) -> Optional[PowBlock]: - return PowBlock(block_hash=hash, parent_hash=Bytes32(), total_difficulty=uint256(0)) - - -def get_execution_state(execution_state_root: Bytes32) -> ExecutionState: - pass - - -def get_pow_chain_head() -> PowBlock: - pass - - -class NoopExecutionEngine(ExecutionEngine): - - def execute_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: - return True - - def notify_forkchoice_updated(self: ExecutionEngine, - head_block_hash: Hash32, - finalized_block_hash: Hash32, - payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: - pass - - def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayload: - raise NotImplementedError("no default block production") - - -EXECUTION_ENGINE = NoopExecutionEngine() - - -assert FINALIZED_ROOT_INDEX == get_generalized_index(BeaconState, 'finalized_checkpoint', 'root') -assert NEXT_SYNC_COMMITTEE_INDEX == get_generalized_index(BeaconState, 'next_sync_committee') diff --git a/tests/core/pyspec/eth2spec/capella/minimal.py b/tests/core/pyspec/eth2spec/capella/minimal.py deleted file mode 100644 index 8f0a5f67aa..0000000000 --- a/tests/core/pyspec/eth2spec/capella/minimal.py +++ /dev/null @@ -1,3074 +0,0 @@ -from lru import LRU -from dataclasses import ( - dataclass, - field, -) -from typing import ( - Any, Callable, Dict, Set, Sequence, Tuple, Optional, TypeVar, NamedTuple -) - -from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy, uint_to_bytes -from eth2spec.utils.ssz.ssz_typing import ( - View, boolean, Container, List, Vector, uint8, uint32, uint64, - Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist) -from eth2spec.utils.ssz.ssz_typing import Bitvector # noqa: F401 -from eth2spec.utils import bls -from eth2spec.utils.hash_function import hash - - -from typing import NewType, Union as PyUnion - -from eth2spec.phase0 import minimal as phase0 -from eth2spec.utils.ssz.ssz_typing import Path - -from typing import Protocol -from eth2spec.altair import minimal as altair -from eth2spec.utils.ssz.ssz_typing import Bytes8, Bytes20, ByteList, ByteVector, uint256 - -from eth2spec.merge import minimal as merge - -SSZObject = TypeVar('SSZObject', bound=View) - - -SSZVariableName = str -GeneralizedIndex = NewType('GeneralizedIndex', int) - - -fork = 'capella' - - -MAX_BYTES_PER_TRANSACTION = uint64(2**30) - - -class Slot(uint64): - pass - - -class Epoch(uint64): - pass - - -class CommitteeIndex(uint64): - pass - - -class ValidatorIndex(uint64): - pass - - -class Gwei(uint64): - pass - - -class Root(Bytes32): - pass - - -class Hash32(Bytes32): - pass - - -class Version(Bytes4): - pass - - -class DomainType(Bytes4): - pass - - -class ForkDigest(Bytes4): - pass - - -class Domain(Bytes32): - pass - - -class BLSPubkey(Bytes48): - pass - - -class BLSSignature(Bytes96): - pass - - -class Ether(uint64): - pass - - -class ParticipationFlags(uint8): - pass - - -class ExecutionAddress(Bytes20): - pass - - -class PayloadId(Bytes8): - pass - - -class WithdrawalReceiptIndex(uint64): - pass - - -Transaction = ByteList[MAX_BYTES_PER_TRANSACTION] - - -def ceillog2(x: int) -> uint64: - if x < 1: - raise ValueError(f"ceillog2 accepts only positive values, x={x}") - return uint64((x - 1).bit_length()) - - -def floorlog2(x: int) -> uint64: - if x < 1: - raise ValueError(f"floorlog2 accepts only positive values, x={x}") - return uint64(x.bit_length() - 1) - - -FINALIZED_ROOT_INDEX = GeneralizedIndex(105) -NEXT_SYNC_COMMITTEE_INDEX = GeneralizedIndex(55) - -# Constant vars -GENESIS_SLOT = Slot(0) -GENESIS_EPOCH = Epoch(0) -FAR_FUTURE_EPOCH = Epoch(2**64 - 1) -BASE_REWARDS_PER_EPOCH = uint64(4) -DEPOSIT_CONTRACT_TREE_DEPTH = uint64(2**5) -JUSTIFICATION_BITS_LENGTH = uint64(4) -ENDIANNESS = 'little' -BLS_WITHDRAWAL_PREFIX = Bytes1('0x00') -ETH1_ADDRESS_WITHDRAWAL_PREFIX = Bytes1('0x01') -DOMAIN_BEACON_PROPOSER = DomainType('0x00000000') -DOMAIN_BEACON_ATTESTER = DomainType('0x01000000') -DOMAIN_RANDAO = DomainType('0x02000000') -DOMAIN_DEPOSIT = DomainType('0x03000000') -DOMAIN_VOLUNTARY_EXIT = DomainType('0x04000000') -DOMAIN_SELECTION_PROOF = DomainType('0x05000000') -DOMAIN_AGGREGATE_AND_PROOF = DomainType('0x06000000') -INTERVALS_PER_SLOT = uint64(3) -TARGET_AGGREGATORS_PER_COMMITTEE = 2**4 -RANDOM_SUBNETS_PER_VALIDATOR = 2**0 -EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION = 2**8 -ATTESTATION_SUBNET_COUNT = 64 -ETH_TO_GWEI = uint64(10**9) -SAFETY_DECAY = uint64(10) -TIMELY_SOURCE_FLAG_INDEX = 0 -TIMELY_TARGET_FLAG_INDEX = 1 -TIMELY_HEAD_FLAG_INDEX = 2 -TIMELY_SOURCE_WEIGHT = uint64(14) -TIMELY_TARGET_WEIGHT = uint64(26) -TIMELY_HEAD_WEIGHT = uint64(14) -SYNC_REWARD_WEIGHT = uint64(2) -PROPOSER_WEIGHT = uint64(8) -WEIGHT_DENOMINATOR = uint64(64) -DOMAIN_SYNC_COMMITTEE = DomainType('0x07000000') -DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF = DomainType('0x08000000') -DOMAIN_CONTRIBUTION_AND_PROOF = DomainType('0x09000000') -PARTICIPATION_FLAG_WEIGHTS = [TIMELY_SOURCE_WEIGHT, TIMELY_TARGET_WEIGHT, TIMELY_HEAD_WEIGHT] -G2_POINT_AT_INFINITY = BLSSignature(b'\xc0' + b'\x00' * 95) -TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE = 2**4 -SYNC_COMMITTEE_SUBNET_COUNT = 4 -WITHDRAWAL_RECEIPT_LIMIT = uint64(2**40) - -# Preset vars -MAX_COMMITTEES_PER_SLOT = uint64(4) -TARGET_COMMITTEE_SIZE = uint64(4) -MAX_VALIDATORS_PER_COMMITTEE = uint64(2048) -SHUFFLE_ROUND_COUNT = uint64(10) -HYSTERESIS_QUOTIENT = uint64(4) -HYSTERESIS_DOWNWARD_MULTIPLIER = uint64(1) -HYSTERESIS_UPWARD_MULTIPLIER = uint64(5) -MIN_DEPOSIT_AMOUNT = Gwei(1000000000) -MAX_EFFECTIVE_BALANCE = Gwei(32000000000) -EFFECTIVE_BALANCE_INCREMENT = Gwei(1000000000) -MIN_ATTESTATION_INCLUSION_DELAY = uint64(1) -SLOTS_PER_EPOCH = uint64(8) -MIN_SEED_LOOKAHEAD = uint64(1) -MAX_SEED_LOOKAHEAD = uint64(4) -MIN_EPOCHS_TO_INACTIVITY_PENALTY = uint64(4) -EPOCHS_PER_ETH1_VOTING_PERIOD = uint64(4) -SLOTS_PER_HISTORICAL_ROOT = uint64(64) -EPOCHS_PER_HISTORICAL_VECTOR = uint64(64) -EPOCHS_PER_SLASHINGS_VECTOR = uint64(64) -HISTORICAL_ROOTS_LIMIT = uint64(16777216) -VALIDATOR_REGISTRY_LIMIT = uint64(1099511627776) -BASE_REWARD_FACTOR = uint64(64) -WHISTLEBLOWER_REWARD_QUOTIENT = uint64(512) -PROPOSER_REWARD_QUOTIENT = uint64(8) -INACTIVITY_PENALTY_QUOTIENT = uint64(33554432) -MIN_SLASHING_PENALTY_QUOTIENT = uint64(64) -PROPORTIONAL_SLASHING_MULTIPLIER = uint64(2) -MAX_PROPOSER_SLASHINGS = 16 -MAX_ATTESTER_SLASHINGS = 2 -MAX_ATTESTATIONS = 128 -MAX_DEPOSITS = 16 -MAX_VOLUNTARY_EXITS = 16 -SAFE_SLOTS_TO_UPDATE_JUSTIFIED = 2 -INACTIVITY_PENALTY_QUOTIENT_ALTAIR = uint64(50331648) -MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR = uint64(64) -PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR = uint64(2) -SYNC_COMMITTEE_SIZE = uint64(32) -EPOCHS_PER_SYNC_COMMITTEE_PERIOD = uint64(8) -MIN_SYNC_COMMITTEE_PARTICIPANTS = 1 -MAX_BYTES_PER_TRANSACTION = uint64(1073741824) -MAX_TRANSACTIONS_PER_PAYLOAD = uint64(1048576) -BYTES_PER_LOGS_BLOOM = uint64(256) -MAX_EXTRA_DATA_BYTES = 32 -INACTIVITY_PENALTY_QUOTIENT_MERGE = uint64(16777216) -MIN_SLASHING_PENALTY_QUOTIENT_MERGE = uint64(32) -PROPORTIONAL_SLASHING_MULTIPLIER_MERGE = uint64(3) - - -class Configuration(NamedTuple): - PRESET_BASE: str - MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: uint64 - MIN_GENESIS_TIME: uint64 - GENESIS_FORK_VERSION: Version - GENESIS_DELAY: uint64 - SECONDS_PER_SLOT: uint64 - SECONDS_PER_ETH1_BLOCK: uint64 - MIN_VALIDATOR_WITHDRAWABILITY_DELAY: uint64 - SHARD_COMMITTEE_PERIOD: uint64 - ETH1_FOLLOW_DISTANCE: uint64 - EJECTION_BALANCE: Gwei - MIN_PER_EPOCH_CHURN_LIMIT: uint64 - CHURN_LIMIT_QUOTIENT: uint64 - PROPOSER_SCORE_BOOST: uint64 - INACTIVITY_SCORE_BIAS: uint64 - INACTIVITY_SCORE_RECOVERY_RATE: uint64 - ALTAIR_FORK_VERSION: Version - ALTAIR_FORK_EPOCH: Epoch - TERMINAL_TOTAL_DIFFICULTY: int - TERMINAL_BLOCK_HASH: Hash32 - TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: int - MERGE_FORK_VERSION: Version - MERGE_FORK_EPOCH: Epoch - CAPELLA_FORK_VERSION: Version - CAPELLA_FORK_EPOCH: Epoch - - -config = Configuration( - PRESET_BASE="minimal", - MIN_GENESIS_ACTIVE_VALIDATOR_COUNT=uint64(64), - MIN_GENESIS_TIME=uint64(1578009600), - GENESIS_FORK_VERSION=Version('0x00000001'), - GENESIS_DELAY=uint64(300), - SECONDS_PER_SLOT=uint64(6), - SECONDS_PER_ETH1_BLOCK=uint64(14), - MIN_VALIDATOR_WITHDRAWABILITY_DELAY=uint64(256), - SHARD_COMMITTEE_PERIOD=uint64(64), - ETH1_FOLLOW_DISTANCE=uint64(16), - EJECTION_BALANCE=Gwei(16000000000), - MIN_PER_EPOCH_CHURN_LIMIT=uint64(4), - CHURN_LIMIT_QUOTIENT=uint64(32), - PROPOSER_SCORE_BOOST=uint64(70), - INACTIVITY_SCORE_BIAS=uint64(4), - INACTIVITY_SCORE_RECOVERY_RATE=uint64(16), - ALTAIR_FORK_VERSION=Version('0x01000001'), - ALTAIR_FORK_EPOCH=Epoch(18446744073709551615), - TERMINAL_TOTAL_DIFFICULTY=115792089237316195423570985008687907853269984665640564039457584007913129638912, - TERMINAL_BLOCK_HASH=Hash32('0x0000000000000000000000000000000000000000000000000000000000000000'), - TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH=18446744073709551615, - MERGE_FORK_VERSION=Version('0x02000001'), - MERGE_FORK_EPOCH=Epoch(18446744073709551615), - CAPELLA_FORK_VERSION=Version('0x03000001'), - CAPELLA_FORK_EPOCH=Epoch(18446744073709551615), -) - - -class Fork(Container): - previous_version: Version - current_version: Version - epoch: Epoch # Epoch of latest fork - - -class ForkData(Container): - current_version: Version - genesis_validators_root: Root - - -class Checkpoint(Container): - epoch: Epoch - root: Root - - -class Validator(Container): - pubkey: BLSPubkey - withdrawal_credentials: Bytes32 # Commitment to pubkey for withdrawals - effective_balance: Gwei # Balance at stake - slashed: boolean - # Status epochs - activation_eligibility_epoch: Epoch # When criteria for activation were met - activation_epoch: Epoch - exit_epoch: Epoch - withdrawable_epoch: Epoch # When validator can withdraw funds - - -class AttestationData(Container): - slot: Slot - index: CommitteeIndex - # LMD GHOST vote - beacon_block_root: Root - # FFG vote - source: Checkpoint - target: Checkpoint - - -class IndexedAttestation(Container): - attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] - data: AttestationData - signature: BLSSignature - - -class PendingAttestation(Container): - aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] - data: AttestationData - inclusion_delay: Slot - proposer_index: ValidatorIndex - - -class Eth1Data(Container): - deposit_root: Root - deposit_count: uint64 - block_hash: Hash32 - - -class HistoricalBatch(Container): - block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] - state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] - - -class DepositMessage(Container): - pubkey: BLSPubkey - withdrawal_credentials: Bytes32 - amount: Gwei - - -class DepositData(Container): - pubkey: BLSPubkey - withdrawal_credentials: Bytes32 - amount: Gwei - signature: BLSSignature # Signing over DepositMessage - - -class BeaconBlockHeader(Container): - slot: Slot - proposer_index: ValidatorIndex - parent_root: Root - state_root: Root - body_root: Root - - -class SigningData(Container): - object_root: Root - domain: Domain - - -class AttesterSlashing(Container): - attestation_1: IndexedAttestation - attestation_2: IndexedAttestation - - -class Attestation(Container): - aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] - data: AttestationData - signature: BLSSignature - - -class Deposit(Container): - proof: Vector[Bytes32, DEPOSIT_CONTRACT_TREE_DEPTH + 1] # Merkle path to deposit root - data: DepositData - - -class VoluntaryExit(Container): - epoch: Epoch # Earliest epoch when voluntary exit can be processed - validator_index: ValidatorIndex - - -class SignedVoluntaryExit(Container): - message: VoluntaryExit - signature: BLSSignature - - -class SignedBeaconBlockHeader(Container): - message: BeaconBlockHeader - signature: BLSSignature - - -class ProposerSlashing(Container): - signed_header_1: SignedBeaconBlockHeader - signed_header_2: SignedBeaconBlockHeader - - -class Eth1Block(Container): - timestamp: uint64 - deposit_root: Root - deposit_count: uint64 - # All other eth1 block fields - - -class AggregateAndProof(Container): - aggregator_index: ValidatorIndex - aggregate: Attestation - selection_proof: BLSSignature - - -class SignedAggregateAndProof(Container): - message: AggregateAndProof - signature: BLSSignature - - -class SyncAggregate(Container): - sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] - sync_committee_signature: BLSSignature - - -class SyncCommittee(Container): - pubkeys: Vector[BLSPubkey, SYNC_COMMITTEE_SIZE] - aggregate_pubkey: BLSPubkey - - -class SyncCommitteeMessage(Container): - # Slot to which this contribution pertains - slot: Slot - # Block root for this signature - beacon_block_root: Root - # Index of the validator that produced this signature - validator_index: ValidatorIndex - # Signature by the validator over the block root of `slot` - signature: BLSSignature - - -class SyncCommitteeContribution(Container): - # Slot to which this contribution pertains - slot: Slot - # Block root for this contribution - beacon_block_root: Root - # The subcommittee this contribution pertains to out of the broader sync committee - subcommittee_index: uint64 - # A bit is set if a signature from the validator at the corresponding - # index in the subcommittee is present in the aggregate `signature`. - aggregation_bits: Bitvector[SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT] - # Signature by the validator(s) over the block root of `slot` - signature: BLSSignature - - -class ContributionAndProof(Container): - aggregator_index: ValidatorIndex - contribution: SyncCommitteeContribution - selection_proof: BLSSignature - - -class SignedContributionAndProof(Container): - message: ContributionAndProof - signature: BLSSignature - - -class SyncAggregatorSelectionData(Container): - slot: Slot - subcommittee_index: uint64 - - -class LightClientSnapshot(Container): - # Beacon block header - header: BeaconBlockHeader - # Sync committees corresponding to the header - current_sync_committee: SyncCommittee - next_sync_committee: SyncCommittee - - -class LightClientUpdate(Container): - # Update beacon block header - header: BeaconBlockHeader - # Next sync committee corresponding to the header - next_sync_committee: SyncCommittee - next_sync_committee_branch: Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_INDEX)] - # Finality proof for the update header - finality_header: BeaconBlockHeader - finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX)] - # Sync committee aggregate signature - sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] - sync_committee_signature: BLSSignature - # Fork version for the aggregate signature - fork_version: Version - - -class ExecutionPayload(Container): - # Execution block header fields - parent_hash: Hash32 - fee_recipient: ExecutionAddress # 'beneficiary' in the yellow paper - state_root: Bytes32 - receipt_root: Bytes32 # 'receipts root' in the yellow paper - logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - random: Bytes32 # 'difficulty' in the yellow paper - block_number: uint64 # 'number' in the yellow paper - gas_limit: uint64 - gas_used: uint64 - timestamp: uint64 - extra_data: ByteList[MAX_EXTRA_DATA_BYTES] - base_fee_per_gas: uint256 - # Extra payload fields - block_hash: Hash32 # Hash of execution block - transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] - - -class BeaconBlockBody(Container): - randao_reveal: BLSSignature - eth1_data: Eth1Data # Eth1 data vote - graffiti: Bytes32 # Arbitrary data - # Operations - proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] - attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] - attestations: List[Attestation, MAX_ATTESTATIONS] - deposits: List[Deposit, MAX_DEPOSITS] - voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] - sync_aggregate: SyncAggregate - # Execution - execution_payload: ExecutionPayload # [New in Merge] - - -class BeaconBlock(Container): - slot: Slot - proposer_index: ValidatorIndex - parent_root: Root - state_root: Root - body: BeaconBlockBody - - -class SignedBeaconBlock(Container): - message: BeaconBlock - signature: BLSSignature - - -class ExecutionPayloadHeader(Container): - # Execution block header fields - parent_hash: Hash32 - fee_recipient: ExecutionAddress - state_root: Bytes32 - receipt_root: Bytes32 - logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - random: Bytes32 - block_number: uint64 - gas_limit: uint64 - gas_used: uint64 - timestamp: uint64 - extra_data: ByteList[MAX_EXTRA_DATA_BYTES] - base_fee_per_gas: uint256 - # Extra payload fields - block_hash: Hash32 # Hash of execution block - transactions_root: Root - - -class PowBlock(Container): - block_hash: Hash32 - parent_hash: Hash32 - total_difficulty: uint256 - - -class WithdrawalReceipt(Container): - index: WithdrawalReceiptIndex - address: ExecutionAddress - amount: Gwei - - -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[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] - current_epoch_participation: List[ParticipationFlags, 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 - # Inactivity - inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] - # Sync - current_sync_committee: SyncCommittee - next_sync_committee: SyncCommittee - # Execution - latest_execution_payload_header: ExecutionPayloadHeader - # Withdrawals - withdrawal_receipts: List[WithdrawalReceipt, WITHDRAWAL_RECEIPT_LIMIT] # [New in Cappela] - - -@dataclass(eq=True, frozen=True) -class LatestMessage(object): - epoch: Epoch - root: Root - - -@dataclass -class Store(object): - time: uint64 - genesis_time: uint64 - justified_checkpoint: Checkpoint - finalized_checkpoint: Checkpoint - best_justified_checkpoint: Checkpoint - proposer_boost_root: Root - blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) - block_states: Dict[Root, BeaconState] = field(default_factory=dict) - checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict) - latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict) - - -@dataclass -class LightClientStore(object): - snapshot: LightClientSnapshot - valid_updates: Set[LightClientUpdate] - - -@dataclass -class PayloadAttributes(object): - timestamp: uint64 - random: Bytes32 - suggested_fee_recipient: ExecutionAddress - - -class ExecutionEngine(Protocol): - - def execute_payload(self, execution_payload: ExecutionPayload) -> bool: - """ - Return ``True`` if and only if ``execution_payload`` is valid with respect to ``self.execution_state``. - """ - ... - - def notify_forkchoice_updated(self, - head_block_hash: Hash32, - finalized_block_hash: Hash32, - payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: - ... - - def get_payload(self, payload_id: PayloadId) -> ExecutionPayload: - """ - Return ``execution_payload`` object. - """ - ... - - -def integer_squareroot(n: uint64) -> uint64: - """ - Return the largest integer ``x`` such that ``x**2 <= n``. - """ - x = n - y = (x + 1) // 2 - while y < x: - x = y - y = (x + n // x) // 2 - return x - - -def xor(bytes_1: Bytes32, bytes_2: Bytes32) -> Bytes32: - """ - Return the exclusive-or of two 32-byte strings. - """ - return Bytes32(a ^ b for a, b in zip(bytes_1, bytes_2)) - - -def bytes_to_uint64(data: bytes) -> uint64: - """ - Return the integer deserialization of ``data`` interpreted as ``ENDIANNESS``-endian. - """ - return uint64(int.from_bytes(data, ENDIANNESS)) - - -def is_active_validator(validator: Validator, epoch: Epoch) -> bool: - """ - Check if ``validator`` is active. - """ - return validator.activation_epoch <= epoch < validator.exit_epoch - - -def is_eligible_for_activation_queue(validator: Validator) -> bool: - """ - Check if ``validator`` is eligible to be placed into the activation queue. - """ - return ( - validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH - and validator.effective_balance == MAX_EFFECTIVE_BALANCE - ) - - -def is_eligible_for_activation(state: BeaconState, validator: Validator) -> bool: - """ - Check if ``validator`` is eligible for activation. - """ - return ( - # Placement in queue is finalized - validator.activation_eligibility_epoch <= state.finalized_checkpoint.epoch - # Has not yet been activated - and validator.activation_epoch == FAR_FUTURE_EPOCH - ) - - -def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool: - """ - Check if ``validator`` is slashable. - """ - return (not validator.slashed) and (validator.activation_epoch <= epoch < validator.withdrawable_epoch) - - -def is_slashable_attestation_data(data_1: AttestationData, data_2: AttestationData) -> bool: - """ - Check if ``data_1`` and ``data_2`` are slashable according to Casper FFG rules. - """ - return ( - # Double vote - (data_1 != data_2 and data_1.target.epoch == data_2.target.epoch) or - # Surround vote - (data_1.source.epoch < data_2.source.epoch and data_2.target.epoch < data_1.target.epoch) - ) - - -def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool: - """ - Check if ``indexed_attestation`` is not empty, has sorted and unique indices and has a valid aggregate signature. - """ - # Verify indices are sorted and unique - indices = indexed_attestation.attesting_indices - if len(indices) == 0 or not indices == sorted(set(indices)): - return False - # Verify aggregate signature - pubkeys = [state.validators[i].pubkey for i in indices] - domain = get_domain(state, DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch) - signing_root = compute_signing_root(indexed_attestation.data, domain) - return bls.FastAggregateVerify(pubkeys, signing_root, indexed_attestation.signature) - - -def is_valid_merkle_branch(leaf: Bytes32, branch: Sequence[Bytes32], depth: uint64, index: uint64, root: Root) -> bool: - """ - Check if ``leaf`` at ``index`` verifies against the Merkle ``root`` and ``branch``. - """ - value = leaf - for i in range(depth): - if index // (2**i) % 2: - value = hash(branch[i] + value) - else: - value = hash(value + branch[i]) - return value == root - - -def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) -> uint64: - """ - Return the shuffled index corresponding to ``seed`` (and ``index_count``). - """ - assert index < index_count - - # Swap or not (https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf) - # See the 'generalized domain' algorithm on page 3 - for current_round in range(SHUFFLE_ROUND_COUNT): - pivot = bytes_to_uint64(hash(seed + uint_to_bytes(uint8(current_round)))[0:8]) % index_count - flip = (pivot + index_count - index) % index_count - position = max(index, flip) - source = hash( - seed - + uint_to_bytes(uint8(current_round)) - + uint_to_bytes(uint32(position // 256)) - ) - byte = uint8(source[(position % 256) // 8]) - bit = (byte >> (position % 8)) % 2 - index = flip if bit else index - - return index - - -def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex], seed: Bytes32) -> ValidatorIndex: - """ - Return from ``indices`` a random index sampled by effective balance. - """ - assert len(indices) > 0 - MAX_RANDOM_BYTE = 2**8 - 1 - i = uint64(0) - total = uint64(len(indices)) - while True: - candidate_index = indices[compute_shuffled_index(i % total, total, seed)] - random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] - effective_balance = state.validators[candidate_index].effective_balance - if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: - return candidate_index - i += 1 - - -def compute_committee(indices: Sequence[ValidatorIndex], - seed: Bytes32, - index: uint64, - count: uint64) -> Sequence[ValidatorIndex]: - """ - Return the committee corresponding to ``indices``, ``seed``, ``index``, and committee ``count``. - """ - start = (len(indices) * index) // count - end = (len(indices) * uint64(index + 1)) // count - return [indices[compute_shuffled_index(uint64(i), uint64(len(indices)), seed)] for i in range(start, end)] - - -def compute_epoch_at_slot(slot: Slot) -> Epoch: - """ - Return the epoch number at ``slot``. - """ - return Epoch(slot // SLOTS_PER_EPOCH) - - -def compute_start_slot_at_epoch(epoch: Epoch) -> Slot: - """ - Return the start slot of ``epoch``. - """ - return Slot(epoch * SLOTS_PER_EPOCH) - - -def compute_activation_exit_epoch(epoch: Epoch) -> Epoch: - """ - Return the epoch during which validator activations and exits initiated in ``epoch`` take effect. - """ - return Epoch(epoch + 1 + MAX_SEED_LOOKAHEAD) - - -def compute_fork_data_root(current_version: Version, genesis_validators_root: Root) -> Root: - """ - Return the 32-byte fork data root for the ``current_version`` and ``genesis_validators_root``. - This is used primarily in signature domains to avoid collisions across forks/chains. - """ - return hash_tree_root(ForkData( - current_version=current_version, - genesis_validators_root=genesis_validators_root, - )) - - -def compute_fork_digest(current_version: Version, genesis_validators_root: Root) -> ForkDigest: - """ - Return the 4-byte fork digest for the ``current_version`` and ``genesis_validators_root``. - This is a digest primarily used for domain separation on the p2p layer. - 4-bytes suffices for practical separation of forks/chains. - """ - return ForkDigest(compute_fork_data_root(current_version, genesis_validators_root)[:4]) - - -def compute_domain(domain_type: DomainType, fork_version: Version=None, genesis_validators_root: Root=None) -> Domain: - """ - Return the domain for the ``domain_type`` and ``fork_version``. - """ - if fork_version is None: - fork_version = config.GENESIS_FORK_VERSION - if genesis_validators_root is None: - genesis_validators_root = Root() # all bytes zero by default - fork_data_root = compute_fork_data_root(fork_version, genesis_validators_root) - return Domain(domain_type + fork_data_root[:28]) - - -def compute_signing_root(ssz_object: SSZObject, domain: Domain) -> Root: - """ - Return the signing root for the corresponding signing data. - """ - return hash_tree_root(SigningData( - object_root=hash_tree_root(ssz_object), - domain=domain, - )) - - -def get_current_epoch(state: BeaconState) -> Epoch: - """ - Return the current epoch. - """ - return compute_epoch_at_slot(state.slot) - - -def get_previous_epoch(state: BeaconState) -> Epoch: - """` - Return the previous epoch (unless the current epoch is ``GENESIS_EPOCH``). - """ - current_epoch = get_current_epoch(state) - return GENESIS_EPOCH if current_epoch == GENESIS_EPOCH else Epoch(current_epoch - 1) - - -def get_block_root(state: BeaconState, epoch: Epoch) -> Root: - """ - Return the block root at the start of a recent ``epoch``. - """ - return get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch)) - - -def get_block_root_at_slot(state: BeaconState, slot: Slot) -> Root: - """ - Return the block root at a recent ``slot``. - """ - assert slot < state.slot <= slot + SLOTS_PER_HISTORICAL_ROOT - return state.block_roots[slot % SLOTS_PER_HISTORICAL_ROOT] - - -def get_randao_mix(state: BeaconState, epoch: Epoch) -> Bytes32: - """ - Return the randao mix at a recent ``epoch``. - """ - return state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR] - - -def get_active_validator_indices(state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: - """ - Return the sequence of active validator indices at ``epoch``. - """ - return [ValidatorIndex(i) for i, v in enumerate(state.validators) if is_active_validator(v, epoch)] - - -def get_validator_churn_limit(state: BeaconState) -> uint64: - """ - Return the validator churn limit for the current epoch. - """ - active_validator_indices = get_active_validator_indices(state, get_current_epoch(state)) - return max(config.MIN_PER_EPOCH_CHURN_LIMIT, uint64(len(active_validator_indices)) // config.CHURN_LIMIT_QUOTIENT) - - -def get_seed(state: BeaconState, epoch: Epoch, domain_type: DomainType) -> Bytes32: - """ - Return the seed at ``epoch``. - """ - mix = get_randao_mix(state, Epoch(epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD - 1)) # Avoid underflow - return hash(domain_type + uint_to_bytes(epoch) + mix) - - -def get_committee_count_per_slot(state: BeaconState, epoch: Epoch) -> uint64: - """ - Return the number of committees in each slot for the given ``epoch``. - """ - return max(uint64(1), min( - MAX_COMMITTEES_PER_SLOT, - uint64(len(get_active_validator_indices(state, epoch))) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE, - )) - - -def get_beacon_committee(state: BeaconState, slot: Slot, index: CommitteeIndex) -> Sequence[ValidatorIndex]: - """ - Return the beacon committee at ``slot`` for ``index``. - """ - epoch = compute_epoch_at_slot(slot) - committees_per_slot = get_committee_count_per_slot(state, epoch) - return compute_committee( - indices=get_active_validator_indices(state, epoch), - seed=get_seed(state, epoch, DOMAIN_BEACON_ATTESTER), - index=(slot % SLOTS_PER_EPOCH) * committees_per_slot + index, - count=committees_per_slot * SLOTS_PER_EPOCH, - ) - - -def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: - """ - Return the beacon proposer index at the current slot. - """ - epoch = get_current_epoch(state) - seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + uint_to_bytes(state.slot)) - indices = get_active_validator_indices(state, epoch) - return compute_proposer_index(state, indices, seed) - - -def get_total_balance(state: BeaconState, indices: Set[ValidatorIndex]) -> Gwei: - """ - Return the combined effective balance of the ``indices``. - ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. - Math safe up to ~10B ETH, afterwhich this overflows uint64. - """ - return Gwei(max(EFFECTIVE_BALANCE_INCREMENT, sum([state.validators[index].effective_balance for index in indices]))) - - -def get_total_active_balance(state: BeaconState) -> Gwei: - """ - Return the combined effective balance of the active validators. - Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. - """ - return get_total_balance(state, set(get_active_validator_indices(state, get_current_epoch(state)))) - - -def get_domain(state: BeaconState, domain_type: DomainType, epoch: Epoch=None) -> Domain: - """ - Return the signature domain (fork version concatenated with domain type) of a message. - """ - epoch = get_current_epoch(state) if epoch is None else epoch - fork_version = state.fork.previous_version if epoch < state.fork.epoch else state.fork.current_version - return compute_domain(domain_type, fork_version, state.genesis_validators_root) - - -def get_indexed_attestation(state: BeaconState, attestation: Attestation) -> IndexedAttestation: - """ - Return the indexed attestation corresponding to ``attestation``. - """ - attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) - - return IndexedAttestation( - attesting_indices=sorted(attesting_indices), - data=attestation.data, - signature=attestation.signature, - ) - - -def get_attesting_indices(state: BeaconState, - data: AttestationData, - bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Set[ValidatorIndex]: - """ - Return the set of attesting indices corresponding to ``data`` and ``bits``. - """ - committee = get_beacon_committee(state, data.slot, data.index) - return set(index for i, index in enumerate(committee) if bits[i]) - - -def increase_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: - """ - Increase the validator balance at index ``index`` by ``delta``. - """ - state.balances[index] += delta - - -def decrease_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: - """ - Decrease the validator balance at index ``index`` by ``delta``, with underflow protection. - """ - state.balances[index] = 0 if delta > state.balances[index] else state.balances[index] - delta - - -def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: - """ - Initiate the exit of the validator with index ``index``. - """ - # Return if validator already initiated exit - validator = state.validators[index] - if validator.exit_epoch != FAR_FUTURE_EPOCH: - return - - # Compute exit queue epoch - exit_epochs = [v.exit_epoch for v in state.validators if v.exit_epoch != FAR_FUTURE_EPOCH] - exit_queue_epoch = max(exit_epochs + [compute_activation_exit_epoch(get_current_epoch(state))]) - exit_queue_churn = len([v for v in state.validators if v.exit_epoch == exit_queue_epoch]) - if exit_queue_churn >= get_validator_churn_limit(state): - exit_queue_epoch += Epoch(1) - - # Set validator exit epoch and withdrawable epoch - validator.exit_epoch = exit_queue_epoch - validator.withdrawable_epoch = Epoch(validator.exit_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY) - - -def slash_validator(state: BeaconState, - slashed_index: ValidatorIndex, - whistleblower_index: ValidatorIndex=None) -> None: - """ - Slash the validator with index ``slashed_index``. - """ - epoch = get_current_epoch(state) - initiate_validator_exit(state, slashed_index) - validator = state.validators[slashed_index] - validator.slashed = True - validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR)) - state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance - slashing_penalty = validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_MERGE # [Modified in Merge] - decrease_balance(state, slashed_index, slashing_penalty) - - # Apply proposer and whistleblower rewards - proposer_index = get_beacon_proposer_index(state) - if whistleblower_index is None: - whistleblower_index = proposer_index - whistleblower_reward = Gwei(validator.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT) - proposer_reward = Gwei(whistleblower_reward * PROPOSER_WEIGHT // WEIGHT_DENOMINATOR) - increase_balance(state, proposer_index, proposer_reward) - increase_balance(state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward)) - - -def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, - eth1_timestamp: uint64, - deposits: Sequence[Deposit], - execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader() - ) -> BeaconState: - fork = Fork( - previous_version=config.MERGE_FORK_VERSION, # [Modified in Merge] for testing only - current_version=config.MERGE_FORK_VERSION, # [Modified in Merge] - epoch=GENESIS_EPOCH, - ) - state = BeaconState( - genesis_time=eth1_timestamp + config.GENESIS_DELAY, - fork=fork, - eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))), - latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), - randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy - ) - - # Process deposits - leaves = list(map(lambda deposit: deposit.data, deposits)) - for index, deposit in enumerate(deposits): - deposit_data_list = List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[:index + 1]) - state.eth1_data.deposit_root = hash_tree_root(deposit_data_list) - process_deposit(state, deposit) - - # Process activations - for index, validator in enumerate(state.validators): - balance = state.balances[index] - validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) - if validator.effective_balance == MAX_EFFECTIVE_BALANCE: - validator.activation_eligibility_epoch = GENESIS_EPOCH - validator.activation_epoch = GENESIS_EPOCH - - # Set genesis validators root for domain separation and chain versioning - state.genesis_validators_root = hash_tree_root(state.validators) - - # Fill in sync committees - # Note: A duplicate committee is assigned for the current and next committee at genesis - state.current_sync_committee = get_next_sync_committee(state) - state.next_sync_committee = get_next_sync_committee(state) - - # [New in Merge] Initialize the execution payload header - # If empty, will initialize a chain that has not yet gone through the Merge transition - state.latest_execution_payload_header = execution_payload_header - - return state - - -def is_valid_genesis_state(state: BeaconState) -> bool: - if state.genesis_time < config.MIN_GENESIS_TIME: - return False - if len(get_active_validator_indices(state, GENESIS_EPOCH)) < config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: - return False - return True - - -def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool=True) -> None: - block = signed_block.message - # Process slots (including those with no blocks) since block - process_slots(state, block.slot) - # Verify signature - if validate_result: - assert verify_block_signature(state, signed_block) - # Process block - process_block(state, block) - # Verify state root - if validate_result: - assert block.state_root == hash_tree_root(state) - - -def verify_block_signature(state: BeaconState, signed_block: SignedBeaconBlock) -> bool: - proposer = state.validators[signed_block.message.proposer_index] - signing_root = compute_signing_root(signed_block.message, get_domain(state, DOMAIN_BEACON_PROPOSER)) - return bls.Verify(proposer.pubkey, signing_root, signed_block.signature) - - -def process_slots(state: BeaconState, slot: Slot) -> None: - assert state.slot < slot - while state.slot < slot: - process_slot(state) - # Process epoch on the start slot of the next epoch - if (state.slot + 1) % SLOTS_PER_EPOCH == 0: - process_epoch(state) - state.slot = Slot(state.slot + 1) - - -def process_slot(state: BeaconState) -> None: - # Cache state root - previous_state_root = hash_tree_root(state) - state.state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_state_root - # Cache latest block header state root - if state.latest_block_header.state_root == Bytes32(): - state.latest_block_header.state_root = previous_state_root - # Cache block root - previous_block_root = hash_tree_root(state.latest_block_header) - state.block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_block_root - - -def process_epoch(state: BeaconState) -> None: - process_justification_and_finalization(state) - process_inactivity_updates(state) - process_rewards_and_penalties(state) - process_registry_updates(state) - process_slashings(state) - process_eth1_data_reset(state) - process_effective_balance_updates(state) - process_slashings_reset(state) - process_randao_mixes_reset(state) - process_historical_roots_update(state) - process_participation_flag_updates(state) - process_sync_committee_updates(state) - process_withdrawals(state) # [New in Cappela] - - -def get_matching_source_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: - assert epoch in (get_previous_epoch(state), get_current_epoch(state)) - return state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations - - -def get_matching_target_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: - return [ - a for a in get_matching_source_attestations(state, epoch) - if a.data.target.root == get_block_root(state, epoch) - ] - - -def get_matching_head_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: - return [ - a for a in get_matching_target_attestations(state, epoch) - if a.data.beacon_block_root == get_block_root_at_slot(state, a.data.slot) - ] - - -def get_unslashed_attesting_indices(state: BeaconState, - attestations: Sequence[PendingAttestation]) -> Set[ValidatorIndex]: - output = set() # type: Set[ValidatorIndex] - for a in attestations: - output = output.union(get_attesting_indices(state, a.data, a.aggregation_bits)) - return set(filter(lambda index: not state.validators[index].slashed, output)) - - -def get_attesting_balance(state: BeaconState, attestations: Sequence[PendingAttestation]) -> Gwei: - """ - Return the combined effective balance of the set of unslashed validators participating in ``attestations``. - Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. - """ - return get_total_balance(state, get_unslashed_attesting_indices(state, attestations)) - - -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_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)) - current_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_current_epoch(state)) - total_active_balance = get_total_active_balance(state) - previous_target_balance = get_total_balance(state, previous_indices) - current_target_balance = get_total_balance(state, current_indices) - weigh_justification_and_finalization(state, total_active_balance, previous_target_balance, current_target_balance) - - -def weigh_justification_and_finalization(state: BeaconState, - total_active_balance: Gwei, - previous_epoch_target_balance: Gwei, - current_epoch_target_balance: Gwei) -> None: - 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 - - # 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 - if previous_epoch_target_balance * 3 >= total_active_balance * 2: - state.current_justified_checkpoint = Checkpoint(epoch=previous_epoch, - root=get_block_root(state, previous_epoch)) - state.justification_bits[1] = 0b1 - if current_epoch_target_balance * 3 >= total_active_balance * 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 - - -def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: - """ - Return the base reward for the validator defined by ``index`` with respect to the current ``state``. - """ - increments = state.validators[index].effective_balance // EFFECTIVE_BALANCE_INCREMENT - return Gwei(increments * get_base_reward_per_increment(state)) - - -def get_proposer_reward(state: BeaconState, attesting_index: ValidatorIndex) -> Gwei: - return Gwei(get_base_reward(state, attesting_index) // PROPOSER_REWARD_QUOTIENT) - - -def get_finality_delay(state: BeaconState) -> uint64: - return get_previous_epoch(state) - state.finalized_checkpoint.epoch - - -def is_in_inactivity_leak(state: BeaconState) -> bool: - return get_finality_delay(state) > MIN_EPOCHS_TO_INACTIVITY_PENALTY - - -def get_eligible_validator_indices(state: BeaconState) -> Sequence[ValidatorIndex]: - previous_epoch = get_previous_epoch(state) - 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) - ] - - -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)] * 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) - 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 - if is_in_inactivity_leak(state): - # Since full base reward will be canceled out by inactivity penalty deltas, - # optimal participation receives full base reward compensation here. - rewards[index] += get_base_reward(state, index) - else: - 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 - - -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 get_attestation_component_deltas(state, matching_source_attestations) - - -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 get_attestation_component_deltas(state, matching_target_attestations) - - -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 get_attestation_component_deltas(state, matching_head_attestations) - - -def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: - """ - Return proposer and inclusion delay micro-rewards/penalties for each validator. - """ - rewards = [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 - if index in get_attesting_indices(state, a.data, a.aggregation_bits) - ], key=lambda a: a.inclusion_delay) - rewards[attestation.proposer_index] += get_proposer_reward(state, index) - max_attester_reward = Gwei(get_base_reward(state, index) - get_proposer_reward(state, index)) - 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 - - -def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: - """ - Return the inactivity penalty deltas by considering timely target participation flags and inactivity scores. - """ - rewards = [Gwei(0) for _ in range(len(state.validators))] - penalties = [Gwei(0) for _ in range(len(state.validators))] - previous_epoch = get_previous_epoch(state) - matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, previous_epoch) - for index in get_eligible_validator_indices(state): - if index not in matching_target_indices: - penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index] - # [Modified in Merge] - penalty_denominator = config.INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_MERGE - penalties[index] += Gwei(penalty_numerator // penalty_denominator) - return rewards, penalties - - -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) - 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 - - -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_index_deltas(state, flag_index) for flag_index in range(len(PARTICIPATION_FLAG_WEIGHTS))] - 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]) - - -def process_registry_updates(state: BeaconState) -> None: - # Process activation eligibility and ejections - for index, validator in enumerate(state.validators): - if is_eligible_for_activation_queue(validator): - validator.activation_eligibility_epoch = get_current_epoch(state) + 1 - - if ( - is_active_validator(validator, get_current_epoch(state)) - and validator.effective_balance <= config.EJECTION_BALANCE - ): - initiate_validator_exit(state, ValidatorIndex(index)) - - # Queue validators eligible for activation and not yet dequeued for activation - activation_queue = sorted([ - index for index, validator in enumerate(state.validators) - if is_eligible_for_activation(state, validator) - # Order by the sequence of activation_eligibility_epoch setting and then index - ], key=lambda index: (state.validators[index].activation_eligibility_epoch, index)) - # Dequeued validators for activation up to churn limit - for index in activation_queue[:get_validator_churn_limit(state)]: - validator = state.validators[index] - validator.activation_epoch = compute_activation_exit_epoch(get_current_epoch(state)) - - -def process_slashings(state: BeaconState) -> None: - epoch = get_current_epoch(state) - total_balance = get_total_active_balance(state) - adjusted_total_slashing_balance = min( - sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_MERGE, # [Modified in Merge] - total_balance - ) - for index, validator in enumerate(state.validators): - if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch: - increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow - penalty_numerator = validator.effective_balance // increment * adjusted_total_slashing_balance - penalty = penalty_numerator // total_balance * increment - decrease_balance(state, ValidatorIndex(index), penalty) - - -def process_eth1_data_reset(state: BeaconState) -> None: - next_epoch = Epoch(get_current_epoch(state) + 1) - # Reset eth1 data votes - if next_epoch % EPOCHS_PER_ETH1_VOTING_PERIOD == 0: - state.eth1_data_votes = [] - - -def process_effective_balance_updates(state: BeaconState) -> None: - # Update effective balances with hysteresis - for index, validator in enumerate(state.validators): - balance = state.balances[index] - HYSTERESIS_INCREMENT = uint64(EFFECTIVE_BALANCE_INCREMENT // HYSTERESIS_QUOTIENT) - DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER - UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER - if ( - balance + DOWNWARD_THRESHOLD < validator.effective_balance - or validator.effective_balance + UPWARD_THRESHOLD < balance - ): - validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) - - -def process_slashings_reset(state: BeaconState) -> None: - next_epoch = Epoch(get_current_epoch(state) + 1) - # Reset slashings - state.slashings[next_epoch % EPOCHS_PER_SLASHINGS_VECTOR] = Gwei(0) - - -def process_randao_mixes_reset(state: BeaconState) -> None: - current_epoch = get_current_epoch(state) - next_epoch = Epoch(current_epoch + 1) - # Set randao mix - state.randao_mixes[next_epoch % EPOCHS_PER_HISTORICAL_VECTOR] = get_randao_mix(state, current_epoch) - - -def process_historical_roots_update(state: BeaconState) -> None: - # Set historical root accumulator - next_epoch = Epoch(get_current_epoch(state) + 1) - if next_epoch % (SLOTS_PER_HISTORICAL_ROOT // SLOTS_PER_EPOCH) == 0: - historical_batch = HistoricalBatch(block_roots=state.block_roots, state_roots=state.state_roots) - state.historical_roots.append(hash_tree_root(historical_batch)) - - -def process_participation_record_updates(state: BeaconState) -> None: - # Rotate current/previous epoch attestations - state.previous_epoch_attestations = state.current_epoch_attestations - state.current_epoch_attestations = [] - - -def process_block(state: BeaconState, block: BeaconBlock) -> None: - process_block_header(state, block) - if is_execution_enabled(state, block.body): - process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [New in Merge] - process_randao(state, block.body) - process_eth1_data(state, block.body) - process_operations(state, block.body) - process_sync_aggregate(state, block.body.sync_aggregate) - - -def process_block_header(state: BeaconState, block: BeaconBlock) -> None: - # Verify that the slots match - assert block.slot == state.slot - # Verify that the block is newer than latest block header - assert block.slot > state.latest_block_header.slot - # Verify that proposer index is the correct index - assert block.proposer_index == get_beacon_proposer_index(state) - # Verify that the parent matches - assert block.parent_root == hash_tree_root(state.latest_block_header) - # Cache current block as the new latest block - state.latest_block_header = BeaconBlockHeader( - slot=block.slot, - proposer_index=block.proposer_index, - parent_root=block.parent_root, - state_root=Bytes32(), # Overwritten in the next process_slot call - body_root=hash_tree_root(block.body), - ) - - # Verify proposer is not slashed - proposer = state.validators[block.proposer_index] - assert not proposer.slashed - - -def process_randao(state: BeaconState, body: BeaconBlockBody) -> None: - epoch = get_current_epoch(state) - # Verify RANDAO reveal - proposer = state.validators[get_beacon_proposer_index(state)] - signing_root = compute_signing_root(epoch, get_domain(state, DOMAIN_RANDAO)) - assert bls.Verify(proposer.pubkey, signing_root, body.randao_reveal) - # Mix in RANDAO reveal - mix = xor(get_randao_mix(state, epoch), hash(body.randao_reveal)) - state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR] = mix - - -def process_eth1_data(state: BeaconState, body: BeaconBlockBody) -> None: - state.eth1_data_votes.append(body.eth1_data) - if state.eth1_data_votes.count(body.eth1_data) * 2 > EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH: - state.eth1_data = body.eth1_data - - -def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: - # Verify that outstanding deposits are processed up to the maximum number of deposits - assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index) - - def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: - for operation in operations: - fn(state, operation) - - for_ops(body.proposer_slashings, process_proposer_slashing) - for_ops(body.attester_slashings, process_attester_slashing) - for_ops(body.attestations, process_attestation) - for_ops(body.deposits, process_deposit) - for_ops(body.voluntary_exits, process_voluntary_exit) - - -def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None: - header_1 = proposer_slashing.signed_header_1.message - header_2 = proposer_slashing.signed_header_2.message - - # Verify header slots match - assert header_1.slot == header_2.slot - # Verify header proposer indices match - assert header_1.proposer_index == header_2.proposer_index - # Verify the headers are different - assert header_1 != header_2 - # Verify the proposer is slashable - proposer = state.validators[header_1.proposer_index] - assert is_slashable_validator(proposer, get_current_epoch(state)) - # Verify signatures - for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2): - domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(signed_header.message.slot)) - signing_root = compute_signing_root(signed_header.message, domain) - assert bls.Verify(proposer.pubkey, signing_root, signed_header.signature) - - slash_validator(state, header_1.proposer_index) - - -def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSlashing) -> None: - attestation_1 = attester_slashing.attestation_1 - attestation_2 = attester_slashing.attestation_2 - assert is_slashable_attestation_data(attestation_1.data, attestation_2.data) - assert is_valid_indexed_attestation(state, attestation_1) - assert is_valid_indexed_attestation(state, attestation_2) - - slashed_any = False - indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices) - for index in sorted(indices): - if is_slashable_validator(state.validators[index], get_current_epoch(state)): - slash_validator(state, index) - slashed_any = True - assert slashed_any - - -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) - - # Participation flag indices - participation_flag_indices = get_attestation_participation_flag_indices(state, data, state.slot - data.slot) - - # Verify signature - assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) - - # Update epoch participation flags - if data.target.epoch == get_current_epoch(state): - epoch_participation = state.current_epoch_participation - else: - epoch_participation = state.previous_epoch_participation - - proposer_reward_numerator = 0 - for index in get_attesting_indices(state, data, attestation.aggregation_bits): - for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS): - if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index): - epoch_participation[index] = add_flag(epoch_participation[index], flag_index) - proposer_reward_numerator += get_base_reward(state, index) * weight - - # Reward proposer - proposer_reward_denominator = (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT - proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator) - increase_balance(state, get_beacon_proposer_index(state), proposer_reward) - - -def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: - amount = deposit.data.amount - effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) - - return Validator( - pubkey=deposit.data.pubkey, - withdrawal_credentials=deposit.data.withdrawal_credentials, - activation_eligibility_epoch=FAR_FUTURE_EPOCH, - activation_epoch=FAR_FUTURE_EPOCH, - exit_epoch=FAR_FUTURE_EPOCH, - withdrawable_epoch=FAR_FUTURE_EPOCH, - effective_balance=effective_balance, - ) - - -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 = [validator.pubkey for validator 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) - # Initialize validator if the deposit signature is valid - if bls.Verify(pubkey, signing_root, deposit.data.signature): - state.validators.append(get_validator_from_deposit(state, deposit)) - state.balances.append(amount) - state.previous_epoch_participation.append(ParticipationFlags(0b0000_0000)) - state.current_epoch_participation.append(ParticipationFlags(0b0000_0000)) - state.inactivity_scores.append(uint64(0)) - else: - # Increase balance by deposit amount - index = ValidatorIndex(validator_pubkeys.index(pubkey)) - increase_balance(state, index, amount) - - -def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVoluntaryExit) -> None: - voluntary_exit = signed_voluntary_exit.message - validator = state.validators[voluntary_exit.validator_index] - # Verify the validator is active - assert is_active_validator(validator, get_current_epoch(state)) - # Verify exit has not been initiated - assert validator.exit_epoch == FAR_FUTURE_EPOCH - # Exits must specify an epoch when they become valid; they are not valid before then - assert get_current_epoch(state) >= voluntary_exit.epoch - # Verify the validator has been active long enough - assert get_current_epoch(state) >= validator.activation_epoch + config.SHARD_COMMITTEE_PERIOD - # Verify signature - domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch) - signing_root = compute_signing_root(voluntary_exit, domain) - assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature) - # Initiate exit - initiate_validator_exit(state, voluntary_exit.validator_index) - - -def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store: - assert anchor_block.state_root == hash_tree_root(anchor_state) - anchor_root = hash_tree_root(anchor_block) - anchor_epoch = get_current_epoch(anchor_state) - justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) - finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) - proposer_boost_root = Root() - return Store( - time=uint64(anchor_state.genesis_time + config.SECONDS_PER_SLOT * anchor_state.slot), - genesis_time=anchor_state.genesis_time, - justified_checkpoint=justified_checkpoint, - finalized_checkpoint=finalized_checkpoint, - best_justified_checkpoint=justified_checkpoint, - proposer_boost_root=proposer_boost_root, - blocks={anchor_root: copy(anchor_block)}, - block_states={anchor_root: copy(anchor_state)}, - checkpoint_states={justified_checkpoint: copy(anchor_state)}, - ) - - -def get_slots_since_genesis(store: Store) -> int: - return (store.time - store.genesis_time) // config.SECONDS_PER_SLOT - - -def get_current_slot(store: Store) -> Slot: - return Slot(GENESIS_SLOT + get_slots_since_genesis(store)) - - -def compute_slots_since_epoch_start(slot: Slot) -> int: - return slot - compute_start_slot_at_epoch(compute_epoch_at_slot(slot)) - - -def get_ancestor(store: Store, root: Root, slot: Slot) -> Root: - block = store.blocks[root] - if block.slot > slot: - return get_ancestor(store, block.parent_root, slot) - elif block.slot == slot: - return root - else: - # root is older than queried slot, thus a skip slot. Return most recent root prior to slot - return root - - -def get_latest_attesting_balance(store: Store, root: Root) -> Gwei: - state = store.checkpoint_states[store.justified_checkpoint] - active_indices = get_active_validator_indices(state, get_current_epoch(state)) - attestation_score = Gwei(sum( - state.validators[i].effective_balance for i in active_indices - if (i in store.latest_messages - and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root) - )) - proposer_score = Gwei(0) - if store.proposer_boost_root != Root(): - block = store.blocks[root] - if get_ancestor(store, root, block.slot) == store.proposer_boost_root: - num_validators = len(get_active_validator_indices(state, get_current_epoch(state))) - avg_balance = get_total_active_balance(state) // num_validators - committee_size = num_validators // SLOTS_PER_EPOCH - committee_weight = committee_size * avg_balance - proposer_score = (committee_weight * config.PROPOSER_SCORE_BOOST) // 100 - return attestation_score + proposer_score - - -def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconBlock]) -> bool: - block = store.blocks[block_root] - children = [ - root for root in store.blocks.keys() - if store.blocks[root].parent_root == block_root - ] - - # If any children branches contain expected finalized/justified checkpoints, - # add to filtered block-tree and signal viability to parent. - if any(children): - filter_block_tree_result = [filter_block_tree(store, child, blocks) for child in children] - if any(filter_block_tree_result): - blocks[block_root] = block - return True - return False - - # If leaf block, check finalized/justified checkpoints as matching latest. - head_state = store.block_states[block_root] - - correct_justified = ( - store.justified_checkpoint.epoch == GENESIS_EPOCH - or head_state.current_justified_checkpoint == store.justified_checkpoint - ) - correct_finalized = ( - store.finalized_checkpoint.epoch == GENESIS_EPOCH - or head_state.finalized_checkpoint == store.finalized_checkpoint - ) - # If expected finalized/justified, add to viable block-tree and signal viability to parent. - if correct_justified and correct_finalized: - blocks[block_root] = block - return True - - # Otherwise, branch not viable - return False - - -def get_filtered_block_tree(store: Store) -> Dict[Root, BeaconBlock]: - """ - Retrieve a filtered block tree from ``store``, only returning branches - whose leaf state's justified/finalized info agrees with that in ``store``. - """ - base = store.justified_checkpoint.root - blocks: Dict[Root, BeaconBlock] = {} - filter_block_tree(store, base, blocks) - return blocks - - -def get_head(store: Store) -> Root: - # Get filtered block tree that only includes viable branches - blocks = get_filtered_block_tree(store) - # Execute the LMD-GHOST fork choice - head = store.justified_checkpoint.root - while True: - children = [ - root for root in blocks.keys() - if blocks[root].parent_root == head - ] - if len(children) == 0: - return head - # Sort by latest attesting balance with ties broken lexicographically - head = max(children, key=lambda root: (get_latest_attesting_balance(store, root), root)) - - -def should_update_justified_checkpoint(store: Store, new_justified_checkpoint: Checkpoint) -> bool: - """ - To address the bouncing attack, only update conflicting justified - checkpoints in the fork choice if in the early slots of the epoch. - Otherwise, delay incorporation of new justified checkpoint until next epoch boundary. - - See https://ethresear.ch/t/prevention-of-bouncing-attack-on-ffg/6114 for more detailed analysis and discussion. - """ - if compute_slots_since_epoch_start(get_current_slot(store)) < SAFE_SLOTS_TO_UPDATE_JUSTIFIED: - return True - - justified_slot = compute_start_slot_at_epoch(store.justified_checkpoint.epoch) - if not get_ancestor(store, new_justified_checkpoint.root, justified_slot) == store.justified_checkpoint.root: - return False - - return True - - -def validate_target_epoch_against_current_time(store: Store, attestation: Attestation) -> None: - target = attestation.data.target - - # Attestations must be from the current or previous epoch - 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] - - -def validate_on_attestation(store: Store, attestation: Attestation, is_from_block: bool) -> None: - target = attestation.data.target - - # If the given attestation is not from a beacon block message, we have to check the target epoch scope. - if not is_from_block: - validate_target_epoch_against_current_time(store, attestation) - - # Check that the epoch number and slot number are matching - assert target.epoch == compute_epoch_at_slot(attestation.data.slot) - - # 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 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 - # Attestations must not be for blocks in the future. If not, the attestation should not be considered - assert store.blocks[attestation.data.beacon_block_root].slot <= attestation.data.slot - - # LMD vote must be consistent with FFG vote target - target_slot = compute_start_slot_at_epoch(target.epoch) - assert target.root == get_ancestor(store, attestation.data.beacon_block_root, target_slot) - - # Attestations can only affect the fork choice of subsequent slots. - # Delay consideration in the fork choice until their slot is in the past. - assert get_current_slot(store) >= attestation.data.slot + 1 - - -def store_target_checkpoint_state(store: Store, target: Checkpoint) -> None: - # Store target checkpoint state if not yet seen - if target not in store.checkpoint_states: - base_state = copy(store.block_states[target.root]) - if base_state.slot < compute_start_slot_at_epoch(target.epoch): - process_slots(base_state, compute_start_slot_at_epoch(target.epoch)) - store.checkpoint_states[target] = base_state - - -def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None: - target = attestation.data.target - beacon_block_root = attestation.data.beacon_block_root - for i in attesting_indices: - if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch: - store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root) - - -def on_tick(store: Store, time: uint64) -> None: - previous_slot = get_current_slot(store) - - # update store time - store.time = time - - current_slot = get_current_slot(store) - - # Reset store.proposer_boost_root if this is a new slot - if current_slot > previous_slot: - store.proposer_boost_root = Root() - - # Not a new epoch, return - if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0): - return - - # Update store.justified_checkpoint if a better checkpoint on the store.finalized_checkpoint chain - if store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch: - finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) - ancestor_at_finalized_slot = get_ancestor(store, store.best_justified_checkpoint.root, finalized_slot) - if ancestor_at_finalized_slot == store.finalized_checkpoint.root: - store.justified_checkpoint = store.best_justified_checkpoint - - -def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: - """ - Run ``on_block`` upon receiving a new block. - - A block that is asserted as invalid due to unavailable PoW block may be valid at a later time, - consider scheduling it for later processing in such case. - """ - block = signed_block.message - # Parent block must be known - assert block.parent_root in store.block_states - # Make a copy of the state to avoid mutability issues - pre_state = copy(store.block_states[block.parent_root]) - # Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past. - assert get_current_slot(store) >= block.slot - - # Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) - finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) - assert block.slot > finalized_slot - # Check block is a descendant of the finalized block at the checkpoint finalized slot - assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root - - # Check the block is valid and compute the post-state - state = pre_state.copy() - state_transition(state, signed_block, True) - - # [New in Merge] - if is_merge_transition_block(pre_state, block.body): - validate_merge_block(block) - - # Add new block to the store - store.blocks[hash_tree_root(block)] = block - # Add new state for this block to the store - store.block_states[hash_tree_root(block)] = state - - # Add proposer score boost if the block is timely - time_into_slot = (store.time - store.genesis_time) % config.SECONDS_PER_SLOT - is_before_attesting_interval = time_into_slot < config.SECONDS_PER_SLOT // INTERVALS_PER_SLOT - if get_current_slot(store) == block.slot and is_before_attesting_interval: - store.proposer_boost_root = hash_tree_root(block) - - # Update justified checkpoint - if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: - if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch: - store.best_justified_checkpoint = state.current_justified_checkpoint - if should_update_justified_checkpoint(store, state.current_justified_checkpoint): - store.justified_checkpoint = state.current_justified_checkpoint - - # Update finalized checkpoint - if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch: - store.finalized_checkpoint = state.finalized_checkpoint - store.justified_checkpoint = state.current_justified_checkpoint - - -def on_attestation(store: Store, attestation: Attestation, is_from_block: bool=False) -> None: - """ - Run ``on_attestation`` upon receiving a new ``attestation`` from either within a block or directly on the wire. - - An ``attestation`` that is asserted as invalid may be valid at a later time, - consider scheduling it for later processing in such case. - """ - validate_on_attestation(store, attestation, is_from_block) - - store_target_checkpoint_state(store, attestation.data.target) - - # Get state at the `target` to fully validate attestation - target_state = store.checkpoint_states[attestation.data.target] - indexed_attestation = get_indexed_attestation(target_state, attestation) - assert is_valid_indexed_attestation(target_state, indexed_attestation) - - # Update latest messages for attesting indices - update_latest_messages(store, indexed_attestation.attesting_indices, attestation) - - -def check_if_validator_active(state: BeaconState, validator_index: ValidatorIndex) -> bool: - validator = state.validators[validator_index] - return is_active_validator(validator, get_current_epoch(state)) - - -def get_committee_assignment(state: BeaconState, - epoch: Epoch, - validator_index: ValidatorIndex - ) -> Optional[Tuple[Sequence[ValidatorIndex], CommitteeIndex, Slot]]: - """ - Return the committee assignment in the ``epoch`` for ``validator_index``. - ``assignment`` returned is a tuple of the following form: - * ``assignment[0]`` is the list of validators in the committee - * ``assignment[1]`` is the index to which the committee is assigned - * ``assignment[2]`` is the slot at which the committee is assigned - Return None if no assignment. - """ - next_epoch = Epoch(get_current_epoch(state) + 1) - assert epoch <= next_epoch - - start_slot = compute_start_slot_at_epoch(epoch) - committee_count_per_slot = get_committee_count_per_slot(state, epoch) - for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH): - for index in range(committee_count_per_slot): - committee = get_beacon_committee(state, Slot(slot), CommitteeIndex(index)) - if validator_index in committee: - return committee, CommitteeIndex(index), Slot(slot) - return None - - -def is_proposer(state: BeaconState, validator_index: ValidatorIndex) -> bool: - return get_beacon_proposer_index(state) == validator_index - - -def get_epoch_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_RANDAO, compute_epoch_at_slot(block.slot)) - signing_root = compute_signing_root(compute_epoch_at_slot(block.slot), domain) - return bls.Sign(privkey, signing_root) - - -def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64: - return uint64(state.genesis_time + slot * config.SECONDS_PER_SLOT) - - -def voting_period_start_time(state: BeaconState) -> uint64: - eth1_voting_period_start_slot = Slot(state.slot - state.slot % (EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH)) - return compute_time_at_slot(state, eth1_voting_period_start_slot) - - -def is_candidate_block(block: Eth1Block, period_start: uint64) -> bool: - return ( - block.timestamp + config.SECONDS_PER_ETH1_BLOCK * config.ETH1_FOLLOW_DISTANCE <= period_start - and block.timestamp + config.SECONDS_PER_ETH1_BLOCK * config.ETH1_FOLLOW_DISTANCE * 2 >= period_start - ) - - -def get_eth1_vote(state: BeaconState, eth1_chain: Sequence[Eth1Block]) -> Eth1Data: - period_start = voting_period_start_time(state) - # `eth1_chain` abstractly represents all blocks in the eth1 chain sorted by ascending block height - votes_to_consider = [ - get_eth1_data(block) for block in eth1_chain - if ( - is_candidate_block(block, period_start) - # Ensure cannot move back to earlier deposit contract states - and get_eth1_data(block).deposit_count >= state.eth1_data.deposit_count - ) - ] - - # Valid votes already cast during this period - valid_votes = [vote for vote in state.eth1_data_votes if vote in votes_to_consider] - - # Default vote on latest eth1 block data in the period range unless eth1 chain is not live - # Non-substantive casting for linter - state_eth1_data: Eth1Data = state.eth1_data - default_vote = votes_to_consider[len(votes_to_consider) - 1] if any(votes_to_consider) else state_eth1_data - - return max( - valid_votes, - key=lambda v: (valid_votes.count(v), -valid_votes.index(v)), # Tiebreak by smallest distance - default=default_vote - ) - - -def compute_new_state_root(state: BeaconState, block: BeaconBlock) -> Root: - temp_state: BeaconState = state.copy() - signed_block = SignedBeaconBlock(message=block) - state_transition(temp_state, signed_block, validate_result=False) - return hash_tree_root(temp_state) - - -def get_block_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(block.slot)) - signing_root = compute_signing_root(block, domain) - return bls.Sign(privkey, signing_root) - - -def get_attestation_signature(state: BeaconState, attestation_data: AttestationData, privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch) - signing_root = compute_signing_root(attestation_data, domain) - return bls.Sign(privkey, signing_root) - - -def compute_subnet_for_attestation(committees_per_slot: uint64, slot: Slot, committee_index: CommitteeIndex) -> uint64: - """ - Compute the correct subnet for an attestation for Phase 0. - Note, this mimics expected future behavior where attestations will be mapped to their shard subnet. - """ - slots_since_epoch_start = uint64(slot % SLOTS_PER_EPOCH) - committees_since_epoch_start = committees_per_slot * slots_since_epoch_start - - return uint64((committees_since_epoch_start + committee_index) % ATTESTATION_SUBNET_COUNT) - - -def get_slot_signature(state: BeaconState, slot: Slot, privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_SELECTION_PROOF, compute_epoch_at_slot(slot)) - signing_root = compute_signing_root(slot, domain) - return bls.Sign(privkey, signing_root) - - -def is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex, slot_signature: BLSSignature) -> bool: - committee = get_beacon_committee(state, slot, index) - modulo = max(1, len(committee) // TARGET_AGGREGATORS_PER_COMMITTEE) - return bytes_to_uint64(hash(slot_signature)[0:8]) % modulo == 0 - - -def get_aggregate_signature(attestations: Sequence[Attestation]) -> BLSSignature: - signatures = [attestation.signature for attestation in attestations] - return bls.Aggregate(signatures) - - -def get_aggregate_and_proof(state: BeaconState, - aggregator_index: ValidatorIndex, - aggregate: Attestation, - privkey: int) -> AggregateAndProof: - return AggregateAndProof( - aggregator_index=aggregator_index, - aggregate=aggregate, - selection_proof=get_slot_signature(state, aggregate.data.slot, privkey), - ) - - -def get_aggregate_and_proof_signature(state: BeaconState, - aggregate_and_proof: AggregateAndProof, - privkey: int) -> BLSSignature: - aggregate = aggregate_and_proof.aggregate - domain = get_domain(state, DOMAIN_AGGREGATE_AND_PROOF, compute_epoch_at_slot(aggregate.data.slot)) - signing_root = compute_signing_root(aggregate_and_proof, domain) - return bls.Sign(privkey, signing_root) - - -def compute_weak_subjectivity_period(state: BeaconState) -> uint64: - """ - Returns the weak subjectivity period for the current ``state``. - This computation takes into account the effect of: - - validator set churn (bounded by ``get_validator_churn_limit()`` per epoch), and - - validator balance top-ups (bounded by ``MAX_DEPOSITS * SLOTS_PER_EPOCH`` per epoch). - A detailed calculation can be found at: - https://github.com/runtimeverification/beacon-chain-verification/blob/master/weak-subjectivity/weak-subjectivity-analysis.pdf - """ - ws_period = config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY - N = len(get_active_validator_indices(state, get_current_epoch(state))) - t = get_total_active_balance(state) // N // ETH_TO_GWEI - T = MAX_EFFECTIVE_BALANCE // ETH_TO_GWEI - delta = get_validator_churn_limit(state) - Delta = MAX_DEPOSITS * SLOTS_PER_EPOCH - D = SAFETY_DECAY - - if T * (200 + 3 * D) < t * (200 + 12 * D): - epochs_for_validator_set_churn = ( - N * (t * (200 + 12 * D) - T * (200 + 3 * D)) // (600 * delta * (2 * t + T)) - ) - epochs_for_balance_top_ups = ( - N * (200 + 3 * D) // (600 * Delta) - ) - ws_period += max(epochs_for_validator_set_churn, epochs_for_balance_top_ups) - else: - ws_period += ( - 3 * N * D * t // (200 * Delta * (T - t)) - ) - - return ws_period - - -def is_within_weak_subjectivity_period(store: Store, ws_state: BeaconState, ws_checkpoint: Checkpoint) -> bool: - # Clients may choose to validate the input state against the input Weak Subjectivity Checkpoint - assert ws_state.latest_block_header.state_root == ws_checkpoint.root - assert compute_epoch_at_slot(ws_state.slot) == ws_checkpoint.epoch - - ws_period = compute_weak_subjectivity_period(ws_state) - ws_state_epoch = compute_epoch_at_slot(ws_state.slot) - current_epoch = compute_epoch_at_slot(get_current_slot(store)) - return current_epoch <= ws_state_epoch + ws_period - - -def add_flag(flags: ParticipationFlags, flag_index: int) -> ParticipationFlags: - """ - Return a new ``ParticipationFlags`` adding ``flag_index`` to ``flags``. - """ - flag = ParticipationFlags(2**flag_index) - return flags | flag - - -def has_flag(flags: ParticipationFlags, flag_index: int) -> bool: - """ - Return whether ``flags`` has ``flag_index`` set. - """ - flag = ParticipationFlags(2**flag_index) - return flags & flag == flag - - -def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorIndex]: - """ - Return the sync committee indices, with possible duplicates, for the next sync committee. - """ - epoch = Epoch(get_current_epoch(state) + 1) - - MAX_RANDOM_BYTE = 2**8 - 1 - active_validator_indices = get_active_validator_indices(state, epoch) - active_validator_count = uint64(len(active_validator_indices)) - seed = get_seed(state, epoch, DOMAIN_SYNC_COMMITTEE) - i = 0 - sync_committee_indices: List[ValidatorIndex] = [] - while len(sync_committee_indices) < SYNC_COMMITTEE_SIZE: - shuffled_index = compute_shuffled_index(uint64(i % active_validator_count), active_validator_count, seed) - candidate_index = active_validator_indices[shuffled_index] - random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] - effective_balance = state.validators[candidate_index].effective_balance - if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: - sync_committee_indices.append(candidate_index) - i += 1 - return sync_committee_indices - - -def get_next_sync_committee(state: BeaconState) -> SyncCommittee: - """ - Return the next sync committee, with possible pubkey duplicates. - """ - indices = get_next_sync_committee_indices(state) - pubkeys = [state.validators[index].pubkey for index in indices] - aggregate_pubkey = eth_aggregate_pubkeys(pubkeys) - return SyncCommittee(pubkeys=pubkeys, aggregate_pubkey=aggregate_pubkey) - - -def get_base_reward_per_increment(state: BeaconState) -> Gwei: - return Gwei(EFFECTIVE_BALANCE_INCREMENT * BASE_REWARD_FACTOR // integer_squareroot(get_total_active_balance(state))) - - -def get_unslashed_participating_indices(state: BeaconState, flag_index: int, epoch: Epoch) -> Set[ValidatorIndex]: - """ - Return the set of validator indices that are both active and unslashed for the given ``flag_index`` and ``epoch``. - """ - 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 - active_validator_indices = get_active_validator_indices(state, epoch) - participating_indices = [i for i in active_validator_indices if has_flag(epoch_participation[i], flag_index)] - return set(filter(lambda index: not state.validators[index].slashed, participating_indices)) - - -def get_attestation_participation_flag_indices(state: BeaconState, - data: AttestationData, - inclusion_delay: uint64) -> Sequence[int]: - """ - Return the flag indices that are satisfied by an attestation. - """ - if data.target.epoch == get_current_epoch(state): - justified_checkpoint = state.current_justified_checkpoint - else: - justified_checkpoint = state.previous_justified_checkpoint - - # Matching roots - is_matching_source = data.source == justified_checkpoint - is_matching_target = is_matching_source and data.target.root == get_block_root(state, data.target.epoch) - is_matching_head = is_matching_target and data.beacon_block_root == get_block_root_at_slot(state, data.slot) - assert is_matching_source - - participation_flag_indices = [] - if is_matching_source and inclusion_delay <= integer_squareroot(SLOTS_PER_EPOCH): - participation_flag_indices.append(TIMELY_SOURCE_FLAG_INDEX) - if is_matching_target and inclusion_delay <= SLOTS_PER_EPOCH: - participation_flag_indices.append(TIMELY_TARGET_FLAG_INDEX) - if is_matching_head and inclusion_delay == MIN_ATTESTATION_INCLUSION_DELAY: - participation_flag_indices.append(TIMELY_HEAD_FLAG_INDEX) - - return participation_flag_indices - - -def get_flag_index_deltas(state: BeaconState, flag_index: int) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: - """ - Return the deltas for a given ``flag_index`` by scanning through the participation flags. - """ - rewards = [Gwei(0)] * len(state.validators) - penalties = [Gwei(0)] * len(state.validators) - previous_epoch = get_previous_epoch(state) - unslashed_participating_indices = get_unslashed_participating_indices(state, flag_index, previous_epoch) - weight = PARTICIPATION_FLAG_WEIGHTS[flag_index] - unslashed_participating_balance = get_total_balance(state, unslashed_participating_indices) - unslashed_participating_increments = unslashed_participating_balance // EFFECTIVE_BALANCE_INCREMENT - active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT - for index in get_eligible_validator_indices(state): - base_reward = get_base_reward(state, index) - if index in unslashed_participating_indices: - if not is_in_inactivity_leak(state): - reward_numerator = base_reward * weight * unslashed_participating_increments - rewards[index] += Gwei(reward_numerator // (active_increments * WEIGHT_DENOMINATOR)) - elif flag_index != TIMELY_HEAD_FLAG_INDEX: - penalties[index] += Gwei(base_reward * weight // WEIGHT_DENOMINATOR) - return rewards, penalties - - -def process_sync_aggregate(state: BeaconState, sync_aggregate: SyncAggregate) -> None: - # Verify sync committee aggregate signature signing over the previous slot block root - committee_pubkeys = state.current_sync_committee.pubkeys - participant_pubkeys = [pubkey for pubkey, bit in zip(committee_pubkeys, sync_aggregate.sync_committee_bits) if bit] - previous_slot = max(state.slot, Slot(1)) - Slot(1) - domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, compute_epoch_at_slot(previous_slot)) - signing_root = compute_signing_root(get_block_root_at_slot(state, previous_slot), domain) - assert eth_fast_aggregate_verify(participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature) - - # Compute participant and proposer rewards - total_active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT - total_base_rewards = Gwei(get_base_reward_per_increment(state) * total_active_increments) - max_participant_rewards = Gwei(total_base_rewards * SYNC_REWARD_WEIGHT // WEIGHT_DENOMINATOR // SLOTS_PER_EPOCH) - participant_reward = Gwei(max_participant_rewards // SYNC_COMMITTEE_SIZE) - proposer_reward = Gwei(participant_reward * PROPOSER_WEIGHT // (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)) - - # Apply participant and proposer rewards - all_pubkeys = [v.pubkey for v in state.validators] - committee_indices = [ValidatorIndex(all_pubkeys.index(pubkey)) for pubkey in state.current_sync_committee.pubkeys] - for participant_index, participation_bit in zip(committee_indices, sync_aggregate.sync_committee_bits): - if participation_bit: - increase_balance(state, participant_index, participant_reward) - increase_balance(state, get_beacon_proposer_index(state), proposer_reward) - else: - decrease_balance(state, participant_index, participant_reward) - - -def process_inactivity_updates(state: BeaconState) -> None: - # Skip the genesis epoch as score updates are based on the previous epoch participation - if get_current_epoch(state) == GENESIS_EPOCH: - return - - for index in get_eligible_validator_indices(state): - # Increase the inactivity score of inactive validators - if index in get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)): - state.inactivity_scores[index] -= min(1, state.inactivity_scores[index]) - else: - state.inactivity_scores[index] += config.INACTIVITY_SCORE_BIAS - # Decrease the inactivity score of all eligible validators during a leak-free epoch - if not is_in_inactivity_leak(state): - state.inactivity_scores[index] -= min(config.INACTIVITY_SCORE_RECOVERY_RATE, state.inactivity_scores[index]) - - -def process_participation_flag_updates(state: BeaconState) -> None: - state.previous_epoch_participation = state.current_epoch_participation - state.current_epoch_participation = [ParticipationFlags(0b0000_0000) for _ in range(len(state.validators))] - - -def process_sync_committee_updates(state: BeaconState) -> None: - 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_next_sync_committee(state) - - -def eth_aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey: - return bls.AggregatePKs(pubkeys) - - -def eth_fast_aggregate_verify(pubkeys: Sequence[BLSPubkey], message: Bytes32, signature: BLSSignature) -> bool: - """ - Wrapper to ``bls.FastAggregateVerify`` accepting the ``G2_POINT_AT_INFINITY`` signature when ``pubkeys`` is empty. - """ - if len(pubkeys) == 0 and signature == G2_POINT_AT_INFINITY: - return True - return bls.FastAggregateVerify(pubkeys, message, signature) - - -def translate_participation(state: BeaconState, pending_attestations: Sequence[phase0.PendingAttestation]) -> None: - for attestation in pending_attestations: - data = attestation.data - inclusion_delay = attestation.inclusion_delay - # Translate attestation inclusion info to flag indices - participation_flag_indices = get_attestation_participation_flag_indices(state, data, inclusion_delay) - - # Apply flags to all attesting validators - epoch_participation = state.previous_epoch_participation - for index in get_attesting_indices(state, data, attestation.aggregation_bits): - for flag_index in participation_flag_indices: - epoch_participation[index] = add_flag(epoch_participation[index], flag_index) - - -def upgrade_to_altair(pre: phase0.BeaconState) -> BeaconState: - epoch = phase0.get_current_epoch(pre) - post = BeaconState( - # Versioning - genesis_time=pre.genesis_time, - genesis_validators_root=pre.genesis_validators_root, - slot=pre.slot, - fork=Fork( - previous_version=pre.fork.current_version, - current_version=config.ALTAIR_FORK_VERSION, - epoch=epoch, - ), - # History - latest_block_header=pre.latest_block_header, - block_roots=pre.block_roots, - state_roots=pre.state_roots, - historical_roots=pre.historical_roots, - # Eth1 - eth1_data=pre.eth1_data, - eth1_data_votes=pre.eth1_data_votes, - eth1_deposit_index=pre.eth1_deposit_index, - # Registry - validators=pre.validators, - balances=pre.balances, - # Randomness - randao_mixes=pre.randao_mixes, - # Slashings - slashings=pre.slashings, - # Participation - previous_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))], - current_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))], - # Finality - justification_bits=pre.justification_bits, - previous_justified_checkpoint=pre.previous_justified_checkpoint, - current_justified_checkpoint=pre.current_justified_checkpoint, - finalized_checkpoint=pre.finalized_checkpoint, - # Inactivity - inactivity_scores=[uint64(0) for _ in range(len(pre.validators))], - ) - # Fill in previous epoch participation from the pre state's pending attestations - translate_participation(post, pre.previous_epoch_attestations) - - # Fill in sync committees - # Note: A duplicate committee is assigned for the current and next committee at the fork boundary - post.current_sync_committee = get_next_sync_committee(post) - post.next_sync_committee = get_next_sync_committee(post) - return post - - -def compute_sync_committee_period(epoch: Epoch) -> uint64: - return epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - - -def is_assigned_to_sync_committee(state: BeaconState, - epoch: Epoch, - validator_index: ValidatorIndex) -> bool: - sync_committee_period = compute_sync_committee_period(epoch) - current_epoch = get_current_epoch(state) - current_sync_committee_period = compute_sync_committee_period(current_epoch) - next_sync_committee_period = current_sync_committee_period + 1 - assert sync_committee_period in (current_sync_committee_period, next_sync_committee_period) - - pubkey = state.validators[validator_index].pubkey - if sync_committee_period == current_sync_committee_period: - return pubkey in state.current_sync_committee.pubkeys - else: # sync_committee_period == next_sync_committee_period - return pubkey in state.next_sync_committee.pubkeys - - -def process_sync_committee_contributions(block: BeaconBlock, - contributions: Set[SyncCommitteeContribution]) -> None: - sync_aggregate = SyncAggregate() - signatures = [] - sync_subcommittee_size = SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT - - for contribution in contributions: - subcommittee_index = contribution.subcommittee_index - for index, participated in enumerate(contribution.aggregation_bits): - if participated: - participant_index = sync_subcommittee_size * subcommittee_index + index - sync_aggregate.sync_committee_bits[participant_index] = True - signatures.append(contribution.signature) - - sync_aggregate.sync_committee_signature = bls.Aggregate(signatures) - - block.body.sync_aggregate = sync_aggregate - - -def get_sync_committee_message(state: BeaconState, - block_root: Root, - validator_index: ValidatorIndex, - privkey: int) -> SyncCommitteeMessage: - epoch = get_current_epoch(state) - domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, epoch) - signing_root = compute_signing_root(block_root, domain) - signature = bls.Sign(privkey, signing_root) - - return SyncCommitteeMessage( - slot=state.slot, - beacon_block_root=block_root, - validator_index=validator_index, - signature=signature, - ) - - -def compute_subnets_for_sync_committee(state: BeaconState, validator_index: ValidatorIndex) -> Set[uint64]: - next_slot_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) - if compute_sync_committee_period(get_current_epoch(state)) == compute_sync_committee_period(next_slot_epoch): - sync_committee = state.current_sync_committee - else: - sync_committee = state.next_sync_committee - - target_pubkey = state.validators[validator_index].pubkey - sync_committee_indices = [index for index, pubkey in enumerate(sync_committee.pubkeys) if pubkey == target_pubkey] - return set([ - uint64(index // (SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT)) - for index in sync_committee_indices - ]) - - -def get_sync_committee_selection_proof(state: BeaconState, - slot: Slot, - subcommittee_index: uint64, - privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF, compute_epoch_at_slot(slot)) - signing_data = SyncAggregatorSelectionData( - slot=slot, - subcommittee_index=subcommittee_index, - ) - signing_root = compute_signing_root(signing_data, domain) - return bls.Sign(privkey, signing_root) - - -def is_sync_committee_aggregator(signature: BLSSignature) -> bool: - modulo = max(1, SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT // TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE) - return bytes_to_uint64(hash(signature)[0:8]) % modulo == 0 - - -def get_contribution_and_proof(state: BeaconState, - aggregator_index: ValidatorIndex, - contribution: SyncCommitteeContribution, - privkey: int) -> ContributionAndProof: - selection_proof = get_sync_committee_selection_proof( - state, - contribution.slot, - contribution.subcommittee_index, - privkey, - ) - return ContributionAndProof( - aggregator_index=aggregator_index, - contribution=contribution, - selection_proof=selection_proof, - ) - - -def get_contribution_and_proof_signature(state: BeaconState, - contribution_and_proof: ContributionAndProof, - privkey: int) -> BLSSignature: - contribution = contribution_and_proof.contribution - domain = get_domain(state, DOMAIN_CONTRIBUTION_AND_PROOF, compute_epoch_at_slot(contribution.slot)) - signing_root = compute_signing_root(contribution_and_proof, domain) - return bls.Sign(privkey, signing_root) - - -def get_sync_subcommittee_pubkeys(state: BeaconState, subcommittee_index: uint64) -> Sequence[BLSPubkey]: - # Committees assigned to `slot` sign for `slot - 1` - # This creates the exceptional logic below when transitioning between sync committee periods - next_slot_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) - if compute_sync_committee_period(get_current_epoch(state)) == compute_sync_committee_period(next_slot_epoch): - sync_committee = state.current_sync_committee - else: - sync_committee = state.next_sync_committee - - # Return pubkeys for the subcommittee index - sync_subcommittee_size = SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT - i = subcommittee_index * sync_subcommittee_size - return sync_committee.pubkeys[i:i + sync_subcommittee_size] - - -def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: - return uint64(generalized_index % 2**(floorlog2(generalized_index))) - - -def validate_light_client_update(snapshot: LightClientSnapshot, - update: LightClientUpdate, - genesis_validators_root: Root) -> None: - # Verify update slot is larger than snapshot slot - assert update.header.slot > snapshot.header.slot - - # Verify update does not skip a sync committee period - snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - assert update_period in (snapshot_period, snapshot_period + 1) - - # Verify update header root is the finalized root of the finality header, if specified - if update.finality_header == BeaconBlockHeader(): - signed_header = update.header - assert update.finality_branch == [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))] - else: - signed_header = update.finality_header - assert is_valid_merkle_branch( - leaf=hash_tree_root(update.header), - branch=update.finality_branch, - depth=floorlog2(FINALIZED_ROOT_INDEX), - index=get_subtree_index(FINALIZED_ROOT_INDEX), - root=update.finality_header.state_root, - ) - - # Verify update next sync committee if the update period incremented - if update_period == snapshot_period: - sync_committee = snapshot.current_sync_committee - assert update.next_sync_committee_branch == [Bytes32() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))] - else: - sync_committee = snapshot.next_sync_committee - assert is_valid_merkle_branch( - leaf=hash_tree_root(update.next_sync_committee), - branch=update.next_sync_committee_branch, - depth=floorlog2(NEXT_SYNC_COMMITTEE_INDEX), - index=get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX), - root=update.header.state_root, - ) - - # Verify sync committee has sufficient participants - assert sum(update.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS - - # Verify sync committee aggregate signature - participant_pubkeys = [pubkey for (bit, pubkey) in zip(update.sync_committee_bits, sync_committee.pubkeys) if bit] - domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version, genesis_validators_root) - signing_root = compute_signing_root(signed_header, domain) - assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature) - - -def apply_light_client_update(snapshot: LightClientSnapshot, update: LightClientUpdate) -> None: - snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - if update_period == snapshot_period + 1: - snapshot.current_sync_committee = snapshot.next_sync_committee - snapshot.next_sync_committee = update.next_sync_committee - snapshot.header = update.header - - -def process_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot, - genesis_validators_root: Root) -> None: - validate_light_client_update(store.snapshot, update, genesis_validators_root) - store.valid_updates.add(update) - - update_timeout = SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD - if ( - sum(update.sync_committee_bits) * 3 >= len(update.sync_committee_bits) * 2 - and update.finality_header != BeaconBlockHeader() - ): - # Apply update if (1) 2/3 quorum is reached and (2) we have a finality proof. - # Note that (2) means that the current light client design needs finality. - # It may be changed to re-organizable light client design. See the on-going issue consensus-specs#2182. - apply_light_client_update(store.snapshot, update) - store.valid_updates = set() - elif current_slot > store.snapshot.header.slot + update_timeout: - # Forced best update when the update timeout has elapsed - apply_light_client_update(store.snapshot, - max(store.valid_updates, key=lambda update: sum(update.sync_committee_bits))) - store.valid_updates = set() - - -def is_merge_transition_complete(state: BeaconState) -> bool: - return state.latest_execution_payload_header != ExecutionPayloadHeader() - - -def is_merge_transition_block(state: BeaconState, body: BeaconBlockBody) -> bool: - return not is_merge_transition_complete(state) and body.execution_payload != ExecutionPayload() - - -def is_execution_enabled(state: BeaconState, body: BeaconBlockBody) -> bool: - return is_merge_transition_block(state, body) or is_merge_transition_complete(state) - - -def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64: - slots_since_genesis = slot - GENESIS_SLOT - return uint64(state.genesis_time + slots_since_genesis * config.SECONDS_PER_SLOT) - - -def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None: - # Verify consistency of the parent hash with respect to the previous execution payload header - if is_merge_transition_complete(state): - assert payload.parent_hash == state.latest_execution_payload_header.block_hash - # Verify random - assert payload.random == get_randao_mix(state, get_current_epoch(state)) - # Verify timestamp - assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) - # Verify the execution payload is valid - assert execution_engine.execute_payload(payload) - # Cache execution payload header - state.latest_execution_payload_header = ExecutionPayloadHeader( - parent_hash=payload.parent_hash, - fee_recipient=payload.fee_recipient, - state_root=payload.state_root, - receipt_root=payload.receipt_root, - logs_bloom=payload.logs_bloom, - random=payload.random, - block_number=payload.block_number, - gas_limit=payload.gas_limit, - gas_used=payload.gas_used, - timestamp=payload.timestamp, - extra_data=payload.extra_data, - base_fee_per_gas=payload.base_fee_per_gas, - block_hash=payload.block_hash, - transactions_root=hash_tree_root(payload.transactions), - ) - - -def upgrade_to_merge(pre: altair.BeaconState) -> BeaconState: - epoch = altair.get_current_epoch(pre) - post = BeaconState( - # Versioning - genesis_time=pre.genesis_time, - genesis_validators_root=pre.genesis_validators_root, - slot=pre.slot, - fork=Fork( - previous_version=pre.fork.current_version, - current_version=config.MERGE_FORK_VERSION, - epoch=epoch, - ), - # History - latest_block_header=pre.latest_block_header, - block_roots=pre.block_roots, - state_roots=pre.state_roots, - historical_roots=pre.historical_roots, - # Eth1 - eth1_data=pre.eth1_data, - eth1_data_votes=pre.eth1_data_votes, - eth1_deposit_index=pre.eth1_deposit_index, - # Registry - validators=pre.validators, - balances=pre.balances, - # Randomness - randao_mixes=pre.randao_mixes, - # Slashings - slashings=pre.slashings, - # Participation - previous_epoch_participation=pre.previous_epoch_participation, - current_epoch_participation=pre.current_epoch_participation, - # Finality - justification_bits=pre.justification_bits, - previous_justified_checkpoint=pre.previous_justified_checkpoint, - current_justified_checkpoint=pre.current_justified_checkpoint, - finalized_checkpoint=pre.finalized_checkpoint, - # Inactivity - inactivity_scores=pre.inactivity_scores, - # Sync - current_sync_committee=pre.current_sync_committee, - next_sync_committee=pre.next_sync_committee, - # Execution-layer - latest_execution_payload_header=ExecutionPayloadHeader(), - ) - - return post - - -def is_valid_terminal_pow_block(block: PowBlock, parent: PowBlock) -> bool: - is_total_difficulty_reached = block.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY - is_parent_total_difficulty_valid = parent.total_difficulty < config.TERMINAL_TOTAL_DIFFICULTY - return is_total_difficulty_reached and is_parent_total_difficulty_valid - - -def validate_merge_block(block: BeaconBlock) -> None: - """ - Check the parent PoW block of execution payload is a valid terminal PoW block. - - Note: Unavailable PoW block(s) may later become available, - and a client software MAY delay a call to ``validate_merge_block`` - until the PoW block(s) become available. - """ - if config.TERMINAL_BLOCK_HASH != Hash32(): - # If `config.TERMINAL_BLOCK_HASH` is used as an override, the activation epoch must be reached. - assert compute_epoch_at_slot(block.slot) >= config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH - assert block.body.execution_payload.parent_hash == config.TERMINAL_BLOCK_HASH - return - - pow_block = get_pow_block(block.body.execution_payload.parent_hash) - # Check if `pow_block` is available - assert pow_block is not None - pow_parent = get_pow_block(pow_block.parent_hash) - # Check if `pow_parent` is available - assert pow_parent is not None - # Check if `pow_block` is a valid terminal PoW block - assert is_valid_terminal_pow_block(pow_block, pow_parent) - - -def get_pow_block_at_terminal_total_difficulty(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlock]: - # `pow_chain` abstractly represents all blocks in the PoW chain - for block in pow_chain.values(): - block_reached_ttd = block.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY - if block_reached_ttd: - # If genesis block, no parent exists so reaching TTD alone qualifies as valid terminal block - if block.parent_hash == Hash32(): - return block - parent = pow_chain[block.parent_hash] - parent_reached_ttd = parent.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY - if not parent_reached_ttd: - return block - - return None - - -def get_terminal_pow_block(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlock]: - if config.TERMINAL_BLOCK_HASH != Hash32(): - # Terminal block hash override takes precedence over terminal total difficulty - if config.TERMINAL_BLOCK_HASH in pow_chain: - return pow_chain[config.TERMINAL_BLOCK_HASH] - else: - return None - - return get_pow_block_at_terminal_total_difficulty(pow_chain) - - -def prepare_execution_payload(state: BeaconState, - pow_chain: Dict[Hash32, PowBlock], - finalized_block_hash: Hash32, - suggested_fee_recipient: ExecutionAddress, - execution_engine: ExecutionEngine) -> Optional[PayloadId]: - if not is_merge_transition_complete(state): - is_terminal_block_hash_set = config.TERMINAL_BLOCK_HASH != Hash32() - is_activation_epoch_reached = get_current_epoch(state) >= config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH - if is_terminal_block_hash_set and not is_activation_epoch_reached: - # Terminal block hash is set but activation epoch is not yet reached, no prepare payload call is needed - return None - - terminal_pow_block = get_terminal_pow_block(pow_chain) - if terminal_pow_block is None: - # Pre-merge, no prepare payload call is needed - return None - # Signify merge via producing on top of the terminal PoW block - parent_hash = terminal_pow_block.block_hash - else: - # Post-merge, normal payload - parent_hash = state.latest_execution_payload_header.block_hash - - # Set the forkchoice head and initiate the payload build process - payload_attributes = PayloadAttributes( - timestamp=compute_timestamp_at_slot(state, state.slot), - random=get_randao_mix(state, get_current_epoch(state)), - suggested_fee_recipient=suggested_fee_recipient, - ) - return execution_engine.notify_forkchoice_updated(parent_hash, finalized_block_hash, payload_attributes) - - -def get_execution_payload(payload_id: Optional[PayloadId], execution_engine: ExecutionEngine) -> ExecutionPayload: - if payload_id is None: - # Pre-merge, empty payload - return ExecutionPayload() - else: - return execution_engine.get_payload(payload_id) - - -def withdraw(state: BeaconState, index: ValidatorIndex, amount: Gwei) -> None: - # Decrease the validator's balance - decrease_balance(state, index, amount) - # Create a corresponding withdrawal receipt - receipt = WithdrawalReceipt( - index=WithdrawalReceiptIndex(len(state.withdrawal_receipts)), - address=state.validators[index].withdrawal_credentials[12:], - amount=amount, - ) - state.withdrawal_receipts.append(receipt) - - -def is_withdrawable_validator(validator: Validator, epoch: Epoch) -> bool: - """ - Check if ``validator`` is withdrawable. - """ - return validator.withdrawable_epoch <= epoch - - -def process_withdrawals(state: BeaconState) -> None: - current_epoch = get_current_epoch(state) - for index, validator in enumerate(state.validators): - balance = state.balances[index] - is_balance_nonzero = state.balances[index] == 0 - is_eth1_withdrawal_prefix = validator.withdrawal_credentials[0] != ETH1_ADDRESS_WITHDRAWAL_PREFIX - if is_balance_nonzero and is_eth1_withdrawal_prefix and is_withdrawable_validator(validator, current_epoch): - withdraw(state, ValidatorIndex(index), balance) - - -def upgrade_to_capella(pre: merge.BeaconState) -> BeaconState: - epoch = merge.get_current_epoch(pre) - post = BeaconState( - # Versioning - genesis_time=pre.genesis_time, - genesis_validators_root=pre.genesis_validators_root, - slot=pre.slot, - fork=Fork( - previous_version=pre.fork.current_version, - current_version=config.CAPELLA_FORK_VERSION, - epoch=epoch, - ), - # History - latest_block_header=pre.latest_block_header, - block_roots=pre.block_roots, - state_roots=pre.state_roots, - historical_roots=pre.historical_roots, - # Eth1 - eth1_data=pre.eth1_data, - eth1_data_votes=pre.eth1_data_votes, - eth1_deposit_index=pre.eth1_deposit_index, - # Registry - validators=pre.validators, - balances=pre.balances, - # Randomness - randao_mixes=pre.randao_mixes, - # Slashings - slashings=pre.slashings, - # Participation - previous_epoch_participation=pre.previous_epoch_participation, - current_epoch_participation=pre.current_epoch_participation, - # Finality - justification_bits=pre.justification_bits, - previous_justified_checkpoint=pre.previous_justified_checkpoint, - current_justified_checkpoint=pre.current_justified_checkpoint, - finalized_checkpoint=pre.finalized_checkpoint, - # Inactivity - inactivity_scores=pre.inactivity_scores, - # Sync - current_sync_committee=pre.current_sync_committee, - next_sync_committee=pre.next_sync_committee, - # Execution-layer - latest_execution_payload_header=pre.latest_execution_payload_header, - # Withdrawals - withdrawal_receipts=[], - ) - - return post - - -def get_eth1_data(block: Eth1Block) -> Eth1Data: - """ - A stub function return mocking Eth1Data. - """ - return Eth1Data( - deposit_root=block.deposit_root, - deposit_count=block.deposit_count, - block_hash=hash_tree_root(block)) - - -def cache_this(key_fn, value_fn, lru_size): # type: ignore - cache_dict = LRU(size=lru_size) - - def wrapper(*args, **kw): # type: ignore - key = key_fn(*args, **kw) - nonlocal cache_dict - if key not in cache_dict: - cache_dict[key] = value_fn(*args, **kw) - return cache_dict[key] - return wrapper - - -_compute_shuffled_index = compute_shuffled_index -compute_shuffled_index = cache_this( - lambda index, index_count, seed: (index, index_count, seed), - _compute_shuffled_index, lru_size=SLOTS_PER_EPOCH * 3) - -_get_total_active_balance = get_total_active_balance -get_total_active_balance = cache_this( - lambda state: (state.validators.hash_tree_root(), compute_epoch_at_slot(state.slot)), - _get_total_active_balance, lru_size=10) - -_get_base_reward = get_base_reward -get_base_reward = cache_this( - lambda state, index: (state.validators.hash_tree_root(), state.slot, index), - _get_base_reward, lru_size=2048) - -_get_committee_count_per_slot = get_committee_count_per_slot -get_committee_count_per_slot = cache_this( - lambda state, epoch: (state.validators.hash_tree_root(), epoch), - _get_committee_count_per_slot, lru_size=SLOTS_PER_EPOCH * 3) - -_get_active_validator_indices = get_active_validator_indices -get_active_validator_indices = cache_this( - lambda state, epoch: (state.validators.hash_tree_root(), epoch), - _get_active_validator_indices, lru_size=3) - -_get_beacon_committee = get_beacon_committee -get_beacon_committee = cache_this( - lambda state, slot, index: (state.validators.hash_tree_root(), state.randao_mixes.hash_tree_root(), slot, index), - _get_beacon_committee, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3) - -_get_matching_target_attestations = get_matching_target_attestations -get_matching_target_attestations = cache_this( - lambda state, epoch: (state.hash_tree_root(), epoch), - _get_matching_target_attestations, lru_size=10) - -_get_matching_head_attestations = get_matching_head_attestations -get_matching_head_attestations = cache_this( - lambda state, epoch: (state.hash_tree_root(), epoch), - _get_matching_head_attestations, lru_size=10) - -_get_attesting_indices = get_attesting_indices -get_attesting_indices = cache_this( - lambda state, data, bits: ( - state.randao_mixes.hash_tree_root(), - state.validators.hash_tree_root(), data.hash_tree_root(), bits.hash_tree_root() - ), - _get_attesting_indices, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3) - - -def get_generalized_index(ssz_class: Any, *path: Sequence[PyUnion[int, SSZVariableName]]) -> GeneralizedIndex: - ssz_path = Path(ssz_class) - for item in path: - ssz_path = ssz_path / item - return GeneralizedIndex(ssz_path.gindex()) - - -ExecutionState = Any - - -def get_pow_block(hash: Bytes32) -> Optional[PowBlock]: - return PowBlock(block_hash=hash, parent_hash=Bytes32(), total_difficulty=uint256(0)) - - -def get_execution_state(execution_state_root: Bytes32) -> ExecutionState: - pass - - -def get_pow_chain_head() -> PowBlock: - pass - - -class NoopExecutionEngine(ExecutionEngine): - - def execute_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: - return True - - def notify_forkchoice_updated(self: ExecutionEngine, - head_block_hash: Hash32, - finalized_block_hash: Hash32, - payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: - pass - - def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayload: - raise NotImplementedError("no default block production") - - -EXECUTION_ENGINE = NoopExecutionEngine() - - -assert FINALIZED_ROOT_INDEX == get_generalized_index(BeaconState, 'finalized_checkpoint', 'root') -assert NEXT_SYNC_COMMITTEE_INDEX == get_generalized_index(BeaconState, 'next_sync_committee') diff --git a/tests/core/pyspec/eth2spec/withdrawals/mainnet.py b/tests/core/pyspec/eth2spec/withdrawals/mainnet.py deleted file mode 100644 index d7c9ce43f8..0000000000 --- a/tests/core/pyspec/eth2spec/withdrawals/mainnet.py +++ /dev/null @@ -1,3018 +0,0 @@ -from lru import LRU -from dataclasses import ( - dataclass, - field, -) -from typing import ( - Any, Callable, Dict, Set, Sequence, Tuple, Optional, TypeVar, NamedTuple -) - -from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy, uint_to_bytes -from eth2spec.utils.ssz.ssz_typing import ( - View, boolean, Container, List, Vector, uint8, uint32, uint64, - Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist) -from eth2spec.utils.ssz.ssz_typing import Bitvector # noqa: F401 -from eth2spec.utils import bls -from eth2spec.utils.hash_function import hash - - -from typing import NewType, Union as PyUnion - -from eth2spec.phase0 import mainnet as phase0 -from eth2spec.utils.ssz.ssz_typing import Path - -from typing import Protocol -from eth2spec.altair import mainnet as altair -from eth2spec.utils.ssz.ssz_typing import Bytes8, Bytes20, ByteList, ByteVector, uint256 - -SSZObject = TypeVar('SSZObject', bound=View) - - -SSZVariableName = str -GeneralizedIndex = NewType('GeneralizedIndex', int) - - -fork = 'withdrawals' - - -MAX_BYTES_PER_TRANSACTION = uint64(2**30) - - -class Slot(uint64): - pass - - -class Epoch(uint64): - pass - - -class CommitteeIndex(uint64): - pass - - -class ValidatorIndex(uint64): - pass - - -class Gwei(uint64): - pass - - -class Root(Bytes32): - pass - - -class Hash32(Bytes32): - pass - - -class Version(Bytes4): - pass - - -class DomainType(Bytes4): - pass - - -class ForkDigest(Bytes4): - pass - - -class Domain(Bytes32): - pass - - -class BLSPubkey(Bytes48): - pass - - -class BLSSignature(Bytes96): - pass - - -class Ether(uint64): - pass - - -class ParticipationFlags(uint8): - pass - - -class ExecutionAddress(Bytes20): - pass - - -class PayloadId(Bytes8): - pass - - -class WithdrawalReceiptIndex(uint64): - pass - - -Transaction = ByteList[MAX_BYTES_PER_TRANSACTION] - - -def ceillog2(x: int) -> uint64: - if x < 1: - raise ValueError(f"ceillog2 accepts only positive values, x={x}") - return uint64((x - 1).bit_length()) - - -def floorlog2(x: int) -> uint64: - if x < 1: - raise ValueError(f"floorlog2 accepts only positive values, x={x}") - return uint64(x.bit_length() - 1) - - -FINALIZED_ROOT_INDEX = GeneralizedIndex(105) -NEXT_SYNC_COMMITTEE_INDEX = GeneralizedIndex(55) - -# Constant vars -GENESIS_SLOT = Slot(0) -GENESIS_EPOCH = Epoch(0) -FAR_FUTURE_EPOCH = Epoch(2**64 - 1) -BASE_REWARDS_PER_EPOCH = uint64(4) -DEPOSIT_CONTRACT_TREE_DEPTH = uint64(2**5) -JUSTIFICATION_BITS_LENGTH = uint64(4) -ENDIANNESS = 'little' -BLS_WITHDRAWAL_PREFIX = Bytes1('0x00') -ETH1_ADDRESS_WITHDRAWAL_PREFIX = Bytes1('0x01') -DOMAIN_BEACON_PROPOSER = DomainType('0x00000000') -DOMAIN_BEACON_ATTESTER = DomainType('0x01000000') -DOMAIN_RANDAO = DomainType('0x02000000') -DOMAIN_DEPOSIT = DomainType('0x03000000') -DOMAIN_VOLUNTARY_EXIT = DomainType('0x04000000') -DOMAIN_SELECTION_PROOF = DomainType('0x05000000') -DOMAIN_AGGREGATE_AND_PROOF = DomainType('0x06000000') -INTERVALS_PER_SLOT = uint64(3) -TARGET_AGGREGATORS_PER_COMMITTEE = 2**4 -RANDOM_SUBNETS_PER_VALIDATOR = 2**0 -EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION = 2**8 -ATTESTATION_SUBNET_COUNT = 64 -ETH_TO_GWEI = uint64(10**9) -SAFETY_DECAY = uint64(10) -TIMELY_SOURCE_FLAG_INDEX = 0 -TIMELY_TARGET_FLAG_INDEX = 1 -TIMELY_HEAD_FLAG_INDEX = 2 -TIMELY_SOURCE_WEIGHT = uint64(14) -TIMELY_TARGET_WEIGHT = uint64(26) -TIMELY_HEAD_WEIGHT = uint64(14) -SYNC_REWARD_WEIGHT = uint64(2) -PROPOSER_WEIGHT = uint64(8) -WEIGHT_DENOMINATOR = uint64(64) -DOMAIN_SYNC_COMMITTEE = DomainType('0x07000000') -DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF = DomainType('0x08000000') -DOMAIN_CONTRIBUTION_AND_PROOF = DomainType('0x09000000') -PARTICIPATION_FLAG_WEIGHTS = [TIMELY_SOURCE_WEIGHT, TIMELY_TARGET_WEIGHT, TIMELY_HEAD_WEIGHT] -G2_POINT_AT_INFINITY = BLSSignature(b'\xc0' + b'\x00' * 95) -TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE = 2**4 -SYNC_COMMITTEE_SUBNET_COUNT = 4 -WITHDRAWAL_RECEIPT_LIMIT = uint64(2**40) - -# Preset vars -MAX_COMMITTEES_PER_SLOT = uint64(64) -TARGET_COMMITTEE_SIZE = uint64(128) -MAX_VALIDATORS_PER_COMMITTEE = uint64(2048) -SHUFFLE_ROUND_COUNT = uint64(90) -HYSTERESIS_QUOTIENT = uint64(4) -HYSTERESIS_DOWNWARD_MULTIPLIER = uint64(1) -HYSTERESIS_UPWARD_MULTIPLIER = uint64(5) -MIN_DEPOSIT_AMOUNT = Gwei(1000000000) -MAX_EFFECTIVE_BALANCE = Gwei(32000000000) -EFFECTIVE_BALANCE_INCREMENT = Gwei(1000000000) -MIN_ATTESTATION_INCLUSION_DELAY = uint64(1) -SLOTS_PER_EPOCH = uint64(32) -MIN_SEED_LOOKAHEAD = uint64(1) -MAX_SEED_LOOKAHEAD = uint64(4) -MIN_EPOCHS_TO_INACTIVITY_PENALTY = uint64(4) -EPOCHS_PER_ETH1_VOTING_PERIOD = uint64(64) -SLOTS_PER_HISTORICAL_ROOT = uint64(8192) -EPOCHS_PER_HISTORICAL_VECTOR = uint64(65536) -EPOCHS_PER_SLASHINGS_VECTOR = uint64(8192) -HISTORICAL_ROOTS_LIMIT = uint64(16777216) -VALIDATOR_REGISTRY_LIMIT = uint64(1099511627776) -BASE_REWARD_FACTOR = uint64(64) -WHISTLEBLOWER_REWARD_QUOTIENT = uint64(512) -PROPOSER_REWARD_QUOTIENT = uint64(8) -INACTIVITY_PENALTY_QUOTIENT = uint64(67108864) -MIN_SLASHING_PENALTY_QUOTIENT = uint64(128) -PROPORTIONAL_SLASHING_MULTIPLIER = uint64(1) -MAX_PROPOSER_SLASHINGS = 16 -MAX_ATTESTER_SLASHINGS = 2 -MAX_ATTESTATIONS = 128 -MAX_DEPOSITS = 16 -MAX_VOLUNTARY_EXITS = 16 -SAFE_SLOTS_TO_UPDATE_JUSTIFIED = 8 -INACTIVITY_PENALTY_QUOTIENT_ALTAIR = uint64(50331648) -MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR = uint64(64) -PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR = uint64(2) -SYNC_COMMITTEE_SIZE = uint64(512) -EPOCHS_PER_SYNC_COMMITTEE_PERIOD = uint64(256) -MIN_SYNC_COMMITTEE_PARTICIPANTS = 1 -MAX_BYTES_PER_TRANSACTION = uint64(1073741824) -MAX_TRANSACTIONS_PER_PAYLOAD = uint64(1048576) -BYTES_PER_LOGS_BLOOM = uint64(256) -MAX_EXTRA_DATA_BYTES = 32 -INACTIVITY_PENALTY_QUOTIENT_MERGE = uint64(16777216) -MIN_SLASHING_PENALTY_QUOTIENT_MERGE = uint64(32) -PROPORTIONAL_SLASHING_MULTIPLIER_MERGE = uint64(3) - - -class Configuration(NamedTuple): - PRESET_BASE: str - MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: uint64 - MIN_GENESIS_TIME: uint64 - GENESIS_FORK_VERSION: Version - GENESIS_DELAY: uint64 - SECONDS_PER_SLOT: uint64 - SECONDS_PER_ETH1_BLOCK: uint64 - MIN_VALIDATOR_WITHDRAWABILITY_DELAY: uint64 - SHARD_COMMITTEE_PERIOD: uint64 - ETH1_FOLLOW_DISTANCE: uint64 - EJECTION_BALANCE: Gwei - MIN_PER_EPOCH_CHURN_LIMIT: uint64 - CHURN_LIMIT_QUOTIENT: uint64 - PROPOSER_SCORE_BOOST: uint64 - INACTIVITY_SCORE_BIAS: uint64 - INACTIVITY_SCORE_RECOVERY_RATE: uint64 - ALTAIR_FORK_VERSION: Version - ALTAIR_FORK_EPOCH: Epoch - TERMINAL_TOTAL_DIFFICULTY: int - TERMINAL_BLOCK_HASH: Hash32 - TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: int - MERGE_FORK_VERSION: Version - MERGE_FORK_EPOCH: Epoch - - -config = Configuration( - PRESET_BASE="mainnet", - MIN_GENESIS_ACTIVE_VALIDATOR_COUNT=uint64(16384), - MIN_GENESIS_TIME=uint64(1606824000), - GENESIS_FORK_VERSION=Version('0x00000000'), - GENESIS_DELAY=uint64(604800), - SECONDS_PER_SLOT=uint64(12), - SECONDS_PER_ETH1_BLOCK=uint64(14), - MIN_VALIDATOR_WITHDRAWABILITY_DELAY=uint64(256), - SHARD_COMMITTEE_PERIOD=uint64(256), - ETH1_FOLLOW_DISTANCE=uint64(2048), - EJECTION_BALANCE=Gwei(16000000000), - MIN_PER_EPOCH_CHURN_LIMIT=uint64(4), - CHURN_LIMIT_QUOTIENT=uint64(65536), - PROPOSER_SCORE_BOOST=uint64(70), - INACTIVITY_SCORE_BIAS=uint64(4), - INACTIVITY_SCORE_RECOVERY_RATE=uint64(16), - ALTAIR_FORK_VERSION=Version('0x01000000'), - ALTAIR_FORK_EPOCH=Epoch(74240), - TERMINAL_TOTAL_DIFFICULTY=115792089237316195423570985008687907853269984665640564039457584007913129638912, - TERMINAL_BLOCK_HASH=Hash32('0x0000000000000000000000000000000000000000000000000000000000000000'), - TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH=18446744073709551615, - MERGE_FORK_VERSION=Version('0x02000000'), - MERGE_FORK_EPOCH=Epoch(18446744073709551615), -) - - -class Fork(Container): - previous_version: Version - current_version: Version - epoch: Epoch # Epoch of latest fork - - -class ForkData(Container): - current_version: Version - genesis_validators_root: Root - - -class Checkpoint(Container): - epoch: Epoch - root: Root - - -class Validator(Container): - pubkey: BLSPubkey - withdrawal_credentials: Bytes32 # Commitment to pubkey for withdrawals - effective_balance: Gwei # Balance at stake - slashed: boolean - # Status epochs - activation_eligibility_epoch: Epoch # When criteria for activation were met - activation_epoch: Epoch - exit_epoch: Epoch - withdrawable_epoch: Epoch # When validator can withdraw funds - - -class AttestationData(Container): - slot: Slot - index: CommitteeIndex - # LMD GHOST vote - beacon_block_root: Root - # FFG vote - source: Checkpoint - target: Checkpoint - - -class IndexedAttestation(Container): - attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] - data: AttestationData - signature: BLSSignature - - -class PendingAttestation(Container): - aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] - data: AttestationData - inclusion_delay: Slot - proposer_index: ValidatorIndex - - -class Eth1Data(Container): - deposit_root: Root - deposit_count: uint64 - block_hash: Hash32 - - -class HistoricalBatch(Container): - block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] - state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] - - -class DepositMessage(Container): - pubkey: BLSPubkey - withdrawal_credentials: Bytes32 - amount: Gwei - - -class DepositData(Container): - pubkey: BLSPubkey - withdrawal_credentials: Bytes32 - amount: Gwei - signature: BLSSignature # Signing over DepositMessage - - -class BeaconBlockHeader(Container): - slot: Slot - proposer_index: ValidatorIndex - parent_root: Root - state_root: Root - body_root: Root - - -class SigningData(Container): - object_root: Root - domain: Domain - - -class AttesterSlashing(Container): - attestation_1: IndexedAttestation - attestation_2: IndexedAttestation - - -class Attestation(Container): - aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] - data: AttestationData - signature: BLSSignature - - -class Deposit(Container): - proof: Vector[Bytes32, DEPOSIT_CONTRACT_TREE_DEPTH + 1] # Merkle path to deposit root - data: DepositData - - -class VoluntaryExit(Container): - epoch: Epoch # Earliest epoch when voluntary exit can be processed - validator_index: ValidatorIndex - - -class SignedVoluntaryExit(Container): - message: VoluntaryExit - signature: BLSSignature - - -class SignedBeaconBlockHeader(Container): - message: BeaconBlockHeader - signature: BLSSignature - - -class ProposerSlashing(Container): - signed_header_1: SignedBeaconBlockHeader - signed_header_2: SignedBeaconBlockHeader - - -class Eth1Block(Container): - timestamp: uint64 - deposit_root: Root - deposit_count: uint64 - # All other eth1 block fields - - -class AggregateAndProof(Container): - aggregator_index: ValidatorIndex - aggregate: Attestation - selection_proof: BLSSignature - - -class SignedAggregateAndProof(Container): - message: AggregateAndProof - signature: BLSSignature - - -class SyncAggregate(Container): - sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] - sync_committee_signature: BLSSignature - - -class SyncCommittee(Container): - pubkeys: Vector[BLSPubkey, SYNC_COMMITTEE_SIZE] - aggregate_pubkey: BLSPubkey - - -class SyncCommitteeMessage(Container): - # Slot to which this contribution pertains - slot: Slot - # Block root for this signature - beacon_block_root: Root - # Index of the validator that produced this signature - validator_index: ValidatorIndex - # Signature by the validator over the block root of `slot` - signature: BLSSignature - - -class SyncCommitteeContribution(Container): - # Slot to which this contribution pertains - slot: Slot - # Block root for this contribution - beacon_block_root: Root - # The subcommittee this contribution pertains to out of the broader sync committee - subcommittee_index: uint64 - # A bit is set if a signature from the validator at the corresponding - # index in the subcommittee is present in the aggregate `signature`. - aggregation_bits: Bitvector[SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT] - # Signature by the validator(s) over the block root of `slot` - signature: BLSSignature - - -class ContributionAndProof(Container): - aggregator_index: ValidatorIndex - contribution: SyncCommitteeContribution - selection_proof: BLSSignature - - -class SignedContributionAndProof(Container): - message: ContributionAndProof - signature: BLSSignature - - -class SyncAggregatorSelectionData(Container): - slot: Slot - subcommittee_index: uint64 - - -class LightClientSnapshot(Container): - # Beacon block header - header: BeaconBlockHeader - # Sync committees corresponding to the header - current_sync_committee: SyncCommittee - next_sync_committee: SyncCommittee - - -class LightClientUpdate(Container): - # Update beacon block header - header: BeaconBlockHeader - # Next sync committee corresponding to the header - next_sync_committee: SyncCommittee - next_sync_committee_branch: Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_INDEX)] - # Finality proof for the update header - finality_header: BeaconBlockHeader - finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX)] - # Sync committee aggregate signature - sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] - sync_committee_signature: BLSSignature - # Fork version for the aggregate signature - fork_version: Version - - -class ExecutionPayload(Container): - # Execution block header fields - parent_hash: Hash32 - fee_recipient: ExecutionAddress # 'beneficiary' in the yellow paper - state_root: Bytes32 - receipt_root: Bytes32 # 'receipts root' in the yellow paper - logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - random: Bytes32 # 'difficulty' in the yellow paper - block_number: uint64 # 'number' in the yellow paper - gas_limit: uint64 - gas_used: uint64 - timestamp: uint64 - extra_data: ByteList[MAX_EXTRA_DATA_BYTES] - base_fee_per_gas: uint256 - # Extra payload fields - block_hash: Hash32 # Hash of execution block - transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] - - -class BeaconBlockBody(Container): - randao_reveal: BLSSignature - eth1_data: Eth1Data # Eth1 data vote - graffiti: Bytes32 # Arbitrary data - # Operations - proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] - attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] - attestations: List[Attestation, MAX_ATTESTATIONS] - deposits: List[Deposit, MAX_DEPOSITS] - voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] - sync_aggregate: SyncAggregate - # Execution - execution_payload: ExecutionPayload # [New in Merge] - - -class BeaconBlock(Container): - slot: Slot - proposer_index: ValidatorIndex - parent_root: Root - state_root: Root - body: BeaconBlockBody - - -class SignedBeaconBlock(Container): - message: BeaconBlock - signature: BLSSignature - - -class ExecutionPayloadHeader(Container): - # Execution block header fields - parent_hash: Hash32 - fee_recipient: ExecutionAddress - state_root: Bytes32 - receipt_root: Bytes32 - logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - random: Bytes32 - block_number: uint64 - gas_limit: uint64 - gas_used: uint64 - timestamp: uint64 - extra_data: ByteList[MAX_EXTRA_DATA_BYTES] - base_fee_per_gas: uint256 - # Extra payload fields - block_hash: Hash32 # Hash of execution block - transactions_root: Root - - -class PowBlock(Container): - block_hash: Hash32 - parent_hash: Hash32 - total_difficulty: uint256 - - -class WithdrawalReceipt(Container): - index: WithdrawalReceiptIndex - address: ExecutionAddress - amount: Gwei - - -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[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] - current_epoch_participation: List[ParticipationFlags, 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 - # Inactivity - inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] - # Sync - current_sync_committee: SyncCommittee - next_sync_committee: SyncCommittee - # Execution - latest_execution_payload_header: ExecutionPayloadHeader - # Withdrawals - withdrawal_receipts: List[WithdrawalReceipt, WITHDRAWAL_RECEIPT_LIMIT] # [New in Withdrawals] - - -@dataclass(eq=True, frozen=True) -class LatestMessage(object): - epoch: Epoch - root: Root - - -@dataclass -class Store(object): - time: uint64 - genesis_time: uint64 - justified_checkpoint: Checkpoint - finalized_checkpoint: Checkpoint - best_justified_checkpoint: Checkpoint - proposer_boost_root: Root - blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) - block_states: Dict[Root, BeaconState] = field(default_factory=dict) - checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict) - latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict) - - -@dataclass -class LightClientStore(object): - snapshot: LightClientSnapshot - valid_updates: Set[LightClientUpdate] - - -@dataclass -class PayloadAttributes(object): - timestamp: uint64 - random: Bytes32 - suggested_fee_recipient: ExecutionAddress - - -class ExecutionEngine(Protocol): - - def execute_payload(self, execution_payload: ExecutionPayload) -> bool: - """ - Return ``True`` if and only if ``execution_payload`` is valid with respect to ``self.execution_state``. - """ - ... - - def notify_forkchoice_updated(self, - head_block_hash: Hash32, - finalized_block_hash: Hash32, - payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: - ... - - def get_payload(self, payload_id: PayloadId) -> ExecutionPayload: - """ - Return ``execution_payload`` object. - """ - ... - - -def integer_squareroot(n: uint64) -> uint64: - """ - Return the largest integer ``x`` such that ``x**2 <= n``. - """ - x = n - y = (x + 1) // 2 - while y < x: - x = y - y = (x + n // x) // 2 - return x - - -def xor(bytes_1: Bytes32, bytes_2: Bytes32) -> Bytes32: - """ - Return the exclusive-or of two 32-byte strings. - """ - return Bytes32(a ^ b for a, b in zip(bytes_1, bytes_2)) - - -def bytes_to_uint64(data: bytes) -> uint64: - """ - Return the integer deserialization of ``data`` interpreted as ``ENDIANNESS``-endian. - """ - return uint64(int.from_bytes(data, ENDIANNESS)) - - -def is_active_validator(validator: Validator, epoch: Epoch) -> bool: - """ - Check if ``validator`` is active. - """ - return validator.activation_epoch <= epoch < validator.exit_epoch - - -def is_eligible_for_activation_queue(validator: Validator) -> bool: - """ - Check if ``validator`` is eligible to be placed into the activation queue. - """ - return ( - validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH - and validator.effective_balance == MAX_EFFECTIVE_BALANCE - ) - - -def is_eligible_for_activation(state: BeaconState, validator: Validator) -> bool: - """ - Check if ``validator`` is eligible for activation. - """ - return ( - # Placement in queue is finalized - validator.activation_eligibility_epoch <= state.finalized_checkpoint.epoch - # Has not yet been activated - and validator.activation_epoch == FAR_FUTURE_EPOCH - ) - - -def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool: - """ - Check if ``validator`` is slashable. - """ - return (not validator.slashed) and (validator.activation_epoch <= epoch < validator.withdrawable_epoch) - - -def is_slashable_attestation_data(data_1: AttestationData, data_2: AttestationData) -> bool: - """ - Check if ``data_1`` and ``data_2`` are slashable according to Casper FFG rules. - """ - return ( - # Double vote - (data_1 != data_2 and data_1.target.epoch == data_2.target.epoch) or - # Surround vote - (data_1.source.epoch < data_2.source.epoch and data_2.target.epoch < data_1.target.epoch) - ) - - -def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool: - """ - Check if ``indexed_attestation`` is not empty, has sorted and unique indices and has a valid aggregate signature. - """ - # Verify indices are sorted and unique - indices = indexed_attestation.attesting_indices - if len(indices) == 0 or not indices == sorted(set(indices)): - return False - # Verify aggregate signature - pubkeys = [state.validators[i].pubkey for i in indices] - domain = get_domain(state, DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch) - signing_root = compute_signing_root(indexed_attestation.data, domain) - return bls.FastAggregateVerify(pubkeys, signing_root, indexed_attestation.signature) - - -def is_valid_merkle_branch(leaf: Bytes32, branch: Sequence[Bytes32], depth: uint64, index: uint64, root: Root) -> bool: - """ - Check if ``leaf`` at ``index`` verifies against the Merkle ``root`` and ``branch``. - """ - value = leaf - for i in range(depth): - if index // (2**i) % 2: - value = hash(branch[i] + value) - else: - value = hash(value + branch[i]) - return value == root - - -def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) -> uint64: - """ - Return the shuffled index corresponding to ``seed`` (and ``index_count``). - """ - assert index < index_count - - # Swap or not (https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf) - # See the 'generalized domain' algorithm on page 3 - for current_round in range(SHUFFLE_ROUND_COUNT): - pivot = bytes_to_uint64(hash(seed + uint_to_bytes(uint8(current_round)))[0:8]) % index_count - flip = (pivot + index_count - index) % index_count - position = max(index, flip) - source = hash( - seed - + uint_to_bytes(uint8(current_round)) - + uint_to_bytes(uint32(position // 256)) - ) - byte = uint8(source[(position % 256) // 8]) - bit = (byte >> (position % 8)) % 2 - index = flip if bit else index - - return index - - -def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex], seed: Bytes32) -> ValidatorIndex: - """ - Return from ``indices`` a random index sampled by effective balance. - """ - assert len(indices) > 0 - MAX_RANDOM_BYTE = 2**8 - 1 - i = uint64(0) - total = uint64(len(indices)) - while True: - candidate_index = indices[compute_shuffled_index(i % total, total, seed)] - random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] - effective_balance = state.validators[candidate_index].effective_balance - if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: - return candidate_index - i += 1 - - -def compute_committee(indices: Sequence[ValidatorIndex], - seed: Bytes32, - index: uint64, - count: uint64) -> Sequence[ValidatorIndex]: - """ - Return the committee corresponding to ``indices``, ``seed``, ``index``, and committee ``count``. - """ - start = (len(indices) * index) // count - end = (len(indices) * uint64(index + 1)) // count - return [indices[compute_shuffled_index(uint64(i), uint64(len(indices)), seed)] for i in range(start, end)] - - -def compute_epoch_at_slot(slot: Slot) -> Epoch: - """ - Return the epoch number at ``slot``. - """ - return Epoch(slot // SLOTS_PER_EPOCH) - - -def compute_start_slot_at_epoch(epoch: Epoch) -> Slot: - """ - Return the start slot of ``epoch``. - """ - return Slot(epoch * SLOTS_PER_EPOCH) - - -def compute_activation_exit_epoch(epoch: Epoch) -> Epoch: - """ - Return the epoch during which validator activations and exits initiated in ``epoch`` take effect. - """ - return Epoch(epoch + 1 + MAX_SEED_LOOKAHEAD) - - -def compute_fork_data_root(current_version: Version, genesis_validators_root: Root) -> Root: - """ - Return the 32-byte fork data root for the ``current_version`` and ``genesis_validators_root``. - This is used primarily in signature domains to avoid collisions across forks/chains. - """ - return hash_tree_root(ForkData( - current_version=current_version, - genesis_validators_root=genesis_validators_root, - )) - - -def compute_fork_digest(current_version: Version, genesis_validators_root: Root) -> ForkDigest: - """ - Return the 4-byte fork digest for the ``current_version`` and ``genesis_validators_root``. - This is a digest primarily used for domain separation on the p2p layer. - 4-bytes suffices for practical separation of forks/chains. - """ - return ForkDigest(compute_fork_data_root(current_version, genesis_validators_root)[:4]) - - -def compute_domain(domain_type: DomainType, fork_version: Version=None, genesis_validators_root: Root=None) -> Domain: - """ - Return the domain for the ``domain_type`` and ``fork_version``. - """ - if fork_version is None: - fork_version = config.GENESIS_FORK_VERSION - if genesis_validators_root is None: - genesis_validators_root = Root() # all bytes zero by default - fork_data_root = compute_fork_data_root(fork_version, genesis_validators_root) - return Domain(domain_type + fork_data_root[:28]) - - -def compute_signing_root(ssz_object: SSZObject, domain: Domain) -> Root: - """ - Return the signing root for the corresponding signing data. - """ - return hash_tree_root(SigningData( - object_root=hash_tree_root(ssz_object), - domain=domain, - )) - - -def get_current_epoch(state: BeaconState) -> Epoch: - """ - Return the current epoch. - """ - return compute_epoch_at_slot(state.slot) - - -def get_previous_epoch(state: BeaconState) -> Epoch: - """` - Return the previous epoch (unless the current epoch is ``GENESIS_EPOCH``). - """ - current_epoch = get_current_epoch(state) - return GENESIS_EPOCH if current_epoch == GENESIS_EPOCH else Epoch(current_epoch - 1) - - -def get_block_root(state: BeaconState, epoch: Epoch) -> Root: - """ - Return the block root at the start of a recent ``epoch``. - """ - return get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch)) - - -def get_block_root_at_slot(state: BeaconState, slot: Slot) -> Root: - """ - Return the block root at a recent ``slot``. - """ - assert slot < state.slot <= slot + SLOTS_PER_HISTORICAL_ROOT - return state.block_roots[slot % SLOTS_PER_HISTORICAL_ROOT] - - -def get_randao_mix(state: BeaconState, epoch: Epoch) -> Bytes32: - """ - Return the randao mix at a recent ``epoch``. - """ - return state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR] - - -def get_active_validator_indices(state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: - """ - Return the sequence of active validator indices at ``epoch``. - """ - return [ValidatorIndex(i) for i, v in enumerate(state.validators) if is_active_validator(v, epoch)] - - -def get_validator_churn_limit(state: BeaconState) -> uint64: - """ - Return the validator churn limit for the current epoch. - """ - active_validator_indices = get_active_validator_indices(state, get_current_epoch(state)) - return max(config.MIN_PER_EPOCH_CHURN_LIMIT, uint64(len(active_validator_indices)) // config.CHURN_LIMIT_QUOTIENT) - - -def get_seed(state: BeaconState, epoch: Epoch, domain_type: DomainType) -> Bytes32: - """ - Return the seed at ``epoch``. - """ - mix = get_randao_mix(state, Epoch(epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD - 1)) # Avoid underflow - return hash(domain_type + uint_to_bytes(epoch) + mix) - - -def get_committee_count_per_slot(state: BeaconState, epoch: Epoch) -> uint64: - """ - Return the number of committees in each slot for the given ``epoch``. - """ - return max(uint64(1), min( - MAX_COMMITTEES_PER_SLOT, - uint64(len(get_active_validator_indices(state, epoch))) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE, - )) - - -def get_beacon_committee(state: BeaconState, slot: Slot, index: CommitteeIndex) -> Sequence[ValidatorIndex]: - """ - Return the beacon committee at ``slot`` for ``index``. - """ - epoch = compute_epoch_at_slot(slot) - committees_per_slot = get_committee_count_per_slot(state, epoch) - return compute_committee( - indices=get_active_validator_indices(state, epoch), - seed=get_seed(state, epoch, DOMAIN_BEACON_ATTESTER), - index=(slot % SLOTS_PER_EPOCH) * committees_per_slot + index, - count=committees_per_slot * SLOTS_PER_EPOCH, - ) - - -def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: - """ - Return the beacon proposer index at the current slot. - """ - epoch = get_current_epoch(state) - seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + uint_to_bytes(state.slot)) - indices = get_active_validator_indices(state, epoch) - return compute_proposer_index(state, indices, seed) - - -def get_total_balance(state: BeaconState, indices: Set[ValidatorIndex]) -> Gwei: - """ - Return the combined effective balance of the ``indices``. - ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. - Math safe up to ~10B ETH, afterwhich this overflows uint64. - """ - return Gwei(max(EFFECTIVE_BALANCE_INCREMENT, sum([state.validators[index].effective_balance for index in indices]))) - - -def get_total_active_balance(state: BeaconState) -> Gwei: - """ - Return the combined effective balance of the active validators. - Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. - """ - return get_total_balance(state, set(get_active_validator_indices(state, get_current_epoch(state)))) - - -def get_domain(state: BeaconState, domain_type: DomainType, epoch: Epoch=None) -> Domain: - """ - Return the signature domain (fork version concatenated with domain type) of a message. - """ - epoch = get_current_epoch(state) if epoch is None else epoch - fork_version = state.fork.previous_version if epoch < state.fork.epoch else state.fork.current_version - return compute_domain(domain_type, fork_version, state.genesis_validators_root) - - -def get_indexed_attestation(state: BeaconState, attestation: Attestation) -> IndexedAttestation: - """ - Return the indexed attestation corresponding to ``attestation``. - """ - attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) - - return IndexedAttestation( - attesting_indices=sorted(attesting_indices), - data=attestation.data, - signature=attestation.signature, - ) - - -def get_attesting_indices(state: BeaconState, - data: AttestationData, - bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Set[ValidatorIndex]: - """ - Return the set of attesting indices corresponding to ``data`` and ``bits``. - """ - committee = get_beacon_committee(state, data.slot, data.index) - return set(index for i, index in enumerate(committee) if bits[i]) - - -def increase_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: - """ - Increase the validator balance at index ``index`` by ``delta``. - """ - state.balances[index] += delta - - -def decrease_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: - """ - Decrease the validator balance at index ``index`` by ``delta``, with underflow protection. - """ - state.balances[index] = 0 if delta > state.balances[index] else state.balances[index] - delta - - -def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: - """ - Initiate the exit of the validator with index ``index``. - """ - # Return if validator already initiated exit - validator = state.validators[index] - if validator.exit_epoch != FAR_FUTURE_EPOCH: - return - - # Compute exit queue epoch - exit_epochs = [v.exit_epoch for v in state.validators if v.exit_epoch != FAR_FUTURE_EPOCH] - exit_queue_epoch = max(exit_epochs + [compute_activation_exit_epoch(get_current_epoch(state))]) - exit_queue_churn = len([v for v in state.validators if v.exit_epoch == exit_queue_epoch]) - if exit_queue_churn >= get_validator_churn_limit(state): - exit_queue_epoch += Epoch(1) - - # Set validator exit epoch and withdrawable epoch - validator.exit_epoch = exit_queue_epoch - validator.withdrawable_epoch = Epoch(validator.exit_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY) - - -def slash_validator(state: BeaconState, - slashed_index: ValidatorIndex, - whistleblower_index: ValidatorIndex=None) -> None: - """ - Slash the validator with index ``slashed_index``. - """ - epoch = get_current_epoch(state) - initiate_validator_exit(state, slashed_index) - validator = state.validators[slashed_index] - validator.slashed = True - validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR)) - state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance - slashing_penalty = validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_MERGE # [Modified in Merge] - decrease_balance(state, slashed_index, slashing_penalty) - - # Apply proposer and whistleblower rewards - proposer_index = get_beacon_proposer_index(state) - if whistleblower_index is None: - whistleblower_index = proposer_index - whistleblower_reward = Gwei(validator.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT) - proposer_reward = Gwei(whistleblower_reward * PROPOSER_WEIGHT // WEIGHT_DENOMINATOR) - increase_balance(state, proposer_index, proposer_reward) - increase_balance(state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward)) - - -def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, - eth1_timestamp: uint64, - deposits: Sequence[Deposit], - execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader() - ) -> BeaconState: - fork = Fork( - previous_version=config.MERGE_FORK_VERSION, # [Modified in Merge] for testing only - current_version=config.MERGE_FORK_VERSION, # [Modified in Merge] - epoch=GENESIS_EPOCH, - ) - state = BeaconState( - genesis_time=eth1_timestamp + config.GENESIS_DELAY, - fork=fork, - eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))), - latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), - randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy - ) - - # Process deposits - leaves = list(map(lambda deposit: deposit.data, deposits)) - for index, deposit in enumerate(deposits): - deposit_data_list = List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[:index + 1]) - state.eth1_data.deposit_root = hash_tree_root(deposit_data_list) - process_deposit(state, deposit) - - # Process activations - for index, validator in enumerate(state.validators): - balance = state.balances[index] - validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) - if validator.effective_balance == MAX_EFFECTIVE_BALANCE: - validator.activation_eligibility_epoch = GENESIS_EPOCH - validator.activation_epoch = GENESIS_EPOCH - - # Set genesis validators root for domain separation and chain versioning - state.genesis_validators_root = hash_tree_root(state.validators) - - # Fill in sync committees - # Note: A duplicate committee is assigned for the current and next committee at genesis - state.current_sync_committee = get_next_sync_committee(state) - state.next_sync_committee = get_next_sync_committee(state) - - # [New in Merge] Initialize the execution payload header - # If empty, will initialize a chain that has not yet gone through the Merge transition - state.latest_execution_payload_header = execution_payload_header - - return state - - -def is_valid_genesis_state(state: BeaconState) -> bool: - if state.genesis_time < config.MIN_GENESIS_TIME: - return False - if len(get_active_validator_indices(state, GENESIS_EPOCH)) < config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: - return False - return True - - -def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool=True) -> None: - block = signed_block.message - # Process slots (including those with no blocks) since block - process_slots(state, block.slot) - # Verify signature - if validate_result: - assert verify_block_signature(state, signed_block) - # Process block - process_block(state, block) - # Verify state root - if validate_result: - assert block.state_root == hash_tree_root(state) - - -def verify_block_signature(state: BeaconState, signed_block: SignedBeaconBlock) -> bool: - proposer = state.validators[signed_block.message.proposer_index] - signing_root = compute_signing_root(signed_block.message, get_domain(state, DOMAIN_BEACON_PROPOSER)) - return bls.Verify(proposer.pubkey, signing_root, signed_block.signature) - - -def process_slots(state: BeaconState, slot: Slot) -> None: - assert state.slot < slot - while state.slot < slot: - process_slot(state) - # Process epoch on the start slot of the next epoch - if (state.slot + 1) % SLOTS_PER_EPOCH == 0: - process_epoch(state) - state.slot = Slot(state.slot + 1) - - -def process_slot(state: BeaconState) -> None: - # Cache state root - previous_state_root = hash_tree_root(state) - state.state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_state_root - # Cache latest block header state root - if state.latest_block_header.state_root == Bytes32(): - state.latest_block_header.state_root = previous_state_root - # Cache block root - previous_block_root = hash_tree_root(state.latest_block_header) - state.block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_block_root - - -def process_epoch(state: BeaconState) -> None: - process_justification_and_finalization(state) - process_inactivity_updates(state) - process_rewards_and_penalties(state) - process_registry_updates(state) - process_slashings(state) - process_eth1_data_reset(state) - process_effective_balance_updates(state) - process_slashings_reset(state) - process_randao_mixes_reset(state) - process_historical_roots_update(state) - process_participation_flag_updates(state) - process_sync_committee_updates(state) - process_withdrawals(state) # [New in Withdrawals] - - -def get_matching_source_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: - assert epoch in (get_previous_epoch(state), get_current_epoch(state)) - return state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations - - -def get_matching_target_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: - return [ - a for a in get_matching_source_attestations(state, epoch) - if a.data.target.root == get_block_root(state, epoch) - ] - - -def get_matching_head_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: - return [ - a for a in get_matching_target_attestations(state, epoch) - if a.data.beacon_block_root == get_block_root_at_slot(state, a.data.slot) - ] - - -def get_unslashed_attesting_indices(state: BeaconState, - attestations: Sequence[PendingAttestation]) -> Set[ValidatorIndex]: - output = set() # type: Set[ValidatorIndex] - for a in attestations: - output = output.union(get_attesting_indices(state, a.data, a.aggregation_bits)) - return set(filter(lambda index: not state.validators[index].slashed, output)) - - -def get_attesting_balance(state: BeaconState, attestations: Sequence[PendingAttestation]) -> Gwei: - """ - Return the combined effective balance of the set of unslashed validators participating in ``attestations``. - Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. - """ - return get_total_balance(state, get_unslashed_attesting_indices(state, attestations)) - - -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_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)) - current_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_current_epoch(state)) - total_active_balance = get_total_active_balance(state) - previous_target_balance = get_total_balance(state, previous_indices) - current_target_balance = get_total_balance(state, current_indices) - weigh_justification_and_finalization(state, total_active_balance, previous_target_balance, current_target_balance) - - -def weigh_justification_and_finalization(state: BeaconState, - total_active_balance: Gwei, - previous_epoch_target_balance: Gwei, - current_epoch_target_balance: Gwei) -> None: - 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 - - # 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 - if previous_epoch_target_balance * 3 >= total_active_balance * 2: - state.current_justified_checkpoint = Checkpoint(epoch=previous_epoch, - root=get_block_root(state, previous_epoch)) - state.justification_bits[1] = 0b1 - if current_epoch_target_balance * 3 >= total_active_balance * 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 - - -def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: - """ - Return the base reward for the validator defined by ``index`` with respect to the current ``state``. - """ - increments = state.validators[index].effective_balance // EFFECTIVE_BALANCE_INCREMENT - return Gwei(increments * get_base_reward_per_increment(state)) - - -def get_proposer_reward(state: BeaconState, attesting_index: ValidatorIndex) -> Gwei: - return Gwei(get_base_reward(state, attesting_index) // PROPOSER_REWARD_QUOTIENT) - - -def get_finality_delay(state: BeaconState) -> uint64: - return get_previous_epoch(state) - state.finalized_checkpoint.epoch - - -def is_in_inactivity_leak(state: BeaconState) -> bool: - return get_finality_delay(state) > MIN_EPOCHS_TO_INACTIVITY_PENALTY - - -def get_eligible_validator_indices(state: BeaconState) -> Sequence[ValidatorIndex]: - previous_epoch = get_previous_epoch(state) - 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) - ] - - -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)] * 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) - 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 - if is_in_inactivity_leak(state): - # Since full base reward will be canceled out by inactivity penalty deltas, - # optimal participation receives full base reward compensation here. - rewards[index] += get_base_reward(state, index) - else: - 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 - - -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 get_attestation_component_deltas(state, matching_source_attestations) - - -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 get_attestation_component_deltas(state, matching_target_attestations) - - -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 get_attestation_component_deltas(state, matching_head_attestations) - - -def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: - """ - Return proposer and inclusion delay micro-rewards/penalties for each validator. - """ - rewards = [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 - if index in get_attesting_indices(state, a.data, a.aggregation_bits) - ], key=lambda a: a.inclusion_delay) - rewards[attestation.proposer_index] += get_proposer_reward(state, index) - max_attester_reward = Gwei(get_base_reward(state, index) - get_proposer_reward(state, index)) - 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 - - -def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: - """ - Return the inactivity penalty deltas by considering timely target participation flags and inactivity scores. - """ - rewards = [Gwei(0) for _ in range(len(state.validators))] - penalties = [Gwei(0) for _ in range(len(state.validators))] - previous_epoch = get_previous_epoch(state) - matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, previous_epoch) - for index in get_eligible_validator_indices(state): - if index not in matching_target_indices: - penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index] - # [Modified in Merge] - penalty_denominator = config.INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_MERGE - penalties[index] += Gwei(penalty_numerator // penalty_denominator) - return rewards, penalties - - -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) - 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 - - -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_index_deltas(state, flag_index) for flag_index in range(len(PARTICIPATION_FLAG_WEIGHTS))] - 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]) - - -def process_registry_updates(state: BeaconState) -> None: - # Process activation eligibility and ejections - for index, validator in enumerate(state.validators): - if is_eligible_for_activation_queue(validator): - validator.activation_eligibility_epoch = get_current_epoch(state) + 1 - - if ( - is_active_validator(validator, get_current_epoch(state)) - and validator.effective_balance <= config.EJECTION_BALANCE - ): - initiate_validator_exit(state, ValidatorIndex(index)) - - # Queue validators eligible for activation and not yet dequeued for activation - activation_queue = sorted([ - index for index, validator in enumerate(state.validators) - if is_eligible_for_activation(state, validator) - # Order by the sequence of activation_eligibility_epoch setting and then index - ], key=lambda index: (state.validators[index].activation_eligibility_epoch, index)) - # Dequeued validators for activation up to churn limit - for index in activation_queue[:get_validator_churn_limit(state)]: - validator = state.validators[index] - validator.activation_epoch = compute_activation_exit_epoch(get_current_epoch(state)) - - -def process_slashings(state: BeaconState) -> None: - epoch = get_current_epoch(state) - total_balance = get_total_active_balance(state) - adjusted_total_slashing_balance = min( - sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_MERGE, # [Modified in Merge] - total_balance - ) - for index, validator in enumerate(state.validators): - if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch: - increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow - penalty_numerator = validator.effective_balance // increment * adjusted_total_slashing_balance - penalty = penalty_numerator // total_balance * increment - decrease_balance(state, ValidatorIndex(index), penalty) - - -def process_eth1_data_reset(state: BeaconState) -> None: - next_epoch = Epoch(get_current_epoch(state) + 1) - # Reset eth1 data votes - if next_epoch % EPOCHS_PER_ETH1_VOTING_PERIOD == 0: - state.eth1_data_votes = [] - - -def process_effective_balance_updates(state: BeaconState) -> None: - # Update effective balances with hysteresis - for index, validator in enumerate(state.validators): - balance = state.balances[index] - HYSTERESIS_INCREMENT = uint64(EFFECTIVE_BALANCE_INCREMENT // HYSTERESIS_QUOTIENT) - DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER - UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER - if ( - balance + DOWNWARD_THRESHOLD < validator.effective_balance - or validator.effective_balance + UPWARD_THRESHOLD < balance - ): - validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) - - -def process_slashings_reset(state: BeaconState) -> None: - next_epoch = Epoch(get_current_epoch(state) + 1) - # Reset slashings - state.slashings[next_epoch % EPOCHS_PER_SLASHINGS_VECTOR] = Gwei(0) - - -def process_randao_mixes_reset(state: BeaconState) -> None: - current_epoch = get_current_epoch(state) - next_epoch = Epoch(current_epoch + 1) - # Set randao mix - state.randao_mixes[next_epoch % EPOCHS_PER_HISTORICAL_VECTOR] = get_randao_mix(state, current_epoch) - - -def process_historical_roots_update(state: BeaconState) -> None: - # Set historical root accumulator - next_epoch = Epoch(get_current_epoch(state) + 1) - if next_epoch % (SLOTS_PER_HISTORICAL_ROOT // SLOTS_PER_EPOCH) == 0: - historical_batch = HistoricalBatch(block_roots=state.block_roots, state_roots=state.state_roots) - state.historical_roots.append(hash_tree_root(historical_batch)) - - -def process_participation_record_updates(state: BeaconState) -> None: - # Rotate current/previous epoch attestations - state.previous_epoch_attestations = state.current_epoch_attestations - state.current_epoch_attestations = [] - - -def process_block(state: BeaconState, block: BeaconBlock) -> None: - process_block_header(state, block) - if is_execution_enabled(state, block.body): - process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [New in Merge] - process_randao(state, block.body) - process_eth1_data(state, block.body) - process_operations(state, block.body) - process_sync_aggregate(state, block.body.sync_aggregate) - - -def process_block_header(state: BeaconState, block: BeaconBlock) -> None: - # Verify that the slots match - assert block.slot == state.slot - # Verify that the block is newer than latest block header - assert block.slot > state.latest_block_header.slot - # Verify that proposer index is the correct index - assert block.proposer_index == get_beacon_proposer_index(state) - # Verify that the parent matches - assert block.parent_root == hash_tree_root(state.latest_block_header) - # Cache current block as the new latest block - state.latest_block_header = BeaconBlockHeader( - slot=block.slot, - proposer_index=block.proposer_index, - parent_root=block.parent_root, - state_root=Bytes32(), # Overwritten in the next process_slot call - body_root=hash_tree_root(block.body), - ) - - # Verify proposer is not slashed - proposer = state.validators[block.proposer_index] - assert not proposer.slashed - - -def process_randao(state: BeaconState, body: BeaconBlockBody) -> None: - epoch = get_current_epoch(state) - # Verify RANDAO reveal - proposer = state.validators[get_beacon_proposer_index(state)] - signing_root = compute_signing_root(epoch, get_domain(state, DOMAIN_RANDAO)) - assert bls.Verify(proposer.pubkey, signing_root, body.randao_reveal) - # Mix in RANDAO reveal - mix = xor(get_randao_mix(state, epoch), hash(body.randao_reveal)) - state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR] = mix - - -def process_eth1_data(state: BeaconState, body: BeaconBlockBody) -> None: - state.eth1_data_votes.append(body.eth1_data) - if state.eth1_data_votes.count(body.eth1_data) * 2 > EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH: - state.eth1_data = body.eth1_data - - -def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: - # Verify that outstanding deposits are processed up to the maximum number of deposits - assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index) - - def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: - for operation in operations: - fn(state, operation) - - for_ops(body.proposer_slashings, process_proposer_slashing) - for_ops(body.attester_slashings, process_attester_slashing) - for_ops(body.attestations, process_attestation) - for_ops(body.deposits, process_deposit) - for_ops(body.voluntary_exits, process_voluntary_exit) - - -def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None: - header_1 = proposer_slashing.signed_header_1.message - header_2 = proposer_slashing.signed_header_2.message - - # Verify header slots match - assert header_1.slot == header_2.slot - # Verify header proposer indices match - assert header_1.proposer_index == header_2.proposer_index - # Verify the headers are different - assert header_1 != header_2 - # Verify the proposer is slashable - proposer = state.validators[header_1.proposer_index] - assert is_slashable_validator(proposer, get_current_epoch(state)) - # Verify signatures - for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2): - domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(signed_header.message.slot)) - signing_root = compute_signing_root(signed_header.message, domain) - assert bls.Verify(proposer.pubkey, signing_root, signed_header.signature) - - slash_validator(state, header_1.proposer_index) - - -def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSlashing) -> None: - attestation_1 = attester_slashing.attestation_1 - attestation_2 = attester_slashing.attestation_2 - assert is_slashable_attestation_data(attestation_1.data, attestation_2.data) - assert is_valid_indexed_attestation(state, attestation_1) - assert is_valid_indexed_attestation(state, attestation_2) - - slashed_any = False - indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices) - for index in sorted(indices): - if is_slashable_validator(state.validators[index], get_current_epoch(state)): - slash_validator(state, index) - slashed_any = True - assert slashed_any - - -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) - - # Participation flag indices - participation_flag_indices = get_attestation_participation_flag_indices(state, data, state.slot - data.slot) - - # Verify signature - assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) - - # Update epoch participation flags - if data.target.epoch == get_current_epoch(state): - epoch_participation = state.current_epoch_participation - else: - epoch_participation = state.previous_epoch_participation - - proposer_reward_numerator = 0 - for index in get_attesting_indices(state, data, attestation.aggregation_bits): - for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS): - if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index): - epoch_participation[index] = add_flag(epoch_participation[index], flag_index) - proposer_reward_numerator += get_base_reward(state, index) * weight - - # Reward proposer - proposer_reward_denominator = (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT - proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator) - increase_balance(state, get_beacon_proposer_index(state), proposer_reward) - - -def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: - amount = deposit.data.amount - effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) - - return Validator( - pubkey=deposit.data.pubkey, - withdrawal_credentials=deposit.data.withdrawal_credentials, - activation_eligibility_epoch=FAR_FUTURE_EPOCH, - activation_epoch=FAR_FUTURE_EPOCH, - exit_epoch=FAR_FUTURE_EPOCH, - withdrawable_epoch=FAR_FUTURE_EPOCH, - effective_balance=effective_balance, - ) - - -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 = [validator.pubkey for validator 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) - # Initialize validator if the deposit signature is valid - if bls.Verify(pubkey, signing_root, deposit.data.signature): - state.validators.append(get_validator_from_deposit(state, deposit)) - state.balances.append(amount) - state.previous_epoch_participation.append(ParticipationFlags(0b0000_0000)) - state.current_epoch_participation.append(ParticipationFlags(0b0000_0000)) - state.inactivity_scores.append(uint64(0)) - else: - # Increase balance by deposit amount - index = ValidatorIndex(validator_pubkeys.index(pubkey)) - increase_balance(state, index, amount) - - -def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVoluntaryExit) -> None: - voluntary_exit = signed_voluntary_exit.message - validator = state.validators[voluntary_exit.validator_index] - # Verify the validator is active - assert is_active_validator(validator, get_current_epoch(state)) - # Verify exit has not been initiated - assert validator.exit_epoch == FAR_FUTURE_EPOCH - # Exits must specify an epoch when they become valid; they are not valid before then - assert get_current_epoch(state) >= voluntary_exit.epoch - # Verify the validator has been active long enough - assert get_current_epoch(state) >= validator.activation_epoch + config.SHARD_COMMITTEE_PERIOD - # Verify signature - domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch) - signing_root = compute_signing_root(voluntary_exit, domain) - assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature) - # Initiate exit - initiate_validator_exit(state, voluntary_exit.validator_index) - - -def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store: - assert anchor_block.state_root == hash_tree_root(anchor_state) - anchor_root = hash_tree_root(anchor_block) - anchor_epoch = get_current_epoch(anchor_state) - justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) - finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) - proposer_boost_root = Root() - return Store( - time=uint64(anchor_state.genesis_time + config.SECONDS_PER_SLOT * anchor_state.slot), - genesis_time=anchor_state.genesis_time, - justified_checkpoint=justified_checkpoint, - finalized_checkpoint=finalized_checkpoint, - best_justified_checkpoint=justified_checkpoint, - proposer_boost_root=proposer_boost_root, - blocks={anchor_root: copy(anchor_block)}, - block_states={anchor_root: copy(anchor_state)}, - checkpoint_states={justified_checkpoint: copy(anchor_state)}, - ) - - -def get_slots_since_genesis(store: Store) -> int: - return (store.time - store.genesis_time) // config.SECONDS_PER_SLOT - - -def get_current_slot(store: Store) -> Slot: - return Slot(GENESIS_SLOT + get_slots_since_genesis(store)) - - -def compute_slots_since_epoch_start(slot: Slot) -> int: - return slot - compute_start_slot_at_epoch(compute_epoch_at_slot(slot)) - - -def get_ancestor(store: Store, root: Root, slot: Slot) -> Root: - block = store.blocks[root] - if block.slot > slot: - return get_ancestor(store, block.parent_root, slot) - elif block.slot == slot: - return root - else: - # root is older than queried slot, thus a skip slot. Return most recent root prior to slot - return root - - -def get_latest_attesting_balance(store: Store, root: Root) -> Gwei: - state = store.checkpoint_states[store.justified_checkpoint] - active_indices = get_active_validator_indices(state, get_current_epoch(state)) - attestation_score = Gwei(sum( - state.validators[i].effective_balance for i in active_indices - if (i in store.latest_messages - and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root) - )) - proposer_score = Gwei(0) - if store.proposer_boost_root != Root(): - block = store.blocks[root] - if get_ancestor(store, root, block.slot) == store.proposer_boost_root: - num_validators = len(get_active_validator_indices(state, get_current_epoch(state))) - avg_balance = get_total_active_balance(state) // num_validators - committee_size = num_validators // SLOTS_PER_EPOCH - committee_weight = committee_size * avg_balance - proposer_score = (committee_weight * config.PROPOSER_SCORE_BOOST) // 100 - return attestation_score + proposer_score - - -def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconBlock]) -> bool: - block = store.blocks[block_root] - children = [ - root for root in store.blocks.keys() - if store.blocks[root].parent_root == block_root - ] - - # If any children branches contain expected finalized/justified checkpoints, - # add to filtered block-tree and signal viability to parent. - if any(children): - filter_block_tree_result = [filter_block_tree(store, child, blocks) for child in children] - if any(filter_block_tree_result): - blocks[block_root] = block - return True - return False - - # If leaf block, check finalized/justified checkpoints as matching latest. - head_state = store.block_states[block_root] - - correct_justified = ( - store.justified_checkpoint.epoch == GENESIS_EPOCH - or head_state.current_justified_checkpoint == store.justified_checkpoint - ) - correct_finalized = ( - store.finalized_checkpoint.epoch == GENESIS_EPOCH - or head_state.finalized_checkpoint == store.finalized_checkpoint - ) - # If expected finalized/justified, add to viable block-tree and signal viability to parent. - if correct_justified and correct_finalized: - blocks[block_root] = block - return True - - # Otherwise, branch not viable - return False - - -def get_filtered_block_tree(store: Store) -> Dict[Root, BeaconBlock]: - """ - Retrieve a filtered block tree from ``store``, only returning branches - whose leaf state's justified/finalized info agrees with that in ``store``. - """ - base = store.justified_checkpoint.root - blocks: Dict[Root, BeaconBlock] = {} - filter_block_tree(store, base, blocks) - return blocks - - -def get_head(store: Store) -> Root: - # Get filtered block tree that only includes viable branches - blocks = get_filtered_block_tree(store) - # Execute the LMD-GHOST fork choice - head = store.justified_checkpoint.root - while True: - children = [ - root for root in blocks.keys() - if blocks[root].parent_root == head - ] - if len(children) == 0: - return head - # Sort by latest attesting balance with ties broken lexicographically - head = max(children, key=lambda root: (get_latest_attesting_balance(store, root), root)) - - -def should_update_justified_checkpoint(store: Store, new_justified_checkpoint: Checkpoint) -> bool: - """ - To address the bouncing attack, only update conflicting justified - checkpoints in the fork choice if in the early slots of the epoch. - Otherwise, delay incorporation of new justified checkpoint until next epoch boundary. - - See https://ethresear.ch/t/prevention-of-bouncing-attack-on-ffg/6114 for more detailed analysis and discussion. - """ - if compute_slots_since_epoch_start(get_current_slot(store)) < SAFE_SLOTS_TO_UPDATE_JUSTIFIED: - return True - - justified_slot = compute_start_slot_at_epoch(store.justified_checkpoint.epoch) - if not get_ancestor(store, new_justified_checkpoint.root, justified_slot) == store.justified_checkpoint.root: - return False - - return True - - -def validate_target_epoch_against_current_time(store: Store, attestation: Attestation) -> None: - target = attestation.data.target - - # Attestations must be from the current or previous epoch - 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] - - -def validate_on_attestation(store: Store, attestation: Attestation, is_from_block: bool) -> None: - target = attestation.data.target - - # If the given attestation is not from a beacon block message, we have to check the target epoch scope. - if not is_from_block: - validate_target_epoch_against_current_time(store, attestation) - - # Check that the epoch number and slot number are matching - assert target.epoch == compute_epoch_at_slot(attestation.data.slot) - - # 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 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 - # Attestations must not be for blocks in the future. If not, the attestation should not be considered - assert store.blocks[attestation.data.beacon_block_root].slot <= attestation.data.slot - - # LMD vote must be consistent with FFG vote target - target_slot = compute_start_slot_at_epoch(target.epoch) - assert target.root == get_ancestor(store, attestation.data.beacon_block_root, target_slot) - - # Attestations can only affect the fork choice of subsequent slots. - # Delay consideration in the fork choice until their slot is in the past. - assert get_current_slot(store) >= attestation.data.slot + 1 - - -def store_target_checkpoint_state(store: Store, target: Checkpoint) -> None: - # Store target checkpoint state if not yet seen - if target not in store.checkpoint_states: - base_state = copy(store.block_states[target.root]) - if base_state.slot < compute_start_slot_at_epoch(target.epoch): - process_slots(base_state, compute_start_slot_at_epoch(target.epoch)) - store.checkpoint_states[target] = base_state - - -def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None: - target = attestation.data.target - beacon_block_root = attestation.data.beacon_block_root - for i in attesting_indices: - if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch: - store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root) - - -def on_tick(store: Store, time: uint64) -> None: - previous_slot = get_current_slot(store) - - # update store time - store.time = time - - current_slot = get_current_slot(store) - - # Reset store.proposer_boost_root if this is a new slot - if current_slot > previous_slot: - store.proposer_boost_root = Root() - - # Not a new epoch, return - if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0): - return - - # Update store.justified_checkpoint if a better checkpoint on the store.finalized_checkpoint chain - if store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch: - finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) - ancestor_at_finalized_slot = get_ancestor(store, store.best_justified_checkpoint.root, finalized_slot) - if ancestor_at_finalized_slot == store.finalized_checkpoint.root: - store.justified_checkpoint = store.best_justified_checkpoint - - -def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: - """ - Run ``on_block`` upon receiving a new block. - - A block that is asserted as invalid due to unavailable PoW block may be valid at a later time, - consider scheduling it for later processing in such case. - """ - block = signed_block.message - # Parent block must be known - assert block.parent_root in store.block_states - # Make a copy of the state to avoid mutability issues - pre_state = copy(store.block_states[block.parent_root]) - # Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past. - assert get_current_slot(store) >= block.slot - - # Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) - finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) - assert block.slot > finalized_slot - # Check block is a descendant of the finalized block at the checkpoint finalized slot - assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root - - # Check the block is valid and compute the post-state - state = pre_state.copy() - state_transition(state, signed_block, True) - - # [New in Merge] - if is_merge_transition_block(pre_state, block.body): - validate_merge_block(block) - - # Add new block to the store - store.blocks[hash_tree_root(block)] = block - # Add new state for this block to the store - store.block_states[hash_tree_root(block)] = state - - # Add proposer score boost if the block is timely - time_into_slot = (store.time - store.genesis_time) % config.SECONDS_PER_SLOT - is_before_attesting_interval = time_into_slot < config.SECONDS_PER_SLOT // INTERVALS_PER_SLOT - if get_current_slot(store) == block.slot and is_before_attesting_interval: - store.proposer_boost_root = hash_tree_root(block) - - # Update justified checkpoint - if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: - if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch: - store.best_justified_checkpoint = state.current_justified_checkpoint - if should_update_justified_checkpoint(store, state.current_justified_checkpoint): - store.justified_checkpoint = state.current_justified_checkpoint - - # Update finalized checkpoint - if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch: - store.finalized_checkpoint = state.finalized_checkpoint - store.justified_checkpoint = state.current_justified_checkpoint - - -def on_attestation(store: Store, attestation: Attestation, is_from_block: bool=False) -> None: - """ - Run ``on_attestation`` upon receiving a new ``attestation`` from either within a block or directly on the wire. - - An ``attestation`` that is asserted as invalid may be valid at a later time, - consider scheduling it for later processing in such case. - """ - validate_on_attestation(store, attestation, is_from_block) - - store_target_checkpoint_state(store, attestation.data.target) - - # Get state at the `target` to fully validate attestation - target_state = store.checkpoint_states[attestation.data.target] - indexed_attestation = get_indexed_attestation(target_state, attestation) - assert is_valid_indexed_attestation(target_state, indexed_attestation) - - # Update latest messages for attesting indices - update_latest_messages(store, indexed_attestation.attesting_indices, attestation) - - -def check_if_validator_active(state: BeaconState, validator_index: ValidatorIndex) -> bool: - validator = state.validators[validator_index] - return is_active_validator(validator, get_current_epoch(state)) - - -def get_committee_assignment(state: BeaconState, - epoch: Epoch, - validator_index: ValidatorIndex - ) -> Optional[Tuple[Sequence[ValidatorIndex], CommitteeIndex, Slot]]: - """ - Return the committee assignment in the ``epoch`` for ``validator_index``. - ``assignment`` returned is a tuple of the following form: - * ``assignment[0]`` is the list of validators in the committee - * ``assignment[1]`` is the index to which the committee is assigned - * ``assignment[2]`` is the slot at which the committee is assigned - Return None if no assignment. - """ - next_epoch = Epoch(get_current_epoch(state) + 1) - assert epoch <= next_epoch - - start_slot = compute_start_slot_at_epoch(epoch) - committee_count_per_slot = get_committee_count_per_slot(state, epoch) - for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH): - for index in range(committee_count_per_slot): - committee = get_beacon_committee(state, Slot(slot), CommitteeIndex(index)) - if validator_index in committee: - return committee, CommitteeIndex(index), Slot(slot) - return None - - -def is_proposer(state: BeaconState, validator_index: ValidatorIndex) -> bool: - return get_beacon_proposer_index(state) == validator_index - - -def get_epoch_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_RANDAO, compute_epoch_at_slot(block.slot)) - signing_root = compute_signing_root(compute_epoch_at_slot(block.slot), domain) - return bls.Sign(privkey, signing_root) - - -def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64: - return uint64(state.genesis_time + slot * config.SECONDS_PER_SLOT) - - -def voting_period_start_time(state: BeaconState) -> uint64: - eth1_voting_period_start_slot = Slot(state.slot - state.slot % (EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH)) - return compute_time_at_slot(state, eth1_voting_period_start_slot) - - -def is_candidate_block(block: Eth1Block, period_start: uint64) -> bool: - return ( - block.timestamp + config.SECONDS_PER_ETH1_BLOCK * config.ETH1_FOLLOW_DISTANCE <= period_start - and block.timestamp + config.SECONDS_PER_ETH1_BLOCK * config.ETH1_FOLLOW_DISTANCE * 2 >= period_start - ) - - -def get_eth1_vote(state: BeaconState, eth1_chain: Sequence[Eth1Block]) -> Eth1Data: - period_start = voting_period_start_time(state) - # `eth1_chain` abstractly represents all blocks in the eth1 chain sorted by ascending block height - votes_to_consider = [ - get_eth1_data(block) for block in eth1_chain - if ( - is_candidate_block(block, period_start) - # Ensure cannot move back to earlier deposit contract states - and get_eth1_data(block).deposit_count >= state.eth1_data.deposit_count - ) - ] - - # Valid votes already cast during this period - valid_votes = [vote for vote in state.eth1_data_votes if vote in votes_to_consider] - - # Default vote on latest eth1 block data in the period range unless eth1 chain is not live - # Non-substantive casting for linter - state_eth1_data: Eth1Data = state.eth1_data - default_vote = votes_to_consider[len(votes_to_consider) - 1] if any(votes_to_consider) else state_eth1_data - - return max( - valid_votes, - key=lambda v: (valid_votes.count(v), -valid_votes.index(v)), # Tiebreak by smallest distance - default=default_vote - ) - - -def compute_new_state_root(state: BeaconState, block: BeaconBlock) -> Root: - temp_state: BeaconState = state.copy() - signed_block = SignedBeaconBlock(message=block) - state_transition(temp_state, signed_block, validate_result=False) - return hash_tree_root(temp_state) - - -def get_block_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(block.slot)) - signing_root = compute_signing_root(block, domain) - return bls.Sign(privkey, signing_root) - - -def get_attestation_signature(state: BeaconState, attestation_data: AttestationData, privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch) - signing_root = compute_signing_root(attestation_data, domain) - return bls.Sign(privkey, signing_root) - - -def compute_subnet_for_attestation(committees_per_slot: uint64, slot: Slot, committee_index: CommitteeIndex) -> uint64: - """ - Compute the correct subnet for an attestation for Phase 0. - Note, this mimics expected future behavior where attestations will be mapped to their shard subnet. - """ - slots_since_epoch_start = uint64(slot % SLOTS_PER_EPOCH) - committees_since_epoch_start = committees_per_slot * slots_since_epoch_start - - return uint64((committees_since_epoch_start + committee_index) % ATTESTATION_SUBNET_COUNT) - - -def get_slot_signature(state: BeaconState, slot: Slot, privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_SELECTION_PROOF, compute_epoch_at_slot(slot)) - signing_root = compute_signing_root(slot, domain) - return bls.Sign(privkey, signing_root) - - -def is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex, slot_signature: BLSSignature) -> bool: - committee = get_beacon_committee(state, slot, index) - modulo = max(1, len(committee) // TARGET_AGGREGATORS_PER_COMMITTEE) - return bytes_to_uint64(hash(slot_signature)[0:8]) % modulo == 0 - - -def get_aggregate_signature(attestations: Sequence[Attestation]) -> BLSSignature: - signatures = [attestation.signature for attestation in attestations] - return bls.Aggregate(signatures) - - -def get_aggregate_and_proof(state: BeaconState, - aggregator_index: ValidatorIndex, - aggregate: Attestation, - privkey: int) -> AggregateAndProof: - return AggregateAndProof( - aggregator_index=aggregator_index, - aggregate=aggregate, - selection_proof=get_slot_signature(state, aggregate.data.slot, privkey), - ) - - -def get_aggregate_and_proof_signature(state: BeaconState, - aggregate_and_proof: AggregateAndProof, - privkey: int) -> BLSSignature: - aggregate = aggregate_and_proof.aggregate - domain = get_domain(state, DOMAIN_AGGREGATE_AND_PROOF, compute_epoch_at_slot(aggregate.data.slot)) - signing_root = compute_signing_root(aggregate_and_proof, domain) - return bls.Sign(privkey, signing_root) - - -def compute_weak_subjectivity_period(state: BeaconState) -> uint64: - """ - Returns the weak subjectivity period for the current ``state``. - This computation takes into account the effect of: - - validator set churn (bounded by ``get_validator_churn_limit()`` per epoch), and - - validator balance top-ups (bounded by ``MAX_DEPOSITS * SLOTS_PER_EPOCH`` per epoch). - A detailed calculation can be found at: - https://github.com/runtimeverification/beacon-chain-verification/blob/master/weak-subjectivity/weak-subjectivity-analysis.pdf - """ - ws_period = config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY - N = len(get_active_validator_indices(state, get_current_epoch(state))) - t = get_total_active_balance(state) // N // ETH_TO_GWEI - T = MAX_EFFECTIVE_BALANCE // ETH_TO_GWEI - delta = get_validator_churn_limit(state) - Delta = MAX_DEPOSITS * SLOTS_PER_EPOCH - D = SAFETY_DECAY - - if T * (200 + 3 * D) < t * (200 + 12 * D): - epochs_for_validator_set_churn = ( - N * (t * (200 + 12 * D) - T * (200 + 3 * D)) // (600 * delta * (2 * t + T)) - ) - epochs_for_balance_top_ups = ( - N * (200 + 3 * D) // (600 * Delta) - ) - ws_period += max(epochs_for_validator_set_churn, epochs_for_balance_top_ups) - else: - ws_period += ( - 3 * N * D * t // (200 * Delta * (T - t)) - ) - - return ws_period - - -def is_within_weak_subjectivity_period(store: Store, ws_state: BeaconState, ws_checkpoint: Checkpoint) -> bool: - # Clients may choose to validate the input state against the input Weak Subjectivity Checkpoint - assert ws_state.latest_block_header.state_root == ws_checkpoint.root - assert compute_epoch_at_slot(ws_state.slot) == ws_checkpoint.epoch - - ws_period = compute_weak_subjectivity_period(ws_state) - ws_state_epoch = compute_epoch_at_slot(ws_state.slot) - current_epoch = compute_epoch_at_slot(get_current_slot(store)) - return current_epoch <= ws_state_epoch + ws_period - - -def add_flag(flags: ParticipationFlags, flag_index: int) -> ParticipationFlags: - """ - Return a new ``ParticipationFlags`` adding ``flag_index`` to ``flags``. - """ - flag = ParticipationFlags(2**flag_index) - return flags | flag - - -def has_flag(flags: ParticipationFlags, flag_index: int) -> bool: - """ - Return whether ``flags`` has ``flag_index`` set. - """ - flag = ParticipationFlags(2**flag_index) - return flags & flag == flag - - -def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorIndex]: - """ - Return the sync committee indices, with possible duplicates, for the next sync committee. - """ - epoch = Epoch(get_current_epoch(state) + 1) - - MAX_RANDOM_BYTE = 2**8 - 1 - active_validator_indices = get_active_validator_indices(state, epoch) - active_validator_count = uint64(len(active_validator_indices)) - seed = get_seed(state, epoch, DOMAIN_SYNC_COMMITTEE) - i = 0 - sync_committee_indices: List[ValidatorIndex] = [] - while len(sync_committee_indices) < SYNC_COMMITTEE_SIZE: - shuffled_index = compute_shuffled_index(uint64(i % active_validator_count), active_validator_count, seed) - candidate_index = active_validator_indices[shuffled_index] - random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] - effective_balance = state.validators[candidate_index].effective_balance - if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: - sync_committee_indices.append(candidate_index) - i += 1 - return sync_committee_indices - - -def get_next_sync_committee(state: BeaconState) -> SyncCommittee: - """ - Return the next sync committee, with possible pubkey duplicates. - """ - indices = get_next_sync_committee_indices(state) - pubkeys = [state.validators[index].pubkey for index in indices] - aggregate_pubkey = eth_aggregate_pubkeys(pubkeys) - return SyncCommittee(pubkeys=pubkeys, aggregate_pubkey=aggregate_pubkey) - - -def get_base_reward_per_increment(state: BeaconState) -> Gwei: - return Gwei(EFFECTIVE_BALANCE_INCREMENT * BASE_REWARD_FACTOR // integer_squareroot(get_total_active_balance(state))) - - -def get_unslashed_participating_indices(state: BeaconState, flag_index: int, epoch: Epoch) -> Set[ValidatorIndex]: - """ - Return the set of validator indices that are both active and unslashed for the given ``flag_index`` and ``epoch``. - """ - 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 - active_validator_indices = get_active_validator_indices(state, epoch) - participating_indices = [i for i in active_validator_indices if has_flag(epoch_participation[i], flag_index)] - return set(filter(lambda index: not state.validators[index].slashed, participating_indices)) - - -def get_attestation_participation_flag_indices(state: BeaconState, - data: AttestationData, - inclusion_delay: uint64) -> Sequence[int]: - """ - Return the flag indices that are satisfied by an attestation. - """ - if data.target.epoch == get_current_epoch(state): - justified_checkpoint = state.current_justified_checkpoint - else: - justified_checkpoint = state.previous_justified_checkpoint - - # Matching roots - is_matching_source = data.source == justified_checkpoint - is_matching_target = is_matching_source and data.target.root == get_block_root(state, data.target.epoch) - is_matching_head = is_matching_target and data.beacon_block_root == get_block_root_at_slot(state, data.slot) - assert is_matching_source - - participation_flag_indices = [] - if is_matching_source and inclusion_delay <= integer_squareroot(SLOTS_PER_EPOCH): - participation_flag_indices.append(TIMELY_SOURCE_FLAG_INDEX) - if is_matching_target and inclusion_delay <= SLOTS_PER_EPOCH: - participation_flag_indices.append(TIMELY_TARGET_FLAG_INDEX) - if is_matching_head and inclusion_delay == MIN_ATTESTATION_INCLUSION_DELAY: - participation_flag_indices.append(TIMELY_HEAD_FLAG_INDEX) - - return participation_flag_indices - - -def get_flag_index_deltas(state: BeaconState, flag_index: int) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: - """ - Return the deltas for a given ``flag_index`` by scanning through the participation flags. - """ - rewards = [Gwei(0)] * len(state.validators) - penalties = [Gwei(0)] * len(state.validators) - previous_epoch = get_previous_epoch(state) - unslashed_participating_indices = get_unslashed_participating_indices(state, flag_index, previous_epoch) - weight = PARTICIPATION_FLAG_WEIGHTS[flag_index] - unslashed_participating_balance = get_total_balance(state, unslashed_participating_indices) - unslashed_participating_increments = unslashed_participating_balance // EFFECTIVE_BALANCE_INCREMENT - active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT - for index in get_eligible_validator_indices(state): - base_reward = get_base_reward(state, index) - if index in unslashed_participating_indices: - if not is_in_inactivity_leak(state): - reward_numerator = base_reward * weight * unslashed_participating_increments - rewards[index] += Gwei(reward_numerator // (active_increments * WEIGHT_DENOMINATOR)) - elif flag_index != TIMELY_HEAD_FLAG_INDEX: - penalties[index] += Gwei(base_reward * weight // WEIGHT_DENOMINATOR) - return rewards, penalties - - -def process_sync_aggregate(state: BeaconState, sync_aggregate: SyncAggregate) -> None: - # Verify sync committee aggregate signature signing over the previous slot block root - committee_pubkeys = state.current_sync_committee.pubkeys - participant_pubkeys = [pubkey for pubkey, bit in zip(committee_pubkeys, sync_aggregate.sync_committee_bits) if bit] - previous_slot = max(state.slot, Slot(1)) - Slot(1) - domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, compute_epoch_at_slot(previous_slot)) - signing_root = compute_signing_root(get_block_root_at_slot(state, previous_slot), domain) - assert eth_fast_aggregate_verify(participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature) - - # Compute participant and proposer rewards - total_active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT - total_base_rewards = Gwei(get_base_reward_per_increment(state) * total_active_increments) - max_participant_rewards = Gwei(total_base_rewards * SYNC_REWARD_WEIGHT // WEIGHT_DENOMINATOR // SLOTS_PER_EPOCH) - participant_reward = Gwei(max_participant_rewards // SYNC_COMMITTEE_SIZE) - proposer_reward = Gwei(participant_reward * PROPOSER_WEIGHT // (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)) - - # Apply participant and proposer rewards - all_pubkeys = [v.pubkey for v in state.validators] - committee_indices = [ValidatorIndex(all_pubkeys.index(pubkey)) for pubkey in state.current_sync_committee.pubkeys] - for participant_index, participation_bit in zip(committee_indices, sync_aggregate.sync_committee_bits): - if participation_bit: - increase_balance(state, participant_index, participant_reward) - increase_balance(state, get_beacon_proposer_index(state), proposer_reward) - else: - decrease_balance(state, participant_index, participant_reward) - - -def process_inactivity_updates(state: BeaconState) -> None: - # Skip the genesis epoch as score updates are based on the previous epoch participation - if get_current_epoch(state) == GENESIS_EPOCH: - return - - for index in get_eligible_validator_indices(state): - # Increase the inactivity score of inactive validators - if index in get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)): - state.inactivity_scores[index] -= min(1, state.inactivity_scores[index]) - else: - state.inactivity_scores[index] += config.INACTIVITY_SCORE_BIAS - # Decrease the inactivity score of all eligible validators during a leak-free epoch - if not is_in_inactivity_leak(state): - state.inactivity_scores[index] -= min(config.INACTIVITY_SCORE_RECOVERY_RATE, state.inactivity_scores[index]) - - -def process_participation_flag_updates(state: BeaconState) -> None: - state.previous_epoch_participation = state.current_epoch_participation - state.current_epoch_participation = [ParticipationFlags(0b0000_0000) for _ in range(len(state.validators))] - - -def process_sync_committee_updates(state: BeaconState) -> None: - 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_next_sync_committee(state) - - -def eth_aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey: - return bls.AggregatePKs(pubkeys) - - -def eth_fast_aggregate_verify(pubkeys: Sequence[BLSPubkey], message: Bytes32, signature: BLSSignature) -> bool: - """ - Wrapper to ``bls.FastAggregateVerify`` accepting the ``G2_POINT_AT_INFINITY`` signature when ``pubkeys`` is empty. - """ - if len(pubkeys) == 0 and signature == G2_POINT_AT_INFINITY: - return True - return bls.FastAggregateVerify(pubkeys, message, signature) - - -def translate_participation(state: BeaconState, pending_attestations: Sequence[phase0.PendingAttestation]) -> None: - for attestation in pending_attestations: - data = attestation.data - inclusion_delay = attestation.inclusion_delay - # Translate attestation inclusion info to flag indices - participation_flag_indices = get_attestation_participation_flag_indices(state, data, inclusion_delay) - - # Apply flags to all attesting validators - epoch_participation = state.previous_epoch_participation - for index in get_attesting_indices(state, data, attestation.aggregation_bits): - for flag_index in participation_flag_indices: - epoch_participation[index] = add_flag(epoch_participation[index], flag_index) - - -def upgrade_to_altair(pre: phase0.BeaconState) -> BeaconState: - epoch = phase0.get_current_epoch(pre) - post = BeaconState( - # Versioning - genesis_time=pre.genesis_time, - genesis_validators_root=pre.genesis_validators_root, - slot=pre.slot, - fork=Fork( - previous_version=pre.fork.current_version, - current_version=config.ALTAIR_FORK_VERSION, - epoch=epoch, - ), - # History - latest_block_header=pre.latest_block_header, - block_roots=pre.block_roots, - state_roots=pre.state_roots, - historical_roots=pre.historical_roots, - # Eth1 - eth1_data=pre.eth1_data, - eth1_data_votes=pre.eth1_data_votes, - eth1_deposit_index=pre.eth1_deposit_index, - # Registry - validators=pre.validators, - balances=pre.balances, - # Randomness - randao_mixes=pre.randao_mixes, - # Slashings - slashings=pre.slashings, - # Participation - previous_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))], - current_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))], - # Finality - justification_bits=pre.justification_bits, - previous_justified_checkpoint=pre.previous_justified_checkpoint, - current_justified_checkpoint=pre.current_justified_checkpoint, - finalized_checkpoint=pre.finalized_checkpoint, - # Inactivity - inactivity_scores=[uint64(0) for _ in range(len(pre.validators))], - ) - # Fill in previous epoch participation from the pre state's pending attestations - translate_participation(post, pre.previous_epoch_attestations) - - # Fill in sync committees - # Note: A duplicate committee is assigned for the current and next committee at the fork boundary - post.current_sync_committee = get_next_sync_committee(post) - post.next_sync_committee = get_next_sync_committee(post) - return post - - -def compute_sync_committee_period(epoch: Epoch) -> uint64: - return epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - - -def is_assigned_to_sync_committee(state: BeaconState, - epoch: Epoch, - validator_index: ValidatorIndex) -> bool: - sync_committee_period = compute_sync_committee_period(epoch) - current_epoch = get_current_epoch(state) - current_sync_committee_period = compute_sync_committee_period(current_epoch) - next_sync_committee_period = current_sync_committee_period + 1 - assert sync_committee_period in (current_sync_committee_period, next_sync_committee_period) - - pubkey = state.validators[validator_index].pubkey - if sync_committee_period == current_sync_committee_period: - return pubkey in state.current_sync_committee.pubkeys - else: # sync_committee_period == next_sync_committee_period - return pubkey in state.next_sync_committee.pubkeys - - -def process_sync_committee_contributions(block: BeaconBlock, - contributions: Set[SyncCommitteeContribution]) -> None: - sync_aggregate = SyncAggregate() - signatures = [] - sync_subcommittee_size = SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT - - for contribution in contributions: - subcommittee_index = contribution.subcommittee_index - for index, participated in enumerate(contribution.aggregation_bits): - if participated: - participant_index = sync_subcommittee_size * subcommittee_index + index - sync_aggregate.sync_committee_bits[participant_index] = True - signatures.append(contribution.signature) - - sync_aggregate.sync_committee_signature = bls.Aggregate(signatures) - - block.body.sync_aggregate = sync_aggregate - - -def get_sync_committee_message(state: BeaconState, - block_root: Root, - validator_index: ValidatorIndex, - privkey: int) -> SyncCommitteeMessage: - epoch = get_current_epoch(state) - domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, epoch) - signing_root = compute_signing_root(block_root, domain) - signature = bls.Sign(privkey, signing_root) - - return SyncCommitteeMessage( - slot=state.slot, - beacon_block_root=block_root, - validator_index=validator_index, - signature=signature, - ) - - -def compute_subnets_for_sync_committee(state: BeaconState, validator_index: ValidatorIndex) -> Set[uint64]: - next_slot_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) - if compute_sync_committee_period(get_current_epoch(state)) == compute_sync_committee_period(next_slot_epoch): - sync_committee = state.current_sync_committee - else: - sync_committee = state.next_sync_committee - - target_pubkey = state.validators[validator_index].pubkey - sync_committee_indices = [index for index, pubkey in enumerate(sync_committee.pubkeys) if pubkey == target_pubkey] - return set([ - uint64(index // (SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT)) - for index in sync_committee_indices - ]) - - -def get_sync_committee_selection_proof(state: BeaconState, - slot: Slot, - subcommittee_index: uint64, - privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF, compute_epoch_at_slot(slot)) - signing_data = SyncAggregatorSelectionData( - slot=slot, - subcommittee_index=subcommittee_index, - ) - signing_root = compute_signing_root(signing_data, domain) - return bls.Sign(privkey, signing_root) - - -def is_sync_committee_aggregator(signature: BLSSignature) -> bool: - modulo = max(1, SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT // TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE) - return bytes_to_uint64(hash(signature)[0:8]) % modulo == 0 - - -def get_contribution_and_proof(state: BeaconState, - aggregator_index: ValidatorIndex, - contribution: SyncCommitteeContribution, - privkey: int) -> ContributionAndProof: - selection_proof = get_sync_committee_selection_proof( - state, - contribution.slot, - contribution.subcommittee_index, - privkey, - ) - return ContributionAndProof( - aggregator_index=aggregator_index, - contribution=contribution, - selection_proof=selection_proof, - ) - - -def get_contribution_and_proof_signature(state: BeaconState, - contribution_and_proof: ContributionAndProof, - privkey: int) -> BLSSignature: - contribution = contribution_and_proof.contribution - domain = get_domain(state, DOMAIN_CONTRIBUTION_AND_PROOF, compute_epoch_at_slot(contribution.slot)) - signing_root = compute_signing_root(contribution_and_proof, domain) - return bls.Sign(privkey, signing_root) - - -def get_sync_subcommittee_pubkeys(state: BeaconState, subcommittee_index: uint64) -> Sequence[BLSPubkey]: - # Committees assigned to `slot` sign for `slot - 1` - # This creates the exceptional logic below when transitioning between sync committee periods - next_slot_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) - if compute_sync_committee_period(get_current_epoch(state)) == compute_sync_committee_period(next_slot_epoch): - sync_committee = state.current_sync_committee - else: - sync_committee = state.next_sync_committee - - # Return pubkeys for the subcommittee index - sync_subcommittee_size = SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT - i = subcommittee_index * sync_subcommittee_size - return sync_committee.pubkeys[i:i + sync_subcommittee_size] - - -def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: - return uint64(generalized_index % 2**(floorlog2(generalized_index))) - - -def validate_light_client_update(snapshot: LightClientSnapshot, - update: LightClientUpdate, - genesis_validators_root: Root) -> None: - # Verify update slot is larger than snapshot slot - assert update.header.slot > snapshot.header.slot - - # Verify update does not skip a sync committee period - snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - assert update_period in (snapshot_period, snapshot_period + 1) - - # Verify update header root is the finalized root of the finality header, if specified - if update.finality_header == BeaconBlockHeader(): - signed_header = update.header - assert update.finality_branch == [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))] - else: - signed_header = update.finality_header - assert is_valid_merkle_branch( - leaf=hash_tree_root(update.header), - branch=update.finality_branch, - depth=floorlog2(FINALIZED_ROOT_INDEX), - index=get_subtree_index(FINALIZED_ROOT_INDEX), - root=update.finality_header.state_root, - ) - - # Verify update next sync committee if the update period incremented - if update_period == snapshot_period: - sync_committee = snapshot.current_sync_committee - assert update.next_sync_committee_branch == [Bytes32() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))] - else: - sync_committee = snapshot.next_sync_committee - assert is_valid_merkle_branch( - leaf=hash_tree_root(update.next_sync_committee), - branch=update.next_sync_committee_branch, - depth=floorlog2(NEXT_SYNC_COMMITTEE_INDEX), - index=get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX), - root=update.header.state_root, - ) - - # Verify sync committee has sufficient participants - assert sum(update.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS - - # Verify sync committee aggregate signature - participant_pubkeys = [pubkey for (bit, pubkey) in zip(update.sync_committee_bits, sync_committee.pubkeys) if bit] - domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version, genesis_validators_root) - signing_root = compute_signing_root(signed_header, domain) - assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature) - - -def apply_light_client_update(snapshot: LightClientSnapshot, update: LightClientUpdate) -> None: - snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - if update_period == snapshot_period + 1: - snapshot.current_sync_committee = snapshot.next_sync_committee - snapshot.next_sync_committee = update.next_sync_committee - snapshot.header = update.header - - -def process_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot, - genesis_validators_root: Root) -> None: - validate_light_client_update(store.snapshot, update, genesis_validators_root) - store.valid_updates.add(update) - - update_timeout = SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD - if ( - sum(update.sync_committee_bits) * 3 >= len(update.sync_committee_bits) * 2 - and update.finality_header != BeaconBlockHeader() - ): - # Apply update if (1) 2/3 quorum is reached and (2) we have a finality proof. - # Note that (2) means that the current light client design needs finality. - # It may be changed to re-organizable light client design. See the on-going issue consensus-specs#2182. - apply_light_client_update(store.snapshot, update) - store.valid_updates = set() - elif current_slot > store.snapshot.header.slot + update_timeout: - # Forced best update when the update timeout has elapsed - apply_light_client_update(store.snapshot, - max(store.valid_updates, key=lambda update: sum(update.sync_committee_bits))) - store.valid_updates = set() - - -def is_merge_transition_complete(state: BeaconState) -> bool: - return state.latest_execution_payload_header != ExecutionPayloadHeader() - - -def is_merge_transition_block(state: BeaconState, body: BeaconBlockBody) -> bool: - return not is_merge_transition_complete(state) and body.execution_payload != ExecutionPayload() - - -def is_execution_enabled(state: BeaconState, body: BeaconBlockBody) -> bool: - return is_merge_transition_block(state, body) or is_merge_transition_complete(state) - - -def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64: - slots_since_genesis = slot - GENESIS_SLOT - return uint64(state.genesis_time + slots_since_genesis * config.SECONDS_PER_SLOT) - - -def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None: - # Verify consistency of the parent hash with respect to the previous execution payload header - if is_merge_transition_complete(state): - assert payload.parent_hash == state.latest_execution_payload_header.block_hash - # Verify random - assert payload.random == get_randao_mix(state, get_current_epoch(state)) - # Verify timestamp - assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) - # Verify the execution payload is valid - assert execution_engine.execute_payload(payload) - # Cache execution payload header - state.latest_execution_payload_header = ExecutionPayloadHeader( - parent_hash=payload.parent_hash, - fee_recipient=payload.fee_recipient, - state_root=payload.state_root, - receipt_root=payload.receipt_root, - logs_bloom=payload.logs_bloom, - random=payload.random, - block_number=payload.block_number, - gas_limit=payload.gas_limit, - gas_used=payload.gas_used, - timestamp=payload.timestamp, - extra_data=payload.extra_data, - base_fee_per_gas=payload.base_fee_per_gas, - block_hash=payload.block_hash, - transactions_root=hash_tree_root(payload.transactions), - ) - - -def upgrade_to_merge(pre: altair.BeaconState) -> BeaconState: - epoch = altair.get_current_epoch(pre) - post = BeaconState( - # Versioning - genesis_time=pre.genesis_time, - genesis_validators_root=pre.genesis_validators_root, - slot=pre.slot, - fork=Fork( - previous_version=pre.fork.current_version, - current_version=config.MERGE_FORK_VERSION, - epoch=epoch, - ), - # History - latest_block_header=pre.latest_block_header, - block_roots=pre.block_roots, - state_roots=pre.state_roots, - historical_roots=pre.historical_roots, - # Eth1 - eth1_data=pre.eth1_data, - eth1_data_votes=pre.eth1_data_votes, - eth1_deposit_index=pre.eth1_deposit_index, - # Registry - validators=pre.validators, - balances=pre.balances, - # Randomness - randao_mixes=pre.randao_mixes, - # Slashings - slashings=pre.slashings, - # Participation - previous_epoch_participation=pre.previous_epoch_participation, - current_epoch_participation=pre.current_epoch_participation, - # Finality - justification_bits=pre.justification_bits, - previous_justified_checkpoint=pre.previous_justified_checkpoint, - current_justified_checkpoint=pre.current_justified_checkpoint, - finalized_checkpoint=pre.finalized_checkpoint, - # Inactivity - inactivity_scores=pre.inactivity_scores, - # Sync - current_sync_committee=pre.current_sync_committee, - next_sync_committee=pre.next_sync_committee, - # Execution-layer - latest_execution_payload_header=ExecutionPayloadHeader(), - ) - - return post - - -def is_valid_terminal_pow_block(block: PowBlock, parent: PowBlock) -> bool: - is_total_difficulty_reached = block.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY - is_parent_total_difficulty_valid = parent.total_difficulty < config.TERMINAL_TOTAL_DIFFICULTY - return is_total_difficulty_reached and is_parent_total_difficulty_valid - - -def validate_merge_block(block: BeaconBlock) -> None: - """ - Check the parent PoW block of execution payload is a valid terminal PoW block. - - Note: Unavailable PoW block(s) may later become available, - and a client software MAY delay a call to ``validate_merge_block`` - until the PoW block(s) become available. - """ - if config.TERMINAL_BLOCK_HASH != Hash32(): - # If `config.TERMINAL_BLOCK_HASH` is used as an override, the activation epoch must be reached. - assert compute_epoch_at_slot(block.slot) >= config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH - assert block.body.execution_payload.parent_hash == config.TERMINAL_BLOCK_HASH - return - - pow_block = get_pow_block(block.body.execution_payload.parent_hash) - # Check if `pow_block` is available - assert pow_block is not None - pow_parent = get_pow_block(pow_block.parent_hash) - # Check if `pow_parent` is available - assert pow_parent is not None - # Check if `pow_block` is a valid terminal PoW block - assert is_valid_terminal_pow_block(pow_block, pow_parent) - - -def get_pow_block_at_terminal_total_difficulty(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlock]: - # `pow_chain` abstractly represents all blocks in the PoW chain - for block in pow_chain.values(): - block_reached_ttd = block.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY - if block_reached_ttd: - # If genesis block, no parent exists so reaching TTD alone qualifies as valid terminal block - if block.parent_hash == Hash32(): - return block - parent = pow_chain[block.parent_hash] - parent_reached_ttd = parent.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY - if not parent_reached_ttd: - return block - - return None - - -def get_terminal_pow_block(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlock]: - if config.TERMINAL_BLOCK_HASH != Hash32(): - # Terminal block hash override takes precedence over terminal total difficulty - if config.TERMINAL_BLOCK_HASH in pow_chain: - return pow_chain[config.TERMINAL_BLOCK_HASH] - else: - return None - - return get_pow_block_at_terminal_total_difficulty(pow_chain) - - -def prepare_execution_payload(state: BeaconState, - pow_chain: Dict[Hash32, PowBlock], - finalized_block_hash: Hash32, - suggested_fee_recipient: ExecutionAddress, - execution_engine: ExecutionEngine) -> Optional[PayloadId]: - if not is_merge_transition_complete(state): - is_terminal_block_hash_set = config.TERMINAL_BLOCK_HASH != Hash32() - is_activation_epoch_reached = get_current_epoch(state) >= config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH - if is_terminal_block_hash_set and not is_activation_epoch_reached: - # Terminal block hash is set but activation epoch is not yet reached, no prepare payload call is needed - return None - - terminal_pow_block = get_terminal_pow_block(pow_chain) - if terminal_pow_block is None: - # Pre-merge, no prepare payload call is needed - return None - # Signify merge via producing on top of the terminal PoW block - parent_hash = terminal_pow_block.block_hash - else: - # Post-merge, normal payload - parent_hash = state.latest_execution_payload_header.block_hash - - # Set the forkchoice head and initiate the payload build process - payload_attributes = PayloadAttributes( - timestamp=compute_timestamp_at_slot(state, state.slot), - random=get_randao_mix(state, get_current_epoch(state)), - suggested_fee_recipient=suggested_fee_recipient, - ) - return execution_engine.notify_forkchoice_updated(parent_hash, finalized_block_hash, payload_attributes) - - -def get_execution_payload(payload_id: Optional[PayloadId], execution_engine: ExecutionEngine) -> ExecutionPayload: - if payload_id is None: - # Pre-merge, empty payload - return ExecutionPayload() - else: - return execution_engine.get_payload(payload_id) - - -def withdraw(state: BeaconState, index: ValidatorIndex, amount: Gwei) -> None: - # Decrease the validator's balance - decrease_balance(state, index, amount) - # Create a corresponding withdrawal receipt - receipt = WithdrawalReceipt( - index=WithdrawalReceiptIndex(len(state.withdrawal_receipts)), - address=state.validators[index].withdrawal_credentials[12:], - amount=amount, - ) - state.withdrawal_receipts.append(receipt) - - -def is_withdrawable_validator(validator: Validator, epoch: Epoch) -> bool: - """ - Check if ``validator`` is withdrawable. - """ - return validator.withdrawable_epoch <= epoch - - -def process_withdrawals(state: BeaconState) -> None: - current_epoch = get_current_epoch(state) - for index, validator in enumerate(state.validators): - balance = state.balances[index] - is_balance_nonzero = state.balances[index] == 0 - is_eth1_withdrawal_prefix = validator.withdrawal_credentials[0] != ETH1_ADDRESS_WITHDRAWAL_PREFIX - if is_balance_nonzero and is_eth1_withdrawal_prefix and is_withdrawable_validator(validator, current_epoch): - withdraw(state, index, balance) - - -def get_eth1_data(block: Eth1Block) -> Eth1Data: - """ - A stub function return mocking Eth1Data. - """ - return Eth1Data( - deposit_root=block.deposit_root, - deposit_count=block.deposit_count, - block_hash=hash_tree_root(block)) - - -def cache_this(key_fn, value_fn, lru_size): # type: ignore - cache_dict = LRU(size=lru_size) - - def wrapper(*args, **kw): # type: ignore - key = key_fn(*args, **kw) - nonlocal cache_dict - if key not in cache_dict: - cache_dict[key] = value_fn(*args, **kw) - return cache_dict[key] - return wrapper - - -_compute_shuffled_index = compute_shuffled_index -compute_shuffled_index = cache_this( - lambda index, index_count, seed: (index, index_count, seed), - _compute_shuffled_index, lru_size=SLOTS_PER_EPOCH * 3) - -_get_total_active_balance = get_total_active_balance -get_total_active_balance = cache_this( - lambda state: (state.validators.hash_tree_root(), compute_epoch_at_slot(state.slot)), - _get_total_active_balance, lru_size=10) - -_get_base_reward = get_base_reward -get_base_reward = cache_this( - lambda state, index: (state.validators.hash_tree_root(), state.slot, index), - _get_base_reward, lru_size=2048) - -_get_committee_count_per_slot = get_committee_count_per_slot -get_committee_count_per_slot = cache_this( - lambda state, epoch: (state.validators.hash_tree_root(), epoch), - _get_committee_count_per_slot, lru_size=SLOTS_PER_EPOCH * 3) - -_get_active_validator_indices = get_active_validator_indices -get_active_validator_indices = cache_this( - lambda state, epoch: (state.validators.hash_tree_root(), epoch), - _get_active_validator_indices, lru_size=3) - -_get_beacon_committee = get_beacon_committee -get_beacon_committee = cache_this( - lambda state, slot, index: (state.validators.hash_tree_root(), state.randao_mixes.hash_tree_root(), slot, index), - _get_beacon_committee, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3) - -_get_matching_target_attestations = get_matching_target_attestations -get_matching_target_attestations = cache_this( - lambda state, epoch: (state.hash_tree_root(), epoch), - _get_matching_target_attestations, lru_size=10) - -_get_matching_head_attestations = get_matching_head_attestations -get_matching_head_attestations = cache_this( - lambda state, epoch: (state.hash_tree_root(), epoch), - _get_matching_head_attestations, lru_size=10) - -_get_attesting_indices = get_attesting_indices -get_attesting_indices = cache_this( - lambda state, data, bits: ( - state.randao_mixes.hash_tree_root(), - state.validators.hash_tree_root(), data.hash_tree_root(), bits.hash_tree_root() - ), - _get_attesting_indices, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3) - - -def get_generalized_index(ssz_class: Any, *path: Sequence[PyUnion[int, SSZVariableName]]) -> GeneralizedIndex: - ssz_path = Path(ssz_class) - for item in path: - ssz_path = ssz_path / item - return GeneralizedIndex(ssz_path.gindex()) - - -ExecutionState = Any - - -def get_pow_block(hash: Bytes32) -> Optional[PowBlock]: - return PowBlock(block_hash=hash, parent_hash=Bytes32(), total_difficulty=uint256(0)) - - -def get_execution_state(execution_state_root: Bytes32) -> ExecutionState: - pass - - -def get_pow_chain_head() -> PowBlock: - pass - - -class NoopExecutionEngine(ExecutionEngine): - - def execute_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: - return True - - def notify_forkchoice_updated(self: ExecutionEngine, - head_block_hash: Hash32, - finalized_block_hash: Hash32, - payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: - pass - - def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayload: - raise NotImplementedError("no default block production") - - -EXECUTION_ENGINE = NoopExecutionEngine() - - -assert FINALIZED_ROOT_INDEX == get_generalized_index(BeaconState, 'finalized_checkpoint', 'root') -assert NEXT_SYNC_COMMITTEE_INDEX == get_generalized_index(BeaconState, 'next_sync_committee') diff --git a/tests/core/pyspec/eth2spec/withdrawals/minimal.py b/tests/core/pyspec/eth2spec/withdrawals/minimal.py deleted file mode 100644 index 820d3703c6..0000000000 --- a/tests/core/pyspec/eth2spec/withdrawals/minimal.py +++ /dev/null @@ -1,3018 +0,0 @@ -from lru import LRU -from dataclasses import ( - dataclass, - field, -) -from typing import ( - Any, Callable, Dict, Set, Sequence, Tuple, Optional, TypeVar, NamedTuple -) - -from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy, uint_to_bytes -from eth2spec.utils.ssz.ssz_typing import ( - View, boolean, Container, List, Vector, uint8, uint32, uint64, - Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist) -from eth2spec.utils.ssz.ssz_typing import Bitvector # noqa: F401 -from eth2spec.utils import bls -from eth2spec.utils.hash_function import hash - - -from typing import NewType, Union as PyUnion - -from eth2spec.phase0 import minimal as phase0 -from eth2spec.utils.ssz.ssz_typing import Path - -from typing import Protocol -from eth2spec.altair import minimal as altair -from eth2spec.utils.ssz.ssz_typing import Bytes8, Bytes20, ByteList, ByteVector, uint256 - -SSZObject = TypeVar('SSZObject', bound=View) - - -SSZVariableName = str -GeneralizedIndex = NewType('GeneralizedIndex', int) - - -fork = 'withdrawals' - - -MAX_BYTES_PER_TRANSACTION = uint64(2**30) - - -class Slot(uint64): - pass - - -class Epoch(uint64): - pass - - -class CommitteeIndex(uint64): - pass - - -class ValidatorIndex(uint64): - pass - - -class Gwei(uint64): - pass - - -class Root(Bytes32): - pass - - -class Hash32(Bytes32): - pass - - -class Version(Bytes4): - pass - - -class DomainType(Bytes4): - pass - - -class ForkDigest(Bytes4): - pass - - -class Domain(Bytes32): - pass - - -class BLSPubkey(Bytes48): - pass - - -class BLSSignature(Bytes96): - pass - - -class Ether(uint64): - pass - - -class ParticipationFlags(uint8): - pass - - -class ExecutionAddress(Bytes20): - pass - - -class PayloadId(Bytes8): - pass - - -class WithdrawalReceiptIndex(uint64): - pass - - -Transaction = ByteList[MAX_BYTES_PER_TRANSACTION] - - -def ceillog2(x: int) -> uint64: - if x < 1: - raise ValueError(f"ceillog2 accepts only positive values, x={x}") - return uint64((x - 1).bit_length()) - - -def floorlog2(x: int) -> uint64: - if x < 1: - raise ValueError(f"floorlog2 accepts only positive values, x={x}") - return uint64(x.bit_length() - 1) - - -FINALIZED_ROOT_INDEX = GeneralizedIndex(105) -NEXT_SYNC_COMMITTEE_INDEX = GeneralizedIndex(55) - -# Constant vars -GENESIS_SLOT = Slot(0) -GENESIS_EPOCH = Epoch(0) -FAR_FUTURE_EPOCH = Epoch(2**64 - 1) -BASE_REWARDS_PER_EPOCH = uint64(4) -DEPOSIT_CONTRACT_TREE_DEPTH = uint64(2**5) -JUSTIFICATION_BITS_LENGTH = uint64(4) -ENDIANNESS = 'little' -BLS_WITHDRAWAL_PREFIX = Bytes1('0x00') -ETH1_ADDRESS_WITHDRAWAL_PREFIX = Bytes1('0x01') -DOMAIN_BEACON_PROPOSER = DomainType('0x00000000') -DOMAIN_BEACON_ATTESTER = DomainType('0x01000000') -DOMAIN_RANDAO = DomainType('0x02000000') -DOMAIN_DEPOSIT = DomainType('0x03000000') -DOMAIN_VOLUNTARY_EXIT = DomainType('0x04000000') -DOMAIN_SELECTION_PROOF = DomainType('0x05000000') -DOMAIN_AGGREGATE_AND_PROOF = DomainType('0x06000000') -INTERVALS_PER_SLOT = uint64(3) -TARGET_AGGREGATORS_PER_COMMITTEE = 2**4 -RANDOM_SUBNETS_PER_VALIDATOR = 2**0 -EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION = 2**8 -ATTESTATION_SUBNET_COUNT = 64 -ETH_TO_GWEI = uint64(10**9) -SAFETY_DECAY = uint64(10) -TIMELY_SOURCE_FLAG_INDEX = 0 -TIMELY_TARGET_FLAG_INDEX = 1 -TIMELY_HEAD_FLAG_INDEX = 2 -TIMELY_SOURCE_WEIGHT = uint64(14) -TIMELY_TARGET_WEIGHT = uint64(26) -TIMELY_HEAD_WEIGHT = uint64(14) -SYNC_REWARD_WEIGHT = uint64(2) -PROPOSER_WEIGHT = uint64(8) -WEIGHT_DENOMINATOR = uint64(64) -DOMAIN_SYNC_COMMITTEE = DomainType('0x07000000') -DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF = DomainType('0x08000000') -DOMAIN_CONTRIBUTION_AND_PROOF = DomainType('0x09000000') -PARTICIPATION_FLAG_WEIGHTS = [TIMELY_SOURCE_WEIGHT, TIMELY_TARGET_WEIGHT, TIMELY_HEAD_WEIGHT] -G2_POINT_AT_INFINITY = BLSSignature(b'\xc0' + b'\x00' * 95) -TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE = 2**4 -SYNC_COMMITTEE_SUBNET_COUNT = 4 -WITHDRAWAL_RECEIPT_LIMIT = uint64(2**40) - -# Preset vars -MAX_COMMITTEES_PER_SLOT = uint64(4) -TARGET_COMMITTEE_SIZE = uint64(4) -MAX_VALIDATORS_PER_COMMITTEE = uint64(2048) -SHUFFLE_ROUND_COUNT = uint64(10) -HYSTERESIS_QUOTIENT = uint64(4) -HYSTERESIS_DOWNWARD_MULTIPLIER = uint64(1) -HYSTERESIS_UPWARD_MULTIPLIER = uint64(5) -MIN_DEPOSIT_AMOUNT = Gwei(1000000000) -MAX_EFFECTIVE_BALANCE = Gwei(32000000000) -EFFECTIVE_BALANCE_INCREMENT = Gwei(1000000000) -MIN_ATTESTATION_INCLUSION_DELAY = uint64(1) -SLOTS_PER_EPOCH = uint64(8) -MIN_SEED_LOOKAHEAD = uint64(1) -MAX_SEED_LOOKAHEAD = uint64(4) -MIN_EPOCHS_TO_INACTIVITY_PENALTY = uint64(4) -EPOCHS_PER_ETH1_VOTING_PERIOD = uint64(4) -SLOTS_PER_HISTORICAL_ROOT = uint64(64) -EPOCHS_PER_HISTORICAL_VECTOR = uint64(64) -EPOCHS_PER_SLASHINGS_VECTOR = uint64(64) -HISTORICAL_ROOTS_LIMIT = uint64(16777216) -VALIDATOR_REGISTRY_LIMIT = uint64(1099511627776) -BASE_REWARD_FACTOR = uint64(64) -WHISTLEBLOWER_REWARD_QUOTIENT = uint64(512) -PROPOSER_REWARD_QUOTIENT = uint64(8) -INACTIVITY_PENALTY_QUOTIENT = uint64(33554432) -MIN_SLASHING_PENALTY_QUOTIENT = uint64(64) -PROPORTIONAL_SLASHING_MULTIPLIER = uint64(2) -MAX_PROPOSER_SLASHINGS = 16 -MAX_ATTESTER_SLASHINGS = 2 -MAX_ATTESTATIONS = 128 -MAX_DEPOSITS = 16 -MAX_VOLUNTARY_EXITS = 16 -SAFE_SLOTS_TO_UPDATE_JUSTIFIED = 2 -INACTIVITY_PENALTY_QUOTIENT_ALTAIR = uint64(50331648) -MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR = uint64(64) -PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR = uint64(2) -SYNC_COMMITTEE_SIZE = uint64(32) -EPOCHS_PER_SYNC_COMMITTEE_PERIOD = uint64(8) -MIN_SYNC_COMMITTEE_PARTICIPANTS = 1 -MAX_BYTES_PER_TRANSACTION = uint64(1073741824) -MAX_TRANSACTIONS_PER_PAYLOAD = uint64(1048576) -BYTES_PER_LOGS_BLOOM = uint64(256) -MAX_EXTRA_DATA_BYTES = 32 -INACTIVITY_PENALTY_QUOTIENT_MERGE = uint64(16777216) -MIN_SLASHING_PENALTY_QUOTIENT_MERGE = uint64(32) -PROPORTIONAL_SLASHING_MULTIPLIER_MERGE = uint64(3) - - -class Configuration(NamedTuple): - PRESET_BASE: str - MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: uint64 - MIN_GENESIS_TIME: uint64 - GENESIS_FORK_VERSION: Version - GENESIS_DELAY: uint64 - SECONDS_PER_SLOT: uint64 - SECONDS_PER_ETH1_BLOCK: uint64 - MIN_VALIDATOR_WITHDRAWABILITY_DELAY: uint64 - SHARD_COMMITTEE_PERIOD: uint64 - ETH1_FOLLOW_DISTANCE: uint64 - EJECTION_BALANCE: Gwei - MIN_PER_EPOCH_CHURN_LIMIT: uint64 - CHURN_LIMIT_QUOTIENT: uint64 - PROPOSER_SCORE_BOOST: uint64 - INACTIVITY_SCORE_BIAS: uint64 - INACTIVITY_SCORE_RECOVERY_RATE: uint64 - ALTAIR_FORK_VERSION: Version - ALTAIR_FORK_EPOCH: Epoch - TERMINAL_TOTAL_DIFFICULTY: int - TERMINAL_BLOCK_HASH: Hash32 - TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: int - MERGE_FORK_VERSION: Version - MERGE_FORK_EPOCH: Epoch - - -config = Configuration( - PRESET_BASE="minimal", - MIN_GENESIS_ACTIVE_VALIDATOR_COUNT=uint64(64), - MIN_GENESIS_TIME=uint64(1578009600), - GENESIS_FORK_VERSION=Version('0x00000001'), - GENESIS_DELAY=uint64(300), - SECONDS_PER_SLOT=uint64(6), - SECONDS_PER_ETH1_BLOCK=uint64(14), - MIN_VALIDATOR_WITHDRAWABILITY_DELAY=uint64(256), - SHARD_COMMITTEE_PERIOD=uint64(64), - ETH1_FOLLOW_DISTANCE=uint64(16), - EJECTION_BALANCE=Gwei(16000000000), - MIN_PER_EPOCH_CHURN_LIMIT=uint64(4), - CHURN_LIMIT_QUOTIENT=uint64(32), - PROPOSER_SCORE_BOOST=uint64(70), - INACTIVITY_SCORE_BIAS=uint64(4), - INACTIVITY_SCORE_RECOVERY_RATE=uint64(16), - ALTAIR_FORK_VERSION=Version('0x01000001'), - ALTAIR_FORK_EPOCH=Epoch(18446744073709551615), - TERMINAL_TOTAL_DIFFICULTY=115792089237316195423570985008687907853269984665640564039457584007913129638912, - TERMINAL_BLOCK_HASH=Hash32('0x0000000000000000000000000000000000000000000000000000000000000000'), - TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH=18446744073709551615, - MERGE_FORK_VERSION=Version('0x02000001'), - MERGE_FORK_EPOCH=Epoch(18446744073709551615), -) - - -class Fork(Container): - previous_version: Version - current_version: Version - epoch: Epoch # Epoch of latest fork - - -class ForkData(Container): - current_version: Version - genesis_validators_root: Root - - -class Checkpoint(Container): - epoch: Epoch - root: Root - - -class Validator(Container): - pubkey: BLSPubkey - withdrawal_credentials: Bytes32 # Commitment to pubkey for withdrawals - effective_balance: Gwei # Balance at stake - slashed: boolean - # Status epochs - activation_eligibility_epoch: Epoch # When criteria for activation were met - activation_epoch: Epoch - exit_epoch: Epoch - withdrawable_epoch: Epoch # When validator can withdraw funds - - -class AttestationData(Container): - slot: Slot - index: CommitteeIndex - # LMD GHOST vote - beacon_block_root: Root - # FFG vote - source: Checkpoint - target: Checkpoint - - -class IndexedAttestation(Container): - attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] - data: AttestationData - signature: BLSSignature - - -class PendingAttestation(Container): - aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] - data: AttestationData - inclusion_delay: Slot - proposer_index: ValidatorIndex - - -class Eth1Data(Container): - deposit_root: Root - deposit_count: uint64 - block_hash: Hash32 - - -class HistoricalBatch(Container): - block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] - state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] - - -class DepositMessage(Container): - pubkey: BLSPubkey - withdrawal_credentials: Bytes32 - amount: Gwei - - -class DepositData(Container): - pubkey: BLSPubkey - withdrawal_credentials: Bytes32 - amount: Gwei - signature: BLSSignature # Signing over DepositMessage - - -class BeaconBlockHeader(Container): - slot: Slot - proposer_index: ValidatorIndex - parent_root: Root - state_root: Root - body_root: Root - - -class SigningData(Container): - object_root: Root - domain: Domain - - -class AttesterSlashing(Container): - attestation_1: IndexedAttestation - attestation_2: IndexedAttestation - - -class Attestation(Container): - aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] - data: AttestationData - signature: BLSSignature - - -class Deposit(Container): - proof: Vector[Bytes32, DEPOSIT_CONTRACT_TREE_DEPTH + 1] # Merkle path to deposit root - data: DepositData - - -class VoluntaryExit(Container): - epoch: Epoch # Earliest epoch when voluntary exit can be processed - validator_index: ValidatorIndex - - -class SignedVoluntaryExit(Container): - message: VoluntaryExit - signature: BLSSignature - - -class SignedBeaconBlockHeader(Container): - message: BeaconBlockHeader - signature: BLSSignature - - -class ProposerSlashing(Container): - signed_header_1: SignedBeaconBlockHeader - signed_header_2: SignedBeaconBlockHeader - - -class Eth1Block(Container): - timestamp: uint64 - deposit_root: Root - deposit_count: uint64 - # All other eth1 block fields - - -class AggregateAndProof(Container): - aggregator_index: ValidatorIndex - aggregate: Attestation - selection_proof: BLSSignature - - -class SignedAggregateAndProof(Container): - message: AggregateAndProof - signature: BLSSignature - - -class SyncAggregate(Container): - sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] - sync_committee_signature: BLSSignature - - -class SyncCommittee(Container): - pubkeys: Vector[BLSPubkey, SYNC_COMMITTEE_SIZE] - aggregate_pubkey: BLSPubkey - - -class SyncCommitteeMessage(Container): - # Slot to which this contribution pertains - slot: Slot - # Block root for this signature - beacon_block_root: Root - # Index of the validator that produced this signature - validator_index: ValidatorIndex - # Signature by the validator over the block root of `slot` - signature: BLSSignature - - -class SyncCommitteeContribution(Container): - # Slot to which this contribution pertains - slot: Slot - # Block root for this contribution - beacon_block_root: Root - # The subcommittee this contribution pertains to out of the broader sync committee - subcommittee_index: uint64 - # A bit is set if a signature from the validator at the corresponding - # index in the subcommittee is present in the aggregate `signature`. - aggregation_bits: Bitvector[SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT] - # Signature by the validator(s) over the block root of `slot` - signature: BLSSignature - - -class ContributionAndProof(Container): - aggregator_index: ValidatorIndex - contribution: SyncCommitteeContribution - selection_proof: BLSSignature - - -class SignedContributionAndProof(Container): - message: ContributionAndProof - signature: BLSSignature - - -class SyncAggregatorSelectionData(Container): - slot: Slot - subcommittee_index: uint64 - - -class LightClientSnapshot(Container): - # Beacon block header - header: BeaconBlockHeader - # Sync committees corresponding to the header - current_sync_committee: SyncCommittee - next_sync_committee: SyncCommittee - - -class LightClientUpdate(Container): - # Update beacon block header - header: BeaconBlockHeader - # Next sync committee corresponding to the header - next_sync_committee: SyncCommittee - next_sync_committee_branch: Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_INDEX)] - # Finality proof for the update header - finality_header: BeaconBlockHeader - finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX)] - # Sync committee aggregate signature - sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] - sync_committee_signature: BLSSignature - # Fork version for the aggregate signature - fork_version: Version - - -class ExecutionPayload(Container): - # Execution block header fields - parent_hash: Hash32 - fee_recipient: ExecutionAddress # 'beneficiary' in the yellow paper - state_root: Bytes32 - receipt_root: Bytes32 # 'receipts root' in the yellow paper - logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - random: Bytes32 # 'difficulty' in the yellow paper - block_number: uint64 # 'number' in the yellow paper - gas_limit: uint64 - gas_used: uint64 - timestamp: uint64 - extra_data: ByteList[MAX_EXTRA_DATA_BYTES] - base_fee_per_gas: uint256 - # Extra payload fields - block_hash: Hash32 # Hash of execution block - transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] - - -class BeaconBlockBody(Container): - randao_reveal: BLSSignature - eth1_data: Eth1Data # Eth1 data vote - graffiti: Bytes32 # Arbitrary data - # Operations - proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] - attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] - attestations: List[Attestation, MAX_ATTESTATIONS] - deposits: List[Deposit, MAX_DEPOSITS] - voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] - sync_aggregate: SyncAggregate - # Execution - execution_payload: ExecutionPayload # [New in Merge] - - -class BeaconBlock(Container): - slot: Slot - proposer_index: ValidatorIndex - parent_root: Root - state_root: Root - body: BeaconBlockBody - - -class SignedBeaconBlock(Container): - message: BeaconBlock - signature: BLSSignature - - -class ExecutionPayloadHeader(Container): - # Execution block header fields - parent_hash: Hash32 - fee_recipient: ExecutionAddress - state_root: Bytes32 - receipt_root: Bytes32 - logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - random: Bytes32 - block_number: uint64 - gas_limit: uint64 - gas_used: uint64 - timestamp: uint64 - extra_data: ByteList[MAX_EXTRA_DATA_BYTES] - base_fee_per_gas: uint256 - # Extra payload fields - block_hash: Hash32 # Hash of execution block - transactions_root: Root - - -class PowBlock(Container): - block_hash: Hash32 - parent_hash: Hash32 - total_difficulty: uint256 - - -class WithdrawalReceipt(Container): - index: WithdrawalReceiptIndex - address: ExecutionAddress - amount: Gwei - - -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[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] - current_epoch_participation: List[ParticipationFlags, 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 - # Inactivity - inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] - # Sync - current_sync_committee: SyncCommittee - next_sync_committee: SyncCommittee - # Execution - latest_execution_payload_header: ExecutionPayloadHeader - # Withdrawals - withdrawal_receipts: List[WithdrawalReceipt, WITHDRAWAL_RECEIPT_LIMIT] # [New in Withdrawals] - - -@dataclass(eq=True, frozen=True) -class LatestMessage(object): - epoch: Epoch - root: Root - - -@dataclass -class Store(object): - time: uint64 - genesis_time: uint64 - justified_checkpoint: Checkpoint - finalized_checkpoint: Checkpoint - best_justified_checkpoint: Checkpoint - proposer_boost_root: Root - blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) - block_states: Dict[Root, BeaconState] = field(default_factory=dict) - checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict) - latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict) - - -@dataclass -class LightClientStore(object): - snapshot: LightClientSnapshot - valid_updates: Set[LightClientUpdate] - - -@dataclass -class PayloadAttributes(object): - timestamp: uint64 - random: Bytes32 - suggested_fee_recipient: ExecutionAddress - - -class ExecutionEngine(Protocol): - - def execute_payload(self, execution_payload: ExecutionPayload) -> bool: - """ - Return ``True`` if and only if ``execution_payload`` is valid with respect to ``self.execution_state``. - """ - ... - - def notify_forkchoice_updated(self, - head_block_hash: Hash32, - finalized_block_hash: Hash32, - payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: - ... - - def get_payload(self, payload_id: PayloadId) -> ExecutionPayload: - """ - Return ``execution_payload`` object. - """ - ... - - -def integer_squareroot(n: uint64) -> uint64: - """ - Return the largest integer ``x`` such that ``x**2 <= n``. - """ - x = n - y = (x + 1) // 2 - while y < x: - x = y - y = (x + n // x) // 2 - return x - - -def xor(bytes_1: Bytes32, bytes_2: Bytes32) -> Bytes32: - """ - Return the exclusive-or of two 32-byte strings. - """ - return Bytes32(a ^ b for a, b in zip(bytes_1, bytes_2)) - - -def bytes_to_uint64(data: bytes) -> uint64: - """ - Return the integer deserialization of ``data`` interpreted as ``ENDIANNESS``-endian. - """ - return uint64(int.from_bytes(data, ENDIANNESS)) - - -def is_active_validator(validator: Validator, epoch: Epoch) -> bool: - """ - Check if ``validator`` is active. - """ - return validator.activation_epoch <= epoch < validator.exit_epoch - - -def is_eligible_for_activation_queue(validator: Validator) -> bool: - """ - Check if ``validator`` is eligible to be placed into the activation queue. - """ - return ( - validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH - and validator.effective_balance == MAX_EFFECTIVE_BALANCE - ) - - -def is_eligible_for_activation(state: BeaconState, validator: Validator) -> bool: - """ - Check if ``validator`` is eligible for activation. - """ - return ( - # Placement in queue is finalized - validator.activation_eligibility_epoch <= state.finalized_checkpoint.epoch - # Has not yet been activated - and validator.activation_epoch == FAR_FUTURE_EPOCH - ) - - -def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool: - """ - Check if ``validator`` is slashable. - """ - return (not validator.slashed) and (validator.activation_epoch <= epoch < validator.withdrawable_epoch) - - -def is_slashable_attestation_data(data_1: AttestationData, data_2: AttestationData) -> bool: - """ - Check if ``data_1`` and ``data_2`` are slashable according to Casper FFG rules. - """ - return ( - # Double vote - (data_1 != data_2 and data_1.target.epoch == data_2.target.epoch) or - # Surround vote - (data_1.source.epoch < data_2.source.epoch and data_2.target.epoch < data_1.target.epoch) - ) - - -def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool: - """ - Check if ``indexed_attestation`` is not empty, has sorted and unique indices and has a valid aggregate signature. - """ - # Verify indices are sorted and unique - indices = indexed_attestation.attesting_indices - if len(indices) == 0 or not indices == sorted(set(indices)): - return False - # Verify aggregate signature - pubkeys = [state.validators[i].pubkey for i in indices] - domain = get_domain(state, DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch) - signing_root = compute_signing_root(indexed_attestation.data, domain) - return bls.FastAggregateVerify(pubkeys, signing_root, indexed_attestation.signature) - - -def is_valid_merkle_branch(leaf: Bytes32, branch: Sequence[Bytes32], depth: uint64, index: uint64, root: Root) -> bool: - """ - Check if ``leaf`` at ``index`` verifies against the Merkle ``root`` and ``branch``. - """ - value = leaf - for i in range(depth): - if index // (2**i) % 2: - value = hash(branch[i] + value) - else: - value = hash(value + branch[i]) - return value == root - - -def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) -> uint64: - """ - Return the shuffled index corresponding to ``seed`` (and ``index_count``). - """ - assert index < index_count - - # Swap or not (https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf) - # See the 'generalized domain' algorithm on page 3 - for current_round in range(SHUFFLE_ROUND_COUNT): - pivot = bytes_to_uint64(hash(seed + uint_to_bytes(uint8(current_round)))[0:8]) % index_count - flip = (pivot + index_count - index) % index_count - position = max(index, flip) - source = hash( - seed - + uint_to_bytes(uint8(current_round)) - + uint_to_bytes(uint32(position // 256)) - ) - byte = uint8(source[(position % 256) // 8]) - bit = (byte >> (position % 8)) % 2 - index = flip if bit else index - - return index - - -def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex], seed: Bytes32) -> ValidatorIndex: - """ - Return from ``indices`` a random index sampled by effective balance. - """ - assert len(indices) > 0 - MAX_RANDOM_BYTE = 2**8 - 1 - i = uint64(0) - total = uint64(len(indices)) - while True: - candidate_index = indices[compute_shuffled_index(i % total, total, seed)] - random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] - effective_balance = state.validators[candidate_index].effective_balance - if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: - return candidate_index - i += 1 - - -def compute_committee(indices: Sequence[ValidatorIndex], - seed: Bytes32, - index: uint64, - count: uint64) -> Sequence[ValidatorIndex]: - """ - Return the committee corresponding to ``indices``, ``seed``, ``index``, and committee ``count``. - """ - start = (len(indices) * index) // count - end = (len(indices) * uint64(index + 1)) // count - return [indices[compute_shuffled_index(uint64(i), uint64(len(indices)), seed)] for i in range(start, end)] - - -def compute_epoch_at_slot(slot: Slot) -> Epoch: - """ - Return the epoch number at ``slot``. - """ - return Epoch(slot // SLOTS_PER_EPOCH) - - -def compute_start_slot_at_epoch(epoch: Epoch) -> Slot: - """ - Return the start slot of ``epoch``. - """ - return Slot(epoch * SLOTS_PER_EPOCH) - - -def compute_activation_exit_epoch(epoch: Epoch) -> Epoch: - """ - Return the epoch during which validator activations and exits initiated in ``epoch`` take effect. - """ - return Epoch(epoch + 1 + MAX_SEED_LOOKAHEAD) - - -def compute_fork_data_root(current_version: Version, genesis_validators_root: Root) -> Root: - """ - Return the 32-byte fork data root for the ``current_version`` and ``genesis_validators_root``. - This is used primarily in signature domains to avoid collisions across forks/chains. - """ - return hash_tree_root(ForkData( - current_version=current_version, - genesis_validators_root=genesis_validators_root, - )) - - -def compute_fork_digest(current_version: Version, genesis_validators_root: Root) -> ForkDigest: - """ - Return the 4-byte fork digest for the ``current_version`` and ``genesis_validators_root``. - This is a digest primarily used for domain separation on the p2p layer. - 4-bytes suffices for practical separation of forks/chains. - """ - return ForkDigest(compute_fork_data_root(current_version, genesis_validators_root)[:4]) - - -def compute_domain(domain_type: DomainType, fork_version: Version=None, genesis_validators_root: Root=None) -> Domain: - """ - Return the domain for the ``domain_type`` and ``fork_version``. - """ - if fork_version is None: - fork_version = config.GENESIS_FORK_VERSION - if genesis_validators_root is None: - genesis_validators_root = Root() # all bytes zero by default - fork_data_root = compute_fork_data_root(fork_version, genesis_validators_root) - return Domain(domain_type + fork_data_root[:28]) - - -def compute_signing_root(ssz_object: SSZObject, domain: Domain) -> Root: - """ - Return the signing root for the corresponding signing data. - """ - return hash_tree_root(SigningData( - object_root=hash_tree_root(ssz_object), - domain=domain, - )) - - -def get_current_epoch(state: BeaconState) -> Epoch: - """ - Return the current epoch. - """ - return compute_epoch_at_slot(state.slot) - - -def get_previous_epoch(state: BeaconState) -> Epoch: - """` - Return the previous epoch (unless the current epoch is ``GENESIS_EPOCH``). - """ - current_epoch = get_current_epoch(state) - return GENESIS_EPOCH if current_epoch == GENESIS_EPOCH else Epoch(current_epoch - 1) - - -def get_block_root(state: BeaconState, epoch: Epoch) -> Root: - """ - Return the block root at the start of a recent ``epoch``. - """ - return get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch)) - - -def get_block_root_at_slot(state: BeaconState, slot: Slot) -> Root: - """ - Return the block root at a recent ``slot``. - """ - assert slot < state.slot <= slot + SLOTS_PER_HISTORICAL_ROOT - return state.block_roots[slot % SLOTS_PER_HISTORICAL_ROOT] - - -def get_randao_mix(state: BeaconState, epoch: Epoch) -> Bytes32: - """ - Return the randao mix at a recent ``epoch``. - """ - return state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR] - - -def get_active_validator_indices(state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: - """ - Return the sequence of active validator indices at ``epoch``. - """ - return [ValidatorIndex(i) for i, v in enumerate(state.validators) if is_active_validator(v, epoch)] - - -def get_validator_churn_limit(state: BeaconState) -> uint64: - """ - Return the validator churn limit for the current epoch. - """ - active_validator_indices = get_active_validator_indices(state, get_current_epoch(state)) - return max(config.MIN_PER_EPOCH_CHURN_LIMIT, uint64(len(active_validator_indices)) // config.CHURN_LIMIT_QUOTIENT) - - -def get_seed(state: BeaconState, epoch: Epoch, domain_type: DomainType) -> Bytes32: - """ - Return the seed at ``epoch``. - """ - mix = get_randao_mix(state, Epoch(epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD - 1)) # Avoid underflow - return hash(domain_type + uint_to_bytes(epoch) + mix) - - -def get_committee_count_per_slot(state: BeaconState, epoch: Epoch) -> uint64: - """ - Return the number of committees in each slot for the given ``epoch``. - """ - return max(uint64(1), min( - MAX_COMMITTEES_PER_SLOT, - uint64(len(get_active_validator_indices(state, epoch))) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE, - )) - - -def get_beacon_committee(state: BeaconState, slot: Slot, index: CommitteeIndex) -> Sequence[ValidatorIndex]: - """ - Return the beacon committee at ``slot`` for ``index``. - """ - epoch = compute_epoch_at_slot(slot) - committees_per_slot = get_committee_count_per_slot(state, epoch) - return compute_committee( - indices=get_active_validator_indices(state, epoch), - seed=get_seed(state, epoch, DOMAIN_BEACON_ATTESTER), - index=(slot % SLOTS_PER_EPOCH) * committees_per_slot + index, - count=committees_per_slot * SLOTS_PER_EPOCH, - ) - - -def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: - """ - Return the beacon proposer index at the current slot. - """ - epoch = get_current_epoch(state) - seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + uint_to_bytes(state.slot)) - indices = get_active_validator_indices(state, epoch) - return compute_proposer_index(state, indices, seed) - - -def get_total_balance(state: BeaconState, indices: Set[ValidatorIndex]) -> Gwei: - """ - Return the combined effective balance of the ``indices``. - ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. - Math safe up to ~10B ETH, afterwhich this overflows uint64. - """ - return Gwei(max(EFFECTIVE_BALANCE_INCREMENT, sum([state.validators[index].effective_balance for index in indices]))) - - -def get_total_active_balance(state: BeaconState) -> Gwei: - """ - Return the combined effective balance of the active validators. - Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. - """ - return get_total_balance(state, set(get_active_validator_indices(state, get_current_epoch(state)))) - - -def get_domain(state: BeaconState, domain_type: DomainType, epoch: Epoch=None) -> Domain: - """ - Return the signature domain (fork version concatenated with domain type) of a message. - """ - epoch = get_current_epoch(state) if epoch is None else epoch - fork_version = state.fork.previous_version if epoch < state.fork.epoch else state.fork.current_version - return compute_domain(domain_type, fork_version, state.genesis_validators_root) - - -def get_indexed_attestation(state: BeaconState, attestation: Attestation) -> IndexedAttestation: - """ - Return the indexed attestation corresponding to ``attestation``. - """ - attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) - - return IndexedAttestation( - attesting_indices=sorted(attesting_indices), - data=attestation.data, - signature=attestation.signature, - ) - - -def get_attesting_indices(state: BeaconState, - data: AttestationData, - bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Set[ValidatorIndex]: - """ - Return the set of attesting indices corresponding to ``data`` and ``bits``. - """ - committee = get_beacon_committee(state, data.slot, data.index) - return set(index for i, index in enumerate(committee) if bits[i]) - - -def increase_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: - """ - Increase the validator balance at index ``index`` by ``delta``. - """ - state.balances[index] += delta - - -def decrease_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: - """ - Decrease the validator balance at index ``index`` by ``delta``, with underflow protection. - """ - state.balances[index] = 0 if delta > state.balances[index] else state.balances[index] - delta - - -def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: - """ - Initiate the exit of the validator with index ``index``. - """ - # Return if validator already initiated exit - validator = state.validators[index] - if validator.exit_epoch != FAR_FUTURE_EPOCH: - return - - # Compute exit queue epoch - exit_epochs = [v.exit_epoch for v in state.validators if v.exit_epoch != FAR_FUTURE_EPOCH] - exit_queue_epoch = max(exit_epochs + [compute_activation_exit_epoch(get_current_epoch(state))]) - exit_queue_churn = len([v for v in state.validators if v.exit_epoch == exit_queue_epoch]) - if exit_queue_churn >= get_validator_churn_limit(state): - exit_queue_epoch += Epoch(1) - - # Set validator exit epoch and withdrawable epoch - validator.exit_epoch = exit_queue_epoch - validator.withdrawable_epoch = Epoch(validator.exit_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY) - - -def slash_validator(state: BeaconState, - slashed_index: ValidatorIndex, - whistleblower_index: ValidatorIndex=None) -> None: - """ - Slash the validator with index ``slashed_index``. - """ - epoch = get_current_epoch(state) - initiate_validator_exit(state, slashed_index) - validator = state.validators[slashed_index] - validator.slashed = True - validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR)) - state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance - slashing_penalty = validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_MERGE # [Modified in Merge] - decrease_balance(state, slashed_index, slashing_penalty) - - # Apply proposer and whistleblower rewards - proposer_index = get_beacon_proposer_index(state) - if whistleblower_index is None: - whistleblower_index = proposer_index - whistleblower_reward = Gwei(validator.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT) - proposer_reward = Gwei(whistleblower_reward * PROPOSER_WEIGHT // WEIGHT_DENOMINATOR) - increase_balance(state, proposer_index, proposer_reward) - increase_balance(state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward)) - - -def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, - eth1_timestamp: uint64, - deposits: Sequence[Deposit], - execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader() - ) -> BeaconState: - fork = Fork( - previous_version=config.MERGE_FORK_VERSION, # [Modified in Merge] for testing only - current_version=config.MERGE_FORK_VERSION, # [Modified in Merge] - epoch=GENESIS_EPOCH, - ) - state = BeaconState( - genesis_time=eth1_timestamp + config.GENESIS_DELAY, - fork=fork, - eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))), - latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), - randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy - ) - - # Process deposits - leaves = list(map(lambda deposit: deposit.data, deposits)) - for index, deposit in enumerate(deposits): - deposit_data_list = List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[:index + 1]) - state.eth1_data.deposit_root = hash_tree_root(deposit_data_list) - process_deposit(state, deposit) - - # Process activations - for index, validator in enumerate(state.validators): - balance = state.balances[index] - validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) - if validator.effective_balance == MAX_EFFECTIVE_BALANCE: - validator.activation_eligibility_epoch = GENESIS_EPOCH - validator.activation_epoch = GENESIS_EPOCH - - # Set genesis validators root for domain separation and chain versioning - state.genesis_validators_root = hash_tree_root(state.validators) - - # Fill in sync committees - # Note: A duplicate committee is assigned for the current and next committee at genesis - state.current_sync_committee = get_next_sync_committee(state) - state.next_sync_committee = get_next_sync_committee(state) - - # [New in Merge] Initialize the execution payload header - # If empty, will initialize a chain that has not yet gone through the Merge transition - state.latest_execution_payload_header = execution_payload_header - - return state - - -def is_valid_genesis_state(state: BeaconState) -> bool: - if state.genesis_time < config.MIN_GENESIS_TIME: - return False - if len(get_active_validator_indices(state, GENESIS_EPOCH)) < config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: - return False - return True - - -def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool=True) -> None: - block = signed_block.message - # Process slots (including those with no blocks) since block - process_slots(state, block.slot) - # Verify signature - if validate_result: - assert verify_block_signature(state, signed_block) - # Process block - process_block(state, block) - # Verify state root - if validate_result: - assert block.state_root == hash_tree_root(state) - - -def verify_block_signature(state: BeaconState, signed_block: SignedBeaconBlock) -> bool: - proposer = state.validators[signed_block.message.proposer_index] - signing_root = compute_signing_root(signed_block.message, get_domain(state, DOMAIN_BEACON_PROPOSER)) - return bls.Verify(proposer.pubkey, signing_root, signed_block.signature) - - -def process_slots(state: BeaconState, slot: Slot) -> None: - assert state.slot < slot - while state.slot < slot: - process_slot(state) - # Process epoch on the start slot of the next epoch - if (state.slot + 1) % SLOTS_PER_EPOCH == 0: - process_epoch(state) - state.slot = Slot(state.slot + 1) - - -def process_slot(state: BeaconState) -> None: - # Cache state root - previous_state_root = hash_tree_root(state) - state.state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_state_root - # Cache latest block header state root - if state.latest_block_header.state_root == Bytes32(): - state.latest_block_header.state_root = previous_state_root - # Cache block root - previous_block_root = hash_tree_root(state.latest_block_header) - state.block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_block_root - - -def process_epoch(state: BeaconState) -> None: - process_justification_and_finalization(state) - process_inactivity_updates(state) - process_rewards_and_penalties(state) - process_registry_updates(state) - process_slashings(state) - process_eth1_data_reset(state) - process_effective_balance_updates(state) - process_slashings_reset(state) - process_randao_mixes_reset(state) - process_historical_roots_update(state) - process_participation_flag_updates(state) - process_sync_committee_updates(state) - process_withdrawals(state) # [New in Withdrawals] - - -def get_matching_source_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: - assert epoch in (get_previous_epoch(state), get_current_epoch(state)) - return state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations - - -def get_matching_target_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: - return [ - a for a in get_matching_source_attestations(state, epoch) - if a.data.target.root == get_block_root(state, epoch) - ] - - -def get_matching_head_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: - return [ - a for a in get_matching_target_attestations(state, epoch) - if a.data.beacon_block_root == get_block_root_at_slot(state, a.data.slot) - ] - - -def get_unslashed_attesting_indices(state: BeaconState, - attestations: Sequence[PendingAttestation]) -> Set[ValidatorIndex]: - output = set() # type: Set[ValidatorIndex] - for a in attestations: - output = output.union(get_attesting_indices(state, a.data, a.aggregation_bits)) - return set(filter(lambda index: not state.validators[index].slashed, output)) - - -def get_attesting_balance(state: BeaconState, attestations: Sequence[PendingAttestation]) -> Gwei: - """ - Return the combined effective balance of the set of unslashed validators participating in ``attestations``. - Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. - """ - return get_total_balance(state, get_unslashed_attesting_indices(state, attestations)) - - -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_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)) - current_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_current_epoch(state)) - total_active_balance = get_total_active_balance(state) - previous_target_balance = get_total_balance(state, previous_indices) - current_target_balance = get_total_balance(state, current_indices) - weigh_justification_and_finalization(state, total_active_balance, previous_target_balance, current_target_balance) - - -def weigh_justification_and_finalization(state: BeaconState, - total_active_balance: Gwei, - previous_epoch_target_balance: Gwei, - current_epoch_target_balance: Gwei) -> None: - 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 - - # 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 - if previous_epoch_target_balance * 3 >= total_active_balance * 2: - state.current_justified_checkpoint = Checkpoint(epoch=previous_epoch, - root=get_block_root(state, previous_epoch)) - state.justification_bits[1] = 0b1 - if current_epoch_target_balance * 3 >= total_active_balance * 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 - - -def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: - """ - Return the base reward for the validator defined by ``index`` with respect to the current ``state``. - """ - increments = state.validators[index].effective_balance // EFFECTIVE_BALANCE_INCREMENT - return Gwei(increments * get_base_reward_per_increment(state)) - - -def get_proposer_reward(state: BeaconState, attesting_index: ValidatorIndex) -> Gwei: - return Gwei(get_base_reward(state, attesting_index) // PROPOSER_REWARD_QUOTIENT) - - -def get_finality_delay(state: BeaconState) -> uint64: - return get_previous_epoch(state) - state.finalized_checkpoint.epoch - - -def is_in_inactivity_leak(state: BeaconState) -> bool: - return get_finality_delay(state) > MIN_EPOCHS_TO_INACTIVITY_PENALTY - - -def get_eligible_validator_indices(state: BeaconState) -> Sequence[ValidatorIndex]: - previous_epoch = get_previous_epoch(state) - 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) - ] - - -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)] * 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) - 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 - if is_in_inactivity_leak(state): - # Since full base reward will be canceled out by inactivity penalty deltas, - # optimal participation receives full base reward compensation here. - rewards[index] += get_base_reward(state, index) - else: - 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 - - -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 get_attestation_component_deltas(state, matching_source_attestations) - - -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 get_attestation_component_deltas(state, matching_target_attestations) - - -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 get_attestation_component_deltas(state, matching_head_attestations) - - -def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: - """ - Return proposer and inclusion delay micro-rewards/penalties for each validator. - """ - rewards = [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 - if index in get_attesting_indices(state, a.data, a.aggregation_bits) - ], key=lambda a: a.inclusion_delay) - rewards[attestation.proposer_index] += get_proposer_reward(state, index) - max_attester_reward = Gwei(get_base_reward(state, index) - get_proposer_reward(state, index)) - 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 - - -def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: - """ - Return the inactivity penalty deltas by considering timely target participation flags and inactivity scores. - """ - rewards = [Gwei(0) for _ in range(len(state.validators))] - penalties = [Gwei(0) for _ in range(len(state.validators))] - previous_epoch = get_previous_epoch(state) - matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, previous_epoch) - for index in get_eligible_validator_indices(state): - if index not in matching_target_indices: - penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index] - # [Modified in Merge] - penalty_denominator = config.INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_MERGE - penalties[index] += Gwei(penalty_numerator // penalty_denominator) - return rewards, penalties - - -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) - 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 - - -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_index_deltas(state, flag_index) for flag_index in range(len(PARTICIPATION_FLAG_WEIGHTS))] - 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]) - - -def process_registry_updates(state: BeaconState) -> None: - # Process activation eligibility and ejections - for index, validator in enumerate(state.validators): - if is_eligible_for_activation_queue(validator): - validator.activation_eligibility_epoch = get_current_epoch(state) + 1 - - if ( - is_active_validator(validator, get_current_epoch(state)) - and validator.effective_balance <= config.EJECTION_BALANCE - ): - initiate_validator_exit(state, ValidatorIndex(index)) - - # Queue validators eligible for activation and not yet dequeued for activation - activation_queue = sorted([ - index for index, validator in enumerate(state.validators) - if is_eligible_for_activation(state, validator) - # Order by the sequence of activation_eligibility_epoch setting and then index - ], key=lambda index: (state.validators[index].activation_eligibility_epoch, index)) - # Dequeued validators for activation up to churn limit - for index in activation_queue[:get_validator_churn_limit(state)]: - validator = state.validators[index] - validator.activation_epoch = compute_activation_exit_epoch(get_current_epoch(state)) - - -def process_slashings(state: BeaconState) -> None: - epoch = get_current_epoch(state) - total_balance = get_total_active_balance(state) - adjusted_total_slashing_balance = min( - sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_MERGE, # [Modified in Merge] - total_balance - ) - for index, validator in enumerate(state.validators): - if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch: - increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow - penalty_numerator = validator.effective_balance // increment * adjusted_total_slashing_balance - penalty = penalty_numerator // total_balance * increment - decrease_balance(state, ValidatorIndex(index), penalty) - - -def process_eth1_data_reset(state: BeaconState) -> None: - next_epoch = Epoch(get_current_epoch(state) + 1) - # Reset eth1 data votes - if next_epoch % EPOCHS_PER_ETH1_VOTING_PERIOD == 0: - state.eth1_data_votes = [] - - -def process_effective_balance_updates(state: BeaconState) -> None: - # Update effective balances with hysteresis - for index, validator in enumerate(state.validators): - balance = state.balances[index] - HYSTERESIS_INCREMENT = uint64(EFFECTIVE_BALANCE_INCREMENT // HYSTERESIS_QUOTIENT) - DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER - UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER - if ( - balance + DOWNWARD_THRESHOLD < validator.effective_balance - or validator.effective_balance + UPWARD_THRESHOLD < balance - ): - validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) - - -def process_slashings_reset(state: BeaconState) -> None: - next_epoch = Epoch(get_current_epoch(state) + 1) - # Reset slashings - state.slashings[next_epoch % EPOCHS_PER_SLASHINGS_VECTOR] = Gwei(0) - - -def process_randao_mixes_reset(state: BeaconState) -> None: - current_epoch = get_current_epoch(state) - next_epoch = Epoch(current_epoch + 1) - # Set randao mix - state.randao_mixes[next_epoch % EPOCHS_PER_HISTORICAL_VECTOR] = get_randao_mix(state, current_epoch) - - -def process_historical_roots_update(state: BeaconState) -> None: - # Set historical root accumulator - next_epoch = Epoch(get_current_epoch(state) + 1) - if next_epoch % (SLOTS_PER_HISTORICAL_ROOT // SLOTS_PER_EPOCH) == 0: - historical_batch = HistoricalBatch(block_roots=state.block_roots, state_roots=state.state_roots) - state.historical_roots.append(hash_tree_root(historical_batch)) - - -def process_participation_record_updates(state: BeaconState) -> None: - # Rotate current/previous epoch attestations - state.previous_epoch_attestations = state.current_epoch_attestations - state.current_epoch_attestations = [] - - -def process_block(state: BeaconState, block: BeaconBlock) -> None: - process_block_header(state, block) - if is_execution_enabled(state, block.body): - process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [New in Merge] - process_randao(state, block.body) - process_eth1_data(state, block.body) - process_operations(state, block.body) - process_sync_aggregate(state, block.body.sync_aggregate) - - -def process_block_header(state: BeaconState, block: BeaconBlock) -> None: - # Verify that the slots match - assert block.slot == state.slot - # Verify that the block is newer than latest block header - assert block.slot > state.latest_block_header.slot - # Verify that proposer index is the correct index - assert block.proposer_index == get_beacon_proposer_index(state) - # Verify that the parent matches - assert block.parent_root == hash_tree_root(state.latest_block_header) - # Cache current block as the new latest block - state.latest_block_header = BeaconBlockHeader( - slot=block.slot, - proposer_index=block.proposer_index, - parent_root=block.parent_root, - state_root=Bytes32(), # Overwritten in the next process_slot call - body_root=hash_tree_root(block.body), - ) - - # Verify proposer is not slashed - proposer = state.validators[block.proposer_index] - assert not proposer.slashed - - -def process_randao(state: BeaconState, body: BeaconBlockBody) -> None: - epoch = get_current_epoch(state) - # Verify RANDAO reveal - proposer = state.validators[get_beacon_proposer_index(state)] - signing_root = compute_signing_root(epoch, get_domain(state, DOMAIN_RANDAO)) - assert bls.Verify(proposer.pubkey, signing_root, body.randao_reveal) - # Mix in RANDAO reveal - mix = xor(get_randao_mix(state, epoch), hash(body.randao_reveal)) - state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR] = mix - - -def process_eth1_data(state: BeaconState, body: BeaconBlockBody) -> None: - state.eth1_data_votes.append(body.eth1_data) - if state.eth1_data_votes.count(body.eth1_data) * 2 > EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH: - state.eth1_data = body.eth1_data - - -def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: - # Verify that outstanding deposits are processed up to the maximum number of deposits - assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index) - - def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: - for operation in operations: - fn(state, operation) - - for_ops(body.proposer_slashings, process_proposer_slashing) - for_ops(body.attester_slashings, process_attester_slashing) - for_ops(body.attestations, process_attestation) - for_ops(body.deposits, process_deposit) - for_ops(body.voluntary_exits, process_voluntary_exit) - - -def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None: - header_1 = proposer_slashing.signed_header_1.message - header_2 = proposer_slashing.signed_header_2.message - - # Verify header slots match - assert header_1.slot == header_2.slot - # Verify header proposer indices match - assert header_1.proposer_index == header_2.proposer_index - # Verify the headers are different - assert header_1 != header_2 - # Verify the proposer is slashable - proposer = state.validators[header_1.proposer_index] - assert is_slashable_validator(proposer, get_current_epoch(state)) - # Verify signatures - for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2): - domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(signed_header.message.slot)) - signing_root = compute_signing_root(signed_header.message, domain) - assert bls.Verify(proposer.pubkey, signing_root, signed_header.signature) - - slash_validator(state, header_1.proposer_index) - - -def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSlashing) -> None: - attestation_1 = attester_slashing.attestation_1 - attestation_2 = attester_slashing.attestation_2 - assert is_slashable_attestation_data(attestation_1.data, attestation_2.data) - assert is_valid_indexed_attestation(state, attestation_1) - assert is_valid_indexed_attestation(state, attestation_2) - - slashed_any = False - indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices) - for index in sorted(indices): - if is_slashable_validator(state.validators[index], get_current_epoch(state)): - slash_validator(state, index) - slashed_any = True - assert slashed_any - - -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) - - # Participation flag indices - participation_flag_indices = get_attestation_participation_flag_indices(state, data, state.slot - data.slot) - - # Verify signature - assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) - - # Update epoch participation flags - if data.target.epoch == get_current_epoch(state): - epoch_participation = state.current_epoch_participation - else: - epoch_participation = state.previous_epoch_participation - - proposer_reward_numerator = 0 - for index in get_attesting_indices(state, data, attestation.aggregation_bits): - for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS): - if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index): - epoch_participation[index] = add_flag(epoch_participation[index], flag_index) - proposer_reward_numerator += get_base_reward(state, index) * weight - - # Reward proposer - proposer_reward_denominator = (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT - proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator) - increase_balance(state, get_beacon_proposer_index(state), proposer_reward) - - -def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: - amount = deposit.data.amount - effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) - - return Validator( - pubkey=deposit.data.pubkey, - withdrawal_credentials=deposit.data.withdrawal_credentials, - activation_eligibility_epoch=FAR_FUTURE_EPOCH, - activation_epoch=FAR_FUTURE_EPOCH, - exit_epoch=FAR_FUTURE_EPOCH, - withdrawable_epoch=FAR_FUTURE_EPOCH, - effective_balance=effective_balance, - ) - - -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 = [validator.pubkey for validator 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) - # Initialize validator if the deposit signature is valid - if bls.Verify(pubkey, signing_root, deposit.data.signature): - state.validators.append(get_validator_from_deposit(state, deposit)) - state.balances.append(amount) - state.previous_epoch_participation.append(ParticipationFlags(0b0000_0000)) - state.current_epoch_participation.append(ParticipationFlags(0b0000_0000)) - state.inactivity_scores.append(uint64(0)) - else: - # Increase balance by deposit amount - index = ValidatorIndex(validator_pubkeys.index(pubkey)) - increase_balance(state, index, amount) - - -def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVoluntaryExit) -> None: - voluntary_exit = signed_voluntary_exit.message - validator = state.validators[voluntary_exit.validator_index] - # Verify the validator is active - assert is_active_validator(validator, get_current_epoch(state)) - # Verify exit has not been initiated - assert validator.exit_epoch == FAR_FUTURE_EPOCH - # Exits must specify an epoch when they become valid; they are not valid before then - assert get_current_epoch(state) >= voluntary_exit.epoch - # Verify the validator has been active long enough - assert get_current_epoch(state) >= validator.activation_epoch + config.SHARD_COMMITTEE_PERIOD - # Verify signature - domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch) - signing_root = compute_signing_root(voluntary_exit, domain) - assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature) - # Initiate exit - initiate_validator_exit(state, voluntary_exit.validator_index) - - -def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store: - assert anchor_block.state_root == hash_tree_root(anchor_state) - anchor_root = hash_tree_root(anchor_block) - anchor_epoch = get_current_epoch(anchor_state) - justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) - finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) - proposer_boost_root = Root() - return Store( - time=uint64(anchor_state.genesis_time + config.SECONDS_PER_SLOT * anchor_state.slot), - genesis_time=anchor_state.genesis_time, - justified_checkpoint=justified_checkpoint, - finalized_checkpoint=finalized_checkpoint, - best_justified_checkpoint=justified_checkpoint, - proposer_boost_root=proposer_boost_root, - blocks={anchor_root: copy(anchor_block)}, - block_states={anchor_root: copy(anchor_state)}, - checkpoint_states={justified_checkpoint: copy(anchor_state)}, - ) - - -def get_slots_since_genesis(store: Store) -> int: - return (store.time - store.genesis_time) // config.SECONDS_PER_SLOT - - -def get_current_slot(store: Store) -> Slot: - return Slot(GENESIS_SLOT + get_slots_since_genesis(store)) - - -def compute_slots_since_epoch_start(slot: Slot) -> int: - return slot - compute_start_slot_at_epoch(compute_epoch_at_slot(slot)) - - -def get_ancestor(store: Store, root: Root, slot: Slot) -> Root: - block = store.blocks[root] - if block.slot > slot: - return get_ancestor(store, block.parent_root, slot) - elif block.slot == slot: - return root - else: - # root is older than queried slot, thus a skip slot. Return most recent root prior to slot - return root - - -def get_latest_attesting_balance(store: Store, root: Root) -> Gwei: - state = store.checkpoint_states[store.justified_checkpoint] - active_indices = get_active_validator_indices(state, get_current_epoch(state)) - attestation_score = Gwei(sum( - state.validators[i].effective_balance for i in active_indices - if (i in store.latest_messages - and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root) - )) - proposer_score = Gwei(0) - if store.proposer_boost_root != Root(): - block = store.blocks[root] - if get_ancestor(store, root, block.slot) == store.proposer_boost_root: - num_validators = len(get_active_validator_indices(state, get_current_epoch(state))) - avg_balance = get_total_active_balance(state) // num_validators - committee_size = num_validators // SLOTS_PER_EPOCH - committee_weight = committee_size * avg_balance - proposer_score = (committee_weight * config.PROPOSER_SCORE_BOOST) // 100 - return attestation_score + proposer_score - - -def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconBlock]) -> bool: - block = store.blocks[block_root] - children = [ - root for root in store.blocks.keys() - if store.blocks[root].parent_root == block_root - ] - - # If any children branches contain expected finalized/justified checkpoints, - # add to filtered block-tree and signal viability to parent. - if any(children): - filter_block_tree_result = [filter_block_tree(store, child, blocks) for child in children] - if any(filter_block_tree_result): - blocks[block_root] = block - return True - return False - - # If leaf block, check finalized/justified checkpoints as matching latest. - head_state = store.block_states[block_root] - - correct_justified = ( - store.justified_checkpoint.epoch == GENESIS_EPOCH - or head_state.current_justified_checkpoint == store.justified_checkpoint - ) - correct_finalized = ( - store.finalized_checkpoint.epoch == GENESIS_EPOCH - or head_state.finalized_checkpoint == store.finalized_checkpoint - ) - # If expected finalized/justified, add to viable block-tree and signal viability to parent. - if correct_justified and correct_finalized: - blocks[block_root] = block - return True - - # Otherwise, branch not viable - return False - - -def get_filtered_block_tree(store: Store) -> Dict[Root, BeaconBlock]: - """ - Retrieve a filtered block tree from ``store``, only returning branches - whose leaf state's justified/finalized info agrees with that in ``store``. - """ - base = store.justified_checkpoint.root - blocks: Dict[Root, BeaconBlock] = {} - filter_block_tree(store, base, blocks) - return blocks - - -def get_head(store: Store) -> Root: - # Get filtered block tree that only includes viable branches - blocks = get_filtered_block_tree(store) - # Execute the LMD-GHOST fork choice - head = store.justified_checkpoint.root - while True: - children = [ - root for root in blocks.keys() - if blocks[root].parent_root == head - ] - if len(children) == 0: - return head - # Sort by latest attesting balance with ties broken lexicographically - head = max(children, key=lambda root: (get_latest_attesting_balance(store, root), root)) - - -def should_update_justified_checkpoint(store: Store, new_justified_checkpoint: Checkpoint) -> bool: - """ - To address the bouncing attack, only update conflicting justified - checkpoints in the fork choice if in the early slots of the epoch. - Otherwise, delay incorporation of new justified checkpoint until next epoch boundary. - - See https://ethresear.ch/t/prevention-of-bouncing-attack-on-ffg/6114 for more detailed analysis and discussion. - """ - if compute_slots_since_epoch_start(get_current_slot(store)) < SAFE_SLOTS_TO_UPDATE_JUSTIFIED: - return True - - justified_slot = compute_start_slot_at_epoch(store.justified_checkpoint.epoch) - if not get_ancestor(store, new_justified_checkpoint.root, justified_slot) == store.justified_checkpoint.root: - return False - - return True - - -def validate_target_epoch_against_current_time(store: Store, attestation: Attestation) -> None: - target = attestation.data.target - - # Attestations must be from the current or previous epoch - 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] - - -def validate_on_attestation(store: Store, attestation: Attestation, is_from_block: bool) -> None: - target = attestation.data.target - - # If the given attestation is not from a beacon block message, we have to check the target epoch scope. - if not is_from_block: - validate_target_epoch_against_current_time(store, attestation) - - # Check that the epoch number and slot number are matching - assert target.epoch == compute_epoch_at_slot(attestation.data.slot) - - # 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 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 - # Attestations must not be for blocks in the future. If not, the attestation should not be considered - assert store.blocks[attestation.data.beacon_block_root].slot <= attestation.data.slot - - # LMD vote must be consistent with FFG vote target - target_slot = compute_start_slot_at_epoch(target.epoch) - assert target.root == get_ancestor(store, attestation.data.beacon_block_root, target_slot) - - # Attestations can only affect the fork choice of subsequent slots. - # Delay consideration in the fork choice until their slot is in the past. - assert get_current_slot(store) >= attestation.data.slot + 1 - - -def store_target_checkpoint_state(store: Store, target: Checkpoint) -> None: - # Store target checkpoint state if not yet seen - if target not in store.checkpoint_states: - base_state = copy(store.block_states[target.root]) - if base_state.slot < compute_start_slot_at_epoch(target.epoch): - process_slots(base_state, compute_start_slot_at_epoch(target.epoch)) - store.checkpoint_states[target] = base_state - - -def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None: - target = attestation.data.target - beacon_block_root = attestation.data.beacon_block_root - for i in attesting_indices: - if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch: - store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root) - - -def on_tick(store: Store, time: uint64) -> None: - previous_slot = get_current_slot(store) - - # update store time - store.time = time - - current_slot = get_current_slot(store) - - # Reset store.proposer_boost_root if this is a new slot - if current_slot > previous_slot: - store.proposer_boost_root = Root() - - # Not a new epoch, return - if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0): - return - - # Update store.justified_checkpoint if a better checkpoint on the store.finalized_checkpoint chain - if store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch: - finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) - ancestor_at_finalized_slot = get_ancestor(store, store.best_justified_checkpoint.root, finalized_slot) - if ancestor_at_finalized_slot == store.finalized_checkpoint.root: - store.justified_checkpoint = store.best_justified_checkpoint - - -def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: - """ - Run ``on_block`` upon receiving a new block. - - A block that is asserted as invalid due to unavailable PoW block may be valid at a later time, - consider scheduling it for later processing in such case. - """ - block = signed_block.message - # Parent block must be known - assert block.parent_root in store.block_states - # Make a copy of the state to avoid mutability issues - pre_state = copy(store.block_states[block.parent_root]) - # Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past. - assert get_current_slot(store) >= block.slot - - # Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) - finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) - assert block.slot > finalized_slot - # Check block is a descendant of the finalized block at the checkpoint finalized slot - assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root - - # Check the block is valid and compute the post-state - state = pre_state.copy() - state_transition(state, signed_block, True) - - # [New in Merge] - if is_merge_transition_block(pre_state, block.body): - validate_merge_block(block) - - # Add new block to the store - store.blocks[hash_tree_root(block)] = block - # Add new state for this block to the store - store.block_states[hash_tree_root(block)] = state - - # Add proposer score boost if the block is timely - time_into_slot = (store.time - store.genesis_time) % config.SECONDS_PER_SLOT - is_before_attesting_interval = time_into_slot < config.SECONDS_PER_SLOT // INTERVALS_PER_SLOT - if get_current_slot(store) == block.slot and is_before_attesting_interval: - store.proposer_boost_root = hash_tree_root(block) - - # Update justified checkpoint - if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: - if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch: - store.best_justified_checkpoint = state.current_justified_checkpoint - if should_update_justified_checkpoint(store, state.current_justified_checkpoint): - store.justified_checkpoint = state.current_justified_checkpoint - - # Update finalized checkpoint - if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch: - store.finalized_checkpoint = state.finalized_checkpoint - store.justified_checkpoint = state.current_justified_checkpoint - - -def on_attestation(store: Store, attestation: Attestation, is_from_block: bool=False) -> None: - """ - Run ``on_attestation`` upon receiving a new ``attestation`` from either within a block or directly on the wire. - - An ``attestation`` that is asserted as invalid may be valid at a later time, - consider scheduling it for later processing in such case. - """ - validate_on_attestation(store, attestation, is_from_block) - - store_target_checkpoint_state(store, attestation.data.target) - - # Get state at the `target` to fully validate attestation - target_state = store.checkpoint_states[attestation.data.target] - indexed_attestation = get_indexed_attestation(target_state, attestation) - assert is_valid_indexed_attestation(target_state, indexed_attestation) - - # Update latest messages for attesting indices - update_latest_messages(store, indexed_attestation.attesting_indices, attestation) - - -def check_if_validator_active(state: BeaconState, validator_index: ValidatorIndex) -> bool: - validator = state.validators[validator_index] - return is_active_validator(validator, get_current_epoch(state)) - - -def get_committee_assignment(state: BeaconState, - epoch: Epoch, - validator_index: ValidatorIndex - ) -> Optional[Tuple[Sequence[ValidatorIndex], CommitteeIndex, Slot]]: - """ - Return the committee assignment in the ``epoch`` for ``validator_index``. - ``assignment`` returned is a tuple of the following form: - * ``assignment[0]`` is the list of validators in the committee - * ``assignment[1]`` is the index to which the committee is assigned - * ``assignment[2]`` is the slot at which the committee is assigned - Return None if no assignment. - """ - next_epoch = Epoch(get_current_epoch(state) + 1) - assert epoch <= next_epoch - - start_slot = compute_start_slot_at_epoch(epoch) - committee_count_per_slot = get_committee_count_per_slot(state, epoch) - for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH): - for index in range(committee_count_per_slot): - committee = get_beacon_committee(state, Slot(slot), CommitteeIndex(index)) - if validator_index in committee: - return committee, CommitteeIndex(index), Slot(slot) - return None - - -def is_proposer(state: BeaconState, validator_index: ValidatorIndex) -> bool: - return get_beacon_proposer_index(state) == validator_index - - -def get_epoch_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_RANDAO, compute_epoch_at_slot(block.slot)) - signing_root = compute_signing_root(compute_epoch_at_slot(block.slot), domain) - return bls.Sign(privkey, signing_root) - - -def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64: - return uint64(state.genesis_time + slot * config.SECONDS_PER_SLOT) - - -def voting_period_start_time(state: BeaconState) -> uint64: - eth1_voting_period_start_slot = Slot(state.slot - state.slot % (EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH)) - return compute_time_at_slot(state, eth1_voting_period_start_slot) - - -def is_candidate_block(block: Eth1Block, period_start: uint64) -> bool: - return ( - block.timestamp + config.SECONDS_PER_ETH1_BLOCK * config.ETH1_FOLLOW_DISTANCE <= period_start - and block.timestamp + config.SECONDS_PER_ETH1_BLOCK * config.ETH1_FOLLOW_DISTANCE * 2 >= period_start - ) - - -def get_eth1_vote(state: BeaconState, eth1_chain: Sequence[Eth1Block]) -> Eth1Data: - period_start = voting_period_start_time(state) - # `eth1_chain` abstractly represents all blocks in the eth1 chain sorted by ascending block height - votes_to_consider = [ - get_eth1_data(block) for block in eth1_chain - if ( - is_candidate_block(block, period_start) - # Ensure cannot move back to earlier deposit contract states - and get_eth1_data(block).deposit_count >= state.eth1_data.deposit_count - ) - ] - - # Valid votes already cast during this period - valid_votes = [vote for vote in state.eth1_data_votes if vote in votes_to_consider] - - # Default vote on latest eth1 block data in the period range unless eth1 chain is not live - # Non-substantive casting for linter - state_eth1_data: Eth1Data = state.eth1_data - default_vote = votes_to_consider[len(votes_to_consider) - 1] if any(votes_to_consider) else state_eth1_data - - return max( - valid_votes, - key=lambda v: (valid_votes.count(v), -valid_votes.index(v)), # Tiebreak by smallest distance - default=default_vote - ) - - -def compute_new_state_root(state: BeaconState, block: BeaconBlock) -> Root: - temp_state: BeaconState = state.copy() - signed_block = SignedBeaconBlock(message=block) - state_transition(temp_state, signed_block, validate_result=False) - return hash_tree_root(temp_state) - - -def get_block_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(block.slot)) - signing_root = compute_signing_root(block, domain) - return bls.Sign(privkey, signing_root) - - -def get_attestation_signature(state: BeaconState, attestation_data: AttestationData, privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch) - signing_root = compute_signing_root(attestation_data, domain) - return bls.Sign(privkey, signing_root) - - -def compute_subnet_for_attestation(committees_per_slot: uint64, slot: Slot, committee_index: CommitteeIndex) -> uint64: - """ - Compute the correct subnet for an attestation for Phase 0. - Note, this mimics expected future behavior where attestations will be mapped to their shard subnet. - """ - slots_since_epoch_start = uint64(slot % SLOTS_PER_EPOCH) - committees_since_epoch_start = committees_per_slot * slots_since_epoch_start - - return uint64((committees_since_epoch_start + committee_index) % ATTESTATION_SUBNET_COUNT) - - -def get_slot_signature(state: BeaconState, slot: Slot, privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_SELECTION_PROOF, compute_epoch_at_slot(slot)) - signing_root = compute_signing_root(slot, domain) - return bls.Sign(privkey, signing_root) - - -def is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex, slot_signature: BLSSignature) -> bool: - committee = get_beacon_committee(state, slot, index) - modulo = max(1, len(committee) // TARGET_AGGREGATORS_PER_COMMITTEE) - return bytes_to_uint64(hash(slot_signature)[0:8]) % modulo == 0 - - -def get_aggregate_signature(attestations: Sequence[Attestation]) -> BLSSignature: - signatures = [attestation.signature for attestation in attestations] - return bls.Aggregate(signatures) - - -def get_aggregate_and_proof(state: BeaconState, - aggregator_index: ValidatorIndex, - aggregate: Attestation, - privkey: int) -> AggregateAndProof: - return AggregateAndProof( - aggregator_index=aggregator_index, - aggregate=aggregate, - selection_proof=get_slot_signature(state, aggregate.data.slot, privkey), - ) - - -def get_aggregate_and_proof_signature(state: BeaconState, - aggregate_and_proof: AggregateAndProof, - privkey: int) -> BLSSignature: - aggregate = aggregate_and_proof.aggregate - domain = get_domain(state, DOMAIN_AGGREGATE_AND_PROOF, compute_epoch_at_slot(aggregate.data.slot)) - signing_root = compute_signing_root(aggregate_and_proof, domain) - return bls.Sign(privkey, signing_root) - - -def compute_weak_subjectivity_period(state: BeaconState) -> uint64: - """ - Returns the weak subjectivity period for the current ``state``. - This computation takes into account the effect of: - - validator set churn (bounded by ``get_validator_churn_limit()`` per epoch), and - - validator balance top-ups (bounded by ``MAX_DEPOSITS * SLOTS_PER_EPOCH`` per epoch). - A detailed calculation can be found at: - https://github.com/runtimeverification/beacon-chain-verification/blob/master/weak-subjectivity/weak-subjectivity-analysis.pdf - """ - ws_period = config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY - N = len(get_active_validator_indices(state, get_current_epoch(state))) - t = get_total_active_balance(state) // N // ETH_TO_GWEI - T = MAX_EFFECTIVE_BALANCE // ETH_TO_GWEI - delta = get_validator_churn_limit(state) - Delta = MAX_DEPOSITS * SLOTS_PER_EPOCH - D = SAFETY_DECAY - - if T * (200 + 3 * D) < t * (200 + 12 * D): - epochs_for_validator_set_churn = ( - N * (t * (200 + 12 * D) - T * (200 + 3 * D)) // (600 * delta * (2 * t + T)) - ) - epochs_for_balance_top_ups = ( - N * (200 + 3 * D) // (600 * Delta) - ) - ws_period += max(epochs_for_validator_set_churn, epochs_for_balance_top_ups) - else: - ws_period += ( - 3 * N * D * t // (200 * Delta * (T - t)) - ) - - return ws_period - - -def is_within_weak_subjectivity_period(store: Store, ws_state: BeaconState, ws_checkpoint: Checkpoint) -> bool: - # Clients may choose to validate the input state against the input Weak Subjectivity Checkpoint - assert ws_state.latest_block_header.state_root == ws_checkpoint.root - assert compute_epoch_at_slot(ws_state.slot) == ws_checkpoint.epoch - - ws_period = compute_weak_subjectivity_period(ws_state) - ws_state_epoch = compute_epoch_at_slot(ws_state.slot) - current_epoch = compute_epoch_at_slot(get_current_slot(store)) - return current_epoch <= ws_state_epoch + ws_period - - -def add_flag(flags: ParticipationFlags, flag_index: int) -> ParticipationFlags: - """ - Return a new ``ParticipationFlags`` adding ``flag_index`` to ``flags``. - """ - flag = ParticipationFlags(2**flag_index) - return flags | flag - - -def has_flag(flags: ParticipationFlags, flag_index: int) -> bool: - """ - Return whether ``flags`` has ``flag_index`` set. - """ - flag = ParticipationFlags(2**flag_index) - return flags & flag == flag - - -def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorIndex]: - """ - Return the sync committee indices, with possible duplicates, for the next sync committee. - """ - epoch = Epoch(get_current_epoch(state) + 1) - - MAX_RANDOM_BYTE = 2**8 - 1 - active_validator_indices = get_active_validator_indices(state, epoch) - active_validator_count = uint64(len(active_validator_indices)) - seed = get_seed(state, epoch, DOMAIN_SYNC_COMMITTEE) - i = 0 - sync_committee_indices: List[ValidatorIndex] = [] - while len(sync_committee_indices) < SYNC_COMMITTEE_SIZE: - shuffled_index = compute_shuffled_index(uint64(i % active_validator_count), active_validator_count, seed) - candidate_index = active_validator_indices[shuffled_index] - random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] - effective_balance = state.validators[candidate_index].effective_balance - if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: - sync_committee_indices.append(candidate_index) - i += 1 - return sync_committee_indices - - -def get_next_sync_committee(state: BeaconState) -> SyncCommittee: - """ - Return the next sync committee, with possible pubkey duplicates. - """ - indices = get_next_sync_committee_indices(state) - pubkeys = [state.validators[index].pubkey for index in indices] - aggregate_pubkey = eth_aggregate_pubkeys(pubkeys) - return SyncCommittee(pubkeys=pubkeys, aggregate_pubkey=aggregate_pubkey) - - -def get_base_reward_per_increment(state: BeaconState) -> Gwei: - return Gwei(EFFECTIVE_BALANCE_INCREMENT * BASE_REWARD_FACTOR // integer_squareroot(get_total_active_balance(state))) - - -def get_unslashed_participating_indices(state: BeaconState, flag_index: int, epoch: Epoch) -> Set[ValidatorIndex]: - """ - Return the set of validator indices that are both active and unslashed for the given ``flag_index`` and ``epoch``. - """ - 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 - active_validator_indices = get_active_validator_indices(state, epoch) - participating_indices = [i for i in active_validator_indices if has_flag(epoch_participation[i], flag_index)] - return set(filter(lambda index: not state.validators[index].slashed, participating_indices)) - - -def get_attestation_participation_flag_indices(state: BeaconState, - data: AttestationData, - inclusion_delay: uint64) -> Sequence[int]: - """ - Return the flag indices that are satisfied by an attestation. - """ - if data.target.epoch == get_current_epoch(state): - justified_checkpoint = state.current_justified_checkpoint - else: - justified_checkpoint = state.previous_justified_checkpoint - - # Matching roots - is_matching_source = data.source == justified_checkpoint - is_matching_target = is_matching_source and data.target.root == get_block_root(state, data.target.epoch) - is_matching_head = is_matching_target and data.beacon_block_root == get_block_root_at_slot(state, data.slot) - assert is_matching_source - - participation_flag_indices = [] - if is_matching_source and inclusion_delay <= integer_squareroot(SLOTS_PER_EPOCH): - participation_flag_indices.append(TIMELY_SOURCE_FLAG_INDEX) - if is_matching_target and inclusion_delay <= SLOTS_PER_EPOCH: - participation_flag_indices.append(TIMELY_TARGET_FLAG_INDEX) - if is_matching_head and inclusion_delay == MIN_ATTESTATION_INCLUSION_DELAY: - participation_flag_indices.append(TIMELY_HEAD_FLAG_INDEX) - - return participation_flag_indices - - -def get_flag_index_deltas(state: BeaconState, flag_index: int) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: - """ - Return the deltas for a given ``flag_index`` by scanning through the participation flags. - """ - rewards = [Gwei(0)] * len(state.validators) - penalties = [Gwei(0)] * len(state.validators) - previous_epoch = get_previous_epoch(state) - unslashed_participating_indices = get_unslashed_participating_indices(state, flag_index, previous_epoch) - weight = PARTICIPATION_FLAG_WEIGHTS[flag_index] - unslashed_participating_balance = get_total_balance(state, unslashed_participating_indices) - unslashed_participating_increments = unslashed_participating_balance // EFFECTIVE_BALANCE_INCREMENT - active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT - for index in get_eligible_validator_indices(state): - base_reward = get_base_reward(state, index) - if index in unslashed_participating_indices: - if not is_in_inactivity_leak(state): - reward_numerator = base_reward * weight * unslashed_participating_increments - rewards[index] += Gwei(reward_numerator // (active_increments * WEIGHT_DENOMINATOR)) - elif flag_index != TIMELY_HEAD_FLAG_INDEX: - penalties[index] += Gwei(base_reward * weight // WEIGHT_DENOMINATOR) - return rewards, penalties - - -def process_sync_aggregate(state: BeaconState, sync_aggregate: SyncAggregate) -> None: - # Verify sync committee aggregate signature signing over the previous slot block root - committee_pubkeys = state.current_sync_committee.pubkeys - participant_pubkeys = [pubkey for pubkey, bit in zip(committee_pubkeys, sync_aggregate.sync_committee_bits) if bit] - previous_slot = max(state.slot, Slot(1)) - Slot(1) - domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, compute_epoch_at_slot(previous_slot)) - signing_root = compute_signing_root(get_block_root_at_slot(state, previous_slot), domain) - assert eth_fast_aggregate_verify(participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature) - - # Compute participant and proposer rewards - total_active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT - total_base_rewards = Gwei(get_base_reward_per_increment(state) * total_active_increments) - max_participant_rewards = Gwei(total_base_rewards * SYNC_REWARD_WEIGHT // WEIGHT_DENOMINATOR // SLOTS_PER_EPOCH) - participant_reward = Gwei(max_participant_rewards // SYNC_COMMITTEE_SIZE) - proposer_reward = Gwei(participant_reward * PROPOSER_WEIGHT // (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)) - - # Apply participant and proposer rewards - all_pubkeys = [v.pubkey for v in state.validators] - committee_indices = [ValidatorIndex(all_pubkeys.index(pubkey)) for pubkey in state.current_sync_committee.pubkeys] - for participant_index, participation_bit in zip(committee_indices, sync_aggregate.sync_committee_bits): - if participation_bit: - increase_balance(state, participant_index, participant_reward) - increase_balance(state, get_beacon_proposer_index(state), proposer_reward) - else: - decrease_balance(state, participant_index, participant_reward) - - -def process_inactivity_updates(state: BeaconState) -> None: - # Skip the genesis epoch as score updates are based on the previous epoch participation - if get_current_epoch(state) == GENESIS_EPOCH: - return - - for index in get_eligible_validator_indices(state): - # Increase the inactivity score of inactive validators - if index in get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)): - state.inactivity_scores[index] -= min(1, state.inactivity_scores[index]) - else: - state.inactivity_scores[index] += config.INACTIVITY_SCORE_BIAS - # Decrease the inactivity score of all eligible validators during a leak-free epoch - if not is_in_inactivity_leak(state): - state.inactivity_scores[index] -= min(config.INACTIVITY_SCORE_RECOVERY_RATE, state.inactivity_scores[index]) - - -def process_participation_flag_updates(state: BeaconState) -> None: - state.previous_epoch_participation = state.current_epoch_participation - state.current_epoch_participation = [ParticipationFlags(0b0000_0000) for _ in range(len(state.validators))] - - -def process_sync_committee_updates(state: BeaconState) -> None: - 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_next_sync_committee(state) - - -def eth_aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey: - return bls.AggregatePKs(pubkeys) - - -def eth_fast_aggregate_verify(pubkeys: Sequence[BLSPubkey], message: Bytes32, signature: BLSSignature) -> bool: - """ - Wrapper to ``bls.FastAggregateVerify`` accepting the ``G2_POINT_AT_INFINITY`` signature when ``pubkeys`` is empty. - """ - if len(pubkeys) == 0 and signature == G2_POINT_AT_INFINITY: - return True - return bls.FastAggregateVerify(pubkeys, message, signature) - - -def translate_participation(state: BeaconState, pending_attestations: Sequence[phase0.PendingAttestation]) -> None: - for attestation in pending_attestations: - data = attestation.data - inclusion_delay = attestation.inclusion_delay - # Translate attestation inclusion info to flag indices - participation_flag_indices = get_attestation_participation_flag_indices(state, data, inclusion_delay) - - # Apply flags to all attesting validators - epoch_participation = state.previous_epoch_participation - for index in get_attesting_indices(state, data, attestation.aggregation_bits): - for flag_index in participation_flag_indices: - epoch_participation[index] = add_flag(epoch_participation[index], flag_index) - - -def upgrade_to_altair(pre: phase0.BeaconState) -> BeaconState: - epoch = phase0.get_current_epoch(pre) - post = BeaconState( - # Versioning - genesis_time=pre.genesis_time, - genesis_validators_root=pre.genesis_validators_root, - slot=pre.slot, - fork=Fork( - previous_version=pre.fork.current_version, - current_version=config.ALTAIR_FORK_VERSION, - epoch=epoch, - ), - # History - latest_block_header=pre.latest_block_header, - block_roots=pre.block_roots, - state_roots=pre.state_roots, - historical_roots=pre.historical_roots, - # Eth1 - eth1_data=pre.eth1_data, - eth1_data_votes=pre.eth1_data_votes, - eth1_deposit_index=pre.eth1_deposit_index, - # Registry - validators=pre.validators, - balances=pre.balances, - # Randomness - randao_mixes=pre.randao_mixes, - # Slashings - slashings=pre.slashings, - # Participation - previous_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))], - current_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))], - # Finality - justification_bits=pre.justification_bits, - previous_justified_checkpoint=pre.previous_justified_checkpoint, - current_justified_checkpoint=pre.current_justified_checkpoint, - finalized_checkpoint=pre.finalized_checkpoint, - # Inactivity - inactivity_scores=[uint64(0) for _ in range(len(pre.validators))], - ) - # Fill in previous epoch participation from the pre state's pending attestations - translate_participation(post, pre.previous_epoch_attestations) - - # Fill in sync committees - # Note: A duplicate committee is assigned for the current and next committee at the fork boundary - post.current_sync_committee = get_next_sync_committee(post) - post.next_sync_committee = get_next_sync_committee(post) - return post - - -def compute_sync_committee_period(epoch: Epoch) -> uint64: - return epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - - -def is_assigned_to_sync_committee(state: BeaconState, - epoch: Epoch, - validator_index: ValidatorIndex) -> bool: - sync_committee_period = compute_sync_committee_period(epoch) - current_epoch = get_current_epoch(state) - current_sync_committee_period = compute_sync_committee_period(current_epoch) - next_sync_committee_period = current_sync_committee_period + 1 - assert sync_committee_period in (current_sync_committee_period, next_sync_committee_period) - - pubkey = state.validators[validator_index].pubkey - if sync_committee_period == current_sync_committee_period: - return pubkey in state.current_sync_committee.pubkeys - else: # sync_committee_period == next_sync_committee_period - return pubkey in state.next_sync_committee.pubkeys - - -def process_sync_committee_contributions(block: BeaconBlock, - contributions: Set[SyncCommitteeContribution]) -> None: - sync_aggregate = SyncAggregate() - signatures = [] - sync_subcommittee_size = SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT - - for contribution in contributions: - subcommittee_index = contribution.subcommittee_index - for index, participated in enumerate(contribution.aggregation_bits): - if participated: - participant_index = sync_subcommittee_size * subcommittee_index + index - sync_aggregate.sync_committee_bits[participant_index] = True - signatures.append(contribution.signature) - - sync_aggregate.sync_committee_signature = bls.Aggregate(signatures) - - block.body.sync_aggregate = sync_aggregate - - -def get_sync_committee_message(state: BeaconState, - block_root: Root, - validator_index: ValidatorIndex, - privkey: int) -> SyncCommitteeMessage: - epoch = get_current_epoch(state) - domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, epoch) - signing_root = compute_signing_root(block_root, domain) - signature = bls.Sign(privkey, signing_root) - - return SyncCommitteeMessage( - slot=state.slot, - beacon_block_root=block_root, - validator_index=validator_index, - signature=signature, - ) - - -def compute_subnets_for_sync_committee(state: BeaconState, validator_index: ValidatorIndex) -> Set[uint64]: - next_slot_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) - if compute_sync_committee_period(get_current_epoch(state)) == compute_sync_committee_period(next_slot_epoch): - sync_committee = state.current_sync_committee - else: - sync_committee = state.next_sync_committee - - target_pubkey = state.validators[validator_index].pubkey - sync_committee_indices = [index for index, pubkey in enumerate(sync_committee.pubkeys) if pubkey == target_pubkey] - return set([ - uint64(index // (SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT)) - for index in sync_committee_indices - ]) - - -def get_sync_committee_selection_proof(state: BeaconState, - slot: Slot, - subcommittee_index: uint64, - privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF, compute_epoch_at_slot(slot)) - signing_data = SyncAggregatorSelectionData( - slot=slot, - subcommittee_index=subcommittee_index, - ) - signing_root = compute_signing_root(signing_data, domain) - return bls.Sign(privkey, signing_root) - - -def is_sync_committee_aggregator(signature: BLSSignature) -> bool: - modulo = max(1, SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT // TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE) - return bytes_to_uint64(hash(signature)[0:8]) % modulo == 0 - - -def get_contribution_and_proof(state: BeaconState, - aggregator_index: ValidatorIndex, - contribution: SyncCommitteeContribution, - privkey: int) -> ContributionAndProof: - selection_proof = get_sync_committee_selection_proof( - state, - contribution.slot, - contribution.subcommittee_index, - privkey, - ) - return ContributionAndProof( - aggregator_index=aggregator_index, - contribution=contribution, - selection_proof=selection_proof, - ) - - -def get_contribution_and_proof_signature(state: BeaconState, - contribution_and_proof: ContributionAndProof, - privkey: int) -> BLSSignature: - contribution = contribution_and_proof.contribution - domain = get_domain(state, DOMAIN_CONTRIBUTION_AND_PROOF, compute_epoch_at_slot(contribution.slot)) - signing_root = compute_signing_root(contribution_and_proof, domain) - return bls.Sign(privkey, signing_root) - - -def get_sync_subcommittee_pubkeys(state: BeaconState, subcommittee_index: uint64) -> Sequence[BLSPubkey]: - # Committees assigned to `slot` sign for `slot - 1` - # This creates the exceptional logic below when transitioning between sync committee periods - next_slot_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) - if compute_sync_committee_period(get_current_epoch(state)) == compute_sync_committee_period(next_slot_epoch): - sync_committee = state.current_sync_committee - else: - sync_committee = state.next_sync_committee - - # Return pubkeys for the subcommittee index - sync_subcommittee_size = SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT - i = subcommittee_index * sync_subcommittee_size - return sync_committee.pubkeys[i:i + sync_subcommittee_size] - - -def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: - return uint64(generalized_index % 2**(floorlog2(generalized_index))) - - -def validate_light_client_update(snapshot: LightClientSnapshot, - update: LightClientUpdate, - genesis_validators_root: Root) -> None: - # Verify update slot is larger than snapshot slot - assert update.header.slot > snapshot.header.slot - - # Verify update does not skip a sync committee period - snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - assert update_period in (snapshot_period, snapshot_period + 1) - - # Verify update header root is the finalized root of the finality header, if specified - if update.finality_header == BeaconBlockHeader(): - signed_header = update.header - assert update.finality_branch == [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))] - else: - signed_header = update.finality_header - assert is_valid_merkle_branch( - leaf=hash_tree_root(update.header), - branch=update.finality_branch, - depth=floorlog2(FINALIZED_ROOT_INDEX), - index=get_subtree_index(FINALIZED_ROOT_INDEX), - root=update.finality_header.state_root, - ) - - # Verify update next sync committee if the update period incremented - if update_period == snapshot_period: - sync_committee = snapshot.current_sync_committee - assert update.next_sync_committee_branch == [Bytes32() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))] - else: - sync_committee = snapshot.next_sync_committee - assert is_valid_merkle_branch( - leaf=hash_tree_root(update.next_sync_committee), - branch=update.next_sync_committee_branch, - depth=floorlog2(NEXT_SYNC_COMMITTEE_INDEX), - index=get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX), - root=update.header.state_root, - ) - - # Verify sync committee has sufficient participants - assert sum(update.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS - - # Verify sync committee aggregate signature - participant_pubkeys = [pubkey for (bit, pubkey) in zip(update.sync_committee_bits, sync_committee.pubkeys) if bit] - domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version, genesis_validators_root) - signing_root = compute_signing_root(signed_header, domain) - assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature) - - -def apply_light_client_update(snapshot: LightClientSnapshot, update: LightClientUpdate) -> None: - snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - if update_period == snapshot_period + 1: - snapshot.current_sync_committee = snapshot.next_sync_committee - snapshot.next_sync_committee = update.next_sync_committee - snapshot.header = update.header - - -def process_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot, - genesis_validators_root: Root) -> None: - validate_light_client_update(store.snapshot, update, genesis_validators_root) - store.valid_updates.add(update) - - update_timeout = SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD - if ( - sum(update.sync_committee_bits) * 3 >= len(update.sync_committee_bits) * 2 - and update.finality_header != BeaconBlockHeader() - ): - # Apply update if (1) 2/3 quorum is reached and (2) we have a finality proof. - # Note that (2) means that the current light client design needs finality. - # It may be changed to re-organizable light client design. See the on-going issue consensus-specs#2182. - apply_light_client_update(store.snapshot, update) - store.valid_updates = set() - elif current_slot > store.snapshot.header.slot + update_timeout: - # Forced best update when the update timeout has elapsed - apply_light_client_update(store.snapshot, - max(store.valid_updates, key=lambda update: sum(update.sync_committee_bits))) - store.valid_updates = set() - - -def is_merge_transition_complete(state: BeaconState) -> bool: - return state.latest_execution_payload_header != ExecutionPayloadHeader() - - -def is_merge_transition_block(state: BeaconState, body: BeaconBlockBody) -> bool: - return not is_merge_transition_complete(state) and body.execution_payload != ExecutionPayload() - - -def is_execution_enabled(state: BeaconState, body: BeaconBlockBody) -> bool: - return is_merge_transition_block(state, body) or is_merge_transition_complete(state) - - -def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64: - slots_since_genesis = slot - GENESIS_SLOT - return uint64(state.genesis_time + slots_since_genesis * config.SECONDS_PER_SLOT) - - -def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None: - # Verify consistency of the parent hash with respect to the previous execution payload header - if is_merge_transition_complete(state): - assert payload.parent_hash == state.latest_execution_payload_header.block_hash - # Verify random - assert payload.random == get_randao_mix(state, get_current_epoch(state)) - # Verify timestamp - assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) - # Verify the execution payload is valid - assert execution_engine.execute_payload(payload) - # Cache execution payload header - state.latest_execution_payload_header = ExecutionPayloadHeader( - parent_hash=payload.parent_hash, - fee_recipient=payload.fee_recipient, - state_root=payload.state_root, - receipt_root=payload.receipt_root, - logs_bloom=payload.logs_bloom, - random=payload.random, - block_number=payload.block_number, - gas_limit=payload.gas_limit, - gas_used=payload.gas_used, - timestamp=payload.timestamp, - extra_data=payload.extra_data, - base_fee_per_gas=payload.base_fee_per_gas, - block_hash=payload.block_hash, - transactions_root=hash_tree_root(payload.transactions), - ) - - -def upgrade_to_merge(pre: altair.BeaconState) -> BeaconState: - epoch = altair.get_current_epoch(pre) - post = BeaconState( - # Versioning - genesis_time=pre.genesis_time, - genesis_validators_root=pre.genesis_validators_root, - slot=pre.slot, - fork=Fork( - previous_version=pre.fork.current_version, - current_version=config.MERGE_FORK_VERSION, - epoch=epoch, - ), - # History - latest_block_header=pre.latest_block_header, - block_roots=pre.block_roots, - state_roots=pre.state_roots, - historical_roots=pre.historical_roots, - # Eth1 - eth1_data=pre.eth1_data, - eth1_data_votes=pre.eth1_data_votes, - eth1_deposit_index=pre.eth1_deposit_index, - # Registry - validators=pre.validators, - balances=pre.balances, - # Randomness - randao_mixes=pre.randao_mixes, - # Slashings - slashings=pre.slashings, - # Participation - previous_epoch_participation=pre.previous_epoch_participation, - current_epoch_participation=pre.current_epoch_participation, - # Finality - justification_bits=pre.justification_bits, - previous_justified_checkpoint=pre.previous_justified_checkpoint, - current_justified_checkpoint=pre.current_justified_checkpoint, - finalized_checkpoint=pre.finalized_checkpoint, - # Inactivity - inactivity_scores=pre.inactivity_scores, - # Sync - current_sync_committee=pre.current_sync_committee, - next_sync_committee=pre.next_sync_committee, - # Execution-layer - latest_execution_payload_header=ExecutionPayloadHeader(), - ) - - return post - - -def is_valid_terminal_pow_block(block: PowBlock, parent: PowBlock) -> bool: - is_total_difficulty_reached = block.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY - is_parent_total_difficulty_valid = parent.total_difficulty < config.TERMINAL_TOTAL_DIFFICULTY - return is_total_difficulty_reached and is_parent_total_difficulty_valid - - -def validate_merge_block(block: BeaconBlock) -> None: - """ - Check the parent PoW block of execution payload is a valid terminal PoW block. - - Note: Unavailable PoW block(s) may later become available, - and a client software MAY delay a call to ``validate_merge_block`` - until the PoW block(s) become available. - """ - if config.TERMINAL_BLOCK_HASH != Hash32(): - # If `config.TERMINAL_BLOCK_HASH` is used as an override, the activation epoch must be reached. - assert compute_epoch_at_slot(block.slot) >= config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH - assert block.body.execution_payload.parent_hash == config.TERMINAL_BLOCK_HASH - return - - pow_block = get_pow_block(block.body.execution_payload.parent_hash) - # Check if `pow_block` is available - assert pow_block is not None - pow_parent = get_pow_block(pow_block.parent_hash) - # Check if `pow_parent` is available - assert pow_parent is not None - # Check if `pow_block` is a valid terminal PoW block - assert is_valid_terminal_pow_block(pow_block, pow_parent) - - -def get_pow_block_at_terminal_total_difficulty(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlock]: - # `pow_chain` abstractly represents all blocks in the PoW chain - for block in pow_chain.values(): - block_reached_ttd = block.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY - if block_reached_ttd: - # If genesis block, no parent exists so reaching TTD alone qualifies as valid terminal block - if block.parent_hash == Hash32(): - return block - parent = pow_chain[block.parent_hash] - parent_reached_ttd = parent.total_difficulty >= config.TERMINAL_TOTAL_DIFFICULTY - if not parent_reached_ttd: - return block - - return None - - -def get_terminal_pow_block(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlock]: - if config.TERMINAL_BLOCK_HASH != Hash32(): - # Terminal block hash override takes precedence over terminal total difficulty - if config.TERMINAL_BLOCK_HASH in pow_chain: - return pow_chain[config.TERMINAL_BLOCK_HASH] - else: - return None - - return get_pow_block_at_terminal_total_difficulty(pow_chain) - - -def prepare_execution_payload(state: BeaconState, - pow_chain: Dict[Hash32, PowBlock], - finalized_block_hash: Hash32, - suggested_fee_recipient: ExecutionAddress, - execution_engine: ExecutionEngine) -> Optional[PayloadId]: - if not is_merge_transition_complete(state): - is_terminal_block_hash_set = config.TERMINAL_BLOCK_HASH != Hash32() - is_activation_epoch_reached = get_current_epoch(state) >= config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH - if is_terminal_block_hash_set and not is_activation_epoch_reached: - # Terminal block hash is set but activation epoch is not yet reached, no prepare payload call is needed - return None - - terminal_pow_block = get_terminal_pow_block(pow_chain) - if terminal_pow_block is None: - # Pre-merge, no prepare payload call is needed - return None - # Signify merge via producing on top of the terminal PoW block - parent_hash = terminal_pow_block.block_hash - else: - # Post-merge, normal payload - parent_hash = state.latest_execution_payload_header.block_hash - - # Set the forkchoice head and initiate the payload build process - payload_attributes = PayloadAttributes( - timestamp=compute_timestamp_at_slot(state, state.slot), - random=get_randao_mix(state, get_current_epoch(state)), - suggested_fee_recipient=suggested_fee_recipient, - ) - return execution_engine.notify_forkchoice_updated(parent_hash, finalized_block_hash, payload_attributes) - - -def get_execution_payload(payload_id: Optional[PayloadId], execution_engine: ExecutionEngine) -> ExecutionPayload: - if payload_id is None: - # Pre-merge, empty payload - return ExecutionPayload() - else: - return execution_engine.get_payload(payload_id) - - -def withdraw(state: BeaconState, index: ValidatorIndex, amount: Gwei) -> None: - # Decrease the validator's balance - decrease_balance(state, index, amount) - # Create a corresponding withdrawal receipt - receipt = WithdrawalReceipt( - index=WithdrawalReceiptIndex(len(state.withdrawal_receipts)), - address=state.validators[index].withdrawal_credentials[12:], - amount=amount, - ) - state.withdrawal_receipts.append(receipt) - - -def is_withdrawable_validator(validator: Validator, epoch: Epoch) -> bool: - """ - Check if ``validator`` is withdrawable. - """ - return validator.withdrawable_epoch <= epoch - - -def process_withdrawals(state: BeaconState) -> None: - current_epoch = get_current_epoch(state) - for index, validator in enumerate(state.validators): - balance = state.balances[index] - is_balance_nonzero = state.balances[index] == 0 - is_eth1_withdrawal_prefix = validator.withdrawal_credentials[0] != ETH1_ADDRESS_WITHDRAWAL_PREFIX - if is_balance_nonzero and is_eth1_withdrawal_prefix and is_withdrawable_validator(validator, current_epoch): - withdraw(state, index, balance) - - -def get_eth1_data(block: Eth1Block) -> Eth1Data: - """ - A stub function return mocking Eth1Data. - """ - return Eth1Data( - deposit_root=block.deposit_root, - deposit_count=block.deposit_count, - block_hash=hash_tree_root(block)) - - -def cache_this(key_fn, value_fn, lru_size): # type: ignore - cache_dict = LRU(size=lru_size) - - def wrapper(*args, **kw): # type: ignore - key = key_fn(*args, **kw) - nonlocal cache_dict - if key not in cache_dict: - cache_dict[key] = value_fn(*args, **kw) - return cache_dict[key] - return wrapper - - -_compute_shuffled_index = compute_shuffled_index -compute_shuffled_index = cache_this( - lambda index, index_count, seed: (index, index_count, seed), - _compute_shuffled_index, lru_size=SLOTS_PER_EPOCH * 3) - -_get_total_active_balance = get_total_active_balance -get_total_active_balance = cache_this( - lambda state: (state.validators.hash_tree_root(), compute_epoch_at_slot(state.slot)), - _get_total_active_balance, lru_size=10) - -_get_base_reward = get_base_reward -get_base_reward = cache_this( - lambda state, index: (state.validators.hash_tree_root(), state.slot, index), - _get_base_reward, lru_size=2048) - -_get_committee_count_per_slot = get_committee_count_per_slot -get_committee_count_per_slot = cache_this( - lambda state, epoch: (state.validators.hash_tree_root(), epoch), - _get_committee_count_per_slot, lru_size=SLOTS_PER_EPOCH * 3) - -_get_active_validator_indices = get_active_validator_indices -get_active_validator_indices = cache_this( - lambda state, epoch: (state.validators.hash_tree_root(), epoch), - _get_active_validator_indices, lru_size=3) - -_get_beacon_committee = get_beacon_committee -get_beacon_committee = cache_this( - lambda state, slot, index: (state.validators.hash_tree_root(), state.randao_mixes.hash_tree_root(), slot, index), - _get_beacon_committee, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3) - -_get_matching_target_attestations = get_matching_target_attestations -get_matching_target_attestations = cache_this( - lambda state, epoch: (state.hash_tree_root(), epoch), - _get_matching_target_attestations, lru_size=10) - -_get_matching_head_attestations = get_matching_head_attestations -get_matching_head_attestations = cache_this( - lambda state, epoch: (state.hash_tree_root(), epoch), - _get_matching_head_attestations, lru_size=10) - -_get_attesting_indices = get_attesting_indices -get_attesting_indices = cache_this( - lambda state, data, bits: ( - state.randao_mixes.hash_tree_root(), - state.validators.hash_tree_root(), data.hash_tree_root(), bits.hash_tree_root() - ), - _get_attesting_indices, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3) - - -def get_generalized_index(ssz_class: Any, *path: Sequence[PyUnion[int, SSZVariableName]]) -> GeneralizedIndex: - ssz_path = Path(ssz_class) - for item in path: - ssz_path = ssz_path / item - return GeneralizedIndex(ssz_path.gindex()) - - -ExecutionState = Any - - -def get_pow_block(hash: Bytes32) -> Optional[PowBlock]: - return PowBlock(block_hash=hash, parent_hash=Bytes32(), total_difficulty=uint256(0)) - - -def get_execution_state(execution_state_root: Bytes32) -> ExecutionState: - pass - - -def get_pow_chain_head() -> PowBlock: - pass - - -class NoopExecutionEngine(ExecutionEngine): - - def execute_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: - return True - - def notify_forkchoice_updated(self: ExecutionEngine, - head_block_hash: Hash32, - finalized_block_hash: Hash32, - payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: - pass - - def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayload: - raise NotImplementedError("no default block production") - - -EXECUTION_ENGINE = NoopExecutionEngine() - - -assert FINALIZED_ROOT_INDEX == get_generalized_index(BeaconState, 'finalized_checkpoint', 'root') -assert NEXT_SYNC_COMMITTEE_INDEX == get_generalized_index(BeaconState, 'next_sync_committee') From 78d55839b9abe70a03ba47fda1dbd81a11942194 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 1 Dec 2021 16:42:12 -0700 Subject: [PATCH 04/98] fix tsts --- tests/core/pyspec/eth2spec/capella/__init__.py | 1 - tests/core/pyspec/eth2spec/test/helpers/fork_transition.py | 6 ++++++ tests/core/pyspec/eth2spec/withdrawals/__init__.py | 1 - 3 files changed, 6 insertions(+), 2 deletions(-) delete mode 100644 tests/core/pyspec/eth2spec/capella/__init__.py delete mode 100644 tests/core/pyspec/eth2spec/withdrawals/__init__.py diff --git a/tests/core/pyspec/eth2spec/capella/__init__.py b/tests/core/pyspec/eth2spec/capella/__init__.py deleted file mode 100644 index 91b4a68537..0000000000 --- a/tests/core/pyspec/eth2spec/capella/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import mainnet as spec # noqa:F401 diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index 853863e511..b75a3ef299 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -12,6 +12,7 @@ from eth2spec.test.helpers.constants import ( ALTAIR, MERGE, + CAPELLA, ) from eth2spec.test.helpers.deposits import ( prepare_state_and_deposit, @@ -147,6 +148,8 @@ def do_fork(state, spec, post_spec, fork_epoch, with_block=True, operation_dict= state = post_spec.upgrade_to_altair(state) elif post_spec.fork == MERGE: state = post_spec.upgrade_to_merge(state) + elif post_spec.fork == CAPELLA: + state = post_spec.upgrade_to_capella(state) assert state.fork.epoch == fork_epoch @@ -156,6 +159,9 @@ def do_fork(state, spec, post_spec, fork_epoch, with_block=True, operation_dict= elif post_spec.fork == MERGE: assert state.fork.previous_version == post_spec.config.ALTAIR_FORK_VERSION assert state.fork.current_version == post_spec.config.MERGE_FORK_VERSION + elif post_spec.fork == CAPELLA: + assert state.fork.previous_version == post_spec.config.MERGE_FORK_VERSION + assert state.fork.current_version == post_spec.config.CAPELLA_FORK_VERSION if with_block: return state, _state_transition_and_sign_block_at_slot(post_spec, state, operation_dict=operation_dict) diff --git a/tests/core/pyspec/eth2spec/withdrawals/__init__.py b/tests/core/pyspec/eth2spec/withdrawals/__init__.py deleted file mode 100644 index 91b4a68537..0000000000 --- a/tests/core/pyspec/eth2spec/withdrawals/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import mainnet as spec # noqa:F401 From ca740946057c71516c073d4112ebaac4a281474e Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 2 Dec 2021 10:31:56 -0700 Subject: [PATCH 05/98] Apply suggestions from code review Co-authored-by: Mikhail Kalinin --- specs/capella/beacon-chain.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index 2ab014daa5..bb085a2b53 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -149,7 +149,7 @@ def process_epoch(state: BeaconState) -> None: #### Withdrawals -*Note*: The function `process_inactivity_updates` is new. +*Note*: The function `process_withdrawals` is new. ```python def process_withdrawals(state: BeaconState) -> None: @@ -157,7 +157,7 @@ def process_withdrawals(state: BeaconState) -> None: for index, validator in enumerate(state.validators): balance = state.balances[index] is_balance_nonzero = state.balances[index] == 0 - is_eth1_withdrawal_prefix = validator.withdrawal_credentials[0] != ETH1_ADDRESS_WITHDRAWAL_PREFIX + is_eth1_withdrawal_prefix = validator.withdrawal_credentials[0] == ETH1_ADDRESS_WITHDRAWAL_PREFIX if is_balance_nonzero and is_eth1_withdrawal_prefix and is_withdrawable_validator(validator, current_epoch): withdraw(state, ValidatorIndex(index), balance) ``` From 3024dc8ba8a9975425b492dec0f3df7c5bacd211 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 2 Dec 2021 10:52:56 -0700 Subject: [PATCH 06/98] add withdrawn_epoch to capella validators --- presets/mainnet/capella.yaml | 2 +- presets/minimal/capella.yaml | 2 +- specs/capella/beacon-chain.md | 26 ++++++++++++++++--- specs/capella/fork.md | 16 +++++++++++- .../eth2spec/test/helpers/capella/fork.py | 16 ++++++++++-- 5 files changed, 53 insertions(+), 9 deletions(-) diff --git a/presets/mainnet/capella.yaml b/presets/mainnet/capella.yaml index 7d9da914ee..c5dfe1d4b8 100644 --- a/presets/mainnet/capella.yaml +++ b/presets/mainnet/capella.yaml @@ -1 +1 @@ -# Minimal preset - Sharding +# Minimal preset - Capella diff --git a/presets/minimal/capella.yaml b/presets/minimal/capella.yaml index 7d9da914ee..c5dfe1d4b8 100644 --- a/presets/minimal/capella.yaml +++ b/presets/minimal/capella.yaml @@ -1 +1 @@ -# Minimal preset - Sharding +# Minimal preset - Capella diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index bb085a2b53..808b52a95f 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -1,4 +1,4 @@ -# Cappela -- The Beacon Chain +# Capella -- The Beacon Chain ## Table of contents @@ -11,7 +11,7 @@ ## Introduction -Cappela is a consensus-layer upgrade containin a number of features related +Capella is a consensus-layer upgrade containin a number of features related to validator withdrawals. Including: * Automatic withdrawals of `withdrawable` validators * Partial withdrawals during block proposal @@ -40,6 +40,23 @@ to validator withdrawals. Including: ### Extended Containers +#### `Validator` + +```python +class Validator(Container): + pubkey: BLSPubkey + withdrawal_credentials: Bytes32 # Commitment to pubkey for withdrawals + effective_balance: Gwei # Balance at stake + slashed: boolean + # Status epochs + activation_eligibility_epoch: Epoch # When criteria for activation were met + activation_epoch: Epoch + exit_epoch: Epoch + withdrawable_epoch: Epoch # When validator can withdraw funds + withdrawn_epoch: Epoch # [New in Capella] +``` + + #### `BeaconState` ```python @@ -81,7 +98,7 @@ class BeaconState(Container): # Execution latest_execution_payload_header: ExecutionPayloadHeader # Withdrawals - withdrawal_receipts: List[WithdrawalReceipt, WITHDRAWAL_RECEIPT_LIMIT] # [New in Cappela] + withdrawal_receipts: List[WithdrawalReceipt, WITHDRAWAL_RECEIPT_LIMIT] # [New in Capella] ``` ### New containers @@ -144,7 +161,7 @@ def process_epoch(state: BeaconState) -> None: process_historical_roots_update(state) process_participation_flag_updates(state) process_sync_committee_updates(state) - process_withdrawals(state) # [New in Cappela] + process_withdrawals(state) # [New in Capella] ``` #### Withdrawals @@ -160,4 +177,5 @@ def process_withdrawals(state: BeaconState) -> None: is_eth1_withdrawal_prefix = validator.withdrawal_credentials[0] == ETH1_ADDRESS_WITHDRAWAL_PREFIX if is_balance_nonzero and is_eth1_withdrawal_prefix and is_withdrawable_validator(validator, current_epoch): withdraw(state, ValidatorIndex(index), balance) + validator.withdrawn_epoch = current_epoch ``` diff --git a/specs/capella/fork.md b/specs/capella/fork.md index e4c6549f74..f525128777 100644 --- a/specs/capella/fork.md +++ b/specs/capella/fork.md @@ -61,7 +61,7 @@ def upgrade_to_capella(pre: merge.BeaconState) -> BeaconState: eth1_data_votes=pre.eth1_data_votes, eth1_deposit_index=pre.eth1_deposit_index, # Registry - validators=pre.validators, + validators=[], balances=pre.balances, # Randomness randao_mixes=pre.randao_mixes, @@ -86,5 +86,19 @@ def upgrade_to_capella(pre: merge.BeaconState) -> BeaconState: withdrawal_receipts=[], ) + for pre_validator in pre.validators: + post_validator = Validator( + pubkey=pre_validator.pubkey, + withdrawal_credentials=pre_validator.withdrawal_credentials, + effective_balance=pre_validator.effective_balance, + slashed=pre_validator.slashed, + activation_eligibility_epoch=pre_validator.activation_eligibility_epoch, + activation_epoch=pre_validator.activation_epoch, + exit_epoch=pre_validator.exit_epoch, + withdrawable_epoch=pre_validator.withdrawable_epoch, + withdrawn_epoch=FAR_FUTURE_EPOCH, + ) + post.validators.append(post_validator) + return post ``` diff --git a/tests/core/pyspec/eth2spec/test/helpers/capella/fork.py b/tests/core/pyspec/eth2spec/test/helpers/capella/fork.py index f5905178e1..e8c41b76ad 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/capella/fork.py +++ b/tests/core/pyspec/eth2spec/test/helpers/capella/fork.py @@ -16,7 +16,7 @@ def run_fork_test(post_spec, pre_state): # Eth1 'eth1_data', 'eth1_data_votes', 'eth1_deposit_index', # Registry - 'validators', 'balances', + 'balances', # Randomness 'randao_mixes', # Slashings @@ -36,10 +36,22 @@ def run_fork_test(post_spec, pre_state): assert getattr(pre_state, field) == getattr(post_state, field) # Modified fields - modified_fields = ['fork'] + modified_fields = ['fork', 'validators'] for field in modified_fields: assert getattr(pre_state, field) != getattr(post_state, field) + assert len(pre_state.validators) == len(post_state.validators) + for pre_validator, post_validator in zip(pre_state.validators, post_state.validators): + stable_validator_fields = [ + 'pubkey', 'withdrawal_credentials', + 'effective_balance', + 'slashed', + 'activation_eligibility_epoch', 'activation_epoch', 'exit_epoch', 'withdrawable_epoch' + ] + for field in stable_validator_fields: + assert getattr(pre_validator, field) == getattr(post_validator, field) + assert post_validator.withdrawn_epoch == post_spec.FAR_FUTURE_EPOCH + assert pre_state.fork.current_version == post_state.fork.previous_version assert post_state.fork.current_version == post_spec.config.CAPELLA_FORK_VERSION assert post_state.fork.epoch == post_spec.get_current_epoch(post_state) From 2010994d10a2b4e9db1d25e5804504957f26f1e4 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 2 Dec 2021 11:56:05 -0700 Subject: [PATCH 07/98] add capella circleci workflow --- .circleci/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index beb0f26a4d..b552950ba6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -257,6 +257,9 @@ workflows: - test-merge: requires: - install_pyspec_test + - test-capella: + requires: + - install_pyspec_test - table_of_contents - codespell - lint: From 180abb90eccc85ec5b388e49a1be84e88a5ebbcb Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 2 Dec 2021 11:56:33 -0700 Subject: [PATCH 08/98] clean up some withdrawal logic and add tests --- specs/capella/beacon-chain.md | 11 +-- .../test_process_withdrawals.py | 95 +++++++++++++++++++ .../eth2spec/test/helpers/capella/fork.py | 2 +- .../eth2spec/test/helpers/epoch_processing.py | 1 + .../pyspec/eth2spec/test/helpers/genesis.py | 9 +- 5 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_withdrawals.py diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index 808b52a95f..d479fa17cb 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -140,7 +140,8 @@ def is_withdrawable_validator(validator: Validator, epoch: Epoch) -> bool: """ Check if ``validator`` is withdrawable. """ - return validator.withdrawable_epoch <= epoch + is_eth1_withdrawal_prefix = validator.withdrawal_credentials[0:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX + return is_eth1_withdrawal_prefix and validator.withdrawable_epoch <= epoch < validator.withdrawn_epoch ``` ## Beacon chain state transition function @@ -172,10 +173,8 @@ def process_epoch(state: BeaconState) -> None: def process_withdrawals(state: BeaconState) -> None: current_epoch = get_current_epoch(state) for index, validator in enumerate(state.validators): - balance = state.balances[index] - is_balance_nonzero = state.balances[index] == 0 - is_eth1_withdrawal_prefix = validator.withdrawal_credentials[0] == ETH1_ADDRESS_WITHDRAWAL_PREFIX - if is_balance_nonzero and is_eth1_withdrawal_prefix and is_withdrawable_validator(validator, current_epoch): - withdraw(state, ValidatorIndex(index), balance) + if is_withdrawable_validator(validator, current_epoch): + # TODO, consider the zero-balance case + withdraw(state, ValidatorIndex(index), state.balances[index]) validator.withdrawn_epoch = current_epoch ``` diff --git a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_withdrawals.py b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_withdrawals.py new file mode 100644 index 0000000000..7d889b8f4d --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_withdrawals.py @@ -0,0 +1,95 @@ +from random import Random + +from eth2spec.test.helpers.constants import MINIMAL +from eth2spec.test.context import ( + with_capella_and_later, + spec_state_test, +) +from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with + + +def set_validator_withdrawable(spec, state, index, withdrawable_epoch=None): + if withdrawable_epoch is None: + withdrawable_epoch = spec.get_current_epoch(state) + + validator = state.validators[index] + validator.withdrawable_epoch = withdrawable_epoch + validator.withdrawal_credentials = spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:] + + assert spec.is_withdrawable_validator(validator, withdrawable_epoch) + + +def run_process_withdrawals(spec, state, num_expected_withdrawals=None): + to_be_withdrawn_indices = [ + index for index, validator in enumerate(state.validators) + if spec.is_withdrawable_validator(validator, spec.get_current_epoch(state)) + ] + + if num_expected_withdrawals is not None: + assert len(to_be_withdrawn_indices) == num_expected_withdrawals + + yield from run_epoch_processing_with(spec, state, 'process_withdrawals') + + for index in to_be_withdrawn_indices: + validator = state.validators[index] + assert validator.withdrawn_epoch == spec.get_current_epoch(state) + assert state.balances[index] == 0 + + +@with_capella_and_later +@spec_state_test +def test_no_withdrawals(spec, state): + pre_validators = state.validators.copy() + yield from run_process_withdrawals(spec, state, 0) + + assert pre_validators == state.validators + + +@with_capella_and_later +@spec_state_test +def test_no_withdrawals_but_some_next_epoch(spec, state): + current_epoch = spec.get_current_epoch(state) + + # Make a few validators withdrawable at the *next* epoch + for index in range(3): + set_validator_withdrawable(spec, state, index, current_epoch + 1) + + yield from run_process_withdrawals(spec, state, 0) + + +@with_capella_and_later +@spec_state_test +def test_single_withdrawal(spec, state): + current_epoch = spec.get_current_epoch(state) + + # Make one validator withdrawable + set_validator_withdrawable(spec, state, current_epoch) + + yield from run_process_withdrawals(spec, state, 1) + + +@with_capella_and_later +@spec_state_test +def test_multi_withdrawal(spec, state): + current_epoch = spec.get_current_epoch(state) + + # Make a few validators withdrawable + for index in range(3): + set_validator_withdrawable(spec, state, index) + + yield from run_process_withdrawals(spec, state, 3) + + +@with_capella_and_later +@spec_state_test +def test_all_withdrawal(spec, state): + current_epoch = spec.get_current_epoch(state) + + # Make all validators withdrawable + for index in range(len(state.validators)): + set_validator_withdrawable(spec, state, index) + + yield from run_process_withdrawals(spec, state, len(state.validators)) + + + diff --git a/tests/core/pyspec/eth2spec/test/helpers/capella/fork.py b/tests/core/pyspec/eth2spec/test/helpers/capella/fork.py index e8c41b76ad..eb85da8bbb 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/capella/fork.py +++ b/tests/core/pyspec/eth2spec/test/helpers/capella/fork.py @@ -46,7 +46,7 @@ def run_fork_test(post_spec, pre_state): 'pubkey', 'withdrawal_credentials', 'effective_balance', 'slashed', - 'activation_eligibility_epoch', 'activation_epoch', 'exit_epoch', 'withdrawable_epoch' + 'activation_eligibility_epoch', 'activation_epoch', 'exit_epoch', 'withdrawable_epoch', ] for field in stable_validator_fields: assert getattr(pre_validator, field) == getattr(post_validator, field) diff --git a/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py b/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py index eed259e819..98984fa221 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py +++ b/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py @@ -28,6 +28,7 @@ def get_process_calls(spec): 'process_participation_record_updates' ), 'process_sync_committee_updates', # altair + 'process_withdrawals', # capella # TODO: add sharding processing functions when spec stabilizes. ] diff --git a/tests/core/pyspec/eth2spec/test/helpers/genesis.py b/tests/core/pyspec/eth2spec/test/helpers/genesis.py index ed90a7d4e8..d7c853fa05 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/genesis.py +++ b/tests/core/pyspec/eth2spec/test/helpers/genesis.py @@ -1,6 +1,6 @@ from eth2spec.test.helpers.constants import ( ALTAIR, MERGE, - FORKS_BEFORE_ALTAIR, FORKS_BEFORE_MERGE, + FORKS_BEFORE_ALTAIR, FORKS_BEFORE_MERGE, FORKS_BEFORE_CAPELLA, ) from eth2spec.test.helpers.keys import pubkeys @@ -9,7 +9,7 @@ def build_mock_validator(spec, i: int, balance: int): pubkey = pubkeys[i] # insecurely use pubkey as withdrawal key as well withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:] - return spec.Validator( + validator = spec.Validator( pubkey=pubkeys[i], withdrawal_credentials=withdrawal_credentials, activation_eligibility_epoch=spec.FAR_FUTURE_EPOCH, @@ -19,6 +19,11 @@ def build_mock_validator(spec, i: int, balance: int): effective_balance=min(balance - balance % spec.EFFECTIVE_BALANCE_INCREMENT, spec.MAX_EFFECTIVE_BALANCE) ) + if spec.fork not in FORKS_BEFORE_CAPELLA: + validator.withdrawn_epoch = spec.FAR_FUTURE_EPOCH + + return validator + def get_sample_genesis_execution_payload_header(spec, eth1_block_hash=None): From 6969c8af67f39c143e7c798c8fccada2715c4e47 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 2 Dec 2021 12:03:51 -0700 Subject: [PATCH 09/98] lint --- .../epoch_processing/test_process_withdrawals.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_withdrawals.py b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_withdrawals.py index 7d889b8f4d..f1f8e084d4 100644 --- a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_withdrawals.py +++ b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_withdrawals.py @@ -1,6 +1,3 @@ -from random import Random - -from eth2spec.test.helpers.constants import MINIMAL from eth2spec.test.context import ( with_capella_and_later, spec_state_test, @@ -60,10 +57,8 @@ def test_no_withdrawals_but_some_next_epoch(spec, state): @with_capella_and_later @spec_state_test def test_single_withdrawal(spec, state): - current_epoch = spec.get_current_epoch(state) - # Make one validator withdrawable - set_validator_withdrawable(spec, state, current_epoch) + set_validator_withdrawable(spec, state, 0) yield from run_process_withdrawals(spec, state, 1) @@ -71,8 +66,6 @@ def test_single_withdrawal(spec, state): @with_capella_and_later @spec_state_test def test_multi_withdrawal(spec, state): - current_epoch = spec.get_current_epoch(state) - # Make a few validators withdrawable for index in range(3): set_validator_withdrawable(spec, state, index) @@ -83,13 +76,8 @@ def test_multi_withdrawal(spec, state): @with_capella_and_later @spec_state_test def test_all_withdrawal(spec, state): - current_epoch = spec.get_current_epoch(state) - # Make all validators withdrawable for index in range(len(state.validators)): set_validator_withdrawable(spec, state, index) yield from run_process_withdrawals(spec, state, len(state.validators)) - - - From 59d48210a15d4ceb10ee9c68349b9311d6f1f862 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 2 Dec 2021 12:10:46 -0700 Subject: [PATCH 10/98] fix forkchoice tests --- .../test/phase0/unittests/fork_choice/test_on_attestation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py index 9007a778f1..b05f56ba99 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py @@ -1,7 +1,7 @@ from eth2spec.test.context import with_all_phases, spec_state_test from eth2spec.test.helpers.block import build_empty_block_for_next_slot from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE +from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE, CAPELLA from eth2spec.test.helpers.state import transition_to, state_transition_and_sign_block, next_epoch, next_slot from eth2spec.test.helpers.fork_choice import get_genesis_forkchoice_store @@ -19,7 +19,7 @@ def run_on_attestation(spec, state, store, attestation, valid=True): spec.on_attestation(store, attestation) sample_index = indexed_attestation.attesting_indices[0] - if spec.fork in (PHASE0, ALTAIR, MERGE): + if spec.fork in (PHASE0, ALTAIR, MERGE, CAPELLA): latest_message = spec.LatestMessage( epoch=attestation.data.target.epoch, root=attestation.data.beacon_block_root, From 365b1f46f9b18f13af871ce46f58c79659ef1c7d Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 2 Dec 2021 12:33:58 -0700 Subject: [PATCH 11/98] remove unnecessary definitions from CapellaSpecBuilder --- setup.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/setup.py b/setup.py index e5d5b4ca27..34e78ca216 100644 --- a/setup.py +++ b/setup.py @@ -561,22 +561,6 @@ def imports(cls, preset_name: str): from eth2spec.merge import {preset_name} as merge ''' - @classmethod - def preparations(cls): - return super().preparations() - - @classmethod - def sundry_functions(cls) -> str: - return super().sundry_functions() - - @classmethod - def hardcoded_ssz_dep_constants(cls) -> Dict[str, str]: - constants = { - 'FINALIZED_ROOT_INDEX': 'GeneralizedIndex(105)', - 'NEXT_SYNC_COMMITTEE_INDEX': 'GeneralizedIndex(55)', - } - return {**super().hardcoded_ssz_dep_constants(), **constants} - spec_builders = { builder.fork: builder From 3b474eb2fefe5db21abff62b85a9ef7bd491f844 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 27 Dec 2021 18:32:19 +0800 Subject: [PATCH 12/98] Add capella to test coverage report target --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 7ba494e0c9..c1fe0b20c0 100644 --- a/Makefile +++ b/Makefile @@ -97,12 +97,12 @@ install_test: # Testing against `minimal` config by default test: pyspec . venv/bin/activate; cd $(PY_SPEC_DIR); \ - python3 -m pytest -n 4 --disable-bls --cov=eth2spec.phase0.minimal --cov=eth2spec.altair.minimal --cov=eth2spec.bellatrix.minimal --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec + python3 -m pytest -n 4 --disable-bls --cov=eth2spec.phase0.minimal --cov=eth2spec.altair.minimal --cov=eth2spec.bellatrix.minimal --cov=eth2spec.capella.minimal --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec # Testing against `minimal` config by default find_test: pyspec . venv/bin/activate; cd $(PY_SPEC_DIR); \ - python3 -m pytest -k=$(K) --disable-bls --cov=eth2spec.phase0.minimal --cov=eth2spec.altair.minimal --cov=eth2spec.bellatrix.minimal --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec + python3 -m pytest -k=$(K) --disable-bls --cov=eth2spec.phase0.minimal --cov=eth2spec.altair.minimal --cov=eth2spec.bellatrix.minimal --cov=eth2spec.capella.minimal --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec citest: pyspec mkdir -p tests/core/pyspec/test-reports/eth2spec; From cbf314c67e1dd13387b5a8bb0b058d4b8031375e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 27 Dec 2021 18:44:03 +0800 Subject: [PATCH 13/98] Move capella fork tests to the correct folder and resolve conflicts --- .../capella/fork/test_capella_fork_basic.py | 16 ++++++++-------- .../capella/fork/test_capella_fork_random.py | 0 2 files changed, 8 insertions(+), 8 deletions(-) rename tests/core/pyspec/eth2spec/{ => test}/capella/fork/test_capella_fork_basic.py (84%) rename tests/core/pyspec/eth2spec/{ => test}/capella/fork/test_capella_fork_random.py (100%) diff --git a/tests/core/pyspec/eth2spec/capella/fork/test_capella_fork_basic.py b/tests/core/pyspec/eth2spec/test/capella/fork/test_capella_fork_basic.py similarity index 84% rename from tests/core/pyspec/eth2spec/capella/fork/test_capella_fork_basic.py rename to tests/core/pyspec/eth2spec/test/capella/fork/test_capella_fork_basic.py index 73ccbb13b9..a235c9e8a1 100644 --- a/tests/core/pyspec/eth2spec/capella/fork/test_capella_fork_basic.py +++ b/tests/core/pyspec/eth2spec/test/capella/fork/test_capella_fork_basic.py @@ -7,7 +7,7 @@ ) from eth2spec.test.utils import with_meta_tags from eth2spec.test.helpers.constants import ( - MERGE, CAPELLA, + BELLATRIX, CAPELLA, MINIMAL, ) from eth2spec.test.helpers.state import ( @@ -20,7 +20,7 @@ ) -@with_phases(phases=[MERGE], other_phases=[CAPELLA]) +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) @spec_test @with_state @with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) @@ -28,7 +28,7 @@ def test_fork_base_state(spec, phases, state): yield from run_fork_test(phases[CAPELLA], state) -@with_phases(phases=[MERGE], other_phases=[CAPELLA]) +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) @spec_test @with_state @with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) @@ -37,7 +37,7 @@ def test_fork_next_epoch(spec, phases, state): yield from run_fork_test(phases[CAPELLA], state) -@with_phases(phases=[MERGE], other_phases=[CAPELLA]) +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) @spec_test @with_state @with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) @@ -46,7 +46,7 @@ def test_fork_next_epoch_with_block(spec, phases, state): yield from run_fork_test(phases[CAPELLA], state) -@with_phases(phases=[MERGE], other_phases=[CAPELLA]) +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) @spec_test @with_state @with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) @@ -56,7 +56,7 @@ def test_fork_many_next_epoch(spec, phases, state): yield from run_fork_test(phases[CAPELLA], state) -@with_phases(phases=[MERGE], other_phases=[CAPELLA]) +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) @with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) @spec_test @with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) @@ -64,7 +64,7 @@ def test_fork_random_low_balances(spec, phases, state): yield from run_fork_test(phases[CAPELLA], state) -@with_phases(phases=[MERGE], other_phases=[CAPELLA]) +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) @with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) @spec_test @with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) @@ -72,7 +72,7 @@ def test_fork_random_misc_balances(spec, phases, state): yield from run_fork_test(phases[CAPELLA], state) -@with_phases(phases=[MERGE], other_phases=[CAPELLA]) +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) @with_presets([MINIMAL], reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") @with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) diff --git a/tests/core/pyspec/eth2spec/capella/fork/test_capella_fork_random.py b/tests/core/pyspec/eth2spec/test/capella/fork/test_capella_fork_random.py similarity index 100% rename from tests/core/pyspec/eth2spec/capella/fork/test_capella_fork_random.py rename to tests/core/pyspec/eth2spec/test/capella/fork/test_capella_fork_random.py From e80a142d836f6762abf100827ad135e513044831 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 23 Feb 2022 15:05:55 -0700 Subject: [PATCH 14/98] modified withdrawals for push --- specs/capella/beacon-chain.md | 148 ++++++++++++++++++++++++++++++---- 1 file changed, 132 insertions(+), 16 deletions(-) diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index d479fa17cb..29094e5c74 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -20,10 +20,6 @@ to validator withdrawals. Including: ## Custom types -| Name | SSZ equivalent | Description | -| - | - | - | -| `WithdrawalReceiptIndex` | `uint64` | a withdrawal receipt index | - ## Constants ## Preset @@ -32,7 +28,14 @@ to validator withdrawals. Including: | Name | Value | Unit | Duration | | - | - | :-: | :-: | -| `WITHDRAWAL_RECEIPT_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | withdrawal receipts| +| `WITHDRAWAL_TRANSACTION_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | withdrawal transactions enqueued in state| + +### Execution + +| Name | Value | Description | +| - | - | - | +| `WITHDRAWAL_TX_TYPE` | `Bytes1(0x05)` | EIP-2718 TX Type | +| `MAX_WITHDRAWAL_TRANSACTIONS_PER_PAYLOAD` | `uint64(2**4)` (= 16) | Maximum amount of withdrawal transactions allowed in each payload | ## Configuration @@ -56,7 +59,6 @@ class Validator(Container): withdrawn_epoch: Epoch # [New in Capella] ``` - #### `BeaconState` ```python @@ -98,18 +100,66 @@ class BeaconState(Container): # Execution latest_execution_payload_header: ExecutionPayloadHeader # Withdrawals - withdrawal_receipts: List[WithdrawalReceipt, WITHDRAWAL_RECEIPT_LIMIT] # [New in Capella] + withdrawal_receipts: List[WithdrawalTransaction, WITHDRAWAL_TRANSACTION_LIMIT] # [New in Capella] +``` + +#### `ExecutionPayload` + +```python +class ExecutionPayload(Container): + # Execution block header fields + parent_hash: Hash32 + fee_recipient: ExecutionAddress # 'beneficiary' in the yellow paper + state_root: Bytes32 + receipt_root: Bytes32 # 'receipts root' in the yellow paper + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + random: Bytes32 # 'difficulty' in the yellow paper + block_number: uint64 # 'number' in the yellow paper + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + # Extra payload fields + block_hash: Hash32 # Hash of execution block + transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] + withdrawal_transactions: List[WithdrawalTransaction, MAX_WITHDRAWAL_TRANSACTIONS_PER_PAYLOAD] # [New in Capella] +``` + +#### `ExecutionPayloadHeader` + +```python +class ExecutionPayloadHeader(Container): + # Execution block header fields + parent_hash: Hash32 + fee_recipient: ExecutionAddress + state_root: Bytes32 + receipt_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + random: Bytes32 + block_number: uint64 + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + # Extra payload fields + block_hash: Hash32 # Hash of execution block + transactions_root: Root + withdrawal_transactions_root: Root # [New in Capella] ``` ### New containers -#### `WithdrawalReceipt` +#### `WithdrawalTransaction` + +New EIP-2718 transaction type, with the format being the single byte `WITHDRAWAL_TX_TYPE` +followed by an SSZ encoding of the `WithdrawalTransaction` container comprising the transaction contents. ```python -class WithdrawalReceipt(Container): - index: WithdrawalReceiptIndex +class WithdrawalTransaction(Container): address: ExecutionAddress - amount: Gwei + value: Gwei ``` ## Helpers @@ -123,8 +173,7 @@ def withdraw(state: BeaconState, index: ValidatorIndex, amount: Gwei) -> None: # Decrease the validator's balance decrease_balance(state, index, amount) # Create a corresponding withdrawal receipt - receipt = WithdrawalReceipt( - index=WithdrawalReceiptIndex(len(state.withdrawal_receipts)), + receipt = WithdrawalTransaction( address=state.validators[index].withdrawal_credentials[12:], amount=amount, ) @@ -162,15 +211,15 @@ def process_epoch(state: BeaconState) -> None: process_historical_roots_update(state) process_participation_flag_updates(state) process_sync_committee_updates(state) - process_withdrawals(state) # [New in Capella] + process_full_withdrawals(state) # [New in Capella] ``` #### Withdrawals -*Note*: The function `process_withdrawals` is new. +*Note*: The function `process_full_withdrawals` is new. ```python -def process_withdrawals(state: BeaconState) -> None: +def process_full_withdrawals(state: BeaconState) -> None: current_epoch = get_current_epoch(state) for index, validator in enumerate(state.validators): if is_withdrawable_validator(validator, current_epoch): @@ -178,3 +227,70 @@ def process_withdrawals(state: BeaconState) -> None: withdraw(state, ValidatorIndex(index), state.balances[index]) validator.withdrawn_epoch = current_epoch ``` + +### Block processing + +```python +def process_block(state: BeaconState, block: BeaconBlock) -> None: + process_block_header(state, block) + process_withdrawal_transactions(state, block.body.execution_payload) # [New in Capella] + process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [Modified in Capella] + process_randao(state, block.body) + process_eth1_data(state, block.body) + process_operations(state, block.body) + process_sync_aggregate(state, block.body.sync_aggregate) +``` + +#### New `process_withdrawal_transactions` + +```python +def process_withdrawal_transactions(state: BeaconState, payload: ExecutionPayload) -> None: + num_withdrawal_transactions = min(MAX_WITHDRAWAL_TRANSACTIONS_PER_PAYLOAD, len(state.withdrawal_receipts)) + dequeued_withdrawal_receipts = state.withdrawal_receipts[:num_withdrawal_transactions] + + assert len(dequeued_withdrawal_receipts) == len(payload.withdrawal_transactions) + for dequeued_receipt, withdrawal_transaction in zip(dequeued_withdrawal_receipts, payload.withdrawal_transactions): + assert dequeued_receipt == withdrawal_transaction + + # Ensure no withdrawal type transactions in the normal payload transactions + # assert no_withdrawal_type_transactions_in(payload.transactions) + + # Remove dequeued receipts from state + state.withdrawal_receipts = state.withdrawal_receipts[num_withdrawal_transactions:] +``` + +#### Modified `process_execution_payload` + +*Note*: The function `process_execution_payload` is modified to use the new `ExecutionPayloadHeader` type. + +```python +def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None: + # Verify consistency of the parent hash with respect to the previous execution payload header + if is_merge_transition_complete(state): + assert payload.parent_hash == state.latest_execution_payload_header.block_hash + # Verify random + assert payload.random == get_randao_mix(state, get_current_epoch(state)) + # Verify timestamp + assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) + # Verify the execution payload is valid + assert execution_engine.execute_payload(payload) + # Cache execution payload header + state.latest_execution_payload_header = ExecutionPayloadHeader( + parent_hash=payload.parent_hash, + fee_recipient=payload.fee_recipient, + state_root=payload.state_root, + receipt_root=payload.receipt_root, + logs_bloom=payload.logs_bloom, + random=payload.random, + block_number=payload.block_number, + gas_limit=payload.gas_limit, + gas_used=payload.gas_used, + timestamp=payload.timestamp, + extra_data=payload.extra_data, + base_fee_per_gas=payload.base_fee_per_gas, + block_hash=payload.block_hash, + transactions_root=hash_tree_root(payload.transactions), + withdrawal_transactions=hash_tree_root(payload.withdrawal_transactions), + ) +``` + From e49f73c9a4353bcc77bbc9753bdc2fff4ab1781f Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 23 Feb 2022 15:15:24 -0700 Subject: [PATCH 15/98] fix tx type --- specs/capella/beacon-chain.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index 29094e5c74..d515cce4db 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -22,6 +22,12 @@ to validator withdrawals. Including: ## Constants +We define the following Python custom types for type hinting and readability: + +| Name | SSZ equivalent | Description | +| - | - | - | +| `TransactionType` | `Bytes1` | an EIP-2718 type | + ## Preset ### State list lengths @@ -34,7 +40,7 @@ to validator withdrawals. Including: | Name | Value | Description | | - | - | - | -| `WITHDRAWAL_TX_TYPE` | `Bytes1(0x05)` | EIP-2718 TX Type | +| `TX_TYPE_WITHDRAWAL` | `TransactionType('0x05')` | EIP-2718 TX Type | | `MAX_WITHDRAWAL_TRANSACTIONS_PER_PAYLOAD` | `uint64(2**4)` (= 16) | Maximum amount of withdrawal transactions allowed in each payload | ## Configuration @@ -153,7 +159,7 @@ class ExecutionPayloadHeader(Container): #### `WithdrawalTransaction` -New EIP-2718 transaction type, with the format being the single byte `WITHDRAWAL_TX_TYPE` +New EIP-2718 transaction type, with the format being the single byte `TX_TYPE_WITHDRAWAL` followed by an SSZ encoding of the `WithdrawalTransaction` container comprising the transaction contents. ```python From eef2dbb2b6a8e40b17b5a03eea275fbdbc1a4fdd Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 25 Feb 2022 01:22:02 +0800 Subject: [PATCH 16/98] Ensure that the SSZ classes are ordered --- setup.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index f2bc1d7114..818428aedc 100644 --- a/setup.py +++ b/setup.py @@ -12,6 +12,9 @@ import ast import subprocess import sys +import copy +from collections import OrderedDict + # NOTE: have to programmatically include third-party dependencies in `setup.py`. def installPackage(package: str): @@ -724,7 +727,6 @@ def dependency_order_class_objects(objects: Dict[str, str], custom_types: Dict[s for item in [dep, key] + key_list[key_list.index(dep)+1:]: objects[item] = objects.pop(item) - def combine_ssz_objects(old_objects: Dict[str, str], new_objects: Dict[str, str], custom_types) -> Dict[str, str]: """ Takes in old spec and new spec ssz objects, combines them, @@ -814,7 +816,12 @@ def _build_spec(preset_name: str, fork: str, spec_object = combine_spec_objects(spec_object, value) class_objects = {**spec_object.ssz_objects, **spec_object.dataclasses} - dependency_order_class_objects(class_objects, spec_object.custom_types) + + # Ensure it's ordered after multiple forks + new_objects = {} + while OrderedDict(new_objects) != OrderedDict(class_objects): + new_objects = copy.deepcopy(class_objects) + dependency_order_class_objects(class_objects, spec_object.custom_types) return objects_to_spec(preset_name, spec_object, spec_builders[fork], class_objects) From 0b89c7fb181040228c261c6410bdf4be165769d8 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Thu, 24 Feb 2022 12:43:57 -0700 Subject: [PATCH 17/98] Update specs/capella/beacon-chain.md --- specs/capella/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index d515cce4db..234da46f2f 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -11,7 +11,7 @@ ## Introduction -Capella is a consensus-layer upgrade containin a number of features related +Capella is a consensus-layer upgrade containing a number of features related to validator withdrawals. Including: * Automatic withdrawals of `withdrawable` validators * Partial withdrawals during block proposal From d513f5cf17dea58a688bce3623e1be91085dc208 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 24 Feb 2022 14:26:15 -0700 Subject: [PATCH 18/98] fix lint --- specs/capella/beacon-chain.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index 234da46f2f..8d50419708 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -239,8 +239,9 @@ def process_full_withdrawals(state: BeaconState) -> None: ```python def process_block(state: BeaconState, block: BeaconBlock) -> None: process_block_header(state, block) - process_withdrawal_transactions(state, block.body.execution_payload) # [New in Capella] - process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [Modified in Capella] + if is_execution_enabled(state, block.body): + process_withdrawal_transactions(state, block.body.execution_payload) # [New in Capella] + process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [Modified in Capella] process_randao(state, block.body) process_eth1_data(state, block.body) process_operations(state, block.body) @@ -274,20 +275,20 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe # Verify consistency of the parent hash with respect to the previous execution payload header if is_merge_transition_complete(state): assert payload.parent_hash == state.latest_execution_payload_header.block_hash - # Verify random - assert payload.random == get_randao_mix(state, get_current_epoch(state)) + # Verify prev_randao + assert payload.prev_randao == get_randao_mix(state, get_current_epoch(state)) # Verify timestamp assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) # Verify the execution payload is valid - assert execution_engine.execute_payload(payload) + assert execution_engine.notify_new_payload(payload) # Cache execution payload header state.latest_execution_payload_header = ExecutionPayloadHeader( parent_hash=payload.parent_hash, fee_recipient=payload.fee_recipient, state_root=payload.state_root, - receipt_root=payload.receipt_root, + receipts_root=payload.receipts_root, logs_bloom=payload.logs_bloom, - random=payload.random, + prev_randao=payload.prev_randao, block_number=payload.block_number, gas_limit=payload.gas_limit, gas_used=payload.gas_used, @@ -299,4 +300,3 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe withdrawal_transactions=hash_tree_root(payload.withdrawal_transactions), ) ``` - From f5dab5b6663afe61f669897a0128a4ab8def56b7 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 24 Feb 2022 15:06:31 -0700 Subject: [PATCH 19/98] fix fully withdrawal tests --- specs/capella/beacon-chain.md | 20 +++++++++--------- .../test_process_full_withdrawals.py} | 21 +++++++++++-------- 2 files changed, 22 insertions(+), 19 deletions(-) rename tests/core/pyspec/eth2spec/test/{altair/epoch_processing/test_process_withdrawals.py => capella/epoch_processing/test_process_full_withdrawals.py} (71%) diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index 8d50419708..a8f509a940 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -117,9 +117,9 @@ class ExecutionPayload(Container): parent_hash: Hash32 fee_recipient: ExecutionAddress # 'beneficiary' in the yellow paper state_root: Bytes32 - receipt_root: Bytes32 # 'receipts root' in the yellow paper + receipts_root: Bytes32 logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - random: Bytes32 # 'difficulty' in the yellow paper + prev_randao: Bytes32 # 'difficulty' in the yellow paper block_number: uint64 # 'number' in the yellow paper gas_limit: uint64 gas_used: uint64 @@ -140,9 +140,9 @@ class ExecutionPayloadHeader(Container): parent_hash: Hash32 fee_recipient: ExecutionAddress state_root: Bytes32 - receipt_root: Bytes32 + receipts_root: Bytes32 logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - random: Bytes32 + prev_randao: Bytes32 block_number: uint64 gas_limit: uint64 gas_used: uint64 @@ -165,7 +165,7 @@ followed by an SSZ encoding of the `WithdrawalTransaction` container comprising ```python class WithdrawalTransaction(Container): address: ExecutionAddress - value: Gwei + amount: Gwei ``` ## Helpers @@ -188,12 +188,12 @@ def withdraw(state: BeaconState, index: ValidatorIndex, amount: Gwei) -> None: ### Predicates -#### `is_withdrawable_validator` +#### `is_fully_withdrawable_validator` ```python -def is_withdrawable_validator(validator: Validator, epoch: Epoch) -> bool: +def is_fully_withdrawable_validator(validator: Validator, epoch: Epoch) -> bool: """ - Check if ``validator`` is withdrawable. + Check if ``validator`` is fully withdrawable. """ is_eth1_withdrawal_prefix = validator.withdrawal_credentials[0:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX return is_eth1_withdrawal_prefix and validator.withdrawable_epoch <= epoch < validator.withdrawn_epoch @@ -228,7 +228,7 @@ def process_epoch(state: BeaconState) -> None: def process_full_withdrawals(state: BeaconState) -> None: current_epoch = get_current_epoch(state) for index, validator in enumerate(state.validators): - if is_withdrawable_validator(validator, current_epoch): + if is_fully_withdrawable_validator(validator, current_epoch): # TODO, consider the zero-balance case withdraw(state, ValidatorIndex(index), state.balances[index]) validator.withdrawn_epoch = current_epoch @@ -297,6 +297,6 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe base_fee_per_gas=payload.base_fee_per_gas, block_hash=payload.block_hash, transactions_root=hash_tree_root(payload.transactions), - withdrawal_transactions=hash_tree_root(payload.withdrawal_transactions), + withdrawal_transactions_root=hash_tree_root(payload.withdrawal_transactions), ) ``` diff --git a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_withdrawals.py b/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_full_withdrawals.py similarity index 71% rename from tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_withdrawals.py rename to tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_full_withdrawals.py index f1f8e084d4..ef7b7c8aca 100644 --- a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_withdrawals.py +++ b/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_full_withdrawals.py @@ -13,31 +13,34 @@ def set_validator_withdrawable(spec, state, index, withdrawable_epoch=None): validator.withdrawable_epoch = withdrawable_epoch validator.withdrawal_credentials = spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:] - assert spec.is_withdrawable_validator(validator, withdrawable_epoch) + assert spec.is_fully_withdrawable_validator(validator, withdrawable_epoch) -def run_process_withdrawals(spec, state, num_expected_withdrawals=None): +def run_process_full_withdrawals(spec, state, num_expected_withdrawals=None): + pre_withdrawal_receipts = state.withdrawal_receipts to_be_withdrawn_indices = [ index for index, validator in enumerate(state.validators) - if spec.is_withdrawable_validator(validator, spec.get_current_epoch(state)) + if spec.is_fully_withdrawable_validator(validator, spec.get_current_epoch(state)) ] if num_expected_withdrawals is not None: assert len(to_be_withdrawn_indices) == num_expected_withdrawals - yield from run_epoch_processing_with(spec, state, 'process_withdrawals') + yield from run_epoch_processing_with(spec, state, 'process_full_withdrawals') for index in to_be_withdrawn_indices: validator = state.validators[index] assert validator.withdrawn_epoch == spec.get_current_epoch(state) assert state.balances[index] == 0 + assert len(state.withdrawal_receipts) == len(pre_withdrawal_receipts) + num_expected_withdrawals + @with_capella_and_later @spec_state_test def test_no_withdrawals(spec, state): pre_validators = state.validators.copy() - yield from run_process_withdrawals(spec, state, 0) + yield from run_process_full_withdrawals(spec, state, 0) assert pre_validators == state.validators @@ -51,7 +54,7 @@ def test_no_withdrawals_but_some_next_epoch(spec, state): for index in range(3): set_validator_withdrawable(spec, state, index, current_epoch + 1) - yield from run_process_withdrawals(spec, state, 0) + yield from run_process_full_withdrawals(spec, state, 0) @with_capella_and_later @@ -60,7 +63,7 @@ def test_single_withdrawal(spec, state): # Make one validator withdrawable set_validator_withdrawable(spec, state, 0) - yield from run_process_withdrawals(spec, state, 1) + yield from run_process_full_withdrawals(spec, state, 1) @with_capella_and_later @@ -70,7 +73,7 @@ def test_multi_withdrawal(spec, state): for index in range(3): set_validator_withdrawable(spec, state, index) - yield from run_process_withdrawals(spec, state, 3) + yield from run_process_full_withdrawals(spec, state, 3) @with_capella_and_later @@ -80,4 +83,4 @@ def test_all_withdrawal(spec, state): for index in range(len(state.validators)): set_validator_withdrawable(spec, state, index) - yield from run_process_withdrawals(spec, state, len(state.validators)) + yield from run_process_full_withdrawals(spec, state, len(state.validators)) From 0a55f062d77c78a31cf8701f2b12ca88409367ea Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 24 Feb 2022 15:29:34 -0700 Subject: [PATCH 20/98] fix execution paylaod tests --- specs/capella/beacon-chain.md | 3 +++ .../pyspec/eth2spec/test/helpers/execution_payload.py | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index a8f509a940..38cda0f2f9 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -162,6 +162,9 @@ class ExecutionPayloadHeader(Container): New EIP-2718 transaction type, with the format being the single byte `TX_TYPE_WITHDRAWAL` followed by an SSZ encoding of the `WithdrawalTransaction` container comprising the transaction contents. +*Note*: This container is used for both a special TX that goes into an `ExecutionPayload` +as well as in the `BeaconState`'s `withdrawal_receipts` queue. + ```python class WithdrawalTransaction(Container): address: ExecutionAddress diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index 14602a2cd6..36fcca2c05 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -1,3 +1,6 @@ +from eth2spec.test.helpers.constants import FORKS_BEFORE_CAPELLA + + def build_empty_execution_payload(spec, state, randao_mix=None): """ Assuming a pre-state of the same slot, build a valid ExecutionPayload without any transactions. @@ -32,7 +35,7 @@ def build_empty_execution_payload(spec, state, randao_mix=None): def get_execution_payload_header(spec, execution_payload): - return spec.ExecutionPayloadHeader( + payload_header = spec.ExecutionPayloadHeader( parent_hash=execution_payload.parent_hash, fee_recipient=execution_payload.fee_recipient, state_root=execution_payload.state_root, @@ -48,6 +51,9 @@ def get_execution_payload_header(spec, execution_payload): block_hash=execution_payload.block_hash, transactions_root=spec.hash_tree_root(execution_payload.transactions) ) + if spec.fork not in FORKS_BEFORE_CAPELLA: + payload_header.withdrawal_transactions_root = spec.hash_tree_root(execution_payload.withdrawal_transactions) + return payload_header def build_state_with_incomplete_transition(spec, state): From c2ff09441d4cfdac5b9bccb28291c9bc304af921 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 1 Mar 2022 18:34:53 +1100 Subject: [PATCH 21/98] Allow any non-merge block --- sync/optimistic.md | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 8e6acc4427..4f6637cbcf 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -81,10 +81,17 @@ def is_execution_block(block: BeaconBlock) -> bool: ```python def is_optimistic_candidate_block(opt_store: OptimisticStore, current_slot: Slot, block: BeaconBlock) -> bool: + if is_execution_block(opt_store.blocks[block.parent_root]): + return True + justified_root = opt_store.block_states[opt_store.head_block_root].current_justified_checkpoint.root - justified_is_execution_block = is_execution_block(opt_store.blocks[justified_root]) - block_is_deep = block.slot + SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY <= current_slot - return justified_is_execution_block or block_is_deep + if is_execution_block(opt_store.blocks[justified_root]): + return True + + if block.slot + SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY <= current_slot: + return True + + return False ``` Let only a node which returns `is_optimistic(opt_store, head) is True` be an *optimistic @@ -99,14 +106,21 @@ behaviours without regard for optimistic sync. ### When to optimistically import blocks A block MAY be optimistically imported when -`is_optimistic_candidate_block(opt_store, current_slot, block)` returns -`True`. This ensures that blocks are only optimistically imported if either: +`is_optimistic_candidate_block(opt_store, current_slot, block)` returns `True`. +This ensures that blocks are only optimistically imported if one or more of the +following are true: +1. The parent of the block has execution enabled. 1. The justified checkpoint has execution enabled. 1. The current slot (as per the system clock) is at least `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` ahead of the slot of the block being imported. +In effect, there are restrictions on when a *merge block* can be optimistically +imported. The merge block is the first block in any chain where +`is_execution_block(block) == True`. Any descendant of a transition block may +be imported optimistically at any time. + *See [Fork Choice Poisoning](#fork-choice-poisoning) for the motivations behind these conditions.* From f071931e46f30de86721fe18298746d7d814bd52 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 1 Mar 2022 18:46:39 +1100 Subject: [PATCH 22/98] Address indenting --- sync/optimistic.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 4f6637cbcf..8b3c1a7307 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -81,17 +81,17 @@ def is_execution_block(block: BeaconBlock) -> bool: ```python def is_optimistic_candidate_block(opt_store: OptimisticStore, current_slot: Slot, block: BeaconBlock) -> bool: - if is_execution_block(opt_store.blocks[block.parent_root]): - return True + if is_execution_block(opt_store.blocks[block.parent_root]): + return True justified_root = opt_store.block_states[opt_store.head_block_root].current_justified_checkpoint.root if is_execution_block(opt_store.blocks[justified_root]): - return True + return True - if block.slot + SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY <= current_slot: - return True + if block.slot + SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY <= current_slot: + return True - return False + return False ``` Let only a node which returns `is_optimistic(opt_store, head) is True` be an *optimistic From 4fbe1626550d5a20f5e1cc596725ca4547f0ff37 Mon Sep 17 00:00:00 2001 From: Aditya Asgaonkar Date: Tue, 1 Mar 2022 11:42:49 -0800 Subject: [PATCH 23/98] Add on_attester_slashing() and related test --- specs/phase0/fork-choice.md | 27 +++++- .../test/phase0/fork_choice/test_get_head.py | 83 +++++++++++++++++++ 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index de0a2e7856..920746da2e 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -101,6 +101,7 @@ class Store(object): finalized_checkpoint: Checkpoint best_justified_checkpoint: Checkpoint proposer_boost_root: Root + has_equivocated: Set[ValidatorIndex] blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) block_states: Dict[Root, BeaconState] = field(default_factory=dict) checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict) @@ -129,6 +130,7 @@ def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) - finalized_checkpoint=finalized_checkpoint, best_justified_checkpoint=justified_checkpoint, proposer_boost_root=proposer_boost_root, + has_equivocated=set(), blocks={anchor_root: copy(anchor_block)}, block_states={anchor_root: copy(anchor_state)}, checkpoint_states={justified_checkpoint: copy(anchor_state)}, @@ -179,6 +181,7 @@ def get_latest_attesting_balance(store: Store, root: Root) -> Gwei: attestation_score = Gwei(sum( state.validators[i].effective_balance for i in active_indices if (i in store.latest_messages + and not i in store.has_equivocated and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root) )) if store.proposer_boost_root == Root(): @@ -358,8 +361,9 @@ def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIn target = attestation.data.target beacon_block_root = attestation.data.beacon_block_root for i in attesting_indices: - if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch: - store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root) + if i not in store.has_equivocated: + if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch: + store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root) ``` @@ -459,3 +463,22 @@ def on_attestation(store: Store, attestation: Attestation, is_from_block: bool=F # Update latest messages for attesting indices update_latest_messages(store, indexed_attestation.attesting_indices, attestation) ``` + +#### `on_attester_slashing` + +```python +def on_attester_slashing(store: Store, attester_slashing: AttesterSlashing) -> None: + """ + Run ``on_attester_slashing`` upon receiving a new ``AttesterSlashing`` from either within a block or directly on the wire. + """ + attestation_1 = attester_slashing.attestation_1 + attestation_2 = attester_slashing.attestation_2 + assert is_slashable_attestation_data(attestation_1.data, attestation_2.data) + state = store.block_states[store.justified_checkpoint.root] + assert is_valid_indexed_attestation(state, attestation_1) + assert is_valid_indexed_attestation(state, attestation_2) + + indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices) + for index in sorted(indices): + store.has_equivocated.add(index) +``` \ No newline at end of file diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py index 5e4d247e73..469c8e9d45 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py @@ -24,6 +24,8 @@ next_epoch, state_transition_and_sign_block, ) +from tests.core.pyspec.eth2spec.test.helpers.block import apply_empty_block +from tests.core.pyspec.eth2spec.test.helpers.fork_choice import run_on_attestation rng = random.Random(1001) @@ -338,3 +340,84 @@ def test_proposer_boost_correct_head(spec, state): }) yield 'steps', test_steps + + +@with_all_phases +@spec_state_test +def test_discard_equivocations(spec, state): + test_steps = [] + genesis_state = state.copy() + + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + anchor_root = get_anchor_root(spec, state) + assert spec.get_head(store) == anchor_root + test_steps.append({ + 'checks': { + 'head': get_formatted_head_output(spec, store), + } + }) + + # Build block that serves as head before discarding equivocations + state_1 = genesis_state.copy() + next_slots(spec, state_1, 3) + block_1 = build_empty_block_for_next_slot(spec, state_1) + signed_block_1 = state_transition_and_sign_block(spec, state_1, block_1) + + # Build equivocating attestations to feed to store + state_eqv = state_1.copy() + block_eqv = apply_empty_block(spec, state_eqv, state_eqv.slot+1) + attestation_eqv = get_valid_attestation(spec, state_eqv, slot=block_eqv.slot, signed=True) + + next_slots(spec, state_1, 1) + attestation = get_valid_attestation(spec, state_1, slot=block_eqv.slot, signed=True) + assert spec.is_slashable_attestation_data(attestation.data, attestation_eqv.data) + + indexed_attestation = spec.get_indexed_attestation(state_1, attestation) + indexed_attestation_eqv = spec.get_indexed_attestation(state_eqv, attestation_eqv) + attester_slashing = spec.AttesterSlashing(attestation_1=indexed_attestation, attestation_2=indexed_attestation_eqv) + + # Build block that serves as head after discarding equivocations + state_2 = genesis_state.copy() + next_slots(spec, state_2, 2) + block_2 = build_empty_block_for_next_slot(spec, state_2) + signed_block_2 = state_transition_and_sign_block(spec, state_2.copy(), block_2) + while spec.hash_tree_root(block_1) >= spec.hash_tree_root(block_2): + block_2.body.graffiti = spec.Bytes32(hex(rng.getrandbits(8 * 32))[2:].zfill(64)) + signed_block_2 = state_transition_and_sign_block(spec, state_2.copy(), block_2) + assert spec.hash_tree_root(block_1) < spec.hash_tree_root(block_2) + + # Tick to (block_eqv.slot + 2) slot time + time = store.genesis_time + (block_eqv.slot + 2) * spec.config.SECONDS_PER_SLOT + on_tick_and_append_step(spec, store, time, test_steps) + + # Process block_2 + yield from add_block(spec, store, signed_block_2, test_steps) + assert store.proposer_boost_root == spec.Root() + assert spec.get_head(store) == spec.hash_tree_root(block_2) + + # Process block_1 + # The head should remain block_2 + yield from add_block(spec, store, signed_block_1, test_steps) + assert store.proposer_boost_root == spec.Root() + assert spec.get_head(store) == spec.hash_tree_root(block_2) + + # Process attestation + # The head should change to block_1 + run_on_attestation(spec, store, attestation) + assert spec.get_head(store) == spec.hash_tree_root(block_1) + + # Process attester_slashing + # The head should revert to block_2 + spec.on_attester_slashing(store, attester_slashing) + assert spec.get_head(store) == spec.hash_tree_root(block_2) + + test_steps.append({ + 'checks': { + 'head': get_formatted_head_output(spec, store), + } + }) + + yield 'steps', test_steps \ No newline at end of file From 7f31c80b8f0ab406f5e2f7500e06c5d5b77ef662 Mon Sep 17 00:00:00 2001 From: Aditya Asgaonkar Date: Tue, 1 Mar 2022 11:47:45 -0800 Subject: [PATCH 24/98] Fix lint & CI --- specs/phase0/fork-choice.md | 6 ++++-- .../eth2spec/test/phase0/fork_choice/test_get_head.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 920746da2e..4d1c967efb 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -32,6 +32,7 @@ - [`on_tick`](#on_tick) - [`on_block`](#on_block) - [`on_attestation`](#on_attestation) + - [`on_attester_slashing`](#on_attester_slashing) @@ -181,7 +182,7 @@ def get_latest_attesting_balance(store: Store, root: Root) -> Gwei: attestation_score = Gwei(sum( state.validators[i].effective_balance for i in active_indices if (i in store.latest_messages - and not i in store.has_equivocated + and i not in store.has_equivocated and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root) )) if store.proposer_boost_root == Root(): @@ -469,7 +470,8 @@ def on_attestation(store: Store, attestation: Attestation, is_from_block: bool=F ```python def on_attester_slashing(store: Store, attester_slashing: AttesterSlashing) -> None: """ - Run ``on_attester_slashing`` upon receiving a new ``AttesterSlashing`` from either within a block or directly on the wire. + Run ``on_attester_slashing`` upon receiving a new ``AttesterSlashing`` from either within a block or directly + on the wire. """ attestation_1 = attester_slashing.attestation_1 attestation_2 = attester_slashing.attestation_2 diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py index 469c8e9d45..1f37625a8c 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py @@ -368,7 +368,7 @@ def test_discard_equivocations(spec, state): # Build equivocating attestations to feed to store state_eqv = state_1.copy() - block_eqv = apply_empty_block(spec, state_eqv, state_eqv.slot+1) + block_eqv = apply_empty_block(spec, state_eqv, state_eqv.slot + 1) attestation_eqv = get_valid_attestation(spec, state_eqv, slot=block_eqv.slot, signed=True) next_slots(spec, state_1, 1) @@ -420,4 +420,4 @@ def test_discard_equivocations(spec, state): } }) - yield 'steps', test_steps \ No newline at end of file + yield 'steps', test_steps From 70e8243450a20151fb42ff4baa8c0689e7c54c43 Mon Sep 17 00:00:00 2001 From: Nishant Das Date: Wed, 2 Mar 2022 12:39:21 +0800 Subject: [PATCH 25/98] Update p2p-interface.md --- 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 0a0eb11925..208414a772 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -355,7 +355,7 @@ The following validations MUST pass before forwarding the `signed_aggregate_and_ (via both gossip and non-gossip sources) (a client MAY queue aggregates for processing once block is retrieved). - _[REJECT]_ The block being voted for (`aggregate.data.beacon_block_root`) passes validation. -- _[REJECT]_ The current `finalized_checkpoint` is an ancestor of the `block` defined by `aggregate.data.beacon_block_root` -- i.e. +- _[IGNORE]_ The current `finalized_checkpoint` is an ancestor of the `block` defined by `aggregate.data.beacon_block_root` -- i.e. `get_ancestor(store, aggregate.data.beacon_block_root, compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)) == store.finalized_checkpoint.root` From bf25d323fc77535f7a168452756257b92b78a54e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 2 Mar 2022 19:03:06 +1100 Subject: [PATCH 26/98] Use merge consistently --- sync/optimistic.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 8b3c1a7307..3237c3f654 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -118,8 +118,8 @@ following are true: In effect, there are restrictions on when a *merge block* can be optimistically imported. The merge block is the first block in any chain where -`is_execution_block(block) == True`. Any descendant of a transition block may -be imported optimistically at any time. +`is_execution_block(block) == True`. Any descendant of a merge block may be +imported optimistically at any time. *See [Fork Choice Poisoning](#fork-choice-poisoning) for the motivations behind these conditions.* From 37b8a89bb13a895c8cb5df81cabfe87aa6235064 Mon Sep 17 00:00:00 2001 From: Aditya Asgaonkar Date: Thu, 3 Mar 2022 11:43:52 -0800 Subject: [PATCH 27/98] Apply code review from @djrtwo --- specs/phase0/fork-choice.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 4d1c967efb..bc611b74bb 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -102,7 +102,7 @@ class Store(object): finalized_checkpoint: Checkpoint best_justified_checkpoint: Checkpoint proposer_boost_root: Root - has_equivocated: Set[ValidatorIndex] + equivocating_indices: Set[ValidatorIndex] blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) block_states: Dict[Root, BeaconState] = field(default_factory=dict) checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict) @@ -131,7 +131,7 @@ def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) - finalized_checkpoint=finalized_checkpoint, best_justified_checkpoint=justified_checkpoint, proposer_boost_root=proposer_boost_root, - has_equivocated=set(), + equivocating_indices=set(), blocks={anchor_root: copy(anchor_block)}, block_states={anchor_root: copy(anchor_state)}, checkpoint_states={justified_checkpoint: copy(anchor_state)}, @@ -182,7 +182,7 @@ def get_latest_attesting_balance(store: Store, root: Root) -> Gwei: attestation_score = Gwei(sum( state.validators[i].effective_balance for i in active_indices if (i in store.latest_messages - and i not in store.has_equivocated + and i not in store.equivocating_indices and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root) )) if store.proposer_boost_root == Root(): @@ -361,10 +361,10 @@ def store_target_checkpoint_state(store: Store, target: Checkpoint) -> None: def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None: target = attestation.data.target beacon_block_root = attestation.data.beacon_block_root - for i in attesting_indices: - if i not in store.has_equivocated: - if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch: - store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root) + non_equivocating_attesting_indices = [i for i in attesting_indices if i not in store.equivocating_indices] + for i in non_equivocating_attesting_indices: + if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch: + store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root) ``` @@ -481,6 +481,6 @@ def on_attester_slashing(store: Store, attester_slashing: AttesterSlashing) -> N assert is_valid_indexed_attestation(state, attestation_2) indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices) - for index in sorted(indices): - store.has_equivocated.add(index) + for index in indices: + store.equivocating_indices.add(index) ``` \ No newline at end of file From 15a90407ef9eee23b7fa9b752592767ff55e6590 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 3 Mar 2022 13:50:05 -0700 Subject: [PATCH 28/98] minor comment change --- specs/phase0/fork-choice.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index bc611b74bb..43dac92d1c 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -470,8 +470,8 @@ def on_attestation(store: Store, attestation: Attestation, is_from_block: bool=F ```python def on_attester_slashing(store: Store, attester_slashing: AttesterSlashing) -> None: """ - Run ``on_attester_slashing`` upon receiving a new ``AttesterSlashing`` from either within a block or directly - on the wire. + Run ``on_attester_slashing`` immediately upon receiving a new ``AttesterSlashing`` + from either within a block or directly on the wire. """ attestation_1 = attester_slashing.attestation_1 attestation_2 = attester_slashing.attestation_2 From e1b9cf97e2304905f8100602e99672ac759709df Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 3 Mar 2022 14:02:52 -0700 Subject: [PATCH 29/98] add withdrawal index to wihdrawal transaction --- specs/capella/beacon-chain.md | 7 ++++++- specs/capella/fork.md | 1 + .../epoch_processing/test_process_full_withdrawals.py | 5 +++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index 38cda0f2f9..f1171a7d57 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -27,6 +27,7 @@ We define the following Python custom types for type hinting and readability: | Name | SSZ equivalent | Description | | - | - | - | | `TransactionType` | `Bytes1` | an EIP-2718 type | +| `WithdrawalIndex` | `uint64` | an index of a `WithdrawalTransaction`| ## Preset @@ -40,7 +41,7 @@ We define the following Python custom types for type hinting and readability: | Name | Value | Description | | - | - | - | -| `TX_TYPE_WITHDRAWAL` | `TransactionType('0x05')` | EIP-2718 TX Type | +| `TX_TYPE_WITHDRAWAL` | `TransactionType('0x03')` | EIP-2718 TX Type | | `MAX_WITHDRAWAL_TRANSACTIONS_PER_PAYLOAD` | `uint64(2**4)` (= 16) | Maximum amount of withdrawal transactions allowed in each payload | ## Configuration @@ -106,6 +107,7 @@ class BeaconState(Container): # Execution latest_execution_payload_header: ExecutionPayloadHeader # Withdrawals + withdrawal_index: WithdrawalIndex withdrawal_receipts: List[WithdrawalTransaction, WITHDRAWAL_TRANSACTION_LIMIT] # [New in Capella] ``` @@ -167,6 +169,7 @@ as well as in the `BeaconState`'s `withdrawal_receipts` queue. ```python class WithdrawalTransaction(Container): + index: WithdrawalIndex address: ExecutionAddress amount: Gwei ``` @@ -183,9 +186,11 @@ def withdraw(state: BeaconState, index: ValidatorIndex, amount: Gwei) -> None: decrease_balance(state, index, amount) # Create a corresponding withdrawal receipt receipt = WithdrawalTransaction( + index=state.withdrawal_index, address=state.validators[index].withdrawal_credentials[12:], amount=amount, ) + state.withdrawal_index = WithdrawalIndex(state.withdrawal_index + 1) state.withdrawal_receipts.append(receipt) ``` diff --git a/specs/capella/fork.md b/specs/capella/fork.md index 17c0171df7..9bb0bb5525 100644 --- a/specs/capella/fork.md +++ b/specs/capella/fork.md @@ -83,6 +83,7 @@ def upgrade_to_capella(pre: bellatrix.BeaconState) -> BeaconState: # Execution-layer latest_execution_payload_header=pre.latest_execution_payload_header, # Withdrawals + withdrawal_index=WithdrawalIndex(0), withdrawal_receipts=[], ) diff --git a/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_full_withdrawals.py b/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_full_withdrawals.py index ef7b7c8aca..5da1c7ece0 100644 --- a/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_full_withdrawals.py +++ b/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_full_withdrawals.py @@ -17,6 +17,7 @@ def set_validator_withdrawable(spec, state, index, withdrawable_epoch=None): def run_process_full_withdrawals(spec, state, num_expected_withdrawals=None): + pre_withdrawal_index = state.withdrawal_index pre_withdrawal_receipts = state.withdrawal_receipts to_be_withdrawn_indices = [ index for index, validator in enumerate(state.validators) @@ -34,6 +35,7 @@ def run_process_full_withdrawals(spec, state, num_expected_withdrawals=None): assert state.balances[index] == 0 assert len(state.withdrawal_receipts) == len(pre_withdrawal_receipts) + num_expected_withdrawals + assert state.withdrawal_index == pre_withdrawal_index + num_expected_withdrawals @with_capella_and_later @@ -63,8 +65,11 @@ def test_single_withdrawal(spec, state): # Make one validator withdrawable set_validator_withdrawable(spec, state, 0) + assert state.withdrawal_index == 0 yield from run_process_full_withdrawals(spec, state, 1) + assert state.withdrawal_index == 1 + @with_capella_and_later @spec_state_test From 1e10e7dc1cf1e47f74dfd08ada965df311276b47 Mon Sep 17 00:00:00 2001 From: Aditya Asgaonkar Date: Thu, 3 Mar 2022 20:16:32 -0800 Subject: [PATCH 30/98] Fix UPDATE_TIMEOUT unit typo --- specs/altair/sync-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index f6809eab5f..8751ba6e8b 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -53,7 +53,7 @@ uses sync committees introduced in [this beacon chain extension](./beacon-chain. | Name | Value | Unit | Duration | | - | - | - | - | | `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | validators | -| `UPDATE_TIMEOUT` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | epochs | ~27.3 hours | +| `UPDATE_TIMEOUT` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | slots | ~27.3 hours | ## Containers From a0ba6b2a1a9c7c1f22cb3637573b56fc66e5cc39 Mon Sep 17 00:00:00 2001 From: Aditya Asgaonkar Date: Tue, 8 Mar 2022 06:52:03 -0800 Subject: [PATCH 31/98] Add note about syncing --- specs/phase0/fork-choice.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 43dac92d1c..66a87021c3 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -467,6 +467,8 @@ def on_attestation(store: Store, attestation: Attestation, is_from_block: bool=F #### `on_attester_slashing` +*Note*: `on_attester_slashing` should be called while syncing and a node MUST maintain the equivocation set of `AttesterSlashing`s from at least the last finalized checkpoint + ```python def on_attester_slashing(store: Store, attester_slashing: AttesterSlashing) -> None: """ From bd6d2ad4ce6f37999189bdd09cf3aa8f6c767b1f Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 8 Mar 2022 11:33:59 -0700 Subject: [PATCH 32/98] minor copy edit --- specs/phase0/fork-choice.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 66a87021c3..9b302b5fd0 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -467,7 +467,7 @@ def on_attestation(store: Store, attestation: Attestation, is_from_block: bool=F #### `on_attester_slashing` -*Note*: `on_attester_slashing` should be called while syncing and a node MUST maintain the equivocation set of `AttesterSlashing`s from at least the last finalized checkpoint +*Note*: `on_attester_slashing` should be called while syncing and a client MUST maintain the equivocation set of `AttesterSlashing`s from at least the latest finalized checkpoint. ```python def on_attester_slashing(store: Store, attester_slashing: AttesterSlashing) -> None: From 95c712598af0bdb234c577e4da859cf2f8043ad7 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 9 Mar 2022 10:07:01 +0800 Subject: [PATCH 33/98] Fix `test_discard_equivocations` test vectors --- .../eth2spec/test/helpers/fork_choice.py | 40 +++++++++++++++++++ .../test/phase0/fork_choice/test_get_head.py | 17 ++++---- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index f056b9acd0..d524060a24 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -92,6 +92,10 @@ def get_attestation_file_name(attestation): return f"attestation_{encode_hex(attestation.hash_tree_root())}" +def get_attester_slashing_file_name(attester_slashing): + return f"attester_slashing_{encode_hex(attester_slashing.hash_tree_root())}" + + def on_tick_and_append_step(spec, store, time, test_steps): spec.on_tick(store, time) test_steps.append({'tick': int(time)}) @@ -142,6 +146,10 @@ def add_block(spec, for attestation in signed_block.message.body.attestations: run_on_attestation(spec, store, attestation, is_from_block=True, valid=True) + # An on_block step implies receiving block's attester slashings + for attester_slashing in signed_block.message.body.attester_slashings: + run_on_attester_slashing(spec, store, attester_slashing, valid=True) + block_root = signed_block.message.hash_tree_root() assert store.blocks[block_root] == signed_block.message assert store.block_states[block_root].hash_tree_root() == signed_block.message.state_root @@ -168,6 +176,38 @@ def add_block(spec, return store.block_states[signed_block.message.hash_tree_root()] +def run_on_attester_slashing(spec, store, attester_slashing, valid=True): + if not valid: + try: + spec.on_attester_slashing(store, attester_slashing) + except AssertionError: + return + else: + assert False + + spec.on_attester_slashing(store, attester_slashing) + + +def add_attester_slashing(spec, store, attester_slashing, test_steps, valid=True): + slashing_file_name = get_attester_slashing_file_name(attester_slashing) + yield get_attester_slashing_file_name(attester_slashing), attester_slashing + + if not valid: + try: + run_on_attester_slashing(spec, store, attester_slashing) + except AssertionError: + test_steps.append({ + 'attester_slashing': slashing_file_name, + 'valid': False, + }) + return + else: + assert False + + run_on_attester_slashing(spec, store, attester_slashing) + test_steps.append({'attester_slashing': slashing_file_name}) + + def get_formatted_head_output(spec, store): head = spec.get_head(store) slot = store.blocks[head].slot diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py index 1f37625a8c..587a1c6e8b 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py @@ -8,24 +8,27 @@ with_presets, ) from eth2spec.test.helpers.attestations import get_valid_attestation, next_epoch_with_attestations -from eth2spec.test.helpers.block import build_empty_block_for_next_slot +from eth2spec.test.helpers.block import ( + apply_empty_block, + build_empty_block_for_next_slot, +) from eth2spec.test.helpers.constants import MINIMAL from eth2spec.test.helpers.fork_choice import ( - tick_and_run_on_attestation, - tick_and_add_block, + add_attester_slashing, + add_block, get_anchor_root, get_genesis_forkchoice_store_and_block, get_formatted_head_output, on_tick_and_append_step, - add_block, + run_on_attestation, + tick_and_run_on_attestation, + tick_and_add_block, ) from eth2spec.test.helpers.state import ( next_slots, next_epoch, state_transition_and_sign_block, ) -from tests.core.pyspec.eth2spec.test.helpers.block import apply_empty_block -from tests.core.pyspec.eth2spec.test.helpers.fork_choice import run_on_attestation rng = random.Random(1001) @@ -411,7 +414,7 @@ def test_discard_equivocations(spec, state): # Process attester_slashing # The head should revert to block_2 - spec.on_attester_slashing(store, attester_slashing) + yield from add_attester_slashing(spec, store, attester_slashing, test_steps) assert spec.get_head(store) == spec.hash_tree_root(block_2) test_steps.append({ From 3cdc0e61edff9f27ab51ea880d27325cc0a9e268 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 9 Mar 2022 10:18:00 +0800 Subject: [PATCH 34/98] Add `on_attester_slashing` execution step --- tests/formats/fork_choice/README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/formats/fork_choice/README.md b/tests/formats/fork_choice/README.md index e4da31a9b3..3266ad4c00 100644 --- a/tests/formats/fork_choice/README.md +++ b/tests/formats/fork_choice/README.md @@ -76,11 +76,27 @@ Adds `PowBlock` data which is required for executing `on_block(store, block)`. { pow_block: string -- the name of the `pow_block_<32-byte-root>.ssz_snappy` file. To be used in `get_pow_block` lookup -} +} ``` The file is located in the same folder (see below). PowBlocks should be used as return values for `get_pow_block(hash: Hash32) -> PowBlock` function if hashes match. +#### `on_attester_slashing` execution step + +The parameter that is required for executing `on_attester_slashing(store, attester_slashing)`. + +```yaml +{ + attester_slashing: string -- the name of the `attester_slashing_<32-byte-root>.ssz_snappy` file. + To execute `on_attester_slashing(store, attester_slashing)` with the given attester slashing. + valid: bool -- optional, default to `true`. + If it's `false`, this execution step is expected to be invalid. +} +``` +The file is located in the same folder (see below). + +After this step, the `store` object may have been updated. + #### Checks step The checks to verify the current status of `store`. From 8ec477333925cb4c31006e7269d9e449d709dcd9 Mon Sep 17 00:00:00 2001 From: protolambda Date: Thu, 10 Mar 2022 06:31:11 +0100 Subject: [PATCH 35/98] EIP-4844: consensus layer changes --- Makefile | 3 +- specs/eip4844/beacon-chain.md | 165 +++++++++++++++++++++ specs/eip4844/fork.md | 43 ++++++ specs/eip4844/p2p-interface.md | 264 +++++++++++++++++++++++++++++++++ specs/eip4844/validator.md | 135 +++++++++++++++++ 5 files changed, 609 insertions(+), 1 deletion(-) create mode 100644 specs/eip4844/beacon-chain.md create mode 100644 specs/eip4844/fork.md create mode 100644 specs/eip4844/p2p-interface.md create mode 100644 specs/eip4844/validator.md diff --git a/Makefile b/Makefile index ec3302e27b..7de4cec2a0 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,8 @@ MARKDOWN_FILES = $(wildcard $(SPEC_DIR)/phase0/*.md) $(wildcard $(SPEC_DIR)/alta $(wildcard $(SPEC_DIR)/bellatrix/*.md) \ $(wildcard $(SPEC_DIR)/custody/*.md) \ $(wildcard $(SPEC_DIR)/das/*.md) \ - $(wildcard $(SPEC_DIR)/sharding/*.md) + $(wildcard $(SPEC_DIR)/sharding/*.md) \ + $(wildcard $(SPEC_DIR)/eip4844/*.md) COV_HTML_OUT=.htmlcov COV_HTML_OUT_DIR=$(PY_SPEC_DIR)/$(COV_HTML_OUT) diff --git a/specs/eip4844/beacon-chain.md b/specs/eip4844/beacon-chain.md new file mode 100644 index 0000000000..4ac7cf3d16 --- /dev/null +++ b/specs/eip4844/beacon-chain.md @@ -0,0 +1,165 @@ +# EIP-4844 -- The Beacon Chain + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Custom types](#custom-types) +- [Constants](#constants) +- [Preset](#preset) + - [Trusted setup](#trusted-setup) +- [Configuration](#configuration) +- [Containers](#containers) + - [Extended containers](#extended-containers) + - [`BeaconBlockBody`](#beaconblockbody) +- [Helper functions](#helper-functions) + - [Misc](#misc) + - [`tx_peek_blob_versioned_hashes`](#tx_peek_blob_versioned_hashes) + - [`verify_kzgs_against_transactions`](#verify_kzgs_against_transactions) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Block processing](#block-processing) + - [Blob KZGs](#blob-kzgs) +- [Testing](#testing) + + + + +## Introduction + +This upgrade adds transaction execution to the beacon chain as part of Bellatrix upgrade. + +Additionally, this upgrade introduces the following minor changes: +* Penalty parameter updates to their planned maximally punitive values + +## Custom types + +| Name | SSZ equivalent | Description | +| - | - | - | +| `BLSFieldElement` | `uint256` | `x < BLS_MODULUS` | +| `Blob` | `Vector[BLSFieldElement, FIELD_ELEMENTS_PER_BLOB]` | | +| `VersionedHash` | `Bytes32` | | +| `KZGCommitment` | `Bytes48` | Same as BLS standard "is valid pubkey" check but also allows `0x00..00` for point-at-infinity | + +## Constants + +| Name | Value | +| - | - | +| `BLOB_TX_TYPE` | `uint8(0x05)` | +| `FIELD_ELEMENTS_PER_BLOB` | `4096` | +| `BLS_MODULUS` | `52435875175126190479447740508185965837690552500527637822603658699938581184513` | + + +## Preset + +### Trusted setup + +The trusted setup is part of the preset: during testing a `minimal` insecure variant may be used, +but reusing the `mainnet` settings in public networks is a critical security requirement. + +| Name | Value | +| - | - | +| `KZG_SETUP_G2` | `Vector[G2Point, FIELD_ELEMENTS_PER_BLOB]`, contents TBD | +| `KZG_SETUP_LAGRANGE` | `Vector[BLSCommitment, FIELD_ELEMENTS_PER_BLOB]`, contents TBD | + +## Configuration + + +## Containers + +### Extended containers + +#### `BeaconBlockBody` + +Note: `BeaconBlock` and `SignedBeaconBlock` types are updated indirectly. + +```python +class BeaconBlockBody(Container): + randao_reveal: BLSSignature + eth1_data: Eth1Data # Eth1 data vote + graffiti: Bytes32 # Arbitrary data + # Operations + proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] + attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] + attestations: List[Attestation, MAX_ATTESTATIONS] + deposits: List[Deposit, MAX_DEPOSITS] + voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] + sync_aggregate: SyncAggregate + # Execution + execution_payload: ExecutionPayload + blob_kzgs: List[KZGCommitment, MAX_OBJECT_LIST_SIZE] # [New in EIP-4844] +``` + +## Helper functions + +### Misc + +#### `tx_peek_blob_versioned_hashes` + +This function retrieves the hashes from the `SignedBlobTransaction` as defined in EIP-4844, using SSZ offsets. +Offsets are little-endian `uint32` values, as defined in the [SSZ specification](../../ssz/simple-serialize.md). + +```python +def tx_peek_blob_versioned_hashes(opaque_tx: Transaction) -> Sequence[VersionedHash]: + assert opaque_tx[0] == BLOB_TX_TYPE + message_offset = 1 + uint32.decode_bytes(opaque_tx[1:5]) + # field offset: 32 + 8 + 32 + 32 + 8 + 4 + 32 + 4 + 4 = 156 + blob_versioned_hashes_offset = uint32.decode_bytes(opaque_tx[message_offset+156:message_offset+160]) + return [VersionedHash(opaque_tx[x:x+32]) for x in range(blob_versioned_hashes_offset, len(opaque_tx), 32)] +``` + +#### `verify_kzgs_against_transactions` + +```python +def verify_kzgs_against_transactions(transactions: Sequence[Transaction], blob_kzgs: Sequence[KZGCommitment]) -> bool: + all_versioned_hashes = [] + for tx in transactions: + if opaque_tx[0] == BLOB_TX_TYPE: + all_versioned_hashes.extend(tx_peek_blob_versioned_hashes(tx)) + return all_versioned_hashes == [ + kzg_to_versioned_hash(kzg) for kzg in blob_kzgs + ] +``` + +## Beacon chain state transition function + +### Block processing + +```python +def process_block(state: BeaconState, block: BeaconBlock) -> None: + process_block_header(state, block) + if is_execution_enabled(state, block.body): + process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) + process_randao(state, block.body) + process_eth1_data(state, block.body) + process_operations(state, block.body) + process_sync_aggregate(state, block.body.sync_aggregate) + process_blob_kzgs(state, block.body) # [New in EIP-4844] +``` + +#### Blob KZGs + +```python +def process_blob_kzgs(body: BeaconBlockBody): + assert verify_kzgs_against_transactions(body.execution_payload.transactions, body.blob_kzgs) + + # TODO do we want to buffer the kzg commitments in the BeaconState, like in the full sharding design? + # This could make a proof to any particular blob commitment more efficient, + # but the buffer structure is also likely to change with full sharding. +``` + +## Testing + +*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure EIP-4844 testing only. + +The `BeaconState` initialization is unchanged, except for the use of the updated `eip4844.BeaconBlockBody` type +when initializing the first body-root: + +```python +state.latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), +``` + diff --git a/specs/eip4844/fork.md b/specs/eip4844/fork.md new file mode 100644 index 0000000000..ad1b00b798 --- /dev/null +++ b/specs/eip4844/fork.md @@ -0,0 +1,43 @@ +# EIP-4844 -- Fork Logic + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + +- [Introduction](#introduction) +- [Configuration](#configuration) +- [Fork to EIP-4844](#fork-to-eip-4844) + - [Fork trigger](#fork-trigger) + - [Upgrading the state](#upgrading-the-state) + + + +## Introduction + +This document describes the process of EIP-4844 upgrade. + +## Configuration + +Warning: this configuration is not definitive. + +| Name | Value | +| - | - | +| `EIP4844_FORK_VERSION` | `Version('0x03000000')` | +| `EIP4844_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** | + +## Fork to EIP-4844 + +### Fork trigger + +TBD. This fork is defined for testing purposes, the EIP may be combined with other consensus-layer upgrade. +For now we assume the condition will be triggered at epoch `EIP4844_FORK_EPOCH`. + +Note that for the pure EIP-4844 networks, we don't apply `upgrade_to_eip4844` since it starts with EIP-4844 version logic. + +### Upgrading the state + +The `eip4844.BeaconState` format is equal to the `bellatrix.BeaconState` format, no upgrade has to be performed. + diff --git a/specs/eip4844/p2p-interface.md b/specs/eip4844/p2p-interface.md new file mode 100644 index 0000000000..6b3ac9bc93 --- /dev/null +++ b/specs/eip4844/p2p-interface.md @@ -0,0 +1,264 @@ +# EIP-4844 -- Networking + +This document contains the consensus-layer networking specification for EIP-4844. + +The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite. + +## Table of contents + + + + + + - [Preset](#preset) + - [Configuration](#configuration) + - [Containers](#containers) + - [`BlobsSidecar`](#blobssidecar) + - [`SignedBlobsSidecar`](#signedblobssidecar) + - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) + - [Topics and messages](#topics-and-messages) + - [Global topics](#global-topics) + - [`beacon_block`](#beacon_block) + - [`blobs_sidecar`](#blobs_sidecar) + - [Transitioning the gossip](#transitioning-the-gossip) + - [The Req/Resp domain](#the-reqresp-domain) + - [Messages](#messages) + - [BeaconBlocksByRange v2](#beaconblocksbyrange-v2) + - [BeaconBlocksByRoot v2](#beaconblocksbyroot-v2) + - [BlobsSidecarsByRange v1](#blobssidecarsbyrange-v1) +- [Design decision rationale](#design-decision-rationale) + - [Why are blobs relayed as a sidecar, separate from beacon blocks?](#why-are-blobs-relayed-as-a-sidecar-separate-from-beacon-blocks) + + + + + +## Preset + +| Name | Value | +| - | - | +| `LIMIT_BLOBS_PER_SIDECAR` | `uint64(2**4)` (= 16) | + +## Configuration + +| Name | Value | Description | +|------------------------------------------|-------------------------------|---------------------------------------------------------------------| +| `MAX_REQUEST_BLOBS_SIDECARS` | `2**10` (= 1024) | Maximum number of blobs sidecars in a single request | +| `MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS` | `2**13` (= 8192, ~1.2 months) | The minimum epoch range over which a node must serve blobs sidecars | + + + +## Containers + +### `BlobsSidecar` + +```python +class BlobsSidecar(Container): + beacon_block_root: Root + beacon_block_slot: Slot + shard: uint64 # [ Forward compatibility ] + blobs: List[Blob, LIMIT_BLOBS_PER_SIDECAR] +``` + +### `SignedBlobsSidecar` + +```python +class SignedBlobsSidecar(Container): + message: BlobsSidecar + signature: BLSSignature +``` + + +## The gossip domain: gossipsub + +Some gossip meshes are upgraded in the fork of EIP4844 to support upgraded types. + +### Topics and messages + +Topics follow the same specification as in prior upgrades. +All topics remain stable except the beacon block topic which is updated with the modified type. + +The specification around the creation, validation, and dissemination of messages has not changed from the Bellatrix document unless explicitly noted here. + +The derivation of the `message-id` remains stable. + +The new topics along with the type of the `data` field of a gossipsub message are given in this table: + +| Name | Message Type | +| - | - | +| `beacon_block` | `SignedBeaconBlock` (modified) | +| `blobs_sidecar` | `SignedBlobsSidecar` (new) | + +Note that the `ForkDigestValue` path segment of the topic separates the old and the new `beacon_block` topics. + +#### Global topics + +EIP4844 changes the type of the global beacon block topic and introduces a new global topic for blobs-sidecars. + +##### `beacon_block` + +The *type* of the payload of this topic changes to the (modified) `SignedBeaconBlock` found in EIP4844. + +In addition to the gossip validations for this topic from prior specifications, +the following validations MUST pass before forwarding the `signed_beacon_block` on the network. +Alias `block = signed_beacon_block.message`, `execution_payload = block.body.execution_payload`. +- _[REJECT]_ The KZG commitments of the blobs are all correctly encoded compressed BLS G1 Points. + -- i.e. `all(bls.KeyValidate(commitment) for commitment in block.body.blob_kzgs)` +- _[REJECT]_ The KZG commitments correspond to the versioned hashes in the transactions list. + -- i.e. `verify_kzgs_against_transactions(block.body.execution_payload.transactions, block.body.blob_kzgs)` + +##### `blobs_sidecar` + +This topic is used to propagate data blobs included in any given beacon block. + +The following validations MUST pass before forwarding the `signed_blobs_sidecar` on the network; +Alias `sidecar = signed_blobs_sidecar.message`. +- _[IGNORE]_ the `sidecar.beacon_block_slot` is for the current slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `blobs_sidecar.beacon_block_slot == current_slot`. +- _[REJECT]_ the `sidecar.blobs` are all well formatted, i.e. the `BLSFieldElement` in valid range (`x < BLS_MODULUS`). +- _[REJECT]_ the beacon proposer signature, `signed_blobs_sidecar.signature`, is valid -- i.e. +```python +domain = get_domain(state, DOMAIN_BLOBS_SIDECAR, blobs_sidecar.beacon_block_slot / SLOTS_PER_EPOCH) +signing_root = compute_signing_root(blobs_sidecar, domain) +assert bls.Verify(proposer_pubkey, signing_root, signed_blob_header.signature) +``` + where `proposer_pubkey` is the pubkey of the beacon block proposer of `blobs_sidecar.beacon_block_slot` +- _[IGNORE]_ The sidecar is the first sidecar with valid signature received for the `(proposer_index, sidecar.beacon_block_root)` combination, + where `proposer_index` is the validator index of the beacon block proposer of `blobs_sidecar.beacon_block_slot` + +Note that a sidecar may be propagated before or after the corresponding beacon block. + +Once both sidecar and beacon block are received, `verify_blobs_sidecar` can unlock the data-availability fork-choice dependency. + +### Transitioning the gossip + +See gossip transition details found in the [Altair document](../altair/p2p-interface.md#transitioning-the-gossip) for +details on how to handle transitioning gossip topics for Bellatrix. + +## The Req/Resp domain + +### Messages + +#### BeaconBlocksByRange v2 + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_range/2/` + +The EIP-4844 fork-digest is introduced to the `context` enum to specify EIP-4844 beacon block type. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + +[0]: # (eth2spec: skip) + +| `fork_version` | Chunk SSZ type | +|--------------------------|-------------------------------| +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | +| `EIP4844_FORK_VERSION` | `eip4844.SignedBeaconBlock` | + +#### BeaconBlocksByRoot v2 + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/2/` + +The EIP-4844 fork-digest is introduced to the `context` enum to specify EIP-4844 beacon block type. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + +[1]: # (eth2spec: skip) + +| `fork_version` | Chunk SSZ type | +| ------------------------ | -------------------------- | +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | +| `EIP4844_FORK_VERSION` | `eip4844.SignedBeaconBlock` | + +#### BlobsSidecarsByRange v1 + +**Protocol ID:** `/eth2/beacon_chain/req/blobs_sidecars_by_range/1/` + +Request Content: +``` +( + start_slot: Slot + count: uint64 + shard: uint64 +) +``` + +Response Content: +``` +( + List[BlobsSidecar, MAX_REQUEST_BLOBS_SIDECARS] +) +``` + +Requests blobs sidecars in the slot range `[start_slot, start_slot + count)`, +leading up to the current head block as selected by fork choice. + +The request and response format is forward-compatible with sharded sidecar sync, but MUST enforce `shard == 0` for now. + +The response is unsigned, i.e. `BlobsSidecarsByRange`, as the signature of the beacon block proposer +may not be available beyond the initial distribution via gossip. + +Before consuming the next response chunk, the response reader SHOULD verify the blobs sidecar is well-formatted and +correct w.r.t. the expected KZG commitments through `verify_blobs_sidecar`. + +`BlobsSidecarsByRange` is primarily used to sync blobs that may have been missed on gossip. + +The request MUST be encoded as an SSZ-container. + +The response MUST consist of zero or more `response_chunk`. +Each _successful_ `response_chunk` MUST contain a single `SignedBlobsSidecar` payload. + +Clients MUST keep a record of signed blobs sidecars seen on the epoch range +`[max(GENESIS_EPOCH, current_epoch - MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS), current_epoch]` +where `current_epoch` is defined by the current wall-clock time, +and clients MUST support serving requests of blocks on this range. + +Peers that are unable to reply to block requests within the `MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS` +epoch range SHOULD respond with error code `3: ResourceUnavailable`. +Such peers that are unable to successfully reply to this range of requests MAY get descored +or disconnected at any time. + +*Note*: The above requirement implies that nodes that start from a recent weak subjectivity checkpoint +MUST backfill the local blobs database to at least epoch `current_epoch - MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS` +to be fully compliant with `BlobsSidecarsByRange` requests. To safely perform such a +backfill of blocks to the recent state, the node MUST validate both (1) the +proposer signatures and (2) that the blocks form a valid chain up to the most +recent block referenced in the weak subjectivity state. + +*Note*: Although clients that bootstrap from a weak subjectivity checkpoint can begin +participating in the networking immediately, other peers MAY +disconnect and/or temporarily ban such an un-synced or semi-synced client. + +Clients MUST respond with at least the first blobs sidecar that exists in the range, if they have it, +and no more than `MAX_REQUEST_BLOBS_SIDECARS` blocks. + +The following blobs sidecars, where they exist, MUST be sent in consecutive order. + +Clients MAY limit the number of blobs sidecars in the response. + +The response MUST contain no more than `count` blobs sidecars. + +Clients MUST respond with blobs sidecars from their view of the current fork choice +-- that is, blobs sidecars as included by blocks from the single chain defined by the current head. +Of note, blocks from slots before the finalization MUST lead to the finalized block reported in the `Status` handshake. + +Clients MUST respond with blobs sidecars that are consistent from a single chain within the context of the request. + +After the initial blobs sidecar, clients MAY stop in the process of responding +if their fork choice changes the view of the chain in the context of the request. + + + +# Design decision rationale + +## Why are blobs relayed as a sidecar, separate from beacon blocks? + +This "sidecar" design provides forward compatibility for further data increases by black-boxing `is_data_available()`: +with full sharding `is_data_available()` can be replaced by data-availability-sampling (DAS) +thus avoiding all blobs being downloaded by all beacon nodes on the network. + +Such sharding design may introduce an updated `BlobsSidecar` to identify the shard, +but does not affect the `BeaconBlock` structure. + diff --git a/specs/eip4844/validator.md b/specs/eip4844/validator.md new file mode 100644 index 0000000000..0bdf947bad --- /dev/null +++ b/specs/eip4844/validator.md @@ -0,0 +1,135 @@ +# EIP-4844 -- Honest Validator + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Prerequisites](#prerequisites) +- [Helpers](#helpers) + - [`is_data_available`](#is_data_available) + - [`verify_blobs_sidecar`](#verify_blobs_sidecar) +- [Beacon chain responsibilities](#beacon-chain-responsibilities) + - [Block proposal](#block-proposal) + - [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody) + - [Blob commitments](#blob-commitments) + - [Beacon Block publishing time](#beacon-block-publishing-time) + + + + +## Introduction + +This document represents the changes to be made in the code of an "honest validator" to implement EIP-4844. + +## Prerequisites + +This document is an extension of the [Bellatrix -- Honest Validator](../altair/validator.md) guide. +All behaviors and definitions defined in this document, and documents it extends, carry over unless explicitly noted or overridden. + +All terminology, constants, functions, and protocol mechanics defined in the updated [Beacon Chain doc of EIP4844](./beacon-chain.md) are requisite for this document and used throughout. +Please see related Beacon Chain doc before continuing and use them as a reference throughout. + +## Helpers + +### `is_data_available` + +The implementation of `is_data_available` is meant to change with later sharding upgrades. +Initially, it requires every verifying actor to retrieve the matching `BlobsSidecar`, +and verify the sidecar with `verify_blobs`. + +Without the sidecar the block may be processed further optimistically, +but MUST NOT be considered valid until a valid `BlobsSidecar` has been downloaded. + +```python +def is_data_available(slot: Slot, beacon_block_root: Root, kzgs: Sequence[KZGCommitment]): + sidecar = retrieve_blobs_sidecar(slot, beacon_block_root) # implementation dependent, raises an exception if not available + verify_blobs_sidecar(slot, beacon_block_root, kzgs, sidecar) +``` + +### `verify_blobs_sidecar` + +```python +def verify_blobs_sidecar(slot: Slot, beacon_block_root: Root, + expected_kzgs: Sequence[KZGCommitment], blobs_sidecar: BlobsSidecar): + assert blobs_sidecar.shard == 0 # always zero, placeholder for future sharding + assert slot == blobs_sidecar.beacon_block_slot + assert beacon_block_root == blobs_sidecar.beacon_block_root + blobs = blobs_sidecar.blobs + assert len(kzgs) == len(blobs) + for kzg, blob in zip(expected_kzgs, blobs): + assert blob_to_kzg(blob) == kzg +``` + + +## Beacon chain responsibilities + +All validator responsibilities remain unchanged other than those noted below. +Namely, the blob handling and the addition of `BlobsSidecar`. + +### Block proposal + +#### Constructing the `BeaconBlockBody` + +##### Blob commitments + +After retrieving the execution payload from the execution engine as specified in Bellatrix, +the blobs are retrieved and processed: + +```python +# execution_payload = xecution_engine.get_payload(payload_id) +# block.body.execution_payload = execution_payload +# ... + +kzgs, blobs = get_blobs(payload_id) + +# Optionally sanity-check that the KZG commitments match the versioned hashes in the transactions +assert verify_kzgs_against_transactions(execution_payload.transactions, kzgs) + +# Optionally sanity-check that the KZG commitments match the blobs (as produced by the execution engine) +assert len(kzgs) == len(blobs) and [blob_to_kzg(blob) == kzg for blob, kzg in zip(blobs, kzgs)] + +# Update the block body +block.body.blob_kzgs = kzgs +``` + +The `blobs` should be held with the block in preparation of publishing. +Without the `blobs`, the published block will effectively be ignored by honest validators. + +Note: This API is *unstable*. `get_blobs` and `get_payload` may be unified. +Implementers may also retrieve blobs individually per transaction. + +### Beacon Block publishing time + +Before publishing a prepared beacon block proposal, the corresponding blobs are packaged into a sidecar object for distribution to the network: + +```python +blobs_sidecar = BlobsSidecar( + beacon_block_root=hash_tree_root(beacon_block) + beacon_block_slot=beacon_block.slot + shard=0, + blobs=blobs, +) +``` + +And then signed: + +```python +domain = get_domain(state, DOMAIN_BLOBS_SIDECAR, blobs_sidecar.beacon_block_slot / SLOTS_PER_EPOCH) +signing_root = compute_signing_root(blobs_sidecar, domain) +signature = bls.Sign(privkey, signing_root) +signed_blobs_sidecar = SignedBlobsSidecar(message=blobs_sidecar, signature=signature) +``` + +This `signed_blobs_sidecar` is then published to the global `blobs_sidecar` topic as soon as the `beacon_block` is published. + +After publishing the sidecar peers on the network may request the sidecar through sync-requests, or a local user may be interested. +The validator MUST hold on to blobs for `MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS` epochs and serve when capable, +to ensure the data-availability of these blobs throughout the network. + +After `MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS` nodes MAY prune the blobs and/or stop serving them. + From 3dd83cf4ee889c5e2c2696c065968559e25ccc26 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 10 Mar 2022 10:38:47 -0700 Subject: [PATCH 36/98] minor withdrawals renamings --- specs/capella/beacon-chain.md | 46 ++++++++----------- specs/capella/fork.md | 2 +- .../test_process_full_withdrawals.py | 4 +- .../eth2spec/test/helpers/epoch_processing.py | 2 +- .../test/helpers/execution_payload.py | 2 +- 5 files changed, 24 insertions(+), 32 deletions(-) diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index f1171a7d57..001bac70ae 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -26,8 +26,7 @@ We define the following Python custom types for type hinting and readability: | Name | SSZ equivalent | Description | | - | - | - | -| `TransactionType` | `Bytes1` | an EIP-2718 type | -| `WithdrawalIndex` | `uint64` | an index of a `WithdrawalTransaction`| +| `WithdrawalIndex` | `uint64` | an index of a `Withdrawal`| ## Preset @@ -35,14 +34,13 @@ We define the following Python custom types for type hinting and readability: | Name | Value | Unit | Duration | | - | - | :-: | :-: | -| `WITHDRAWAL_TRANSACTION_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | withdrawal transactions enqueued in state| +| `WITHDRAWALS_QUEUE_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | withdrawals enqueued in state| ### Execution | Name | Value | Description | | - | - | - | -| `TX_TYPE_WITHDRAWAL` | `TransactionType('0x03')` | EIP-2718 TX Type | -| `MAX_WITHDRAWAL_TRANSACTIONS_PER_PAYLOAD` | `uint64(2**4)` (= 16) | Maximum amount of withdrawal transactions allowed in each payload | +| `MAX_WITHDRAWALS_PER_PAYLOAD` | `uint64(2**4)` (= 16) | Maximum amount of withdrawals allowed in each payload | ## Configuration @@ -108,7 +106,7 @@ class BeaconState(Container): latest_execution_payload_header: ExecutionPayloadHeader # Withdrawals withdrawal_index: WithdrawalIndex - withdrawal_receipts: List[WithdrawalTransaction, WITHDRAWAL_TRANSACTION_LIMIT] # [New in Capella] + withdrawals_queue: List[Withdrawal, WITHDRAWALS_QUEUE_LIMIT] # [New in Capella] ``` #### `ExecutionPayload` @@ -131,7 +129,7 @@ class ExecutionPayload(Container): # Extra payload fields block_hash: Hash32 # Hash of execution block transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] - withdrawal_transactions: List[WithdrawalTransaction, MAX_WITHDRAWAL_TRANSACTIONS_PER_PAYLOAD] # [New in Capella] + withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] # [New in Capella] ``` #### `ExecutionPayloadHeader` @@ -154,21 +152,15 @@ class ExecutionPayloadHeader(Container): # Extra payload fields block_hash: Hash32 # Hash of execution block transactions_root: Root - withdrawal_transactions_root: Root # [New in Capella] + withdrawals_root: Root # [New in Capella] ``` ### New containers -#### `WithdrawalTransaction` - -New EIP-2718 transaction type, with the format being the single byte `TX_TYPE_WITHDRAWAL` -followed by an SSZ encoding of the `WithdrawalTransaction` container comprising the transaction contents. - -*Note*: This container is used for both a special TX that goes into an `ExecutionPayload` -as well as in the `BeaconState`'s `withdrawal_receipts` queue. +#### `Withdrawal` ```python -class WithdrawalTransaction(Container): +class Withdrawal(Container): index: WithdrawalIndex address: ExecutionAddress amount: Gwei @@ -185,13 +177,13 @@ def withdraw(state: BeaconState, index: ValidatorIndex, amount: Gwei) -> None: # Decrease the validator's balance decrease_balance(state, index, amount) # Create a corresponding withdrawal receipt - receipt = WithdrawalTransaction( + withdrawal = Withdrawal( index=state.withdrawal_index, address=state.validators[index].withdrawal_credentials[12:], amount=amount, ) state.withdrawal_index = WithdrawalIndex(state.withdrawal_index + 1) - state.withdrawal_receipts.append(receipt) + state.withdrawals_queue.append(withdrawal) ``` ### Predicates @@ -248,7 +240,7 @@ def process_full_withdrawals(state: BeaconState) -> None: def process_block(state: BeaconState, block: BeaconBlock) -> None: process_block_header(state, block) if is_execution_enabled(state, block.body): - process_withdrawal_transactions(state, block.body.execution_payload) # [New in Capella] + process_withdrawals(state, block.body.execution_payload) # [New in Capella] process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [Modified in Capella] process_randao(state, block.body) process_eth1_data(state, block.body) @@ -256,22 +248,22 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: process_sync_aggregate(state, block.body.sync_aggregate) ``` -#### New `process_withdrawal_transactions` +#### New `process_withdrawals` ```python -def process_withdrawal_transactions(state: BeaconState, payload: ExecutionPayload) -> None: - num_withdrawal_transactions = min(MAX_WITHDRAWAL_TRANSACTIONS_PER_PAYLOAD, len(state.withdrawal_receipts)) - dequeued_withdrawal_receipts = state.withdrawal_receipts[:num_withdrawal_transactions] +def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None: + num_withdrawals = min(MAX_WITHDRAWALS_PER_PAYLOAD, len(state.withdrawals_queue)) + dequeued_withdrawals = state.withdrawals_queue[:num_withdrawals] - assert len(dequeued_withdrawal_receipts) == len(payload.withdrawal_transactions) - for dequeued_receipt, withdrawal_transaction in zip(dequeued_withdrawal_receipts, payload.withdrawal_transactions): + assert len(dequeued_withdrawals) == len(payload.withdrawals) + for dequeued_receipt, withdrawal_transaction in zip(dequeued_withdrawals, payload.withdrawals): assert dequeued_receipt == withdrawal_transaction # Ensure no withdrawal type transactions in the normal payload transactions # assert no_withdrawal_type_transactions_in(payload.transactions) # Remove dequeued receipts from state - state.withdrawal_receipts = state.withdrawal_receipts[num_withdrawal_transactions:] + state.withdrawals_queue = state.withdrawals_queue[num_withdrawals:] ``` #### Modified `process_execution_payload` @@ -305,6 +297,6 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe base_fee_per_gas=payload.base_fee_per_gas, block_hash=payload.block_hash, transactions_root=hash_tree_root(payload.transactions), - withdrawal_transactions_root=hash_tree_root(payload.withdrawal_transactions), + withdrawals_root=hash_tree_root(payload.withdrawals), ) ``` diff --git a/specs/capella/fork.md b/specs/capella/fork.md index 9bb0bb5525..8585eddf4e 100644 --- a/specs/capella/fork.md +++ b/specs/capella/fork.md @@ -84,7 +84,7 @@ def upgrade_to_capella(pre: bellatrix.BeaconState) -> BeaconState: latest_execution_payload_header=pre.latest_execution_payload_header, # Withdrawals withdrawal_index=WithdrawalIndex(0), - withdrawal_receipts=[], + withdrawals_queue=[], ) for pre_validator in pre.validators: diff --git a/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_full_withdrawals.py b/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_full_withdrawals.py index 5da1c7ece0..64aba5f6d3 100644 --- a/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_full_withdrawals.py +++ b/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_full_withdrawals.py @@ -18,7 +18,7 @@ def set_validator_withdrawable(spec, state, index, withdrawable_epoch=None): def run_process_full_withdrawals(spec, state, num_expected_withdrawals=None): pre_withdrawal_index = state.withdrawal_index - pre_withdrawal_receipts = state.withdrawal_receipts + pre_withdrawals_queue = state.withdrawals_queue to_be_withdrawn_indices = [ index for index, validator in enumerate(state.validators) if spec.is_fully_withdrawable_validator(validator, spec.get_current_epoch(state)) @@ -34,7 +34,7 @@ def run_process_full_withdrawals(spec, state, num_expected_withdrawals=None): assert validator.withdrawn_epoch == spec.get_current_epoch(state) assert state.balances[index] == 0 - assert len(state.withdrawal_receipts) == len(pre_withdrawal_receipts) + num_expected_withdrawals + assert len(state.withdrawals_queue) == len(pre_withdrawals_queue) + num_expected_withdrawals assert state.withdrawal_index == pre_withdrawal_index + num_expected_withdrawals diff --git a/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py b/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py index 98984fa221..8c27480c04 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py +++ b/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py @@ -28,7 +28,7 @@ def get_process_calls(spec): 'process_participation_record_updates' ), 'process_sync_committee_updates', # altair - 'process_withdrawals', # capella + 'process_full_withdrawals', # capella # TODO: add sharding processing functions when spec stabilizes. ] diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index 36fcca2c05..915883aac2 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -52,7 +52,7 @@ def get_execution_payload_header(spec, execution_payload): transactions_root=spec.hash_tree_root(execution_payload.transactions) ) if spec.fork not in FORKS_BEFORE_CAPELLA: - payload_header.withdrawal_transactions_root = spec.hash_tree_root(execution_payload.withdrawal_transactions) + payload_header.withdrawals_root = spec.hash_tree_root(execution_payload.withdrawals) return payload_header From 84ab086d9490bd74ed7933058d6fa0ad767970ab Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Mon, 14 Mar 2022 19:09:21 +0600 Subject: [PATCH 37/98] Add safe_block_hash to notify_fc_updated --- setup.py | 1 + specs/bellatrix/fork-choice.md | 8 ++++++-- specs/bellatrix/validator.md | 8 +++++++- .../test/bellatrix/unittests/validator/test_validator.py | 7 ++++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index f826968f21..00c2a40307 100644 --- a/setup.py +++ b/setup.py @@ -529,6 +529,7 @@ def notify_new_payload(self: ExecutionEngine, execution_payload: ExecutionPayloa def notify_forkchoice_updated(self: ExecutionEngine, head_block_hash: Hash32, + safe_block_hash: Hash32, finalized_block_hash: Hash32, payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: pass diff --git a/specs/bellatrix/fork-choice.md b/specs/bellatrix/fork-choice.md index 0f5a7b6463..488f8bac5d 100644 --- a/specs/bellatrix/fork-choice.md +++ b/specs/bellatrix/fork-choice.md @@ -47,8 +47,9 @@ The Engine API may be used to implement it with an external execution engine. #### `notify_forkchoice_updated` -This function performs two actions *atomically*: +This function performs three actions *atomically*: * Re-organizes the execution payload chain and corresponding state to make `head_block_hash` the head. +* Updates safe block hash with the value provided by `safe_block_hash` parameter. * Applies finality to the execution state: it irreversibly persists the chain of all execution payloads and corresponding state, up to and including `finalized_block_hash`. @@ -58,18 +59,21 @@ Additionally, if `payload_attributes` is provided, this function sets in motion ```python def notify_forkchoice_updated(self: ExecutionEngine, head_block_hash: Hash32, + safe_block_hash: Hash32, finalized_block_hash: Hash32, payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: ... ``` -*Note*: The call of the `notify_forkchoice_updated` function maps on the `POS_FORKCHOICE_UPDATED` event defined in the [EIP-3675](https://eips.ethereum.org/EIPS/eip-3675#definitions). +*Note*: The `(head_block_hash, finalized_block_hash)` values of the `notify_forkchoice_updated` function call maps on the `POS_FORKCHOICE_UPDATED` event defined in the [EIP-3675](https://eips.ethereum.org/EIPS/eip-3675#definitions). As per EIP-3675, before a post-transition block is finalized, `notify_forkchoice_updated` MUST be called with `finalized_block_hash = Hash32()`. *Note*: Client software MUST NOT call this function until the transition conditions are met on the PoW network, i.e. there exists a block for which `is_valid_terminal_pow_block` function returns `True`. *Note*: Client software MUST call this function to initiate the payload build process to produce the merge transition block; the `head_block_hash` parameter MUST be set to the hash of a terminal PoW block in this case. +*Note*: Until safe head function is implemented, `safe_block_hash` parameter MUST be stubbed with the `head_block_hash` value. + ## Helpers ### `PayloadAttributes` diff --git a/specs/bellatrix/validator.md b/specs/bellatrix/validator.md index 47de49ba65..c392c8bdd0 100644 --- a/specs/bellatrix/validator.md +++ b/specs/bellatrix/validator.md @@ -146,7 +146,13 @@ def prepare_execution_payload(state: BeaconState, prev_randao=get_randao_mix(state, get_current_epoch(state)), suggested_fee_recipient=suggested_fee_recipient, ) - return execution_engine.notify_forkchoice_updated(parent_hash, finalized_block_hash, payload_attributes) + # Set safe and head block hashes to the same value + return execution_engine.notify_forkchoice_updated( + head_block_hash=parent_hash, + safe_block_hash=parent_hash, + finalized_block_hash=finalized_block_hash, + payload_attributes=payload_attributes, + ) ``` 2. Set `block.body.execution_payload = get_execution_payload(payload_id, execution_engine)`, where: diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/unittests/validator/test_validator.py b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/validator/test_validator.py index 05a941dfe4..770c05d949 100644 --- a/tests/core/pyspec/eth2spec/test/bellatrix/unittests/validator/test_validator.py +++ b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/validator/test_validator.py @@ -1,4 +1,5 @@ from copy import deepcopy +from typing import Optional from eth2spec.test.helpers.pow_block import ( prepare_random_pow_chain, @@ -146,7 +147,11 @@ def test_prepare_execution_payload(spec, state): # Mock execution_engine class TestEngine(spec.NoopExecutionEngine): - def notify_forkchoice_updated(self, parent_hash, finalized_block_hash, payload_attributes) -> bool: + def notify_forkchoice_updated(self, + head_block_hash, + safe_block_hash, + finalized_block_hash, + payload_attributes) -> Optional[spec.PayloadId]: return SAMPLE_PAYLOAD_ID payload_id = spec.prepare_execution_payload( From 45e207be4d99247c9b33fb5a44a0bdc038b2d945 Mon Sep 17 00:00:00 2001 From: protolambda Date: Mon, 14 Mar 2022 18:54:54 +0100 Subject: [PATCH 38/98] EIP-4844 consensus-specs review fixes Co-Authored-By: terenc3t Co-Authored-By: djrtwo --- specs/bellatrix/p2p-interface.md | 2 +- specs/eip4844/beacon-chain.md | 43 +++++++++++++++++++++++++------- specs/eip4844/p2p-interface.md | 18 ++++++------- specs/eip4844/validator.md | 7 +++--- 4 files changed, 45 insertions(+), 25 deletions(-) diff --git a/specs/bellatrix/p2p-interface.md b/specs/bellatrix/p2p-interface.md index 60a9be7746..21badce839 100644 --- a/specs/bellatrix/p2p-interface.md +++ b/specs/bellatrix/p2p-interface.md @@ -110,7 +110,7 @@ The following gossip validation from prior specifications MUST NOT be applied if ### Transitioning the gossip See gossip transition details found in the [Altair document](../altair/p2p-interface.md#transitioning-the-gossip) for -details on how to handle transitioning gossip topics for Bellatrix. +details on how to handle transitioning gossip topics for EIP-4844. ## The Req/Resp domain diff --git a/specs/eip4844/beacon-chain.md b/specs/eip4844/beacon-chain.md index 4ac7cf3d16..1bcab3a983 100644 --- a/specs/eip4844/beacon-chain.md +++ b/specs/eip4844/beacon-chain.md @@ -31,10 +31,7 @@ ## Introduction -This upgrade adds transaction execution to the beacon chain as part of Bellatrix upgrade. - -Additionally, this upgrade introduces the following minor changes: -* Penalty parameter updates to their planned maximally punitive values +This upgrade adds blobs to the beacon chain as part of EIP-4844. ## Custom types @@ -53,6 +50,11 @@ Additionally, this upgrade introduces the following minor changes: | `FIELD_ELEMENTS_PER_BLOB` | `4096` | | `BLS_MODULUS` | `52435875175126190479447740508185965837690552500527637822603658699938581184513` | +### Domain types + +| Name | Value | +| - | - | +| `DOMAIN_BLOBS_SIDECAR` | `DomainType('0x0a000000')` | ## Preset @@ -91,11 +93,36 @@ class BeaconBlockBody(Container): sync_aggregate: SyncAggregate # Execution execution_payload: ExecutionPayload - blob_kzgs: List[KZGCommitment, MAX_OBJECT_LIST_SIZE] # [New in EIP-4844] + blob_kzgs: List[KZGCommitment, MAX_BLOBS_PER_BLOCK] # [New in EIP-4844] ``` ## Helper functions +### KZG core + +KZG core functions. These are also defined in EIP-4844 execution specs. + +#### `blob_to_kzg` + +```python +def blob_to_kzg(blob: Blob) -> KZGCommitment: + computed_kzg = bls.Z1 + for value, point_kzg in zip(blob, KZG_SETUP_LAGRANGE): + assert value < BLS_MODULUS + computed_kzg = bls.add( + computed_kzg, + bls.multiply(point_kzg, value) + ) + return computed_kzg +``` + +#### `kzg_to_versioned_hash` + +```python +def kzg_to_versioned_hash(kzg: KZGCommitment) -> VersionedHash: + return BLOB_COMMITMENT_VERSION_KZG + hash(kzg)[1:] +``` + ### Misc #### `tx_peek_blob_versioned_hashes` @@ -120,9 +147,7 @@ def verify_kzgs_against_transactions(transactions: Sequence[Transaction], blob_k for tx in transactions: if opaque_tx[0] == BLOB_TX_TYPE: all_versioned_hashes.extend(tx_peek_blob_versioned_hashes(tx)) - return all_versioned_hashes == [ - kzg_to_versioned_hash(kzg) for kzg in blob_kzgs - ] + return all_versioned_hashes == [ksg_to_version_hash(kzg) for kzg in blob_kzgs] ``` ## Beacon chain state transition function @@ -138,7 +163,7 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: process_eth1_data(state, block.body) process_operations(state, block.body) process_sync_aggregate(state, block.body.sync_aggregate) - process_blob_kzgs(state, block.body) # [New in EIP-4844] + process_blob_kzgs(state, block.body) # [New in EIP-4844] ``` #### Blob KZGs diff --git a/specs/eip4844/p2p-interface.md b/specs/eip4844/p2p-interface.md index 6b3ac9bc93..ff2a11e252 100644 --- a/specs/eip4844/p2p-interface.md +++ b/specs/eip4844/p2p-interface.md @@ -37,13 +37,13 @@ The specification of these changes continues in the same format as the network s | Name | Value | | - | - | -| `LIMIT_BLOBS_PER_SIDECAR` | `uint64(2**4)` (= 16) | +| `MAX_BLOBS_PER_BLOCK` | `uint64(2**4)` (= 16) | ## Configuration | Name | Value | Description | |------------------------------------------|-------------------------------|---------------------------------------------------------------------| -| `MAX_REQUEST_BLOBS_SIDECARS` | `2**10` (= 1024) | Maximum number of blobs sidecars in a single request | +| `MAX_REQUEST_BLOBS_SIDECARS` | `2**7` (= 128) | Maximum number of blobs sidecars in a single request | | `MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS` | `2**13` (= 8192, ~1.2 months) | The minimum epoch range over which a node must serve blobs sidecars | @@ -56,8 +56,7 @@ The specification of these changes continues in the same format as the network s class BlobsSidecar(Container): beacon_block_root: Root beacon_block_slot: Slot - shard: uint64 # [ Forward compatibility ] - blobs: List[Blob, LIMIT_BLOBS_PER_SIDECAR] + blobs: List[Blob, MAX_BLOBS_PER_BLOCK] ``` ### `SignedBlobsSidecar` @@ -117,12 +116,12 @@ Alias `sidecar = signed_blobs_sidecar.message`. - _[REJECT]_ the `sidecar.blobs` are all well formatted, i.e. the `BLSFieldElement` in valid range (`x < BLS_MODULUS`). - _[REJECT]_ the beacon proposer signature, `signed_blobs_sidecar.signature`, is valid -- i.e. ```python -domain = get_domain(state, DOMAIN_BLOBS_SIDECAR, blobs_sidecar.beacon_block_slot / SLOTS_PER_EPOCH) +domain = get_domain(state, DOMAIN_BLOBS_SIDECAR, blobs_sidecar.beacon_block_slot // SLOTS_PER_EPOCH) signing_root = compute_signing_root(blobs_sidecar, domain) assert bls.Verify(proposer_pubkey, signing_root, signed_blob_header.signature) ``` where `proposer_pubkey` is the pubkey of the beacon block proposer of `blobs_sidecar.beacon_block_slot` -- _[IGNORE]_ The sidecar is the first sidecar with valid signature received for the `(proposer_index, sidecar.beacon_block_root)` combination, +- _[IGNORE]_ The sidecar is the first sidecar with valid signature received for the `(proposer_index, sidecar.beacon_block_slot)` combination, where `proposer_index` is the validator index of the beacon block proposer of `blobs_sidecar.beacon_block_slot` Note that a sidecar may be propagated before or after the corresponding beacon block. @@ -132,7 +131,7 @@ Once both sidecar and beacon block are received, `verify_blobs_sidecar` can unlo ### Transitioning the gossip See gossip transition details found in the [Altair document](../altair/p2p-interface.md#transitioning-the-gossip) for -details on how to handle transitioning gossip topics for Bellatrix. +details on how to handle transitioning gossip topics for this upgrade. ## The Req/Resp domain @@ -181,7 +180,6 @@ Request Content: ( start_slot: Slot count: uint64 - shard: uint64 ) ``` @@ -195,8 +193,6 @@ Response Content: Requests blobs sidecars in the slot range `[start_slot, start_slot + count)`, leading up to the current head block as selected by fork choice. -The request and response format is forward-compatible with sharded sidecar sync, but MUST enforce `shard == 0` for now. - The response is unsigned, i.e. `BlobsSidecarsByRange`, as the signature of the beacon block proposer may not be available beyond the initial distribution via gossip. @@ -232,7 +228,7 @@ participating in the networking immediately, other peers MAY disconnect and/or temporarily ban such an un-synced or semi-synced client. Clients MUST respond with at least the first blobs sidecar that exists in the range, if they have it, -and no more than `MAX_REQUEST_BLOBS_SIDECARS` blocks. +and no more than `MAX_REQUEST_BLOBS_SIDECARS` sidecars. The following blobs sidecars, where they exist, MUST be sent in consecutive order. diff --git a/specs/eip4844/validator.md b/specs/eip4844/validator.md index 0bdf947bad..2a1c0a24a3 100644 --- a/specs/eip4844/validator.md +++ b/specs/eip4844/validator.md @@ -28,7 +28,7 @@ This document represents the changes to be made in the code of an "honest valida ## Prerequisites -This document is an extension of the [Bellatrix -- Honest Validator](../altair/validator.md) guide. +This document is an extension of the [Bellatrix -- Honest Validator](../bellatrix/validator.md) guide. All behaviors and definitions defined in this document, and documents it extends, carry over unless explicitly noted or overridden. All terminology, constants, functions, and protocol mechanics defined in the updated [Beacon Chain doc of EIP4844](./beacon-chain.md) are requisite for this document and used throughout. @@ -56,13 +56,12 @@ def is_data_available(slot: Slot, beacon_block_root: Root, kzgs: Sequence[KZGCom ```python def verify_blobs_sidecar(slot: Slot, beacon_block_root: Root, expected_kzgs: Sequence[KZGCommitment], blobs_sidecar: BlobsSidecar): - assert blobs_sidecar.shard == 0 # always zero, placeholder for future sharding assert slot == blobs_sidecar.beacon_block_slot assert beacon_block_root == blobs_sidecar.beacon_block_root blobs = blobs_sidecar.blobs assert len(kzgs) == len(blobs) for kzg, blob in zip(expected_kzgs, blobs): - assert blob_to_kzg(blob) == kzg + assert blob_to_kzg(blob) == kzg ``` @@ -81,7 +80,7 @@ After retrieving the execution payload from the execution engine as specified in the blobs are retrieved and processed: ```python -# execution_payload = xecution_engine.get_payload(payload_id) +# execution_payload = execution_engine.get_payload(payload_id) # block.body.execution_payload = execution_payload # ... From 9b760dfb54cce4dddb6bfcc94d9772475e8675a4 Mon Sep 17 00:00:00 2001 From: protolambda Date: Mon, 14 Mar 2022 19:08:50 +0100 Subject: [PATCH 39/98] eip4844: beacon doc - update TOC --- specs/eip4844/beacon-chain.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/specs/eip4844/beacon-chain.md b/specs/eip4844/beacon-chain.md index 1bcab3a983..9bbe6b3eea 100644 --- a/specs/eip4844/beacon-chain.md +++ b/specs/eip4844/beacon-chain.md @@ -11,6 +11,7 @@ - [Introduction](#introduction) - [Custom types](#custom-types) - [Constants](#constants) + - [Domain types](#domain-types) - [Preset](#preset) - [Trusted setup](#trusted-setup) - [Configuration](#configuration) @@ -18,6 +19,9 @@ - [Extended containers](#extended-containers) - [`BeaconBlockBody`](#beaconblockbody) - [Helper functions](#helper-functions) + - [KZG core](#kzg-core) + - [`blob_to_kzg`](#blob_to_kzg) + - [`kzg_to_versioned_hash`](#kzg_to_versioned_hash) - [Misc](#misc) - [`tx_peek_blob_versioned_hashes`](#tx_peek_blob_versioned_hashes) - [`verify_kzgs_against_transactions`](#verify_kzgs_against_transactions) From dc5f9dffa02d1c926437b3dfc7f23e9d1be85168 Mon Sep 17 00:00:00 2001 From: protolambda Date: Mon, 14 Mar 2022 21:50:07 +0100 Subject: [PATCH 40/98] eip4844: process_blob_kzgs - move TODO to issue, add missing input argument --- specs/eip4844/beacon-chain.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/specs/eip4844/beacon-chain.md b/specs/eip4844/beacon-chain.md index 9bbe6b3eea..d0de754457 100644 --- a/specs/eip4844/beacon-chain.md +++ b/specs/eip4844/beacon-chain.md @@ -173,12 +173,8 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: #### Blob KZGs ```python -def process_blob_kzgs(body: BeaconBlockBody): +def process_blob_kzgs(state: BeaconState, body: BeaconBlockBody): assert verify_kzgs_against_transactions(body.execution_payload.transactions, body.blob_kzgs) - - # TODO do we want to buffer the kzg commitments in the BeaconState, like in the full sharding design? - # This could make a proof to any particular blob commitment more efficient, - # but the buffer structure is also likely to change with full sharding. ``` ## Testing From 80292f7957fe92d3d27f2d555241c98709eebe94 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 15 Mar 2022 13:14:40 -0600 Subject: [PATCH 41/98] Update specs/bellatrix/validator.md Co-authored-by: Hsiao-Wei Wang --- specs/bellatrix/validator.md | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/bellatrix/validator.md b/specs/bellatrix/validator.md index c392c8bdd0..dc64d1c8ee 100644 --- a/specs/bellatrix/validator.md +++ b/specs/bellatrix/validator.md @@ -149,6 +149,7 @@ def prepare_execution_payload(state: BeaconState, # Set safe and head block hashes to the same value return execution_engine.notify_forkchoice_updated( head_block_hash=parent_hash, + # TODO: Use `parent_hash` as a stub for now. safe_block_hash=parent_hash, finalized_block_hash=finalized_block_hash, payload_attributes=payload_attributes, From 0afa7e1b38ff882f720f78b2560a7edaac5aa9a0 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 15 Mar 2022 18:01:47 -0600 Subject: [PATCH 42/98] update language to reflect latest work on withdrawals --- specs/phase0/validator.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index e21ff980ab..acffccebc8 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -163,10 +163,7 @@ The `withdrawal_credentials` field must be such that: * `withdrawal_credentials[12:] == eth1_withdrawal_address` After the merge of the current Ethereum application layer into the Beacon Chain, -withdrawals to `eth1_withdrawal_address` will be normal ETH transfers (with no payload other than the validator's ETH) -triggered by a user transaction that will set the gas price and gas limit as well pay fees. -As long as the account or contract with address `eth1_withdrawal_address` can receive ETH transfers, -the future withdrawal protocol is agnostic to all other implementation details. +withdrawals to `eth1_withdrawal_address` will simply be increases to the account's ETH balance that do **NOT** trigger any EVM execution. ### Submit deposit From 255e942f64feac2ac499c6e1b3c2e164833e0511 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 16 Mar 2022 12:01:42 -0600 Subject: [PATCH 43/98] add validator and fork-choice mods for withdrawals in capella --- setup.py | 3 +- specs/capella/fork-choice.md | 53 +++++++++++++++++++++ specs/capella/validator.md | 91 ++++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 specs/capella/fork-choice.md diff --git a/setup.py b/setup.py index 818428aedc..4d88e709a7 100644 --- a/setup.py +++ b/setup.py @@ -701,7 +701,7 @@ def combine_dicts(old_dict: Dict[str, T], new_dict: Dict[str, T]) -> Dict[str, T 'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256', 'bytes', 'byte', 'ByteList', 'ByteVector', 'Dict', 'dict', 'field', 'ceillog2', 'floorlog2', 'Set', - 'Optional', + 'Optional', 'Sequence', ] @@ -896,6 +896,7 @@ def finalize_options(self): self.md_doc_paths += """ specs/capella/beacon-chain.md specs/capella/fork.md + specs/capella/fork-choice.md specs/capella/validator.md specs/capella/p2p-interface.md """ diff --git a/specs/capella/fork-choice.md b/specs/capella/fork-choice.md new file mode 100644 index 0000000000..10f034e1dc --- /dev/null +++ b/specs/capella/fork-choice.md @@ -0,0 +1,53 @@ +# Capella -- Fork Choice + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + + + +## Introduction + +This is the modification of the fork choice according to the Capella upgrade. + +Unless stated explicitly, all prior functionality from [Bellatrix](../bellatrix/fork-choice.md) is inherited. + +## Custom types + +## Protocols + +### `ExecutionEngine` + +*Note*: The `notify_forkchoice_updated` function is modified in the `ExecutionEngine` protocol at the Capella upgrade. + +#### `notify_forkchoice_updated` + +The only change made is to the `PayloadAttributes` container through the addition of `withdrawals`. +Otherwise, `notify_forkchoice_updated` inherits all prior functionality. + +```python +def notify_forkchoice_updated(self: ExecutionEngine, + head_block_hash: Hash32, + finalized_block_hash: Hash32, + payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: + ... +``` + +## Helpers + +### Extended `PayloadAttributes` + +`PayloadAttributes` is extended with the `withdrawals` field. + +```python +@dataclass +class PayloadAttributes(object): + timestamp: uint64 + prev_randao: Bytes32 + suggested_fee_recipient: ExecutionAddress + withdrawals: Sequence[Withdrawal] +``` diff --git a/specs/capella/validator.md b/specs/capella/validator.md index e69de29bb2..edf5d72bd9 100644 --- a/specs/capella/validator.md +++ b/specs/capella/validator.md @@ -0,0 +1,91 @@ +# Capella -- Honest Validator + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + + + + +## Introduction + +This document represents the changes to be made in the code of an "honest validator" to implement the Capella upgrade. + +## Prerequisites + +This document is an extension of the [Bellatrix -- Honest Validator](../bellatrix/validator.md) guide. +All behaviors and definitions defined in this document, and documents it extends, carry over unless explicitly noted or overridden. + +All terminology, constants, functions, and protocol mechanics defined in the updated Beacon Chain doc of [Capella](./beacon-chain.md) are requisite for this document and used throughout. +Please see related Beacon Chain doc before continuing and use them as a reference throughout. + +## Helpers + +## Protocols + +### `ExecutionEngine` + +#### `get_payload` + +`get_payload` returns the upgraded Capella `ExecutionPayload` type. + +## Beacon chain responsibilities + +All validator responsibilities remain unchanged other than those noted below. + +### Block proposal + +#### Constructing the `BeaconBlockBody` + +##### ExecutionPayload + +`ExecutionPayload`s are constructed as they were in Bellatrix, except that the +expected withdrawals for the slot must be gathered from the `state` (utilizing the +helper `get_expected_withdrawals`) and passed into the `ExecutionEngine` within `prepare_execution_payload`. + + +```python +def get_expected_withdrawals(state: BeaconState) -> Sequence[Withdrawal]: + num_withdrawals = min(MAX_WITHDRAWALS_PER_PAYLOAD, len(state.withdrawals_queue)) + return state.withdrawals_queue[:num_withdrawals] +``` + +*Note*: The only change made to `prepare_execution_payload` is to call +`get_expected_withdrawals()` to set the new `withdrawals` field of `PayloadAttributes`. + +```python +def prepare_execution_payload(state: BeaconState, + pow_chain: Dict[Hash32, PowBlock], + finalized_block_hash: Hash32, + suggested_fee_recipient: ExecutionAddress, + execution_engine: ExecutionEngine) -> Optional[PayloadId]: + if not is_merge_transition_complete(state): + is_terminal_block_hash_set = TERMINAL_BLOCK_HASH != Hash32() + is_activation_epoch_reached = get_current_epoch(state) >= TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH + if is_terminal_block_hash_set and not is_activation_epoch_reached: + # Terminal block hash is set but activation epoch is not yet reached, no prepare payload call is needed + return None + + terminal_pow_block = get_terminal_pow_block(pow_chain) + if terminal_pow_block is None: + # Pre-merge, no prepare payload call is needed + return None + # Signify merge via producing on top of the terminal PoW block + parent_hash = terminal_pow_block.block_hash + else: + # Post-merge, normal payload + parent_hash = state.latest_execution_payload_header.block_hash + + # Set the forkchoice head and initiate the payload build process + payload_attributes = PayloadAttributes( + timestamp=compute_timestamp_at_slot(state, state.slot), + prev_randao=get_randao_mix(state, get_current_epoch(state)), + suggested_fee_recipient=suggested_fee_recipient, + withdrawals=get_expected_withdrawals(state), # [New in Capella] + ) + return execution_engine.notify_forkchoice_updated(parent_hash, finalized_block_hash, payload_attributes) +``` From 36aae1d84877d7c34a8b3e02fdc058ec618708d3 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 16 Mar 2022 13:35:29 -0600 Subject: [PATCH 44/98] add tests for process_withdrawals --- specs/capella/beacon-chain.md | 7 +- .../test_process_withdrawals.py | 242 ++++++++++++++++++ .../test/helpers/execution_payload.py | 4 + 3 files changed, 248 insertions(+), 5 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_withdrawals.py diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index 001bac70ae..e912f608e0 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -256,11 +256,8 @@ def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None: dequeued_withdrawals = state.withdrawals_queue[:num_withdrawals] assert len(dequeued_withdrawals) == len(payload.withdrawals) - for dequeued_receipt, withdrawal_transaction in zip(dequeued_withdrawals, payload.withdrawals): - assert dequeued_receipt == withdrawal_transaction - - # Ensure no withdrawal type transactions in the normal payload transactions - # assert no_withdrawal_type_transactions_in(payload.transactions) + for dequeued_receipt, withdrawal in zip(dequeued_withdrawals, payload.withdrawals): + assert dequeued_receipt == withdrawal # Remove dequeued receipts from state state.withdrawals_queue = state.withdrawals_queue[num_withdrawals:] diff --git a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_withdrawals.py b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_withdrawals.py new file mode 100644 index 0000000000..204816c994 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_withdrawals.py @@ -0,0 +1,242 @@ +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, +) + +from eth2spec.test.context import spec_state_test, expect_assertion_error, with_capella_and_later + +from eth2spec.test.helpers.state import next_slot + + +def prepare_withdrawals_queue(spec, state, num_withdrawals): + pre_queue_len = len(state.withdrawals_queue) + + for i in range(num_withdrawals): + withdrawal = spec.Withdrawal( + index=i + 5, + address=b'\x42' * 20, + amount=200000 + i, + ) + state.withdrawals_queue.append(withdrawal) + + assert len(state.withdrawals_queue) == num_withdrawals + pre_queue_len + + +def run_withdrawals_processing(spec, state, execution_payload, valid=True): + """ + Run ``process_execution_payload``, yielding: + - pre-state ('pre') + - execution payload ('execution_payload') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + + pre_withdrawals_queue = state.withdrawals_queue.copy() + num_withdrawals = min(spec.MAX_WITHDRAWALS_PER_PAYLOAD, len(pre_withdrawals_queue)) + + yield 'pre', state + yield 'execution_payload', execution_payload + + if not valid: + expect_assertion_error(lambda: spec.process_withdrawals(state, execution_payload)) + yield 'post', None + return + + spec.process_withdrawals(state, execution_payload) + + yield 'post', state + + if len(pre_withdrawals_queue) == 0: + assert len(state.withdrawals_queue) == 0 + elif len(pre_withdrawals_queue) <= num_withdrawals: + assert len(state.withdrawals_queue) == 0 + else: + assert state.withdrawals_queue == pre_withdrawals_queue[num_withdrawals:] + + +@with_capella_and_later +@spec_state_test +def test_success_empty_queue(spec, state): + assert len(state.withdrawals_queue) == 0 + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing(spec, state, execution_payload) + + +@with_capella_and_later +@spec_state_test +def test_success_one_in_queue(spec, state): + prepare_withdrawals_queue(spec, state, 1) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing(spec, state, execution_payload) + + +@with_capella_and_later +@spec_state_test +def test_success_max_per_slot_in_queue(spec, state): + prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing(spec, state, execution_payload) + + +@with_capella_and_later +@spec_state_test +def test_success_a_lot_in_queue(spec, state): + prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing(spec, state, execution_payload) + + +# +# Failure cases in which the number of withdrawals in the execution_payload is incorrect +# + +@with_capella_and_later +@spec_state_test +def test_fail_empty_queue_non_empty_withdrawals(spec, state): + assert len(state.withdrawals_queue) == 0 + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + withdrawal = spec.Withdrawal( + index=0, + address=b'\x30' * 20, + amount=420, + ) + execution_payload.withdrawals.append(withdrawal) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_one_in_queue_none_in_withdrawals(spec, state): + prepare_withdrawals_queue(spec, state, 1) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals = [] + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_one_in_queue_two_in_withdrawals(spec, state): + prepare_withdrawals_queue(spec, state, 1) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals.append(execution_payload.withdrawals[0].copy()) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_max_per_slot_in_queue_one_less_in_withdrawals(spec, state): + prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals = execution_payload.withdrawals[:-1] + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_a_lot_in_queue_too_few_in_withdrawals(spec, state): + prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals = execution_payload.withdrawals[:-1] + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +# +# Failure cases in which the withdrawals in the execution_payload are incorrect +# + +@with_capella_and_later +@spec_state_test +def test_fail_incorrect_dequeue_index(spec, state): + prepare_withdrawals_queue(spec, state, 1) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals[0].index += 1 + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_incorrect_dequeue_address(spec, state): + prepare_withdrawals_queue(spec, state, 1) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals[0].address = b'\xff' * 20 + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_incorrect_dequeue_amount(spec, state): + prepare_withdrawals_queue(spec, state, 1) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals[0].amount += 1 + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_one_of_many_dequeued_incorrectly(spec, state): + prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + num_withdrawals = len(execution_payload.withdrawals) + + # Pick withdrawal in middle of list and mutate + withdrawal = execution_payload.withdrawals[num_withdrawals // 2] + withdrawal.index += 1 + withdrawal.address = b'\x99' * 20 + withdrawal.amount += 4000000 + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_many_dequeued_incorrectly(spec, state): + prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + for i, withdrawal in enumerate(execution_payload.withdrawals): + if i % 3 == 0: + withdrawal.index += 1 + elif i % 3 == 1: + withdrawal.address = (i).to_bytes(20, 'big') + else: + withdrawal.amount += 1 + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index 915883aac2..5e70666324 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -28,6 +28,10 @@ def build_empty_execution_payload(spec, state, randao_mix=None): block_hash=spec.Hash32(), transactions=empty_txs, ) + if spec.fork not in FORKS_BEFORE_CAPELLA: + num_withdrawals = min(spec.MAX_WITHDRAWALS_PER_PAYLOAD, len(state.withdrawals_queue)) + payload.withdrawals = state.withdrawals_queue[:num_withdrawals] + # TODO: real RLP + block hash logic would be nice, requires RLP and keccak256 dependency however. payload.block_hash = spec.Hash32(spec.hash(payload.hash_tree_root() + b"FAKE RLP HASH")) From 79cdb88e66b0ec1c0a8e403391cd07428d5e99ee Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 16 Mar 2022 15:41:37 -0600 Subject: [PATCH 45/98] wip 00 to 01 cred change --- specs/capella/beacon-chain.md | 98 ++++++++++++++++++- .../test_process_bls_to_execution_change.py | 47 +++++++++ 2 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index e912f608e0..bcc35381f4 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -20,14 +20,20 @@ to validator withdrawals. Including: ## Custom types -## Constants - We define the following Python custom types for type hinting and readability: | Name | SSZ equivalent | Description | | - | - | - | | `WithdrawalIndex` | `uint64` | an index of a `Withdrawal`| +## Constants + +### Domain types + +| Name | Value | +| - | - | +| `DOMAIN_BLS_TO_EXECUTION_CHANGE` | `DomainType('0x0A000000')` | + ## Preset ### State list lengths @@ -36,6 +42,12 @@ We define the following Python custom types for type hinting and readability: | - | - | :-: | :-: | | `WITHDRAWALS_QUEUE_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | withdrawals enqueued in state| +### Max operations per block + +| Name | Value | +| - | - | +| `MAX_BLS_TO_EXECUTION_CHANGES` | `2**4` (= 16) | + ### Execution | Name | Value | Description | @@ -64,6 +76,25 @@ class Validator(Container): withdrawn_epoch: Epoch # [New in Capella] ``` +#### `BeaconBlockBody` + +```python +class BeaconBlockBody(Container): + randao_reveal: BLSSignature + eth1_data: Eth1Data # Eth1 data vote + graffiti: Bytes32 # Arbitrary data + # Operations + proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] + attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] + attestations: List[Attestation, MAX_ATTESTATIONS] + deposits: List[Deposit, MAX_DEPOSITS] + voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] + bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES] + sync_aggregate: SyncAggregate + # Execution + execution_payload: ExecutionPayload +``` + #### `BeaconState` ```python @@ -166,6 +197,23 @@ class Withdrawal(Container): amount: Gwei ``` +#### `BLSToExecutionChange` + +```python +class BLSToExecutionChange(Container): + validator_index: ValidatorIndex + from_bls_pubkey: BLSPubkey + to_execution_address: ExecutionAddress +``` + +#### `SignedBLSToExecutionChange` + +```python +class SignedBLSToExecutionChange(Container): + message: BLSToExecutionChange + signature: BLSSignature +``` + ## Helpers ### Beacon state mutators @@ -297,3 +345,49 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe withdrawals_root=hash_tree_root(payload.withdrawals), ) ``` + +#### Modified `process_operations` + +*Note*: The function `process_operations` is modified to process `BLSToExecutionChange` operations included in the block. + +```python +def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: + # Verify that outstanding deposits are processed up to the maximum number of deposits + assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index) + + def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: + for operation in operations: + fn(state, operation) + + for_ops(body.proposer_slashings, process_proposer_slashing) + for_ops(body.attester_slashings, process_attester_slashing) + for_ops(body.attestations, process_attestation) + for_ops(body.deposits, process_deposit) + for_ops(body.voluntary_exits, process_voluntary_exit) + for_ops(body.bls_to_execution_changes, process_bls_to_execution_change) +``` + +#### New `process_bls_to_execution_change` + +```python +def process_bls_to_execution_change(state: BeaconState, + signed_address_change: SignedBLSToExecutionChange) -> None: + address_change = signed_address_change.message + + assert address_change.validator_index < len(state.validators) + + validator = state.validators[address_change.validator_index] + + assert validator.withdrawal_credentials[:1] == BLS_WITHDRAWAL_PREFIX + assert validator.withdrawal_credentials[1:] == hash(address_change.from_bls_pubkey)[1:] + + domain = get_domain(state, DOMAIN_BLS_TO_EXECUTION_CHANGE) + signing_root = compute_signing_root(address_change, domain) + assert bls.Verify(address_change.from_bls_pubkey, signing_root, signed_address_change.signature) + + validator.withdrawal_credentials = ( + ETH1_ADDRESS_WITHDRAWAL_PREFIX + + 0x00 * 11 + + address_change.to_execution_address + ) +``` diff --git a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py new file mode 100644 index 0000000000..1a7fbfa815 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py @@ -0,0 +1,47 @@ +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, +) + +from eth2spec.test.context import spec_state_test, expect_assertion_error, with_capella_and_later + +def run_bls_to_execution_change_processing(spec, state, address_change, valid=True): + """ + Run ``process_bls_to_execution_change``, yielding: + - pre-state ('pre') + - address-change ('address_change') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + # yield pre-state + yield 'pre', state + + yield 'address_change', address_change + + # If the address_change is invalid, processing is aborted, and there is no post-state. + if not valid: + expect_assertion_error(lambda: spec.process_bls_to_execution_change(state, attestation)) + yield 'post', None + return + + # process address change + spec.process_bls_to_execution_change(state, attestation) + + # Make sure the address change has been processed + assert state.withdrawal_credentials[:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX + assert state.withdrawal_credentials[1:12] == b'\x00' * 11 + assert state.withdrawal_credentials[12:] == address_change.to_execution_address + + # yield post-state + yield 'post', state + + +@with_capella_and_later +@spec_state_test +def test_success(spec, state): + address_change = spec.BLSToExecutionChange( + validator_index=0, + from_bls_pubkey=TEST, + to_execution_address=b'\x42' * 20, + ) + + yield from run_bls_to_execution_change_processing(spec, state, address_change) From 36b004456994a52fa0a1a57b346f8bd83e8bec5c Mon Sep 17 00:00:00 2001 From: terence tsao Date: Sun, 20 Mar 2022 08:20:08 -0700 Subject: [PATCH 46/98] EIP4844: fix a minor typo --- specs/eip4844/validator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/eip4844/validator.md b/specs/eip4844/validator.md index 2a1c0a24a3..2083934c54 100644 --- a/specs/eip4844/validator.md +++ b/specs/eip4844/validator.md @@ -59,7 +59,7 @@ def verify_blobs_sidecar(slot: Slot, beacon_block_root: Root, assert slot == blobs_sidecar.beacon_block_slot assert beacon_block_root == blobs_sidecar.beacon_block_root blobs = blobs_sidecar.blobs - assert len(kzgs) == len(blobs) + assert len(expected_kzgs) == len(blobs) for kzg, blob in zip(expected_kzgs, blobs): assert blob_to_kzg(blob) == kzg ``` From 493b16902276c44bcda1e6cf26ff38ec7d92919e Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 22 Mar 2022 07:55:45 -0600 Subject: [PATCH 47/98] refine naming for withdrawals --- specs/capella/beacon-chain.md | 10 +++++----- specs/capella/fork.md | 2 +- .../epoch_processing/test_process_full_withdrawals.py | 2 +- tests/core/pyspec/eth2spec/test/helpers/genesis.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index e912f608e0..c7b4fe8978 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -61,7 +61,7 @@ class Validator(Container): activation_epoch: Epoch exit_epoch: Epoch withdrawable_epoch: Epoch # When validator can withdraw funds - withdrawn_epoch: Epoch # [New in Capella] + fully_withdrawn_epoch: Epoch # [New in Capella] ``` #### `BeaconState` @@ -173,7 +173,7 @@ class Withdrawal(Container): #### `withdraw` ```python -def withdraw(state: BeaconState, index: ValidatorIndex, amount: Gwei) -> None: +def withdraw_balance(state: BeaconState, index: ValidatorIndex, amount: Gwei) -> None: # Decrease the validator's balance decrease_balance(state, index, amount) # Create a corresponding withdrawal receipt @@ -196,7 +196,7 @@ def is_fully_withdrawable_validator(validator: Validator, epoch: Epoch) -> bool: Check if ``validator`` is fully withdrawable. """ is_eth1_withdrawal_prefix = validator.withdrawal_credentials[0:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX - return is_eth1_withdrawal_prefix and validator.withdrawable_epoch <= epoch < validator.withdrawn_epoch + return is_eth1_withdrawal_prefix and validator.withdrawable_epoch <= epoch < validator.fully_withdrawn_epoch ``` ## Beacon chain state transition function @@ -230,8 +230,8 @@ def process_full_withdrawals(state: BeaconState) -> None: for index, validator in enumerate(state.validators): if is_fully_withdrawable_validator(validator, current_epoch): # TODO, consider the zero-balance case - withdraw(state, ValidatorIndex(index), state.balances[index]) - validator.withdrawn_epoch = current_epoch + withdraw_balance(state, ValidatorIndex(index), state.balances[index]) + validator.fully_withdrawn_epoch = current_epoch ``` ### Block processing diff --git a/specs/capella/fork.md b/specs/capella/fork.md index 8585eddf4e..c0363727d1 100644 --- a/specs/capella/fork.md +++ b/specs/capella/fork.md @@ -97,7 +97,7 @@ def upgrade_to_capella(pre: bellatrix.BeaconState) -> BeaconState: activation_epoch=pre_validator.activation_epoch, exit_epoch=pre_validator.exit_epoch, withdrawable_epoch=pre_validator.withdrawable_epoch, - withdrawn_epoch=FAR_FUTURE_EPOCH, + fully_withdrawn_epoch=FAR_FUTURE_EPOCH, ) post.validators.append(post_validator) diff --git a/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_full_withdrawals.py b/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_full_withdrawals.py index 64aba5f6d3..305f6e1baa 100644 --- a/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_full_withdrawals.py +++ b/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_full_withdrawals.py @@ -31,7 +31,7 @@ def run_process_full_withdrawals(spec, state, num_expected_withdrawals=None): for index in to_be_withdrawn_indices: validator = state.validators[index] - assert validator.withdrawn_epoch == spec.get_current_epoch(state) + assert validator.fully_withdrawn_epoch == spec.get_current_epoch(state) assert state.balances[index] == 0 assert len(state.withdrawals_queue) == len(pre_withdrawals_queue) + num_expected_withdrawals diff --git a/tests/core/pyspec/eth2spec/test/helpers/genesis.py b/tests/core/pyspec/eth2spec/test/helpers/genesis.py index e92d3ead9c..1ca408598e 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/genesis.py +++ b/tests/core/pyspec/eth2spec/test/helpers/genesis.py @@ -20,7 +20,7 @@ def build_mock_validator(spec, i: int, balance: int): ) if spec.fork not in FORKS_BEFORE_CAPELLA: - validator.withdrawn_epoch = spec.FAR_FUTURE_EPOCH + validator.fully_withdrawn_epoch = spec.FAR_FUTURE_EPOCH return validator From 9a8ff4f3ab0a33e2c8f3f4ad8e61ce4a09b7d08f Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 22 Mar 2022 08:00:53 -0600 Subject: [PATCH 48/98] topo sort --- specs/capella/beacon-chain.md | 116 +++++++++++++++++----------------- specs/capella/fork-choice.md | 1 + 2 files changed, 59 insertions(+), 58 deletions(-) diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index c7b4fe8978..b09695b763 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -46,8 +46,65 @@ We define the following Python custom types for type hinting and readability: ## Containers +### New containers + +#### `Withdrawal` + +```python +class Withdrawal(Container): + index: WithdrawalIndex + address: ExecutionAddress + amount: Gwei +``` + ### Extended Containers +#### `ExecutionPayload` + +```python +class ExecutionPayload(Container): + # Execution block header fields + parent_hash: Hash32 + fee_recipient: ExecutionAddress # 'beneficiary' in the yellow paper + state_root: Bytes32 + receipts_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + prev_randao: Bytes32 # 'difficulty' in the yellow paper + block_number: uint64 # 'number' in the yellow paper + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + # Extra payload fields + block_hash: Hash32 # Hash of execution block + transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] + withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] # [New in Capella] +``` + +#### `ExecutionPayloadHeader` + +```python +class ExecutionPayloadHeader(Container): + # Execution block header fields + parent_hash: Hash32 + fee_recipient: ExecutionAddress + state_root: Bytes32 + receipts_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + prev_randao: Bytes32 + block_number: uint64 + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + # Extra payload fields + block_hash: Hash32 # Hash of execution block + transactions_root: Root + withdrawals_root: Root # [New in Capella] +``` + #### `Validator` ```python @@ -109,63 +166,6 @@ class BeaconState(Container): withdrawals_queue: List[Withdrawal, WITHDRAWALS_QUEUE_LIMIT] # [New in Capella] ``` -#### `ExecutionPayload` - -```python -class ExecutionPayload(Container): - # Execution block header fields - parent_hash: Hash32 - fee_recipient: ExecutionAddress # 'beneficiary' in the yellow paper - state_root: Bytes32 - receipts_root: Bytes32 - logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - prev_randao: Bytes32 # 'difficulty' in the yellow paper - block_number: uint64 # 'number' in the yellow paper - gas_limit: uint64 - gas_used: uint64 - timestamp: uint64 - extra_data: ByteList[MAX_EXTRA_DATA_BYTES] - base_fee_per_gas: uint256 - # Extra payload fields - block_hash: Hash32 # Hash of execution block - transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] - withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] # [New in Capella] -``` - -#### `ExecutionPayloadHeader` - -```python -class ExecutionPayloadHeader(Container): - # Execution block header fields - parent_hash: Hash32 - fee_recipient: ExecutionAddress - state_root: Bytes32 - receipts_root: Bytes32 - logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - prev_randao: Bytes32 - block_number: uint64 - gas_limit: uint64 - gas_used: uint64 - timestamp: uint64 - extra_data: ByteList[MAX_EXTRA_DATA_BYTES] - base_fee_per_gas: uint256 - # Extra payload fields - block_hash: Hash32 # Hash of execution block - transactions_root: Root - withdrawals_root: Root # [New in Capella] -``` - -### New containers - -#### `Withdrawal` - -```python -class Withdrawal(Container): - index: WithdrawalIndex - address: ExecutionAddress - amount: Gwei -``` - ## Helpers ### Beacon state mutators @@ -294,6 +294,6 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe base_fee_per_gas=payload.base_fee_per_gas, block_hash=payload.block_hash, transactions_root=hash_tree_root(payload.transactions), - withdrawals_root=hash_tree_root(payload.withdrawals), + withdrawals_root=hash_tree_root(payload.withdrawals), # [New in Capella] ) ``` diff --git a/specs/capella/fork-choice.md b/specs/capella/fork-choice.md index 10f034e1dc..b99ff4de4c 100644 --- a/specs/capella/fork-choice.md +++ b/specs/capella/fork-choice.md @@ -32,6 +32,7 @@ Otherwise, `notify_forkchoice_updated` inherits all prior functionality. ```python def notify_forkchoice_updated(self: ExecutionEngine, head_block_hash: Hash32, + safe_block_hash: Hash32, finalized_block_hash: Hash32, payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: ... From 03f9503de0f229610b7d8dcc2a3b846548320dd8 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 22 Mar 2022 08:03:55 -0600 Subject: [PATCH 49/98] Apply suggestions from code review Co-authored-by: Alex Stokes --- specs/capella/beacon-chain.md | 8 ++++---- specs/capella/fork-choice.md | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index c7b4fe8978..7bae80d570 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -195,7 +195,7 @@ def is_fully_withdrawable_validator(validator: Validator, epoch: Epoch) -> bool: """ Check if ``validator`` is fully withdrawable. """ - is_eth1_withdrawal_prefix = validator.withdrawal_credentials[0:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX + is_eth1_withdrawal_prefix = validator.withdrawal_credentials[:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX return is_eth1_withdrawal_prefix and validator.withdrawable_epoch <= epoch < validator.fully_withdrawn_epoch ``` @@ -256,10 +256,10 @@ def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None: dequeued_withdrawals = state.withdrawals_queue[:num_withdrawals] assert len(dequeued_withdrawals) == len(payload.withdrawals) - for dequeued_receipt, withdrawal in zip(dequeued_withdrawals, payload.withdrawals): - assert dequeued_receipt == withdrawal + for dequeued_withdrawal, withdrawal in zip(dequeued_withdrawals, payload.withdrawals): + assert dequeued_withdrawal == withdrawal - # Remove dequeued receipts from state + # Remove dequeued withdrawals from state state.withdrawals_queue = state.withdrawals_queue[num_withdrawals:] ``` diff --git a/specs/capella/fork-choice.md b/specs/capella/fork-choice.md index 10f034e1dc..5a2ef00b68 100644 --- a/specs/capella/fork-choice.md +++ b/specs/capella/fork-choice.md @@ -33,7 +33,7 @@ Otherwise, `notify_forkchoice_updated` inherits all prior functionality. def notify_forkchoice_updated(self: ExecutionEngine, head_block_hash: Hash32, finalized_block_hash: Hash32, - payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: + payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: # new in Capella ... ``` @@ -49,5 +49,5 @@ class PayloadAttributes(object): timestamp: uint64 prev_randao: Bytes32 suggested_fee_recipient: ExecutionAddress - withdrawals: Sequence[Withdrawal] + withdrawals: Sequence[Withdrawal] # new in Capella ``` From a2db44693b55a352ea4dd11ba92aaaa32369deb9 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 22 Mar 2022 08:14:38 -0600 Subject: [PATCH 50/98] build --- specs/capella/fork-choice.md | 2 +- specs/capella/validator.md | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/specs/capella/fork-choice.md b/specs/capella/fork-choice.md index 80bec29337..47acf2cfb5 100644 --- a/specs/capella/fork-choice.md +++ b/specs/capella/fork-choice.md @@ -34,7 +34,7 @@ def notify_forkchoice_updated(self: ExecutionEngine, head_block_hash: Hash32, safe_block_hash: Hash32, finalized_block_hash: Hash32, - payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: # new in Capella + payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: ... ``` diff --git a/specs/capella/validator.md b/specs/capella/validator.md index edf5d72bd9..5e1f5fd0ed 100644 --- a/specs/capella/validator.md +++ b/specs/capella/validator.md @@ -87,5 +87,12 @@ def prepare_execution_payload(state: BeaconState, suggested_fee_recipient=suggested_fee_recipient, withdrawals=get_expected_withdrawals(state), # [New in Capella] ) - return execution_engine.notify_forkchoice_updated(parent_hash, finalized_block_hash, payload_attributes) + # Set safe and head block hashes to the same value + return execution_engine.notify_forkchoice_updated( + head_block_hash=parent_hash, + # TODO: Use `parent_hash` as a stub for now. + safe_block_hash=parent_hash, + finalized_block_hash=finalized_block_hash, + payload_attributes=payload_attributes, + ) ``` From b469593219d8967feac2ed310b5766b09284271d Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 22 Mar 2022 08:22:35 -0600 Subject: [PATCH 51/98] toc --- Makefile | 1 + specs/capella/beacon-chain.md | 27 +++++++++++++++++++ specs/capella/fork-choice.md | 8 ++++++ specs/capella/fork.md | 6 +++++ .../eth2spec/test/helpers/capella/fork.py | 2 +- 5 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ee3b4095f8..17aac0cc24 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,7 @@ GENERATOR_VENVS = $(patsubst $(GENERATOR_DIR)/%, $(GENERATOR_DIR)/%venv, $(GENER MARKDOWN_FILES = $(wildcard $(SPEC_DIR)/phase0/*.md) $(wildcard $(SPEC_DIR)/altair/*.md) $(wildcard $(SSZ_DIR)/*.md) \ $(wildcard $(SPEC_DIR)/bellatrix/*.md) \ + $(wildcard $(SPEC_DIR)/capella/*.md) \ $(wildcard $(SPEC_DIR)/custody/*.md) \ $(wildcard $(SPEC_DIR)/das/*.md) \ $(wildcard $(SPEC_DIR)/sharding/*.md) \ diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index eff3db7b79..81a7c17072 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -6,6 +6,33 @@ +- [Introduction](#introduction) +- [Custom types](#custom-types) +- [Constants](#constants) +- [Preset](#preset) + - [State list lengths](#state-list-lengths) + - [Execution](#execution) +- [Configuration](#configuration) +- [Containers](#containers) + - [New containers](#new-containers) + - [`Withdrawal`](#withdrawal) + - [Extended Containers](#extended-containers) + - [`ExecutionPayload`](#executionpayload) + - [`ExecutionPayloadHeader`](#executionpayloadheader) + - [`Validator`](#validator) + - [`BeaconState`](#beaconstate) +- [Helpers](#helpers) + - [Beacon state mutators](#beacon-state-mutators) + - [`withdraw`](#withdraw) + - [Predicates](#predicates) + - [`is_fully_withdrawable_validator`](#is_fully_withdrawable_validator) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Epoch processing](#epoch-processing) + - [Withdrawals](#withdrawals) + - [Block processing](#block-processing) + - [New `process_withdrawals`](#new-process_withdrawals) + - [Modified `process_execution_payload`](#modified-process_execution_payload) + diff --git a/specs/capella/fork-choice.md b/specs/capella/fork-choice.md index 47acf2cfb5..f7a76275d4 100644 --- a/specs/capella/fork-choice.md +++ b/specs/capella/fork-choice.md @@ -7,6 +7,14 @@ +- [Introduction](#introduction) +- [Custom types](#custom-types) +- [Protocols](#protocols) + - [`ExecutionEngine`](#executionengine) + - [`notify_forkchoice_updated`](#notify_forkchoice_updated) +- [Helpers](#helpers) + - [Extended `PayloadAttributes`](#extended-payloadattributes) + diff --git a/specs/capella/fork.md b/specs/capella/fork.md index c0363727d1..5f015a4ff2 100644 --- a/specs/capella/fork.md +++ b/specs/capella/fork.md @@ -5,6 +5,12 @@ +- [Introduction](#introduction) +- [Configuration](#configuration) +- [Fork to Capella](#fork-to-capella) + - [Fork trigger](#fork-trigger) + - [Upgrading the state](#upgrading-the-state) + ## Introduction diff --git a/tests/core/pyspec/eth2spec/test/helpers/capella/fork.py b/tests/core/pyspec/eth2spec/test/helpers/capella/fork.py index eb85da8bbb..41975904fe 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/capella/fork.py +++ b/tests/core/pyspec/eth2spec/test/helpers/capella/fork.py @@ -50,7 +50,7 @@ def run_fork_test(post_spec, pre_state): ] for field in stable_validator_fields: assert getattr(pre_validator, field) == getattr(post_validator, field) - assert post_validator.withdrawn_epoch == post_spec.FAR_FUTURE_EPOCH + assert post_validator.fully_withdrawn_epoch == post_spec.FAR_FUTURE_EPOCH assert pre_state.fork.current_version == post_state.fork.previous_version assert post_state.fork.current_version == post_spec.config.CAPELLA_FORK_VERSION From 38496ba021e3c838434d2fbea561f6c2fe1b211f Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 22 Mar 2022 08:23:59 -0600 Subject: [PATCH 52/98] toc --- specs/capella/beacon-chain.md | 7 +++++++ specs/capella/validator.md | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index 6cff9a9c28..443051114f 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -9,17 +9,22 @@ - [Introduction](#introduction) - [Custom types](#custom-types) - [Constants](#constants) + - [Domain types](#domain-types) - [Preset](#preset) - [State list lengths](#state-list-lengths) + - [Max operations per block](#max-operations-per-block) - [Execution](#execution) - [Configuration](#configuration) - [Containers](#containers) - [New containers](#new-containers) - [`Withdrawal`](#withdrawal) + - [`BLSToExecutionChange`](#blstoexecutionchange) + - [`SignedBLSToExecutionChange`](#signedblstoexecutionchange) - [Extended Containers](#extended-containers) - [`ExecutionPayload`](#executionpayload) - [`ExecutionPayloadHeader`](#executionpayloadheader) - [`Validator`](#validator) + - [`BeaconBlockBody`](#beaconblockbody) - [`BeaconState`](#beaconstate) - [Helpers](#helpers) - [Beacon state mutators](#beacon-state-mutators) @@ -32,6 +37,8 @@ - [Block processing](#block-processing) - [New `process_withdrawals`](#new-process_withdrawals) - [Modified `process_execution_payload`](#modified-process_execution_payload) + - [Modified `process_operations`](#modified-process_operations) + - [New `process_bls_to_execution_change`](#new-process_bls_to_execution_change) diff --git a/specs/capella/validator.md b/specs/capella/validator.md index 5e1f5fd0ed..3caeaec9c0 100644 --- a/specs/capella/validator.md +++ b/specs/capella/validator.md @@ -8,6 +8,17 @@ +- [Introduction](#introduction) +- [Prerequisites](#prerequisites) +- [Helpers](#helpers) +- [Protocols](#protocols) + - [`ExecutionEngine`](#executionengine) + - [`get_payload`](#get_payload) +- [Beacon chain responsibilities](#beacon-chain-responsibilities) + - [Block proposal](#block-proposal) + - [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody) + - [ExecutionPayload](#executionpayload) + From 199398c503a37ee9f7d9bb6e4ec7252233d16e4b Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Tue, 22 Mar 2022 20:26:09 +0600 Subject: [PATCH 53/98] Bellatrix: pass justified as a safe block --- specs/bellatrix/fork-choice.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/specs/bellatrix/fork-choice.md b/specs/bellatrix/fork-choice.md index 488f8bac5d..612e840c3b 100644 --- a/specs/bellatrix/fork-choice.md +++ b/specs/bellatrix/fork-choice.md @@ -72,7 +72,23 @@ As per EIP-3675, before a post-transition block is finalized, `notify_forkchoice *Note*: Client software MUST call this function to initiate the payload build process to produce the merge transition block; the `head_block_hash` parameter MUST be set to the hash of a terminal PoW block in this case. -*Note*: Until safe head function is implemented, `safe_block_hash` parameter MUST be stubbed with the `head_block_hash` value. +##### `safe_block_hash` + +The `safe_block_hash` parameter in a call to `notify_forkchoice_updated` function +MUST be set to the return value of the following function: + +```python +def get_safe_block_hash(store: Store) -> Hash32: + # Use most recent justified block as a stopgap + safe_block_root = store.justified_checkpoint.root + safe_block = store.blocks[safe_block_root] + + # Return Hash32() if no payload is yet justified + if compute_epoch_at_slot(safe_block.slot) >= BELLATRIX_FORK_EPOCH: + return safe_block.body.execution_payload.block_hash + else: + return Hash32() +``` ## Helpers From d195e066adb041224e99a8ed6160ea03f9de97f4 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Tue, 22 Mar 2022 20:41:42 +0600 Subject: [PATCH 54/98] Fix toc --- specs/bellatrix/fork-choice.md | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/bellatrix/fork-choice.md b/specs/bellatrix/fork-choice.md index 612e840c3b..b03c0f4e4a 100644 --- a/specs/bellatrix/fork-choice.md +++ b/specs/bellatrix/fork-choice.md @@ -12,6 +12,7 @@ - [Protocols](#protocols) - [`ExecutionEngine`](#executionengine) - [`notify_forkchoice_updated`](#notify_forkchoice_updated) + - [`safe_block_hash`](#safe_block_hash) - [Helpers](#helpers) - [`PayloadAttributes`](#payloadattributes) - [`PowBlock`](#powblock) From 95a232780079d2fe3e026295a4e28c3247ba8fc2 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Tue, 22 Mar 2022 22:51:45 +0600 Subject: [PATCH 55/98] Bellatrix: add get_safe_block_hash to validator.md --- specs/bellatrix/validator.md | 7 ++++--- .../test/bellatrix/unittests/validator/test_validator.py | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/specs/bellatrix/validator.md b/specs/bellatrix/validator.md index dc64d1c8ee..c19b07cafc 100644 --- a/specs/bellatrix/validator.md +++ b/specs/bellatrix/validator.md @@ -110,10 +110,11 @@ All validator responsibilities remain unchanged other than those noted below. Na To obtain an execution payload, a block proposer building a block on top of a `state` must take the following actions: -1. Set `payload_id = prepare_execution_payload(state, pow_chain, finalized_block_hash, suggested_fee_recipient, execution_engine)`, where: +1. Set `payload_id = prepare_execution_payload(state, pow_chain, finalized_block_hash, safe_block_hash, suggested_fee_recipient, execution_engine)`, where: * `state` is the state object after applying `process_slots(state, slot)` transition to the resulting state of the parent block processing * `pow_chain` is a `Dict[Hash32, PowBlock]` dictionary that abstractly represents all blocks in the PoW chain with block hash as the dictionary key * `finalized_block_hash` is the hash of the latest finalized execution payload (`Hash32()` if none yet finalized) + * `safe_block_hash` is the return value of the `get_safe_block_hash(store: Store)` function call * `suggested_fee_recipient` is the value suggested to be used for the `fee_recipient` field of the execution payload @@ -121,6 +122,7 @@ To obtain an execution payload, a block proposer building a block on top of a `s def prepare_execution_payload(state: BeaconState, pow_chain: Dict[Hash32, PowBlock], finalized_block_hash: Hash32, + safe_block_hash: Hash32, suggested_fee_recipient: ExecutionAddress, execution_engine: ExecutionEngine) -> Optional[PayloadId]: if not is_merge_transition_complete(state): @@ -149,8 +151,7 @@ def prepare_execution_payload(state: BeaconState, # Set safe and head block hashes to the same value return execution_engine.notify_forkchoice_updated( head_block_hash=parent_hash, - # TODO: Use `parent_hash` as a stub for now. - safe_block_hash=parent_hash, + safe_block_hash=safe_block_hash, finalized_block_hash=finalized_block_hash, payload_attributes=payload_attributes, ) diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/unittests/validator/test_validator.py b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/validator/test_validator.py index 770c05d949..4474ef8536 100644 --- a/tests/core/pyspec/eth2spec/test/bellatrix/unittests/validator/test_validator.py +++ b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/validator/test_validator.py @@ -143,6 +143,7 @@ def test_prepare_execution_payload(spec, state): # Dummy arguments finalized_block_hash = b'\x56' * 32 + safe_block_hash = b'\x58' * 32 suggested_fee_recipient = b'\x78' * 20 # Mock execution_engine @@ -158,6 +159,7 @@ def notify_forkchoice_updated(self, state=state, pow_chain=pow_chain.to_dict(), finalized_block_hash=finalized_block_hash, + safe_block_hash=safe_block_hash, suggested_fee_recipient=suggested_fee_recipient, execution_engine=TestEngine(), ) From 046eaf2ea014577f5b4c8413c30548eb72f6ea46 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Tue, 22 Mar 2022 22:53:47 +0600 Subject: [PATCH 56/98] Bellatrix: remove a comment about safe head stub --- specs/bellatrix/validator.md | 1 - 1 file changed, 1 deletion(-) diff --git a/specs/bellatrix/validator.md b/specs/bellatrix/validator.md index c19b07cafc..458546e760 100644 --- a/specs/bellatrix/validator.md +++ b/specs/bellatrix/validator.md @@ -148,7 +148,6 @@ def prepare_execution_payload(state: BeaconState, prev_randao=get_randao_mix(state, get_current_epoch(state)), suggested_fee_recipient=suggested_fee_recipient, ) - # Set safe and head block hashes to the same value return execution_engine.notify_forkchoice_updated( head_block_hash=parent_hash, safe_block_hash=safe_block_hash, From 2017b6126514bc0c873630d654e7951e8fb9c347 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 22 Mar 2022 13:37:17 -0600 Subject: [PATCH 57/98] test address_change --- specs/capella/beacon-chain.md | 2 +- .../test_process_bls_to_execution_change.py | 173 ++++++++++++++++-- .../pyspec/eth2spec/test/helpers/genesis.py | 7 +- 3 files changed, 163 insertions(+), 19 deletions(-) diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index 443051114f..a749aec26b 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -421,7 +421,7 @@ def process_bls_to_execution_change(state: BeaconState, validator.withdrawal_credentials = ( ETH1_ADDRESS_WITHDRAWAL_PREFIX - + 0x00 * 11 + + (0x00).to_bytes(11, 'little') + address_change.to_execution_address ) ``` diff --git a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py index 1a7fbfa815..01ed7b8f16 100644 --- a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py +++ b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py @@ -1,10 +1,10 @@ -from eth2spec.test.helpers.execution_payload import ( - build_empty_execution_payload, -) +from eth2spec.utils import bls +from eth2spec.test.helpers.keys import pubkeys, privkeys, pubkey_to_privkey from eth2spec.test.context import spec_state_test, expect_assertion_error, with_capella_and_later -def run_bls_to_execution_change_processing(spec, state, address_change, valid=True): + +def run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=True): """ Run ``process_bls_to_execution_change``, yielding: - pre-state ('pre') @@ -15,33 +15,176 @@ def run_bls_to_execution_change_processing(spec, state, address_change, valid=Tr # yield pre-state yield 'pre', state - yield 'address_change', address_change + yield 'address_change', signed_address_change # If the address_change is invalid, processing is aborted, and there is no post-state. if not valid: - expect_assertion_error(lambda: spec.process_bls_to_execution_change(state, attestation)) + expect_assertion_error(lambda: spec.process_bls_to_execution_change(state, signed_address_change)) yield 'post', None return # process address change - spec.process_bls_to_execution_change(state, attestation) + spec.process_bls_to_execution_change(state, signed_address_change) # Make sure the address change has been processed - assert state.withdrawal_credentials[:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX - assert state.withdrawal_credentials[1:12] == b'\x00' * 11 - assert state.withdrawal_credentials[12:] == address_change.to_execution_address + validator_index = signed_address_change.message.validator_index + validator = state.validators[validator_index] + assert validator.withdrawal_credentials[:1] == spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + assert validator.withdrawal_credentials[1:12] == b'\x00' * 11 + assert validator.withdrawal_credentials[12:] == signed_address_change.message.to_execution_address # yield post-state yield 'post', state +def get_signed_address_change(spec, state, validator_index=None, withdrawal_pubkey=None): + if validator_index is None: + validator_index = 0 + + if withdrawal_pubkey is None: + key_index = -1 - validator_index + withdrawal_pubkey = pubkeys[key_index] + withdrawal_privkey = privkeys[key_index] + else: + withdrawal_privkey = pubkey_to_privkey[withdrawal_pubkey] + + domain = spec.get_domain(state, spec.DOMAIN_BLS_TO_EXECUTION_CHANGE) + address_change = spec.BLSToExecutionChange( + validator_index=validator_index, + from_bls_pubkey=withdrawal_pubkey, + to_execution_address=b'\x42' * 20, + ) + + signing_root = spec.compute_signing_root(address_change, domain) + return spec.SignedBLSToExecutionChange( + message=address_change, + signature=bls.Sign(withdrawal_privkey, signing_root), + ) + + @with_capella_and_later @spec_state_test def test_success(spec, state): - address_change = spec.BLSToExecutionChange( - validator_index=0, - from_bls_pubkey=TEST, - to_execution_address=b'\x42' * 20, + signed_address_change = get_signed_address_change(spec, state) + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change) + + +@with_capella_and_later +@spec_state_test +def test_success_not_activated(spec, state): + validator_index = 3 + validator = state.validators[validator_index] + validator.activation_eligibility_epoch += 4 + validator.activation_epoch = spec.FAR_FUTURE_EPOCH + + assert not spec.is_active_validator(validator, spec.get_current_epoch(state)) + + signed_address_change = get_signed_address_change(spec, state) + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change) + + assert not spec.is_fully_withdrawable_validator(state.validators[validator_index], spec.get_current_epoch(state)) + + +@with_capella_and_later +@spec_state_test +def test_success_in_activation_queue(spec, state): + validator_index = 3 + validator = state.validators[validator_index] + validator.activation_eligibility_epoch = spec.get_current_epoch(state) + validator.activation_epoch += 4 + + assert not spec.is_active_validator(validator, spec.get_current_epoch(state)) + + signed_address_change = get_signed_address_change(spec, state) + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change) + + assert not spec.is_fully_withdrawable_validator(state.validators[validator_index], spec.get_current_epoch(state)) + + +@with_capella_and_later +@spec_state_test +def test_success_in_exit_queue(spec, state): + validator_index = 3 + spec.initiate_validator_exit(state, validator_index) + + assert spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state)) + assert spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch + + signed_address_change = get_signed_address_change(spec, state, validator_index=validator_index) + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change) + + +@with_capella_and_later +@spec_state_test +def test_success_exited(spec, state): + validator_index = 4 + validator = state.validators[validator_index] + validator.exit_epoch = spec.get_current_epoch(state) + + assert not spec.is_active_validator(validator, spec.get_current_epoch(state)) + + signed_address_change = get_signed_address_change(spec, state, validator_index=validator_index) + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change) + + assert not spec.is_fully_withdrawable_validator(state.validators[validator_index], spec.get_current_epoch(state)) + + +@with_capella_and_later +@spec_state_test +def test_success_withdrawable(spec, state): + validator_index = 4 + validator = state.validators[validator_index] + validator.exit_epoch = spec.get_current_epoch(state) + validator.withdrawable_epoch = spec.get_current_epoch(state) + + assert not spec.is_active_validator(validator, spec.get_current_epoch(state)) + + signed_address_change = get_signed_address_change(spec, state, validator_index=validator_index) + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change) + + assert spec.is_fully_withdrawable_validator(state.validators[validator_index], spec.get_current_epoch(state)) + + +@with_capella_and_later +@spec_state_test +def test_fail_val_index_out_of_range(spec, state): + # Create for one validator beyond the validator list length + signed_address_change = get_signed_address_change(spec, state, validator_index=len(state.validators)) + + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_already_0x01(spec, state): + # Create for one validator beyond the validator list length + validator_index = len(state.validators) // 2 + validator = state.validators[validator_index] + validator.withdrawal_credentials = b'\x01' + b'\x00' * 11 + b'\x23' * 20 + signed_address_change = get_signed_address_change(spec, state, validator_index=validator_index) + + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_incorrect_from_bls_pubkey(spec, state): + # Create for one validator beyond the validator list length + validator_index = 2 + signed_address_change = get_signed_address_change( + spec, state, + validator_index=validator_index, + withdrawal_pubkey=pubkeys[0], ) - yield from run_bls_to_execution_change_processing(spec, state, address_change) + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_bad_signature(spec, state): + signed_address_change = get_signed_address_change(spec, state) + # Mutate sigature + signed_address_change.signature = spec.BLSSignature(b'\x42' * 96) + + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=False) diff --git a/tests/core/pyspec/eth2spec/test/helpers/genesis.py b/tests/core/pyspec/eth2spec/test/helpers/genesis.py index 1ca408598e..83994c4096 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/genesis.py +++ b/tests/core/pyspec/eth2spec/test/helpers/genesis.py @@ -6,11 +6,12 @@ def build_mock_validator(spec, i: int, balance: int): - pubkey = pubkeys[i] + active_pubkey = pubkeys[i] + withdrawal_pubkey = pubkeys[-1 - i] # insecurely use pubkey as withdrawal key as well - withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:] + withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(withdrawal_pubkey)[1:] validator = spec.Validator( - pubkey=pubkeys[i], + pubkey=active_pubkey, withdrawal_credentials=withdrawal_credentials, activation_eligibility_epoch=spec.FAR_FUTURE_EPOCH, activation_epoch=spec.FAR_FUTURE_EPOCH, From 4c89fdb9b9841dfef87ef17cfadc11a8e8ce8361 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Tue, 22 Mar 2022 15:05:56 -0500 Subject: [PATCH 58/98] Fix indententation for one line in the Makefile Real minor fix. Rule lines should start with a single tab, not spaces. Vim clearly pointed this out by marking the entire line as red. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7de4cec2a0..0016808144 100644 --- a/Makefile +++ b/Makefile @@ -68,7 +68,7 @@ partial_clean: clean: partial_clean rm -rf venv - # legacy cleanup. The pyspec venv should be located at the repository root + # legacy cleanup. The pyspec venv should be located at the repository root rm -rf $(PY_SPEC_DIR)/venv rm -rf $(DEPOSIT_CONTRACT_COMPILER_DIR)/venv rm -rf $(DEPOSIT_CONTRACT_TESTER_DIR)/venv From 8a388f2226404d0d4de22bb95583dc8142c4ea75 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 23 Mar 2022 15:38:17 +0800 Subject: [PATCH 59/98] toc --- specs/capella/validator.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/specs/capella/validator.md b/specs/capella/validator.md index 5e1f5fd0ed..3caeaec9c0 100644 --- a/specs/capella/validator.md +++ b/specs/capella/validator.md @@ -8,6 +8,17 @@ +- [Introduction](#introduction) +- [Prerequisites](#prerequisites) +- [Helpers](#helpers) +- [Protocols](#protocols) + - [`ExecutionEngine`](#executionengine) + - [`get_payload`](#get_payload) +- [Beacon chain responsibilities](#beacon-chain-responsibilities) + - [Block proposal](#block-proposal) + - [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody) + - [ExecutionPayload](#executionpayload) + From 4ac4158b42162acd5b17a50fd10f63f3e263958f Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 23 Mar 2022 10:54:40 -0600 Subject: [PATCH 60/98] move bls chang operation to end of block body --- specs/capella/beacon-chain.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index a749aec26b..ff93bec9c8 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -197,10 +197,11 @@ class BeaconBlockBody(Container): attestations: List[Attestation, MAX_ATTESTATIONS] deposits: List[Deposit, MAX_DEPOSITS] voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] - bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES] sync_aggregate: SyncAggregate # Execution execution_payload: ExecutionPayload + # Capella operations + bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES] # [New in Capella] ``` #### `BeaconState` @@ -398,7 +399,7 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: for_ops(body.attestations, process_attestation) for_ops(body.deposits, process_deposit) for_ops(body.voluntary_exits, process_voluntary_exit) - for_ops(body.bls_to_execution_changes, process_bls_to_execution_change) + for_ops(body.bls_to_execution_changes, process_bls_to_execution_change) # [New in Capella] ``` #### New `process_bls_to_execution_change` From 6e369e4a5325a8e554857d9032835a30a0ceee25 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 23 Mar 2022 10:55:27 -0600 Subject: [PATCH 61/98] Apply suggestions from code review Co-authored-by: Hsiao-Wei Wang --- .../block_processing/test_process_bls_to_execution_change.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py index 01ed7b8f16..416544c551 100644 --- a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py +++ b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py @@ -182,6 +182,7 @@ def test_fail_incorrect_from_bls_pubkey(spec, state): @with_capella_and_later @spec_state_test +@always_bls def test_fail_bad_signature(spec, state): signed_address_change = get_signed_address_change(spec, state) # Mutate sigature From bc95973232666c84d1bff828cdd3c107b91e2cbe Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Thu, 24 Mar 2022 16:35:28 +0600 Subject: [PATCH 62/98] Reorder params Co-authored-by: Hsiao-Wei Wang --- specs/bellatrix/validator.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/bellatrix/validator.md b/specs/bellatrix/validator.md index 458546e760..ee19f3d073 100644 --- a/specs/bellatrix/validator.md +++ b/specs/bellatrix/validator.md @@ -113,16 +113,16 @@ To obtain an execution payload, a block proposer building a block on top of a `s 1. Set `payload_id = prepare_execution_payload(state, pow_chain, finalized_block_hash, safe_block_hash, suggested_fee_recipient, execution_engine)`, where: * `state` is the state object after applying `process_slots(state, slot)` transition to the resulting state of the parent block processing * `pow_chain` is a `Dict[Hash32, PowBlock]` dictionary that abstractly represents all blocks in the PoW chain with block hash as the dictionary key - * `finalized_block_hash` is the hash of the latest finalized execution payload (`Hash32()` if none yet finalized) * `safe_block_hash` is the return value of the `get_safe_block_hash(store: Store)` function call + * `finalized_block_hash` is the hash of the latest finalized execution payload (`Hash32()` if none yet finalized) * `suggested_fee_recipient` is the value suggested to be used for the `fee_recipient` field of the execution payload ```python def prepare_execution_payload(state: BeaconState, pow_chain: Dict[Hash32, PowBlock], - finalized_block_hash: Hash32, safe_block_hash: Hash32, + finalized_block_hash: Hash32, suggested_fee_recipient: ExecutionAddress, execution_engine: ExecutionEngine) -> Optional[PayloadId]: if not is_merge_transition_complete(state): From c97cc6f4dc8c3a25c7727b5aba859df00e6c4c9c Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Thu, 24 Mar 2022 17:22:11 +0600 Subject: [PATCH 63/98] Add separate get_safe_beacon_block function --- fork_choice/safe-block.md | 31 +++++++++++++++++++++++++++++++ specs/bellatrix/fork-choice.md | 3 +-- 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 fork_choice/safe-block.md diff --git a/fork_choice/safe-block.md b/fork_choice/safe-block.md new file mode 100644 index 0000000000..158a2b0691 --- /dev/null +++ b/fork_choice/safe-block.md @@ -0,0 +1,31 @@ +# Fork Choice -- Safe Block + +## Table of contents + + + + +- [Introduction](#introduction) +- [`get_safe_beacon_block`](#get_safe_beacon_block) + + + + +## Introduction + +Under honest majority and certain network synchronicity assumptions +there exist a block that is safe from re-orgs. Normally this block is +pretty close to the head of canonical chain which makes it valuable +to expose a safe block to users. + +This section describes an algorithm to find a safe block. + +## `get_safe_beacon_block` + +```python +def get_safe_beacon_block(store: Store) -> Root: + # Use most recent justified block as a stopgap + return store.justified_checkpoint.root +``` +*Note*: Currently safe block algorithm simply returns `store.justified_checkpoint.root` +and is meant to be improved in the future. diff --git a/specs/bellatrix/fork-choice.md b/specs/bellatrix/fork-choice.md index b03c0f4e4a..05bde7e659 100644 --- a/specs/bellatrix/fork-choice.md +++ b/specs/bellatrix/fork-choice.md @@ -80,8 +80,7 @@ MUST be set to the return value of the following function: ```python def get_safe_block_hash(store: Store) -> Hash32: - # Use most recent justified block as a stopgap - safe_block_root = store.justified_checkpoint.root + safe_block_root = get_safe_beacon_block(store) safe_block = store.blocks[safe_block_root] # Return Hash32() if no payload is yet justified From ee5f29cb75eb81413b62ef3774d4c231cc6f47db Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 24 Mar 2022 09:57:43 -0600 Subject: [PATCH 64/98] fix ci --- .../block_processing/test_process_bls_to_execution_change.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py index 416544c551..4b69e04a63 100644 --- a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py +++ b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py @@ -1,7 +1,7 @@ from eth2spec.utils import bls from eth2spec.test.helpers.keys import pubkeys, privkeys, pubkey_to_privkey -from eth2spec.test.context import spec_state_test, expect_assertion_error, with_capella_and_later +from eth2spec.test.context import spec_state_test, expect_assertion_error, with_capella_and_later, always_bls def run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=True): From bd66114f4ac464c52abeaefb3e5375156e5f55ad Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Sat, 26 Mar 2022 02:02:38 +0600 Subject: [PATCH 65/98] Clarify names and move get_safe_block_hash to safe-block.md --- fork_choice/safe-block.md | 21 ++++++++++++++++++--- specs/bellatrix/fork-choice.md | 15 +-------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/fork_choice/safe-block.md b/fork_choice/safe-block.md index 158a2b0691..127002b156 100644 --- a/fork_choice/safe-block.md +++ b/fork_choice/safe-block.md @@ -6,7 +6,8 @@ - [Introduction](#introduction) -- [`get_safe_beacon_block`](#get_safe_beacon_block) +- [`get_safe_beacon_block_root`](#get_safe_beacon_block_root) +- [`get_safe_execution_payload_hash`](#get_safe_execution_payload_hash) @@ -20,12 +21,26 @@ to expose a safe block to users. This section describes an algorithm to find a safe block. -## `get_safe_beacon_block` +## `get_safe_beacon_block_root` ```python -def get_safe_beacon_block(store: Store) -> Root: +def get_safe_beacon_block_root(store: Store) -> Root: # Use most recent justified block as a stopgap return store.justified_checkpoint.root ``` *Note*: Currently safe block algorithm simply returns `store.justified_checkpoint.root` and is meant to be improved in the future. + +## `get_safe_execution_payload_hash` + +```python +def get_safe_execution_payload_hash(store: Store) -> Hash32: + safe_block_root = get_safe_beacon_block_root(store) + safe_block = store.blocks[safe_block_root] + + # Return Hash32() if no payload is yet justified + if compute_epoch_at_slot(safe_block.slot) >= BELLATRIX_FORK_EPOCH: + return safe_block.body.execution_payload.block_hash + else: + return Hash32() +``` diff --git a/specs/bellatrix/fork-choice.md b/specs/bellatrix/fork-choice.md index 05bde7e659..305d58a57d 100644 --- a/specs/bellatrix/fork-choice.md +++ b/specs/bellatrix/fork-choice.md @@ -75,20 +75,7 @@ As per EIP-3675, before a post-transition block is finalized, `notify_forkchoice ##### `safe_block_hash` -The `safe_block_hash` parameter in a call to `notify_forkchoice_updated` function -MUST be set to the return value of the following function: - -```python -def get_safe_block_hash(store: Store) -> Hash32: - safe_block_root = get_safe_beacon_block(store) - safe_block = store.blocks[safe_block_root] - - # Return Hash32() if no payload is yet justified - if compute_epoch_at_slot(safe_block.slot) >= BELLATRIX_FORK_EPOCH: - return safe_block.body.execution_payload.block_hash - else: - return Hash32() -``` +The `safe_block_hash` parameter MUST be set to return value of `get_safe_execution_payload_hash(store: Store)` function. ## Helpers From eb26a1d74c8f44b3faa399f6cd8d71903ab4b4e1 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Sat, 26 Mar 2022 08:26:52 -0700 Subject: [PATCH 66/98] EIP4844: fix a minor typo --- specs/eip4844/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/eip4844/beacon-chain.md b/specs/eip4844/beacon-chain.md index d0de754457..4caa91ead0 100644 --- a/specs/eip4844/beacon-chain.md +++ b/specs/eip4844/beacon-chain.md @@ -149,7 +149,7 @@ def tx_peek_blob_versioned_hashes(opaque_tx: Transaction) -> Sequence[VersionedH def verify_kzgs_against_transactions(transactions: Sequence[Transaction], blob_kzgs: Sequence[KZGCommitment]) -> bool: all_versioned_hashes = [] for tx in transactions: - if opaque_tx[0] == BLOB_TX_TYPE: + if tx[0] == BLOB_TX_TYPE: all_versioned_hashes.extend(tx_peek_blob_versioned_hashes(tx)) return all_versioned_hashes == [ksg_to_version_hash(kzg) for kzg in blob_kzgs] ``` From b13a9f0f8af77cedb487ba3a15ccb89b02d2b1b2 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Wed, 30 Mar 2022 15:36:01 +0600 Subject: [PATCH 67/98] Apply suggestions as per review --- fork_choice/safe-block.md | 2 ++ specs/bellatrix/fork-choice.md | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/fork_choice/safe-block.md b/fork_choice/safe-block.md index 127002b156..490d245381 100644 --- a/fork_choice/safe-block.md +++ b/fork_choice/safe-block.md @@ -44,3 +44,5 @@ def get_safe_execution_payload_hash(store: Store) -> Hash32: else: return Hash32() ``` + +*Note*: This helper uses beacon block container extended in [Bellatrix](../specs/bellatrix/beacon-chain.md). diff --git a/specs/bellatrix/fork-choice.md b/specs/bellatrix/fork-choice.md index 305d58a57d..60a54da9c1 100644 --- a/specs/bellatrix/fork-choice.md +++ b/specs/bellatrix/fork-choice.md @@ -75,7 +75,8 @@ As per EIP-3675, before a post-transition block is finalized, `notify_forkchoice ##### `safe_block_hash` -The `safe_block_hash` parameter MUST be set to return value of `get_safe_execution_payload_hash(store: Store)` function. +The `safe_block_hash` parameter MUST be set to return value of +[`get_safe_execution_payload_hash(store: Store)`](../../fork_choice/safe-block.md#get_safe_execution_payload_hash) function. ## Helpers From c87fc480531655bca0369dff222ca7624cef46c2 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 30 Mar 2022 12:04:23 -0600 Subject: [PATCH 68/98] minor typo --- specs/bellatrix/validator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/bellatrix/validator.md b/specs/bellatrix/validator.md index ee19f3d073..94671db08f 100644 --- a/specs/bellatrix/validator.md +++ b/specs/bellatrix/validator.md @@ -113,7 +113,7 @@ To obtain an execution payload, a block proposer building a block on top of a `s 1. Set `payload_id = prepare_execution_payload(state, pow_chain, finalized_block_hash, safe_block_hash, suggested_fee_recipient, execution_engine)`, where: * `state` is the state object after applying `process_slots(state, slot)` transition to the resulting state of the parent block processing * `pow_chain` is a `Dict[Hash32, PowBlock]` dictionary that abstractly represents all blocks in the PoW chain with block hash as the dictionary key - * `safe_block_hash` is the return value of the `get_safe_block_hash(store: Store)` function call + * `safe_block_hash` is the return value of the `get_safe_execution_payload_hash(store: Store)` function call * `finalized_block_hash` is the hash of the latest finalized execution payload (`Hash32()` if none yet finalized) * `suggested_fee_recipient` is the value suggested to be used for the `fee_recipient` field of the execution payload From 6576cab61289ebb78e2420c8c1047ab3489a0380 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 6 Apr 2022 18:13:39 +0800 Subject: [PATCH 69/98] Fix Capella `prepare_execution_payload` --- specs/capella/validator.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/specs/capella/validator.md b/specs/capella/validator.md index 3caeaec9c0..8c6c860a38 100644 --- a/specs/capella/validator.md +++ b/specs/capella/validator.md @@ -71,6 +71,7 @@ def get_expected_withdrawals(state: BeaconState) -> Sequence[Withdrawal]: ```python def prepare_execution_payload(state: BeaconState, pow_chain: Dict[Hash32, PowBlock], + safe_block_hash: Hash32, finalized_block_hash: Hash32, suggested_fee_recipient: ExecutionAddress, execution_engine: ExecutionEngine) -> Optional[PayloadId]: @@ -98,11 +99,9 @@ def prepare_execution_payload(state: BeaconState, suggested_fee_recipient=suggested_fee_recipient, withdrawals=get_expected_withdrawals(state), # [New in Capella] ) - # Set safe and head block hashes to the same value return execution_engine.notify_forkchoice_updated( head_block_hash=parent_hash, - # TODO: Use `parent_hash` as a stub for now. - safe_block_hash=parent_hash, + safe_block_hash=safe_block_hash, finalized_block_hash=finalized_block_hash, payload_attributes=payload_attributes, ) From 5978c86f3a6454ffa8d91326fe8c3f758d1ff4a1 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sun, 10 Apr 2022 18:41:49 +0800 Subject: [PATCH 70/98] Typo fix: `ksg_to_version_hash` -> `kzg_to_versioned_hash` --- specs/eip4844/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/eip4844/beacon-chain.md b/specs/eip4844/beacon-chain.md index 4caa91ead0..54ff3f79cd 100644 --- a/specs/eip4844/beacon-chain.md +++ b/specs/eip4844/beacon-chain.md @@ -151,7 +151,7 @@ def verify_kzgs_against_transactions(transactions: Sequence[Transaction], blob_k for tx in transactions: if tx[0] == BLOB_TX_TYPE: all_versioned_hashes.extend(tx_peek_blob_versioned_hashes(tx)) - return all_versioned_hashes == [ksg_to_version_hash(kzg) for kzg in blob_kzgs] + return all_versioned_hashes == [kzg_to_versioned_hash(kzg) for kzg in blob_kzgs] ``` ## Beacon chain state transition function From eca7237b275bf492003c5eb1f7019aa7ee11e1f3 Mon Sep 17 00:00:00 2001 From: Ben Edgington Date: Wed, 13 Apr 2022 15:04:55 +0100 Subject: [PATCH 71/98] Correct typo: BLSCommitment -> KZGCommitment The consensus chain counterpart to https://github.com/ethereum/EIPs/pull/4992. The same comment from there applies here as well. --- specs/eip4844/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/eip4844/beacon-chain.md b/specs/eip4844/beacon-chain.md index 4caa91ead0..bbe023bdc9 100644 --- a/specs/eip4844/beacon-chain.md +++ b/specs/eip4844/beacon-chain.md @@ -70,7 +70,7 @@ but reusing the `mainnet` settings in public networks is a critical security req | Name | Value | | - | - | | `KZG_SETUP_G2` | `Vector[G2Point, FIELD_ELEMENTS_PER_BLOB]`, contents TBD | -| `KZG_SETUP_LAGRANGE` | `Vector[BLSCommitment, FIELD_ELEMENTS_PER_BLOB]`, contents TBD | +| `KZG_SETUP_LAGRANGE` | `Vector[KZGCommitment, FIELD_ELEMENTS_PER_BLOB]`, contents TBD | ## Configuration From f1255381221028a88a03f76ec09b15ec354fd735 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Mon, 25 Apr 2022 16:49:00 +0200 Subject: [PATCH 72/98] Fix param order in `prepare_execution_payload` Documentation on how to call `prepare_execution_payload` had the params for `safe_block_hash` and `finalized_block` hash flipped compared to the function definition. Also updated tests for consistency. --- specs/bellatrix/validator.md | 2 +- .../test/bellatrix/unittests/validator/test_validator.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/bellatrix/validator.md b/specs/bellatrix/validator.md index 94671db08f..c88aa9babe 100644 --- a/specs/bellatrix/validator.md +++ b/specs/bellatrix/validator.md @@ -110,7 +110,7 @@ All validator responsibilities remain unchanged other than those noted below. Na To obtain an execution payload, a block proposer building a block on top of a `state` must take the following actions: -1. Set `payload_id = prepare_execution_payload(state, pow_chain, finalized_block_hash, safe_block_hash, suggested_fee_recipient, execution_engine)`, where: +1. Set `payload_id = prepare_execution_payload(state, pow_chain, safe_block_hash, finalized_block_hash, suggested_fee_recipient, execution_engine)`, where: * `state` is the state object after applying `process_slots(state, slot)` transition to the resulting state of the parent block processing * `pow_chain` is a `Dict[Hash32, PowBlock]` dictionary that abstractly represents all blocks in the PoW chain with block hash as the dictionary key * `safe_block_hash` is the return value of the `get_safe_execution_payload_hash(store: Store)` function call diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/unittests/validator/test_validator.py b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/validator/test_validator.py index 4474ef8536..6398aedd84 100644 --- a/tests/core/pyspec/eth2spec/test/bellatrix/unittests/validator/test_validator.py +++ b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/validator/test_validator.py @@ -158,8 +158,8 @@ def notify_forkchoice_updated(self, payload_id = spec.prepare_execution_payload( state=state, pow_chain=pow_chain.to_dict(), - finalized_block_hash=finalized_block_hash, safe_block_hash=safe_block_hash, + finalized_block_hash=finalized_block_hash, suggested_fee_recipient=suggested_fee_recipient, execution_engine=TestEngine(), ) From b9cb2946507ceeffba6a75193cbaa84305472d40 Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Mon, 25 Apr 2022 23:23:44 +0300 Subject: [PATCH 73/98] Yield attestation in equivocating indices test --- .../pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py index 587a1c6e8b..e8c2dc2682 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py @@ -20,7 +20,7 @@ get_genesis_forkchoice_store_and_block, get_formatted_head_output, on_tick_and_append_step, - run_on_attestation, + add_attestation, tick_and_run_on_attestation, tick_and_add_block, ) @@ -409,7 +409,7 @@ def test_discard_equivocations(spec, state): # Process attestation # The head should change to block_1 - run_on_attestation(spec, store, attestation) + yield from add_attestation(spec, store, attestation, test_steps) assert spec.get_head(store) == spec.hash_tree_root(block_1) # Process attester_slashing From 706f417ba03f0d16e4de85737299163a96ecd53c Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Thu, 28 Apr 2022 13:17:19 +0400 Subject: [PATCH 74/98] Remove justified from optimistic import conditions --- sync/optimistic.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 3237c3f654..6d99c592e7 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -84,10 +84,6 @@ def is_optimistic_candidate_block(opt_store: OptimisticStore, current_slot: Slot if is_execution_block(opt_store.blocks[block.parent_root]): return True - justified_root = opt_store.block_states[opt_store.head_block_root].current_justified_checkpoint.root - if is_execution_block(opt_store.blocks[justified_root]): - return True - if block.slot + SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY <= current_slot: return True @@ -111,7 +107,6 @@ This ensures that blocks are only optimistically imported if one or more of the following are true: 1. The parent of the block has execution enabled. -1. The justified checkpoint has execution enabled. 1. The current slot (as per the system clock) is at least `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` ahead of the slot of the block being imported. From 3f76792ee363643a6a940a9805cc632c7678052d Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Thu, 5 May 2022 11:21:55 +1000 Subject: [PATCH 75/98] Require fork choice to run before proposal --- specs/phase0/validator.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index acffccebc8..4f79c2d9d4 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -276,9 +276,15 @@ A validator has two primary responsibilities to the beacon chain: [proposing blo ### Block proposal A validator is expected to propose a [`SignedBeaconBlock`](./beacon-chain.md#signedbeaconblock) at -the beginning of any slot during which `is_proposer(state, validator_index)` returns `True`. -To propose, the validator selects the `BeaconBlock`, `parent`, -that in their view of the fork choice is the head of the chain during `slot - 1`. +the beginning of any `slot` during which `is_proposer(state, validator_index)` returns `True`. + +To propose, the validator selects the `BeaconBlock`, `parent` which: + +1. In their view of fork choice is the head of the chain at the start of + `slot`, after applying any queued attestations from `slot - 1`. +2. Is from a slot strictly less than the slot of the block about to be proposed, + i.e. `parent.slot < slot`. + The validator creates, signs, and broadcasts a `block` that is a child of `parent` that satisfies a valid [beacon chain state transition](./beacon-chain.md#beacon-chain-state-transition-function). From 3c7544af3d90dacd22166e6f0255c8710adc2a35 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Fri, 6 May 2022 13:56:40 -0500 Subject: [PATCH 76/98] Add invalid large withdrawable epoch test --- .../test_process_registry_updates.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py index 6539dc92d4..2aff866c2e 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py @@ -1,3 +1,5 @@ +import pytest + from eth2spec.test.helpers.deposits import mock_deposit from eth2spec.test.helpers.state import next_epoch, next_slots from eth2spec.test.helpers.constants import MINIMAL @@ -342,3 +344,27 @@ def test_activation_queue_activation_and_ejection__exceed_scaled_churn_limit(spe churn_limit = spec.get_validator_churn_limit(state) assert churn_limit > spec.config.MIN_PER_EPOCH_CHURN_LIMIT yield from run_test_activation_queue_activation_and_ejection(spec, state, churn_limit * 2) + + +@with_all_phases +@spec_state_test +def test_invalid_large_withdrawable_epoch(spec, state): + """ + This test forces a validator into a withdrawable epoch that overflows the + epoch (uint64) type. To do this we need two validators, one validator that + already has an exit epoch and another with a low effective balance. When + calculating the withdrawable epoch for the second validator, it will + use the greatest exit epoch of all of the validators. If the first + validator is given an exit epoch between + (FAR_FUTURE_EPOCH-MIN_VALIDATOR_WITHDRAWABILITY_DELAY+1) and + (FAR_FUTURE_EPOCH-1), it will cause an overflow. + """ + assert spec.is_active_validator(state.validators[0], spec.get_current_epoch(state)) + assert spec.is_active_validator(state.validators[1], spec.get_current_epoch(state)) + + state.validators[0].exit_epoch = spec.FAR_FUTURE_EPOCH - 1 + state.validators[1].effective_balance = spec.config.EJECTION_BALANCE + + with pytest.raises(ValueError): + yield from run_process_registry_updates(spec, state) + yield 'post', None From f5303ca58ec1bb048c4c05a2dcc9dee7340a9792 Mon Sep 17 00:00:00 2001 From: Caspar Schwarz-Schilling Date: Mon, 9 May 2022 10:17:24 +0200 Subject: [PATCH 77/98] Update PROPOSER_SCORE_BOOST to 33 percent --- configs/mainnet.yaml | 4 +-- configs/minimal.yaml | 4 +-- specs/phase0/fork-choice.md | 72 +++++++++++++++++++------------------ 3 files changed, 41 insertions(+), 39 deletions(-) diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 5b2046654d..be07de5d56 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -84,8 +84,8 @@ CHURN_LIMIT_QUOTIENT: 65536 # Fork choice # --------------------------------------------------------------- -# 70% -PROPOSER_SCORE_BOOST: 70 +# 33% +PROPOSER_SCORE_BOOST: 33 # Deposit contract # --------------------------------------------------------------- diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 0910a1430d..e84d2bdf59 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -81,8 +81,8 @@ CHURN_LIMIT_QUOTIENT: 32 # Fork choice # --------------------------------------------------------------- -# 70% -PROPOSER_SCORE_BOOST: 70 +# 33% +PROPOSER_SCORE_BOOST: 33 # Deposit contract diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 9b302b5fd0..0f6dd1931a 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -5,34 +5,36 @@ -- [Introduction](#introduction) -- [Fork choice](#fork-choice) - - [Constant](#constant) - - [Preset](#preset) - - [Configuration](#configuration) - - [Helpers](#helpers) - - [`LatestMessage`](#latestmessage) - - [`Store`](#store) - - [`get_forkchoice_store`](#get_forkchoice_store) - - [`get_slots_since_genesis`](#get_slots_since_genesis) - - [`get_current_slot`](#get_current_slot) - - [`compute_slots_since_epoch_start`](#compute_slots_since_epoch_start) - - [`get_ancestor`](#get_ancestor) - - [`get_latest_attesting_balance`](#get_latest_attesting_balance) - - [`filter_block_tree`](#filter_block_tree) - - [`get_filtered_block_tree`](#get_filtered_block_tree) - - [`get_head`](#get_head) - - [`should_update_justified_checkpoint`](#should_update_justified_checkpoint) - - [`on_attestation` helpers](#on_attestation-helpers) - - [`validate_target_epoch_against_current_time`](#validate_target_epoch_against_current_time) - - [`validate_on_attestation`](#validate_on_attestation) - - [`store_target_checkpoint_state`](#store_target_checkpoint_state) - - [`update_latest_messages`](#update_latest_messages) - - [Handlers](#handlers) - - [`on_tick`](#on_tick) - - [`on_block`](#on_block) - - [`on_attestation`](#on_attestation) - - [`on_attester_slashing`](#on_attester_slashing) +- [Phase 0 -- Beacon Chain Fork Choice](#phase-0----beacon-chain-fork-choice) + - [Table of contents](#table-of-contents) + - [Introduction](#introduction) + - [Fork choice](#fork-choice) + - [Constant](#constant) + - [Preset](#preset) + - [Configuration](#configuration) + - [Helpers](#helpers) + - [`LatestMessage`](#latestmessage) + - [`Store`](#store) + - [`get_forkchoice_store`](#get_forkchoice_store) + - [`get_slots_since_genesis`](#get_slots_since_genesis) + - [`get_current_slot`](#get_current_slot) + - [`compute_slots_since_epoch_start`](#compute_slots_since_epoch_start) + - [`get_ancestor`](#get_ancestor) + - [`get_latest_attesting_balance`](#get_latest_attesting_balance) + - [`filter_block_tree`](#filter_block_tree) + - [`get_filtered_block_tree`](#get_filtered_block_tree) + - [`get_head`](#get_head) + - [`should_update_justified_checkpoint`](#should_update_justified_checkpoint) + - [`on_attestation` helpers](#on_attestation-helpers) + - [`validate_target_epoch_against_current_time`](#validate_target_epoch_against_current_time) + - [`validate_on_attestation`](#validate_on_attestation) + - [`store_target_checkpoint_state`](#store_target_checkpoint_state) + - [`update_latest_messages`](#update_latest_messages) + - [Handlers](#handlers) + - [`on_tick`](#on_tick) + - [`on_block`](#on_block) + - [`on_attestation`](#on_attestation) + - [`on_attester_slashing`](#on_attester_slashing) @@ -62,21 +64,21 @@ Any of the above handlers that trigger an unhandled exception (e.g. a failed ass ### Constant -| Name | Value | -| - | - | +| Name | Value | +| -------------------- | ----------- | | `INTERVALS_PER_SLOT` | `uint64(3)` | ### Preset -| Name | Value | Unit | Duration | -| - | - | :-: | :-: | +| Name | Value | Unit | Duration | +| -------------------------------- | ------------ | :---: | :--------: | | `SAFE_SLOTS_TO_UPDATE_JUSTIFIED` | `2**3` (= 8) | slots | 96 seconds | ### Configuration -| Name | Value | -| - | - | -| `PROPOSER_SCORE_BOOST` | `uint64(70)` | +| Name | Value | +| ---------------------- | ------------ | +| `PROPOSER_SCORE_BOOST` | `uint64(33)` | - The proposer score boost is worth `PROPOSER_SCORE_BOOST` percentage of the committee's weight, i.e., for slot with committee weight `committee_weight` the boost weight is equal to `(committee_weight * PROPOSER_SCORE_BOOST) // 100`. From 712a4f9cf1cc17f5309604675baa07ad0eb6f730 Mon Sep 17 00:00:00 2001 From: Caspar Schwarz-Schilling Date: Mon, 9 May 2022 15:05:05 +0200 Subject: [PATCH 78/98] Fix linting --- specs/phase0/fork-choice.md | 58 ++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 0f6dd1931a..2c33768e51 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -5,36 +5,34 @@ -- [Phase 0 -- Beacon Chain Fork Choice](#phase-0----beacon-chain-fork-choice) - - [Table of contents](#table-of-contents) - - [Introduction](#introduction) - - [Fork choice](#fork-choice) - - [Constant](#constant) - - [Preset](#preset) - - [Configuration](#configuration) - - [Helpers](#helpers) - - [`LatestMessage`](#latestmessage) - - [`Store`](#store) - - [`get_forkchoice_store`](#get_forkchoice_store) - - [`get_slots_since_genesis`](#get_slots_since_genesis) - - [`get_current_slot`](#get_current_slot) - - [`compute_slots_since_epoch_start`](#compute_slots_since_epoch_start) - - [`get_ancestor`](#get_ancestor) - - [`get_latest_attesting_balance`](#get_latest_attesting_balance) - - [`filter_block_tree`](#filter_block_tree) - - [`get_filtered_block_tree`](#get_filtered_block_tree) - - [`get_head`](#get_head) - - [`should_update_justified_checkpoint`](#should_update_justified_checkpoint) - - [`on_attestation` helpers](#on_attestation-helpers) - - [`validate_target_epoch_against_current_time`](#validate_target_epoch_against_current_time) - - [`validate_on_attestation`](#validate_on_attestation) - - [`store_target_checkpoint_state`](#store_target_checkpoint_state) - - [`update_latest_messages`](#update_latest_messages) - - [Handlers](#handlers) - - [`on_tick`](#on_tick) - - [`on_block`](#on_block) - - [`on_attestation`](#on_attestation) - - [`on_attester_slashing`](#on_attester_slashing) +- [Introduction](#introduction) +- [Fork choice](#fork-choice) + - [Constant](#constant) + - [Preset](#preset) + - [Configuration](#configuration) + - [Helpers](#helpers) + - [`LatestMessage`](#latestmessage) + - [`Store`](#store) + - [`get_forkchoice_store`](#get_forkchoice_store) + - [`get_slots_since_genesis`](#get_slots_since_genesis) + - [`get_current_slot`](#get_current_slot) + - [`compute_slots_since_epoch_start`](#compute_slots_since_epoch_start) + - [`get_ancestor`](#get_ancestor) + - [`get_latest_attesting_balance`](#get_latest_attesting_balance) + - [`filter_block_tree`](#filter_block_tree) + - [`get_filtered_block_tree`](#get_filtered_block_tree) + - [`get_head`](#get_head) + - [`should_update_justified_checkpoint`](#should_update_justified_checkpoint) + - [`on_attestation` helpers](#on_attestation-helpers) + - [`validate_target_epoch_against_current_time`](#validate_target_epoch_against_current_time) + - [`validate_on_attestation`](#validate_on_attestation) + - [`store_target_checkpoint_state`](#store_target_checkpoint_state) + - [`update_latest_messages`](#update_latest_messages) + - [Handlers](#handlers) + - [`on_tick`](#on_tick) + - [`on_block`](#on_block) + - [`on_attestation`](#on_attestation) + - [`on_attester_slashing`](#on_attester_slashing) From a7bda480fe9c747349c92248385d0a2a809b0b66 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Mon, 9 May 2022 14:09:27 -0500 Subject: [PATCH 79/98] Convert sets to sorted lists prior to sampling This is because sampling of sets has been deprecated in Python 3.9. I used sorted() instead of list() so that things are deterministic. --- .../test/altair/transition/test_transition.py | 4 ++-- .../altair/unittests/validator/test_validator.py | 2 +- .../test_process_rewards_and_penalties.py | 12 ++++++------ .../test/phase0/fork_choice/test_on_block.py | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py index 2ac8bfb485..32861b866f 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py @@ -281,7 +281,7 @@ def _drop_random_quarter(_slot, _index, indices): assert committee_len >= 4 filter_len = committee_len // 4 participant_count = committee_len - filter_len - return rng.sample(indices, participant_count) + return rng.sample(sorted(indices), participant_count) yield from _run_transition_test_with_attestations( state, @@ -304,7 +304,7 @@ def _drop_random_half(_slot, _index, indices): assert committee_len >= 2 filter_len = committee_len // 2 participant_count = committee_len - filter_len - return rng.sample(indices, participant_count) + return rng.sample(sorted(indices), participant_count) yield from _run_transition_test_with_attestations( state, diff --git a/tests/core/pyspec/eth2spec/test/altair/unittests/validator/test_validator.py b/tests/core/pyspec/eth2spec/test/altair/unittests/validator/test_validator.py index dd9214040e..1efeca5471 100644 --- a/tests/core/pyspec/eth2spec/test/altair/unittests/validator/test_validator.py +++ b/tests/core/pyspec/eth2spec/test/altair/unittests/validator/test_validator.py @@ -59,7 +59,7 @@ def test_is_assigned_to_sync_committee(spec, state): if disqualified_pubkeys: sample_size = 3 assert validator_count >= sample_size - some_pubkeys = rng.sample(disqualified_pubkeys, sample_size) + some_pubkeys = rng.sample(sorted(disqualified_pubkeys), sample_size) for pubkey in some_pubkeys: validator_index = active_pubkeys.index(pubkey) is_current = spec.is_assigned_to_sync_committee( 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 7ff4e83d3b..10772ca824 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 @@ -200,7 +200,7 @@ def participation_tracker(slot, comm_index, comm): @spec_state_test def test_almost_empty_attestations(spec, state): rng = Random(1234) - yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, 1)) + yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(sorted(comm), 1)) @with_all_phases @@ -208,14 +208,14 @@ def test_almost_empty_attestations(spec, state): @leaking() def test_almost_empty_attestations_with_leak(spec, state): rng = Random(1234) - yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, 1)) + yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(sorted(comm), 1)) @with_all_phases @spec_state_test def test_random_fill_attestations(spec, state): rng = Random(4567) - yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, len(comm) // 3)) + yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(sorted(comm), len(comm) // 3)) @with_all_phases @@ -223,14 +223,14 @@ def test_random_fill_attestations(spec, state): @leaking() def test_random_fill_attestations_with_leak(spec, state): rng = Random(4567) - yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, len(comm) // 3)) + yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(sorted(comm), len(comm) // 3)) @with_all_phases @spec_state_test def test_almost_full_attestations(spec, state): rng = Random(8901) - yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, len(comm) - 1)) + yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(sorted(comm), len(comm) - 1)) @with_all_phases @@ -238,7 +238,7 @@ def test_almost_full_attestations(spec, state): @leaking() def test_almost_full_attestations_with_leak(spec, state): rng = Random(8901) - yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, len(comm) - 1)) + yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(sorted(comm), len(comm) - 1)) @with_all_phases diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py index f57522ad76..eede246302 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py @@ -38,7 +38,7 @@ def _drop_random_one_third(_slot, _index, indices): assert committee_len >= 3 filter_len = committee_len // 3 participant_count = committee_len - filter_len - return rng.sample(indices, participant_count) + return rng.sample(sorted(indices), participant_count) @with_all_phases From 9dcb2eecbc6bf077900997ddb9b48379d88ea833 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Mon, 9 May 2022 14:28:54 -0500 Subject: [PATCH 80/98] Fix linter warnings --- .../test_process_rewards_and_penalties.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) 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 10772ca824..ee1a751d25 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 @@ -215,7 +215,8 @@ def test_almost_empty_attestations_with_leak(spec, state): @spec_state_test def test_random_fill_attestations(spec, state): rng = Random(4567) - yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(sorted(comm), len(comm) // 3)) + yield from run_with_participation(spec, state, + lambda slot, comm_index, comm: rng.sample(sorted(comm), len(comm) // 3)) @with_all_phases @@ -223,14 +224,16 @@ def test_random_fill_attestations(spec, state): @leaking() def test_random_fill_attestations_with_leak(spec, state): rng = Random(4567) - yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(sorted(comm), len(comm) // 3)) + yield from run_with_participation(spec, state, + lambda slot, comm_index, comm: rng.sample(sorted(comm), len(comm) // 3)) @with_all_phases @spec_state_test def test_almost_full_attestations(spec, state): rng = Random(8901) - yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(sorted(comm), len(comm) - 1)) + yield from run_with_participation(spec, state, + lambda slot, comm_index, comm: rng.sample(sorted(comm), len(comm) - 1)) @with_all_phases @@ -238,7 +241,8 @@ def test_almost_full_attestations(spec, state): @leaking() def test_almost_full_attestations_with_leak(spec, state): rng = Random(8901) - yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(sorted(comm), len(comm) - 1)) + yield from run_with_participation(spec, state, + lambda slot, comm_index, comm: rng.sample(sorted(comm), len(comm) - 1)) @with_all_phases From 90c182563756f15873cfb7f89b33df71cad84ef4 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Mon, 9 May 2022 15:53:36 -0500 Subject: [PATCH 81/98] Convert participation_fn from lambda to def I felt that the the lambda was a little too complicated. --- .../test_process_rewards_and_penalties.py | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) 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 ee1a751d25..2bb2974980 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 @@ -200,7 +200,10 @@ def participation_tracker(slot, comm_index, comm): @spec_state_test def test_almost_empty_attestations(spec, state): rng = Random(1234) - yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(sorted(comm), 1)) + + def participation_fn(slot, comm_index, comm): + return rng.sample(sorted(comm), 1) + yield from run_with_participation(spec, state, participation_fn) @with_all_phases @@ -208,15 +211,20 @@ def test_almost_empty_attestations(spec, state): @leaking() def test_almost_empty_attestations_with_leak(spec, state): rng = Random(1234) - yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(sorted(comm), 1)) + + def participation_fn(slot, comm_index, comm): + return rng.sample(sorted(comm), 1) + yield from run_with_participation(spec, state, participation_fn) @with_all_phases @spec_state_test def test_random_fill_attestations(spec, state): rng = Random(4567) - yield from run_with_participation(spec, state, - lambda slot, comm_index, comm: rng.sample(sorted(comm), len(comm) // 3)) + + def participation_fn(slot, comm_index, comm): + return rng.sample(sorted(comm), len(comm) // 3) + yield from run_with_participation(spec, state, participation_fn) @with_all_phases @@ -224,16 +232,20 @@ def test_random_fill_attestations(spec, state): @leaking() def test_random_fill_attestations_with_leak(spec, state): rng = Random(4567) - yield from run_with_participation(spec, state, - lambda slot, comm_index, comm: rng.sample(sorted(comm), len(comm) // 3)) + + def participation_fn(slot, comm_index, comm): + return rng.sample(sorted(comm), len(comm) // 3) + yield from run_with_participation(spec, state, participation_fn) @with_all_phases @spec_state_test def test_almost_full_attestations(spec, state): rng = Random(8901) - yield from run_with_participation(spec, state, - lambda slot, comm_index, comm: rng.sample(sorted(comm), len(comm) - 1)) + + def participation_fn(slot, comm_index, comm): + return rng.sample(sorted(comm), len(comm) - 1) + yield from run_with_participation(spec, state, participation_fn) @with_all_phases @@ -241,8 +253,10 @@ def test_almost_full_attestations(spec, state): @leaking() def test_almost_full_attestations_with_leak(spec, state): rng = Random(8901) - yield from run_with_participation(spec, state, - lambda slot, comm_index, comm: rng.sample(sorted(comm), len(comm) - 1)) + + def participation_fn(slot, comm_index, comm): + return rng.sample(sorted(comm), len(comm) - 1) + yield from run_with_participation(spec, state, participation_fn) @with_all_phases From e2cbdb2b672e1b7a74a94e60e3218c7b79743e88 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Mon, 9 May 2022 16:47:19 -0500 Subject: [PATCH 82/98] Move test case to new location --- .../test_process_registry_updates.py | 26 ------------------ .../test/phase0/helper_functions/test_misc.py | 27 +++++++++++++++++++ 2 files changed, 27 insertions(+), 26 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase0/helper_functions/test_misc.py diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py index 2aff866c2e..6539dc92d4 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py @@ -1,5 +1,3 @@ -import pytest - from eth2spec.test.helpers.deposits import mock_deposit from eth2spec.test.helpers.state import next_epoch, next_slots from eth2spec.test.helpers.constants import MINIMAL @@ -344,27 +342,3 @@ def test_activation_queue_activation_and_ejection__exceed_scaled_churn_limit(spe churn_limit = spec.get_validator_churn_limit(state) assert churn_limit > spec.config.MIN_PER_EPOCH_CHURN_LIMIT yield from run_test_activation_queue_activation_and_ejection(spec, state, churn_limit * 2) - - -@with_all_phases -@spec_state_test -def test_invalid_large_withdrawable_epoch(spec, state): - """ - This test forces a validator into a withdrawable epoch that overflows the - epoch (uint64) type. To do this we need two validators, one validator that - already has an exit epoch and another with a low effective balance. When - calculating the withdrawable epoch for the second validator, it will - use the greatest exit epoch of all of the validators. If the first - validator is given an exit epoch between - (FAR_FUTURE_EPOCH-MIN_VALIDATOR_WITHDRAWABILITY_DELAY+1) and - (FAR_FUTURE_EPOCH-1), it will cause an overflow. - """ - assert spec.is_active_validator(state.validators[0], spec.get_current_epoch(state)) - assert spec.is_active_validator(state.validators[1], spec.get_current_epoch(state)) - - state.validators[0].exit_epoch = spec.FAR_FUTURE_EPOCH - 1 - state.validators[1].effective_balance = spec.config.EJECTION_BALANCE - - with pytest.raises(ValueError): - yield from run_process_registry_updates(spec, state) - yield 'post', None diff --git a/tests/core/pyspec/eth2spec/test/phase0/helper_functions/test_misc.py b/tests/core/pyspec/eth2spec/test/phase0/helper_functions/test_misc.py new file mode 100644 index 0000000000..7fdc57e910 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase0/helper_functions/test_misc.py @@ -0,0 +1,27 @@ +import pytest + +from eth2spec.test.context import spec_state_test, with_all_phases + +@with_all_phases +@spec_state_test +def test_invalid_large_withdrawable_epoch(spec, state): + """ + This test forces a validator into a withdrawable epoch that overflows the + epoch (uint64) type. To do this we need two validators, one validator that + already has an exit epoch and another with a low effective balance. When + calculating the withdrawable epoch for the second validator, it will + use the greatest exit epoch of all of the validators. If the first + validator is given an exit epoch between + (FAR_FUTURE_EPOCH-MIN_VALIDATOR_WITHDRAWABILITY_DELAY+1) and + (FAR_FUTURE_EPOCH-1), it will cause an overflow. + """ + assert spec.is_active_validator(state.validators[0], spec.get_current_epoch(state)) + assert spec.is_active_validator(state.validators[1], spec.get_current_epoch(state)) + + state.validators[0].exit_epoch = spec.FAR_FUTURE_EPOCH - 1 + state.validators[1].effective_balance = spec.config.EJECTION_BALANCE + + yield 'pre', state + with pytest.raises(ValueError): + spec.initiate_validator_exit(state, 1) + yield 'post', None From ac7267c93873cca968aa7b6811fb53fb8b0407e9 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Mon, 9 May 2022 16:50:43 -0500 Subject: [PATCH 83/98] Fix linter warnings again --- .../pyspec/eth2spec/test/phase0/helper_functions/test_misc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/core/pyspec/eth2spec/test/phase0/helper_functions/test_misc.py b/tests/core/pyspec/eth2spec/test/phase0/helper_functions/test_misc.py index 7fdc57e910..88d7478f5c 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/helper_functions/test_misc.py +++ b/tests/core/pyspec/eth2spec/test/phase0/helper_functions/test_misc.py @@ -2,6 +2,7 @@ from eth2spec.test.context import spec_state_test, with_all_phases + @with_all_phases @spec_state_test def test_invalid_large_withdrawable_epoch(spec, state): From c0a8178fffdcf360029fc16ac9d9d9b9c85bde32 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 10 May 2022 09:37:01 -0600 Subject: [PATCH 84/98] minor patch to bytes formatting in capella --- specs/capella/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index ff93bec9c8..f940b3a273 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -422,7 +422,7 @@ def process_bls_to_execution_change(state: BeaconState, validator.withdrawal_credentials = ( ETH1_ADDRESS_WITHDRAWAL_PREFIX - + (0x00).to_bytes(11, 'little') + + b'\x00' * 11 + address_change.to_execution_address ) ``` From 5868a53cf19e6520260cbeef85b8248791937ea8 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Tue, 10 May 2022 10:48:51 -0500 Subject: [PATCH 85/98] Revert "Fix linter warnings again" This reverts commit ac7267c93873cca968aa7b6811fb53fb8b0407e9. --- .../pyspec/eth2spec/test/phase0/helper_functions/test_misc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/helper_functions/test_misc.py b/tests/core/pyspec/eth2spec/test/phase0/helper_functions/test_misc.py index 88d7478f5c..7fdc57e910 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/helper_functions/test_misc.py +++ b/tests/core/pyspec/eth2spec/test/phase0/helper_functions/test_misc.py @@ -2,7 +2,6 @@ from eth2spec.test.context import spec_state_test, with_all_phases - @with_all_phases @spec_state_test def test_invalid_large_withdrawable_epoch(spec, state): From d1f01870767ba7554b89b429f5ec65eee6344a6b Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Tue, 10 May 2022 10:48:59 -0500 Subject: [PATCH 86/98] Revert "Move test case to new location" This reverts commit e2cbdb2b672e1b7a74a94e60e3218c7b79743e88. --- .../test_process_registry_updates.py | 26 ++++++++++++++++++ .../test/phase0/helper_functions/test_misc.py | 27 ------------------- 2 files changed, 26 insertions(+), 27 deletions(-) delete mode 100644 tests/core/pyspec/eth2spec/test/phase0/helper_functions/test_misc.py diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py index 6539dc92d4..2aff866c2e 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py @@ -1,3 +1,5 @@ +import pytest + from eth2spec.test.helpers.deposits import mock_deposit from eth2spec.test.helpers.state import next_epoch, next_slots from eth2spec.test.helpers.constants import MINIMAL @@ -342,3 +344,27 @@ def test_activation_queue_activation_and_ejection__exceed_scaled_churn_limit(spe churn_limit = spec.get_validator_churn_limit(state) assert churn_limit > spec.config.MIN_PER_EPOCH_CHURN_LIMIT yield from run_test_activation_queue_activation_and_ejection(spec, state, churn_limit * 2) + + +@with_all_phases +@spec_state_test +def test_invalid_large_withdrawable_epoch(spec, state): + """ + This test forces a validator into a withdrawable epoch that overflows the + epoch (uint64) type. To do this we need two validators, one validator that + already has an exit epoch and another with a low effective balance. When + calculating the withdrawable epoch for the second validator, it will + use the greatest exit epoch of all of the validators. If the first + validator is given an exit epoch between + (FAR_FUTURE_EPOCH-MIN_VALIDATOR_WITHDRAWABILITY_DELAY+1) and + (FAR_FUTURE_EPOCH-1), it will cause an overflow. + """ + assert spec.is_active_validator(state.validators[0], spec.get_current_epoch(state)) + assert spec.is_active_validator(state.validators[1], spec.get_current_epoch(state)) + + state.validators[0].exit_epoch = spec.FAR_FUTURE_EPOCH - 1 + state.validators[1].effective_balance = spec.config.EJECTION_BALANCE + + with pytest.raises(ValueError): + yield from run_process_registry_updates(spec, state) + yield 'post', None diff --git a/tests/core/pyspec/eth2spec/test/phase0/helper_functions/test_misc.py b/tests/core/pyspec/eth2spec/test/phase0/helper_functions/test_misc.py deleted file mode 100644 index 7fdc57e910..0000000000 --- a/tests/core/pyspec/eth2spec/test/phase0/helper_functions/test_misc.py +++ /dev/null @@ -1,27 +0,0 @@ -import pytest - -from eth2spec.test.context import spec_state_test, with_all_phases - -@with_all_phases -@spec_state_test -def test_invalid_large_withdrawable_epoch(spec, state): - """ - This test forces a validator into a withdrawable epoch that overflows the - epoch (uint64) type. To do this we need two validators, one validator that - already has an exit epoch and another with a low effective balance. When - calculating the withdrawable epoch for the second validator, it will - use the greatest exit epoch of all of the validators. If the first - validator is given an exit epoch between - (FAR_FUTURE_EPOCH-MIN_VALIDATOR_WITHDRAWABILITY_DELAY+1) and - (FAR_FUTURE_EPOCH-1), it will cause an overflow. - """ - assert spec.is_active_validator(state.validators[0], spec.get_current_epoch(state)) - assert spec.is_active_validator(state.validators[1], spec.get_current_epoch(state)) - - state.validators[0].exit_epoch = spec.FAR_FUTURE_EPOCH - 1 - state.validators[1].effective_balance = spec.config.EJECTION_BALANCE - - yield 'pre', state - with pytest.raises(ValueError): - spec.initiate_validator_exit(state, 1) - yield 'post', None From 02090d94ab8c8eeba1d4b30d2fac440d9f378ca1 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Tue, 10 May 2022 11:00:16 -0500 Subject: [PATCH 87/98] Remove pytest import --- .../epoch_processing/test_process_registry_updates.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py index 2aff866c2e..9c6461fb45 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py @@ -1,5 +1,3 @@ -import pytest - from eth2spec.test.helpers.deposits import mock_deposit from eth2spec.test.helpers.state import next_epoch, next_slots from eth2spec.test.helpers.constants import MINIMAL @@ -365,6 +363,10 @@ def test_invalid_large_withdrawable_epoch(spec, state): state.validators[0].exit_epoch = spec.FAR_FUTURE_EPOCH - 1 state.validators[1].effective_balance = spec.config.EJECTION_BALANCE - with pytest.raises(ValueError): + try: yield from run_process_registry_updates(spec, state) - yield 'post', None + except ValueError: + yield 'post', None + return + + raise AssertionError('expected ValueError') From 89e7a1073d37c2d027c40a0fa6f4cd982bf45234 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 10 May 2022 19:00:08 +0200 Subject: [PATCH 88/98] Add notes about invalid case to `epoch_processing` test format --- tests/formats/epoch_processing/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/formats/epoch_processing/README.md b/tests/formats/epoch_processing/README.md index 1032026a63..e0e6dc3675 100644 --- a/tests/formats/epoch_processing/README.md +++ b/tests/formats/epoch_processing/README.md @@ -21,7 +21,7 @@ An SSZ-snappy encoded `BeaconState`, the state before running the epoch sub-tran ### `post.ssz_snappy` -An SSZ-snappy encoded `BeaconState`, the state after applying the epoch sub-transition. +An SSZ-snappy encoded `BeaconState`, the state after applying the epoch sub-transition. No value if the sub-transition processing is aborted. ## Condition From 08db749bf7bf6132f715bb47b9f71b83fa1ebd40 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Fri, 13 May 2022 16:44:55 +0400 Subject: [PATCH 89/98] Optimistic Sync: remove INVALID_TERMINAL_BLOCK --- sync/optimistic.md | 1 - 1 file changed, 1 deletion(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index 6d99c592e7..e3f2d6126c 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -34,7 +34,6 @@ For brevity, we define two aliases for values of the `status` field on - Alias `INVALIDATED` to: - `INVALID` - `INVALID_BLOCK_HASH` - - `INVALID_TERMINAL_BLOCK` Let `head: BeaconBlock` be the result of calling of the fork choice algorithm at the time of block production. Let `head_block_root: Root` be the From ec7bb2369785e01c38829e51274f56c7e719257b Mon Sep 17 00:00:00 2001 From: terencechain Date: Sat, 14 May 2022 10:30:20 -0700 Subject: [PATCH 90/98] bellatrix-p2p: fix a typo --- specs/bellatrix/p2p-interface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/bellatrix/p2p-interface.md b/specs/bellatrix/p2p-interface.md index 21badce839..02d4ef6c9f 100644 --- a/specs/bellatrix/p2p-interface.md +++ b/specs/bellatrix/p2p-interface.md @@ -170,7 +170,7 @@ Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: ### Why was the max gossip message size increased at Bellatrix? With the addition of `ExecutionPayload` to `BeaconBlock`s, there is a dynamic -field -- `transactions` -- which can validly exceed the `GOSSIP_MAX_SIZE` limit (1 MiB) put in place in +field -- `transactions` -- which can validly exceed the `GOSSIP_MAX_SIZE` limit (1 MiB) put in place at Phase 0. At the `GAS_LIMIT` (~30M) currently seen on mainnet in 2021, a single transaction filled entirely with data at a cost of 16 gas per byte can create a valid `ExecutionPayload` of ~2 MiB. Thus we need a size limit to at least account for From 87991c8fa24b529358f12254dbe9ea758173b552 Mon Sep 17 00:00:00 2001 From: ChihChengLiang Date: Sat, 14 May 2022 19:47:46 +0200 Subject: [PATCH 91/98] bump milagro bls version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 35b1a2c8a3..57d8d8fe12 100644 --- a/setup.py +++ b/setup.py @@ -1048,7 +1048,7 @@ def run(self): "eth-typing>=2.1.0,<3.0.0", "pycryptodome==3.9.4", "py_ecc==5.2.0", - "milagro_bls_binding==1.6.3", + "milagro_bls_binding==1.9.0", "dataclasses==0.6", "remerkleable==0.1.24", RUAMEL_YAML_VERSION, From 20a90f1df7c5bf1eda45122e6a74abc07e02fd98 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 16 May 2022 08:08:15 -0600 Subject: [PATCH 92/98] Add new `DomainType` for application usage (#2884) --- specs/phase0/beacon-chain.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 618f396374..f06c3d56f0 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -203,6 +203,9 @@ The following values are (non-configurable) constants used throughout the specif | `DOMAIN_VOLUNTARY_EXIT` | `DomainType('0x04000000')` | | `DOMAIN_SELECTION_PROOF` | `DomainType('0x05000000')` | | `DOMAIN_AGGREGATE_AND_PROOF` | `DomainType('0x06000000')` | +| `DOMAIN_APPLICATION_MASK` | `DomainType('0x00000001')` | + +*Note*: `DOMAIN_APPLICATION_MASK` reserves the rest of the bitspace in `DomainType` for application usage. This means for some `DomainType` `DOMAIN_SOME_APPLICATION`, `DOMAIN_SOME_APPLICATION && DOMAIN_APPLICATION_MASK` **MUST** be non-zero. This expression for any other `DomainType` in the consensus specs **MUST** be zero. ## Preset From bab5e402dfe0b27db245ac5931fba090292cdf8a Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Tue, 17 May 2022 15:05:22 +0200 Subject: [PATCH 93/98] Ignore subset aggregates (#2847) * Ignore subset aggregates When aggregates are propagated through the network, it is often the case that a better aggregate has already been seen - in particular, this happens when an aggregator has not been able to include itself in the mesh and therefore publishes an aggregate with only its own attestations. This new ignore rule allows dropping all aggregates that are (non-strict) subsets of aggregates that have already been seen on the network. In particular, it does not mandate dropping aggregates where a union of previous aggregates would cause it to become a subset). The logic for allowing this is based on the premise that any aggregate that has already been seen by a peer will also have been seen by its neighbours - a subset aggregate (strict or not) brings no new value to the aggregation algorithm, except in the extreme edge case where you could combine several such sparse aggregates into a single, more dense "combined" aggregate and thus use less block space. Further, as a small benefit, computing the `hash_tree_root` of the full aggregate is generally not done -however, `hash_tree_root(data)` is already done for other purposes as this is used as index in the beacon API. * add subset ignore rule to sync contributions as well * typo --- specs/altair/p2p-interface.md | 1 + specs/phase0/p2p-interface.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/specs/altair/p2p-interface.md b/specs/altair/p2p-interface.md index f2700b3115..8d6b1c433a 100644 --- a/specs/altair/p2p-interface.md +++ b/specs/altair/p2p-interface.md @@ -144,6 +144,7 @@ def get_sync_subcommittee_pubkeys(state: BeaconState, subcommittee_index: uint64 - _[REJECT]_ `contribution_and_proof.selection_proof` selects the validator as an aggregator for the slot -- i.e. `is_sync_committee_aggregator(contribution_and_proof.selection_proof)` returns `True`. - _[REJECT]_ The aggregator's validator index is in the declared subcommittee of the current sync committee -- i.e. `state.validators[contribution_and_proof.aggregator_index].pubkey in get_sync_subcommittee_pubkeys(state, contribution.subcommittee_index)`. +- _[IGNORE]_ A valid sync committee contribution with equal `slot`, `beacon_block_root` and `subcommittee_index` whose `aggregation_bits` is non-strict superset has _not_ already been seen. - _[IGNORE]_ The sync committee contribution is the first valid contribution received for the aggregator with index `contribution_and_proof.aggregator_index` for the slot `contribution.slot` and subcommittee index `contribution.subcommittee_index` (this requires maintaining a cache of size `SYNC_COMMITTEE_SIZE` for this topic that can be flushed after each slot). diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 208414a772..69f6559bd9 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -252,7 +252,7 @@ Likewise, clients MUST NOT emit or propagate messages larger than this limit. The optional `from` (1), `seqno` (3), `signature` (5) and `key` (6) protobuf fields are omitted from the message, since messages are identified by content, anonymous, and signed where necessary in the application layer. Starting from Gossipsub v1.1, clients MUST enforce this by applying the `StrictNoSign` -[signature policy](https://github.com/libp2p/specs/blob/master/pubsub/README.md#signature-policy-options). +[signature policy](https://github.com/libp2p/specs/blob/master/pubsub/README.md#signature-policy-options). The `message-id` of a gossipsub message MUST be the following 20 byte value computed from the message data: * If `message.data` has a valid snappy decompression, set `message-id` to the first 20 bytes of the `SHA256` hash of @@ -337,7 +337,7 @@ The following validations MUST pass before forwarding the `signed_aggregate_and_ (a client MAY queue future aggregates for processing at the appropriate slot). - _[REJECT]_ The aggregate attestation's epoch matches its target -- i.e. `aggregate.data.target.epoch == compute_epoch_at_slot(aggregate.data.slot)` -- _[IGNORE]_ The valid aggregate attestation defined by `hash_tree_root(aggregate)` has _not_ already been seen +- _[IGNORE]_ A valid aggregate attestation defined by `hash_tree_root(aggregate.data)` whose `aggregation_bits` is a non-strict superset has _not_ already been seen. (via aggregate gossip, within a verified block, or through the creation of an equivalent aggregate locally). - _[IGNORE]_ 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`. From 5337da5dff85cd584c4330b46a881510c1218ca3 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 17 May 2022 07:14:19 -0600 Subject: [PATCH 94/98] use python syntax for bitwise AND operation (#2894) --- specs/phase0/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index f06c3d56f0..7e14fa951a 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -205,7 +205,7 @@ The following values are (non-configurable) constants used throughout the specif | `DOMAIN_AGGREGATE_AND_PROOF` | `DomainType('0x06000000')` | | `DOMAIN_APPLICATION_MASK` | `DomainType('0x00000001')` | -*Note*: `DOMAIN_APPLICATION_MASK` reserves the rest of the bitspace in `DomainType` for application usage. This means for some `DomainType` `DOMAIN_SOME_APPLICATION`, `DOMAIN_SOME_APPLICATION && DOMAIN_APPLICATION_MASK` **MUST** be non-zero. This expression for any other `DomainType` in the consensus specs **MUST** be zero. +*Note*: `DOMAIN_APPLICATION_MASK` reserves the rest of the bitspace in `DomainType` for application usage. This means for some `DomainType` `DOMAIN_SOME_APPLICATION`, `DOMAIN_SOME_APPLICATION & DOMAIN_APPLICATION_MASK` **MUST** be non-zero. This expression for any other `DomainType` in the consensus specs **MUST** be zero. ## Preset From d62ebaeaca6720606a9dc79bc7c4a4cb927c2757 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Thu, 19 May 2022 17:56:49 +0400 Subject: [PATCH 95/98] Run on_tick before picking the head for proposal (#2897) --- specs/phase0/validator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index 4f79c2d9d4..1b7124e92b 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -281,7 +281,7 @@ the beginning of any `slot` during which `is_proposer(state, validator_index)` r To propose, the validator selects the `BeaconBlock`, `parent` which: 1. In their view of fork choice is the head of the chain at the start of - `slot`, after applying any queued attestations from `slot - 1`. + `slot`, after running `on_tick` and applying any queued attestations from `slot - 1`. 2. Is from a slot strictly less than the slot of the block about to be proposed, i.e. `parent.slot < slot`. From b23cdef6ea8c890aa8c2308575cebd433405cb0e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 20 May 2022 01:27:51 +1000 Subject: [PATCH 96/98] Remove API restrictions for optimistic sync (#2869) * Remove API restrictions * Update optimistic.md --- sync/optimistic.md | 37 +++---------------------------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/sync/optimistic.md b/sync/optimistic.md index e3f2d6126c..c2f06a9be0 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -264,40 +264,9 @@ An optimistic validator MUST NOT participate in sync committees (i.e., sign acro ## Ethereum Beacon APIs Consensus engines which provide an implementation of the [Ethereum Beacon -APIs](https://github.com/ethereum/beacon-APIs) must take care to avoid -presenting optimistic blocks as fully-verified blocks. - -### Helpers - -Let the following response types be defined as any response with the -corresponding HTTP status code: - -- "Success" Response: Status Codes 200-299. -- "Not Found" Response: Status Code 404. -- "Syncing" Response: Status Code 503. - -### Requests for Optimistic Blocks - -When information about an optimistic block is requested, the consensus engine: - -- MUST NOT respond with success. -- MAY respond with not found. -- MAY respond with syncing. - -### Requests for an Optimistic Head - -When `is_optimistic(opt_store, head) is True`, the consensus engine: - -- MUST NOT return an optimistic `head`. -- MAY substitute the head block with `latest_verified_ancestor(block)`. -- MAY return syncing. - -### Requests to Validators Endpoints - -When `is_optimistic(opt_store, head) is True`, the consensus engine MUST return syncing to -all endpoints which match the following pattern: - -- `eth/*/validator/*` +APIs](https://github.com/ethereum/beacon-APIs) must take care to ensure the +`execution_optimistic` value is set to `True` whenever the request references +optimistic blocks (and vice-versa). ## Design Decision Rationale From 72f7b88f2a2e7d0ce0c08a36085259506c3f5df7 Mon Sep 17 00:00:00 2001 From: Caspar Schwarz-Schilling <31305984+casparschwa@users.noreply.github.com> Date: Fri, 20 May 2022 21:15:40 +0200 Subject: [PATCH 97/98] Update PROPOSER_SCORE_BOOST to 40 percent (#2895) --- configs/mainnet.yaml | 4 ++-- configs/minimal.yaml | 4 ++-- specs/phase0/fork-choice.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index be07de5d56..1c0a12d4e9 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -84,8 +84,8 @@ CHURN_LIMIT_QUOTIENT: 65536 # Fork choice # --------------------------------------------------------------- -# 33% -PROPOSER_SCORE_BOOST: 33 +# 40% +PROPOSER_SCORE_BOOST: 40 # Deposit contract # --------------------------------------------------------------- diff --git a/configs/minimal.yaml b/configs/minimal.yaml index e84d2bdf59..e619ee9316 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -81,8 +81,8 @@ CHURN_LIMIT_QUOTIENT: 32 # Fork choice # --------------------------------------------------------------- -# 33% -PROPOSER_SCORE_BOOST: 33 +# 40% +PROPOSER_SCORE_BOOST: 40 # Deposit contract diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 2c33768e51..1593e07fe0 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -76,7 +76,7 @@ Any of the above handlers that trigger an unhandled exception (e.g. a failed ass | Name | Value | | ---------------------- | ------------ | -| `PROPOSER_SCORE_BOOST` | `uint64(33)` | +| `PROPOSER_SCORE_BOOST` | `uint64(40)` | - The proposer score boost is worth `PROPOSER_SCORE_BOOST` percentage of the committee's weight, i.e., for slot with committee weight `committee_weight` the boost weight is equal to `(committee_weight * PROPOSER_SCORE_BOOST) // 100`. From 0e6a7cd39a44ba64897c359e1d62c8b475e5d926 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Fri, 20 May 2022 21:18:24 +0200 Subject: [PATCH 98/98] deprecate `BeaconBlocksByRange.step` (#2856) * deprecate `BeaconBlocksByRange.step` The `step` parameter has not seen much implementation in real life clients which instead opt to request variations on a few epochs at a time (instead of interleaving single blocks, entire epochs are interleaved). At the same time, supporting `step` on the server side brings several complications: more complex bounds checking logic, more complex loading of blocks from linear storage (which presumably stores all blocks and not just certain increments). This PR suggests that we deprecate the whole idea. Backwards compatibility is kept by simply responding with a single block when `step > 0` - this is allowed by the spec and should thus be handled gracefully by requesting clients already, should there exist any that use larger step counts. Removing `step` now allows simplifying the EL-CL protocol for serving execution data from the EL to avoid double storage. * Update specs/phase0/p2p-interface.md Co-authored-by: Danny Ryan Co-authored-by: Danny Ryan --- specs/phase0/p2p-interface.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 69f6559bd9..34b0375e06 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -727,7 +727,7 @@ Request Content: ( start_slot: Slot count: uint64 - step: uint64 + step: uint64 # Deprecated, must be set to 1 ) ``` @@ -738,12 +738,12 @@ Response Content: ) ``` -Requests beacon blocks in the slot range `[start_slot, start_slot + count * step)`, leading up to the current head block as selected by fork choice. -`step` defines the slot increment between blocks. -For example, requesting blocks starting at `start_slot` 2 with a step value of 2 would return the blocks at slots [2, 4, 6, …]. +Requests beacon blocks in the slot range `[start_slot, start_slot + count)`, leading up to the current head block as selected by fork choice. +For example, requesting blocks starting at `start_slot=2` and `count=4` would return the blocks at slots `[2, 3, 4, 5]`. In cases where a slot is empty for a given slot number, no block is returned. -For example, if slot 4 were empty in the previous example, the returned array would contain [2, 6, …]. -A request MUST NOT have a 0 slot increment, i.e. `step >= 1`. +For example, if slot 4 were empty in the previous example, the returned array would contain `[2, 3, 5]`. + +`step` is deprecated and must be set to 1. Clients may respond with a single block if a larger step is returned during the deprecation transition period. `BeaconBlocksByRange` is primarily used to sync historical blocks.