diff --git a/.circleci/config.yml b/.circleci/config.yml index 4f806b00f..fd7708f8d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -84,11 +84,25 @@ jobs: command: make citest - store_test_results: path: test_libs/pyspec/test-reports + lint: + docker: + - image: circleci/python:3.6 + working_directory: ~/specs-repo + steps: + - restore_cache: + key: v1-specs-repo-{{ .Branch }}-{{ .Revision }} + - restore_cached_venv: + venv_name: v1-pyspec + reqs_checksum: '{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "test_libs/pyspec/requirements-testing.txt" }}' + - run: + name: Run linter + command: make install_lint && make pyspec && make lint workflows: version: 2.1 test_spec: jobs: - checkout_specs + - lint - install_test: requires: - checkout_specs diff --git a/Makefile b/Makefile index 5ca35c257..4c4bfd88b 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,13 @@ test: $(PY_SPEC_ALL_TARGETS) citest: $(PY_SPEC_ALL_TARGETS) cd $(PY_SPEC_DIR); mkdir -p test-reports/eth2spec; . venv/bin/activate; python -m pytest --junitxml=test-reports/eth2spec/test_results.xml . +install_lint: + cd $(PY_SPEC_DIR); python3 -m venv venv; . venv/bin/activate; pip3 install flake8==3.5.0 + +lint: + cd $(PY_SPEC_DIR); . venv/bin/activate; \ + flake8 --max-line-length=120 ./eth2spec; + # "make pyspec" to create the pyspec for all phases. pyspec: $(PY_SPEC_ALL_TARGETS) diff --git a/scripts/phase0/build_spec.py b/scripts/phase0/build_spec.py index 231acb812..0ebfd9906 100644 --- a/scripts/phase0/build_spec.py +++ b/scripts/phase0/build_spec.py @@ -5,6 +5,7 @@ from optparse import OptionParser def build_phase0_spec(sourcefile, outfile): code_lines = [] code_lines.append(""" + from typing import ( Any, Dict, @@ -12,8 +13,18 @@ from typing import ( NewType, Tuple, ) -from eth2spec.utils.minimal_ssz import * -from eth2spec.utils.bls_stub import * +from eth2spec.utils.minimal_ssz import ( + SSZType, + hash_tree_root, + signing_root, +) +from eth2spec.utils.bls_stub import ( + bls_aggregate_pubkeys, + bls_verify, + bls_verify_multiple, +) +from eth2spec.utils.hash_function import hash + # stub, will get overwritten by real var SLOTS_PER_EPOCH = 64 @@ -61,6 +72,7 @@ def hash(x): hash_cache[x] = ret return ret + # Access to overwrite spec constants based on configuration def apply_constants_preset(preset: Dict[str, Any]): global_vars = globals() diff --git a/scripts/phase0/function_puller.py b/scripts/phase0/function_puller.py index 26ac2f17f..418eb05b1 100644 --- a/scripts/phase0/function_puller.py +++ b/scripts/phase0/function_puller.py @@ -69,15 +69,19 @@ def get_spec(file_name: str, phase:int = 0) -> List[str]: if eligible: code_lines.append(row[0] + ' = ' + (row[1].replace('**TBD**', '0x1234567890123456789012345678901234567890'))) # Build type-def re-initialization - code_lines.append('') + code_lines.append('\n') code_lines.append('def init_SSZ_types():') code_lines.append(' global_vars = globals()') for ssz_type_name, ssz_type in type_defs: code_lines.append('') for type_line in ssz_type: - code_lines.append(' ' + type_line) + if len(type_line) > 0: + code_lines.append(' ' + type_line) code_lines.append('\n') - code_lines.append('ssz_types = [' + ', '.join([f'\'{ssz_type_name}\'' for (ssz_type_name, _) in type_defs]) + ']') + code_lines.append('ssz_types = [\n') + for (ssz_type_name, _) in type_defs: + code_lines.append(f' {ssz_type_name},\n') + code_lines.append(']') code_lines.append('\n') code_lines.append('def get_ssz_type_by_name(name: str) -> SSZType:') code_lines.append(' return globals()[name]') diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index a0c26bf59..ee37d9217 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -98,8 +98,7 @@ - [Genesis state](#genesis-state) - [Genesis block](#genesis-block) - [Beacon chain state transition function](#beacon-chain-state-transition-function) - - [State caching](#state-caching) - - [Per-epoch processing](#per-epoch-processing) + - [Epoch processing](#epoch-processing) - [Helper functions](#helper-functions-1) - [Justification and finalization](#justification-and-finalization) - [Crosslinks](#crosslinks) @@ -107,8 +106,7 @@ - [Registry updates](#registry-updates) - [Slashings](#slashings) - [Final updates](#final-updates) - - [Per-slot processing](#per-slot-processing) - - [Per-block processing](#per-block-processing) + - [Block processing](#block-processing) - [Block header](#block-header) - [RANDAO](#randao) - [Eth1 data](#eth1-data) @@ -119,7 +117,6 @@ - [Deposits](#deposits) - [Voluntary exits](#voluntary-exits) - [Transfers](#transfers) - - [State root verification](#state-root-verification) @@ -375,6 +372,7 @@ The types are defined topologically to aid in facilitating an executable version 'signature': 'bytes96', } ``` + #### `Validator` ```python @@ -579,8 +577,8 @@ The types are defined topologically to aid in facilitating an executable version 'latest_block_roots': ['bytes32', SLOTS_PER_HISTORICAL_ROOT], 'latest_state_roots': ['bytes32', SLOTS_PER_HISTORICAL_ROOT], 'latest_active_index_roots': ['bytes32', LATEST_ACTIVE_INDEX_ROOTS_LENGTH], - 'latest_slashed_balances': ['uint64', LATEST_SLASHED_EXIT_LENGTH], # Balances slashed at every withdrawal period - 'latest_block_header': BeaconBlockHeader, # `latest_block_header.state_root == ZERO_HASH` temporarily + 'latest_slashed_balances': ['uint64', LATEST_SLASHED_EXIT_LENGTH], + 'latest_block_header': BeaconBlockHeader, 'historical_roots': ['bytes32'], # Ethereum 1.0 chain data @@ -1127,7 +1125,7 @@ def get_churn_limit(state: BeaconState) -> int: ```python def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: """ - Initiate the validator of the given ``index``. + Initiate the exit of the validator of the given ``index``. """ # Return if validator already initiated exit validator = state.validator_registry[index] @@ -1149,7 +1147,9 @@ def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: #### `slash_validator` ```python -def slash_validator(state: BeaconState, slashed_index: ValidatorIndex, whistleblower_index: ValidatorIndex=None) -> None: +def slash_validator(state: BeaconState, + slashed_index: ValidatorIndex, + whistleblower_index: ValidatorIndex=None) -> None: """ Slash the validator with index ``slashed_index``. """ @@ -1215,50 +1215,61 @@ Let `genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state))`. ## Beacon chain state transition function -We now define the state transition function. At a high level, the state transition is made up of four parts: - -1. State caching, which happens at the start of every slot. -2. The per-epoch transitions, which happens at the start of the first slot of every epoch. -3. The per-slot transitions, which happens at every slot. -4. The per-block transitions, which happens at every block. - -Transition section notes: -* The state caching caches the state root of the previous slot and updates block and state roots records. -* The per-epoch transitions focus on the [validator](#dfn-validator) registry, including adjusting balances and activating and exiting [validators](#dfn-validator), as well as processing crosslinks and managing block justification/finalization. -* The per-slot transitions focus on the slot counter. -* The per-block transitions generally focus on verifying aggregate signatures and saving temporary records relating to the per-block activity in the `BeaconState`. - -Beacon blocks that trigger unhandled Python exceptions (e.g. out-of-range list accesses) and failed `assert`s during the state transition are considered invalid. - -*Note*: If there are skipped slots between a block and its parent block, run the steps in the [state-root](#state-caching), [per-epoch](#per-epoch-processing), and [per-slot](#per-slot-processing) sections once for each skipped slot and then once for the slot containing the new block. - -### State caching - -At every `slot > GENESIS_SLOT` run the following function: +The post-state corresponding to a pre-state `state` and a block `block` is defined as `state_transition(state, block)`. State transitions that trigger an unhandled excpetion (e.g. a failed `assert` or an out-of-range list access) are considered invalid. ```python -def cache_state(state: BeaconState) -> None: - # Cache latest known state root (for previous slot) - latest_state_root = hash_tree_root(state) - state.latest_state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = latest_state_root - - # Store latest known state root (for previous slot) in latest_block_header if it is empty - if state.latest_block_header.state_root == ZERO_HASH: - state.latest_block_header.state_root = latest_state_root - - # Cache latest known block root (for previous slot) - latest_block_root = signing_root(state.latest_block_header) - state.latest_block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = latest_block_root +def state_transition(state: BeaconState, block: BeaconBlock, validate_state_root: bool=False) -> BeaconState: + # Process slots (including those with no blocks) since block + process_slots(state, block.slot) + # Process block + process_block(state, block) + # Validate state root (`validate_state_root == True` in production) + if validate_state_root: + assert block.state_root == hash_tree_root(state) + # Return post-state + return state ``` -### Per-epoch processing +```python +def process_slots(state: BeaconState, slot: Slot) -> None: + assert state.slot < slot + while state.slot < slot: + process_slot(state) + # Process epoch on the first slot of the next epoch + if (state.slot + 1) % SLOTS_PER_EPOCH == 0: + process_epoch(state) + state.slot += 1 +``` -The steps below happen when `state.slot > GENESIS_SLOT and (state.slot + 1) % SLOTS_PER_EPOCH == 0`. +```python +def process_slot(state: BeaconState) -> None: + # Cache state root + previous_state_root = hash_tree_root(state) + state.latest_state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_state_root + + # Cache latest block header state root + if state.latest_block_header.state_root == ZERO_HASH: + state.latest_block_header.state_root = previous_state_root + + # Cache block root + previous_block_root = signing_root(state.latest_block_header) + state.latest_block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_block_root +``` + +### Epoch processing + +```python +def process_epoch(state: BeaconState) -> None: + process_justification_and_finalization(state) + process_crosslinks(state) + process_rewards_and_penalties(state) + process_registry_updates(state) + process_slashings(state) + process_final_updates(state) +``` #### Helper functions -We define epoch transition helper functions: - ```python def get_total_active_balance(state: BeaconState) -> Gwei: return get_total_balance(state, get_active_validator_indices(state, get_current_epoch(state))) @@ -1287,7 +1298,8 @@ def get_matching_head_attestations(state: BeaconState, epoch: Epoch) -> List[Pen ``` ```python -def get_unslashed_attesting_indices(state: BeaconState, attestations: List[PendingAttestation]) -> List[ValidatorIndex]: +def get_unslashed_attesting_indices(state: BeaconState, + attestations: List[PendingAttestation]) -> List[ValidatorIndex]: output = set() for a in attestations: output = output.union(get_attesting_indices(state, a.data, a.aggregation_bitfield)) @@ -1300,7 +1312,9 @@ def get_attesting_balance(state: BeaconState, attestations: List[PendingAttestat ``` ```python -def get_winning_crosslink_and_attesting_indices(state: BeaconState, epoch: Epoch, shard: Shard) -> Tuple[Crosslink, List[ValidatorIndex]]: +def get_winning_crosslink_and_attesting_indices(state: BeaconState, + epoch: Epoch, + shard: Shard) -> Tuple[Crosslink, List[ValidatorIndex]]: attestations = [a for a in get_matching_source_attestations(state, epoch) if a.data.crosslink.shard == shard] crosslinks = list(filter( lambda c: hash_tree_root(state.current_crosslinks[shard]) in (c.parent_root, hash_tree_root(c)), @@ -1316,8 +1330,6 @@ def get_winning_crosslink_and_attesting_indices(state: BeaconState, epoch: Epoch #### Justification and finalization -Run the following function: - ```python def process_justification_and_finalization(state: BeaconState) -> None: if get_current_epoch(state) <= GENESIS_EPOCH + 1: @@ -1332,12 +1344,16 @@ def process_justification_and_finalization(state: BeaconState) -> None: state.previous_justified_epoch = state.current_justified_epoch state.previous_justified_root = state.current_justified_root state.justification_bitfield = (state.justification_bitfield << 1) % 2**64 - previous_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, previous_epoch)) + previous_epoch_matching_target_balance = get_attesting_balance( + state, get_matching_target_attestations(state, previous_epoch) + ) if previous_epoch_matching_target_balance * 3 >= get_total_active_balance(state) * 2: state.current_justified_epoch = previous_epoch state.current_justified_root = get_block_root(state, state.current_justified_epoch) state.justification_bitfield |= (1 << 1) - current_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, current_epoch)) + current_epoch_matching_target_balance = get_attesting_balance( + state, get_matching_target_attestations(state, current_epoch) + ) if current_epoch_matching_target_balance * 3 >= get_total_active_balance(state) * 2: state.current_justified_epoch = current_epoch state.current_justified_root = get_block_root(state, state.current_justified_epoch) @@ -1365,8 +1381,6 @@ def process_justification_and_finalization(state: BeaconState) -> None: #### Crosslinks -Run the following function: - ```python def process_crosslinks(state: BeaconState) -> None: state.previous_crosslinks = [c for c in state.current_crosslinks] @@ -1381,8 +1395,6 @@ def process_crosslinks(state: BeaconState) -> None: #### Rewards and penalties -First, we define additional helpers: - ```python def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: adjusted_quotient = integer_squareroot(get_total_active_balance(state)) // BASE_REWARD_QUOTIENT @@ -1431,7 +1443,9 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: for index in eligible_validator_indices: penalties[index] += BASE_REWARDS_PER_EPOCH * get_base_reward(state, index) if index not in matching_target_attesting_indices: - penalties[index] += state.validator_registry[index].effective_balance * finality_delay // INACTIVITY_PENALTY_QUOTIENT + penalties[index] += ( + state.validator_registry[index].effective_balance * finality_delay // INACTIVITY_PENALTY_QUOTIENT + ) return rewards, penalties ``` @@ -1456,8 +1470,6 @@ def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: return rewards, penalties ``` -Run the following function: - ```python def process_rewards_and_penalties(state: BeaconState) -> None: if get_current_epoch(state) == GENESIS_EPOCH: @@ -1472,13 +1484,14 @@ def process_rewards_and_penalties(state: BeaconState) -> None: #### Registry updates -Run the following function: - ```python def process_registry_updates(state: BeaconState) -> None: # Process activation eligibility and ejections for index, validator in enumerate(state.validator_registry): - if validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and validator.effective_balance >= MAX_EFFECTIVE_BALANCE: + if ( + validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and + validator.effective_balance >= MAX_EFFECTIVE_BALANCE + ): validator.activation_eligibility_epoch = get_current_epoch(state) if is_active_validator(validator, get_current_epoch(state)) and validator.effective_balance <= EJECTION_BALANCE: @@ -1499,8 +1512,6 @@ def process_registry_updates(state: BeaconState) -> None: #### Slashings -Run the following function: - ```python def process_slashings(state: BeaconState) -> None: current_epoch = get_current_epoch(state) @@ -1523,8 +1534,6 @@ def process_slashings(state: BeaconState) -> None: #### Final updates -Run the following function: - ```python def process_final_updates(state: BeaconState) -> None: current_epoch = get_current_epoch(state) @@ -1563,19 +1572,16 @@ def process_final_updates(state: BeaconState) -> None: state.current_epoch_attestations = [] ``` -### Per-slot processing - -At every `slot > GENESIS_SLOT` run the following function: +### Block processing ```python -def advance_slot(state: BeaconState) -> None: - state.slot += 1 +def process_block(state: BeaconState, block: BeaconBlock) -> None: + process_block_header(state, block) + process_randao(state, block.body) + process_eth1_data(state, block.body) + process_operations(state, block.body) ``` -### Per-block processing - -For every `block` except the genesis block, run `process_block_header(state, block)`, `process_randao(state, block)` and `process_eth1_data(state, block)`. - #### Block header ```python @@ -1600,39 +1606,57 @@ def process_block_header(state: BeaconState, block: BeaconBlock) -> None: #### RANDAO ```python -def process_randao(state: BeaconState, block: BeaconBlock) -> None: +def process_randao(state: BeaconState, body: BeaconBlockBody) -> None: proposer = state.validator_registry[get_beacon_proposer_index(state)] # Verify that the provided randao value is valid - assert bls_verify(proposer.pubkey, hash_tree_root(get_current_epoch(state)), block.body.randao_reveal, get_domain(state, DOMAIN_RANDAO)) + assert bls_verify( + proposer.pubkey, + hash_tree_root(get_current_epoch(state)), + body.randao_reveal, + get_domain(state, DOMAIN_RANDAO), + ) # Mix it in state.latest_randao_mixes[get_current_epoch(state) % LATEST_RANDAO_MIXES_LENGTH] = ( xor(get_randao_mix(state, get_current_epoch(state)), - hash(block.body.randao_reveal)) + hash(body.randao_reveal)) ) ``` #### Eth1 data ```python -def process_eth1_data(state: BeaconState, block: BeaconBlock) -> None: - state.eth1_data_votes.append(block.body.eth1_data) - if state.eth1_data_votes.count(block.body.eth1_data) * 2 > SLOTS_PER_ETH1_VOTING_PERIOD: - state.latest_eth1_data = block.body.eth1_data +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 > SLOTS_PER_ETH1_VOTING_PERIOD: + state.latest_eth1_data = body.eth1_data ``` #### Operations -*Note*: All functions in this section mutate `state`. +```python +def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: + # Verify that outstanding deposits are processed up to the maximum number of deposits + assert len(body.deposits) == min(MAX_DEPOSITS, state.latest_eth1_data.deposit_count - state.deposit_index) + # Verify that there are no duplicate transfers + assert len(body.transfers) == len(set(body.transfers)) + + for operations, max_operations, function in ( + (body.proposer_slashings, MAX_PROPOSER_SLASHINGS, process_proposer_slashing), + (body.attester_slashings, MAX_ATTESTER_SLASHINGS, process_attester_slashing), + (body.attestations, MAX_ATTESTATIONS, process_attestation), + (body.deposits, MAX_DEPOSITS, process_deposit), + (body.voluntary_exits, MAX_VOLUNTARY_EXITS, process_voluntary_exit), + (body.transfers, MAX_TRANSFERS, process_transfer), + ): + assert len(operations) <= max_operations + for operation in operations: + function(state, operation) +``` ##### Proposer slashings -Verify that `len(block.body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS`. - -For each `proposer_slashing` in `block.body.proposer_slashings`, run the following function: - ```python -def process_proposer_slashing(state: BeaconState, - proposer_slashing: ProposerSlashing) -> None: +def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None: """ Process ``ProposerSlashing`` operation. """ @@ -1653,13 +1677,8 @@ def process_proposer_slashing(state: BeaconState, ##### Attester slashings -Verify that `len(block.body.attester_slashings) <= MAX_ATTESTER_SLASHINGS`. - -For each `attester_slashing` in `block.body.attester_slashings`, run the following function: - ```python -def process_attester_slashing(state: BeaconState, - attester_slashing: AttesterSlashing) -> None: +def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSlashing) -> None: """ Process ``AttesterSlashing`` operation. """ @@ -1681,10 +1700,6 @@ def process_attester_slashing(state: BeaconState, ##### Attestations -Verify that `len(block.body.attestations) <= MAX_ATTESTATIONS`. - -For each `attestation` in `block.body.attestations`, run the following function: - ```python def process_attestation(state: BeaconState, attestation: Attestation) -> None: """ @@ -1721,10 +1736,6 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: ##### Deposits -Verify that `len(block.body.deposits) == min(MAX_DEPOSITS, state.latest_eth1_data.deposit_count - state.deposit_index)`. - -For each `deposit` in `block.body.deposits`, run the following function: - ```python def process_deposit(state: BeaconState, deposit: Deposit) -> None: """ @@ -1748,7 +1759,9 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: validator_pubkeys = [v.pubkey for v in state.validator_registry] if pubkey not in validator_pubkeys: # Verify the deposit signature (proof of possession) - if not bls_verify(pubkey, signing_root(deposit.data), deposit.data.signature, get_domain(state, DOMAIN_DEPOSIT)): + if not bls_verify( + pubkey, signing_root(deposit.data), deposit.data.signature, get_domain(state, DOMAIN_DEPOSIT) + ): return # Add validator and balance entries @@ -1770,10 +1783,6 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: ##### Voluntary exits -Verify that `len(block.body.voluntary_exits) <= MAX_VOLUNTARY_EXITS`. - -For each `exit` in `block.body.voluntary_exits`, run the following function: - ```python def process_voluntary_exit(state: BeaconState, exit: VoluntaryExit) -> None: """ @@ -1797,10 +1806,6 @@ def process_voluntary_exit(state: BeaconState, exit: VoluntaryExit) -> None: ##### Transfers -Verify that `len(block.body.transfers) <= MAX_TRANSFERS` and that all transfers are distinct. - -For each `transfer` in `block.body.transfers`, run the following function: - ```python def process_transfer(state: BeaconState, transfer: Transfer) -> None: """ @@ -1831,12 +1836,3 @@ def process_transfer(state: BeaconState, transfer: Transfer) -> None: assert not (0 < state.balances[transfer.sender] < MIN_DEPOSIT_AMOUNT) assert not (0 < state.balances[transfer.recipient] < MIN_DEPOSIT_AMOUNT) ``` - -#### State root verification - -Verify the block's `state_root` by running the following function: - -```python -def verify_block_state_root(state: BeaconState, block: BeaconBlock) -> None: - assert block.state_root == hash_tree_root(state) -``` diff --git a/specs/core/0_deposit-contract.md b/specs/core/0_deposit-contract.md index 917adaecb..5d9398589 100644 --- a/specs/core/0_deposit-contract.md +++ b/specs/core/0_deposit-contract.md @@ -72,7 +72,7 @@ Every Ethereum 1.0 deposit, of size at least `MIN_DEPOSIT_AMOUNT`, emits a `Depo ### `Eth2Genesis` log -When `CHAIN_START_FULL_DEPOSIT_THRESHOLD` of full deposits have been made, the deposit contract emits the `Eth2Genesis` log. The beacon chain state may then be initialized by calling the `get_genesis_beacon_state` function (defined below) where: +When `CHAIN_START_FULL_DEPOSIT_THRESHOLD` of full deposits have been made, the deposit contract emits the `Eth2Genesis` log. The beacon chain state may then be initialized by calling the `get_genesis_beacon_state` function (defined [here](./0_beacon-chain.md#genesis-state)) where: * `genesis_time` equals `time` in the `Eth2Genesis` log * `latest_eth1_data.deposit_root` equals `deposit_root` in the `Eth2Genesis` log diff --git a/specs/core/1_custody-game.md b/specs/core/1_custody-game.md index 2b1c75fc7..ba828dd2c 100644 --- a/specs/core/1_custody-game.md +++ b/specs/core/1_custody-game.md @@ -104,6 +104,8 @@ This document details the beacon chain additions and changes in Phase 1 of Ether ### Reward and penalty quotients +| Name | Value | +| - | - | | `EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE` | `2**1` (= 2) | ### Signature domains diff --git a/specs/simple-serialize.md b/specs/simple-serialize.md index b4ea8135a..8633c7ed1 100644 --- a/specs/simple-serialize.md +++ b/specs/simple-serialize.md @@ -47,7 +47,7 @@ * **list**: ordered variable-length homogeneous collection of values * angle bracket notation `[type]`, e.g. `["uint64"]` * **union**: union type containing one of the given subtypes - * round bracket notation `(type_1, type_2, ...)`, e.g. `("uint64", "null")` + * round bracket notation `(type_1, type_2, ...)`, e.g. `("null", "uint64")` ### Variable-size and fixed-size diff --git a/specs/validator/0_beacon-chain-validator.md b/specs/validator/0_beacon-chain-validator.md index 35de666d7..ef6e8bf25 100644 --- a/specs/validator/0_beacon-chain-validator.md +++ b/specs/validator/0_beacon-chain-validator.md @@ -238,7 +238,7 @@ A validator should create and broadcast the attestation halfway through the `slo First the validator should construct `attestation_data`, an [`AttestationData`](../core/0_beacon-chain.md#attestationdata) object based upon the state at the assigned slot. * Let `head_block` be the result of running the fork choice during the assigned slot. -* Let `head_state` be the state of `head_block` processed through any empty slots up to the assigned slot. +* Let `head_state` be the state of `head_block` processed through any empty slots up to the assigned slot using `process_slots(state, slot)`. ##### LMD GHOST vote @@ -246,7 +246,7 @@ Set `attestation_data.beacon_block_root = signing_root(head_block)`. ##### FFG vote -* Set `attestation_data.source_epoch = head_state.justified_epoch`. +* Set `attestation_data.source_epoch = head_state.current_justified_epoch`. * Set `attestation_data.source_root = head_state.current_justified_root`. * Set `attestation_data.target_epoch = get_current_epoch(head_state)` * Set `attestation_data.target_root = signing_root(epoch_boundary_block)` where `epoch_boundary_block` is the block at the most recent epoch boundary. @@ -360,7 +360,7 @@ def is_proposer_at_slot(state: BeaconState, return get_beacon_proposer_index(state) == validator_index ``` -*Note*: To see if a validator is assigned to proposer during the slot, the validator must run an empty slot transition from the previous state to the current slot. +*Note*: To see if a validator is assigned to proposer during the slot, the validator must run an empty slot transition from the previous state to the current slot using `process_slots(state, current_slot)`. ### Lookahead diff --git a/test_libs/pyspec/eth2spec/debug/decode.py b/test_libs/pyspec/eth2spec/debug/decode.py index aeac3924d..e9aa8bc2b 100644 --- a/test_libs/pyspec/eth2spec/debug/decode.py +++ b/test_libs/pyspec/eth2spec/debug/decode.py @@ -16,11 +16,11 @@ def decode(json, typ): for field, subtype in typ.fields.items(): temp[field] = decode(json[field], subtype) if field + "_hash_tree_root" in json: - assert(json[field + "_hash_tree_root"][2:] == + assert(json[field + "_hash_tree_root"][2:] == hash_tree_root(temp[field], subtype).hex()) ret = typ(**temp) if "hash_tree_root" in json: - assert(json["hash_tree_root"][2:] == + assert(json["hash_tree_root"][2:] == hash_tree_root(ret, typ).hex()) return ret else: diff --git a/test_libs/pyspec/eth2spec/debug/encode.py b/test_libs/pyspec/eth2spec/debug/encode.py index d3513e638..b38e5fe98 100644 --- a/test_libs/pyspec/eth2spec/debug/encode.py +++ b/test_libs/pyspec/eth2spec/debug/encode.py @@ -25,4 +25,3 @@ def encode(value, typ, include_hash_tree_roots=False): else: print(value, typ) raise Exception("Type not recognized") - diff --git a/test_libs/pyspec/eth2spec/debug/random_value.py b/test_libs/pyspec/eth2spec/debug/random_value.py index a853d2328..f28181943 100644 --- a/test_libs/pyspec/eth2spec/debug/random_value.py +++ b/test_libs/pyspec/eth2spec/debug/random_value.py @@ -31,7 +31,12 @@ class RandomizationMode(Enum): return self.value in [0, 4, 5] -def get_random_ssz_object(rng: Random, typ: Any, max_bytes_length: int, max_list_length: int, mode: RandomizationMode, chaos: bool) -> Any: +def get_random_ssz_object(rng: Random, + typ: Any, + max_bytes_length: int, + max_list_length: int, + mode: RandomizationMode, + chaos: bool) -> Any: """ Create an object for a given type, filled with random data. :param rng: The random number generator to use. @@ -77,7 +82,10 @@ def get_random_ssz_object(rng: Random, typ: Any, max_bytes_length: int, max_list return get_random_basic_value(rng, typ) # Vector: elif isinstance(typ, list) and len(typ) == 2: - return [get_random_ssz_object(rng, typ[0], max_bytes_length, max_list_length, mode, chaos) for _ in range(typ[1])] + return [ + get_random_ssz_object(rng, typ[0], max_bytes_length, max_list_length, mode, chaos) + for _ in range(typ[1]) + ] # List: elif isinstance(typ, list) and len(typ) == 1: length = rng.randint(0, max_list_length) @@ -85,10 +93,17 @@ def get_random_ssz_object(rng: Random, typ: Any, max_bytes_length: int, max_list length = 1 if mode == RandomizationMode.mode_max_count: length = max_list_length - return [get_random_ssz_object(rng, typ[0], max_bytes_length, max_list_length, mode, chaos) for _ in range(length)] + return [ + get_random_ssz_object(rng, typ[0], max_bytes_length, max_list_length, mode, chaos) + for _ in range(length) + ] # Container: elif hasattr(typ, 'fields'): - return typ(**{field: get_random_ssz_object(rng, subtype, max_bytes_length, max_list_length, mode, chaos) for field, subtype in typ.fields.items()}) + return typ(**{ + field: + get_random_ssz_object(rng, subtype, max_bytes_length, max_list_length, mode, chaos) + for field, subtype in typ.fields.items() + }) else: print(typ) raise Exception("Type not recognized") diff --git a/test_libs/pyspec/eth2spec/phase0/state_transition.py b/test_libs/pyspec/eth2spec/phase0/state_transition.py deleted file mode 100644 index 1bef358d4..000000000 --- a/test_libs/pyspec/eth2spec/phase0/state_transition.py +++ /dev/null @@ -1,112 +0,0 @@ -from . import spec - - -from typing import ( - Any, - Callable, - List -) - -from .spec import ( - BeaconState, - BeaconBlock, - Slot, -) - - -def expected_deposit_count(state: BeaconState) -> int: - return min( - spec.MAX_DEPOSITS, - state.latest_eth1_data.deposit_count - state.deposit_index - ) - - -def process_operation_type(state: BeaconState, - operations: List[Any], - max_operations: int, - tx_fn: Callable[[BeaconState, Any], None]) -> None: - assert len(operations) <= max_operations - for operation in operations: - tx_fn(state, operation) - - -def process_operations(state: BeaconState, block: BeaconBlock) -> None: - process_operation_type( - state, - block.body.proposer_slashings, - spec.MAX_PROPOSER_SLASHINGS, - spec.process_proposer_slashing, - ) - - process_operation_type( - state, - block.body.attester_slashings, - spec.MAX_ATTESTER_SLASHINGS, - spec.process_attester_slashing, - ) - - process_operation_type( - state, - block.body.attestations, - spec.MAX_ATTESTATIONS, - spec.process_attestation, - ) - - assert len(block.body.deposits) == expected_deposit_count(state) - process_operation_type( - state, - block.body.deposits, - spec.MAX_DEPOSITS, - spec.process_deposit, - ) - - process_operation_type( - state, - block.body.voluntary_exits, - spec.MAX_VOLUNTARY_EXITS, - spec.process_voluntary_exit, - ) - - assert len(block.body.transfers) == len(set(block.body.transfers)) - process_operation_type( - state, - block.body.transfers, - spec.MAX_TRANSFERS, - spec.process_transfer, - ) - - -def process_block(state: BeaconState, - block: BeaconBlock, - verify_state_root: bool=False) -> None: - spec.process_block_header(state, block) - spec.process_randao(state, block) - spec.process_eth1_data(state, block) - - process_operations(state, block) - if verify_state_root: - spec.verify_block_state_root(state, block) - - -def process_epoch_transition(state: BeaconState) -> None: - spec.process_justification_and_finalization(state) - spec.process_crosslinks(state) - spec.process_rewards_and_penalties(state) - spec.process_registry_updates(state) - spec.process_slashings(state) - spec.process_final_updates(state) - - -def state_transition_to(state: BeaconState, up_to: Slot) -> BeaconState: - while state.slot < up_to: - spec.cache_state(state) - if (state.slot + 1) % spec.SLOTS_PER_EPOCH == 0: - process_epoch_transition(state) - spec.advance_slot(state) - - -def state_transition(state: BeaconState, - block: BeaconBlock, - verify_state_root: bool=False) -> BeaconState: - state_transition_to(state, block.slot) - process_block(state, block, verify_state_root) diff --git a/test_libs/pyspec/tests/block_processing/test_process_attestation.py b/test_libs/pyspec/tests/block_processing/test_process_attestation.py index 12e6c57fa..0b1ceb79e 100644 --- a/test_libs/pyspec/tests/block_processing/test_process_attestation.py +++ b/test_libs/pyspec/tests/block_processing/test_process_attestation.py @@ -10,6 +10,7 @@ from eth2spec.phase1.spec import ( get_current_epoch, process_attestation, slot_to_epoch, + state_transition, ) from tests.helpers import ( build_empty_block_for_next_slot, diff --git a/test_libs/pyspec/tests/block_processing/test_process_block_header.py b/test_libs/pyspec/tests/block_processing/test_process_block_header.py index 65de39620..bb9deab28 100644 --- a/test_libs/pyspec/tests/block_processing/test_process_block_header.py +++ b/test_libs/pyspec/tests/block_processing/test_process_block_header.py @@ -4,11 +4,11 @@ import pytest from eth2spec.phase1.spec import ( get_beacon_proposer_index, - cache_state, - advance_slot, + process_slot, process_block_header, ) from tests.helpers import ( + advance_slot, build_empty_block_for_next_slot, next_slot, ) @@ -18,7 +18,7 @@ pytestmark = pytest.mark.header def prepare_state_for_header_processing(state): - cache_state(state) + process_slot(state) advance_slot(state) diff --git a/test_libs/pyspec/tests/epoch_processing/test_process_crosslinks.py b/test_libs/pyspec/tests/epoch_processing/test_process_crosslinks.py index 26720028d..b2202255f 100644 --- a/test_libs/pyspec/tests/epoch_processing/test_process_crosslinks.py +++ b/test_libs/pyspec/tests/epoch_processing/test_process_crosslinks.py @@ -3,13 +3,16 @@ import pytest import eth2spec.phase1.spec as spec +<<<<<<< HEAD from eth2spec.phase1.state_transition import ( state_transition, ) from eth2spec.phase1.spec import ( cache_state, + process_slot, get_crosslink_deltas, process_crosslinks, + state_transition, ) from tests.helpers import ( add_attestation_to_state, @@ -35,7 +38,7 @@ def run_process_crosslinks(state, valid=True): state_transition(state, block) # cache state before epoch transition - cache_state(state) + process_slot(state) post_state = deepcopy(state) process_crosslinks(post_state) diff --git a/test_libs/pyspec/tests/helpers.py b/test_libs/pyspec/tests/helpers.py index 0e0d8659b..ed08013f1 100644 --- a/test_libs/pyspec/tests/helpers.py +++ b/test_libs/pyspec/tests/helpers.py @@ -2,10 +2,14 @@ from copy import deepcopy from py_ecc import bls +<<<<<<< HEAD from eth2spec.phase1.state_transition import ( state_transition, ) import eth2spec.phase1.spec as spec +======= +import eth2spec.phase0.spec as spec +>>>>>>> dev from eth2spec.utils.minimal_ssz import signing_root from eth2spec.phase1.spec import ( # constants @@ -40,6 +44,7 @@ from eth2spec.phase1.spec import ( get_shard_delta, hash_tree_root, slot_to_epoch, + state_transition, verify_merkle_branch, hash, ) @@ -55,6 +60,10 @@ pubkeys = [bls.privtopub(privkey) for privkey in privkeys] pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys, pubkeys)} +def advance_slot(state) -> None: + state.slot += 1 + + def get_balance(state, index): return state.balances[index] diff --git a/test_libs/pyspec/tests/test_finality.py b/test_libs/pyspec/tests/test_finality.py index 46f08cfbb..20590154b 100644 --- a/test_libs/pyspec/tests/test_finality.py +++ b/test_libs/pyspec/tests/test_finality.py @@ -7,6 +7,7 @@ import eth2spec.phase1.spec as spec from eth2spec.phase1.state_transition import ( state_transition, ) + from .helpers import ( build_empty_block_for_next_slot, fill_aggregate_attestation, @@ -67,7 +68,7 @@ def next_epoch_with_attestations(state, fill_aggregate_attestation(post_state, prev_attestation) block.body.attestations.append(prev_attestation) - state_transition(post_state, block) + spec.state_transition(post_state, block) blocks.append(block) return state, blocks, post_state diff --git a/test_libs/pyspec/tests/test_sanity.py b/test_libs/pyspec/tests/test_sanity.py index ea2d0cdc6..33a6f7221 100644 --- a/test_libs/pyspec/tests/test_sanity.py +++ b/test_libs/pyspec/tests/test_sanity.py @@ -20,13 +20,13 @@ from eth2spec.phase1.spec import ( get_block_root_at_slot, get_current_epoch, get_domain, - advance_slot, - cache_state, + process_slot, verify_merkle_branch, hash, ) from eth2spec.phase1.state_transition import ( state_transition, + hash, ) from eth2spec.utils.merkle_minimal import ( calc_merkle_tree_from_leaves, @@ -34,6 +34,7 @@ from eth2spec.utils.merkle_minimal import ( get_merkle_root, ) from .helpers import ( + advance_slot, get_balance, build_deposit_data, build_empty_block_for_next_slot, @@ -54,7 +55,7 @@ pytestmark = pytest.mark.sanity def test_slot_transition(state): test_state = deepcopy(state) - cache_state(test_state) + process_slot(test_state) advance_slot(test_state) assert test_state.slot == state.slot + 1 assert get_state_root(test_state, state.slot) == state.hash_tree_root()