From 9e0901ba2a974868271ffef188c5998231b6b1dc Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 29 Apr 2021 23:18:24 +0800 Subject: [PATCH] Replace `assert` with `require` in phase0/beacon-chain.md --- setup.py | 1 + specs/phase0/beacon-chain.md | 114 ++++++++++-------- tests/core/pyspec/eth2spec/test/context.py | 4 +- tests/core/pyspec/eth2spec/test/exceptions.py | 4 + .../fork_choice/test_on_attestation.py | 10 +- 5 files changed, 74 insertions(+), 59 deletions(-) diff --git a/setup.py b/setup.py index 0bdf04b55b..d423f9d32d 100644 --- a/setup.py +++ b/setup.py @@ -291,6 +291,7 @@ def imports(cls) -> str: from eth2spec.utils.ssz.ssz_typing import Bitvector # noqa: F401 from eth2spec.utils import bls from eth2spec.utils.hash_function import hash +from eth2spec.test.exceptions import ValidationError ''' @classmethod diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 3176d543c3..ab7ebe7968 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -50,6 +50,8 @@ - [`SignedBeaconBlock`](#signedbeaconblock) - [`SignedBeaconBlockHeader`](#signedbeaconblockheader) - [Helper functions](#helper-functions) + - [Validation](#validation) + - [`require`](#require) - [Math](#math) - [`integer_squareroot`](#integer_squareroot) - [`xor`](#xor) @@ -563,6 +565,23 @@ class SignedBeaconBlockHeader(Container): *Note*: The definitions below are for specification purposes and are not necessarily optimal implementations. + +### Validation + +#### `require` + +```python +def require(condition: bool, message: str=None) -> None: + """ + Check if the given condition is satisfied. + If not, raise an exception to signal that it's INVALID. + """ + if not condition: + raise ValidationError(str(condition) if message is None else message) +``` + +**Note**: The `message` value is just for debugging the executable spec. The error handling details are implementation specific. + ### Math #### `integer_squareroot` @@ -734,7 +753,7 @@ def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) -> """ Return the shuffled index corresponding to ``seed`` (and ``index_count``). """ - assert index < index_count + require(index < index_count) # Swap or not (https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf) # See the 'generalized domain' algorithm on page 3 @@ -761,7 +780,7 @@ def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex] """ Return from ``indices`` a random index sampled by effective balance. """ - assert len(indices) > 0 + require(len(indices) > 0) MAX_RANDOM_BYTE = 2**8 - 1 i = uint64(0) total = uint64(len(indices)) @@ -913,7 +932,7 @@ def get_block_root_at_slot(state: BeaconState, slot: Slot) -> Root: """ Return the block root at a recent ``slot``. """ - assert slot < state.slot <= slot + SLOTS_PER_HISTORICAL_ROOT + require(slot < state.slot <= slot + SLOTS_PER_HISTORICAL_ROOT) return state.block_roots[slot % SLOTS_PER_HISTORICAL_ROOT] ``` @@ -1208,7 +1227,7 @@ Let `genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state))`. ## Beacon chain state transition function -The post-state corresponding to a pre-state `state` and a signed block `signed_block` is defined as `state_transition(state, signed_block)`. State transitions that trigger an unhandled exception (e.g. a failed `assert` or an out-of-range list access) are considered invalid. State transitions that cause a `uint64` overflow or underflow are also considered invalid. +The post-state corresponding to a pre-state `state` and a signed block `signed_block` is defined as `state_transition(state, signed_block)`. State transitions that trigger an unhandled exception (e.g. `ValidationError` exception, a failed `assert`, or an out-of-range list access) are considered invalid. State transitions that cause a `uint64` overflow or underflow are also considered invalid. ```python def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool=True) -> None: @@ -1217,12 +1236,12 @@ def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, valida process_slots(state, block.slot) # Verify signature if validate_result: - assert verify_block_signature(state, signed_block) + require(verify_block_signature(state, signed_block)) # Process block process_block(state, block) # Verify state root if validate_result: - assert block.state_root == hash_tree_root(state) + require(block.state_root == hash_tree_root(state)) ``` ```python @@ -1234,7 +1253,7 @@ def verify_block_signature(state: BeaconState, signed_block: SignedBeaconBlock) ```python def process_slots(state: BeaconState, slot: Slot) -> None: - assert state.slot < slot + require(state.slot < slot) while state.slot < slot: process_slot(state) # Process epoch on the start slot of the next epoch @@ -1276,7 +1295,7 @@ def process_epoch(state: BeaconState) -> None: ```python def get_matching_source_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: - assert epoch in (get_previous_epoch(state), get_current_epoch(state)) + require(epoch in (get_previous_epoch(state), get_current_epoch(state))) return state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations ``` @@ -1664,14 +1683,10 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: ```python def process_block_header(state: BeaconState, block: BeaconBlock) -> None: - # Verify that the slots match - assert block.slot == state.slot - # Verify that the block is newer than latest block header - assert block.slot > state.latest_block_header.slot - # Verify that proposer index is the correct index - assert block.proposer_index == get_beacon_proposer_index(state) - # Verify that the parent matches - assert block.parent_root == hash_tree_root(state.latest_block_header) + require(block.slot == state.slot, 'The slots should match.') + require(block.slot > state.latest_block_header.slot, 'The block should be newer than latest block header.') + require(block.proposer_index == get_beacon_proposer_index(state), 'The proposer index should be the correct.') + require(block.parent_root == hash_tree_root(state.latest_block_header), 'The parent root should match.') # Cache current block as the new latest block state.latest_block_header = BeaconBlockHeader( slot=block.slot, @@ -1683,7 +1698,7 @@ def process_block_header(state: BeaconState, block: BeaconBlock) -> None: # Verify proposer is not slashed proposer = state.validators[block.proposer_index] - assert not proposer.slashed + require(not proposer.slashed) ``` #### RANDAO @@ -1694,7 +1709,7 @@ def process_randao(state: BeaconState, body: BeaconBlockBody) -> None: # Verify RANDAO reveal proposer = state.validators[get_beacon_proposer_index(state)] signing_root = compute_signing_root(epoch, get_domain(state, DOMAIN_RANDAO)) - assert bls.Verify(proposer.pubkey, signing_root, body.randao_reveal) + require(bls.Verify(proposer.pubkey, signing_root, body.randao_reveal)) # Mix in RANDAO reveal mix = xor(get_randao_mix(state, epoch), hash(body.randao_reveal)) state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR] = mix @@ -1713,8 +1728,10 @@ def process_eth1_data(state: BeaconState, body: BeaconBlockBody) -> None: ```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) + require( + len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index), + 'The outstanding deposits should be processed up to the maximum number of deposits.', + ) def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: for operation in operations: @@ -1734,20 +1751,17 @@ def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSla header_1 = proposer_slashing.signed_header_1.message header_2 = proposer_slashing.signed_header_2.message - # Verify header slots match - assert header_1.slot == header_2.slot - # Verify header proposer indices match - assert header_1.proposer_index == header_2.proposer_index - # Verify the headers are different - assert header_1 != header_2 + require(header_1.slot == header_2.slot, 'The header slots should match.') + require(header_1.proposer_index == header_2.proposer_index, 'The header proposer indices should match.') + require(header_1 != header_2, 'The headers should be different.') # Verify the proposer is slashable proposer = state.validators[header_1.proposer_index] - assert is_slashable_validator(proposer, get_current_epoch(state)) + require(is_slashable_validator(proposer, get_current_epoch(state))) # Verify signatures for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2): domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(signed_header.message.slot)) signing_root = compute_signing_root(signed_header.message, domain) - assert bls.Verify(proposer.pubkey, signing_root, signed_header.signature) + require(bls.Verify(proposer.pubkey, signing_root, signed_header.signature)) slash_validator(state, header_1.proposer_index) ``` @@ -1758,9 +1772,9 @@ def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSla def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSlashing) -> None: attestation_1 = attester_slashing.attestation_1 attestation_2 = attester_slashing.attestation_2 - assert is_slashable_attestation_data(attestation_1.data, attestation_2.data) - assert is_valid_indexed_attestation(state, attestation_1) - assert is_valid_indexed_attestation(state, attestation_2) + require(is_slashable_attestation_data(attestation_1.data, attestation_2.data)) + require(is_valid_indexed_attestation(state, attestation_1)) + require(is_valid_indexed_attestation(state, attestation_2)) slashed_any = False indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices) @@ -1768,7 +1782,7 @@ def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSla if is_slashable_validator(state.validators[index], get_current_epoch(state)): slash_validator(state, index) slashed_any = True - assert slashed_any + require(slashed_any) ``` ##### Attestations @@ -1776,13 +1790,13 @@ def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSla ```python def process_attestation(state: BeaconState, attestation: Attestation) -> None: data = attestation.data - assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state)) - assert data.target.epoch == compute_epoch_at_slot(data.slot) - assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= data.slot + SLOTS_PER_EPOCH - assert data.index < get_committee_count_per_slot(state, data.target.epoch) + require(data.target.epoch in (get_previous_epoch(state), get_current_epoch(state))) + require(data.target.epoch == compute_epoch_at_slot(data.slot)) + require(data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= data.slot + SLOTS_PER_EPOCH) + require(data.index < get_committee_count_per_slot(state, data.target.epoch)) committee = get_beacon_committee(state, data.slot, data.index) - assert len(attestation.aggregation_bits) == len(committee) + require(len(attestation.aggregation_bits) == len(committee)) pending_attestation = PendingAttestation( data=data, @@ -1792,14 +1806,14 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: ) if data.target.epoch == get_current_epoch(state): - assert data.source == state.current_justified_checkpoint + require(data.source == state.current_justified_checkpoint) state.current_epoch_attestations.append(pending_attestation) else: - assert data.source == state.previous_justified_checkpoint + require(data.source == state.previous_justified_checkpoint) state.previous_epoch_attestations.append(pending_attestation) # Verify signature - assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) + require(is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation))) ``` ##### Deposits @@ -1823,13 +1837,13 @@ def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validato ```python def process_deposit(state: BeaconState, deposit: Deposit) -> None: # Verify the Merkle branch - assert is_valid_merkle_branch( + require(is_valid_merkle_branch( leaf=hash_tree_root(deposit.data), branch=deposit.proof, depth=DEPOSIT_CONTRACT_TREE_DEPTH + 1, # Add 1 for the List length mix-in index=state.eth1_deposit_index, root=state.eth1_data.deposit_root, - ) + )) # Deposits must be processed in order state.eth1_deposit_index += 1 @@ -1864,18 +1878,16 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVoluntaryExit) -> None: voluntary_exit = signed_voluntary_exit.message validator = state.validators[voluntary_exit.validator_index] - # Verify the validator is active - assert is_active_validator(validator, get_current_epoch(state)) - # Verify exit has not been initiated - assert validator.exit_epoch == FAR_FUTURE_EPOCH - # Exits must specify an epoch when they become valid; they are not valid before then - assert get_current_epoch(state) >= voluntary_exit.epoch - # Verify the validator has been active long enough - assert get_current_epoch(state) >= validator.activation_epoch + SHARD_COMMITTEE_PERIOD + require(is_active_validator(validator, get_current_epoch(state)), 'The validator should be active.') + require(validator.exit_epoch == FAR_FUTURE_EPOCH, 'The exit should have not been initiated.') + require(get_current_epoch(state) >= voluntary_exit.epoch, + 'Exits must specify an epoch when they become valid; they are not valid before then.') + require(get_current_epoch(state) >= validator.activation_epoch + SHARD_COMMITTEE_PERIOD, + 'The validator should have been active long enough.') # Verify signature domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch) signing_root = compute_signing_root(voluntary_exit, domain) - assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature) + require(bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature)) # Initiate exit initiate_validator_exit(state, voluntary_exit.validator_index) ``` diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 57071169fa..6e5082eeee 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -5,7 +5,7 @@ from eth2spec.merge import spec as spec_merge from eth2spec.utils import bls -from .exceptions import SkippedTest +from .exceptions import SkippedTest, ValidationError from .helpers.constants import ( PHASE0, ALTAIR, MERGE, ALL_PHASES, FORKS_BEFORE_ALTAIR, FORKS_BEFORE_MERGE, @@ -209,6 +209,8 @@ def expect_assertion_error(fn): try: fn() bad = True + except ValidationError: + pass except AssertionError: pass except IndexError: diff --git a/tests/core/pyspec/eth2spec/test/exceptions.py b/tests/core/pyspec/eth2spec/test/exceptions.py index c553ec3744..ad81d10fdf 100644 --- a/tests/core/pyspec/eth2spec/test/exceptions.py +++ b/tests/core/pyspec/eth2spec/test/exceptions.py @@ -1,2 +1,6 @@ class SkippedTest(Exception): ... + + +class ValidationError(Exception): + ... diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py index 8ff6bbb389..8b83751b75 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py @@ -1,4 +1,4 @@ -from eth2spec.test.context import with_all_phases, spec_state_test +from eth2spec.test.context import with_all_phases, spec_state_test, expect_assertion_error from eth2spec.test.helpers.block import build_empty_block_for_next_slot from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE @@ -8,12 +8,8 @@ def run_on_attestation(spec, state, store, attestation, valid=True): if not valid: - try: - spec.on_attestation(store, attestation) - except AssertionError: - return - else: - assert False + expect_assertion_error(lambda: spec.on_attestation(store, attestation)) + return indexed_attestation = spec.get_indexed_attestation(state, attestation) spec.on_attestation(store, attestation)