Merge pull request #1921 from ethereum/hwwhww/shard-tests
Fix no winning root case + add tests + fix #1881
This commit is contained in:
commit
e4d4527845
|
@ -652,11 +652,11 @@ def get_offset_slots(state: BeaconState, shard: Shard) -> Sequence[Slot]:
|
|||
|
||||
```python
|
||||
def is_on_time_attestation(state: BeaconState,
|
||||
attestation: Attestation) -> bool:
|
||||
attestation_data: AttestationData) -> bool:
|
||||
"""
|
||||
Check if the given attestation is on-time.
|
||||
Check if the given ``attestation_data`` is on-time.
|
||||
"""
|
||||
return attestation.data.slot == compute_previous_slot(state.slot)
|
||||
return attestation_data.slot == compute_previous_slot(state.slot)
|
||||
```
|
||||
|
||||
#### `is_winning_attestation`
|
||||
|
@ -667,11 +667,11 @@ def is_winning_attestation(state: BeaconState,
|
|||
committee_index: CommitteeIndex,
|
||||
winning_root: Root) -> bool:
|
||||
"""
|
||||
Check if ``attestation`` helped contribute to the successful crosslink of
|
||||
``winning_root`` formed by ``committee_index`` committee at the current slot.
|
||||
Check if on-time ``attestation`` helped contribute to the successful crosslink of
|
||||
``winning_root`` formed by ``committee_index`` committee.
|
||||
"""
|
||||
return (
|
||||
attestation.data.slot == state.slot
|
||||
is_on_time_attestation(state, attestation.data)
|
||||
and attestation.data.index == committee_index
|
||||
and attestation.data.shard_transition_root == winning_root
|
||||
)
|
||||
|
@ -766,12 +766,14 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None:
|
|||
assert attestation.data.source == state.previous_justified_checkpoint
|
||||
|
||||
# Type 1: on-time attestations
|
||||
if is_on_time_attestation(state, attestation):
|
||||
if is_on_time_attestation(state, attestation.data):
|
||||
# Correct parent block root
|
||||
assert data.beacon_block_root == get_block_root_at_slot(state, compute_previous_slot(state.slot))
|
||||
# Correct shard number
|
||||
shard = compute_shard_from_committee_index(state, attestation.data.index, attestation.data.slot)
|
||||
assert attestation.data.shard == shard
|
||||
# On-time attestations should have a non-empty shard transition root
|
||||
assert attestation.data.shard_transition_root != hash_tree_root(ShardTransition())
|
||||
# Type 2: no shard transition
|
||||
else:
|
||||
# Ensure delayed attestation
|
||||
|
@ -886,9 +888,6 @@ def process_crosslink_for_shard(state: BeaconState,
|
|||
for attestation in transition_attestations:
|
||||
participants = get_attesting_indices(state, attestation.data, attestation.aggregation_bits)
|
||||
transition_participants = transition_participants.union(participants)
|
||||
assert attestation.data.shard_head_root == shard_transition.shard_data_roots[
|
||||
len(shard_transition.shard_data_roots) - 1
|
||||
]
|
||||
|
||||
enough_online_stake = (
|
||||
get_total_balance(state, online_indices.intersection(transition_participants)) * 3 >=
|
||||
|
@ -901,6 +900,12 @@ def process_crosslink_for_shard(state: BeaconState,
|
|||
# Attestation <-> shard transition consistency
|
||||
assert shard_transition_root == hash_tree_root(shard_transition)
|
||||
|
||||
# Check `shard_head_root` of the winning root
|
||||
last_offset_index = len(shard_transition.shard_states) - 1
|
||||
shard_head_root = shard_transition.shard_states[last_offset_index].latest_block_root
|
||||
for attestation in transition_attestations:
|
||||
assert attestation.data.shard_head_root == shard_head_root
|
||||
|
||||
# Apply transition
|
||||
apply_shard_transition(state, shard, shard_transition)
|
||||
# Apply proposer reward and cost
|
||||
|
@ -939,7 +944,7 @@ def process_crosslinks(state: BeaconState,
|
|||
# Since the attestations are validated, all `shard_attestations` satisfy `attestation.data.shard == shard`
|
||||
shard_attestations = [
|
||||
attestation for attestation in attestations
|
||||
if is_on_time_attestation(state, attestation) and attestation.data.index == committee_index
|
||||
if is_on_time_attestation(state, attestation.data) and attestation.data.index == committee_index
|
||||
]
|
||||
winning_root = process_crosslink_for_shard(
|
||||
state, committee_index, shard_transitions[shard], shard_attestations
|
||||
|
|
|
@ -157,7 +157,7 @@ def get_shard_winning_roots(state: BeaconState,
|
|||
# All attestations in the block for this committee/shard and are "on time"
|
||||
shard_attestations = [
|
||||
attestation for attestation in attestations
|
||||
if is_on_time_attestation(state, attestation) and attestation.data.index == committee_index
|
||||
if is_on_time_attestation(state, attestation.data) and attestation.data.index == committee_index
|
||||
]
|
||||
committee = get_beacon_committee(state, on_time_attestation_slot, committee_index)
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ def apply_shard_and_beacon(spec, state, store, shard_store, shard_blocks_buffer)
|
|||
shard_transitions = get_shard_transitions(
|
||||
spec,
|
||||
state,
|
||||
shard_blocks={shard: shard_blocks_buffer},
|
||||
shard_block_dict={shard: shard_blocks_buffer},
|
||||
)
|
||||
shard_transition = shard_transitions[shard]
|
||||
attestation = get_valid_on_time_attestation(
|
||||
|
|
|
@ -83,14 +83,14 @@ def build_attestation_data(spec, state, slot, index, shard=None, shard_transitio
|
|||
attestation_data.shard = shard
|
||||
|
||||
if shard_transition is not None:
|
||||
lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1
|
||||
attestation_data.shard_head_root = shard_transition.shard_data_roots[lastest_shard_data_root_index]
|
||||
last_offset_index = len(shard_transition.shard_data_roots) - 1
|
||||
attestation_data.shard_head_root = shard_transition.shard_states[last_offset_index].latest_block_root
|
||||
attestation_data.shard_transition_root = shard_transition.hash_tree_root()
|
||||
else:
|
||||
if on_time:
|
||||
shard_transition = spec.get_shard_transition(state, shard, shard_blocks=[])
|
||||
lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1
|
||||
attestation_data.shard_head_root = shard_transition.shard_data_roots[lastest_shard_data_root_index]
|
||||
last_offset_index = len(shard_transition.shard_data_roots) - 1
|
||||
attestation_data.shard_head_root = shard_transition.shard_states[last_offset_index].latest_block_root
|
||||
attestation_data.shard_transition_root = shard_transition.hash_tree_root()
|
||||
else:
|
||||
attestation_data.shard_head_root = state.shard_states[shard].latest_block_root
|
||||
|
|
|
@ -172,7 +172,7 @@ def get_sample_shard_transition(spec, start_slot, block_lengths):
|
|||
start_slot=start_slot,
|
||||
shard_block_lengths=block_lengths,
|
||||
shard_data_roots=b,
|
||||
shard_states=[spec.Root() for x in block_lengths],
|
||||
shard_states=[spec.ShardState() for x in block_lengths],
|
||||
proposer_signature_aggregate=spec.BLSSignature(),
|
||||
)
|
||||
return shard_transition
|
||||
|
|
|
@ -30,7 +30,7 @@ def build_shard_block(spec,
|
|||
slot = shard_parent_state.slot + 1
|
||||
|
||||
if body is None:
|
||||
body = b'\x56' * 128
|
||||
body = get_sample_shard_block_body(spec)
|
||||
|
||||
beacon_state, beacon_parent_root = get_state_and_beacon_parent_root_at_slot(spec, beacon_state, slot)
|
||||
proposer_index = spec.get_shard_proposer_index(beacon_state, slot, shard)
|
||||
|
@ -52,10 +52,10 @@ def build_shard_block(spec,
|
|||
return signed_block
|
||||
|
||||
|
||||
def get_shard_transitions(spec, parent_beacon_state, shard_blocks):
|
||||
def get_shard_transitions(spec, parent_beacon_state, shard_block_dict):
|
||||
shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS
|
||||
on_time_slot = parent_beacon_state.slot + 1
|
||||
for shard, blocks in shard_blocks.items():
|
||||
for shard, blocks in shard_block_dict.items():
|
||||
shard_transition = spec.get_shard_transition(parent_beacon_state, shard, blocks)
|
||||
offset_slots = spec.compute_offset_slots(
|
||||
spec.get_latest_slot_for_shard(parent_beacon_state, shard),
|
||||
|
@ -81,3 +81,8 @@ def get_committee_index_of_shard(spec, state, slot, shard): # Optional[Committe
|
|||
if (start_shard + committee_index) % active_shard_count == shard:
|
||||
return committee_index
|
||||
return None
|
||||
|
||||
|
||||
def get_sample_shard_block_body(spec, is_max=False):
|
||||
size = spec.MAX_SHARD_BLOCK_SIZE if is_max else 128
|
||||
return b'\x56' * size
|
||||
|
|
|
@ -3,69 +3,208 @@ from eth2spec.test.context import (
|
|||
with_all_phases_except,
|
||||
spec_state_test,
|
||||
)
|
||||
from eth2spec.test.helpers.attestations import get_valid_on_time_attestation
|
||||
from eth2spec.test.helpers.attestations import (
|
||||
get_valid_attestation,
|
||||
get_valid_on_time_attestation,
|
||||
run_attestation_processing,
|
||||
)
|
||||
from eth2spec.test.helpers.shard_transitions import run_shard_transitions_processing
|
||||
from eth2spec.test.helpers.shard_block import (
|
||||
build_shard_block,
|
||||
get_shard_transitions,
|
||||
get_sample_shard_block_body,
|
||||
get_committee_index_of_shard,
|
||||
)
|
||||
from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot, next_slot
|
||||
|
||||
|
||||
def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True):
|
||||
def get_initial_env(spec, state, target_len_offset_slot):
|
||||
state = transition_to_valid_shard_slot(spec, state)
|
||||
committee_index = spec.CommitteeIndex(0)
|
||||
target_shard_slot = state.slot + target_len_offset_slot - 1
|
||||
shard = spec.compute_shard_from_committee_index(state, committee_index, target_shard_slot)
|
||||
assert state.shard_states[shard].slot == state.slot - 1
|
||||
return state, shard, target_shard_slot
|
||||
|
||||
|
||||
def get_attestations_and_shard_transitions(spec, state, shard_block_dict):
|
||||
shard_transitions = get_shard_transitions(spec, state, shard_block_dict)
|
||||
attestations = [
|
||||
get_valid_on_time_attestation(
|
||||
spec, state,
|
||||
index=get_committee_index_of_shard(spec, state, state.slot, shard),
|
||||
shard_transition=shard_transition,
|
||||
signed=True,
|
||||
)
|
||||
for shard, shard_transition in enumerate(shard_transitions)
|
||||
if shard_transition != spec.ShardTransition()
|
||||
]
|
||||
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
|
||||
shard_slot = state.slot + target_len_offset_slot - 1
|
||||
shard = spec.compute_shard_from_committee_index(state, committee_index, shard_slot)
|
||||
assert state.shard_states[shard].slot == init_slot - 1
|
||||
|
||||
# Create SignedShardBlock
|
||||
body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE
|
||||
shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True)
|
||||
shard_blocks = [shard_block]
|
||||
# Create SignedShardBlock at init_slot
|
||||
shard_block = build_shard_block(
|
||||
spec, state, shard,
|
||||
slot=init_slot, body=get_sample_shard_block_body(spec, is_max=True), signed=True
|
||||
)
|
||||
|
||||
# Transition state to target shard slot
|
||||
transition_to(spec, state, target_shard_slot)
|
||||
|
||||
# Create a shard_transitions that would be included at beacon block `target_shard_slot + 1`
|
||||
shard_block_dict = {shard: [shard_block]}
|
||||
attestations, shard_transitions = get_attestations_and_shard_transitions(spec, state, shard_block_dict)
|
||||
|
||||
# Transition state latest shard slot
|
||||
transition_to(spec, state, shard_slot)
|
||||
# Create a shard_transitions that would be included at beacon block `state.slot + target_len_offset_slot`
|
||||
shard_transitions = get_shard_transitions(
|
||||
spec,
|
||||
state,
|
||||
shard_blocks={shard: shard_blocks},
|
||||
)
|
||||
shard_transition = shard_transitions[shard]
|
||||
attestation = get_valid_on_time_attestation(
|
||||
spec,
|
||||
state,
|
||||
index=committee_index,
|
||||
shard_transition=shard_transition,
|
||||
signed=False,
|
||||
)
|
||||
next_slot(spec, state)
|
||||
|
||||
for attestation in attestations:
|
||||
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||
|
||||
_, winning_roots = spec.get_shard_winning_roots(state, attestations)
|
||||
assert len(winning_roots) == 1
|
||||
shard_transition = shard_transitions[shard]
|
||||
assert winning_roots[0] == shard_transition.hash_tree_root()
|
||||
|
||||
pre_gasprice = state.shard_states[shard].gasprice
|
||||
pre_shard_states = state.shard_states.copy()
|
||||
yield from run_shard_transitions_processing(spec, state, shard_transitions, attestations)
|
||||
|
||||
transition_to(spec, state, init_slot + target_len_offset_slot)
|
||||
pre_shard_state = state.shard_states[shard]
|
||||
yield from run_shard_transitions_processing(spec, state, shard_transitions, [attestation], valid=valid)
|
||||
for index, shard_state in enumerate(state.shard_states):
|
||||
if index == shard:
|
||||
assert shard_state != pre_shard_states[index]
|
||||
assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1]
|
||||
assert shard_state.latest_block_root == shard_block.message.hash_tree_root()
|
||||
if target_len_offset_slot == 1:
|
||||
assert shard_state.gasprice > pre_gasprice
|
||||
else:
|
||||
assert shard_state == pre_shard_states[index]
|
||||
|
||||
if valid:
|
||||
shard_state = state.shard_states[shard]
|
||||
assert shard_state != pre_shard_state
|
||||
assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1]
|
||||
assert shard_state.latest_block_root == shard_block.message.hash_tree_root()
|
||||
if target_len_offset_slot == 1:
|
||||
assert shard_state.gasprice > pre_gasprice
|
||||
for pending_attestation in state.current_epoch_attestations:
|
||||
assert bool(pending_attestation.crosslink_success) is True
|
||||
|
||||
|
||||
@with_all_phases_except([PHASE0])
|
||||
@spec_state_test
|
||||
def test_basic_crosslinks(spec, state):
|
||||
# NOTE: this test is only for full crosslink (minimal config), not for mainnet
|
||||
yield from run_basic_crosslink_tests(spec, state, target_len_offset_slot=1, valid=True)
|
||||
if not is_full_crosslink(spec, state):
|
||||
# Skip this test
|
||||
return
|
||||
|
||||
yield from run_successful_crosslink_tests(spec, state, target_len_offset_slot=1)
|
||||
|
||||
|
||||
@with_all_phases_except([PHASE0])
|
||||
@spec_state_test
|
||||
def test_multiple_offset_slots(spec, state):
|
||||
# NOTE: this test is only for full crosslink (minimal config), not for mainnet
|
||||
yield from run_basic_crosslink_tests(spec, state, target_len_offset_slot=2, valid=True)
|
||||
if not is_full_crosslink(spec, state):
|
||||
# Skip this test
|
||||
return
|
||||
|
||||
yield from run_successful_crosslink_tests(spec, state, target_len_offset_slot=2)
|
||||
|
||||
|
||||
@with_all_phases_except([PHASE0])
|
||||
@spec_state_test
|
||||
def test_no_winning_root(spec, state):
|
||||
if not is_full_crosslink(spec, state):
|
||||
# Skip this test
|
||||
return
|
||||
|
||||
state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot=1)
|
||||
init_slot = state.slot
|
||||
|
||||
# Create SignedShardBlock at init_slot
|
||||
shard_block = build_shard_block(
|
||||
spec, state, shard,
|
||||
slot=init_slot, body=get_sample_shard_block_body(spec, is_max=True), signed=True
|
||||
)
|
||||
|
||||
# Transition state to target shard slot
|
||||
transition_to(spec, state, target_shard_slot)
|
||||
|
||||
# Create a shard_transitions that would be included at beacon block `target_shard_slot + 1`
|
||||
shard_transitions = get_shard_transitions(spec, state, {shard: [shard_block]})
|
||||
shard_transition = shard_transitions[shard]
|
||||
committee_index = get_committee_index_of_shard(spec, state, state.slot, shard)
|
||||
attestation = get_valid_attestation(
|
||||
spec, state,
|
||||
index=committee_index,
|
||||
shard_transition=shard_transition,
|
||||
# Decrease attested participants to 1/3 committee
|
||||
filter_participant_set=lambda committee: set(list(committee)[:len(committee) // 3]),
|
||||
signed=True,
|
||||
on_time=True,
|
||||
)
|
||||
|
||||
next_slot(spec, state)
|
||||
|
||||
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||
|
||||
_, winning_roots = spec.get_shard_winning_roots(state, [attestation])
|
||||
assert len(winning_roots) == 0
|
||||
|
||||
# No winning root, shard_transitions[shard] is empty
|
||||
shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS
|
||||
pre_shard_states = state.shard_states.copy()
|
||||
yield from run_shard_transitions_processing(spec, state, shard_transitions, [attestation])
|
||||
|
||||
for pending_attestation in state.current_epoch_attestations:
|
||||
assert bool(pending_attestation.crosslink_success) is False
|
||||
|
||||
assert state.shard_states == pre_shard_states
|
||||
|
||||
|
||||
@with_all_phases_except([PHASE0])
|
||||
@spec_state_test
|
||||
def test_wrong_shard_transition_root(spec, state):
|
||||
if not is_full_crosslink(spec, state):
|
||||
# Skip this test
|
||||
return
|
||||
|
||||
state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot=1)
|
||||
init_slot = state.slot
|
||||
|
||||
# Create SignedShardBlock at init_slot
|
||||
shard_block = build_shard_block(
|
||||
spec, state, shard,
|
||||
slot=init_slot, body=get_sample_shard_block_body(spec, is_max=True), signed=True
|
||||
)
|
||||
|
||||
# Transition state to target shard slot
|
||||
transition_to(spec, state, target_shard_slot)
|
||||
|
||||
# Create a shard_transitions that would be included at beacon block `target_shard_slot + 1`
|
||||
shard_transitions = get_shard_transitions(spec, state, {shard: [shard_block]})
|
||||
shard_transition = shard_transitions[shard]
|
||||
wrong_shard_transition = shard_transition.copy()
|
||||
wrong_shard_transition.shard_states[shard].gasprice = shard_transition.shard_states[shard].gasprice + 1
|
||||
committee_index = get_committee_index_of_shard(spec, state, state.slot, shard)
|
||||
attestation = get_valid_attestation(
|
||||
spec, state,
|
||||
index=committee_index,
|
||||
shard_transition=wrong_shard_transition,
|
||||
signed=True,
|
||||
on_time=True,
|
||||
)
|
||||
attestations = [attestation]
|
||||
|
||||
next_slot(spec, state)
|
||||
|
||||
run_attestation_processing(spec, state, attestation)
|
||||
|
||||
# Check if winning root != shard_transition.hash_tree_root()
|
||||
_, winning_roots = spec.get_shard_winning_roots(state, attestations)
|
||||
assert len(winning_roots) == 1
|
||||
shard_transition = shard_transitions[shard]
|
||||
assert winning_roots[0] != shard_transition.hash_tree_root()
|
||||
|
||||
yield from run_shard_transitions_processing(spec, state, shard_transitions, attestations, valid=False)
|
||||
|
|
|
@ -19,9 +19,9 @@ def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, comm
|
|||
|
||||
body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE
|
||||
shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True)
|
||||
shard_blocks: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]}
|
||||
shard_block_dict: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]}
|
||||
|
||||
shard_transitions = get_shard_transitions(spec, state, shard_blocks)
|
||||
shard_transitions = get_shard_transitions(spec, state, shard_block_dict)
|
||||
attestations = [
|
||||
get_valid_on_time_attestation(
|
||||
spec,
|
||||
|
@ -30,7 +30,7 @@ def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, comm
|
|||
shard_transition=shard_transitions[shard],
|
||||
signed=True,
|
||||
)
|
||||
for shard in shard_blocks.keys()
|
||||
for shard in shard_block_dict.keys()
|
||||
]
|
||||
|
||||
beacon_block = build_empty_block(spec, state, slot=state.slot + 1)
|
||||
|
@ -50,16 +50,16 @@ def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, comm
|
|||
|
||||
for shard in range(spec.get_active_shard_count(state)):
|
||||
post_shard_state = state.shard_states[shard]
|
||||
if shard in shard_blocks:
|
||||
if shard in shard_block_dict:
|
||||
# Shard state has been changed to state_transition result
|
||||
assert post_shard_state == shard_transitions[shard].shard_states[
|
||||
len(shard_transitions[shard].shard_states) - 1
|
||||
]
|
||||
assert post_shard_state.slot == state.slot - 1
|
||||
if len(shard_blocks[shard]) == 0:
|
||||
if len((shard_block_dict[shard])) == 0:
|
||||
# `latest_block_root` is the same
|
||||
assert post_shard_state.latest_block_root == pre_shard_states[shard].latest_block_root
|
||||
if target_len_offset_slot == 1 and len(shard_blocks) > 0:
|
||||
if target_len_offset_slot == 1 and len(shard_block_dict[shard]) > 0:
|
||||
assert post_shard_state.gasprice > pre_gasprice
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue