diff --git a/.circleci/config.yml b/.circleci/config.yml index 4f806b00f..fd7708f8d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -84,11 +84,25 @@ jobs: command: make citest - store_test_results: path: test_libs/pyspec/test-reports + lint: + docker: + - image: circleci/python:3.6 + working_directory: ~/specs-repo + steps: + - restore_cache: + key: v1-specs-repo-{{ .Branch }}-{{ .Revision }} + - restore_cached_venv: + venv_name: v1-pyspec + reqs_checksum: '{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "test_libs/pyspec/requirements-testing.txt" }}' + - run: + name: Run linter + command: make install_lint && make pyspec && make lint workflows: version: 2.1 test_spec: jobs: - checkout_specs + - lint - install_test: requires: - checkout_specs diff --git a/Makefile b/Makefile index 73d8adea8..8cc889f21 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,13 @@ test: $(PY_SPEC_ALL_TARGETS) citest: $(PY_SPEC_ALL_TARGETS) cd $(PY_SPEC_DIR); mkdir -p test-reports/eth2spec; . venv/bin/activate; python -m pytest --junitxml=test-reports/eth2spec/test_results.xml . +install_lint: + cd $(PY_SPEC_DIR); python3 -m venv venv; . venv/bin/activate; pip3 install flake8==3.5.0 + +lint: + cd $(PY_SPEC_DIR); . venv/bin/activate; \ + flake8 --max-line-length=120 ./eth2spec; + # "make pyspec" to create the pyspec for all phases. pyspec: $(PY_SPEC_ALL_TARGETS) diff --git a/scripts/phase0/build_spec.py b/scripts/phase0/build_spec.py index e3c431a4a..c8cd7348b 100644 --- a/scripts/phase0/build_spec.py +++ b/scripts/phase0/build_spec.py @@ -12,8 +12,18 @@ from typing import ( NewType, Tuple, ) -from eth2spec.utils.minimal_ssz import * -from eth2spec.utils.bls_stub import * +from eth2spec.utils.minimal_ssz import ( + SSZType, + hash_tree_root, + signing_root, +) +from eth2spec.utils.bls_stub import ( + bls_aggregate_pubkeys, + bls_verify, + bls_verify_multiple, +) +from eth2spec.utils.hash_function import hash + # stub, will get overwritten by real var SLOTS_PER_EPOCH = 64 @@ -61,6 +71,7 @@ def hash(x): hash_cache[x] = ret return ret + # Access to overwrite spec constants based on configuration def apply_constants_preset(preset: Dict[str, Any]): global_vars = globals() diff --git a/scripts/phase0/function_puller.py b/scripts/phase0/function_puller.py index 1fad41fa9..750f19590 100644 --- a/scripts/phase0/function_puller.py +++ b/scripts/phase0/function_puller.py @@ -55,15 +55,19 @@ def get_spec(file_name: str) -> List[str]: if eligible: code_lines.append(row[0] + ' = ' + (row[1].replace('**TBD**', '0x1234567890123456789012345678901234567890'))) # Build type-def re-initialization - code_lines.append('') + code_lines.append('\n') code_lines.append('def init_SSZ_types():') code_lines.append(' global_vars = globals()') for ssz_type_name, ssz_type in type_defs: code_lines.append('') for type_line in ssz_type: - code_lines.append(' ' + type_line) + if len(type_line) > 0: + code_lines.append(' ' + type_line) code_lines.append('\n') - code_lines.append('ssz_types = [' + ', '.join([f'\'{ssz_type_name}\'' for (ssz_type_name, _) in type_defs]) + ']') + code_lines.append('ssz_types = [\n') + for (ssz_type_name, _) in type_defs: + code_lines.append(f' {ssz_type_name},\n') + code_lines.append(']') code_lines.append('\n') code_lines.append('def get_ssz_type_by_name(name: str) -> SSZType:') code_lines.append(' return globals()[name]') diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 103870ce2..bbca333cd 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -579,8 +579,10 @@ The types are defined topologically to aid in facilitating an executable version 'latest_block_roots': ['bytes32', SLOTS_PER_HISTORICAL_ROOT], 'latest_state_roots': ['bytes32', SLOTS_PER_HISTORICAL_ROOT], 'latest_active_index_roots': ['bytes32', LATEST_ACTIVE_INDEX_ROOTS_LENGTH], - 'latest_slashed_balances': ['uint64', LATEST_SLASHED_EXIT_LENGTH], # Balances slashed at every withdrawal period - 'latest_block_header': BeaconBlockHeader, # `latest_block_header.state_root == ZERO_HASH` temporarily + # Balances slashed at every withdrawal period + 'latest_slashed_balances': ['uint64', LATEST_SLASHED_EXIT_LENGTH], + # `latest_block_header.state_root == ZERO_HASH` temporarily + 'latest_block_header': BeaconBlockHeader, 'historical_roots': ['bytes32'], # Ethereum 1.0 chain data @@ -1149,7 +1151,9 @@ def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: #### `slash_validator` ```python -def slash_validator(state: BeaconState, slashed_index: ValidatorIndex, whistleblower_index: ValidatorIndex=None) -> None: +def slash_validator(state: BeaconState, + slashed_index: ValidatorIndex, + whistleblower_index: ValidatorIndex=None) -> None: """ Slash the validator with index ``slashed_index``. """ @@ -1287,7 +1291,8 @@ def get_matching_head_attestations(state: BeaconState, epoch: Epoch) -> List[Pen ``` ```python -def get_unslashed_attesting_indices(state: BeaconState, attestations: List[PendingAttestation]) -> List[ValidatorIndex]: +def get_unslashed_attesting_indices(state: BeaconState, + attestations: List[PendingAttestation]) -> List[ValidatorIndex]: output = set() for a in attestations: output = output.union(get_attesting_indices(state, a.data, a.aggregation_bitfield)) @@ -1300,7 +1305,9 @@ def get_attesting_balance(state: BeaconState, attestations: List[PendingAttestat ``` ```python -def get_winning_crosslink_and_attesting_indices(state: BeaconState, epoch: Epoch, shard: Shard) -> Tuple[Crosslink, List[ValidatorIndex]]: +def get_winning_crosslink_and_attesting_indices(state: BeaconState, + epoch: Epoch, + shard: Shard) -> Tuple[Crosslink, List[ValidatorIndex]]: attestations = [a for a in get_matching_source_attestations(state, epoch) if a.data.crosslink.shard == shard] crosslinks = list(filter( lambda c: hash_tree_root(state.current_crosslinks[shard]) in (c.parent_root, hash_tree_root(c)), @@ -1332,12 +1339,16 @@ def process_justification_and_finalization(state: BeaconState) -> None: state.previous_justified_epoch = state.current_justified_epoch state.previous_justified_root = state.current_justified_root state.justification_bitfield = (state.justification_bitfield << 1) % 2**64 - previous_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, previous_epoch)) + previous_epoch_matching_target_balance = get_attesting_balance( + state, get_matching_target_attestations(state, previous_epoch) + ) if previous_epoch_matching_target_balance * 3 >= get_total_active_balance(state) * 2: state.current_justified_epoch = previous_epoch state.current_justified_root = get_block_root(state, state.current_justified_epoch) state.justification_bitfield |= (1 << 1) - current_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, current_epoch)) + current_epoch_matching_target_balance = get_attesting_balance( + state, get_matching_target_attestations(state, current_epoch) + ) if current_epoch_matching_target_balance * 3 >= get_total_active_balance(state) * 2: state.current_justified_epoch = current_epoch state.current_justified_root = get_block_root(state, state.current_justified_epoch) @@ -1431,7 +1442,9 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: for index in eligible_validator_indices: penalties[index] += BASE_REWARDS_PER_EPOCH * get_base_reward(state, index) if index not in matching_target_attesting_indices: - penalties[index] += state.validator_registry[index].effective_balance * finality_delay // INACTIVITY_PENALTY_QUOTIENT + penalties[index] += ( + state.validator_registry[index].effective_balance * finality_delay // INACTIVITY_PENALTY_QUOTIENT + ) return rewards, penalties ``` @@ -1478,7 +1491,10 @@ Run the following function: def process_registry_updates(state: BeaconState) -> None: # Process activation eligibility and ejections for index, validator in enumerate(state.validator_registry): - if validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and validator.effective_balance >= MAX_EFFECTIVE_BALANCE: + if ( + validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and + validator.effective_balance >= MAX_EFFECTIVE_BALANCE + ): validator.activation_eligibility_epoch = get_current_epoch(state) if is_active_validator(validator, get_current_epoch(state)) and validator.effective_balance <= EJECTION_BALANCE: @@ -1603,7 +1619,12 @@ def process_block_header(state: BeaconState, block: BeaconBlock) -> None: def process_randao(state: BeaconState, block: BeaconBlock) -> None: proposer = state.validator_registry[get_beacon_proposer_index(state)] # Verify that the provided randao value is valid - assert bls_verify(proposer.pubkey, hash_tree_root(get_current_epoch(state)), block.body.randao_reveal, get_domain(state, DOMAIN_RANDAO)) + assert bls_verify( + proposer.pubkey, + hash_tree_root(get_current_epoch(state)), + block.body.randao_reveal, + get_domain(state, DOMAIN_RANDAO), + ) # Mix it in state.latest_randao_mixes[get_current_epoch(state) % LATEST_RANDAO_MIXES_LENGTH] = ( xor(get_randao_mix(state, get_current_epoch(state)), @@ -1748,7 +1769,9 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: validator_pubkeys = [v.pubkey for v in state.validator_registry] if pubkey not in validator_pubkeys: # Verify the deposit signature (proof of possession) - if not bls_verify(pubkey, signing_root(deposit.data), deposit.data.signature, get_domain(state, DOMAIN_DEPOSIT)): + if not bls_verify( + pubkey, signing_root(deposit.data), deposit.data.signature, get_domain(state, DOMAIN_DEPOSIT) + ): return # Add validator and balance entries diff --git a/test_libs/pyspec/eth2spec/debug/decode.py b/test_libs/pyspec/eth2spec/debug/decode.py index aeac3924d..e9aa8bc2b 100644 --- a/test_libs/pyspec/eth2spec/debug/decode.py +++ b/test_libs/pyspec/eth2spec/debug/decode.py @@ -16,11 +16,11 @@ def decode(json, typ): for field, subtype in typ.fields.items(): temp[field] = decode(json[field], subtype) if field + "_hash_tree_root" in json: - assert(json[field + "_hash_tree_root"][2:] == + assert(json[field + "_hash_tree_root"][2:] == hash_tree_root(temp[field], subtype).hex()) ret = typ(**temp) if "hash_tree_root" in json: - assert(json["hash_tree_root"][2:] == + assert(json["hash_tree_root"][2:] == hash_tree_root(ret, typ).hex()) return ret else: diff --git a/test_libs/pyspec/eth2spec/debug/encode.py b/test_libs/pyspec/eth2spec/debug/encode.py index d3513e638..b38e5fe98 100644 --- a/test_libs/pyspec/eth2spec/debug/encode.py +++ b/test_libs/pyspec/eth2spec/debug/encode.py @@ -25,4 +25,3 @@ def encode(value, typ, include_hash_tree_roots=False): else: print(value, typ) raise Exception("Type not recognized") - diff --git a/test_libs/pyspec/eth2spec/debug/random_value.py b/test_libs/pyspec/eth2spec/debug/random_value.py index a853d2328..f28181943 100644 --- a/test_libs/pyspec/eth2spec/debug/random_value.py +++ b/test_libs/pyspec/eth2spec/debug/random_value.py @@ -31,7 +31,12 @@ class RandomizationMode(Enum): return self.value in [0, 4, 5] -def get_random_ssz_object(rng: Random, typ: Any, max_bytes_length: int, max_list_length: int, mode: RandomizationMode, chaos: bool) -> Any: +def get_random_ssz_object(rng: Random, + typ: Any, + max_bytes_length: int, + max_list_length: int, + mode: RandomizationMode, + chaos: bool) -> Any: """ Create an object for a given type, filled with random data. :param rng: The random number generator to use. @@ -77,7 +82,10 @@ def get_random_ssz_object(rng: Random, typ: Any, max_bytes_length: int, max_list return get_random_basic_value(rng, typ) # Vector: elif isinstance(typ, list) and len(typ) == 2: - return [get_random_ssz_object(rng, typ[0], max_bytes_length, max_list_length, mode, chaos) for _ in range(typ[1])] + return [ + get_random_ssz_object(rng, typ[0], max_bytes_length, max_list_length, mode, chaos) + for _ in range(typ[1]) + ] # List: elif isinstance(typ, list) and len(typ) == 1: length = rng.randint(0, max_list_length) @@ -85,10 +93,17 @@ def get_random_ssz_object(rng: Random, typ: Any, max_bytes_length: int, max_list length = 1 if mode == RandomizationMode.mode_max_count: length = max_list_length - return [get_random_ssz_object(rng, typ[0], max_bytes_length, max_list_length, mode, chaos) for _ in range(length)] + return [ + get_random_ssz_object(rng, typ[0], max_bytes_length, max_list_length, mode, chaos) + for _ in range(length) + ] # Container: elif hasattr(typ, 'fields'): - return typ(**{field: get_random_ssz_object(rng, subtype, max_bytes_length, max_list_length, mode, chaos) for field, subtype in typ.fields.items()}) + return typ(**{ + field: + get_random_ssz_object(rng, subtype, max_bytes_length, max_list_length, mode, chaos) + for field, subtype in typ.fields.items() + }) else: print(typ) raise Exception("Type not recognized")