Merge pull request #1928 from ethereum/hwwhww/shard-block-tests
Add tests for `shard_state_transition` and some refactorings
This commit is contained in:
commit
a159a2da82
|
@ -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.
|
||||
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
Loading…
Reference in New Issue