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.config.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 test_proposer_slashing(spec, state): # copy for later balance lookups. pre_state = state.copy() proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) slashed_index = proposer_slashing.signed_header_1.message.proposer_index assert not state.validators[slashed_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_proposer_slashing_effect(spec, pre_state, state, slashed_index, block)
def test_multiple_different_proposer_slashings_same_block(spec, state): pre_state = state.copy() num_slashings = 3 proposer_slashings = [] for i in range(num_slashings): slashed_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[i] assert not state.validators[slashed_index].slashed proposer_slashing = get_valid_proposer_slashing(spec, state, slashed_index=slashed_index, signed_1=True, signed_2=True) proposer_slashings.append(proposer_slashing) yield 'pre', state # # Add to state via block transition # block = build_empty_block_for_next_slot(spec, state) block.body.proposer_slashings = proposer_slashings signed_block = state_transition_and_sign_block(spec, state, block) yield 'blocks', [signed_block] yield 'post', state for proposer_slashing in proposer_slashings: slashed_index = proposer_slashing.signed_header_1.message.proposer_index check_proposer_slashing_effect(spec, pre_state, state, slashed_index)
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) sign_block(spec, state, block) state_transition_and_sign_block(spec, state, block) yield 'blocks', [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)
def test_proposer_is_slashed(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) # set proposer to slashed proposer_index = proposer_slashing.signed_header_1.message.proposer_index state.validators[proposer_index].slashed = True yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
def test_proposer_is_not_activated(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) # set proposer to be not active yet proposer_index = proposer_slashing.signed_header_1.message.proposer_index state.validators[proposer_index].activation_epoch = spec.get_current_epoch(state) + 1 yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
def test_success_block_header_from_future(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, slot=state.slot + 5, signed_1=True, signed_2=True) yield from run_proposer_slashing_processing(spec, state, proposer_slashing)
def test_invalid_sig_1_and_2_swap(spec, state): # Get valid signatures for the slashings proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) # But swap them signature_1 = proposer_slashing.signed_header_1.signature proposer_slashing.signed_header_1.signature = proposer_slashing.signed_header_2.signature proposer_slashing.signed_header_2.signature = signature_1 yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
def test_invalid_proposer_index(state): proposer_slashing = get_valid_proposer_slashing(state, signed_1=True, signed_2=True) # Index just too high (by 1) proposer_slashing.proposer_index = len(state.validator_registry) yield from run_proposer_slashing_processing(state, proposer_slashing, False)
def test_proposer_is_slashed(state): proposer_slashing = get_valid_proposer_slashing(state, signed_1=True, signed_2=True) # set proposer to slashed state.validator_registry[proposer_slashing.proposer_index].slashed = True yield from run_proposer_slashing_processing(state, proposer_slashing, False)
def test_headers_are_same(state): proposer_slashing = get_valid_proposer_slashing(state, signed_1=True, signed_2=False) # set headers to be the same proposer_slashing.header_2 = proposer_slashing.header_1 yield from run_proposer_slashing_processing(state, proposer_slashing, False)
def test_epochs_are_different(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=False) # set slots to be in different epochs header_2 = proposer_slashing.signed_header_2.message proposer_index = header_2.proposer_index header_2.slot += spec.SLOTS_PER_EPOCH proposer_slashing.signed_header_2 = sign_block_header(spec, state, header_2, privkeys[proposer_index]) yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
def test_success(spec, state): # Get proposer for next slot block = build_empty_block_for_next_slot(spec, state) proposer_index = block.proposer_index # Create slashing for same proposer proposer_slashing = get_valid_proposer_slashing(spec, state, slashed_index=proposer_index, signed_1=True, signed_2=True) yield from run_proposer_slashing_processing(spec, state, proposer_slashing)
def test_double_similar_proposer_slashings_same_block(spec, state): slashed_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] # Same validator, but different slashable offences in the same block proposer_slashing_1 = get_valid_proposer_slashing(spec, state, random_root=b'\xaa' * 32, slashed_index=slashed_index, signed_1=True, signed_2=True) proposer_slashing_2 = get_valid_proposer_slashing(spec, state, random_root=b'\xbb' * 32, slashed_index=slashed_index, signed_1=True, signed_2=True) assert not state.validators[slashed_index].slashed yield 'pre', state block = build_empty_block_for_next_slot(spec, state) block.body.proposer_slashings = [proposer_slashing_1, proposer_slashing_2] signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) yield 'blocks', [signed_block] yield 'post', None
def test_headers_are_same_sigs_are_different(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=False) # set headers to be the same proposer_slashing.signed_header_2 = proposer_slashing.signed_header_1.copy() # but signatures to be different proposer_slashing.signed_header_2.signature = proposer_slashing.signed_header_2.signature[:-1] + b'\x00' assert proposer_slashing.signed_header_1.signature != proposer_slashing.signed_header_2.signature yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
def test_proposer_is_withdrawn(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) # move 1 epoch into future, to allow for past withdrawable epoch next_epoch(spec, state) # set proposer withdrawable_epoch in past current_epoch = spec.get_current_epoch(state) proposer_index = proposer_slashing.signed_header_1.message.proposer_index state.validators[proposer_index].withdrawable_epoch = current_epoch - 1 yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
def test_invalid_different_proposer_indices(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) # set different index and sign header_1 = proposer_slashing.signed_header_1.message header_2 = proposer_slashing.signed_header_2.message active_indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)) active_indices = [i for i in active_indices if i != header_1.proposer_index] header_2.proposer_index = active_indices[0] proposer_slashing.signed_header_2 = sign_block_header(spec, state, header_2, privkeys[header_2.proposer_index]) yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
def test_proposer_is_not_activated(state): proposer_slashing = get_valid_proposer_slashing(state, signed_1=True, signed_2=True) # set proposer to be not active yet state.validator_registry[ proposer_slashing. proposer_index].activation_epoch = get_current_epoch(state) + 1 yield from run_proposer_slashing_processing(state, proposer_slashing, False)
def test_invalid_proposer_index(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) # Index just too high (by 1) proposer_slashing.signed_header_1.message.proposer_index = len( state.validators) proposer_slashing.signed_header_2.message.proposer_index = len( state.validators) yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
def test_double_same_proposer_slashings_same_block(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) slashed_index = proposer_slashing.signed_header_1.message.proposer_index assert not state.validators[slashed_index].slashed yield 'pre', state block = build_empty_block_for_next_slot(spec, state) block.body.proposer_slashings = [proposer_slashing, proposer_slashing] signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) yield 'blocks', [signed_block] yield 'post', None
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 test_proposer_is_withdrawn(state): proposer_slashing = get_valid_proposer_slashing(state, signed_1=True, signed_2=True) # move 1 epoch into future, to allow for past withdrawable epoch state.slot += spec.SLOTS_PER_EPOCH # set proposer withdrawable_epoch in past current_epoch = get_current_epoch(state) proposer_index = proposer_slashing.proposer_index state.validator_registry[ proposer_index].withdrawable_epoch = current_epoch - 1 yield from run_proposer_slashing_processing(state, proposer_slashing, False)
def test_proposer_self_slashing(spec, state): yield 'pre', state block = build_empty_block_for_next_slot(spec, state) assert not state.validators[block.proposer_index].slashed proposer_slashing = get_valid_proposer_slashing( spec, state, slashed_index=block.proposer_index, signed_1=True, signed_2=True) block.body.proposer_slashings.append(proposer_slashing) # The header is processed *before* the block body: # the proposer was not slashed before the body, thus the block is valid. signed_block = state_transition_and_sign_block(spec, state, block) # The proposer slashed themselves. assert state.validators[block.proposer_index].slashed yield 'blocks', [signed_block] yield 'post', state
def run_transition_with_operation(state, fork_epoch, spec, post_spec, pre_tag, post_tag, operation_type, operation_at_slot): """ Generate `operation_type` operation with the spec before fork. The operation would be included into the block at `operation_at_slot`. """ is_at_fork = operation_at_slot == fork_epoch * spec.SLOTS_PER_EPOCH is_right_before_fork = operation_at_slot == fork_epoch * spec.SLOTS_PER_EPOCH - 1 assert is_at_fork or is_right_before_fork if is_at_fork: transition_until_fork(spec, state, fork_epoch) elif is_right_before_fork: _transition_until_fork_minus_one(spec, state, fork_epoch) is_slashing_operation = operation_type in (OperationType.PROPOSER_SLASHING, OperationType.ATTESTER_SLASHING) # prepare operation selected_validator_index = None if is_slashing_operation: # avoid slashing the next proposer future_state = state.copy() next_slot(spec, future_state) proposer_index = spec.get_beacon_proposer_index(future_state) selected_validator_index = (proposer_index + 1) % len(state.validators) if operation_type == OperationType.PROPOSER_SLASHING: proposer_slashing = get_valid_proposer_slashing( spec, state, slashed_index=selected_validator_index, signed_1=True, signed_2=True) operation_dict = {'proposer_slashings': [proposer_slashing]} else: # operation_type == OperationType.ATTESTER_SLASHING: attester_slashing = get_valid_attester_slashing_by_indices( spec, state, [selected_validator_index], signed_1=True, signed_2=True, ) operation_dict = {'attester_slashings': [attester_slashing]} elif operation_type == OperationType.DEPOSIT: # create a new deposit selected_validator_index = len(state.validators) amount = spec.MAX_EFFECTIVE_BALANCE deposit = prepare_state_and_deposit(spec, state, selected_validator_index, amount, signed=True) operation_dict = {'deposits': [deposit]} elif operation_type == OperationType.VOLUNTARY_EXIT: selected_validator_index = 0 signed_exits = prepare_signed_exits(spec, state, [selected_validator_index]) operation_dict = {'voluntary_exits': signed_exits} def _check_state(): if operation_type == OperationType.PROPOSER_SLASHING: slashed_proposer = state.validators[ proposer_slashing.signed_header_1.message.proposer_index] assert slashed_proposer.slashed elif operation_type == OperationType.ATTESTER_SLASHING: indices = set( attester_slashing.attestation_1.attesting_indices ).intersection(attester_slashing.attestation_2.attesting_indices) assert selected_validator_index in indices assert len(indices) > 0 for validator_index in indices: assert state.validators[validator_index].slashed elif operation_type == OperationType.DEPOSIT: assert not post_spec.is_active_validator( state.validators[selected_validator_index], post_spec.get_current_epoch(state)) elif operation_type == OperationType.VOLUNTARY_EXIT: validator = state.validators[selected_validator_index] assert validator.exit_epoch < post_spec.FAR_FUTURE_EPOCH yield "pre", state blocks = [] if is_right_before_fork: # add a block with operation. block = build_empty_block_for_next_slot(spec, state) _set_operations_by_dict(block, operation_dict) signed_block = state_transition_and_sign_block(spec, state, block) blocks.append(pre_tag(signed_block)) _check_state() # irregular state transition to handle fork: _operation_at_slot = operation_dict if is_at_fork else None state, block = do_fork(state, spec, post_spec, fork_epoch, operation_dict=_operation_at_slot) blocks.append(post_tag(block)) if is_at_fork: _check_state() # after the fork if operation_type == OperationType.DEPOSIT: state = _transition_until_active(post_spec, state, post_tag, blocks, selected_validator_index) else: # avoid using the slashed validators as block proposers ignoring_proposers = [selected_validator_index ] if is_slashing_operation else None # continue regular state transition with new spec into next epoch transition_to_next_epoch_and_append_blocks( post_spec, state, post_tag, blocks, only_last_block=True, ignoring_proposers=ignoring_proposers, ) yield "blocks", blocks yield "post", state
def test_invalid_sig_1_and_2(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=False, signed_2=False) yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
def test_success_slashed_and_proposer_index_the_same(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) yield from run_proposer_slashing_processing(spec, state, proposer_slashing)
def test_success(state): proposer_slashing = get_valid_proposer_slashing(state, signed_1=True, signed_2=True) yield from run_proposer_slashing_processing(state, proposer_slashing)