Skip to content

Commit

Permalink
Replace assert with require in phase0/beacon-chain.md
Browse files Browse the repository at this point in the history
  • Loading branch information
hwwhww committed Apr 29, 2021
1 parent c3dfc2e commit bacd3e4
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 59 deletions.
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,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
Expand Down
114 changes: 63 additions & 51 deletions specs/phase0/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
- [`SignedBeaconBlock`](#signedbeaconblock)
- [`SignedBeaconBlockHeader`](#signedbeaconblockheader)
- [Helper functions](#helper-functions)
- [Validation](#validation)
- [`require`](#require)
- [Math](#math)
- [`integer_squareroot`](#integer_squareroot)
- [`xor`](#xor)
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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
Expand All @@ -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))
Expand Down Expand Up @@ -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]
```

Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
```

Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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)
```
Expand All @@ -1758,31 +1772,31 @@ 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)
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
require(slashed_any)
```

##### Attestations

```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,
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
```
4 changes: 3 additions & 1 deletion tests/core/pyspec/eth2spec/test/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
ALL_PHASES, FORKS_BEFORE_ALTAIR,
Expand Down Expand Up @@ -209,6 +209,8 @@ def expect_assertion_error(fn):
try:
fn()
bad = True
except ValidationError:
pass
except AssertionError:
pass
except IndexError:
Expand Down
4 changes: 4 additions & 0 deletions tests/core/pyspec/eth2spec/test/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
class SkippedTest(Exception):
...


class ValidationError(Exception):
...
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
Expand Down

0 comments on commit bacd3e4

Please sign in to comment.