Merge pull request #2110 from ethereum/multi-op-tests

Multi operation tests
This commit is contained in:
Danny Ryan 2020-11-02 10:47:48 -07:00 committed by GitHub
commit bdd1077423
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 198 additions and 33 deletions

View File

@ -1,8 +1,11 @@
from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation, sign_indexed_attestation from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation, sign_indexed_attestation
def get_valid_attester_slashing(spec, state, signed_1=False, signed_2=False): def get_valid_attester_slashing(spec, state, slot=None, signed_1=False, signed_2=False, filter_participant_set=None):
attestation_1 = get_valid_attestation(spec, state, signed=signed_1) attestation_1 = get_valid_attestation(
spec, state,
slot=slot, signed=signed_1, filter_participant_set=filter_participant_set
)
attestation_2 = attestation_1.copy() attestation_2 = attestation_1.copy()
attestation_2.data.target.root = b'\x01' * 32 attestation_2.data.target.root = b'\x01' * 32
@ -16,14 +19,17 @@ def get_valid_attester_slashing(spec, state, signed_1=False, signed_2=False):
) )
def get_valid_attester_slashing_by_indices(spec, state, indices_1, indices_2=None, signed_1=False, signed_2=False): def get_valid_attester_slashing_by_indices(spec, state,
indices_1, indices_2=None,
slot=None,
signed_1=False, signed_2=False):
if indices_2 is None: if indices_2 is None:
indices_2 = indices_1 indices_2 = indices_1
assert indices_1 == sorted(indices_1) assert indices_1 == sorted(indices_1)
assert indices_2 == sorted(indices_2) assert indices_2 == sorted(indices_2)
attester_slashing = get_valid_attester_slashing(spec, state) attester_slashing = get_valid_attester_slashing(spec, state, slot=slot)
attester_slashing.attestation_1.attesting_indices = indices_1 attester_slashing.attestation_1.attesting_indices = indices_1
attester_slashing.attestation_2.attesting_indices = indices_2 attester_slashing.attestation_2.attesting_indices = indices_2

View File

@ -56,7 +56,10 @@ def deposit_from_context(spec, deposit_data_list, index):
deposit_data = deposit_data_list[index] deposit_data = deposit_data_list[index]
root = hash_tree_root(List[spec.DepositData, 2**spec.DEPOSIT_CONTRACT_TREE_DEPTH](*deposit_data_list)) root = hash_tree_root(List[spec.DepositData, 2**spec.DEPOSIT_CONTRACT_TREE_DEPTH](*deposit_data_list))
tree = calc_merkle_tree_from_leaves(tuple([d.hash_tree_root() for d in deposit_data_list])) tree = calc_merkle_tree_from_leaves(tuple([d.hash_tree_root() for d in deposit_data_list]))
proof = list(get_merkle_proof(tree, item_index=index, tree_len=32)) + [(index + 1).to_bytes(32, 'little')] proof = (
list(get_merkle_proof(tree, item_index=index, tree_len=32))
+ [len(deposit_data_list).to_bytes(32, 'little')]
)
leaf = deposit_data.hash_tree_root() leaf = deposit_data.hash_tree_root()
assert spec.is_valid_merkle_branch(leaf, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH + 1, index, root) assert spec.is_valid_merkle_branch(leaf, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH + 1, index, root)
deposit = spec.Deposit(proof=proof, data=deposit_data) deposit = spec.Deposit(proof=proof, data=deposit_data)

View File

@ -0,0 +1,155 @@
from random import Random
from eth2spec.test.helpers.keys import privkeys, pubkeys
from eth2spec.test.helpers.state import (
state_transition_and_sign_block,
)
from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot,
)
from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing
from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing_by_indices
from eth2spec.test.helpers.attestations import get_valid_attestation
from eth2spec.test.helpers.deposits import build_deposit, deposit_from_context
from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits
def run_slash_and_exit(spec, state, slash_index, exit_index, valid=True):
"""
Helper function to run a test that slashes and exits two validators
"""
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
state.slot += spec.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
proposer_slashing = get_valid_proposer_slashing(
spec, state, slashed_index=slash_index, signed_1=True, signed_2=True)
signed_exit = prepare_signed_exits(spec, state, [exit_index])[0]
block.body.proposer_slashings.append(proposer_slashing)
block.body.voluntary_exits.append(signed_exit)
signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=(not valid))
yield 'blocks', [signed_block]
if not valid:
yield 'post', None
return
yield 'post', state
def get_random_proposer_slashings(spec, state, rng):
num_slashings = rng.randrange(spec.MAX_PROPOSER_SLASHINGS)
indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy()
slashings = [
get_valid_proposer_slashing(
spec, state,
slashed_index=indices.pop(rng.randrange(len(indices))), signed_1=True, signed_2=True,
)
for _ in range(num_slashings)
]
return slashings
def get_random_attester_slashings(spec, state, rng):
num_slashings = rng.randrange(spec.MAX_ATTESTER_SLASHINGS)
indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy()
slot_range = list(range(state.slot - spec.SLOTS_PER_HISTORICAL_ROOT + 1, state.slot))
slashings = [
get_valid_attester_slashing_by_indices(
spec, state,
sorted([indices.pop(rng.randrange(len(indices))) for _ in range(rng.randrange(1, 4))]),
slot=slot_range.pop(rng.randrange(len(slot_range))),
signed_1=True, signed_2=True,
)
for _ in range(num_slashings)
]
return slashings
def get_random_attestations(spec, state, rng):
num_attestations = rng.randrange(spec.MAX_ATTESTATIONS)
attestations = [
get_valid_attestation(
spec, state,
slot=rng.randrange(state.slot - spec.SLOTS_PER_EPOCH + 1, state.slot),
signed=True,
)
for _ in range(num_attestations)
]
return attestations
def prepare_state_and_get_random_deposits(spec, state, rng):
num_deposits = rng.randrange(spec.MAX_DEPOSITS)
deposit_data_leaves = [spec.DepositData() for _ in range(len(state.validators))]
deposits = []
# First build deposit data leaves
for i in range(num_deposits):
index = len(state.validators) + i
_, root, deposit_data_leaves = build_deposit(
spec,
deposit_data_leaves,
pubkeys[index],
privkeys[index],
spec.MAX_EFFECTIVE_BALANCE,
withdrawal_credentials=b'\x00' * 32,
signed=True,
)
state.eth1_data.deposit_root = root
state.eth1_data.deposit_count += num_deposits
# Then for that context, build deposits/proofs
for i in range(num_deposits):
index = len(state.validators) + i
deposit, _, _ = deposit_from_context(spec, deposit_data_leaves, index)
deposits.append(deposit)
return deposits
def get_random_voluntary_exits(spec, state, to_be_slashed_indices, rng):
num_exits = rng.randrange(spec.MAX_VOLUNTARY_EXITS)
indices = set(spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy())
eligible_indices = indices - to_be_slashed_indices
exit_indices = [eligible_indices.pop() for _ in range(num_exits)]
return prepare_signed_exits(spec, state, exit_indices)
def run_test_full_random_operations(spec, state, rng=Random(2080)):
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
state.slot += spec.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
# prepare state for deposits before building block
deposits = prepare_state_and_get_random_deposits(spec, state, rng)
block = build_empty_block_for_next_slot(spec, state)
block.body.proposer_slashings = get_random_proposer_slashings(spec, state, rng)
block.body.attester_slashings = get_random_attester_slashings(spec, state, rng)
block.body.attestations = get_random_attestations(spec, state, rng)
block.body.deposits = deposits
# cannot include to be slashed indices as exits
slashed_indices = set([
slashing.signed_header_1.message.proposer_index
for slashing in block.body.proposer_slashings
])
for attester_slashing in block.body.attester_slashings:
slashed_indices = slashed_indices.union(attester_slashing.attestation_1.attesting_indices)
slashed_indices = slashed_indices.union(attester_slashing.attestation_2.attesting_indices)
block.body.voluntary_exits = get_random_voluntary_exits(spec, state, slashed_indices, rng)
signed_block = state_transition_and_sign_block(spec, state, block)
yield 'pre', state
yield 'blocks', [signed_block]
yield 'post', state

View File

@ -1,3 +1,4 @@
from random import Random
from eth2spec.utils import bls from eth2spec.utils import bls
from eth2spec.test.helpers.state import ( from eth2spec.test.helpers.state import (
@ -20,6 +21,10 @@ from eth2spec.test.helpers.attestations import get_valid_attestation
from eth2spec.test.helpers.deposits import prepare_state_and_deposit from eth2spec.test.helpers.deposits import prepare_state_and_deposit
from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits
from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee
from eth2spec.test.helpers.multi_operations import (
run_slash_and_exit,
run_test_full_random_operations,
)
from eth2spec.test.context import ( from eth2spec.test.context import (
PHASE0, PHASE1, MINIMAL, PHASE0, PHASE1, MINIMAL,
@ -882,34 +887,6 @@ def test_multiple_different_validator_exits_same_block(spec, state):
assert state.validators[index].exit_epoch < spec.FAR_FUTURE_EPOCH assert state.validators[index].exit_epoch < spec.FAR_FUTURE_EPOCH
def run_slash_and_exit(spec, state, slash_index, exit_index, valid=True):
"""
Helper function to run a test that slashes and exits two validators
"""
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
state.slot += spec.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
proposer_slashing = get_valid_proposer_slashing(
spec, state, slashed_index=slash_index, signed_1=True, signed_2=True)
signed_exit = prepare_signed_exits(spec, state, [exit_index])[0]
block.body.proposer_slashings.append(proposer_slashing)
block.body.voluntary_exits.append(signed_exit)
signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=(not valid))
yield 'blocks', [signed_block]
if valid:
yield 'post', state
else:
yield 'post', None
@with_all_phases @with_all_phases
@spec_state_test @spec_state_test
@disable_process_reveal_deadlines @disable_process_reveal_deadlines
@ -1045,3 +1022,27 @@ def test_eth1_data_votes_no_consensus(spec, state):
yield 'blocks', blocks yield 'blocks', blocks
yield 'post', state yield 'post', state
@with_phases([PHASE0])
@spec_state_test
def test_full_random_operations_0(spec, state):
yield from run_test_full_random_operations(spec, state, rng=Random(2020))
@with_phases([PHASE0])
@spec_state_test
def test_full_random_operations_1(spec, state):
yield from run_test_full_random_operations(spec, state, rng=Random(2021))
@with_phases([PHASE0])
@spec_state_test
def test_full_random_operations_2(spec, state):
yield from run_test_full_random_operations(spec, state, rng=Random(2022))
@with_phases([PHASE0])
@spec_state_test
def test_full_random_operations_3(spec, state):
yield from run_test_full_random_operations(spec, state, rng=Random(2023))