From 4f24fe455b13a05728cf62b7efde70ccee4482f8 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 1 Dec 2021 11:37:30 -0700 Subject: [PATCH 01/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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 e1b9cf97e2304905f8100602e99672ac759709df Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 3 Mar 2022 14:02:52 -0700 Subject: [PATCH 21/30] 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 3dd83cf4ee889c5e2c2696c065968559e25ccc26 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 10 Mar 2022 10:38:47 -0700 Subject: [PATCH 22/30] 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 255e942f64feac2ac499c6e1b3c2e164833e0511 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 16 Mar 2022 12:01:42 -0600 Subject: [PATCH 23/30] 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 24/30] 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 493b16902276c44bcda1e6cf26ff38ec7d92919e Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 22 Mar 2022 07:55:45 -0600 Subject: [PATCH 25/30] 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 26/30] 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 27/30] 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 28/30] 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 29/30] 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 8a388f2226404d0d4de22bb95583dc8142c4ea75 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 23 Mar 2022 15:38:17 +0800 Subject: [PATCH 30/30] 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) +