diff --git a/Makefile b/Makefile index 0d9fd9ae5f..c779c5e8ea 100644 --- a/Makefile +++ b/Makefile @@ -118,7 +118,9 @@ $(PY_SPEC_PHASE_0_TARGETS): $(PY_SPEC_PHASE_0_DEPS) python3 $(SCRIPT_DIR)/build_spec.py -p0 $(PHASE0_SPEC_DIR)/beacon-chain.md $(PHASE0_SPEC_DIR)/fork-choice.md $(PHASE0_SPEC_DIR)/validator.md $@ $(PY_SPEC_DIR)/eth2spec/phase1/spec.py: $(PY_SPEC_PHASE_1_DEPS) - python3 $(SCRIPT_DIR)/build_spec.py -p1 $(PHASE0_SPEC_DIR)/beacon-chain.md $(PHASE0_SPEC_DIR)/fork-choice.md $(SSZ_DIR)/merkle-proofs.md $(PHASE1_SPEC_DIR)/custody-game.md $(PHASE1_SPEC_DIR)/shard-data-chains.md $(PHASE1_SPEC_DIR)/beacon-chain-misc.md $@ + python3 $(SCRIPT_DIR)/build_spec.py -p1 $(PHASE0_SPEC_DIR)/beacon-chain.md $(PHASE0_SPEC_DIR)/fork-choice.md $(PHASE1_SPEC_DIR)/custody-game.md $(PHASE1_SPEC_DIR)/beacon-chain.md $(PHASE1_SPEC_DIR)/fraud-proofs.md $(PHASE1_SPEC_DIR)/fork-choice.md $(PHASE1_SPEC_DIR)/phase1-fork.md $@ + +# TODO: also build validator spec and light-client-sync CURRENT_DIR = ${CURDIR} diff --git a/README.md b/README.md index d08cc0bb5f..49d8511976 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,13 @@ Core specifications for Eth2 clients be found in [specs/](specs/). These are div * [Fork Choice](specs/phase0/fork-choice.md) * [Deposit Contract](specs/phase0/deposit-contract.md) * [Honest Validator](specs/phase0/validator.md) +* [P2P Networking](specs/phase0/p2p-interface.md) ### Phase 1 +* [From Phase 0 to Phase 1](specs/phase1/phase1-fork.md) +* [The Beacon Chain for Shards](specs/phase1/beacon-chain.md) * [Custody Game](specs/phase1/custody-game.md) -* [Shard Data Chains](specs/phase1/shard-data-chains.md) -* [Misc beacon chain updates](specs/phase1/beacon-chain-misc.md) +* [Shard Transition and Fraud Proofs](specs/phase1/fraud-proofs.md) * [Light client syncing protocol](specs/phase1/light-client-sync.md) ### Phase 2 diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 0ff217dd84..6eb5641d09 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -94,9 +94,6 @@ PERSISTENT_COMMITTEE_PERIOD: 2048 MAX_EPOCHS_PER_CROSSLINK: 64 # 2**2 (= 4) epochs 25.6 minutes MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4 -# 2**14 (= 16,384) epochs ~73 days -EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 16384 - # State vector lengths @@ -146,6 +143,73 @@ DOMAIN_BEACON_ATTESTER: 0x01000000 DOMAIN_RANDAO: 0x02000000 DOMAIN_DEPOSIT: 0x03000000 DOMAIN_VOLUNTARY_EXIT: 0x04000000 -DOMAIN_CUSTODY_BIT_CHALLENGE: 0x06000000 -DOMAIN_SHARD_PROPOSER: 0x80000000 -DOMAIN_SHARD_ATTESTER: 0x81000000 +# Phase 1 +DOMAIN_SHARD_PROPOSAL: 0x80000000 +DOMAIN_SHARD_COMMITTEE: 0x81000000 +DOMAIN_LIGHT_CLIENT: 0x82000000 +DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000 + + +# Phase 1: Upgrade from Phase 0 +# --------------------------------------------------------------- +PHASE_1_FORK_VERSION: 0x01000000 +INITIAL_ACTIVE_SHARDS: 64 +# Placeholder +INITIAL_GASPRICE: 10 + + +# Phase 1: General +# --------------------------------------------------------------- +# 2**10` (= 1024) +MAX_SHARDS: 1024 +# 2**3 (= 8) | online epochs | ~51 min +ONLINE_PERIOD: 8 +# 2**7 (= 128) +LIGHT_CLIENT_COMMITTEE_SIZE: 128 +# 2**8 (= 256) | epochs | ~27 hours +LIGHT_CLIENT_COMMITTEE_PERIOD: 256 +# 2**8 (= 256) | epochs | ~27 hours +SHARD_COMMITTEE_PERIOD: 256 +# 2**18 (= 262,144) +SHARD_BLOCK_CHUNK_SIZE: 262144 +# 2**2 (= 4) +MAX_SHARD_BLOCK_CHUNKS: 4 +# 3 * 2**16` (= 196,608) +TARGET_SHARD_BLOCK_SIZE: 196608 +# Note: MAX_SHARD_BLOCKS_PER_ATTESTATION is derived from the list length. +SHARD_BLOCK_OFFSETS: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233] +# len(SHARD_BLOCK_OFFSETS) +MAX_SHARD_BLOCKS_PER_ATTESTATION: 12 +# 2**14 (= 16,384) Gwei +MAX_GASPRICE: 16384 +# 2**5 (= 32) Gwei +MIN_GASPRICE: 32 +# 2**3 (= 8) +GASPRICE_ADJUSTMENT_COEFFICIENT: 8 + + +# Phase 1: Custody Game +# --------------------------------------------------------------- + +# Time parameters +# 2**1 (= 2) epochs, 12.8 minutes +RANDAO_PENALTY_EPOCHS: 2 +# 2**14 (= 16,384) epochs ~73 days +EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 16384 +# 2**11 (= 2,048) epochs, ~9 days +EPOCHS_PER_CUSTODY_PERIOD: 2048 +# 2**11 (= 2,048) epochs, ~9 days +CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048 +# 2**7 (= 128) epochs, ~14 hours +MAX_REVEAL_LATENESS_DECREMENT: 128 + +# Max operations +# 2**8 (= 256) +MAX_CUSTODY_KEY_REVEALS: 256 +MAX_EARLY_DERIVED_SECRET_REVEALS: 1 +MAX_CUSTODY_SLASHINGS: 1 + +# Reward and penalty quotients +EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE: 2 +# 2**8 (= 256) +MINOR_REWARD_QUOTIENT: 256 diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 6a8b827090..03ffa90e36 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -21,14 +21,13 @@ MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 64 # Jan 3, 2020 MIN_GENESIS_TIME: 1578009600 -# -# + # Fork Choice # --------------------------------------------------------------- # 2**1 (= 1) SAFE_SLOTS_TO_UPDATE_JUSTIFIED: 2 -# + # Validator # --------------------------------------------------------------- # [customized] process deposits more quickly, but insecure @@ -88,18 +87,12 @@ SLOTS_PER_ETH1_VOTING_PERIOD: 16 SLOTS_PER_HISTORICAL_ROOT: 64 # 2**8 (= 256) epochs MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256 -# 2**11 (= 2,048) epochs -PERSISTENT_COMMITTEE_PERIOD: 2048 +# [customized] higher frequency of committee turnover and faster time to acceptable voluntary exit +PERSISTENT_COMMITTEE_PERIOD: 128 # [customized] fast catchup crosslinks MAX_EPOCHS_PER_CROSSLINK: 4 # 2**2 (= 4) epochs MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4 -# [customized] 2**12 (= 4,096) epochs -EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096 -# 2**2 (= 4) epochs -EPOCHS_PER_CUSTODY_PERIOD: 4 -# 2**2 (= 4) epochs -CUSTODY_PERIOD_TO_RANDAO_PADDING: 4 # State vector lengths @@ -149,16 +142,75 @@ DOMAIN_BEACON_ATTESTER: 0x01000000 DOMAIN_RANDAO: 0x02000000 DOMAIN_DEPOSIT: 0x03000000 DOMAIN_VOLUNTARY_EXIT: 0x04000000 -DOMAIN_CUSTODY_BIT_CHALLENGE: 0x06000000 -DOMAIN_SHARD_PROPOSER: 0x80000000 -DOMAIN_SHARD_ATTESTER: 0x81000000 +# Phase 1 +DOMAIN_SHARD_PROPOSAL: 0x80000000 +DOMAIN_SHARD_COMMITTEE: 0x81000000 +DOMAIN_LIGHT_CLIENT: 0x82000000 +DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000 -# Phase 1 +# Phase 1: Upgrade from Phase 0 +# --------------------------------------------------------------- +# [customized] for testnet distinction +PHASE_1_FORK_VERSION: 0x01000001 +# [customized] reduced for testing +INITIAL_ACTIVE_SHARDS: 4 +# Placeholder +INITIAL_GASPRICE: 10 + + +# Phase 1: General +# --------------------------------------------------------------- +# [customized] reduced for testing +MAX_SHARDS: 8 +# 2**3 (= 8) | online epochs +ONLINE_PERIOD: 8 +# 2**7 (= 128) +LIGHT_CLIENT_COMMITTEE_SIZE: 128 +# 2**8 (= 256) | epochs +LIGHT_CLIENT_COMMITTEE_PERIOD: 256 +# 2**8 (= 256) | epochs +SHARD_COMMITTEE_PERIOD: 256 +# 2**18 (= 262,144) +SHARD_BLOCK_CHUNK_SIZE: 262144 +# 2**2 (= 4) +MAX_SHARD_BLOCK_CHUNKS: 4 +# 3 * 2**16` (= 196,608) +TARGET_SHARD_BLOCK_SIZE: 196608 +# Note: MAX_SHARD_BLOCKS_PER_ATTESTATION is derived from the list length. +SHARD_BLOCK_OFFSETS: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233] +# len(SHARD_BLOCK_OFFSETS) +MAX_SHARD_BLOCKS_PER_ATTESTATION: 12 +# 2**14 (= 16,384) Gwei +MAX_GASPRICE: 16384 +# 2**5 (= 32) Gwei +MIN_GASPRICE: 32 +# 2**3 (= 8) +GASPRICE_ADJUSTMENT_COEFFICIENT: 8 + + +# Phase 1: Custody Game # --------------------------------------------------------------- -SHARD_SLOTS_PER_BEACON_SLOT: 2 -EPOCHS_PER_SHARD_PERIOD: 4 -# PHASE_1_FORK_EPOCH >= EPOCHS_PER_SHARD_PERIOD * 2 -PHASE_1_FORK_EPOCH: 8 -# PHASE_1_FORK_SLOT = PHASE_1_FORK_EPOCH * SLOTS_PER_EPOCH -PHASE_1_FORK_SLOT: 64 + +# Time parameters +# 2**1 (= 2) epochs +RANDAO_PENALTY_EPOCHS: 2 +# [customized] quicker for testing +EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096 +# 2**11 (= 2,048) epochs +EPOCHS_PER_CUSTODY_PERIOD: 2048 +# 2**11 (= 2,048) epochs +CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048 +# 2**7 (= 128) epochs +MAX_REVEAL_LATENESS_DECREMENT: 128 + +# Max operations +# 2**8 (= 256) +MAX_CUSTODY_KEY_REVEALS: 256 +MAX_EARLY_DERIVED_SECRET_REVEALS: 1 +MAX_CUSTODY_SLASHINGS: 1 + +# Reward and penalty quotients +EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE: 2 +# 2**8 (= 256) +MINOR_REWARD_QUOTIENT: 256 diff --git a/scripts/build_spec.py b/scripts/build_spec.py index 114832bc2a..90e9b3fb43 100644 --- a/scripts/build_spec.py +++ b/scripts/build_spec.py @@ -9,9 +9,13 @@ Optional, ) +CONFIG_LOADER = ''' +apply_constants_preset(globals()) +''' -PHASE0_IMPORTS = '''from typing import ( - Any, Dict, Set, Sequence, Tuple, Optional, TypeVar +PHASE0_IMPORTS = '''from eth2spec.config.apply_config import apply_constants_preset +from typing import ( + Any, Callable, Dict, Set, Sequence, Tuple, Optional, TypeVar ) from dataclasses import ( @@ -30,11 +34,10 @@ SSZObject = TypeVar('SSZObject', bound=SSZType) ''' -PHASE1_IMPORTS = '''from typing import ( - Any, Dict, Set, Sequence, MutableSequence, NewType, Tuple, Union, TypeVar -) -from math import ( - log2, +PHASE1_IMPORTS = '''from eth2spec.phase0 import spec as phase0 +from eth2spec.config.apply_config import apply_constants_preset +from typing import ( + Any, Callable, Dict, Set, Sequence, NewType, Tuple, TypeVar ) from dataclasses import ( @@ -42,15 +45,11 @@ field, ) -from eth2spec.utils.ssz.ssz_impl import ( - hash_tree_root, - is_zero, -) +from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.utils.ssz.ssz_typing import ( - BasicValue, Elements, BaseBytes, BaseList, SSZType, - Container, List, Vector, ByteList, ByteVector, Bitlist, Bitvector, Bits, + SSZType, Container, List, Vector, ByteList, ByteVector, Bitlist, Bitvector, Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, - uint64, bit, boolean, byte, + uint64, uint8, bit, boolean, ) from eth2spec.utils import bls @@ -94,51 +93,15 @@ def compute_committee(indices: Sequence[ValidatorIndex], # type: ignore if param_hash not in committee_cache: committee_cache[param_hash] = _compute_committee(indices, seed, index, count) - return committee_cache[param_hash] - - -# Access to overwrite spec constants based on configuration -def apply_constants_preset(preset: Dict[str, Any]) -> None: - global_vars = globals() - for k, v in preset.items(): - if k.startswith('DOMAIN_'): - global_vars[k] = DomainType(v) # domain types are defined as bytes in the configs - else: - global_vars[k] = v - - # Deal with derived constants - global_vars['GENESIS_EPOCH'] = compute_epoch_at_slot(GENESIS_SLOT) - - # Initialize SSZ types again, to account for changed lengths - init_SSZ_types() -''' - - -def remove_for_phase1(functions: Dict[str, str]): - for key, value in functions.items(): - lines = value.split("\n") - lines = filter(lambda s: "[to be removed in phase 1]" not in s, lines) - functions[key] = "\n".join(lines) - - -def strip_comments(raw: str) -> str: - comment_line_regex = re.compile(r'^\s+# ') - lines = raw.split('\n') - out = [] - for line in lines: - if not comment_line_regex.match(line): - if ' #' in line: - line = line[:line.index(' #')] - out.append(line) - return '\n'.join(out) + return committee_cache[param_hash]''' def objects_to_spec(functions: Dict[str, str], custom_types: Dict[str, str], constants: Dict[str, str], ssz_objects: Dict[str, str], - inserts: Dict[str, str], imports: Dict[str, str], + version: str, ) -> str: """ Given all the objects that constitute a spec, combine them into a single pyfile. @@ -160,27 +123,18 @@ def objects_to_spec(functions: Dict[str, str], constants[k] += " # noqa: E501" constants_spec = '\n'.join(map(lambda x: '%s = %s' % (x, constants[x]), constants)) ssz_objects_instantiation_spec = '\n\n'.join(ssz_objects.values()) - ssz_objects_reinitialization_spec = ( - 'def init_SSZ_types() -> None:\n global_vars = globals()\n\n ' - + '\n\n '.join([strip_comments(re.sub(r'(?!\n\n)\n', r'\n ', value[:-1])) - for value in ssz_objects.values()]) - + '\n\n' - + '\n'.join(map(lambda x: ' global_vars[\'%s\'] = %s' % (x, x), ssz_objects.keys())) - ) spec = ( imports + + '\n\n' + f"version = \'{version}\'\n" + '\n\n' + new_type_definitions + '\n' + SUNDRY_CONSTANTS_FUNCTIONS + '\n\n' + constants_spec - + '\n\n\n' + ssz_objects_instantiation_spec + + '\n\n' + CONFIG_LOADER + + '\n\n' + ssz_objects_instantiation_spec + '\n\n' + functions_spec + '\n' + SUNDRY_FUNCTIONS - + '\n\n' + ssz_objects_reinitialization_spec + '\n' ) - # Handle @inserts - for key, value in inserts.items(): - spec = re.sub('[ ]*# %s\\n' % key, value, spec) return spec @@ -197,10 +151,10 @@ def combine_constants(old_constants: Dict[str, str], new_constants: Dict[str, st ignored_dependencies = [ - 'bit', 'boolean', 'Vector', 'List', 'Container', 'Hash', 'BLSPubkey', 'BLSSignature', 'ByteList', 'ByteVector' + 'bit', 'boolean', 'Vector', 'List', 'Container', 'BLSPubkey', 'BLSSignature', 'Bytes1', 'Bytes4', 'Bytes32', 'Bytes48', 'Bytes96', 'Bitlist', 'Bitvector', 'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256', - 'bytes', 'byte', 'ByteVector' # to be removed after updating spec doc + 'bytes', 'byte', 'ByteList', 'ByteVector' ] @@ -233,32 +187,26 @@ def combine_ssz_objects(old_objects: Dict[str, str], new_objects: Dict[str, str] and returns the newer versions of the objects in dependency order. """ for key, value in new_objects.items(): - if key in old_objects: - # remove trailing newline - old_objects[key] = old_objects[key] - # remove leading variable name - value = re.sub(r'^class [\w]*\(Container\):\n', '', value) - old_objects[key] = old_objects.get(key, '') + value - dependency_order_ssz_objects(old_objects, custom_types) + old_objects[key] = value return old_objects -# inserts are handled the same way as functions -combine_inserts = combine_functions - - def combine_spec_objects(spec0: SpecObject, spec1: SpecObject) -> SpecObject: """ Takes in two spec variants (as tuples of their objects) and combines them using the appropriate combiner function. """ - functions0, custom_types0, constants0, ssz_objects0, inserts0 = spec0 - functions1, custom_types1, constants1, ssz_objects1, inserts1 = spec1 + functions0, custom_types0, constants0, ssz_objects0 = spec0 + functions1, custom_types1, constants1, ssz_objects1 = spec1 functions = combine_functions(functions0, functions1) custom_types = combine_constants(custom_types0, custom_types1) constants = combine_constants(constants0, constants1) ssz_objects = combine_ssz_objects(ssz_objects0, ssz_objects1, custom_types) - inserts = combine_inserts(inserts0, inserts1) - return functions, custom_types, constants, ssz_objects, inserts + return functions, custom_types, constants, ssz_objects + + +def dependency_order_spec(objs: SpecObject): + functions, custom_types, constants, ssz_objects = objs + dependency_order_ssz_objects(ssz_objects, custom_types) def build_phase0_spec(phase0_sourcefile: str, fork_choice_sourcefile: str, @@ -269,7 +217,8 @@ def build_phase0_spec(phase0_sourcefile: str, fork_choice_sourcefile: str, spec_objects = phase0_spec for value in [fork_choice_spec, v_guide]: spec_objects = combine_spec_objects(spec_objects, value) - spec = objects_to_spec(*spec_objects, PHASE0_IMPORTS) + dependency_order_spec(spec_objects) + spec = objects_to_spec(*spec_objects, PHASE0_IMPORTS, 'phase0') if outfile is not None: with open(outfile, 'w') as out: out.write(spec) @@ -278,26 +227,27 @@ def build_phase0_spec(phase0_sourcefile: str, fork_choice_sourcefile: str, def build_phase1_spec(phase0_beacon_sourcefile: str, phase0_fork_choice_sourcefile: str, - merkle_proofs_sourcefile: str, phase1_custody_sourcefile: str, - phase1_shard_sourcefile: str, - phase1_beacon_misc_sourcefile: str, + phase1_beacon_sourcefile: str, + phase1_fraud_sourcefile: str, + phase1_fork_choice_sourcefile: str, + phase1_fork_sourcefile: str, outfile: str=None) -> Optional[str]: all_sourcefiles = ( phase0_beacon_sourcefile, phase0_fork_choice_sourcefile, - merkle_proofs_sourcefile, phase1_custody_sourcefile, - phase1_shard_sourcefile, - phase1_beacon_misc_sourcefile, + phase1_beacon_sourcefile, + phase1_fraud_sourcefile, + phase1_fork_choice_sourcefile, + phase1_fork_sourcefile, ) all_spescs = [get_spec(spec) for spec in all_sourcefiles] - for spec in all_spescs: - remove_for_phase1(spec[0]) spec_objects = all_spescs[0] for value in all_spescs[1:]: spec_objects = combine_spec_objects(spec_objects, value) - spec = objects_to_spec(*spec_objects, PHASE1_IMPORTS) + dependency_order_spec(spec_objects) + spec = objects_to_spec(*spec_objects, PHASE1_IMPORTS, 'phase1') if outfile is not None: with open(outfile, 'w') as out: out.write(spec) @@ -316,11 +266,12 @@ def build_phase1_spec(phase0_beacon_sourcefile: str, If building phase 1: 1st argument is input phase0/beacon-chain.md 2nd argument is input phase0/fork-choice.md - 3rd argument is input ssz/merkle-proofs.md - 4th argument is input phase1/custody-game.md - 5th argument is input phase1/shard-data-chains.md - 6th argument is input phase1/beacon-chain-misc.md - 7th argument is output spec.py + 3rd argument is input phase1/custody-game.md + 4th argument is input phase1/beacon-chain.md + 5th argument is input phase1/fraud-proofs.md + 6th argument is input phase1/fork-choice.md + 7th argument is input phase1/phase1-fork.md + 8th argument is output spec.py ''' parser = ArgumentParser(description=description) parser.add_argument("-p", "--phase", dest="phase", type=int, default=0, help="Build for phase #") @@ -333,14 +284,13 @@ def build_phase1_spec(phase0_beacon_sourcefile: str, else: print(" Phase 0 requires spec, forkchoice, and v-guide inputs as well as an output file.") elif args.phase == 1: - if len(args.files) == 7: + if len(args.files) == 8: build_phase1_spec(*args.files) else: print( " Phase 1 requires input files as well as an output file:\n" "\t phase0: (beacon-chain.md, fork-choice.md)\n" - "\t ssz: (merkle-proofs.md)\n" - "\t phase1: (custody-game.md, shard-data-chains.md, beacon-chain-misc.md)\n" + "\t phase1: (custody-game.md, beacon-chain.md, fraud-proofs.md, fork-choice.md, phase1-fork.md)\n" "\t and output.py" ) else: diff --git a/scripts/function_puller.py b/scripts/function_puller.py index b30e5b75cd..1a134007e0 100644 --- a/scripts/function_puller.py +++ b/scripts/function_puller.py @@ -3,8 +3,6 @@ FUNCTION_REGEX = r'^def [\w_]*' -BEGIN_INSERT_REGEX = r'# begin insert ' -END_INSERT_REGEX = r'# end insert' SpecObject = NewType('SpecObjects', Tuple[Dict[str, str], Dict[str, str], Dict[str, str], Dict[str, str]]) @@ -15,22 +13,18 @@ def get_spec(file_name: str) -> SpecObject: functions = {function_name: function_code} constants= {constant_name: constant_code} ssz_objects= {object_name: object} - inserts= {insert_tag: code to be inserted} Note: This function makes heavy use of the inherent ordering of dicts, if this is not supported by your python version, it will not work. """ pulling_from = None # line number of start of latest object current_name = None # most recent section title - insert_name = None # stores the label of the current insert object - functions = {} - constants = {} - ssz_objects = {} - inserts = {} + functions: Dict[str, str] = {} + constants: Dict[str, str] = {} + ssz_objects: Dict[str, str] = {} function_matcher = re.compile(FUNCTION_REGEX) - inserts_matcher = re.compile(BEGIN_INSERT_REGEX) is_ssz = False - custom_types = {} + custom_types: Dict[str, str] = {} for linenum, line in enumerate(open(file_name).readlines()): line = line.rstrip() if pulling_from is None and len(line) > 0 and line[0] == '#' and line[-1] == '`': @@ -40,15 +34,6 @@ def get_spec(file_name: str) -> SpecObject: pulling_from = linenum + 1 elif line[:3] == '```': pulling_from = None - elif inserts_matcher.match(line) is not None: - # Find @insert names - insert_name = re.search(r'@[\w]*', line).group(0) - elif insert_name is not None: - # In insert mode, either the next line is more code, or the end of the insert - if re.match(END_INSERT_REGEX, line) is not None: - insert_name = None - else: - inserts[insert_name] = inserts.get(insert_name, '') + line + '\n' else: # Handle function definitions & ssz_objects if pulling_from is not None: @@ -84,4 +69,4 @@ def get_spec(file_name: str) -> SpecObject: constants[row[0]] = row[1].replace('**TBD**', '2**32') elif row[1].startswith('uint') or row[1].startswith('Bytes'): custom_types[row[0]] = row[1] - return functions, custom_types, constants, ssz_objects, inserts + return SpecObject((functions, custom_types, constants, ssz_objects)) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 7e07f4b474..410d094b0c 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1189,19 +1189,13 @@ def process_slot(state: BeaconState) -> None: ### Epoch processing -*Note*: The `# @LabelHere` lines below are placeholders to show that code will be inserted here in a future phase. - ```python def process_epoch(state: BeaconState) -> None: process_justification_and_finalization(state) process_rewards_and_penalties(state) process_registry_updates(state) - # @process_reveal_deadlines - # @process_challenge_deadlines process_slashings(state) - # @update_period_committee process_final_updates(state) - # @after_process_final_updates ``` #### Helper functions @@ -1480,16 +1474,15 @@ 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) - for operations, function in ( - (body.proposer_slashings, process_proposer_slashing), - (body.attester_slashings, process_attester_slashing), - (body.attestations, process_attestation), - (body.deposits, process_deposit), - (body.voluntary_exits, process_voluntary_exit), - # @process_shard_receipt_proofs - ): + def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: for operation in operations: - function(state, operation) + 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) ``` ##### Proposer slashings diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 0d9823fcdb..e2f24705ea 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -14,7 +14,7 @@ - [Helpers](#helpers) - [`LatestMessage`](#latestmessage) - [`Store`](#store) - - [`get_genesis_store`](#get_genesis_store) + - [`get_forkchoice_store`](#get_forkchoice_store) - [`get_slots_since_genesis`](#get_slots_since_genesis) - [`get_current_slot`](#get_current_slot) - [`compute_slots_since_epoch_start`](#compute_slots_since_epoch_start) @@ -24,6 +24,10 @@ - [`get_filtered_block_tree`](#get_filtered_block_tree) - [`get_head`](#get_head) - [`should_update_justified_checkpoint`](#should_update_justified_checkpoint) + - [`on_attestation` helpers](#on_attestation-helpers) + - [`validate_on_attestation`](#validate_on_attestation) + - [`store_target_checkpoint_state`](#store_target_checkpoint_state) + - [`update_latest_messages`](#update_latest_messages) - [Handlers](#handlers) - [`on_tick`](#on_tick) - [`on_block`](#on_block) @@ -38,7 +42,7 @@ This document is the beacon chain fork choice spec, part of Ethereum 2.0 Phase 0 ## Fork choice -The head block root associated with a `store` is defined as `get_head(store)`. At genesis, let `store = get_genesis_store(genesis_state)` and update `store` by running: +The head block root associated with a `store` is defined as `get_head(store)`. At genesis, let `store = get_checkpoint_store(genesis_state)` and update `store` by running: - `on_tick(time)` whenever `time > store.time` where `time` is the current Unix time - `on_block(block)` whenever a block `block: SignedBeaconBlock` is received @@ -79,29 +83,35 @@ class Store(object): justified_checkpoint: Checkpoint finalized_checkpoint: Checkpoint best_justified_checkpoint: Checkpoint - blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) + blocks: Dict[Root, BeaconBlockHeader] = 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) ``` -#### `get_genesis_store` +#### `get_forkchoice_store` + +The provided anchor-state will be regarded as a trusted state, to not roll back beyond. +This should be the genesis state for a full client. ```python -def get_genesis_store(genesis_state: BeaconState) -> Store: - genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state)) - root = hash_tree_root(genesis_block) - justified_checkpoint = Checkpoint(epoch=GENESIS_EPOCH, root=root) - finalized_checkpoint = Checkpoint(epoch=GENESIS_EPOCH, root=root) +def get_forkchoice_store(anchor_state: BeaconState) -> Store: + anchor_block_header = anchor_state.latest_block_header.copy() + if anchor_block_header.state_root == Bytes32(): + anchor_block_header.state_root = hash_tree_root(anchor_state) + anchor_root = hash_tree_root(anchor_block_header) + 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) return Store( - time=genesis_state.genesis_time, - genesis_time=genesis_state.genesis_time, + time=anchor_state.genesis_time, + genesis_time=anchor_state.genesis_time, justified_checkpoint=justified_checkpoint, finalized_checkpoint=finalized_checkpoint, best_justified_checkpoint=justified_checkpoint, - blocks={root: genesis_block}, - block_states={root: genesis_state.copy()}, - checkpoint_states={justified_checkpoint: genesis_state.copy()}, + blocks={anchor_root: anchor_block_header}, + block_states={anchor_root: anchor_state.copy()}, + checkpoint_states={justified_checkpoint: anchor_state.copy()}, ) ``` @@ -251,6 +261,59 @@ def should_update_justified_checkpoint(store: Store, new_justified_checkpoint: C return True ``` +#### `on_attestation` helpers + +##### `validate_on_attestation` + +```python +def validate_on_attestation(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 + assert target.epoch in [current_epoch, previous_epoch] + 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 cannot be from future epochs. If they are, delay consideration until the epoch arrives + assert get_current_slot(store) >= compute_start_slot_at_epoch(target.epoch) + + # Attestations must be for a known block. If block is unknown, delay consideration until the block is found + assert attestation.data.beacon_block_root in store.blocks + # 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 + + # 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 +``` + +##### `store_target_checkpoint_state` + +```python +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 = store.block_states[target.root].copy() + process_slots(base_state, compute_start_slot_at_epoch(target.epoch)) + store.checkpoint_states[target] = base_state +``` + +##### `update_latest_messages` + +```python +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) +``` + + ### Handlers #### `on_tick` @@ -317,42 +380,14 @@ def on_attestation(store: Store, attestation: Attestation) -> None: An ``attestation`` that is asserted as invalid may be valid at a later time, consider scheduling it for later processing in such case. """ - 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 - assert target.epoch in [current_epoch, previous_epoch] - 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 cannot be from future epochs. If they are, delay consideration until the epoch arrives - base_state = store.block_states[target.root].copy() - assert get_current_slot(store) >= compute_start_slot_at_epoch(target.epoch) + validate_on_attestation(store, attestation) + store_target_checkpoint_state(store, attestation.data.target) - # 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 - - # Store target checkpoint state if not yet seen - if target not in store.checkpoint_states: - process_slots(base_state, compute_start_slot_at_epoch(target.epoch)) - store.checkpoint_states[target] = base_state - target_state = store.checkpoint_states[target] - - # 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 - - # Get state at the `target` to validate attestation and calculate the committees + # 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 i in indexed_attestation.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=attestation.data.beacon_block_root) + # Update latest messages for attesting indices + update_latest_messages(store, indexed_attestation.attesting_indices, attestation) ``` diff --git a/specs/phase1/beacon-chain-misc.md b/specs/phase1/beacon-chain-misc.md deleted file mode 100644 index b27c72b347..0000000000 --- a/specs/phase1/beacon-chain-misc.md +++ /dev/null @@ -1,254 +0,0 @@ -# Phase 1 miscellaneous beacon chain changes - -## Table of contents - - - - - - -- [Configuration](#configuration) -- [Containers](#containers) - - [`CompactCommittee`](#compactcommittee) - - [`ShardReceiptDelta`](#shardreceiptdelta) - - [`ShardReceiptProof`](#shardreceiptproof) -- [Helper functions](#helper-functions) - - [`pack_compact_validator`](#pack_compact_validator) - - [`unpack_compact_validator`](#unpack_compact_validator) - - [`committee_to_compact_committee`](#committee_to_compact_committee) - - [`verify_merkle_proof`](#verify_merkle_proof) - - [`compute_historical_state_generalized_index`](#compute_historical_state_generalized_index) - - [`get_generalized_index_of_crosslink_header`](#get_generalized_index_of_crosslink_header) - - [`process_shard_receipt_proof`](#process_shard_receipt_proof) -- [Changes](#changes) - - [Phase 0 container updates](#phase-0-container-updates) - - [`BeaconState`](#beaconstate) - - [`BeaconBlockBody`](#beaconblockbody) - - [Persistent committees](#persistent-committees) - - [Shard receipt processing](#shard-receipt-processing) - - - - -## Configuration - -| Name | Value | Unit | Duration -| - | - | - | - | -| `MAX_SHARD_RECEIPT_PROOFS` | `2**0` (= 1) | - | - | -| `PERIOD_COMMITTEE_ROOT_LENGTH` | `2**8` (= 256) | periods | ~9 months | -| `MINOR_REWARD_QUOTIENT` | `2**8` (=256) | - | - | -| `REWARD_COEFFICIENT_BASE` | **TBD** | - | - | - -## Containers - -#### `CompactCommittee` - -```python -class CompactCommittee(Container): - pubkeys: List[BLSPubkey, MAX_VALIDATORS_PER_COMMITTEE] - compact_validators: List[uint64, MAX_VALIDATORS_PER_COMMITTEE] -``` - -#### `ShardReceiptDelta` - -```python -class ShardReceiptDelta(Container): - index: ValidatorIndex - reward_coefficient: uint64 - block_fee: Gwei -``` - - -#### `ShardReceiptProof` - -```python -class ShardReceiptProof(Container): - shard: Shard - proof: List[Bytes32, PLACEHOLDER] - receipt: List[ShardReceiptDelta, PLACEHOLDER] -``` - -## Helper functions - -#### `pack_compact_validator` - -```python -def pack_compact_validator(index: int, slashed: bool, balance_in_increments: int) -> int: - """ - Creates a compact validator object representing index, slashed status, and compressed balance. - Takes as input balance-in-increments (// EFFECTIVE_BALANCE_INCREMENT) to preserve symmetry with - the unpacking function. - """ - return (index << 16) + (slashed << 15) + balance_in_increments -``` - -#### `unpack_compact_validator` - -```python -def unpack_compact_validator(compact_validator: int) -> Tuple[int, bool, int]: - """ - Returns validator index, slashed, balance // EFFECTIVE_BALANCE_INCREMENT - """ - return compact_validator >> 16, bool((compact_validator >> 15) % 2), compact_validator & (2**15 - 1) -``` - -#### `committee_to_compact_committee` - -```python -def committee_to_compact_committee(state: BeaconState, committee: Sequence[ValidatorIndex]) -> CompactCommittee: - """ - Given a state and a list of validator indices, outputs the CompactCommittee representing them. - """ - validators = [state.validators[i] for i in committee] - compact_validators = [ - pack_compact_validator(i, v.slashed, v.effective_balance // EFFECTIVE_BALANCE_INCREMENT) - for i, v in zip(committee, validators) - ] - pubkeys = [v.pubkey for v in validators] - return CompactCommittee(pubkeys=pubkeys, compact_validators=compact_validators) -``` - -#### `verify_merkle_proof` - -```python -def verify_merkle_proof(leaf: Bytes32, proof: Sequence[Bytes32], index: GeneralizedIndex, root: Root) -> bool: - assert len(proof) == get_generalized_index_length(index) - for i, h in enumerate(proof): - if get_generalized_index_bit(index, i): - leaf = hash(h + leaf) - else: - leaf = hash(leaf + h) - return leaf == root -``` - -#### `compute_historical_state_generalized_index` - -```python -def compute_historical_state_generalized_index(earlier: ShardSlot, later: ShardSlot) -> GeneralizedIndex: - """ - Computes the generalized index of the state root of slot `earlier` based on the state root of slot `later`. - Relies on the `history_accumulator` in the `ShardState`, where `history_accumulator[i]` maintains the most - recent 2**i'th slot state. Works by tracing a `log(later-earlier)` step path from `later` to `earlier` - through intermediate blocks at the next available multiples of descending powers of two. - """ - o = GeneralizedIndex(1) - for i in range(HISTORY_ACCUMULATOR_DEPTH - 1, -1, -1): - if (later - 1) & 2**i > (earlier - 1) & 2**i: - later = later - ((later - 1) % 2**i) - 1 - gindex = GeneralizedIndex(get_generalized_index(ShardState, ['history_accumulator', i])) - o = concat_generalized_indices(o, gindex) - return o -``` - -#### `get_generalized_index_of_crosslink_header` - -```python -def get_generalized_index_of_crosslink_header(index: int) -> GeneralizedIndex: - """ - Gets the generalized index for the root of the index'th header in a crosslink. - """ - MAX_CROSSLINK_SIZE = ( - MAX_SHARD_BLOCK_SIZE * SHARD_SLOTS_PER_EPOCH * MAX_EPOCHS_PER_CROSSLINK - ) - assert MAX_CROSSLINK_SIZE == get_previous_power_of_two(MAX_CROSSLINK_SIZE) - return GeneralizedIndex(MAX_CROSSLINK_SIZE // SHARD_HEADER_SIZE + index) -``` - -#### `process_shard_receipt_proof` - -```python -def process_shard_receipt_proof(state: BeaconState, receipt_proof: ShardReceiptProof) -> None: - """ - Processes a ShardReceipt object. - """ - receipt_slot = ( - state.next_shard_receipt_period[receipt_proof.shard] * - SHARD_SLOTS_PER_EPOCH * EPOCHS_PER_SHARD_PERIOD - ) - first_slot_in_last_crosslink = state.current_crosslinks[receipt_proof.shard].start_epoch * SHARD_SLOTS_PER_EPOCH - gindex = concat_generalized_indices( - get_generalized_index_of_crosslink_header(0), - GeneralizedIndex(get_generalized_index(ShardBlockHeader, 'state_root')), - compute_historical_state_generalized_index(receipt_slot, first_slot_in_last_crosslink), - GeneralizedIndex(get_generalized_index(ShardState, 'receipt_root')) - ) - assert verify_merkle_proof( - leaf=hash_tree_root(receipt_proof.receipt), - proof=receipt_proof.proof, - index=gindex, - root=state.current_crosslinks[receipt_proof.shard].data_root - ) - for delta in receipt_proof.receipt: - if get_current_epoch(state) < state.validators[delta.index].withdrawable_epoch: - increase_amount = ( - state.validators[delta.index].effective_balance * delta.reward_coefficient // REWARD_COEFFICIENT_BASE - ) - increase_balance(state, delta.index, increase_amount) - decrease_balance(state, delta.index, delta.block_fee) - state.next_shard_receipt_period[receipt_proof.shard] += 1 - proposer_index = get_beacon_proposer_index(state) - increase_balance(state, proposer_index, Gwei(get_base_reward(state, proposer_index) // MINOR_REWARD_QUOTIENT)) -``` - -## Changes - -### Phase 0 container updates - -Add the following fields to the end of the specified container objects. - -#### `BeaconState` - -```python -class BeaconState(Container): - # Period committees - period_committee_roots: Vector[Root, PERIOD_COMMITTEE_ROOT_LENGTH] - next_shard_receipt_period: Vector[uint64, SHARD_COUNT] -``` - -`period_committee_roots` values are initialized to `Bytes32()` (empty bytes value). -`next_shard_receipt_period` values are initialized to `compute_epoch_at_slot(PHASE_1_FORK_SLOT) // EPOCHS_PER_SHARD_PERIOD`. - -#### `BeaconBlockBody` - -```python -class BeaconBlockBody(Container): - shard_receipt_proofs: List[ShardReceiptProof, MAX_SHARD_RECEIPT_PROOFS] -``` - -`shard_receipt_proofs` is initialized to `[]`. - -### Persistent committees - -Run `update_period_committee` immediately before `process_final_updates`: - -```python -# begin insert @update_period_committee - update_period_committee(state) -# end insert @update_period_committee -def update_period_committee(state: BeaconState) -> None: - """ - Updates period committee roots at boundary blocks. - """ - if (get_current_epoch(state) + 1) % EPOCHS_PER_SHARD_PERIOD != 0: - return - - period = (get_current_epoch(state) + 1) // EPOCHS_PER_SHARD_PERIOD - committees = Vector[CompactCommittee, SHARD_COUNT]([ - committee_to_compact_committee( - state, - get_period_committee(state, Shard(shard), Epoch(get_current_epoch(state) + 1)), - ) - for shard in range(SHARD_COUNT) - ]) - state.period_committee_roots[period % PERIOD_COMMITTEE_ROOT_LENGTH] = hash_tree_root(committees) -``` - -### Shard receipt processing - -Run `process_shard_receipt_proof` on each `ShardReceiptProof` during block processing. - -```python -# begin insert @process_shard_receipt_proofs - (body.shard_receipt_proofs, process_shard_receipt_proof), -# end insert @process_shard_receipt_proofs -``` diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md new file mode 100644 index 0000000000..2944596733 --- /dev/null +++ b/specs/phase1/beacon-chain.md @@ -0,0 +1,932 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Ethereum 2.0 Phase 1 -- The Beacon Chain for Shards](#ethereum-20-phase-1----the-beacon-chain-for-shards) + - [Table of contents](#table-of-contents) + - [Introduction](#introduction) + - [Custom types](#custom-types) + - [Configuration](#configuration) + - [Misc](#misc) + - [Updated containers](#updated-containers) + - [Extended `AttestationData`](#extended-attestationdata) + - [Extended `Attestation`](#extended-attestation) + - [Extended `PendingAttestation`](#extended-pendingattestation) + - [`IndexedAttestation`](#indexedattestation) + - [Extended `AttesterSlashing`](#extended-attesterslashing) + - [Extended `Validator`](#extended-validator) + - [Extended `BeaconBlockBody`](#extended-beaconblockbody) + - [Extended `BeaconBlock`](#extended-beaconblock) + - [Extended `SignedBeaconBlock`](#extended-signedbeaconblock) + - [Extended `BeaconState`](#extended-beaconstate) + - [New containers](#new-containers) + - [`ShardBlockWrapper`](#shardblockwrapper) + - [`ShardSignableHeader`](#shardsignableheader) + - [`ShardState`](#shardstate) + - [`ShardTransition`](#shardtransition) + - [`CompactCommittee`](#compactcommittee) + - [`AttestationCustodyBitWrapper`](#attestationcustodybitwrapper) + - [Helper functions](#helper-functions) + - [Misc](#misc-1) + - [`get_previous_slot`](#get_previous_slot) + - [`pack_compact_validator`](#pack_compact_validator) + - [`committee_to_compact_committee`](#committee_to_compact_committee) + - [`chunks_to_body_root`](#chunks_to_body_root) + - [`compute_shard_from_committee_index`](#compute_shard_from_committee_index) + - [Beacon state accessors](#beacon-state-accessors) + - [`get_active_shard_count`](#get_active_shard_count) + - [`get_online_validator_indices`](#get_online_validator_indices) + - [`get_shard_committee`](#get_shard_committee) + - [`get_shard_proposer_index`](#get_shard_proposer_index) + - [`get_light_client_committee`](#get_light_client_committee) + - [`get_indexed_attestation`](#get_indexed_attestation) + - [`get_updated_gasprice`](#get_updated_gasprice) + - [`get_start_shard`](#get_start_shard) + - [`get_shard`](#get_shard) + - [`get_next_slot_for_shard`](#get_next_slot_for_shard) + - [`get_offset_slots`](#get_offset_slots) + - [Predicates](#predicates) + - [Updated `is_valid_indexed_attestation`](#updated-is_valid_indexed_attestation) + - [Block processing](#block-processing) + - [Operations](#operations) + - [New Attestation processing](#new-attestation-processing) + - [`validate_attestation`](#validate_attestation) + - [`apply_shard_transition`](#apply_shard_transition) + - [`process_crosslink_for_shard`](#process_crosslink_for_shard) + - [`process_crosslinks`](#process_crosslinks) + - [`process_attestations`](#process_attestations) + - [New Attester slashing processing](#new-attester-slashing-processing) + - [Shard transition false positives](#shard-transition-false-positives) + - [Light client processing](#light-client-processing) + - [Epoch transition](#epoch-transition) + - [Custody game updates](#custody-game-updates) + - [Online-tracking](#online-tracking) + - [Light client committee updates](#light-client-committee-updates) + + + +# Ethereum 2.0 Phase 1 -- The Beacon Chain for Shards + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + +TODO + + + +## Introduction + +This document describes the extensions made to the Phase 0 design of The Beacon Chain + to facilitate the new shards as part of Phase 1 of Eth2. + +## Custom types + +We define the following Python custom types for type hinting and readability: + +| Name | SSZ equivalent | Description | +| - | - | - | +| `Shard` | `uint64` | a shard number | +| `OnlineEpochs` | `uint8` | online countdown epochs | + +## Configuration + +Configuration is not namespaced. Instead it is strictly an extension; + no constants of phase 0 change, but new constants are adopted for changing behaviors. + +### Misc + +| Name | Value | Unit | Duration | +| - | - | - | - | +| `MAX_SHARDS` | `2**10` (= 1024) | +| `ONLINE_PERIOD` | `OnlineEpochs(2**3)` (= 8) | online epochs | ~51 min | +| `LIGHT_CLIENT_COMMITTEE_SIZE` | `2**7` (= 128) | +| `LIGHT_CLIENT_COMMITTEE_PERIOD` | `Epoch(2**8)` (= 256) | epochs | ~27 hours | +| `SHARD_COMMITTEE_PERIOD` | `Epoch(2**8)` (= 256) | epochs | ~27 hours | +| `SHARD_BLOCK_CHUNK_SIZE` | `2**18` (= 262,144) | | +| `MAX_SHARD_BLOCK_CHUNKS` | `2**2` (= 4) | | +| `TARGET_SHARD_BLOCK_SIZE` | `3 * 2**16` (= 196,608) | | +| `SHARD_BLOCK_OFFSETS` | `[1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]` | | +| `MAX_SHARD_BLOCKS_PER_ATTESTATION` | `len(SHARD_BLOCK_OFFSETS)` | | +| `MAX_GASPRICE` | `Gwei(2**14)` (= 16,384) | Gwei | | +| `MIN_GASPRICE` | `Gwei(2**5)` (= 32) | Gwei | | +| `GASPRICE_ADJUSTMENT_COEFFICIENT` | `2**3` (= 8) | | +| `DOMAIN_SHARD_PROPOSAL` | `DomainType('0x80000000')` | | +| `DOMAIN_SHARD_COMMITTEE` | `DomainType('0x81000000')` | | +| `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` | | + +## Updated containers + +The following containers have updated definitions in Phase 1. + +### Extended `AttestationData` + +```python +class AttestationData(Container): + slot: Slot + index: CommitteeIndex + # LMD GHOST vote + beacon_block_root: Root + # FFG vote + source: Checkpoint + target: Checkpoint + # Current-slot shard block root + head_shard_root: Root + # Shard transition root + shard_transition_root: Root +``` + +### Extended `Attestation` + +```python +class Attestation(Container): + aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] + data: AttestationData + custody_bits_blocks: List[Bitlist[MAX_VALIDATORS_PER_COMMITTEE], MAX_SHARD_BLOCKS_PER_ATTESTATION] + signature: BLSSignature +``` + +### Extended `PendingAttestation` + +```python +class PendingAttestation(Container): + aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] + data: AttestationData + inclusion_delay: Slot + proposer_index: ValidatorIndex + crosslink_success: boolean +``` + +### `IndexedAttestation` + +```python +class IndexedAttestation(Container): + committee: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] + attestation: Attestation +``` + +#### Extended `AttesterSlashing` + +Note that the `attestation_1` and `attestation_2` have a new `IndexedAttestation` definition. + +```python +class AttesterSlashing(Container): + attestation_1: IndexedAttestation + attestation_2: IndexedAttestation +``` + +### Extended `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 + # Custody game + # next_custody_secret_to_reveal is initialised to the custody period + # (of the particular validator) in which the validator is activated + # = get_custody_period_for_validator(...) + next_custody_secret_to_reveal: uint64 + max_reveal_lateness: Epoch +``` + +### Extended `BeaconBlockBody` + +```python +class BeaconBlockBody(Container): + randao_reveal: BLSSignature + eth1_data: Eth1Data # Eth1 data vote + graffiti: Bytes32 # Arbitrary data + # Slashings + proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] + attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] + # Attesting + attestations: List[Attestation, MAX_ATTESTATIONS] + # Entry & exit + deposits: List[Deposit, MAX_DEPOSITS] + voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] + # Custody game + custody_slashings: List[SignedCustodySlashing, MAX_CUSTODY_SLASHINGS] + custody_key_reveals: List[CustodyKeyReveal, MAX_CUSTODY_KEY_REVEALS] + early_derived_secret_reveals: List[EarlyDerivedSecretReveal, MAX_EARLY_DERIVED_SECRET_REVEALS] + # Shards + shard_transitions: Vector[ShardTransition, MAX_SHARDS] + # Light clients + light_client_signature_bitfield: Bitvector[LIGHT_CLIENT_COMMITTEE_SIZE] + light_client_signature: BLSSignature +``` + +### Extended `BeaconBlock` + +Note that the `body` has a new `BeaconBlockBody` definition. + +```python +class BeaconBlock(Container): + slot: Slot + parent_root: Root + state_root: Root + body: BeaconBlockBody +``` + +#### Extended `SignedBeaconBlock` + +Note that the `message` has a new `BeaconBlock` definition. + +```python +class SignedBeaconBlock(Container): + message: BeaconBlock + signature: BLSSignature +``` + +### Extended `BeaconState` + +Note that aside from the new additions, `Validator` and `PendingAttestation` have new definitions. + +```python +class BeaconState(Container): + # Versioning + genesis_time: uint64 + 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, SLOTS_PER_ETH1_VOTING_PERIOD] + eth1_deposit_index: uint64 + # Registry + validators: List[Validator, VALIDATOR_REGISTRY_LIMIT] + balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT] + # Randomness + randao_mixes: Vector[Root, EPOCHS_PER_HISTORICAL_VECTOR] + # Slashings + slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances + # Attestations + previous_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH] + current_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH] + # Finality + justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch + previous_justified_checkpoint: Checkpoint # Previous epoch snapshot + current_justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + # Phase 1 + shard_states: List[ShardState, MAX_SHARDS] + online_countdown: List[OnlineEpochs, VALIDATOR_REGISTRY_LIMIT] # not a raw byte array, considered its large size. + current_light_committee: CompactCommittee + next_light_committee: CompactCommittee + # Custody game + # Future derived secrets already exposed; contains the indices of the exposed validator + # at RANDAO reveal period % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS + exposed_derived_secrets: Vector[List[ValidatorIndex, MAX_EARLY_DERIVED_SECRET_REVEALS * SLOTS_PER_EPOCH], + EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS] +``` + +## New containers + +The following containers are new in Phase 1. + +### `ShardBlockWrapper` + +_Wrapper for being broadcasted over the network._ + +```python +class ShardBlockWrapper(Container): + shard_parent_root: Root + beacon_parent_root: Root + slot: Slot + body: ByteList[MAX_SHARD_BLOCK_CHUNKS * SHARD_BLOCK_CHUNK_SIZE] + signature: BLSSignature +``` + +### `ShardSignableHeader` + +```python +class ShardSignableHeader(Container): + shard_parent_root: Root + beacon_parent_root: Root + slot: Slot + body_root: Root +``` + +### `ShardState` + +```python +class ShardState(Container): + slot: Slot + gasprice: Gwei + data: Bytes32 + latest_block_root: Root +``` + +### `ShardTransition` + +```python +class ShardTransition(Container): + # Starting from slot + start_slot: Slot + # Shard block lengths + shard_block_lengths: List[uint64, MAX_SHARD_BLOCKS_PER_ATTESTATION] + # Shard data roots + shard_data_roots: List[List[Bytes32, MAX_SHARD_BLOCK_CHUNKS], MAX_SHARD_BLOCKS_PER_ATTESTATION] + # Intermediate shard states + shard_states: List[ShardState, MAX_SHARD_BLOCKS_PER_ATTESTATION] + # Proposer signature aggregate + proposer_signature_aggregate: BLSSignature +``` + +### `CompactCommittee` + +```python +class CompactCommittee(Container): + pubkeys: List[BLSPubkey, MAX_VALIDATORS_PER_COMMITTEE] + compact_validators: List[uint64, MAX_VALIDATORS_PER_COMMITTEE] +``` + +### `AttestationCustodyBitWrapper` + +```python +class AttestationCustodyBitWrapper(Container): + attestation_data_root: Root + block_index: uint64 + bit: boolean +``` + +## Helper functions + +### Misc + +#### `get_previous_slot` + +```python +def get_previous_slot(slot: Slot) -> Slot: + if slot > 0: + return Slot(slot - 1) + else: + return Slot(0) +``` + +#### `pack_compact_validator` + +```python +def pack_compact_validator(index: int, slashed: bool, balance_in_increments: int) -> int: + """ + Creates a compact validator object representing index, slashed status, and compressed balance. + Takes as input balance-in-increments (// EFFECTIVE_BALANCE_INCREMENT) to preserve symmetry with + the unpacking function. + """ + return (index << 16) + (slashed << 15) + balance_in_increments +``` + +#### `committee_to_compact_committee` + +```python +def committee_to_compact_committee(state: BeaconState, committee: Sequence[ValidatorIndex]) -> CompactCommittee: + """ + Given a state and a list of validator indices, outputs the CompactCommittee representing them. + """ + validators = [state.validators[i] for i in committee] + compact_validators = [ + pack_compact_validator(i, v.slashed, v.effective_balance // EFFECTIVE_BALANCE_INCREMENT) + for i, v in zip(committee, validators) + ] + pubkeys = [v.pubkey for v in validators] + return CompactCommittee(pubkeys=pubkeys, compact_validators=compact_validators) +``` + +#### `chunks_to_body_root` + +```python +def chunks_to_body_root(chunks: List[Bytes32, MAX_SHARD_BLOCK_CHUNKS]) -> Root: + empty_chunk_root = hash_tree_root(ByteList[SHARD_BLOCK_CHUNK_SIZE]()) + return hash_tree_root(Vector[Bytes32, MAX_SHARD_BLOCK_CHUNKS]( + chunks + [empty_chunk_root] * (MAX_SHARD_BLOCK_CHUNKS - len(chunks)) + )) +``` + +#### `compute_shard_from_committee_index` + +```python +def compute_shard_from_committee_index(state: BeaconState, index: CommitteeIndex, slot: Slot) -> Shard: + active_shards = get_active_shard_count(state) + return Shard((index + get_start_shard(state, slot)) % active_shards) +``` + +### Beacon state accessors + +#### `get_active_shard_count` + +```python +def get_active_shard_count(state: BeaconState) -> uint64: + return len(state.shard_states) # May adapt in the future, or change over time. +``` + +#### `get_online_validator_indices` + +```python +def get_online_validator_indices(state: BeaconState) -> Set[ValidatorIndex]: + active_validators = get_active_validator_indices(state, get_current_epoch(state)) + return set([i for i in active_validators if state.online_countdown[i] != 0]) +``` + +#### `get_shard_committee` + +```python +def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) -> Sequence[ValidatorIndex]: + source_epoch = epoch - epoch % SHARD_COMMITTEE_PERIOD + if source_epoch > 0: + source_epoch -= SHARD_COMMITTEE_PERIOD + active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) + seed = get_seed(beacon_state, source_epoch, DOMAIN_SHARD_COMMITTEE) + return compute_committee(active_validator_indices, seed, shard, get_active_shard_count(beacon_state)) +``` + +#### `get_shard_proposer_index` + +```python +def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard) -> ValidatorIndex: + committee = get_shard_committee(beacon_state, compute_epoch_at_slot(slot), shard) + r = bytes_to_int(get_seed(beacon_state, get_current_epoch(beacon_state), DOMAIN_SHARD_COMMITTEE)[:8]) + return committee[r % len(committee)] +``` + +#### `get_light_client_committee` + +```python +def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: + source_epoch = epoch - epoch % LIGHT_CLIENT_COMMITTEE_PERIOD + if source_epoch > 0: + source_epoch -= LIGHT_CLIENT_COMMITTEE_PERIOD + active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) + seed = get_seed(beacon_state, source_epoch, DOMAIN_LIGHT_CLIENT) + active_shards = get_active_shard_count(beacon_state) + return compute_committee(active_validator_indices, seed, 0, active_shards)[:TARGET_COMMITTEE_SIZE] +``` + +#### `get_indexed_attestation` + +```python +def get_indexed_attestation(beacon_state: BeaconState, attestation: Attestation) -> IndexedAttestation: + committee = get_beacon_committee(beacon_state, attestation.data.slot, attestation.data.index) + return IndexedAttestation( + committee=committee, + attestation=attestation, + ) +``` + +#### `get_updated_gasprice` + +```python +def get_updated_gasprice(prev_gasprice: Gwei, length: uint8) -> Gwei: + if length > TARGET_SHARD_BLOCK_SIZE: + delta = (prev_gasprice * (length - TARGET_SHARD_BLOCK_SIZE) + // TARGET_SHARD_BLOCK_SIZE // GASPRICE_ADJUSTMENT_COEFFICIENT) + return min(prev_gasprice + delta, MAX_GASPRICE) + else: + delta = (prev_gasprice * (TARGET_SHARD_BLOCK_SIZE - length) + // TARGET_SHARD_BLOCK_SIZE // GASPRICE_ADJUSTMENT_COEFFICIENT) + return max(prev_gasprice, MIN_GASPRICE + delta) - delta +``` + +#### `get_start_shard` + +```python +def get_start_shard(state: BeaconState, slot: Slot) -> Shard: + # TODO: implement start shard logic + return Shard(0) +``` + +#### `get_shard` + +```python +def get_shard(state: BeaconState, attestation: Attestation) -> Shard: + return compute_shard_from_committee_index(state, attestation.data.index, attestation.data.slot) +``` + +#### `get_next_slot_for_shard` + +```python +def get_next_slot_for_shard(state: BeaconState, shard: Shard) -> Slot: + return Slot(state.shard_states[shard].slot + 1) +``` + + +#### `get_offset_slots` + +```python +def get_offset_slots(state: BeaconState, start_slot: Slot) -> Sequence[Slot]: + return [Slot(start_slot + x) for x in SHARD_BLOCK_OFFSETS if start_slot + x < state.slot] +``` + +### Predicates + +#### Updated `is_valid_indexed_attestation` + +Note that this replaces the Phase 0 `is_valid_indexed_attestation`. + +```python +def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool: + """ + Check if ``indexed_attestation`` has valid indices and signature. + """ + # Verify aggregate signature + all_pubkeys = [] + all_signing_roots = [] + attestation = indexed_attestation.attestation + domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch) + aggregation_bits = attestation.aggregation_bits + assert len(aggregation_bits) == len(indexed_attestation.committee) + + if len(attestation.custody_bits_blocks) == 0: + # fall back on phase0 behavior if there is no shard data. + for participant, abit in zip(indexed_attestation.committee, aggregation_bits): + if abit: + all_pubkeys.append(state.validators[participant].pubkey) + signing_root = compute_signing_root(indexed_attestation.attestation.data, domain) + return bls.FastAggregateVerify(all_pubkeys, signing_root, signature=attestation.signature) + else: + for i, custody_bits in enumerate(attestation.custody_bits_blocks): + assert len(custody_bits) == len(indexed_attestation.committee) + for participant, abit, cbit in zip(indexed_attestation.committee, aggregation_bits, custody_bits): + if abit: + all_pubkeys.append(state.validators[participant].pubkey) + # Note: only 2N distinct message hashes + all_signing_roots.append(compute_signing_root( + AttestationCustodyBitWrapper(hash_tree_root(attestation.data), i, cbit), domain)) + else: + assert not cbit + return bls.AggregateVerify(zip(all_pubkeys, all_signing_roots), signature=attestation.signature) +``` + + +### Block processing + +```python +def process_block(state: BeaconState, block: BeaconBlock) -> None: + process_block_header(state, block) + process_randao(state, block.body) + process_eth1_data(state, block.body) + verify_shard_transition_false_positives(state, block.body) + process_light_client_signatures(state, block.body) + process_operations(state, block.body) +``` + + +#### Operations + +```python +def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: + # Verify that outstanding deposits are processed up to the maximum number of deposits + assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index) + + def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: + for operation in operations: + fn(state, operation) + + for_ops(body.proposer_slashings, process_proposer_slashing) + for_ops(body.attester_slashings, process_attester_slashing) + + # New attestation processing + process_attestations(state, body, body.attestations) + + for_ops(body.deposits, process_deposit) + for_ops(body.voluntary_exits, process_voluntary_exit) + + # See custody game spec. + process_custody_game_operations(state, body) + + # TODO process_operations(body.shard_receipt_proofs, process_shard_receipt_proofs) +``` + +##### New Attestation processing + +###### `validate_attestation` + +```python +def validate_attestation(state: BeaconState, attestation: Attestation) -> None: + data = attestation.data + assert data.index < get_committee_count_at_slot(state, data.slot) + assert data.index < get_active_shard_count(state) + assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state)) + assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= data.slot + SLOTS_PER_EPOCH + + committee = get_beacon_committee(state, data.slot, data.index) + assert len(attestation.aggregation_bits) == len(committee) + + if attestation.data.target.epoch == get_current_epoch(state): + assert attestation.data.source == state.current_justified_checkpoint + else: + assert attestation.data.source == state.previous_justified_checkpoint + + shard = get_shard(state, attestation) + shard_start_slot = get_next_slot_for_shard(state, shard) + + # Signature check + assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) + # Type 1: on-time attestations + if attestation.custody_bits_blocks != []: + # Correct slot + assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY == state.slot + # Correct data root count + assert len(attestation.custody_bits_blocks) == len(get_offset_slots(state, shard_start_slot)) + # Correct parent block root + assert data.beacon_block_root == get_block_root_at_slot(state, get_previous_slot(state.slot)) + # Type 2: no shard transition, no custody bits # TODO: could only allow for older attestations. + else: + # assert state.slot - compute_start_slot_at_epoch(compute_epoch_at_slot(data.slot)) < SLOTS_PER_EPOCH + assert data.shard_transition_root == Root() +``` + +###### `apply_shard_transition` + +```python +def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTransition) -> None: + # Slot the attestation starts counting from + start_slot = get_next_slot_for_shard(state, shard) + + # Correct data root count + offset_slots = get_offset_slots(state, start_slot) + assert ( + len(transition.shard_data_roots) + == len(transition.shard_states) + == len(transition.shard_block_lengths) + == len(offset_slots) + ) + assert transition.start_slot == start_slot + + # Reconstruct shard headers + headers = [] + proposers = [] + shard_parent_root = state.shard_states[shard].latest_block_root + for i in range(len(offset_slots)): + if any(transition.shard_data_roots): + headers.append(ShardSignableHeader( + shard_parent_root=shard_parent_root, + parent_hash=get_block_root_at_slot(state, get_previous_slot(state.slot)), + slot=offset_slots[i], + body_root=chunks_to_body_root(transition.shard_data_roots[i]) + )) + proposers.append(get_shard_proposer_index(state, shard, offset_slots[i])) + shard_parent_root = hash_tree_root(headers[-1]) + + # Verify correct calculation of gas prices and slots and chunk roots + prev_gasprice = state.shard_states[shard].gasprice + for i in range(len(offset_slots)): + shard_state = transition.shard_states[i] + block_length = transition.shard_block_lengths[i] + chunks = transition.shard_data_roots[i] + assert shard_state.gasprice == get_updated_gasprice(prev_gasprice, block_length) + assert shard_state.slot == offset_slots[i] + assert len(chunks) == block_length // SHARD_BLOCK_CHUNK_SIZE + prev_gasprice = shard_state.gasprice + + pubkeys = [state.validators[proposer].pubkey for proposer in proposers] + signing_roots = [ + compute_signing_root(header, get_domain(state, DOMAIN_SHARD_PROPOSAL, compute_epoch_at_slot(header.slot))) + for header in headers + ] + # Verify combined proposer signature + assert bls.AggregateVerify(zip(pubkeys, signing_roots), signature=transition.proposer_signature_aggregate) + + # Save updated state + state.shard_states[shard] = transition.shard_states[-1] + state.shard_states[shard].slot = state.slot - 1 +``` + +###### `process_crosslink_for_shard` + +```python +def process_crosslink_for_shard(state: BeaconState, + shard: Shard, + shard_transition: ShardTransition, + attestations: Sequence[Attestation]) -> Root: + committee = get_beacon_committee(state, get_current_epoch(state), shard) + online_indices = get_online_validator_indices(state) + + # Loop over all shard transition roots + shard_transition_roots = set([a.data.shard_transition_root for a in attestations]) + for shard_transition_root in sorted(shard_transition_roots): + transition_attestations = [a for a in attestations if a.data.shard_transition_root == shard_transition_root] + transition_participants: Set[ValidatorIndex] = set() + for attestation in transition_attestations: + participants = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) + transition_participants = transition_participants.union(participants) + + enough_online_stake = ( + get_total_balance(state, online_indices.intersection(transition_participants)) * 3 >= + get_total_balance(state, online_indices.intersection(committee)) * 2 + ) + # If not enough stake, try next transition root + if not enough_online_stake: + continue + + # Attestation <-> shard transition consistency + assert shard_transition_root == hash_tree_root(shard_transition) + assert ( + attestation.data.head_shard_root + == chunks_to_body_root(shard_transition.shard_data_roots[-1]) + ) + + # Apply transition + apply_shard_transition(state, shard, shard_transition) + # Apply proposer reward and cost + beacon_proposer_index = get_beacon_proposer_index(state) + estimated_attester_reward = sum([get_base_reward(state, attester) for attester in transition_participants]) + proposer_reward = Gwei(estimated_attester_reward // PROPOSER_REWARD_QUOTIENT) + increase_balance(state, beacon_proposer_index, proposer_reward) + states_slots_lengths = zip( + shard_transition.shard_states, + get_offset_slots(state, get_next_slot_for_shard(state, shard)), + shard_transition.shard_block_lengths + ) + for shard_state, slot, length in states_slots_lengths: + proposer_index = get_shard_proposer_index(state, shard, slot) + decrease_balance(state, proposer_index, shard_state.gasprice * length) + + # Return winning transition root + return shard_transition_root + + # No winning transition root, ensure empty and return empty root + assert shard_transition == ShardTransition() + return Root() +``` + +###### `process_crosslinks` + +```python +def process_crosslinks(state: BeaconState, + block_body: BeaconBlockBody, + attestations: Sequence[Attestation]) -> Set[Tuple[Shard, Root]]: + winners: Set[Tuple[Shard, Root]] = set() + committee_count = get_committee_count_at_slot(state, state.slot) + for committee_index in map(CommitteeIndex, range(committee_count)): + shard = compute_shard_from_committee_index(state, committee_index, state.slot) + # All attestations in the block for this shard + shard_attestations = [ + attestation for attestation in attestations + if get_shard(state, attestation) == shard and attestation.data.slot == state.slot + ] + shard_transition = block_body.shard_transitions[shard] + winning_root = process_crosslink_for_shard(state, shard, shard_transition, shard_attestations) + if winning_root != Root(): + winners.add((shard, winning_root)) + return winners +``` + +###### `process_attestations` + +```python +def process_attestations(state: BeaconState, block_body: BeaconBlockBody, attestations: Sequence[Attestation]) -> None: + # Basic validation + for attestation in attestations: + validate_attestation(state, attestation) + + # Process crosslinks + winners = process_crosslinks(state, block_body, attestations) + + # Store pending attestations for epoch processing + for attestation in attestations: + is_winning_transition = (get_shard(state, attestation), attestation.data.shard_transition_root) in winners + pending_attestation = PendingAttestation( + aggregation_bits=attestation.aggregation_bits, + data=attestation.data, + inclusion_delay=state.slot - attestation.data.slot, + crosslink_success=is_winning_transition and attestation.data.slot == state.slot, + proposer_index=get_beacon_proposer_index(state), + ) + if attestation.data.target.epoch == get_current_epoch(state): + state.current_epoch_attestations.append(pending_attestation) + else: + state.previous_epoch_attestations.append(pending_attestation) +``` + +##### New Attester slashing processing + +```python +def get_indices_from_committee( + committee: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE], + bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE]: + assert len(bits) == len(committee) + return List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE]( + [validator_index for i, validator_index in enumerate(committee) if bits[i]] + ) +``` + +```python +def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSlashing) -> None: + indexed_attestation_1 = attester_slashing.attestation_1 + indexed_attestation_2 = attester_slashing.attestation_2 + assert is_slashable_attestation_data( + indexed_attestation_1.attestation.data, + indexed_attestation_2.attestation.data, + ) + assert is_valid_indexed_attestation(state, indexed_attestation_1) + assert is_valid_indexed_attestation(state, indexed_attestation_2) + + indices_1 = get_indices_from_committee( + indexed_attestation_1.committee, + indexed_attestation_1.attestation.aggregation_bits, + ) + indices_2 = get_indices_from_committee( + indexed_attestation_2.committee, + indexed_attestation_2.attestation.aggregation_bits, + ) + + slashed_any = False + indices = set(indices_1).intersection(indices_2) + 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 +``` + +#### Shard transition false positives + +```python +def verify_shard_transition_false_positives(state: BeaconState, block_body: BeaconBlockBody) -> None: + # Verify that a `shard_transition` in a block is empty if an attestation was not processed for it + for shard in range(get_active_shard_count(state)): + if state.shard_states[shard].slot != state.slot - 1: + assert block_body.shard_transitions[shard] == ShardTransition() +``` + +#### Light client processing + +```python +def process_light_client_signatures(state: BeaconState, block_body: BeaconBlockBody) -> None: + committee = get_light_client_committee(state, get_current_epoch(state)) + total_reward = Gwei(0) + signer_pubkeys = [] + for bit_index, participant_index in enumerate(committee): + if block_body.light_client_signature_bitfield[bit_index]: + signer_pubkeys.append(state.validators[participant_index].pubkey) + increase_balance(state, participant_index, get_base_reward(state, participant_index)) + total_reward += get_base_reward(state, participant_index) + + increase_balance(state, get_beacon_proposer_index(state), Gwei(total_reward // PROPOSER_REWARD_QUOTIENT)) + + slot = get_previous_slot(state.slot) + signing_root = compute_signing_root(get_block_root_at_slot(state, slot), + get_domain(state, DOMAIN_LIGHT_CLIENT, compute_epoch_at_slot(slot))) + return bls.FastAggregateVerify(signer_pubkeys, signing_root, signature=block_body.light_client_signature) +``` + + +### Epoch transition + +This epoch transition overrides the phase0 epoch transition: + +```python +def process_epoch(state: BeaconState) -> None: + process_justification_and_finalization(state) + process_rewards_and_penalties(state) + process_registry_updates(state) + process_reveal_deadlines(state) + process_slashings(state) + process_final_updates(state) + process_custody_final_updates(state) + process_online_tracking(state) + process_light_client_committee_updates(state) +``` + +#### Custody game updates + +`process_reveal_deadlines` and `process_custody_final_updates` are defined in [the Custody Game spec](./1_custody-game.md), + +#### Online-tracking + +```python +def process_online_tracking(state: BeaconState) -> None: + # Slowly remove validators from the "online" set if they do not show up + for index in range(len(state.validators)): + if state.online_countdown[index] != 0: + state.online_countdown[index] = state.online_countdown[index] - 1 + + # Process pending attestations + for pending_attestation in state.current_epoch_attestations + state.previous_epoch_attestations: + for index in get_attesting_indices(state, pending_attestation.data, pending_attestation.aggregation_bits): + state.online_countdown[index] = ONLINE_PERIOD +``` + +#### Light client committee updates + +```python +def process_light_client_committee_updates(state: BeaconState) -> None: + # Update light client committees + if get_current_epoch(state) % LIGHT_CLIENT_COMMITTEE_PERIOD == 0: + state.current_light_committee = state.next_light_committee + new_committee = get_light_client_committee(state, get_current_epoch(state) + LIGHT_CLIENT_COMMITTEE_PERIOD) + state.next_light_committee = committee_to_compact_committee(state, new_committee) +``` + diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index c09fa18673..fd35e6515e 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -4,107 +4,62 @@ ## Table of contents - - [Introduction](#introduction) -- [Terminology](#terminology) - [Constants](#constants) - [Misc](#misc) - - [Custody game parameters](#custody-game-parameters) +- [Configuration](#configuration) - [Time parameters](#time-parameters) - [Max operations per block](#max-operations-per-block) - [Reward and penalty quotients](#reward-and-penalty-quotients) - [Signature domain types](#signature-domain-types) - - [TODO PLACEHOLDER](#todo-placeholder) - [Data structures](#data-structures) - - [Custody objects](#custody-objects) - - [`CustodyChunkChallenge`](#custodychunkchallenge) - - [`CustodyBitChallenge`](#custodybitchallenge) - - [`CustodyChunkChallengeRecord`](#custodychunkchallengerecord) - - [`CustodyBitChallengeRecord`](#custodybitchallengerecord) - - [`CustodyResponse`](#custodyresponse) - - [New beacon operations](#new-beacon-operations) + - [New Beacon Chain operations](#new-beacon-chain-operations) + - [`CustodySlashing`](#custodyslashing) + - [`SignedCustodySlashing`](#signedcustodyslashing) - [`CustodyKeyReveal`](#custodykeyreveal) - [`EarlyDerivedSecretReveal`](#earlyderivedsecretreveal) - - [Phase 0 container updates](#phase-0-container-updates) - - [`Validator`](#validator) - - [`BeaconState`](#beaconstate) - - [`BeaconBlockBody`](#beaconblockbody) - [Helpers](#helpers) - - [`ceillog2`](#ceillog2) - - [`is_valid_merkle_branch_with_mixin`](#is_valid_merkle_branch_with_mixin) - - [`get_crosslink_chunk_count`](#get_crosslink_chunk_count) - [`legendre_bit`](#legendre_bit) - - [`custody_subchunkify`](#custody_subchunkify) - - [`get_custody_chunk_bit`](#get_custody_chunk_bit) - - [`get_chunk_bits_root`](#get_chunk_bits_root) + - [`custody_atoms`](#custody_atoms) + - [`compute_custody_bit`](#compute_custody_bit) - [`get_randao_epoch_for_custody_period`](#get_randao_epoch_for_custody_period) - [`get_custody_period_for_validator`](#get_custody_period_for_validator) - - [`replace_empty_or_append`](#replace_empty_or_append) - [Per-block processing](#per-block-processing) - - [Operations](#operations) + - [Custody Game Operations](#custody-game-operations) - [Custody key reveals](#custody-key-reveals) - [Early derived secret reveals](#early-derived-secret-reveals) - - [Chunk challenges](#chunk-challenges) - - [Bit challenges](#bit-challenges) - - [Custody responses](#custody-responses) + - [Custody Slashings](#custody-slashings) - [Per-epoch processing](#per-epoch-processing) - - [Handling of custody-related deadlines](#handling-of-custody-related-deadlines) + - [Handling of reveal deadlines](#handling-of-reveal-deadlines) + - [Final updates](#final-updates) - ## Introduction This document details the beacon chain additions and changes in Phase 1 of Ethereum 2.0 to support the shard data custody game, building upon the [Phase 0](../phase0/beacon-chain.md) specification. -## Terminology - -- **Custody game**— -- **Custody period**— -- **Custody chunk**— -- **Custody chunk bit**— -- **Custody chunk challenge**— -- **Custody bit**— -- **Custody bit challenge**— -- **Custody key**— -- **Custody key reveal**— -- **Custody key mask**— -- **Custody response**— -- **Custody response deadline**— - ## Constants ### Misc -| Name | Value | + +| Name | Value | Unit | | - | - | | `BLS12_381_Q` | `4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787` | -| `MINOR_REWARD_QUOTIENT` | `2**8` (= 256) | -| `MAX_EPOCHS_PER_CROSSLINK` | `2**6` (= 64) | epochs | ~7 hours | +| `BYTES_PER_CUSTODY_ATOM` | `48` | bytes | -### Custody game parameters - -| Name | Value | -| - | - | -| `BYTES_PER_SHARD_BLOCK` | `2**14` (= 16,384) | -| `BYTES_PER_CUSTODY_CHUNK` | `2**9` (= 512) | -| `BYTES_PER_CUSTODY_SUBCHUNK` | `48` | -| `CHUNKS_PER_EPOCH` | `2 * BYTES_PER_SHARD_BLOCK * SLOTS_PER_EPOCH // BYTES_PER_CUSTODY_CHUNK` | -| `MAX_CUSTODY_CHUNKS` | `MAX_EPOCHS_PER_CROSSLINK * CHUNKS_PER_EPOCH` | -| `CUSTODY_DATA_DEPTH` | `ceillog2(MAX_CUSTODY_CHUNKS) + 1` | -| `CUSTODY_CHUNK_BIT_DEPTH` | `ceillog2(MAX_EPOCHS_PER_CROSSLINK * CHUNKS_PER_EPOCH // 256) + 2` | +## Configuration ### Time parameters | Name | Value | Unit | Duration | | - | - | :-: | :-: | -| `MAX_CHUNK_CHALLENGE_DELAY` | `2**11` (= 2,048) | epochs | ~9 days | -| `CUSTODY_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days | | `RANDAO_PENALTY_EPOCHS` | `2**1` (= 2) | epochs | 12.8 minutes | -| `EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS` | `2**14` | epochs | ~73 days | +| `EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS` | `2**14` (= 16,384) | epochs | ~73 days | | `EPOCHS_PER_CUSTODY_PERIOD` | `2**11` (= 2,048) | epochs | ~9 days | | `CUSTODY_PERIOD_TO_RANDAO_PADDING` | `2**11` (= 2,048) | epochs | ~9 days | | `MAX_REVEAL_LATENESS_DECREMENT` | `2**7` (= 128) | epochs | ~14 hours | @@ -113,17 +68,16 @@ This document details the beacon chain additions and changes in Phase 1 of Ether | Name | Value | | - | - | -| `MAX_CUSTODY_KEY_REVEALS` | `2**4` (= 16) | +| `MAX_CUSTODY_KEY_REVEALS` | `2**8` (= 256) | | `MAX_EARLY_DERIVED_SECRET_REVEALS` | `1` | -| `MAX_CUSTODY_CHUNK_CHALLENGES` | `2**2` (= 4) | -| `MAX_CUSTODY_BIT_CHALLENGES` | `2**2` (= 4) | -| `MAX_CUSTODY_RESPONSES` | `2**5` (= 32) | +| `MAX_CUSTODY_SLASHINGS` | `1` | ### Reward and penalty quotients | Name | Value | | - | - | | `EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE` | `2**1` (= 2) | +| `MINOR_REWARD_QUOTIENT` | `2**8` (= 256) | ### Signature domain types @@ -131,79 +85,35 @@ The following types are defined, mapping into `DomainType` (little endian): | Name | Value | | - | - | -| `DOMAIN_CUSTODY_BIT_CHALLENGE` | `DomainType('0x06000000')` | - -### TODO PLACEHOLDER - -| Name | Value | -| - | - | -| `PLACEHOLDER` | `2**32` | +| `DOMAIN_CUSTODY_BIT_SLASHING` | `DomainType('0x83000000')` | ## Data structures -### Custody objects +### New Beacon Chain operations -#### `CustodyChunkChallenge` +#### `CustodySlashing` ```python -class CustodyChunkChallenge(Container): - responder_index: ValidatorIndex +class CustodySlashing(Container): + # Attestation.custody_bits_blocks[data_index][committee.index(malefactor_index)] is the target custody bit to check. + # (Attestation.data.shard_transition_root as ShardTransition).shard_data_roots[data_index] is the root of the data. + data_index: uint64 + malefactor_index: ValidatorIndex + malefactor_secret: BLSSignature + whistleblower_index: ValidatorIndex + shard_transition: ShardTransition attestation: Attestation - chunk_index: uint64 + data: ByteList[MAX_SHARD_BLOCK_CHUNKS * SHARD_BLOCK_CHUNK_SIZE] ``` -#### `CustodyBitChallenge` +#### `SignedCustodySlashing` ```python -class CustodyBitChallenge(Container): - responder_index: ValidatorIndex - attestation: Attestation - challenger_index: ValidatorIndex - responder_key: BLSSignature - chunk_bits: Bitlist[MAX_CUSTODY_CHUNKS] +class SignedCustodySlashing(Container): + message: CustodySlashing signature: BLSSignature ``` -#### `CustodyChunkChallengeRecord` - -```python -class CustodyChunkChallengeRecord(Container): - challenge_index: uint64 - challenger_index: ValidatorIndex - responder_index: ValidatorIndex - inclusion_epoch: Epoch - data_root: Root - depth: uint64 - chunk_index: uint64 -``` - -#### `CustodyBitChallengeRecord` - -```python -class CustodyBitChallengeRecord(Container): - challenge_index: uint64 - challenger_index: ValidatorIndex - responder_index: ValidatorIndex - inclusion_epoch: Epoch - data_root: Root - chunk_count: uint64 - chunk_bits_merkle_root: Root - responder_key: BLSSignature -``` - -#### `CustodyResponse` - -```python -class CustodyResponse(Container): - challenge_index: uint64 - chunk_index: uint64 - chunk: ByteVector[BYTES_PER_CUSTODY_CHUNK] - data_branch: List[Bytes32, CUSTODY_DATA_DEPTH] - chunk_bits_branch: List[Bytes32, CUSTODY_CHUNK_BIT_DEPTH] - chunk_bits_leaf: Bitvector[256] -``` - -### New beacon operations #### `CustodyKeyReveal` @@ -233,82 +143,9 @@ class EarlyDerivedSecretReveal(Container): mask: Bytes32 ``` -### Phase 0 container updates - -Add the following fields to the end of the specified container objects. Fields with underlying type `uint64` are initialized to `0` and list fields are initialized to `[]`. - -#### `Validator` - -```python -class Validator(Container): - # next_custody_secret_to_reveal is initialised to the custody period - # (of the particular validator) in which the validator is activated - # = get_custody_period_for_validator(...) - next_custody_secret_to_reveal: uint64 - max_reveal_lateness: Epoch -``` - -#### `BeaconState` - -```python -class BeaconState(Container): - custody_chunk_challenge_records: List[CustodyChunkChallengeRecord, PLACEHOLDER] - custody_bit_challenge_records: List[CustodyBitChallengeRecord, PLACEHOLDER] - custody_challenge_index: uint64 - - # Future derived secrets already exposed; contains the indices of the exposed validator - # at RANDAO reveal period % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS - exposed_derived_secrets: Vector[List[ValidatorIndex, PLACEHOLDER], - EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS] -``` - -#### `BeaconBlockBody` - -```python -class BeaconBlockBody(Container): - custody_chunk_challenges: List[CustodyChunkChallenge, PLACEHOLDER] - custody_bit_challenges: List[CustodyBitChallenge, PLACEHOLDER] - custody_responses: List[CustodyResponse, PLACEHOLDER] - custody_key_reveals: List[CustodyKeyReveal, PLACEHOLDER] - early_derived_secret_reveals: List[EarlyDerivedSecretReveal, PLACEHOLDER] -``` ## Helpers -### `ceillog2` - -```python -def ceillog2(x: uint64) -> int: - return (x - 1).bit_length() -``` - -### `is_valid_merkle_branch_with_mixin` - -```python -def is_valid_merkle_branch_with_mixin(leaf: Bytes32, - branch: Sequence[Bytes32], - depth: uint64, - index: uint64, - root: Root, - mixin: uint64) -> bool: - value = leaf - for i in range(depth): - if index // (2**i) % 2: - value = hash(branch[i] + value) - else: - value = hash(value + branch[i]) - value = hash(value + mixin.to_bytes(32, "little")) - return value == root -``` - -### `get_crosslink_chunk_count` - -```python -def get_custody_chunk_count(crosslink: Crosslink) -> int: - crosslink_length = min(MAX_EPOCHS_PER_CROSSLINK, crosslink.end_epoch - crosslink.start_epoch) - return crosslink_length * CHUNKS_PER_EPOCH -``` - ### `legendre_bit` Returns the Legendre symbol `(a/q)` normalizes as a bit (i.e. `((a/q) + 1) // 2`). In a production implementation, a well-optimized library (e.g. GMP) should be used for this. @@ -338,37 +175,27 @@ def legendre_bit(a: int, q: int) -> int: return 0 ``` -### `custody_subchunkify` +### `custody_atoms` -Given one proof of custody chunk, returns the proof of custody subchunks of the correct sizes. +Given one set of data, return the custody atoms: each atom will be combined with one legendre bit. ```python -def custody_subchunkify(bytez: bytes) -> Sequence[bytes]: - bytez += b'\x00' * (-len(bytez) % BYTES_PER_CUSTODY_SUBCHUNK) - return [bytez[i:i + BYTES_PER_CUSTODY_SUBCHUNK] - for i in range(0, len(bytez), BYTES_PER_CUSTODY_SUBCHUNK)] +def get_custody_atoms(bytez: bytes) -> Sequence[bytes]: + bytez += b'\x00' * (-len(bytez) % BYTES_PER_CUSTODY_ATOM) # right-padding + return [bytez[i:i + BYTES_PER_CUSTODY_ATOM] + for i in range(0, len(bytez), BYTES_PER_CUSTODY_ATOM)] ``` -### `get_custody_chunk_bit` +### `compute_custody_bit` ```python -def get_custody_chunk_bit(key: BLSSignature, chunk: bytes) -> bool: +def compute_custody_bit(key: BLSSignature, data: bytes) -> bit: full_G2_element = bls.signature_to_G2(key) s = full_G2_element[0].coeffs - bits = [legendre_bit((i + 1) * s[i % 2] + int.from_bytes(subchunk, "little"), BLS12_381_Q) - for i, subchunk in enumerate(custody_subchunkify(chunk))] - - return bool(sum(bits) % 2) -``` - -### `get_chunk_bits_root` - -```python -def get_chunk_bits_root(chunk_bits: Bitlist[MAX_CUSTODY_CHUNKS]) -> bit: - aggregated_bits = 0 - for i, b in enumerate(chunk_bits): - aggregated_bits += 2**i * b - return legendre_bit(aggregated_bits, BLS12_381_Q) + bits = [legendre_bit(sum(s[i % 2]**i * int.from_bytes(atom, "little")), BLS12_381_Q) + for i, atom in enumerate(get_custody_atoms(data))] + # XOR all atom bits + return bit(sum(bits) % 2) ``` ### `get_randao_epoch_for_custody_period` @@ -382,38 +209,31 @@ def get_randao_epoch_for_custody_period(period: uint64, validator_index: Validat ### `get_custody_period_for_validator` ```python -def get_custody_period_for_validator(state: BeaconState, validator_index: ValidatorIndex, epoch: Epoch=None) -> int: +def get_custody_period_for_validator(validator_index: ValidatorIndex, epoch: Epoch) -> int: ''' Return the reveal period for a given validator. ''' - epoch = get_current_epoch(state) if epoch is None else epoch return (epoch + validator_index % EPOCHS_PER_CUSTODY_PERIOD) // EPOCHS_PER_CUSTODY_PERIOD ``` -### `replace_empty_or_append` - -```python -def replace_empty_or_append(list: MutableSequence[Any], new_element: Any) -> int: - for i in range(len(list)): - if is_zero(list[i]): - list[i] = new_element - return i - list.append(new_element) - return len(list) - 1 -``` ## Per-block processing -### Operations +### Custody Game Operations -Add the following operations to the per-block processing, in the order given below and after all other operations in Phase 0. +```python +def process_custody_game_operations(state: BeaconState, body: BeaconBlockBody) -> None: + def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: + for operation in operations: + fn(state, operation) + + for_ops(body.custody_key_reveals, process_custody_key_reveal) + for_ops(body.early_derived_secret_reveals, process_early_derived_secret_reveal) + for_ops(body.custody_slashings, process_custody_slashing) +``` #### Custody key reveals -Verify that `len(block.body.custody_key_reveals) <= MAX_CUSTODY_KEY_REVEALS`. - -For each `reveal` in `block.body.custody_key_reveals`, run the following function: - ```python def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> None: """ @@ -423,7 +243,8 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> revealer = state.validators[reveal.revealer_index] epoch_to_sign = get_randao_epoch_for_custody_period(revealer.next_custody_secret_to_reveal, reveal.revealer_index) - assert revealer.next_custody_secret_to_reveal < get_custody_period_for_validator(state, reveal.revealer_index) + custody_reveal_period = get_custody_period_for_validator(reveal.revealer_index, get_current_epoch(state)) + assert revealer.next_custody_secret_to_reveal < custody_reveal_period # Revealed validator is active or exited, but not withdrawn assert is_slashable_validator(revealer, get_current_epoch(state)) @@ -448,7 +269,7 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> # Process reveal revealer.next_custody_secret_to_reveal += 1 - # Reward Block Preposer + # Reward Block Proposer proposer_index = get_beacon_proposer_index(state) increase_balance( state, @@ -459,10 +280,6 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> #### Early derived secret reveals -Verify that `len(block.body.early_derived_secret_reveals) <= MAX_EARLY_DERIVED_SECRET_REVEALS`. - -For each `reveal` in `block.body.early_derived_secret_reveals`, run the following function: - ```python def process_early_derived_secret_reveal(state: BeaconState, reveal: EarlyDerivedSecretReveal) -> None: """ @@ -520,252 +337,95 @@ def process_early_derived_secret_reveal(state: BeaconState, reveal: EarlyDerived state.exposed_derived_secrets[derived_secret_location].append(reveal.revealed_index) ``` -#### Chunk challenges - -Verify that `len(block.body.custody_chunk_challenges) <= MAX_CUSTODY_CHUNK_CHALLENGES`. - -For each `challenge` in `block.body.custody_chunk_challenges`, run the following function: +#### Custody Slashings ```python -def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge) -> None: - # Verify the attestation - assert is_valid_indexed_attestation(state, get_indexed_attestation(state, challenge.attestation)) - # Verify it is not too late to challenge - assert (compute_epoch_at_slot(challenge.attestation.data.slot) - >= get_current_epoch(state) - MAX_CHUNK_CHALLENGE_DELAY) - responder = state.validators[challenge.responder_index] - assert responder.exit_epoch >= get_current_epoch(state) - MAX_CHUNK_CHALLENGE_DELAY - # Verify the responder participated in the attestation - attesters = get_attesting_indices(state, challenge.attestation.data, challenge.attestation.aggregation_bits) - assert challenge.responder_index in attesters - # Verify the challenge is not a duplicate - for record in state.custody_chunk_challenge_records: - assert ( - record.data_root != challenge.attestation.data.crosslink.data_root or - record.chunk_index != challenge.chunk_index - ) - # Verify depth - depth = ceillog2(get_custody_chunk_count(challenge.attestation.data.crosslink)) - assert challenge.chunk_index < 2**depth - # Add new chunk challenge record - new_record = CustodyChunkChallengeRecord( - challenge_index=state.custody_challenge_index, - challenger_index=get_beacon_proposer_index(state), - responder_index=challenge.responder_index, - inclusion_epoch=get_current_epoch(state), - data_root=challenge.attestation.data.crosslink.data_root, - depth=depth, - chunk_index=challenge.chunk_index, - ) - replace_empty_or_append(state.custody_chunk_challenge_records, new_record) - - state.custody_challenge_index += 1 - # Postpone responder withdrawability - responder.withdrawable_epoch = FAR_FUTURE_EPOCH -``` +def process_custody_slashing(state: BeaconState, signed_custody_slashing: SignedCustodySlashing) -> None: + custody_slashing = signed_custody_slashing.message + attestation = custody_slashing.attestation + + # Any signed custody-slashing should result in at least one slashing. + # If the custody bits are valid, then the claim itself is slashed. + malefactor = state.validators[custody_slashing.malefactor_index] + whistleblower = state.validators[custody_slashing.whistleblower_index] + domain = get_domain(state, DOMAIN_CUSTODY_BIT_SLASHING, get_current_epoch(state)) + signing_root = compute_signing_root(custody_slashing, domain) + assert bls.Verify(whistleblower.pubkey, signing_root, signed_custody_slashing.signature) + # Verify that the whistleblower is slashable + assert is_slashable_validator(whistleblower, get_current_epoch(state)) + # Verify that the claimed malefactor is slashable + assert is_slashable_validator(malefactor, get_current_epoch(state)) -#### Bit challenges + # Verify the attestation + assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) -Verify that `len(block.body.custody_bit_challenges) <= MAX_CUSTODY_BIT_CHALLENGES`. + # TODO: custody_slashing.data is not chunked like shard blocks yet, result is lots of padding. -For each `challenge` in `block.body.custody_bit_challenges`, run the following function: + # TODO: can do a single combined merkle proof of data being attested. + # Verify the shard transition is indeed attested by the attestation + shard_transition = custody_slashing.shard_transition + assert hash_tree_root(shard_transition) == attestation.shard_transition_root + # Verify that the provided data matches the shard-transition + shard_chunk_roots = shard_transition.shard_data_roots[custody_slashing.data_index] + assert hash_tree_root(custody_slashing.data) == chunks_to_body_root(shard_chunk_roots) -```python -def process_bit_challenge(state: BeaconState, challenge: CustodyBitChallenge) -> None: - attestation = challenge.attestation - epoch = attestation.data.target.epoch - shard = attestation.data.crosslink.shard - - # Verify challenge signature - challenger = state.validators[challenge.challenger_index] - domain = get_domain(state, DOMAIN_CUSTODY_BIT_CHALLENGE, get_current_epoch(state)) - # TODO incorrect hash-tree-root, but this changes with phase 1 PR #1483 - assert bls.Verify(challenger.pubkey, compute_signing_root(challenge, domain), challenge.signature) - # Verify challenger is slashable - assert is_slashable_validator(challenger, get_current_epoch(state)) - # Verify attestation - assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) - # Verify attestation is eligible for challenging - responder = state.validators[challenge.responder_index] - assert get_current_epoch(state) <= get_randao_epoch_for_custody_period( - get_custody_period_for_validator(state, challenge.responder_index, epoch), - challenge.responder_index - ) + 2 * EPOCHS_PER_CUSTODY_PERIOD + responder.max_reveal_lateness - - # Verify the responder participated in the attestation + # Verify existence and participation of claimed malefactor attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) - assert challenge.responder_index in attesters - # Verifier challenger is not already challenging - for record in state.custody_bit_challenge_records: - assert record.challenger_index != challenge.challenger_index - # Verify the responder custody key + assert custody_slashing.malefactor_index in attesters + + # Verify the malefactor custody key epoch_to_sign = get_randao_epoch_for_custody_period( - get_custody_period_for_validator(state, challenge.responder_index, epoch), - challenge.responder_index, + get_custody_period_for_validator(custody_slashing.malefactor_index, attestation.data.target.epoch), + custody_slashing.malefactor_index, ) domain = get_domain(state, DOMAIN_RANDAO, epoch_to_sign) - assert bls.Verify(responder.pubkey, compute_signing_root(epoch_to_sign, domain), challenge.responder_key) - # Verify the chunk count - chunk_count = get_custody_chunk_count(attestation.data.crosslink) - assert chunk_count == len(challenge.chunk_bits) - # Verify custody bit is incorrect - committee = get_beacon_committee(state, epoch, shard) - custody_bit = attestation.custody_bits[committee.index(challenge.responder_index)] - assert custody_bit != get_chunk_bits_root(challenge.chunk_bits) - # Add new bit challenge record - new_record = CustodyBitChallengeRecord( - challenge_index=state.custody_challenge_index, - challenger_index=challenge.challenger_index, - responder_index=challenge.responder_index, - inclusion_epoch=get_current_epoch(state), - data_root=attestation.data.crosslink.data_root, - chunk_count=chunk_count, - chunk_bits_merkle_root=hash_tree_root(challenge.chunk_bits), - responder_key=challenge.responder_key, - ) - replace_empty_or_append(state.custody_bit_challenge_records, new_record) - state.custody_challenge_index += 1 - # Postpone responder withdrawability - responder.withdrawable_epoch = FAR_FUTURE_EPOCH -``` - -#### Custody responses - -Verify that `len(block.body.custody_responses) <= MAX_CUSTODY_RESPONSES`. - -For each `response` in `block.body.custody_responses`, run the following function: - -```python -def process_custody_response(state: BeaconState, response: CustodyResponse) -> None: - chunk_challenge = next((record for record in state.custody_chunk_challenge_records - if record.challenge_index == response.challenge_index), None) - if chunk_challenge is not None: - return process_chunk_challenge_response(state, response, chunk_challenge) - - bit_challenge = next((record for record in state.custody_bit_challenge_records - if record.challenge_index == response.challenge_index), None) - if bit_challenge is not None: - return process_bit_challenge_response(state, response, bit_challenge) - - assert False -``` - -```python -def process_chunk_challenge_response(state: BeaconState, - response: CustodyResponse, - challenge: CustodyChunkChallengeRecord) -> None: - # Verify chunk index - assert response.chunk_index == challenge.chunk_index - # Verify bit challenge data is null - assert response.chunk_bits_branch == [] and response.chunk_bits_leaf == Bytes32() - # Verify minimum delay - assert get_current_epoch(state) >= challenge.inclusion_epoch + MAX_SEED_LOOKAHEAD - # Verify the chunk matches the crosslink data root - assert is_valid_merkle_branch( - leaf=hash_tree_root(response.chunk), - branch=response.data_branch, - depth=challenge.depth, - index=response.chunk_index, - root=challenge.data_root, - ) - # Clear the challenge - records = state.custody_chunk_challenge_records - records[records.index(challenge)] = CustodyChunkChallengeRecord() - # Reward the proposer - proposer_index = get_beacon_proposer_index(state) - increase_balance(state, proposer_index, Gwei(get_base_reward(state, proposer_index) // MINOR_REWARD_QUOTIENT)) + signing_root = compute_signing_root(epoch_to_sign, domain) + assert bls.Verify(malefactor.pubkey, signing_root, custody_slashing.malefactor_secret) + + # Get the custody bit + custody_bits = attestation.custody_bits_blocks[custody_slashing.data_index] + committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index) + claimed_custody_bit = custody_bits[committee.index(custody_slashing.malefactor_index)] + + # Compute the custody bit + computed_custody_bit = compute_custody_bit(custody_slashing.malefactor_secret, custody_slashing.data) + + # Verify the claim + if claimed_custody_bit != computed_custody_bit: + # Slash the malefactor, reward the other committee members + slash_validator(state, custody_slashing.malefactor_index) + others_count = len(committee) - 1 + whistleblower_reward = Gwei(malefactor.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT // others_count) + for attester_index in attesters: + if attester_index != custody_slashing.malefactor_index: + increase_balance(state, attester_index, whistleblower_reward) + # No special whisteblower reward: it is expected to be an attester. Others are free to slash too however. + else: + # The claim was false, the custody bit was correct. Slash the whistleblower that induced this work. + slash_validator(state, custody_slashing.whistleblower_index) ``` -```python -def process_bit_challenge_response(state: BeaconState, - response: CustodyResponse, - challenge: CustodyBitChallengeRecord) -> None: - # Verify chunk index - assert response.chunk_index < challenge.chunk_count - # Verify responder has not been slashed - responder = state.validators[challenge.responder_index] - assert not responder.slashed - # Verify the chunk matches the crosslink data root - assert is_valid_merkle_branch( - leaf=hash_tree_root(response.chunk), - branch=response.data_branch, - depth=ceillog2(challenge.chunk_count), - index=response.chunk_index, - root=challenge.data_root, - ) - # Verify the chunk bit leaf matches the challenge data - assert is_valid_merkle_branch_with_mixin( - leaf=hash_tree_root(response.chunk_bits_leaf), - branch=response.chunk_bits_branch, - depth=ceillog2(MAX_CUSTODY_CHUNKS // 256), - index=response.chunk_index // 256, - root=challenge.chunk_bits_merkle_root, - mixin=challenge.chunk_count, - ) - # Verify the chunk bit does not match the challenge chunk bit - assert (get_custody_chunk_bit(challenge.responder_key, response.chunk) - != response.chunk_bits_leaf[response.chunk_index % 256]) - # Clear the challenge - records = state.custody_bit_challenge_records - records[records.index(challenge)] = CustodyBitChallengeRecord() - # Slash challenger - slash_validator(state, challenge.challenger_index, challenge.responder_index) -``` ## Per-epoch processing -### Handling of custody-related deadlines +### Handling of reveal deadlines -Run `process_reveal_deadlines(state)` immediately after `process_registry_updates(state)`: +Run `process_reveal_deadlines(state)` after `process_registry_updates(state)`: ```python -# begin insert @process_reveal_deadlines - process_reveal_deadlines(state) -# end insert @process_reveal_deadlines def process_reveal_deadlines(state: BeaconState) -> None: + epoch = get_current_epoch(state) for index, validator in enumerate(state.validators): - deadline = validator.next_custody_secret_to_reveal + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD) - if get_custody_period_for_validator(state, ValidatorIndex(index)) > deadline: + if get_custody_period_for_validator(ValidatorIndex(index), epoch) > validator.next_custody_secret_to_reveal: slash_validator(state, ValidatorIndex(index)) ``` -Run `process_challenge_deadlines(state)` immediately after `process_reveal_deadlines(state)`: - -```python -# begin insert @process_challenge_deadlines - process_challenge_deadlines(state) -# end insert @process_challenge_deadlines -def process_challenge_deadlines(state: BeaconState) -> None: - for custody_chunk_challenge in state.custody_chunk_challenge_records: - if get_current_epoch(state) > custody_chunk_challenge.inclusion_epoch + CUSTODY_RESPONSE_DEADLINE: - slash_validator(state, custody_chunk_challenge.responder_index, custody_chunk_challenge.challenger_index) - records = state.custody_chunk_challenge - records[records.index(custody_chunk_challenge)] = CustodyChunkChallengeRecord() - - for custody_bit_challenge in state.custody_bit_challenge_records: - if get_current_epoch(state) > custody_bit_challenge.inclusion_epoch + CUSTODY_RESPONSE_DEADLINE: - slash_validator(state, custody_bit_challenge.responder_index, custody_bit_challenge.challenger_index) - records = state.custody_bit_challenge_records - records[records.index(custody_bit_challenge)] = CustodyBitChallengeRecord() -``` +### Final updates -Append this to `process_final_updates(state)`: +After `process_final_updates(state)`, additional updates are made for the custody game: ```python -# begin insert @after_process_final_updates - after_process_final_updates(state) -# end insert @after_process_final_updates -def after_process_final_updates(state: BeaconState) -> None: - current_epoch = get_current_epoch(state) +def process_custody_final_updates(state: BeaconState) -> None: # Clean up exposed RANDAO key reveals - state.exposed_derived_secrets[current_epoch % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS] = [] - # Reset withdrawable epochs if challenge records are empty - records = state.custody_chunk_challenge_records + state.custody_bit_challenge_records - validator_indices_in_records = set( - [record.challenger_index for record in records] + [record.responder_index for record in records] - ) - for index, validator in enumerate(state.validators): - if index not in validator_indices_in_records: - if validator.exit_epoch != FAR_FUTURE_EPOCH and validator.withdrawable_epoch == FAR_FUTURE_EPOCH: - validator.withdrawable_epoch = Epoch(validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY) + state.exposed_derived_secrets[get_current_epoch(state) % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS] = [] ``` diff --git a/specs/phase1/fork-choice.md b/specs/phase1/fork-choice.md new file mode 100644 index 0000000000..d8bf7fa090 --- /dev/null +++ b/specs/phase1/fork-choice.md @@ -0,0 +1,52 @@ +# Ethereum 2.0 Phase 1 -- Beacon Chain Fork Choice + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Fork choice](#fork-choice) + - [Handlers](#handlers) + + + + +## Introduction + +This document is the beacon chain fork choice spec for part of Ethereum 2.0 Phase 1. + +## Fork choice + +Due to the changes in the structure of `IndexedAttestation` in Phase 1, `on_attestation` must be re-specified to handle this. The bulk of `on_attestation` has been moved out into a few helpers to reduce code duplication where possible. + +The rest of the fork choice remains stable. + +### Handlers + +```python +def on_attestation(store: Store, attestation: Attestation) -> 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) + 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 + attesting_indices = [ + index for i, index in enumerate(indexed_attestation.committee) + if attestation.aggregation_bits[i] + ] + update_latest_messages(store, attesting_indices, attestation) +``` \ No newline at end of file diff --git a/specs/phase1/fraud-proofs.md b/specs/phase1/fraud-proofs.md new file mode 100644 index 0000000000..c9368ad2c3 --- /dev/null +++ b/specs/phase1/fraud-proofs.md @@ -0,0 +1,70 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Ethereum 2.0 Phase 1 -- Shard Transition and Fraud Proofs](#ethereum-20-phase-1----shard-transition-and-fraud-proofs) + - [Table of contents](#table-of-contents) + - [Introduction](#introduction) + - [Fraud proofs](#fraud-proofs) + - [Shard state transition function](#shard-state-transition-function) + - [Honest committee member behavior](#honest-committee-member-behavior) + + + +# Ethereum 2.0 Phase 1 -- Shard Transition and Fraud Proofs + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + TODO + + + +## Introduction + +This document describes the shard transition function and fraud proofs as part of Phase 1 of Ethereum 2.0. + +## Fraud proofs + +TODO. The intent is to have a single universal fraud proof type, which contains the following parts: + +1. An on-time attestation on some `shard` signing a `ShardTransition` +2. An index `i` of a particular position to focus on +3. The `ShardTransition` itself +4. The full body of the block +5. A Merkle proof to the `shard_states` in the parent block the attestation is referencing + +The proof verifies that one of the two conditions is false: + +1. `custody_bits[i][j] != generate_custody_bit(subkey, block_contents)` for any `j` +2. `execute_state_transition(shard, slot, transition.shard_states[i-1].data, hash_tree_root(parent), get_shard_proposer_index(state, shard, slot), block_contents) != transition.shard_states[i].data` (if `i=0` then instead use `parent.shard_states[shard][-1].data`) + +## Shard state transition function + +```python +def shard_state_transition(shard: Shard, + slot: Slot, + pre_state: Root, + previous_beacon_root: Root, + proposer_pubkey: BLSPubkey, + block_data: ByteVector[MAX_SHARD_BLOCK_CHUNKS * SHARD_BLOCK_CHUNK_SIZE]) -> Root: + # We will add something more substantive in phase 2 + return hash(pre_state + hash_tree_root(previous_beacon_root) + hash_tree_root(block_data)) +``` + +## Honest committee member behavior + +Suppose you are a committee member on shard `shard` at slot `current_slot`. Let `state` be the head beacon state you are building on, and let `QUARTER_PERIOD = SECONDS_PER_SLOT // 4`. `2 * QUARTER_PERIOD` seconds into slot `slot`, run the following procedure: + +* Initialize `proposals = []`, `shard_states = []`, `shard_state = state.shard_states[shard][-1]`, `start_slot = shard_state.slot`. +* For `slot in get_offset_slots(state, start_slot)`, do the following: + * Look for all valid proposals for `slot`; that is, a Bytes `proposal` where `shard_state_transition(shard, slot, shard_state, get_block_root_at_slot(state, state.slot - 1), get_shard_proposer_index(state, shard, slot), proposal)` returns a result and does not throw an exception. Let `choices` be the set of non-empty valid proposals you discover. + * If `len(choices) == 0`, do `proposals.append(make_empty_proposal(shard_state, slot))` + * If `len(choices) == 1`, do `proposals.append(choices[0])` + * If `len(choices) > 1`, let `winning_proposal` be the proposal with the largest number of total attestations from slots in `state.shard_next_slots[shard]....slot-1` supporting it or any of its descendants, breaking ties by choosing the first proposal locally seen. Do `proposals.append(winning_proposal)`. + * If `proposals[-1]` is NOT an empty proposal, set `shard_state = shard_state_transition(shard, slot, shard_state, get_block_root_at_slot(state, state.slot - 1), get_shard_proposer_index(state, shard, slot), proposals[-1])` and do `shard_states.append(shard_state)`. If it is an empty proposal, leave `shard_state` unchanged. + +Make an attestation using `shard_data_roots = [hash_tree_root(proposal) for proposal in proposals]` and `shard_state_roots = shard_states`. diff --git a/specs/phase1/light-client-sync.md b/specs/phase1/light-client-sync.md index 5a74101800..4d14485dd8 100644 --- a/specs/phase1/light-client-sync.md +++ b/specs/phase1/light-client-sync.md @@ -49,7 +49,7 @@ We define the following Python custom types for type hinting and readability: ### `LightClientUpdate` ```python -class LightClientUpdate(container): +class LightClientUpdate(Container): # Shard block root (and authenticating signature data) shard_block_root: Root fork_version: Version diff --git a/specs/phase1/phase1-fork.md b/specs/phase1/phase1-fork.md new file mode 100644 index 0000000000..56eee410b0 --- /dev/null +++ b/specs/phase1/phase1-fork.md @@ -0,0 +1,121 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Ethereum 2.0 Phase 1 -- From Phase 0 to Phase 1](#ethereum-20-phase-1----from-phase-0-to-phase-1) + - [Table of contents](#table-of-contents) + - [Introduction](#introduction) + - [Configuration](#configuration) + - [Fork to Phase 1](#fork-to-phase-1) + - [Fork trigger.](#fork-trigger) + - [Upgrading the state](#upgrading-the-state) + + + +# Ethereum 2.0 Phase 1 -- From Phase 0 to Phase 1 + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + TODO + + + +## Introduction + +This document describes the process of moving from Phase 0 to Phase 1 of Ethereum 2.0. + +## Configuration + +Warning: this configuration is not definitive. + +| Name | Value | +| - | - | - | +| `PHASE_1_FORK_VERSION` | `Version('0x01000000')` | +| `INITIAL_ACTIVE_SHARDS` | `2**6` (= 64) | +| `INITIAL_GASPRICE` | `Gwei(10)` | + +## Fork to Phase 1 + +### Fork trigger. + +TBD. Social consensus, along with state conditions such as epoch boundary, finality, deposits, active validator count, etc. may be part of the decision process to trigger the fork. + +### Upgrading the state + +After `process_slots` of Phase 0 finishes, but before the first Phase 1 block is processed, an irregular state change is made to upgrade to Phase 1. + +```python +def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState: + epoch = get_current_epoch(pre) + post = BeaconState( + genesis_time=pre.genesis_time, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + current_version=PHASE_1_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=List[Validator, VALIDATOR_REGISTRY_LIMIT]( + Validator( + pubkey=phase0_validator.pubkey, + withdrawal_credentials=phase0_validator.withdrawal_credentials, + effective_balance=phase0_validator.effective_balance, + slashed=phase0_validator.slashed, + activation_eligibility_epoch=phase0_validator.activation_eligibility_epoch, + activation_epoch=phase0_validator.activation_eligibility_epoch, + exit_epoch=phase0_validator.exit_epoch, + withdrawable_epoch=phase0_validator.withdrawable_epoch, + next_custody_secret_to_reveal=get_custody_period_for_validator(ValidatorIndex(i), epoch), + max_reveal_lateness=0, # TODO custody refactor. Outdated? + ) for i, phase0_validator in enumerate(pre.validators) + ), + balances=pre.balances, + # Randomness + randao_mixes=pre.randao_mixes, + # Slashings + slashings=pre.slashings, + # Attestations + # previous_epoch_attestations is cleared on upgrade. + previous_epoch_attestations=List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH](), + # empty in pre state, since the upgrade is performed just after an epoch boundary. + current_epoch_attestations=List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH](), + # 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, + # Phase 1 + shard_states=List[ShardState, MAX_SHARDS]( + ShardState( + slot=pre.slot, + gasprice=INITIAL_GASPRICE, + data=Root(), + latest_block_root=Root(), + ) for i in range(INITIAL_ACTIVE_SHARDS) + ), + online_countdown=[ONLINE_PERIOD] * len(pre.validators), # all online + current_light_committee=CompactCommittee(), # computed after state creation + next_light_committee=CompactCommittee(), + # Custody game + custody_challenge_index=0, + # exposed_derived_secrets will fully default to zeroes + ) + next_epoch = Epoch(epoch + 1) + post.current_light_committee = committee_to_compact_committee(post, get_light_client_committee(post, epoch)) + post.next_light_committee = committee_to_compact_committee(post, get_light_client_committee(post, next_epoch)) + return post +``` diff --git a/specs/phase1/shard-data-chains.md b/specs/phase1/shard-data-chains.md deleted file mode 100644 index e04e3ba5e4..0000000000 --- a/specs/phase1/shard-data-chains.md +++ /dev/null @@ -1,444 +0,0 @@ -# Ethereum 2.0 Phase 1 -- Shard Data Chains - -**Notice**: This document is a work-in-progress for researchers and implementers. - -## Table of contents - - - - - - -- [Introduction](#introduction) -- [Custom types](#custom-types) -- [Configuration](#configuration) - - [Misc](#misc) - - [Initial values](#initial-values) - - [Time parameters](#time-parameters) - - [State list lengths](#state-list-lengths) - - [Rewards and penalties](#rewards-and-penalties) - - [Signature domain types](#signature-domain-types) -- [Containers](#containers) - - [`Crosslink`](#crosslink) - - [`ShardBlock`](#shardblock) - - [`ShardBlockHeader`](#shardblockheader) - - [`ShardState`](#shardstate) - - [`ShardAttestationData`](#shardattestationdata) -- [Helper functions](#helper-functions) - - [Misc](#misc-1) - - [`compute_epoch_of_shard_slot`](#compute_epoch_of_shard_slot) - - [`compute_shard_period_start_epoch`](#compute_shard_period_start_epoch) - - [Beacon state accessors](#beacon-state-accessors) - - [`get_period_committee`](#get_period_committee) - - [`get_shard_committee`](#get_shard_committee) - - [`get_shard_proposer_index`](#get_shard_proposer_index) - - [Shard state mutators](#shard-state-mutators) - - [`process_delta`](#process_delta) -- [Genesis](#genesis) - - [`get_genesis_shard_state`](#get_genesis_shard_state) - - [`get_genesis_shard_block`](#get_genesis_shard_block) -- [Shard state transition function](#shard-state-transition-function) - - [Period processing](#period-processing) - - [Block processing](#block-processing) - - [Block header](#block-header) - - [Attestations](#attestations) - - [Block body](#block-body) -- [Shard fork choice rule](#shard-fork-choice-rule) - - - - -## Introduction - -This document describes the shard transition function (data layer only) and the shard fork choice rule as part of Phase 1 of Ethereum 2.0. - -## Custom types - -| Name | SSZ equivalent | Description | -| - | - | - | -| `Shard` | `uint64` | a shard number | -| `ShardSlot` | `uint64` | a shard slot number | - -## Configuration - -### Misc - -| Name | Value | -| - | - | -| `SHARD_COUNT` | `2**10` (= 1,024) | -| `MIN_BLOCK_BODY_PRICE` | `2**0` (= 1) | -| `MAX_PERIOD_COMMITTEE_SIZE` | `2**7` (= 128) | -| `SHARD_HEADER_SIZE` | `2**10` (= 1024) | -| `SHARD_BLOCK_SIZE_TARGET` | `2**14` (= 16,384) | -| `MAX_SHARD_BLOCK_SIZE` | `2**16` (= 65,536) | - -### Initial values - -| Name | Value | Unit | -| - | - | -| `SHARD_GENESIS_EPOCH` | **TBD** | Epoch | - -### Time parameters - -| Name | Value | Unit | Duration | -| - | - | :-: | :-: | -| `SHARD_SLOTS_PER_EPOCH` | `2**7` (= 128) | shard slots | 6.4 minutes | -| `EPOCHS_PER_SHARD_PERIOD` | `2**8` (= 256) | epochs | ~27 hours | - -### State list lengths - -| Name | Value | -| - | - | -| `HISTORY_ACCUMULATOR_DEPTH` | `2**6` (= 64) | - -### Rewards and penalties - -| Name | Value | -| - | - | -| `BLOCK_BODY_PRICE_QUOTIENT` | `2**3` (= 8) | - -### Signature domain types - -| Name | Value | -| - | - | -| `DOMAIN_SHARD_PROPOSER` | `DomainType('0x80000000')` | -| `DOMAIN_SHARD_ATTESTER` | `DomainType('0x81000000')` | - -## Containers - -### `Crosslink` - -```python -# Crosslink is a placeholder to appease the build script until phase 1 is reworked -class Crosslink(Container): - shard: Shard -``` - -### `ShardBlock` - -```python -class ShardBlock(Container): - shard: Shard - slot: ShardSlot - beacon_block_root: Root - parent_root: Root - state_root: Root - body: List[byte, MAX_SHARD_BLOCK_SIZE - SHARD_HEADER_SIZE] - block_size_sum: uint64 - aggregation_bits: Bitvector[2 * MAX_PERIOD_COMMITTEE_SIZE] - attestations: BLSSignature - signature: BLSSignature -``` - -### `ShardBlockHeader` - -```python -class ShardBlockHeader(Container): - shard: Shard - slot: ShardSlot - beacon_block_root: Root - parent_root: Root - state_root: Root - body_root: Root - block_size_sum: uint64 - aggregation_bits: Bitvector[2 * MAX_PERIOD_COMMITTEE_SIZE] - attestations: BLSSignature - signature: BLSSignature -``` - -### `ShardState` - -```python -class ShardState(Container): - shard: Shard - slot: ShardSlot - history_accumulator: Vector[Bytes32, HISTORY_ACCUMULATOR_DEPTH] - latest_block_header: ShardBlockHeader - block_size_sum: uint64 - # Fees and rewards - block_body_price: Gwei - older_committee_positive_deltas: Vector[Gwei, MAX_PERIOD_COMMITTEE_SIZE] - older_committee_negative_deltas: Vector[Gwei, MAX_PERIOD_COMMITTEE_SIZE] - newer_committee_positive_deltas: Vector[Gwei, MAX_PERIOD_COMMITTEE_SIZE] - newer_committee_negative_deltas: Vector[Gwei, MAX_PERIOD_COMMITTEE_SIZE] -``` - -### `ShardAttestationData` - -```python -class ShardAttestationData(Container): - slot: ShardSlot - parent_root: Root -``` - -## Helper functions - -### Misc - -#### `compute_epoch_of_shard_slot` - -```python -def compute_epoch_of_shard_slot(slot: ShardSlot) -> Epoch: - return Epoch(slot // SHARD_SLOTS_PER_EPOCH) -``` - -#### `compute_shard_period_start_epoch` - -```python -def compute_shard_period_start_epoch(epoch: Epoch, lookback: uint64) -> Epoch: - return Epoch(epoch - (epoch % EPOCHS_PER_SHARD_PERIOD) - lookback * EPOCHS_PER_SHARD_PERIOD) -``` - -### Beacon state accessors - -#### `get_period_committee` - -```python -def get_period_committee(beacon_state: BeaconState, shard: Shard, epoch: Epoch) -> Sequence[ValidatorIndex]: - active_validator_indices = get_active_validator_indices(beacon_state, epoch) - seed = get_seed(beacon_state, epoch, DOMAIN_SHARD_ATTESTER) - return compute_committee(active_validator_indices, seed, shard, SHARD_COUNT)[:MAX_PERIOD_COMMITTEE_SIZE] -``` - -#### `get_shard_committee` - -```python -def get_shard_committee(beacon_state: BeaconState, shard: Shard, epoch: Epoch) -> Sequence[ValidatorIndex]: - older_committee = get_period_committee(beacon_state, shard, compute_shard_period_start_epoch(epoch, 2)) - newer_committee = get_period_committee(beacon_state, shard, compute_shard_period_start_epoch(epoch, 1)) - # Every epoch cycle out validators from the older committee and cycle in validators from the newer committee - older_subcommittee = [i for i in older_committee if i % EPOCHS_PER_SHARD_PERIOD > epoch % EPOCHS_PER_SHARD_PERIOD] - newer_subcommittee = [i for i in newer_committee if i % EPOCHS_PER_SHARD_PERIOD <= epoch % EPOCHS_PER_SHARD_PERIOD] - return older_subcommittee + newer_subcommittee -``` - -#### `get_shard_proposer_index` - -```python -def get_shard_proposer_index(beacon_state: BeaconState, shard: Shard, slot: ShardSlot) -> ValidatorIndex: - epoch = get_current_epoch(beacon_state) - shard_committee = get_shard_committee(beacon_state, shard, epoch) - active_indices = [i for i in shard_committee if is_active_validator(beacon_state.validators[i], epoch)] - assert any(active_indices) - - epoch_seed = get_seed(beacon_state, epoch, DOMAIN_SHARD_PROPOSER) - seed = hash(epoch_seed + int_to_bytes(slot, length=8) + int_to_bytes(shard, length=8)) - return compute_proposer_index(beacon_state, active_indices, seed) -``` - -### Shard state mutators - -#### `process_delta` - -```python -def process_delta(beacon_state: BeaconState, - shard_state: ShardState, - index: ValidatorIndex, - delta: Gwei, - positive: bool=True) -> None: - epoch = compute_epoch_of_shard_slot(shard_state.slot) - older_committee = get_period_committee(beacon_state, shard_state.shard, compute_shard_period_start_epoch(epoch, 2)) - newer_committee = get_period_committee(beacon_state, shard_state.shard, compute_shard_period_start_epoch(epoch, 1)) - if index in older_committee: - if positive: - shard_state.older_committee_positive_deltas[older_committee.index(index)] += delta - else: - shard_state.older_committee_negative_deltas[older_committee.index(index)] += delta - elif index in newer_committee: - if positive: - shard_state.newer_committee_positive_deltas[newer_committee.index(index)] += delta - else: - shard_state.newer_committee_negative_deltas[newer_committee.index(index)] += delta -``` - -## Genesis - -### `get_genesis_shard_state` - -```python -def get_genesis_shard_state(shard: Shard) -> ShardState: - return ShardState( - shard=shard, - slot=ShardSlot(SHARD_GENESIS_EPOCH * SHARD_SLOTS_PER_EPOCH), - latest_block_header=ShardBlockHeader( - shard=shard, - slot=ShardSlot(SHARD_GENESIS_EPOCH * SHARD_SLOTS_PER_EPOCH), - body_root=hash_tree_root(List[byte, MAX_SHARD_BLOCK_SIZE - SHARD_HEADER_SIZE]()), - ), - block_body_price=MIN_BLOCK_BODY_PRICE, - ) -``` - -### `get_genesis_shard_block` - -```python -def get_genesis_shard_block(shard: Shard) -> ShardBlock: - return ShardBlock( - shard=shard, - slot=ShardSlot(SHARD_GENESIS_EPOCH * SHARD_SLOTS_PER_EPOCH), - state_root=hash_tree_root(get_genesis_shard_state(shard)), - ) -``` - -## Shard state transition function - -```python -def shard_state_transition(beacon_state: BeaconState, - shard_state: ShardState, - block: ShardBlock, - validate_state_root: bool=False) -> ShardState: - # Process slots (including those with no blocks) since block - process_shard_slots(shard_state, block.slot) - # Process block - process_shard_block(beacon_state, shard_state, block) - # Validate state root (`validate_state_root == True` in production) - if validate_state_root: - assert block.state_root == hash_tree_root(shard_state) - # Return post-state - return shard_state -``` - -```python -def process_shard_slots(shard_state: ShardState, slot: ShardSlot) -> None: - assert shard_state.slot <= slot - while shard_state.slot < slot: - process_shard_slot(shard_state) - # Process shard period on the start slot of the next shard period - if (shard_state.slot + 1) % (SHARD_SLOTS_PER_EPOCH * EPOCHS_PER_SHARD_PERIOD) == 0: - process_shard_period(shard_state) - shard_state.slot += ShardSlot(1) -``` - -```python -def process_shard_slot(shard_state: ShardState) -> None: - # Cache state root - previous_state_root = hash_tree_root(shard_state) - if shard_state.latest_block_header.state_root == Bytes32(): - shard_state.latest_block_header.state_root = previous_state_root - # Cache state root in history accumulator - depth = 0 - while shard_state.slot % 2**depth == 0 and depth < HISTORY_ACCUMULATOR_DEPTH: - shard_state.history_accumulator[depth] = previous_state_root - depth += 1 -``` - -### Period processing - -```python -def process_shard_period(shard_state: ShardState) -> None: - # Rotate committee deltas - shard_state.older_committee_positive_deltas = shard_state.newer_committee_positive_deltas - shard_state.older_committee_negative_deltas = shard_state.newer_committee_negative_deltas - shard_state.newer_committee_positive_deltas = [Gwei(0) for _ in range(MAX_PERIOD_COMMITTEE_SIZE)] - shard_state.newer_committee_negative_deltas = [Gwei(0) for _ in range(MAX_PERIOD_COMMITTEE_SIZE)] -``` - -### Block processing - -```python -def process_shard_block(beacon_state: BeaconState, shard_state: ShardState, block: ShardBlock) -> None: - process_shard_block_header(beacon_state, shard_state, block) - process_shard_attestations(beacon_state, shard_state, block) - process_shard_block_body(beacon_state, shard_state, block) -``` - -#### Block header - -```python -def process_shard_block_header(beacon_state: BeaconState, shard_state: ShardState, block: ShardBlock) -> None: - # Verify the shard number - assert block.shard == shard_state.shard - # Verify the slot number - assert block.slot == shard_state.slot - # Verify the beacon chain root - epoch = compute_epoch_of_shard_slot(shard_state.slot) - assert epoch * SLOTS_PER_EPOCH == beacon_state.slot - beacon_block_header = BeaconBlockHeader( - slot=beacon_state.latest_block_header.slot, - parent_root=beacon_state.latest_block_header.parent_root, - state_root=beacon_state.latest_block_header.state_root, - body_root=beacon_state.latest_block_header.body_root, - ) - if beacon_block_header.state_root == Bytes32(): - beacon_block_header.state_root = hash_tree_root(beacon_state) - assert block.beacon_block_root == hash_tree_root(beacon_block_header) - # Verify the parent root - assert block.parent_root == hash_tree_root(shard_state.latest_block_header) - # Save current block as the new latest block - shard_state.latest_block_header = ShardBlockHeader( - shard=block.shard, - slot=block.slot, - beacon_block_root=block.beacon_block_root, - parent_root=block.parent_root, - # `state_root` is zeroed and overwritten in the next `process_shard_slot` call - body_root=hash_tree_root(block.body), - block_size_sum=block.block_size_sum, - aggregation_bits=block.aggregation_bits, - attestations=block.attestations, - # `signature` is zeroed - ) - # Verify the sum of the block sizes since genesis - shard_state.block_size_sum += SHARD_HEADER_SIZE + len(block.body) - assert block.block_size_sum == shard_state.block_size_sum - # Verify proposer is not slashed - proposer_index = get_shard_proposer_index(beacon_state, shard_state.shard, block.slot) - proposer = beacon_state.validators[proposer_index] - assert not proposer.slashed - # Verify proposer signature - domain = get_domain(beacon_state, DOMAIN_SHARD_PROPOSER, compute_epoch_of_shard_slot(block.slot)) - assert bls.Verify(proposer.pubkey, compute_signing_root(block, domain), block.signature) -``` - -#### Attestations - -```python -def process_shard_attestations(beacon_state: BeaconState, shard_state: ShardState, block: ShardBlock) -> None: - pubkeys = [] - attestation_count = 0 - shard_committee = get_shard_committee(beacon_state, shard_state.shard, block.slot) - for i, validator_index in enumerate(shard_committee): - if block.aggregation_bits[i]: - pubkeys.append(beacon_state.validators[validator_index].pubkey) - process_delta(beacon_state, shard_state, validator_index, get_base_reward(beacon_state, validator_index)) - attestation_count += 1 - # Verify there are no extraneous bits set beyond the shard committee - for i in range(len(shard_committee), 2 * MAX_PERIOD_COMMITTEE_SIZE): - assert block.aggregation_bits[i] == 0b0 - # Verify attester aggregate signature - domain = get_domain(beacon_state, DOMAIN_SHARD_ATTESTER, compute_epoch_of_shard_slot(block.slot)) - shard_attestation_data = ShardAttestationData(slot=shard_state.slot, parent_root=block.parent_root) - signing_root = compute_signing_root(shard_attestation_data, domain) - assert bls.FastAggregateVerify(pubkeys, signing_root, block.attestations) - # Proposer micro-reward - proposer_index = get_shard_proposer_index(beacon_state, shard_state.shard, block.slot) - reward = attestation_count * get_base_reward(beacon_state, proposer_index) // PROPOSER_REWARD_QUOTIENT - process_delta(beacon_state, shard_state, proposer_index, Gwei(reward)) -``` - -#### Block body - -```python -def process_shard_block_body(beacon_state: BeaconState, shard_state: ShardState, block: ShardBlock) -> None: - # Verify block body size is a multiple of the header size - assert len(block.body) % SHARD_HEADER_SIZE == 0 - # Apply proposer block body fee - block_body_fee = shard_state.block_body_price * len(block.body) // MAX_SHARD_BLOCK_SIZE - proposer_index = get_shard_proposer_index(beacon_state, shard_state.shard, block.slot) - process_delta(beacon_state, shard_state, proposer_index, Gwei(block_body_fee), positive=False) # Burn - process_delta(beacon_state, shard_state, proposer_index, Gwei(block_body_fee // PROPOSER_REWARD_QUOTIENT)) # Reward - # Calculate new block body price - block_size = SHARD_HEADER_SIZE + len(block.body) - QUOTIENT = MAX_SHARD_BLOCK_SIZE * BLOCK_BODY_PRICE_QUOTIENT - if block_size > SHARD_BLOCK_SIZE_TARGET: - price_delta = Gwei(shard_state.block_body_price * (block_size - SHARD_BLOCK_SIZE_TARGET) // QUOTIENT) - # The maximum block body price caps the amount burnt on fees within a shard period - MAX_BLOCK_BODY_PRICE = MAX_EFFECTIVE_BALANCE // EPOCHS_PER_SHARD_PERIOD // SHARD_SLOTS_PER_EPOCH - shard_state.block_body_price = Gwei(min(MAX_BLOCK_BODY_PRICE, shard_state.block_body_price + price_delta)) - else: - price_delta = Gwei(shard_state.block_body_price * (SHARD_BLOCK_SIZE_TARGET - block_size) // QUOTIENT) - shard_state.block_body_price = Gwei(max(MIN_BLOCK_BODY_PRICE, shard_state.block_body_price + price_delta)) -``` - -## Shard fork choice rule - -The fork choice rule for any shard is LMD GHOST using the shard attestations of the shard committee and the beacon chain attestations of the crosslink committee currently assigned to that shard, but instead of being rooted in the genesis it is rooted in the block referenced in the most recent accepted crosslink (i.e. `beacon_state.crosslinks[shard].shard_block_root`). Only blocks whose `beacon_block_root` is the block in the main beacon chain at the specified `slot` should be considered. (If the beacon chain skips a slot, then the block at that slot is considered to be the block in the beacon chain at the highest slot lower than that slot.) diff --git a/tests/core/config_helpers/preset_loader/loader.py b/tests/core/config_helpers/preset_loader/loader.py index 9d75932df6..95f147f6e0 100644 --- a/tests/core/config_helpers/preset_loader/loader.py +++ b/tests/core/config_helpers/preset_loader/loader.py @@ -18,7 +18,9 @@ def load_presets(configs_dir, presets_name) -> Dict[str, Any]: loaded = yaml.load(path) out = dict() for k, v in loaded.items(): - if v.startswith("0x"): + if isinstance(v, list): + out[k] = v + elif isinstance(v, str) and v.startswith("0x"): out[k] = bytes.fromhex(v[2:]) else: out[k] = int(v) diff --git a/tests/core/pyspec/__init__.py b/tests/core/pyspec/eth2spec/config/__init__.py similarity index 100% rename from tests/core/pyspec/__init__.py rename to tests/core/pyspec/eth2spec/config/__init__.py diff --git a/tests/core/pyspec/eth2spec/config/apply_config.py b/tests/core/pyspec/eth2spec/config/apply_config.py new file mode 100644 index 0000000000..2f0ce59021 --- /dev/null +++ b/tests/core/pyspec/eth2spec/config/apply_config.py @@ -0,0 +1,22 @@ +from preset_loader import loader +from typing import Dict, Any + +presets: Dict[str, Any] = {} + + +# Access to overwrite spec constants based on configuration +# This is called by the spec module after declaring its globals, and applies the loaded presets. +def apply_constants_preset(spec_globals: Dict[str, Any]) -> None: + global presets + for k, v in presets.items(): + if k.startswith('DOMAIN_'): + spec_globals[k] = spec_globals['DomainType'](v) # domain types are defined as bytes in the configs + else: + spec_globals[k] = v + + +# Load presets from a file. This does not apply the presets. +# To apply the presets, reload the spec module (it will re-initialize with the presets taken from here). +def load_presets(configs_path, config_name): + global presets + presets = loader.load_presets(configs_path, config_name) diff --git a/tests/core/pyspec/eth2spec/test/conftest.py b/tests/core/pyspec/eth2spec/test/conftest.py index 1d299042a9..9e9252d2e8 100644 --- a/tests/core/pyspec/eth2spec/test/conftest.py +++ b/tests/core/pyspec/eth2spec/test/conftest.py @@ -1,5 +1,5 @@ -from eth2spec.phase0 import spec as spec_phase0 -from eth2spec.phase1 import spec as spec_phase1 +from eth2spec.config import apply_config +from eth2spec.test.context import reload_specs # We import pytest only when it's present, i.e. when we are running tests. # The test-cases themselves can be generated without installing pytest. @@ -33,7 +33,6 @@ def pytest_addoption(parser): @fixture(autouse=True) def config(request): config_name = request.config.getoption("--config") - from preset_loader import loader - presets = loader.load_presets('../../../configs/', config_name) - spec_phase0.apply_constants_preset(presets) - spec_phase1.apply_constants_preset(presets) + apply_config.load_presets('../../../configs/', config_name) + # now that the presets are loaded, reload the specs to apply them + reload_specs() diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 3177cd0b84..6134243df0 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -1,29 +1,64 @@ from eth2spec.phase0 import spec as spec_phase0 -# from eth2spec.phase1 import spec as spec_phase1 +from eth2spec.phase1 import spec as spec_phase1 from eth2spec.utils import bls from .helpers.genesis import create_genesis_state from .utils import vector_test, with_meta_tags -from typing import Any, Callable, Sequence +from typing import Any, Callable, Sequence, TypedDict, Protocol + +from importlib import reload + + +def reload_specs(): + reload(spec_phase0) + reload(spec_phase1) + + +# Some of the Spec module functionality is exposed here to deal with phase-specific changes. + +# TODO: currently phases are defined as python modules. +# It would be better if they would be more well-defined interfaces for stronger typing. +class Spec(Protocol): + version: str + + +class Phase0(Spec): + ... + + +class Phase1(Spec): + def upgrade_to_phase1(self, state: spec_phase0.BeaconState) -> spec_phase1.BeaconState: + ... + + +# add transfer, bridge, etc. as the spec evolves +class SpecForks(TypedDict, total=False): + phase0: Phase0 + phase1: Phase1 def with_custom_state(balances_fn: Callable[[Any], Sequence[int]], threshold_fn: Callable[[Any], int]): def deco(fn): - def entry(*args, **kw): + def entry(*args, spec: Spec, phases: SpecForks, **kw): try: - spec = kw['spec'] - - balances = balances_fn(spec) - activation_threshold = threshold_fn(spec) - - kw['state'] = create_genesis_state(spec=spec, validator_balances=balances, - activation_threshold=activation_threshold) + p0 = phases["phase0"] + balances = balances_fn(p0) + activation_threshold = threshold_fn(p0) + + state = create_genesis_state(spec=p0, validator_balances=balances, + activation_threshold=activation_threshold) + if spec.version == 'phase1': + # TODO: instead of upgrading a test phase0 genesis state we can also write a phase1 state helper. + # Decide based on performance/consistency results later. + state = phases["phase1"].upgrade_to_phase1(state) + + kw['state'] = state except KeyError: raise TypeError('Spec decorator must come within state decorator to inject spec into state.') - return fn(*args, **kw) + return fn(*args, spec=spec, phases=phases, **kw) return entry return deco @@ -69,6 +104,19 @@ def misc_balances(spec): return [spec.MAX_EFFECTIVE_BALANCE] * num_validators + [spec.MIN_DEPOSIT_AMOUNT] * num_misc_validators +def single_phase(fn): + """ + Decorator that filters out the phases data. + most state tests only focus on behavior of a single phase (the "spec"). + This decorator is applied as part of spec_state_test(fn). + """ + def entry(*args, **kw): + if 'phases' in kw: + kw.pop('phases') + return fn(*args, **kw) + return entry + + # BLS is turned off by default *for performance purposes during TESTING*. # The runner of the test can indicate the preferred setting (test generators prefer BLS to be ON). # - Some tests are marked as BLS-requiring, and ignore this setting. @@ -88,9 +136,9 @@ def spec_test(fn): return vector_test()(bls_switch(fn)) -# shorthand for decorating @spectest() @with_state +# shorthand for decorating @spectest() @with_state @single_phase def spec_state_test(fn): - return spec_test(with_state(fn)) + return spec_test(with_state(single_phase(fn))) def expect_assertion_error(fn): @@ -169,15 +217,12 @@ def decorator(fn): return decorator -def with_phases(phases): +def with_phases(phases, other_phases=None): """ - Decorator factory that returns a decorator that runs a test for the appropriate phases + Decorator factory that returns a decorator that runs a test for the appropriate phases. + Additional phases that do not initially run, but are made available through the test, are optional. """ def decorator(fn): - def run_with_spec_version(spec, *args, **kw): - kw['spec'] = spec - return fn(*args, **kw) - def wrapper(*args, **kw): run_phases = phases @@ -188,12 +233,25 @@ def wrapper(*args, **kw): return run_phases = [phase] + available_phases = set(run_phases) + if other_phases is not None: + available_phases += set(other_phases) + + # TODO: test state is dependent on phase0 but is immediately transitioned to phase1. + # A new state-creation helper for phase 1 may be in place, and then phase1+ tests can run without phase0 + available_phases.add('phase0') + + phase_dir = {} + if 'phase0' in available_phases: + phase_dir['phase0'] = spec_phase0 + if 'phase1' in available_phases: + phase_dir['phase1'] = spec_phase1 + + # return is ignored whenever multiple phases are ran. If if 'phase0' in run_phases: - ret = run_with_spec_version(spec_phase0, *args, **kw) + ret = fn(spec=spec_phase0, phases=phase_dir, *args, **kw) if 'phase1' in run_phases: - # temporarily disable phase 1 tests - return - # ret = run_with_spec_version(spec_phase1, *args, **kw) + ret = fn(spec=spec_phase1, phases=phase_dir, *args, **kw) return ret return wrapper return decorator diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py index 1f412e7879..e34c32c0e9 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py @@ -30,22 +30,29 @@ def add_attestation_to_store(spec, store, attestation): spec.on_attestation(store, attestation) +def get_anchor_root(spec, state): + anchor_block_header = state.latest_block_header.copy() + if anchor_block_header.state_root == spec.Bytes32(): + anchor_block_header.state_root = spec.hash_tree_root(state) + return spec.hash_tree_root(anchor_block_header) + + @with_all_phases @spec_state_test def test_genesis(spec, state): # Initialization - store = spec.get_genesis_store(state) - genesis_block = spec.BeaconBlock(state_root=state.hash_tree_root()) - assert spec.get_head(store) == spec.hash_tree_root(genesis_block) + store = spec.get_forkchoice_store(state) + anchor_root = get_anchor_root(spec, state) + assert spec.get_head(store) == anchor_root @with_all_phases @spec_state_test def test_chain_no_attestations(spec, state): # Initialization - store = spec.get_genesis_store(state) - genesis_block = spec.BeaconBlock(state_root=state.hash_tree_root()) - assert spec.get_head(store) == spec.hash_tree_root(genesis_block) + store = spec.get_forkchoice_store(state) + anchor_root = get_anchor_root(spec, state) + assert spec.get_head(store) == anchor_root # On receiving a block of `GENESIS_SLOT + 1` slot block_1 = build_empty_block_for_next_slot(spec, state) @@ -66,9 +73,9 @@ def test_split_tie_breaker_no_attestations(spec, state): genesis_state = state.copy() # Initialization - store = spec.get_genesis_store(state) - genesis_block = spec.BeaconBlock(state_root=state.hash_tree_root()) - assert spec.get_head(store) == spec.hash_tree_root(genesis_block) + store = spec.get_forkchoice_store(state) + anchor_root = get_anchor_root(spec, state) + assert spec.get_head(store) == anchor_root # block at slot 1 block_1_state = genesis_state.copy() @@ -94,9 +101,9 @@ def test_shorter_chain_but_heavier_weight(spec, state): genesis_state = state.copy() # Initialization - store = spec.get_genesis_store(state) - genesis_block = spec.BeaconBlock(state_root=state.hash_tree_root()) - assert spec.get_head(store) == spec.hash_tree_root(genesis_block) + store = spec.get_forkchoice_store(state) + anchor_root = get_anchor_root(spec, state) + assert spec.get_head(store) == anchor_root # build longer tree long_state = genesis_state.copy() @@ -122,15 +129,14 @@ def test_shorter_chain_but_heavier_weight(spec, state): @spec_state_test def test_filtered_block_tree(spec, state): # Initialization - genesis_state_root = state.hash_tree_root() - store = spec.get_genesis_store(state) - genesis_block = spec.BeaconBlock(state_root=genesis_state_root) + store = spec.get_forkchoice_store(state) + anchor_root = get_anchor_root(spec, state) # transition state past initial couple of epochs next_epoch(spec, state) next_epoch(spec, state) - assert spec.get_head(store) == spec.hash_tree_root(genesis_block) + assert spec.get_head(store) == anchor_root # fill in attestations for entire epoch, justifying the recent epoch prev_state, signed_blocks, state = next_epoch_with_attestations(spec, state, True, False) diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py index d7fbc4777a..a0a33ca50c 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py @@ -15,8 +15,17 @@ def run_on_attestation(spec, state, store, attestation, valid=True): indexed_attestation = spec.get_indexed_attestation(state, attestation) spec.on_attestation(store, attestation) + + if spec.version == 'phase0': + sample_index = indexed_attestation.attesting_indices[0] + else: + attesting_indices = [ + index for i, index in enumerate(indexed_attestation.committee) + if attestation.aggregation_bits[i] + ] + sample_index = attesting_indices[0] assert ( - store.latest_messages[indexed_attestation.attesting_indices[0]] == + store.latest_messages[sample_index] == spec.LatestMessage( epoch=attestation.data.target.epoch, root=attestation.data.beacon_block_root, @@ -27,7 +36,7 @@ def run_on_attestation(spec, state, store, attestation, valid=True): @with_all_phases @spec_state_test def test_on_attestation_current_epoch(spec, state): - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) spec.on_tick(store, store.time + spec.SECONDS_PER_SLOT * 2) block = build_empty_block_for_next_slot(spec, state) @@ -46,7 +55,7 @@ def test_on_attestation_current_epoch(spec, state): @with_all_phases @spec_state_test def test_on_attestation_previous_epoch(spec, state): - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) spec.on_tick(store, store.time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH) block = build_empty_block_for_next_slot(spec, state) @@ -65,7 +74,7 @@ def test_on_attestation_previous_epoch(spec, state): @with_all_phases @spec_state_test def test_on_attestation_past_epoch(spec, state): - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) # move time forward 2 epochs time = store.time + 2 * spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH @@ -87,7 +96,7 @@ def test_on_attestation_past_epoch(spec, state): @with_all_phases @spec_state_test def test_on_attestation_mismatched_target_and_slot(spec, state): - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) spec.on_tick(store, store.time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH) block = build_empty_block_for_next_slot(spec, state) @@ -110,7 +119,7 @@ def test_on_attestation_mismatched_target_and_slot(spec, state): @with_all_phases @spec_state_test def test_on_attestation_target_not_in_store(spec, state): - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) time = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH spec.on_tick(store, time) @@ -131,7 +140,7 @@ def test_on_attestation_target_not_in_store(spec, state): @with_all_phases @spec_state_test def test_on_attestation_beacon_block_not_in_store(spec, state): - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) time = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH spec.on_tick(store, time) @@ -159,7 +168,7 @@ def test_on_attestation_beacon_block_not_in_store(spec, state): @with_all_phases @spec_state_test def test_on_attestation_future_epoch(spec, state): - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) time = 3 * spec.SECONDS_PER_SLOT spec.on_tick(store, time) @@ -179,7 +188,7 @@ def test_on_attestation_future_epoch(spec, state): @with_all_phases @spec_state_test def test_on_attestation_future_block(spec, state): - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) time = spec.SECONDS_PER_SLOT * 5 spec.on_tick(store, time) @@ -199,7 +208,7 @@ def test_on_attestation_future_block(spec, state): @with_all_phases @spec_state_test def test_on_attestation_same_slot(spec, state): - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) time = 1 * spec.SECONDS_PER_SLOT spec.on_tick(store, time) @@ -215,7 +224,7 @@ def test_on_attestation_same_slot(spec, state): @with_all_phases @spec_state_test def test_on_attestation_invalid_attestation(spec, state): - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) time = 3 * spec.SECONDS_PER_SLOT spec.on_tick(store, time) diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_block.py index 10d1c0011b..6a72d61e1c 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_block.py @@ -36,7 +36,7 @@ def apply_next_epoch_with_attestations(spec, state, store): @spec_state_test def test_basic(spec, state): # Initialization - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) time = 100 spec.on_tick(store, time) assert store.time == time @@ -60,7 +60,7 @@ def test_basic(spec, state): @spec_state_test def test_on_block_checkpoints(spec, state): # Initialization - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) time = 100 spec.on_tick(store, time) @@ -86,7 +86,7 @@ def test_on_block_checkpoints(spec, state): @spec_state_test def test_on_block_future_block(spec, state): # Initialization - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) # do not tick time @@ -100,7 +100,7 @@ def test_on_block_future_block(spec, state): @spec_state_test def test_on_block_bad_parent_root(spec, state): # Initialization - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) time = 100 spec.on_tick(store, time) @@ -120,7 +120,7 @@ def test_on_block_bad_parent_root(spec, state): @spec_state_test def test_on_block_before_finalized(spec, state): # Initialization - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) time = 100 spec.on_tick(store, time) @@ -139,7 +139,7 @@ def test_on_block_before_finalized(spec, state): @spec_state_test def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): # Initialization - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) time = 100 spec.on_tick(store, time) @@ -170,7 +170,7 @@ def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): @spec_state_test def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): # Initialization - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) time = 100 spec.on_tick(store, time) diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_tick.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_tick.py index 77222f65c6..27b64ac098 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_tick.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_tick.py @@ -19,14 +19,14 @@ def run_on_tick(spec, store, time, new_justified_checkpoint=False): @with_all_phases @spec_state_test def test_basic(spec, state): - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) run_on_tick(spec, store, store.time + 1) @with_all_phases @spec_state_test def test_update_justified_single(spec, state): - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH store.best_justified_checkpoint = spec.Checkpoint( @@ -40,7 +40,7 @@ def test_update_justified_single(spec, state): @with_all_phases @spec_state_test def test_no_update_same_slot_at_epoch_boundary(spec, state): - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH store.best_justified_checkpoint = spec.Checkpoint( @@ -57,7 +57,7 @@ def test_no_update_same_slot_at_epoch_boundary(spec, state): @with_all_phases @spec_state_test def test_no_update_not_epoch_boundary(spec, state): - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) store.best_justified_checkpoint = spec.Checkpoint( epoch=store.justified_checkpoint.epoch + 1, @@ -70,7 +70,7 @@ def test_no_update_not_epoch_boundary(spec, state): @with_all_phases @spec_state_test def test_no_update_new_justified_equal_epoch(spec, state): - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH store.best_justified_checkpoint = spec.Checkpoint( @@ -89,7 +89,7 @@ def test_no_update_new_justified_equal_epoch(spec, state): @with_all_phases @spec_state_test def test_no_update_new_justified_later_epoch(spec, state): - store = spec.get_genesis_store(state) + store = spec.get_forkchoice_store(state) seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH store.best_justified_checkpoint = spec.Checkpoint( diff --git a/tests/core/pyspec/eth2spec/test/genesis/test_initialization.py b/tests/core/pyspec/eth2spec/test/genesis/test_initialization.py index 9b326590fb..61a2ffb1eb 100644 --- a/tests/core/pyspec/eth2spec/test/genesis/test_initialization.py +++ b/tests/core/pyspec/eth2spec/test/genesis/test_initialization.py @@ -1,4 +1,4 @@ -from eth2spec.test.context import spec_test, with_phases +from eth2spec.test.context import spec_test, with_phases, single_phase from eth2spec.test.helpers.deposits import ( prepare_genesis_deposits, ) @@ -6,6 +6,7 @@ @with_phases(['phase0']) @spec_test +@single_phase def test_initialize_beacon_state_from_eth1(spec): deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT deposits, deposit_root, _ = prepare_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True) @@ -33,6 +34,7 @@ def test_initialize_beacon_state_from_eth1(spec): @with_phases(['phase0']) @spec_test +@single_phase def test_initialize_beacon_state_some_small_balances(spec): main_deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT main_deposits, _, deposit_data_list = prepare_genesis_deposits(spec, main_deposit_count, diff --git a/tests/core/pyspec/eth2spec/test/genesis/test_validity.py b/tests/core/pyspec/eth2spec/test/genesis/test_validity.py index a003938e7e..a90b4a6956 100644 --- a/tests/core/pyspec/eth2spec/test/genesis/test_validity.py +++ b/tests/core/pyspec/eth2spec/test/genesis/test_validity.py @@ -1,4 +1,4 @@ -from eth2spec.test.context import spec_test, with_phases +from eth2spec.test.context import spec_test, with_phases, single_phase from eth2spec.test.helpers.deposits import ( prepare_genesis_deposits, ) @@ -27,6 +27,7 @@ def run_is_valid_genesis_state(spec, state, valid=True): @with_phases(['phase0']) @spec_test +@single_phase def test_is_valid_genesis_state_true(spec): state = create_valid_beacon_state(spec) @@ -35,6 +36,7 @@ def test_is_valid_genesis_state_true(spec): @with_phases(['phase0']) @spec_test +@single_phase def test_is_valid_genesis_state_false_invalid_timestamp(spec): state = create_valid_beacon_state(spec) state.genesis_time = spec.MIN_GENESIS_TIME - 1 @@ -44,6 +46,7 @@ def test_is_valid_genesis_state_false_invalid_timestamp(spec): @with_phases(['phase0']) @spec_test +@single_phase def test_is_valid_genesis_state_true_more_balance(spec): state = create_valid_beacon_state(spec) state.validators[0].effective_balance = spec.MAX_EFFECTIVE_BALANCE + 1 @@ -63,6 +66,7 @@ def test_is_valid_genesis_state_true_more_balance(spec): @with_phases(['phase0']) @spec_test +@single_phase def test_is_valid_genesis_state_true_one_more_validator(spec): deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT + 1 deposits, _, _ = prepare_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True) @@ -76,6 +80,7 @@ def test_is_valid_genesis_state_true_one_more_validator(spec): @with_phases(['phase0']) @spec_test +@single_phase def test_is_valid_genesis_state_false_not_enough_validator(spec): deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - 1 deposits, _, _ = prepare_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True) diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index cb3e863203..b8733705a6 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -77,12 +77,22 @@ def sign_aggregate_attestation(spec, state, attestation_data, participants: List privkey ) ) + # TODO: we should try signing custody bits if spec.version == 'phase1' return bls.Aggregate(signatures) def sign_indexed_attestation(spec, state, indexed_attestation): - participants = indexed_attestation.attesting_indices - indexed_attestation.signature = sign_aggregate_attestation(spec, state, indexed_attestation.data, participants) + if spec.version == 'phase0': + participants = indexed_attestation.attesting_indices + data = indexed_attestation.data + indexed_attestation.signature = sign_aggregate_attestation(spec, state, data, participants) + else: + participants = spec.get_indices_from_committee( + indexed_attestation.committee, + indexed_attestation.attestation.aggregation_bits, + ) + data = indexed_attestation.attestation.data + indexed_attestation.attestation.signature = sign_aggregate_attestation(spec, state, data, participants) def sign_attestation(spec, state, attestation): diff --git a/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py b/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py index 20abcacfb8..8a342dd4d8 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py @@ -16,3 +16,40 @@ def get_valid_attester_slashing(spec, state, signed_1=False, signed_2=False): attestation_1=spec.get_indexed_attestation(state, attestation_1), attestation_2=spec.get_indexed_attestation(state, attestation_2), ) + + +def get_indexed_attestation_participants(spec, indexed_att): + """ + Wrapper around index-attestation to return the list of participant indices, regardless of spec phase. + """ + if spec.version == "phase1": + return list(spec.get_indices_from_committee( + indexed_att.committee, + indexed_att.attestation.aggregation_bits, + )) + else: + return list(indexed_att.attesting_indices) + + +def set_indexed_attestation_participants(spec, indexed_att, participants): + """ + Wrapper around index-attestation to return the list of participant indices, regardless of spec phase. + """ + if spec.version == "phase1": + indexed_att.attestation.aggregation_bits = [bool(i in participants) for i in indexed_att.committee] + else: + indexed_att.attesting_indices = participants + + +def get_attestation_1_data(spec, att_slashing): + if spec.version == "phase1": + return att_slashing.attestation_1.attestation.data + else: + return att_slashing.attestation_1.data + + +def get_attestation_2_data(spec, att_slashing): + if spec.version == "phase1": + return att_slashing.attestation_2.attestation.data + else: + return att_slashing.attestation_2.data diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index e00d64a172..bcf2c199b8 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -1,6 +1,5 @@ from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls -from eth2spec.utils.hash_function import hash from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, Bitvector from eth2spec.utils.ssz.ssz_impl import chunkify, pack, hash_tree_root from eth2spec.utils.merkle_minimal import get_merkle_tree, get_merkle_proof @@ -21,7 +20,7 @@ def get_valid_early_derived_secret_reveal(spec, state, epoch=None): signing_root = spec.compute_signing_root(spec.Epoch(epoch), domain) reveal = bls.Sign(privkeys[revealed_index], signing_root) # Generate the mask (any random 32 bytes that don't reveal the masker's secret will do) - mask = hash(reveal) + mask = spec.hash(reveal) # Generate masker's signature on the mask signing_root = spec.compute_signing_root(mask, domain) masker_signature = bls.Sign(privkeys[masker_index], signing_root) diff --git a/tests/core/pyspec/eth2spec/test/merkle_proofs/__init__.py b/tests/core/pyspec/eth2spec/test/merkle_proofs/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/core/pyspec/eth2spec/test/merkle_proofs/test_merkle_proofs.py b/tests/core/pyspec/eth2spec/test/merkle_proofs/test_merkle_proofs.py deleted file mode 100644 index 62a2f63793..0000000000 --- a/tests/core/pyspec/eth2spec/test/merkle_proofs/test_merkle_proofs.py +++ /dev/null @@ -1,152 +0,0 @@ -import re -from eth_utils import ( - to_tuple, -) - -from eth2spec.test.context import ( - expect_assertion_error, - spec_state_test, - with_all_phases_except, -) -from eth2spec.utils.ssz.ssz_typing import ( - Bytes32, - Container, - List, - uint64, -) - - -class Foo(Container): - x: uint64 - y: List[Bytes32, 2] - -# Tree -# root -# / \ -# x y_root -# / \ -# y_data_root len(y) -# / \ -# / \ / \ -# -# Generalized indices -# 1 -# / \ -# 2 (x) 3 (y_root) -# / \ -# 6 7 -# / \ -# 12 13 - - -@to_tuple -def ssz_object_to_path(start, end): - is_len = False - len_findall = re.findall(r"(?<=len\().*(?=\))", end) - if len_findall: - is_len = True - end = len_findall[0] - - route = '' - if end.startswith(start): - route = end[len(start):] - - segments = route.split('.') - for word in segments: - index_match = re.match(r"(\w+)\[(\d+)]", word) - if index_match: - yield from index_match.groups() - elif len(word): - yield word - if is_len: - yield '__len__' - - -to_path_test_cases = [ - ('foo', 'foo.x', ('x',)), - ('foo', 'foo.x[100].y', ('x', '100', 'y')), - ('foo', 'foo.x[100].y[1].z[2]', ('x', '100', 'y', '1', 'z', '2')), - ('foo', 'len(foo.x[100].y[1].z[2])', ('x', '100', 'y', '1', 'z', '2', '__len__')), -] - - -def test_to_path(): - for test_case in to_path_test_cases: - start, end, expected = test_case - assert ssz_object_to_path(start, end) == expected - - -generalized_index_cases = [ - (Foo, ('x',), 2), - (Foo, ('y',), 3), - (Foo, ('y', 0), 12), - (Foo, ('y', 1), 13), - (Foo, ('y', '__len__'), None), -] - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_get_generalized_index(spec, state): - for typ, path, generalized_index in generalized_index_cases: - if generalized_index is not None: - assert spec.get_generalized_index( - typ=typ, - path=path, - ) == generalized_index - else: - expect_assertion_error(lambda: spec.get_generalized_index(typ=typ, path=path)) - - yield 'typ', typ - yield 'path', path - yield 'generalized_index', generalized_index - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_verify_merkle_proof(spec, state): - h = spec.hash - a = b'\x11' * 32 - b = b'\x22' * 32 - c = b'\x33' * 32 - d = b'\x44' * 32 - root = h(h(a + b) + h(c + d)) - leaf = a - generalized_index = 4 - proof = [b, h(c + d)] - - is_valid = spec.verify_merkle_proof( - leaf=leaf, - proof=proof, - index=generalized_index, - root=root, - ) - assert is_valid - - yield 'proof', proof - yield 'is_valid', is_valid - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_verify_merkle_multiproof(spec, state): - h = spec.hash - a = b'\x11' * 32 - b = b'\x22' * 32 - c = b'\x33' * 32 - d = b'\x44' * 32 - root = h(h(a + b) + h(c + d)) - leaves = [a, d] - generalized_indices = [4, 7] - proof = [c, b] # helper_indices = [6, 5] - - is_valid = spec.verify_merkle_multiproof( - leaves=leaves, - proof=proof, - indices=generalized_indices, - root=root, - ) - assert is_valid - - yield 'proof', proof - yield 'is_valid', is_valid diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py b/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py index d48386fd49..7937614a46 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py @@ -6,7 +6,7 @@ spec_test, low_balances, with_custom_state, -) + single_phase) from eth2spec.test.helpers.attestations import ( get_valid_attestation, sign_aggregate_attestation, @@ -66,6 +66,7 @@ def test_success(spec, state): @with_all_phases @spec_test @with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.EJECTION_BALANCE) +@single_phase def test_success_multi_proposer_index_iterations(spec, state): state.slot += spec.SLOTS_PER_EPOCH * 2 attestation = get_valid_attestation(spec, state, signed=True) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py b/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py index dba48ca641..4bd3a96b5e 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py @@ -1,6 +1,7 @@ -from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls, with_all_phases +from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls, with_all_phases, with_phases from eth2spec.test.helpers.attestations import sign_indexed_attestation -from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing +from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing, \ + get_indexed_attestation_participants, get_attestation_2_data, get_attestation_1_data from eth2spec.test.helpers.block import apply_empty_block from eth2spec.test.helpers.state import ( get_balance, @@ -25,7 +26,7 @@ def run_attester_slashing_processing(spec, state, attester_slashing, valid=True) yield 'post', None return - slashed_indices = attester_slashing.attestation_1.attesting_indices + slashed_indices = get_indexed_attestation_participants(spec, attester_slashing.attestation_1) proposer_index = spec.get_beacon_proposer_index(state) pre_proposer_balance = get_balance(state, proposer_index) @@ -92,12 +93,12 @@ def test_success_surround(spec, state): state.current_justified_checkpoint.epoch += 1 attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True) - attestation_1 = attester_slashing.attestation_1 - attestation_2 = attester_slashing.attestation_2 + att_1_data = get_attestation_1_data(spec, attester_slashing) + att_2_data = get_attestation_2_data(spec, attester_slashing) # set attestion1 to surround attestation 2 - attestation_1.data.source.epoch = attestation_2.data.source.epoch - 1 - attestation_1.data.target.epoch = attestation_2.data.target.epoch + 1 + att_1_data.source.epoch = att_2_data.source.epoch - 1 + att_1_data.target.epoch = att_2_data.target.epoch + 1 sign_indexed_attestation(spec, state, attester_slashing.attestation_1) @@ -109,7 +110,7 @@ def test_success_surround(spec, state): @always_bls def test_success_already_exited_recent(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) - slashed_indices = attester_slashing.attestation_1.attesting_indices + slashed_indices = get_indexed_attestation_participants(spec, attester_slashing.attestation_1) for index in slashed_indices: spec.initiate_validator_exit(state, index) @@ -121,7 +122,7 @@ def test_success_already_exited_recent(spec, state): @always_bls def test_success_already_exited_long_ago(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) - slashed_indices = attester_slashing.attestation_1.attesting_indices + slashed_indices = get_indexed_attestation_participants(spec, attester_slashing.attestation_1) for index in slashed_indices: spec.initiate_validator_exit(state, index) state.validators[index].withdrawable_epoch = spec.get_current_epoch(state) + 2 @@ -158,7 +159,12 @@ def test_invalid_sig_1_and_2(spec, state): def test_same_data(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True) - attester_slashing.attestation_1.data = attester_slashing.attestation_2.data + indexed_att_1 = attester_slashing.attestation_1 + att_2_data = get_attestation_2_data(spec, attester_slashing) + if spec.version == 'phase1': + indexed_att_1.attestation.data = att_2_data + else: + indexed_att_1.data = att_2_data sign_indexed_attestation(spec, state, attester_slashing.attestation_1) yield from run_attester_slashing_processing(spec, state, attester_slashing, False) @@ -169,7 +175,9 @@ def test_same_data(spec, state): def test_no_double_or_surround(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True) - attester_slashing.attestation_1.data.target.epoch += 1 + att_1_data = get_attestation_1_data(spec, attester_slashing) + att_1_data.target.epoch += 1 + sign_indexed_attestation(spec, state, attester_slashing.attestation_1) yield from run_attester_slashing_processing(spec, state, attester_slashing, False) @@ -181,20 +189,23 @@ def test_participants_already_slashed(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) # set all indices to slashed - validator_indices = attester_slashing.attestation_1.attesting_indices + validator_indices = get_indexed_attestation_participants(spec, attester_slashing.attestation_1) for index in validator_indices: state.validators[index].slashed = True yield from run_attester_slashing_processing(spec, state, attester_slashing, False) -@with_all_phases +# Some of the following tests are phase0 only: phase 1 lists participants with bitfields instead of index list. + + +@with_phases(['phase0']) @spec_state_test @always_bls def test_att1_bad_extra_index(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) - indices = attester_slashing.attestation_1.attesting_indices + indices = get_indexed_attestation_participants(spec, attester_slashing.attestation_1) options = list(set(range(len(state.validators))) - set(indices)) indices.append(options[len(options) // 2]) # add random index, not previously in attestation. attester_slashing.attestation_1.attesting_indices = sorted(indices) @@ -204,7 +215,7 @@ def test_att1_bad_extra_index(spec, state): yield from run_attester_slashing_processing(spec, state, attester_slashing, False) -@with_all_phases +@with_phases(['phase0']) @spec_state_test @always_bls def test_att1_bad_replaced_index(spec, state): @@ -220,7 +231,7 @@ def test_att1_bad_replaced_index(spec, state): yield from run_attester_slashing_processing(spec, state, attester_slashing, False) -@with_all_phases +@with_phases(['phase0']) @spec_state_test @always_bls def test_att2_bad_extra_index(spec, state): @@ -236,7 +247,7 @@ def test_att2_bad_extra_index(spec, state): yield from run_attester_slashing_processing(spec, state, attester_slashing, False) -@with_all_phases +@with_phases(['phase0']) @spec_state_test @always_bls def test_att2_bad_replaced_index(spec, state): @@ -252,7 +263,7 @@ def test_att2_bad_replaced_index(spec, state): yield from run_attester_slashing_processing(spec, state, attester_slashing, False) -@with_all_phases +@with_phases(['phase0']) @spec_state_test @always_bls def test_att1_duplicate_index_normal_signed(spec, state): @@ -272,7 +283,7 @@ def test_att1_duplicate_index_normal_signed(spec, state): yield from run_attester_slashing_processing(spec, state, attester_slashing, False) -@with_all_phases +@with_phases(['phase0']) @spec_state_test @always_bls def test_att2_duplicate_index_normal_signed(spec, state): @@ -292,7 +303,7 @@ def test_att2_duplicate_index_normal_signed(spec, state): yield from run_attester_slashing_processing(spec, state, attester_slashing, False) -@with_all_phases +@with_phases(['phase0']) @spec_state_test @always_bls def test_att1_duplicate_index_double_signed(spec, state): @@ -307,7 +318,7 @@ def test_att1_duplicate_index_double_signed(spec, state): yield from run_attester_slashing_processing(spec, state, attester_slashing, False) -@with_all_phases +@with_phases(['phase0']) @spec_state_test @always_bls def test_att2_duplicate_index_double_signed(spec, state): @@ -322,7 +333,7 @@ def test_att2_duplicate_index_double_signed(spec, state): yield from run_attester_slashing_processing(spec, state, attester_slashing, False) -@with_all_phases +@with_phases(['phase0']) @spec_state_test def test_unsorted_att_1(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True) @@ -335,7 +346,7 @@ def test_unsorted_att_1(spec, state): yield from run_attester_slashing_processing(spec, state, attester_slashing, False) -@with_all_phases +@with_phases(['phase0']) @spec_state_test def test_unsorted_att_2(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=False) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_rewards_and_penalties.py b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_rewards_and_penalties.py index b4fc46b7db..fa394df564 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_rewards_and_penalties.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_rewards_and_penalties.py @@ -1,7 +1,7 @@ from copy import deepcopy from eth2spec.test.context import spec_state_test, with_all_phases, spec_test, \ - misc_balances, with_custom_state, default_activation_threshold + misc_balances, with_custom_state, default_activation_threshold, single_phase from eth2spec.test.helpers.state import ( next_epoch, next_slot, @@ -10,6 +10,7 @@ add_attestations_to_state, get_valid_attestation, ) +from eth2spec.test.helpers.attester_slashings import get_indexed_attestation_participants from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with @@ -96,6 +97,7 @@ def test_full_attestations(spec, state): @with_all_phases @spec_test @with_custom_state(balances_fn=misc_balances, threshold_fn=default_activation_threshold) +@single_phase def test_full_attestations_misc_balances(spec, state): attestations = prepare_state_with_full_attestations(spec, state) @@ -141,7 +143,7 @@ def test_duplicate_attestation(spec, state): attestation = get_valid_attestation(spec, state, signed=True) indexed_attestation = spec.get_indexed_attestation(state, attestation) - participants = indexed_attestation.attesting_indices + participants = get_indexed_attestation_participants(spec, indexed_attestation) assert len(participants) > 0 diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_bit_challenge.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_bit_challenge.py deleted file mode 100644 index ae6ff258c5..0000000000 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_bit_challenge.py +++ /dev/null @@ -1,350 +0,0 @@ -from eth2spec.test.helpers.custody import ( - get_valid_bit_challenge, - get_valid_custody_response, - get_custody_test_vector, - get_custody_merkle_root -) -from eth2spec.test.helpers.attestations import ( - get_valid_attestation, -) -from eth2spec.utils.ssz.ssz_impl import hash_tree_root -from eth2spec.test.helpers.state import next_epoch, get_balance -from eth2spec.test.helpers.block import apply_empty_block -from eth2spec.test.context import ( - with_all_phases_except, - spec_state_test, - expect_assertion_error, -) -from eth2spec.test.phase_0.block_processing.test_process_attestation import run_attestation_processing - - -def run_bit_challenge_processing(spec, state, custody_bit_challenge, valid=True): - """ - Run ``process_bit_challenge``, yielding: - - pre-state ('pre') - - CustodyBitChallenge ('custody_bit_challenge') - - post-state ('post'). - If ``valid == False``, run expecting ``AssertionError`` - """ - yield 'pre', state - yield 'custody_bit_challenge', custody_bit_challenge - - if not valid: - expect_assertion_error(lambda: spec.process_bit_challenge(state, custody_bit_challenge)) - yield 'post', None - return - - spec.process_bit_challenge(state, custody_bit_challenge) - - assert state.custody_bit_challenge_records[state.custody_challenge_index - 1].chunk_bits_merkle_root == \ - hash_tree_root(custody_bit_challenge.chunk_bits) - assert state.custody_bit_challenge_records[state.custody_challenge_index - 1].challenger_index == \ - custody_bit_challenge.challenger_index - assert state.custody_bit_challenge_records[state.custody_challenge_index - 1].responder_index == \ - custody_bit_challenge.responder_index - - yield 'post', state - - -def run_custody_response_processing(spec, state, custody_response, valid=True): - """ - Run ``process_bit_challenge_response``, yielding: - - pre-state ('pre') - - CustodyResponse ('custody_response') - - post-state ('post'). - If ``valid == False``, run expecting ``AssertionError`` - """ - yield 'pre', state - yield 'custody_response', custody_response - - if not valid: - expect_assertion_error(lambda: spec.process_custody_response(state, custody_response)) - yield 'post', None - return - - # TODO: Add capability to also process chunk challenges, not only bit challenges - challenge = state.custody_bit_challenge_records[custody_response.challenge_index] - pre_slashed_balance = get_balance(state, challenge.challenger_index) - - spec.process_custody_response(state, custody_response) - - slashed_validator = state.validators[challenge.challenger_index] - - assert slashed_validator.slashed - assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH - assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH - - assert get_balance(state, challenge.challenger_index) < pre_slashed_balance - yield 'post', state - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_challenge_appended(spec, state): - state.slot = spec.SLOTS_PER_EPOCH - attestation = get_valid_attestation(spec, state, signed=True) - - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY - - _, _, _ = run_attestation_processing(spec, state, attestation) - - state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD - - challenge = get_valid_bit_challenge(spec, state, attestation) - - yield from run_bit_challenge_processing(spec, state, challenge) - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_multiple_epochs_custody(spec, state): - state.slot = spec.SLOTS_PER_EPOCH * 3 - attestation = get_valid_attestation(spec, state, signed=True) - - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY - - _, _, _ = run_attestation_processing(spec, state, attestation) - - state.slot += spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1) - - challenge = get_valid_bit_challenge(spec, state, attestation) - - yield from run_bit_challenge_processing(spec, state, challenge) - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_many_epochs_custody(spec, state): - state.slot = spec.SLOTS_PER_EPOCH * 100 - attestation = get_valid_attestation(spec, state, signed=True) - - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY - - _, _, _ = run_attestation_processing(spec, state, attestation) - - state.slot += spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1) - - challenge = get_valid_bit_challenge(spec, state, attestation) - - yield from run_bit_challenge_processing(spec, state, challenge) - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_off_chain_attestation(spec, state): - state.slot = spec.SLOTS_PER_EPOCH - attestation = get_valid_attestation(spec, state, signed=True) - - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY - state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD - - challenge = get_valid_bit_challenge(spec, state, attestation) - - yield from run_bit_challenge_processing(spec, state, challenge) - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_invalid_custody_bit_challenge(spec, state): - state.slot = spec.SLOTS_PER_EPOCH - attestation = get_valid_attestation(spec, state, signed=True) - - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY - - _, _, _ = run_attestation_processing(spec, state, attestation) - - state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD - - challenge = get_valid_bit_challenge(spec, state, attestation, invalid_custody_bit=True) - - yield from run_bit_challenge_processing(spec, state, challenge, valid=False) - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_max_reveal_lateness_1(spec, state): - next_epoch(spec, state) - apply_empty_block(spec, state) - - attestation = get_valid_attestation(spec, state, signed=True) - - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - - next_epoch(spec, state) - apply_empty_block(spec, state) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - challenge = get_valid_bit_challenge(spec, state, attestation) - - responder_index = challenge.responder_index - target_epoch = attestation.data.target.epoch - - state.validators[responder_index].max_reveal_lateness = 3 - - latest_reveal_epoch = spec.get_randao_epoch_for_custody_period( - spec.get_custody_period_for_validator(state, responder_index, target_epoch), - responder_index - ) + 2 * spec.EPOCHS_PER_CUSTODY_PERIOD + state.validators[responder_index].max_reveal_lateness - - while spec.get_current_epoch(state) < latest_reveal_epoch - 2: - next_epoch(spec, state) - apply_empty_block(spec, state) - - yield from run_bit_challenge_processing(spec, state, challenge) - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_max_reveal_lateness_2(spec, state): - next_epoch(spec, state) - apply_empty_block(spec, state) - - attestation = get_valid_attestation(spec, state, signed=True) - - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - - next_epoch(spec, state) - apply_empty_block(spec, state) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - challenge = get_valid_bit_challenge(spec, state, attestation) - - responder_index = challenge.responder_index - - state.validators[responder_index].max_reveal_lateness = 3 - - for i in range(spec.get_randao_epoch_for_custody_period( - spec.get_custody_period_for_validator(state, responder_index), - responder_index - ) + 2 * spec.EPOCHS_PER_CUSTODY_PERIOD + state.validators[responder_index].max_reveal_lateness - 1): - next_epoch(spec, state) - apply_empty_block(spec, state) - - yield from run_bit_challenge_processing(spec, state, challenge, False) - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_custody_response(spec, state): - state.slot = spec.SLOTS_PER_EPOCH - attestation = get_valid_attestation(spec, state, signed=True) - - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY - - _, _, _ = run_attestation_processing(spec, state, attestation) - - state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD - - challenge = get_valid_bit_challenge(spec, state, attestation) - - _, _, _ = run_bit_challenge_processing(spec, state, challenge) - - bit_challenge_index = state.custody_challenge_index - 1 - - custody_response = get_valid_custody_response(spec, state, challenge, test_vector, bit_challenge_index) - - yield from run_custody_response_processing(spec, state, custody_response) - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_custody_response_multiple_epochs(spec, state): - state.slot = spec.SLOTS_PER_EPOCH * 3 - attestation = get_valid_attestation(spec, state, signed=True) - - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY - - _, _, _ = run_attestation_processing(spec, state, attestation) - - state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD - - challenge = get_valid_bit_challenge(spec, state, attestation) - - _, _, _ = run_bit_challenge_processing(spec, state, challenge) - - bit_challenge_index = state.custody_challenge_index - 1 - - custody_response = get_valid_custody_response(spec, state, challenge, test_vector, bit_challenge_index) - - yield from run_custody_response_processing(spec, state, custody_response) - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_custody_response_many_epochs(spec, state): - state.slot = spec.SLOTS_PER_EPOCH * 100 - attestation = get_valid_attestation(spec, state, signed=True) - - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY - - _, _, _ = run_attestation_processing(spec, state, attestation) - - state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD - - challenge = get_valid_bit_challenge(spec, state, attestation) - - _, _, _ = run_bit_challenge_processing(spec, state, challenge) - - bit_challenge_index = state.custody_challenge_index - 1 - - custody_response = get_valid_custody_response(spec, state, challenge, test_vector, bit_challenge_index) - - yield from run_custody_response_processing(spec, state, custody_response) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py index f8860cf877..fb9157f2f1 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py @@ -55,8 +55,8 @@ def run_custody_key_reveal_processing(spec, state, custody_key_reveal, valid=Tru @with_all_phases_except(['phase0']) -@always_bls @spec_state_test +@always_bls def test_success(spec, state): state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH custody_key_reveal = get_valid_custody_key_reveal(spec, state) @@ -65,8 +65,8 @@ def test_success(spec, state): @with_all_phases_except(['phase0']) -@always_bls @spec_state_test +@always_bls def test_reveal_too_early(spec, state): custody_key_reveal = get_valid_custody_key_reveal(spec, state) @@ -74,8 +74,8 @@ def test_reveal_too_early(spec, state): @with_all_phases_except(['phase0']) -@always_bls @spec_state_test +@always_bls def test_wrong_period(spec, state): custody_key_reveal = get_valid_custody_key_reveal(spec, state, period=5) @@ -83,8 +83,8 @@ def test_wrong_period(spec, state): @with_all_phases_except(['phase0']) -@always_bls @spec_state_test +@always_bls def test_late_reveal(spec, state): state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH * 3 + 150 custody_key_reveal = get_valid_custody_key_reveal(spec, state) @@ -93,8 +93,8 @@ def test_late_reveal(spec, state): @with_all_phases_except(['phase0']) -@always_bls @spec_state_test +@always_bls def test_double_reveal(spec, state): state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH * 2 custody_key_reveal = get_valid_custody_key_reveal(spec, state) @@ -105,8 +105,8 @@ def test_double_reveal(spec, state): @with_all_phases_except(['phase0']) -@always_bls @spec_state_test +@always_bls def test_max_decrement(spec, state): state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH * 3 + 150 custody_key_reveal = get_valid_custody_key_reveal(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_shard_blocks.py b/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_shard_blocks.py deleted file mode 100644 index 43b0c56c24..0000000000 --- a/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_shard_blocks.py +++ /dev/null @@ -1,177 +0,0 @@ -from copy import deepcopy - -from eth2spec.test.helpers.phase1.shard_block import ( - build_empty_shard_block, - sign_shard_block, -) -from eth2spec.test.helpers.phase1.shard_state import ( - configure_shard_state, - shard_state_transition_and_sign_block, -) -from eth2spec.test.context import ( - always_bls, - expect_assertion_error, - spec_state_test, - with_all_phases_except, -) - - -@with_all_phases_except(['phase0']) -@spec_state_test -@always_bls -def test_process_empty_shard_block(spec, state): - beacon_state, shard_state = configure_shard_state(spec, state) - - block = build_empty_shard_block( - spec, - beacon_state, - shard_state, - slot=shard_state.slot + 1, - signed=True, - full_attestation=False, - ) - - yield 'pre', shard_state - yield 'beacon_state', beacon_state - - shard_state_transition_and_sign_block(spec, beacon_state, shard_state, block) - - yield 'blocks', [block] - yield 'post', shard_state - - -@with_all_phases_except(['phase0']) -@spec_state_test -@always_bls -def test_process_full_attestation_shard_block(spec, state): - beacon_state, shard_state = configure_shard_state(spec, state) - - block = build_empty_shard_block( - spec, - beacon_state, - shard_state, - slot=shard_state.slot + 1, - signed=True, - full_attestation=True, - ) - - yield 'pre', shard_state - yield 'beacon_state', beacon_state - - shard_state_transition_and_sign_block(spec, beacon_state, shard_state, block) - - yield 'blocks', [block] - yield 'post', shard_state - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_prev_slot_block_transition(spec, state): - beacon_state, shard_state = configure_shard_state(spec, state) - - # Go to clean slot - spec.process_shard_slots(shard_state, shard_state.slot + 1) - # Make a block for it - block = build_empty_shard_block(spec, beacon_state, shard_state, slot=shard_state.slot, signed=True) - # Transition to next slot, above block will not be invalid on top of new state. - spec.process_shard_slots(shard_state, shard_state.slot + 1) - - yield 'pre', shard_state - yield 'beacon_state', beacon_state - expect_assertion_error( - lambda: spec.shard_state_transition(beacon_state, shard_state, block) - ) - yield 'blocks', [block] - yield 'post', None - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_same_slot_block_transition(spec, state): - beacon_state, shard_state = configure_shard_state(spec, state) - - # Same slot on top of pre-state, but move out of slot 0 first. - spec.process_shard_slots(shard_state, shard_state.slot + 1) - block = build_empty_shard_block(spec, beacon_state, shard_state, slot=shard_state.slot, signed=True) - - yield 'pre', shard_state - yield 'beacon_state', beacon_state - - shard_state_transition_and_sign_block(spec, beacon_state, shard_state, block) - - yield 'blocks', [block] - yield 'post', shard_state - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_invalid_state_root(spec, state): - beacon_state, shard_state = configure_shard_state(spec, state) - - spec.process_shard_slots(shard_state, shard_state.slot + 1) - block = build_empty_shard_block(spec, beacon_state, shard_state, slot=shard_state.slot) - block.state_root = b'\x36' * 32 - sign_shard_block(spec, beacon_state, shard_state, block) - - yield 'pre', shard_state - yield 'beacon_state', beacon_state - expect_assertion_error( - lambda: spec.shard_state_transition(beacon_state, shard_state, block, validate_state_root=True) - ) - yield 'blocks', [block] - yield 'post', None - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_skipped_slots(spec, state): - beacon_state, shard_state = configure_shard_state(spec, state) - - block = build_empty_shard_block(spec, beacon_state, shard_state, slot=shard_state.slot + 3, signed=True) - - yield 'pre', shard_state - yield 'beacon_state', beacon_state - - shard_state_transition_and_sign_block(spec, beacon_state, shard_state, block) - - yield 'blocks', [block] - yield 'post', shard_state - - assert shard_state.slot == block.slot - latest_block_header = deepcopy(shard_state.latest_block_header) - latest_block_header.state_root = shard_state.hash_tree_root() - assert latest_block_header.hash_tree_root() == block.hash_tree_root() - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_empty_shard_period_transition(spec, state): - beacon_state, shard_state = configure_shard_state(spec, state) - - # modify some of the deltas to ensure the period transition works properly - stub_delta = 10 - shard_state.newer_committee_positive_deltas[0] = stub_delta - shard_state.newer_committee_negative_deltas[0] = stub_delta - - slot = shard_state.slot + spec.SHARD_SLOTS_PER_EPOCH * spec.EPOCHS_PER_SHARD_PERIOD - beacon_state.slot = spec.compute_epoch_of_shard_slot(slot) * spec.SLOTS_PER_EPOCH - 4 - spec.process_slots(beacon_state, spec.compute_epoch_of_shard_slot(slot) * spec.SLOTS_PER_EPOCH) - - # all validators get slashed for not revealing keys - # undo this to allow for a block proposal - for index in range(len(beacon_state.validators)): - beacon_state.validators[index].slashed = False - block = build_empty_shard_block(spec, beacon_state, shard_state, slot=slot, signed=True) - - yield 'pre', shard_state - yield 'beacon_state', beacon_state - - shard_state_transition_and_sign_block(spec, beacon_state, shard_state, block) - - yield 'blocks', [block] - yield 'post', shard_state - - shard_state.older_committee_positive_deltas[0] == stub_delta - shard_state.older_committee_negative_deltas[0] == stub_delta - shard_state.newer_committee_positive_deltas[0] == 0 - shard_state.newer_committee_negative_deltas[0] == 0 diff --git a/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py index b386d36b40..9027660ab2 100644 --- a/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py @@ -6,7 +6,7 @@ from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_empty_block, sign_block, \ transition_unsigned_block from eth2spec.test.helpers.keys import privkeys, pubkeys -from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing +from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing, get_indexed_attestation_participants from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing from eth2spec.test.helpers.attestations import get_valid_attestation from eth2spec.test.helpers.deposits import prepare_state_and_deposit @@ -220,7 +220,7 @@ def test_attester_slashing(spec, state): pre_state = deepcopy(state) attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) - validator_index = attester_slashing.attestation_1.attesting_indices[0] + validator_index = get_indexed_attestation_participants(spec, attester_slashing.attestation_1)[0] assert not state.validators[validator_index].slashed diff --git a/tests/core/pyspec/eth2spec/utils/ssz/ssz_impl.py b/tests/core/pyspec/eth2spec/utils/ssz/ssz_impl.py index c7a144ec2f..113bcf169a 100644 --- a/tests/core/pyspec/eth2spec/utils/ssz/ssz_impl.py +++ b/tests/core/pyspec/eth2spec/utils/ssz/ssz_impl.py @@ -2,7 +2,7 @@ from ..hash_function import hash from .ssz_typing import ( SSZValue, SSZType, BasicValue, BasicType, Series, Elements, Bits, boolean, Container, List, ByteList, - Bitlist, Bitvector, uint, + Bitlist, Bitvector, uint, Bytes32 ) # SSZ Serialization @@ -140,7 +140,7 @@ def chunk_count(typ: SSZType) -> int: raise Exception(f"Type not supported: {typ}") -def hash_tree_root(obj: SSZValue): +def hash_tree_root(obj: SSZValue) -> Bytes32: if isinstance(obj, Series): if is_bottom_layer_kind(obj.type()): leaves = chunkify(pack(obj)) @@ -152,6 +152,6 @@ def hash_tree_root(obj: SSZValue): raise Exception(f"Type not supported: {type(obj)}") if isinstance(obj, (List, ByteList, Bitlist)): - return mix_in_length(merkleize_chunks(leaves, limit=chunk_count(obj.type())), len(obj)) + return Bytes32(mix_in_length(merkleize_chunks(leaves, limit=chunk_count(obj.type())), len(obj))) else: - return merkleize_chunks(leaves) + return Bytes32(merkleize_chunks(leaves)) diff --git a/tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py b/tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py index 4ddff1b5e3..823a4a8d52 100644 --- a/tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py +++ b/tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py @@ -119,6 +119,8 @@ def coerce_type_maybe(v, typ: SSZType, strict: bool = False): return typ(v) elif isinstance(v, GeneratorType): return typ(v) + elif issubclass(typ, Container) and not isinstance(v, typ): + return typ(**{field_name: getattr(v, field_name) for field_name in typ.get_field_names()}) # just return as-is, Value-checkers will take care of it not being coerced, if we are not strict. if strict and not isinstance(v, typ): @@ -192,7 +194,7 @@ def get_fields(cls) -> Dict[str, SSZType]: return dict(cls.__annotations__) @classmethod - def get_field_names(cls) -> Iterable[SSZType]: + def get_field_names(cls) -> Iterable[str]: if not hasattr(cls, '__annotations__'): # no container fields return () return list(cls.__annotations__.keys()) diff --git a/tests/core/pyspec/setup.py b/tests/core/pyspec/setup.py index 0364ccfd81..a196c27a30 100644 --- a/tests/core/pyspec/setup.py +++ b/tests/core/pyspec/setup.py @@ -3,6 +3,7 @@ setup( name='pyspec', packages=find_packages(), + python_requires=">=3.8, <4", tests_require=["pytest"], install_requires=[ "eth-utils>=1.3.0,<2",