414 lines
12 KiB
Python
414 lines
12 KiB
Python
from copy import deepcopy
|
|
|
|
from py_ecc import bls
|
|
import eth2spec.phase0.spec as spec
|
|
|
|
from eth2spec.utils.minimal_ssz import signing_root
|
|
from eth2spec.phase0.spec import (
|
|
# SSZ
|
|
VoluntaryExit,
|
|
# functions
|
|
get_active_validator_indices,
|
|
get_beacon_proposer_index,
|
|
get_block_root_at_slot,
|
|
get_current_epoch,
|
|
get_domain,
|
|
advance_slot,
|
|
cache_state,
|
|
)
|
|
from eth2spec.phase0.state_transition import (
|
|
state_transition,
|
|
)
|
|
from .helpers import (
|
|
get_balance,
|
|
build_empty_block_for_next_slot,
|
|
get_state_root,
|
|
get_valid_attestation,
|
|
get_valid_attester_slashing,
|
|
get_valid_proposer_slashing,
|
|
get_valid_transfer,
|
|
prepare_state_and_deposit,
|
|
privkeys,
|
|
pubkeys,
|
|
)
|
|
|
|
from .context import spec_state_test
|
|
|
|
|
|
@spec_state_test
|
|
def test_slot_transition(state):
|
|
pre_slot = state.slot
|
|
pre_root = state.hash_tree_root()
|
|
yield 'pre', state
|
|
|
|
cache_state(state)
|
|
advance_slot(state)
|
|
yield 'post', state
|
|
|
|
assert state.slot == pre_slot + 1
|
|
assert get_state_root(state, pre_slot) == pre_root
|
|
|
|
|
|
@spec_state_test
|
|
def test_empty_block_transition(state):
|
|
pre_slot = state.slot
|
|
pre_eth1_votes = len(state.eth1_data_votes)
|
|
|
|
yield 'pre', state
|
|
|
|
block = build_empty_block_for_next_slot(state)
|
|
yield 'blocks', [block], [spec.BeaconBlock]
|
|
|
|
state_transition(state, block)
|
|
yield 'post', state
|
|
|
|
assert len(state.eth1_data_votes) == pre_eth1_votes + 1
|
|
assert get_block_root_at_slot(state, pre_slot) == block.previous_block_root
|
|
|
|
|
|
@spec_state_test
|
|
def test_skipped_slots(state):
|
|
pre_slot = state.slot
|
|
yield 'pre', state
|
|
|
|
block = build_empty_block_for_next_slot(state)
|
|
block.slot += 3
|
|
yield 'blocks', [block], [spec.BeaconBlock]
|
|
|
|
state_transition(state, block)
|
|
yield 'post', state
|
|
|
|
assert state.slot == block.slot
|
|
for slot in range(pre_slot, state.slot):
|
|
assert get_block_root_at_slot(state, slot) == block.previous_block_root
|
|
|
|
|
|
@spec_state_test
|
|
def test_empty_epoch_transition(state):
|
|
pre_slot = state.slot
|
|
yield 'pre', state
|
|
|
|
block = build_empty_block_for_next_slot(state)
|
|
block.slot += spec.SLOTS_PER_EPOCH
|
|
yield 'blocks', [block], [spec.BeaconBlock]
|
|
|
|
state_transition(state, block)
|
|
yield 'post', state
|
|
|
|
assert state.slot == block.slot
|
|
for slot in range(pre_slot, state.slot):
|
|
assert get_block_root_at_slot(state, slot) == block.previous_block_root
|
|
|
|
|
|
@spec_state_test
|
|
def test_empty_epoch_transition_not_finalizing(state):
|
|
# copy for later balance lookups.
|
|
pre_state = deepcopy(state)
|
|
yield 'pre', state
|
|
|
|
block = build_empty_block_for_next_slot(state)
|
|
block.slot += spec.SLOTS_PER_EPOCH * 5
|
|
yield 'blocks', [block], [spec.BeaconBlock]
|
|
|
|
state_transition(state, block)
|
|
yield 'post', state
|
|
|
|
assert state.slot == block.slot
|
|
assert state.finalized_epoch < get_current_epoch(state) - 4
|
|
for index in range(len(state.validator_registry)):
|
|
assert get_balance(state, index) < get_balance(pre_state, index)
|
|
|
|
|
|
@spec_state_test
|
|
def test_proposer_slashing(state):
|
|
# copy for later balance lookups.
|
|
pre_state = deepcopy(state)
|
|
proposer_slashing = get_valid_proposer_slashing(state)
|
|
validator_index = proposer_slashing.proposer_index
|
|
|
|
assert not state.validator_registry[validator_index].slashed
|
|
|
|
yield 'pre', state
|
|
|
|
#
|
|
# Add to state via block transition
|
|
#
|
|
block = build_empty_block_for_next_slot(state)
|
|
block.body.proposer_slashings.append(proposer_slashing)
|
|
yield 'blocks', [block], [spec.BeaconBlock]
|
|
|
|
state_transition(state, block)
|
|
yield 'post', state
|
|
|
|
# check if slashed
|
|
slashed_validator = state.validator_registry[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)
|
|
|
|
|
|
@spec_state_test
|
|
def test_attester_slashing(state):
|
|
# copy for later balance lookups.
|
|
pre_state = deepcopy(state)
|
|
|
|
attester_slashing = get_valid_attester_slashing(state)
|
|
validator_index = attester_slashing.attestation_1.custody_bit_0_indices[0]
|
|
|
|
assert not state.validator_registry[validator_index].slashed
|
|
|
|
yield 'pre', state
|
|
|
|
#
|
|
# Add to state via block transition
|
|
#
|
|
block = build_empty_block_for_next_slot(state)
|
|
block.body.attester_slashings.append(attester_slashing)
|
|
yield 'blocks', [block], [spec.BeaconBlock]
|
|
|
|
state_transition(state, block)
|
|
yield 'post', state
|
|
|
|
slashed_validator = state.validator_registry[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 = get_beacon_proposer_index(state)
|
|
# gained whistleblower reward
|
|
assert (
|
|
get_balance(state, proposer_index) >
|
|
get_balance(pre_state, proposer_index)
|
|
)
|
|
|
|
|
|
# TODO update functions below to be like above, i.e. with @spec_state_test and yielding data to put into the test vector
|
|
|
|
@spec_state_test
|
|
def test_deposit_in_block(state):
|
|
initial_registry_len = len(state.validator_registry)
|
|
initial_balances_len = len(state.balances)
|
|
|
|
validator_index = len(state.validator_registry)
|
|
amount = spec.MAX_EFFECTIVE_BALANCE
|
|
deposit = prepare_state_and_deposit(state, validator_index, amount)
|
|
|
|
yield 'pre', state
|
|
|
|
block = build_empty_block_for_next_slot(state)
|
|
block.body.deposits.append(deposit)
|
|
|
|
yield 'blocks', [block], [spec.BeaconBlock]
|
|
|
|
state_transition(state, block)
|
|
yield 'post', state
|
|
|
|
assert len(state.validator_registry) == initial_registry_len + 1
|
|
assert len(state.balances) == initial_balances_len + 1
|
|
assert get_balance(state, validator_index) == spec.MAX_EFFECTIVE_BALANCE
|
|
assert state.validator_registry[validator_index].pubkey == pubkeys[validator_index]
|
|
|
|
|
|
@spec_state_test
|
|
def test_deposit_top_up(state):
|
|
validator_index = 0
|
|
amount = spec.MAX_EFFECTIVE_BALANCE // 4
|
|
deposit = prepare_state_and_deposit(state, validator_index, amount)
|
|
|
|
initial_registry_len = len(state.validator_registry)
|
|
initial_balances_len = len(state.balances)
|
|
validator_pre_balance = get_balance(state, validator_index)
|
|
|
|
yield 'pre', state
|
|
|
|
block = build_empty_block_for_next_slot(state)
|
|
block.body.deposits.append(deposit)
|
|
|
|
yield 'blocks', [block], [spec.BeaconBlock]
|
|
|
|
state_transition(state, block)
|
|
yield 'post', state
|
|
|
|
assert len(state.validator_registry) == initial_registry_len
|
|
assert len(state.balances) == initial_balances_len
|
|
assert get_balance(state, validator_index) == validator_pre_balance + amount
|
|
|
|
|
|
@spec_state_test
|
|
def test_attestation(state):
|
|
state.slot = spec.SLOTS_PER_EPOCH
|
|
|
|
yield 'pre', state
|
|
|
|
attestation = get_valid_attestation(state)
|
|
|
|
# Add to state via block transition
|
|
pre_current_attestations_len = len(state.current_epoch_attestations)
|
|
attestation_block = build_empty_block_for_next_slot(state)
|
|
attestation_block.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
|
attestation_block.body.attestations.append(attestation)
|
|
state_transition(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_for_next_slot(state)
|
|
epoch_block.slot += spec.SLOTS_PER_EPOCH
|
|
state_transition(state, epoch_block)
|
|
|
|
yield 'blocks', [attestation_block, epoch_block], [spec.BeaconBlock]
|
|
yield 'post', state
|
|
|
|
assert len(state.current_epoch_attestations) == 0
|
|
assert spec.hash_tree_root(state.previous_epoch_attestations) == pre_current_attestations_root
|
|
|
|
|
|
@spec_state_test
|
|
def test_voluntary_exit(state):
|
|
validator_index = get_active_validator_indices(
|
|
state,
|
|
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 = VoluntaryExit(
|
|
epoch=get_current_epoch(state),
|
|
validator_index=validator_index,
|
|
)
|
|
voluntary_exit.signature = bls.sign(
|
|
message_hash=signing_root(voluntary_exit),
|
|
privkey=privkeys[validator_index],
|
|
domain=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(state)
|
|
initiate_exit_block.body.voluntary_exits.append(voluntary_exit)
|
|
state_transition(state, initiate_exit_block)
|
|
|
|
assert state.validator_registry[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
|
|
|
|
# Process within epoch transition
|
|
exit_block = build_empty_block_for_next_slot(state)
|
|
exit_block.slot += spec.SLOTS_PER_EPOCH
|
|
state_transition(state, exit_block)
|
|
|
|
yield 'blocks', [initiate_exit_block, exit_block], [spec.BeaconBlock]
|
|
yield 'post', state
|
|
|
|
assert state.validator_registry[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
|
|
|
|
|
|
@spec_state_test
|
|
def test_transfer(state):
|
|
# overwrite default 0 to test
|
|
spec.MAX_TRANSFERS = 1
|
|
|
|
sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1]
|
|
amount = get_balance(state, sender_index)
|
|
|
|
transfer = get_valid_transfer(state, state.slot + 1, sender_index, amount)
|
|
recipient_index = transfer.recipient
|
|
pre_transfer_recipient_balance = get_balance(state, recipient_index)
|
|
|
|
# un-activate so validator can transfer
|
|
state.validator_registry[sender_index].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
|
|
|
|
yield 'pre', state
|
|
|
|
# Add to state via block transition
|
|
block = build_empty_block_for_next_slot(state)
|
|
block.body.transfers.append(transfer)
|
|
|
|
yield 'blocks', [block], [spec.BeaconBlock]
|
|
|
|
state_transition(state, block)
|
|
yield 'post', state
|
|
|
|
sender_balance = get_balance(state, sender_index)
|
|
recipient_balance = get_balance(state, recipient_index)
|
|
assert sender_balance == 0
|
|
assert recipient_balance == pre_transfer_recipient_balance + amount
|
|
|
|
|
|
@spec_state_test
|
|
def test_balance_driven_status_transitions(state):
|
|
current_epoch = get_current_epoch(state)
|
|
validator_index = get_active_validator_indices(state, current_epoch)[-1]
|
|
|
|
assert state.validator_registry[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH
|
|
|
|
# set validator balance to below ejection threshold
|
|
state.validator_registry[validator_index].effective_balance = spec.EJECTION_BALANCE
|
|
|
|
yield 'pre', state
|
|
|
|
# trigger epoch transition
|
|
block = build_empty_block_for_next_slot(state)
|
|
block.slot += spec.SLOTS_PER_EPOCH
|
|
state_transition(state, block)
|
|
|
|
yield 'blocks', [block], [spec.BeaconBlock]
|
|
yield 'post', state
|
|
|
|
assert state.validator_registry[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
|
|
|
|
|
|
@spec_state_test
|
|
def test_historical_batch(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(state)
|
|
state_transition(state, block)
|
|
|
|
yield 'blocks', [block], [spec.BeaconBlock]
|
|
yield 'post', state
|
|
|
|
assert state.slot == block.slot
|
|
assert get_current_epoch(state) % (spec.SLOTS_PER_HISTORICAL_ROOT // spec.SLOTS_PER_EPOCH) == 0
|
|
assert len(state.historical_roots) == pre_historical_roots_len + 1
|
|
|
|
|
|
@spec_state_test
|
|
def test_eth1_data_votes(state):
|
|
yield 'pre', state
|
|
|
|
expected_votes = 0
|
|
assert len(state.eth1_data_votes) == expected_votes
|
|
|
|
blocks = []
|
|
for _ in range(spec.SLOTS_PER_ETH1_VOTING_PERIOD - 1):
|
|
block = build_empty_block_for_next_slot(state)
|
|
state_transition(state, block)
|
|
expected_votes += 1
|
|
assert len(state.eth1_data_votes) == expected_votes
|
|
blocks.append(block)
|
|
|
|
block = build_empty_block_for_next_slot(state)
|
|
blocks.append(block)
|
|
|
|
state_transition(state, block)
|
|
|
|
yield 'blocks', [block], [spec.BeaconBlock]
|
|
yield 'post', state
|
|
|
|
assert state.slot % spec.SLOTS_PER_ETH1_VOTING_PERIOD == 0
|
|
assert len(state.eth1_data_votes) == 1
|