567 lines
19 KiB
Python
567 lines
19 KiB
Python
from copy import deepcopy
|
|
|
|
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
|
from eth2spec.utils.bls import bls_sign
|
|
|
|
from eth2spec.test.helpers.state import get_balance, state_transition_and_sign_block, next_slot
|
|
from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_empty_block, sign_block, \
|
|
transition_unsigned_block
|
|
from eth2spec.test.helpers.keys import privkeys, pubkeys
|
|
from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing
|
|
from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing
|
|
from eth2spec.test.helpers.attestations import get_valid_attestation
|
|
from eth2spec.test.helpers.deposits import prepare_state_and_deposit
|
|
|
|
from eth2spec.test.context import spec_state_test, with_all_phases, expect_assertion_error, always_bls
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
def test_prev_slot_block_transition(spec, state):
|
|
# Go to clean slot
|
|
spec.process_slots(state, state.slot + 1)
|
|
# Make a block for it
|
|
block = build_empty_block(spec, state, slot=state.slot)
|
|
proposer_index = spec.get_beacon_proposer_index(state)
|
|
# Transition to next slot, above block will not be invalid on top of new state.
|
|
spec.process_slots(state, state.slot + 1)
|
|
|
|
yield 'pre', state
|
|
# State is beyond block slot, but the block can still be realistic when invalid.
|
|
# Try the transition, and update the state root to where it is halted. Then sign with the supposed proposer.
|
|
expect_assertion_error(lambda: transition_unsigned_block(spec, state, block))
|
|
block.state_root = state.hash_tree_root()
|
|
signed_block = sign_block(spec, state, block, proposer_index=proposer_index)
|
|
yield 'blocks', [signed_block]
|
|
yield 'post', None
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
def test_same_slot_block_transition(spec, state):
|
|
# Same slot on top of pre-state, but move out of slot 0 first.
|
|
spec.process_slots(state, state.slot + 1)
|
|
|
|
block = build_empty_block(spec, state, slot=state.slot)
|
|
|
|
yield 'pre', state
|
|
|
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
|
|
|
yield 'blocks', [signed_block]
|
|
yield 'post', state
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
def test_empty_block_transition(spec, state):
|
|
pre_slot = state.slot
|
|
pre_eth1_votes = len(state.eth1_data_votes)
|
|
|
|
yield 'pre', state
|
|
|
|
block = build_empty_block_for_next_slot(spec, state)
|
|
|
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
|
|
|
yield 'blocks', [signed_block]
|
|
yield 'post', state
|
|
|
|
assert len(state.eth1_data_votes) == pre_eth1_votes + 1
|
|
assert spec.get_block_root_at_slot(state, pre_slot) == signed_block.message.parent_root
|
|
assert spec.get_randao_mix(state, spec.get_current_epoch(state)) != spec.Bytes32()
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
def test_invalid_state_root(spec, state):
|
|
yield 'pre', state
|
|
|
|
block = build_empty_block_for_next_slot(spec, state)
|
|
block.state_root = b"\xaa" * 32
|
|
signed_block = sign_block(spec, state, block)
|
|
|
|
expect_assertion_error(lambda: spec.state_transition(state, signed_block))
|
|
|
|
yield 'blocks', [signed_block]
|
|
yield 'post', None
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
@always_bls
|
|
def test_zero_block_sig(spec, state):
|
|
yield 'pre', state
|
|
|
|
block = build_empty_block_for_next_slot(spec, state)
|
|
invalid_signed_block = spec.SignedBeaconBlock(message=block)
|
|
expect_assertion_error(lambda: spec.state_transition(state, invalid_signed_block))
|
|
|
|
yield 'blocks', [invalid_signed_block]
|
|
yield 'post', None
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
@always_bls
|
|
def test_invalid_block_sig(spec, state):
|
|
yield 'pre', state
|
|
|
|
block = build_empty_block_for_next_slot(spec, state)
|
|
invalid_signed_block = spec.SignedBeaconBlock(
|
|
message=block,
|
|
signature=bls_sign(
|
|
message_hash=hash_tree_root(block),
|
|
privkey=123456,
|
|
domain=spec.get_domain(
|
|
state,
|
|
spec.DOMAIN_BEACON_PROPOSER,
|
|
spec.compute_epoch_at_slot(block.slot)))
|
|
)
|
|
expect_assertion_error(lambda: spec.state_transition(state, invalid_signed_block))
|
|
|
|
yield 'blocks', [invalid_signed_block]
|
|
yield 'post', None
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
def test_skipped_slots(spec, state):
|
|
pre_slot = state.slot
|
|
yield 'pre', state
|
|
|
|
block = build_empty_block(spec, state, state.slot + 4)
|
|
|
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
|
|
|
yield 'blocks', [signed_block]
|
|
yield 'post', state
|
|
|
|
assert state.slot == block.slot
|
|
assert spec.get_randao_mix(state, spec.get_current_epoch(state)) != spec.Bytes32()
|
|
for slot in range(pre_slot, state.slot):
|
|
assert spec.get_block_root_at_slot(state, slot) == block.parent_root
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
def test_empty_epoch_transition(spec, state):
|
|
pre_slot = state.slot
|
|
yield 'pre', state
|
|
|
|
block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
|
|
|
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
|
|
|
yield 'blocks', [signed_block]
|
|
yield 'post', state
|
|
|
|
assert state.slot == block.slot
|
|
for slot in range(pre_slot, state.slot):
|
|
assert spec.get_block_root_at_slot(state, slot) == block.parent_root
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
def test_empty_epoch_transition_not_finalizing(spec, state):
|
|
# Don't run for non-minimal configs, it takes very long, and the effect
|
|
# of calling finalization/justification is just the same as with the minimal configuration.
|
|
if spec.SLOTS_PER_EPOCH > 8:
|
|
return
|
|
|
|
# copy for later balance lookups.
|
|
pre_balances = list(state.balances)
|
|
yield 'pre', state
|
|
|
|
spec.process_slots(state, state.slot + (spec.SLOTS_PER_EPOCH * 5))
|
|
block = build_empty_block_for_next_slot(spec, state)
|
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
|
|
|
yield 'blocks', [signed_block]
|
|
yield 'post', state
|
|
|
|
assert state.slot == block.slot
|
|
assert state.finalized_checkpoint.epoch < spec.get_current_epoch(state) - 4
|
|
for index in range(len(state.validators)):
|
|
assert state.balances[index] < pre_balances[index]
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
def test_proposer_slashing(spec, state):
|
|
# copy for later balance lookups.
|
|
pre_state = deepcopy(state)
|
|
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)
|
|
validator_index = proposer_slashing.proposer_index
|
|
|
|
assert not state.validators[validator_index].slashed
|
|
|
|
yield 'pre', state
|
|
|
|
#
|
|
# Add to state via block transition
|
|
#
|
|
block = build_empty_block_for_next_slot(spec, state)
|
|
block.body.proposer_slashings.append(proposer_slashing)
|
|
|
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
|
|
|
yield 'blocks', [signed_block]
|
|
yield 'post', state
|
|
|
|
# check if slashed
|
|
slashed_validator = state.validators[validator_index]
|
|
assert slashed_validator.slashed
|
|
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
|
|
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
|
# lost whistleblower reward
|
|
assert get_balance(state, validator_index) < get_balance(pre_state, validator_index)
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
def test_attester_slashing(spec, state):
|
|
# copy for later balance lookups.
|
|
pre_state = deepcopy(state)
|
|
|
|
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
|
|
validator_index = attester_slashing.attestation_1.attesting_indices[0]
|
|
|
|
assert not state.validators[validator_index].slashed
|
|
|
|
yield 'pre', state
|
|
|
|
#
|
|
# Add to state via block transition
|
|
#
|
|
block = build_empty_block_for_next_slot(spec, state)
|
|
block.body.attester_slashings.append(attester_slashing)
|
|
|
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
|
|
|
yield 'blocks', [signed_block]
|
|
yield 'post', state
|
|
|
|
slashed_validator = state.validators[validator_index]
|
|
assert slashed_validator.slashed
|
|
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
|
|
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
|
# lost whistleblower reward
|
|
assert get_balance(state, validator_index) < get_balance(pre_state, validator_index)
|
|
|
|
proposer_index = spec.get_beacon_proposer_index(state)
|
|
# gained whistleblower reward
|
|
assert (
|
|
get_balance(state, proposer_index) >
|
|
get_balance(pre_state, proposer_index)
|
|
)
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
def test_proposer_after_inactive_index(spec, state):
|
|
# disable some low validator index to check after for
|
|
inactive_index = 10
|
|
state.validators[inactive_index].exit_epoch = spec.get_current_epoch(state)
|
|
|
|
# skip forward, get brand new proposers
|
|
state.slot = spec.SLOTS_PER_EPOCH * 2
|
|
block = build_empty_block_for_next_slot(spec, state)
|
|
state_transition_and_sign_block(spec, state, block)
|
|
|
|
while True:
|
|
next_slot(spec, state)
|
|
proposer_index = spec.get_beacon_proposer_index(state)
|
|
if proposer_index > inactive_index:
|
|
# found a proposer that has a higher index than a disabled validator
|
|
yield 'pre', state
|
|
# test if the proposer can be recognized correctly after the inactive validator
|
|
signed_block = state_transition_and_sign_block(spec, state, build_empty_block(spec, state))
|
|
yield 'blocks', [signed_block]
|
|
yield 'post', state
|
|
break
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
def test_high_proposer_index(spec, state):
|
|
# disable a good amount of validators to make the active count lower, for a faster test
|
|
current_epoch = spec.get_current_epoch(state)
|
|
for i in range(len(state.validators) // 3):
|
|
state.validators[i].exit_epoch = current_epoch
|
|
|
|
# skip forward, get brand new proposers
|
|
state.slot = spec.SLOTS_PER_EPOCH * 2
|
|
block = build_empty_block_for_next_slot(spec, state)
|
|
state_transition_and_sign_block(spec, state, block)
|
|
|
|
active_count = len(spec.get_active_validator_indices(state, current_epoch))
|
|
while True:
|
|
next_slot(spec, state)
|
|
proposer_index = spec.get_beacon_proposer_index(state)
|
|
if proposer_index >= active_count:
|
|
# found a proposer that has a higher index than the active validator count
|
|
yield 'pre', state
|
|
# test if the proposer can be recognized correctly, even while it has a high index.
|
|
signed_block = state_transition_and_sign_block(spec, state, build_empty_block(spec, state))
|
|
yield 'blocks', [signed_block]
|
|
yield 'post', state
|
|
break
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
def test_expected_deposit_in_block(spec, state):
|
|
# Make the state expect a deposit, then don't provide it.
|
|
state.eth1_data.deposit_count += 1
|
|
yield 'pre', state
|
|
|
|
block = build_empty_block_for_next_slot(spec, state)
|
|
signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True)
|
|
|
|
yield 'blocks', [signed_block]
|
|
yield 'post', None
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
def test_deposit_in_block(spec, state):
|
|
initial_registry_len = len(state.validators)
|
|
initial_balances_len = len(state.balances)
|
|
|
|
validator_index = len(state.validators)
|
|
amount = spec.MAX_EFFECTIVE_BALANCE
|
|
deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True)
|
|
|
|
yield 'pre', state
|
|
|
|
block = build_empty_block_for_next_slot(spec, state)
|
|
block.body.deposits.append(deposit)
|
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
|
|
|
yield 'blocks', [signed_block]
|
|
yield 'post', state
|
|
|
|
assert len(state.validators) == initial_registry_len + 1
|
|
assert len(state.balances) == initial_balances_len + 1
|
|
assert get_balance(state, validator_index) == spec.MAX_EFFECTIVE_BALANCE
|
|
assert state.validators[validator_index].pubkey == pubkeys[validator_index]
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
def test_deposit_top_up(spec, state):
|
|
validator_index = 0
|
|
amount = spec.MAX_EFFECTIVE_BALANCE // 4
|
|
deposit = prepare_state_and_deposit(spec, state, validator_index, amount)
|
|
|
|
initial_registry_len = len(state.validators)
|
|
initial_balances_len = len(state.balances)
|
|
validator_pre_balance = get_balance(state, validator_index)
|
|
|
|
yield 'pre', state
|
|
|
|
block = build_empty_block_for_next_slot(spec, state)
|
|
block.body.deposits.append(deposit)
|
|
|
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
|
|
|
yield 'blocks', [signed_block]
|
|
yield 'post', state
|
|
|
|
assert len(state.validators) == initial_registry_len
|
|
assert len(state.balances) == initial_balances_len
|
|
assert get_balance(state, validator_index) == validator_pre_balance + amount
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
def test_attestation(spec, state):
|
|
state.slot = spec.SLOTS_PER_EPOCH
|
|
|
|
yield 'pre', state
|
|
|
|
attestation = get_valid_attestation(spec, state, signed=True)
|
|
|
|
# Add to state via block transition
|
|
pre_current_attestations_len = len(state.current_epoch_attestations)
|
|
attestation_block = build_empty_block(spec, state, state.slot + 1 + spec.MIN_ATTESTATION_INCLUSION_DELAY)
|
|
attestation_block.body.attestations.append(attestation)
|
|
signed_attestation_block = state_transition_and_sign_block(spec, state, attestation_block)
|
|
|
|
assert len(state.current_epoch_attestations) == pre_current_attestations_len + 1
|
|
|
|
# Epoch transition should move to previous_epoch_attestations
|
|
pre_current_attestations_root = spec.hash_tree_root(state.current_epoch_attestations)
|
|
|
|
epoch_block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
|
|
signed_epoch_block = state_transition_and_sign_block(spec, state, epoch_block)
|
|
|
|
yield 'blocks', [signed_attestation_block, signed_epoch_block]
|
|
yield 'post', state
|
|
|
|
assert len(state.current_epoch_attestations) == 0
|
|
assert spec.hash_tree_root(state.previous_epoch_attestations) == pre_current_attestations_root
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
def test_voluntary_exit(spec, state):
|
|
validator_index = spec.get_active_validator_indices(
|
|
state,
|
|
spec.get_current_epoch(state)
|
|
)[-1]
|
|
|
|
# move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
|
|
state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
|
|
|
|
yield 'pre', state
|
|
|
|
voluntary_exit = spec.VoluntaryExit(
|
|
epoch=spec.get_current_epoch(state),
|
|
validator_index=validator_index,
|
|
)
|
|
signed_voluntary_exit = spec.SignedVoluntaryExit(
|
|
message=voluntary_exit,
|
|
signature=bls_sign(
|
|
message_hash=hash_tree_root(voluntary_exit),
|
|
privkey=privkeys[validator_index],
|
|
domain=spec.get_domain(
|
|
state=state,
|
|
domain_type=spec.DOMAIN_VOLUNTARY_EXIT,
|
|
)
|
|
)
|
|
)
|
|
|
|
# Add to state via block transition
|
|
initiate_exit_block = build_empty_block_for_next_slot(spec, state)
|
|
initiate_exit_block.body.voluntary_exits.append(signed_voluntary_exit)
|
|
signed_initiate_exit_block = state_transition_and_sign_block(spec, state, initiate_exit_block)
|
|
|
|
assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
|
|
|
|
# Process within epoch transition
|
|
exit_block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
|
|
signed_exit_block = state_transition_and_sign_block(spec, state, exit_block)
|
|
|
|
yield 'blocks', [signed_initiate_exit_block, signed_exit_block]
|
|
yield 'post', state
|
|
|
|
assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
def test_balance_driven_status_transitions(spec, state):
|
|
current_epoch = spec.get_current_epoch(state)
|
|
validator_index = spec.get_active_validator_indices(state, current_epoch)[-1]
|
|
|
|
assert state.validators[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH
|
|
|
|
# set validator balance to below ejection threshold
|
|
state.validators[validator_index].effective_balance = spec.EJECTION_BALANCE
|
|
|
|
yield 'pre', state
|
|
|
|
# trigger epoch transition
|
|
block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
|
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
|
|
|
yield 'blocks', [signed_block]
|
|
yield 'post', state
|
|
|
|
assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
def test_historical_batch(spec, state):
|
|
state.slot += spec.SLOTS_PER_HISTORICAL_ROOT - (state.slot % spec.SLOTS_PER_HISTORICAL_ROOT) - 1
|
|
pre_historical_roots_len = len(state.historical_roots)
|
|
|
|
yield 'pre', state
|
|
|
|
block = build_empty_block_for_next_slot(spec, state)
|
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
|
|
|
yield 'blocks', [signed_block]
|
|
yield 'post', state
|
|
|
|
assert state.slot == block.slot
|
|
assert spec.get_current_epoch(state) % (spec.SLOTS_PER_HISTORICAL_ROOT // spec.SLOTS_PER_EPOCH) == 0
|
|
assert len(state.historical_roots) == pre_historical_roots_len + 1
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
def test_eth1_data_votes_consensus(spec, state):
|
|
# Don't run when it will take very, very long to simulate. Minimal configuration suffices.
|
|
if spec.SLOTS_PER_ETH1_VOTING_PERIOD > 16:
|
|
return
|
|
|
|
offset_block = build_empty_block(spec, state, slot=spec.SLOTS_PER_ETH1_VOTING_PERIOD - 1)
|
|
state_transition_and_sign_block(spec, state, offset_block)
|
|
yield 'pre', state
|
|
|
|
a = b'\xaa' * 32
|
|
b = b'\xbb' * 32
|
|
c = b'\xcc' * 32
|
|
|
|
blocks = []
|
|
|
|
for i in range(0, spec.SLOTS_PER_ETH1_VOTING_PERIOD):
|
|
block = build_empty_block_for_next_slot(spec, state)
|
|
# wait for over 50% for A, then start voting B
|
|
block.body.eth1_data.block_hash = b if i * 2 > spec.SLOTS_PER_ETH1_VOTING_PERIOD else a
|
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
|
blocks.append(signed_block)
|
|
|
|
assert len(state.eth1_data_votes) == spec.SLOTS_PER_ETH1_VOTING_PERIOD
|
|
assert state.eth1_data.block_hash == a
|
|
|
|
# transition to next eth1 voting period
|
|
block = build_empty_block_for_next_slot(spec, state)
|
|
block.body.eth1_data.block_hash = c
|
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
|
blocks.append(signed_block)
|
|
|
|
yield 'blocks', blocks
|
|
yield 'post', state
|
|
|
|
assert state.eth1_data.block_hash == a
|
|
assert state.slot % spec.SLOTS_PER_ETH1_VOTING_PERIOD == 0
|
|
assert len(state.eth1_data_votes) == 1
|
|
assert state.eth1_data_votes[0].block_hash == c
|
|
|
|
|
|
@with_all_phases
|
|
@spec_state_test
|
|
def test_eth1_data_votes_no_consensus(spec, state):
|
|
# Don't run when it will take very, very long to simulate. Minimal configuration suffices.
|
|
if spec.SLOTS_PER_ETH1_VOTING_PERIOD > 16:
|
|
return
|
|
|
|
pre_eth1_hash = state.eth1_data.block_hash
|
|
|
|
offset_block = build_empty_block(spec, state, slot=spec.SLOTS_PER_ETH1_VOTING_PERIOD - 1)
|
|
state_transition_and_sign_block(spec, state, offset_block)
|
|
yield 'pre', state
|
|
|
|
a = b'\xaa' * 32
|
|
b = b'\xbb' * 32
|
|
|
|
blocks = []
|
|
|
|
for i in range(0, spec.SLOTS_PER_ETH1_VOTING_PERIOD):
|
|
block = build_empty_block_for_next_slot(spec, state)
|
|
# wait for precisely 50% for A, then start voting B for other 50%
|
|
block.body.eth1_data.block_hash = b if i * 2 >= spec.SLOTS_PER_ETH1_VOTING_PERIOD else a
|
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
|
blocks.append(signed_block)
|
|
|
|
assert len(state.eth1_data_votes) == spec.SLOTS_PER_ETH1_VOTING_PERIOD
|
|
assert state.eth1_data.block_hash == pre_eth1_hash
|
|
|
|
yield 'blocks', blocks
|
|
yield 'post', state
|