Merge pull request #1928 from ethereum/hwwhww/shard-block-tests

Add tests for `shard_state_transition` and some refactorings
This commit is contained in:
Hsiao-Wei Wang 2020-06-26 11:54:23 +08:00 committed by GitHub
commit a159a2da82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 208 additions and 30 deletions

View File

@ -1,4 +1,4 @@
# Ethereum 2.0 Phase 1 -- The Beacon Chain for Shards
# Ethereum 2.0 Phase 1 -- The Beacon Chain with Shards
**Notice**: This document is a work-in-progress for researchers and implementers.

View File

@ -174,9 +174,7 @@ def on_shard_block(store: Store, shard_store: ShardStore, signed_shard_block: Si
# Check the block is valid and compute the post-state
shard_state = shard_parent_state.copy()
shard_state_transition(
shard_state, signed_shard_block,
validate=True, beacon_parent_state=beacon_parent_state)
shard_state_transition(shard_state, signed_shard_block, beacon_parent_state, validate_result=True)
# Add new block to the store
# Note: storing `SignedShardBlock` format for computing `ShardTransition.proposer_signature_aggregate`

View File

@ -11,7 +11,9 @@
- [Introduction](#introduction)
- [Helper functions](#helper-functions)
- [Shard block verification functions](#shard-block-verification-functions)
- [Shard state transition](#shard-state-transition)
- [`verify_shard_block_message`](#verify_shard_block_message)
- [`verify_shard_block_signature`](#verify_shard_block_signature)
- [Shard state transition function](#shard-state-transition-function)
- [Fraud proofs](#fraud-proofs)
- [Verifying the proof](#verifying-the-proof)
@ -25,6 +27,8 @@ This document describes the shard transition function and fraud proofs as part o
### Shard block verification functions
#### `verify_shard_block_message`
```python
def verify_shard_block_message(beacon_parent_state: BeaconState,
shard_parent_state: ShardState,
@ -49,6 +53,8 @@ def verify_shard_block_message(beacon_parent_state: BeaconState,
return True
```
#### `verify_shard_block_signature`
```python
def verify_shard_block_signature(beacon_parent_state: BeaconState,
signed_block: SignedShardBlock) -> bool:
@ -58,16 +64,18 @@ def verify_shard_block_signature(beacon_parent_state: BeaconState,
return bls.Verify(proposer.pubkey, signing_root, signed_block.signature)
```
## Shard state transition
## Shard state transition function
The post-state corresponding to a pre-state `shard_state` and a signed block `signed_block` is defined as `shard_state_transition(shard_state, signed_block, beacon_parent_state)`, where `beacon_parent_state` is the parent beacon state of the `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.
```python
def shard_state_transition(shard_state: ShardState,
signed_block: SignedShardBlock,
validate: bool = True,
beacon_parent_state: Optional[BeaconState] = None) -> ShardState:
if validate:
assert beacon_parent_state is not None
assert verify_shard_block_message(beacon_parent_state, shard_state, signed_block.message)
beacon_parent_state: BeaconState,
validate_result: bool = True) -> ShardState:
assert verify_shard_block_message(beacon_parent_state, shard_state, signed_block.message)
if validate_result:
assert verify_shard_block_signature(beacon_parent_state, signed_block)
process_shard_block(shard_state, signed_block.message)
@ -84,11 +92,8 @@ def process_shard_block(shard_state: ShardState,
prev_gasprice = shard_state.gasprice
shard_block_length = len(block.body)
shard_state.gasprice = compute_updated_gasprice(prev_gasprice, uint64(shard_block_length))
if shard_block_length == 0:
latest_block_root = shard_state.latest_block_root
else:
latest_block_root = hash_tree_root(block)
shard_state.latest_block_root = latest_block_root
if shard_block_length != 0:
shard_state.latest_block_root = hash_tree_root(block)
```
## Fraud proofs
@ -128,7 +133,7 @@ def is_valid_fraud_proof(beacon_state: BeaconState,
else:
shard_state = transition.shard_states[offset_index - 1] # Not doing the actual state updates here.
shard_state_transition(shard_state, block, validate=False)
process_shard_block(shard_state, block.message)
if shard_state != transition.shard_states[offset_index]:
return True

View File

@ -299,7 +299,7 @@ def get_shard_transition_fields(
shard_block = SignedShardBlock(message=ShardBlock(slot=slot, shard=shard))
shard_data_roots.append(Root())
shard_state = shard_state.copy()
shard_state_transition(shard_state, shard_block, validate=False)
process_shard_block(shard_state, shard_block.message)
shard_states.append(shard_state)
shard_block_lengths.append(len(shard_block.message.body))

View File

@ -35,3 +35,8 @@ def get_shard_transition_of_committee(spec, state, committee_index, shard_blocks
shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot)
shard_transition = spec.get_shard_transition(state, shard, shard_blocks=shard_blocks)
return shard_transition
def is_full_crosslink(spec, state):
epoch = spec.compute_epoch_at_slot(state.slot)
return spec.get_committee_count_per_slot(state, epoch) >= spec.get_active_shard_count(state)

View File

@ -8,7 +8,10 @@ from eth2spec.test.helpers.attestations import (
get_valid_on_time_attestation,
run_attestation_processing,
)
from eth2spec.test.helpers.shard_transitions import run_shard_transitions_processing
from eth2spec.test.helpers.shard_transitions import (
run_shard_transitions_processing,
is_full_crosslink,
)
from eth2spec.test.helpers.shard_block import (
build_shard_block,
get_shard_transitions,
@ -42,11 +45,6 @@ def get_attestations_and_shard_transitions(spec, state, shard_block_dict):
return attestations, shard_transitions
def is_full_crosslink(spec, state):
epoch = spec.compute_epoch_at_slot(state.slot)
return spec.get_committee_count_per_slot(state, epoch) >= spec.get_active_shard_count(state)
def run_successful_crosslink_tests(spec, state, target_len_offset_slot):
state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot)
init_slot = state.slot

View File

@ -9,15 +9,17 @@ from eth2spec.test.helpers.attestations import get_valid_on_time_attestation
from eth2spec.test.helpers.block import build_empty_block
from eth2spec.test.helpers.shard_block import (
build_shard_block,
get_sample_shard_block_body,
get_shard_transitions,
)
from eth2spec.test.helpers.shard_transitions import is_full_crosslink
from eth2spec.test.helpers.state import state_transition_and_sign_block, transition_to_valid_shard_slot, transition_to
def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, committee_index, shard, valid=True):
transition_to(spec, state, state.slot + target_len_offset_slot)
body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE
body = get_sample_shard_block_body(spec, is_max=True)
shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True)
shard_block_dict: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]}
@ -40,14 +42,17 @@ def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, comm
pre_gasprice = state.shard_states[shard].gasprice
pre_shard_states = state.shard_states.copy()
yield 'pre', state.copy()
yield 'block', beacon_block
state_transition_and_sign_block(spec, state, beacon_block)
if valid:
yield 'post', state
else:
if not valid:
state_transition_and_sign_block(spec, state, beacon_block, expect_fail=True)
yield 'block', beacon_block
yield 'post', None
return
state_transition_and_sign_block(spec, state, beacon_block)
yield 'block', beacon_block
yield 'post', state
for shard in range(spec.get_active_shard_count(state)):
post_shard_state = state.shard_states[shard]
if shard in shard_block_dict:
@ -67,6 +72,10 @@ def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, comm
@spec_state_test
def test_process_beacon_block_with_normal_shard_transition(spec, state):
# NOTE: this test is only for full crosslink (minimal config), not for mainnet
if not is_full_crosslink(spec, state):
# skip
return
state = transition_to_valid_shard_slot(spec, state)
target_len_offset_slot = 1
@ -81,6 +90,10 @@ def test_process_beacon_block_with_normal_shard_transition(spec, state):
@spec_state_test
def test_process_beacon_block_with_empty_proposal_transition(spec, state):
# NOTE: this test is only for full crosslink (minimal config), not for mainnet
if not is_full_crosslink(spec, state):
# skip
return
state = transition_to_valid_shard_slot(spec, state)
target_len_offset_slot = 1

View File

@ -0,0 +1,159 @@
from eth2spec.test.context import (
PHASE0,
always_bls,
expect_assertion_error,
spec_state_test,
with_all_phases_except,
)
from eth2spec.test.helpers.shard_block import (
build_shard_block,
sign_shard_block,
)
from eth2spec.test.helpers.shard_transitions import is_full_crosslink
from eth2spec.test.helpers.state import transition_to_valid_shard_slot
def run_shard_blocks(spec, shard_state, signed_shard_block, beacon_parent_state, valid=True):
pre_shard_state = shard_state.copy()
yield 'pre', pre_shard_state
yield 'signed_shard_block', signed_shard_block
yield 'beacon_parent_state', beacon_parent_state
if not valid:
expect_assertion_error(
lambda: spec.shard_state_transition(shard_state, signed_shard_block, beacon_parent_state)
)
yield 'post', None
return
spec.shard_state_transition(shard_state, signed_shard_block, beacon_parent_state)
yield 'post', shard_state
# Verify `process_shard_block`
block = signed_shard_block.message
assert shard_state.slot == block.slot
shard_block_length = len(block.body)
assert shard_state.gasprice == spec.compute_updated_gasprice(pre_shard_state.gasprice, shard_block_length)
if shard_block_length != 0:
shard_state.latest_block_root == block.hash_tree_root()
else:
shard_state.latest_block_root == pre_shard_state.latest_block_root
@with_all_phases_except([PHASE0])
@spec_state_test
@always_bls
def test_valid_shard_block(spec, state):
if not is_full_crosslink(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_slot(spec, state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state)
#
# verify_shard_block_message
#
@with_all_phases_except([PHASE0])
@spec_state_test
def test_invalid_shard_parent_root(spec, state):
if not is_full_crosslink(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_slot(spec, state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
signed_shard_block.message.shard_parent_root = b'\x12' * 32
sign_shard_block(spec, beacon_state, shard, signed_shard_block)
yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)
@with_all_phases_except([PHASE0])
@spec_state_test
def test_invalid_beacon_parent_root(spec, state):
if not is_full_crosslink(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_slot(spec, state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
signed_shard_block.message.beacon_parent_root = b'\x12' * 32
sign_shard_block(spec, beacon_state, shard, signed_shard_block)
yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)
@with_all_phases_except([PHASE0])
@spec_state_test
def test_invalid_slot(spec, state):
if not is_full_crosslink(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_slot(spec, state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
signed_shard_block.message.slot = beacon_state.slot + 1
proposer_index = spec.get_shard_proposer_index(beacon_state, signed_shard_block.message.slot, shard)
sign_shard_block(spec, beacon_state, shard, signed_shard_block, proposer_index=proposer_index)
yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)
@with_all_phases_except([PHASE0])
@spec_state_test
def test_invalid_proposer_index(spec, state):
if not is_full_crosslink(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_slot(spec, state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
active_validator_indices = spec.get_active_validator_indices(beacon_state, spec.get_current_epoch(beacon_state))
proposer_index = (
(spec.get_shard_proposer_index(beacon_state, signed_shard_block.message.slot, shard) + 1)
% len(active_validator_indices)
)
signed_shard_block.message.proposer_index = proposer_index
sign_shard_block(spec, beacon_state, shard, signed_shard_block, proposer_index=proposer_index)
yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)
#
# verify_shard_block_signature
#
@with_all_phases_except([PHASE0])
@spec_state_test
@always_bls
def test_invalid_signature(spec, state):
if not is_full_crosslink(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_slot(spec, state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=False)
yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)