def test_proposer_slashing(state): # copy for later balance lookups. pre_state = deepcopy(state) proposer_slashing = get_valid_proposer_slashing(state, signed_1=True, signed_2=True) 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) sign_block(state, block) 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)
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, signed=True) 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) sign_block(state, block) 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
def run_proposer_slashing_processing(spec, state, proposer_slashing, valid=True): """ Run ``process_proposer_slashing``, yielding: - pre-state ('pre') - proposer_slashing ('proposer_slashing') - post-state ('post'). If ``valid == False``, run expecting ``AssertionError`` """ yield 'pre', state yield 'proposer_slashing', proposer_slashing if not valid: expect_assertion_error( lambda: spec.process_proposer_slashing(state, proposer_slashing)) yield 'post', None return proposer_index = proposer_slashing.signed_header_1.message.proposer_index pre_proposer_balance = get_balance(state, proposer_index) spec.process_proposer_slashing(state, proposer_slashing) yield 'post', state # check if slashed slashed_validator = state.validators[proposer_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, proposer_index) < pre_proposer_balance
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 = get_indexed_attestation_participants(spec, attester_slashing.attestation_1)[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) )
def run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, valid=True): """ Run ``process_randao_key_reveal``, yielding: - pre-state ('pre') - randao_key_reveal ('randao_key_reveal') - post-state ('post'). If ``valid == False``, run expecting ``AssertionError`` """ yield 'pre', state yield 'randao_key_reveal', randao_key_reveal if not valid: expect_assertion_error(lambda: spec.process_early_derived_secret_reveal(state, randao_key_reveal)) yield 'post', None return pre_slashed_balance = get_balance(state, randao_key_reveal.revealed_index) spec.process_early_derived_secret_reveal(state, randao_key_reveal) slashed_validator = state.validators[randao_key_reveal.revealed_index] if randao_key_reveal.epoch >= spec.get_current_epoch(state) + spec.CUSTODY_PERIOD_TO_RANDAO_PADDING: assert slashed_validator.slashed assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH assert get_balance(state, randao_key_reveal.revealed_index) < pre_slashed_balance yield 'post', state
def run_custody_response_processing(spec, state, custody_response, valid=True): """ Run ``process_bit_challenge_response``, yielding: - pre-state ('pre') - CustodyResponse ('custody_response') - post-state ('post'). If ``valid == False``, run expecting ``AssertionError`` """ yield 'pre', state yield 'custody_response', custody_response if not valid: expect_assertion_error(lambda: spec.process_custody_response(state, custody_response)) yield 'post', None return # TODO: Add capability to also process chunk challenges, not only bit challenges challenge = state.custody_bit_challenge_records[custody_response.challenge_index] pre_slashed_balance = get_balance(state, challenge.challenger_index) spec.process_custody_response(state, custody_response) slashed_validator = state.validators[challenge.challenger_index] assert slashed_validator.slashed assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH assert get_balance(state, challenge.challenger_index) < pre_slashed_balance yield 'post', state
def run_custody_slashing_processing(spec, state, custody_slashing, valid=True, correct=True): """ Run ``process_bit_challenge``, yielding: - pre-state ('pre') - CustodySlashing ('custody_slashing') - post-state ('post'). If ``valid == False``, run expecting ``AssertionError`` """ yield 'pre', state yield 'custody_slashing', custody_slashing if not valid: expect_assertion_error(lambda: spec.process_custody_slashing(state, custody_slashing)) yield 'post', None return if correct: pre_slashed_balance = get_balance(state, custody_slashing.message.malefactor_index) else: pre_slashed_balance = get_balance(state, custody_slashing.message.whistleblower_index) spec.process_custody_slashing(state, custody_slashing) if correct: slashed_validator = state.validators[custody_slashing.message.malefactor_index] assert get_balance(state, custody_slashing.message.malefactor_index) < pre_slashed_balance else: slashed_validator = state.validators[custody_slashing.message.whistleblower_index] assert get_balance(state, custody_slashing.message.whistleblower_index) < pre_slashed_balance assert slashed_validator.slashed assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH yield 'post', state
def run_deposit_processing(spec, state, deposit, validator_index, valid=True, effective=True): """ Run ``process_deposit``, yielding: - pre-state ('pre') - deposit ('deposit') - post-state ('post'). If ``valid == False``, run expecting ``AssertionError`` """ pre_validator_count = len(state.validators) pre_balance = 0 if validator_index < pre_validator_count: pre_balance = get_balance(state, validator_index) yield 'pre', state yield 'deposit', deposit if not valid: expect_assertion_error(lambda: spec.process_deposit(state, deposit)) yield 'post', None return spec.process_deposit(state, deposit) yield 'post', state if not effective: assert len(state.validators) == pre_validator_count assert len(state.balances) == pre_validator_count if validator_index < pre_validator_count: assert get_balance(state, validator_index) == pre_balance else: if validator_index < pre_validator_count: # top-up assert len(state.validators) == pre_validator_count assert len(state.balances) == pre_validator_count else: # new validator assert len(state.validators) == pre_validator_count + 1 assert len(state.balances) == pre_validator_count + 1 assert get_balance( state, validator_index) == pre_balance + deposit.data.amount effective = min(spec.MAX_EFFECTIVE_BALANCE, pre_balance + deposit.data.amount) effective -= effective % spec.EFFECTIVE_BALANCE_INCREMENT assert state.validators[validator_index].effective_balance == effective assert state.eth1_deposit_index == state.eth1_data.deposit_count
def check_attester_slashing_effect(spec, pre_state, state, slashed_indices): for slashed_index in slashed_indices: slashed_validator = state.validators[slashed_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, slashed_index) < get_balance(pre_state, slashed_index) proposer_index = spec.get_beacon_proposer_index(state) # gained whistleblower reward assert get_balance(state, proposer_index) > get_balance(pre_state, proposer_index)
def run_deposit_processing(state, deposit, validator_index, valid=True, effective=True): """ Run ``process_deposit``, yielding: - pre-state ('pre') - deposit ('deposit') - post-state ('post'). If ``valid == False``, run expecting ``AssertionError`` """ pre_validator_count = len(state.validator_registry) pre_balance = 0 if validator_index < pre_validator_count: pre_balance = get_balance(state, validator_index) else: # if it is a new validator, it should be right at the end of the current registry. assert validator_index == pre_validator_count yield 'pre', state yield 'deposit', deposit if not valid: expect_assertion_error(lambda: process_deposit(state, deposit)) yield 'post', None return process_deposit(state, deposit) yield 'post', state if not effective: assert len(state.validator_registry) == pre_validator_count assert len(state.balances) == pre_validator_count if validator_index < pre_validator_count: assert get_balance(state, validator_index) == pre_balance else: if validator_index < pre_validator_count: # top-up assert len(state.validator_registry) == pre_validator_count assert len(state.balances) == pre_validator_count else: # new validator assert len(state.validator_registry) == pre_validator_count + 1 assert len(state.balances) == pre_validator_count + 1 assert get_balance( state, validator_index) == pre_balance + deposit.data.amount assert state.deposit_index == state.latest_eth1_data.deposit_count
def run_attester_slashing_processing(spec, state, attester_slashing, valid=True): """ Run ``process_attester_slashing``, yielding: - pre-state ('pre') - attester_slashing ('attester_slashing') - post-state ('post'). If ``valid == False``, run expecting ``AssertionError`` """ yield 'pre', state yield 'attester_slashing', attester_slashing if not valid: expect_assertion_error( lambda: spec.process_attester_slashing(state, attester_slashing)) yield 'post', None return slashed_index = attester_slashing.attestation_1.custody_bit_0_indices[0] pre_slashed_balance = get_balance(state, slashed_index) proposer_index = spec.get_beacon_proposer_index(state) pre_proposer_balance = get_balance(state, proposer_index) # Process slashing spec.process_attester_slashing(state, attester_slashing) slashed_validator = state.validator_registry[slashed_index] # Check slashing assert slashed_validator.slashed assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH if slashed_index != proposer_index: # lost whistleblower reward assert get_balance(state, slashed_index) < pre_slashed_balance # gained whistleblower reward assert get_balance(state, proposer_index) > pre_proposer_balance else: # gained rewards for all slashings, which may include others. And only lost that of themselves. # Netto at least 0, if more people where slashed, a balance increase. assert get_balance(state, slashed_index) >= pre_slashed_balance yield 'post', state
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]
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) pre_state = state.copy() yield 'pre', 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 # Altair introduces sync committee (sm) reward and penalty sync_committee_reward = sync_committee_penalty = 0 if is_post_altair(spec): committee_indices = compute_committee_indices( spec, state, state.current_sync_committee) committee_bits = block.body.sync_aggregate.sync_committee_bits sync_committee_reward, sync_committee_penalty = compute_sync_committee_participant_reward_and_penalty( spec, pre_state, validator_index, committee_indices, committee_bits, ) assert get_balance(state, validator_index) == (validator_pre_balance + amount + sync_committee_reward - sync_committee_penalty)
def get_valid_transfer(spec, state, slot=None, sender_index=None, recipient_index=None, amount=None, fee=None, signed=False): if slot is None: slot = state.slot current_epoch = spec.get_current_epoch(state) if sender_index is None: sender_index = spec.get_active_validator_indices(state, current_epoch)[-1] if recipient_index is None: recipient_index = spec.get_active_validator_indices( state, current_epoch)[0] transfer_pubkey = pubkeys[-1] transfer_privkey = privkeys[-1] if fee is None: fee = get_balance(state, sender_index) // 32 if amount is None: amount = get_balance(state, sender_index) - fee transfer = spec.Transfer( sender=sender_index, recipient=recipient_index, amount=amount, fee=fee, slot=slot, pubkey=transfer_pubkey, ) if signed: sign_transfer(spec, state, transfer, transfer_privkey) # ensure withdrawal_credentials reproducible state.validators[transfer.sender].withdrawal_credentials = ( spec.BLS_WITHDRAWAL_PREFIX + spec.hash(transfer.pubkey)[1:]) return transfer
def check_proposer_slashing_effect(spec, pre_state, state, slashed_index): slashed_validator = state.validators[slashed_index] assert slashed_validator.slashed assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH proposer_index = spec.get_beacon_proposer_index(state) slash_penalty = state.validators[ slashed_index].effective_balance // get_min_slashing_penalty_quotient( spec) whistleblower_reward = state.validators[ slashed_index].effective_balance // spec.WHISTLEBLOWER_REWARD_QUOTIENT if proposer_index != slashed_index: # slashed validator lost initial slash penalty assert (get_balance( state, slashed_index) == get_balance(pre_state, slashed_index) - slash_penalty) # block proposer gained whistleblower reward # >= because proposer could have reported multiple assert (get_balance(state, proposer_index) >= get_balance(pre_state, proposer_index) + whistleblower_reward) else: # proposer reported themself so get penalty and reward # >= because proposer could have reported multiple assert (get_balance( state, slashed_index) >= get_balance(pre_state, slashed_index) - slash_penalty + whistleblower_reward)
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
def check_proposer_slashing_effect(spec, pre_state, state, slashed_index, block=None): slashed_validator = state.validators[slashed_index] assert slashed_validator.slashed assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH proposer_index = spec.get_beacon_proposer_index(state) slash_penalty = state.validators[ slashed_index].effective_balance // get_min_slashing_penalty_quotient( spec) whistleblower_reward = state.validators[ slashed_index].effective_balance // spec.WHISTLEBLOWER_REWARD_QUOTIENT # Altair introduces sync committee (SC) reward and penalty sc_reward_for_slashed = sc_penalty_for_slashed = sc_reward_for_proposer = sc_penalty_for_proposer = 0 if is_post_altair(spec) and block is not None: committee_indices = compute_committee_indices( spec, state, state.current_sync_committee) committee_bits = block.body.sync_aggregate.sync_committee_bits sc_reward_for_slashed, sc_penalty_for_slashed = compute_sync_committee_participant_reward_and_penalty( spec, pre_state, slashed_index, committee_indices, committee_bits, ) sc_reward_for_proposer, sc_penalty_for_proposer = compute_sync_committee_participant_reward_and_penalty( spec, pre_state, proposer_index, committee_indices, committee_bits, ) if proposer_index != slashed_index: # slashed validator lost initial slash penalty assert (get_balance( state, slashed_index) == get_balance(pre_state, slashed_index) - slash_penalty + sc_reward_for_slashed - sc_penalty_for_slashed) # block proposer gained whistleblower reward # >= because proposer could have reported multiple assert ( get_balance(state, proposer_index) >= (get_balance(pre_state, proposer_index) + whistleblower_reward + sc_reward_for_proposer - sc_penalty_for_proposer)) else: # proposer reported themself so get penalty and reward # >= because proposer could have reported multiple assert (get_balance(state, slashed_index) >= (get_balance(pre_state, slashed_index) - slash_penalty + whistleblower_reward + sc_reward_for_slashed - sc_penalty_for_slashed))
def run_attester_slashing_processing(spec, state, attester_slashing, valid=True): """ Run ``process_attester_slashing``, yielding: - pre-state ('pre') - attester_slashing ('attester_slashing') - post-state ('post'). If ``valid == False``, run expecting ``AssertionError`` """ yield 'pre', state yield 'attester_slashing', attester_slashing if not valid: expect_assertion_error( lambda: spec.process_attester_slashing(state, attester_slashing)) yield 'post', None return slashed_indices = attester_slashing.attestation_1.attesting_indices proposer_index = spec.get_beacon_proposer_index(state) pre_proposer_balance = get_balance(state, proposer_index) pre_slashings = { slashed_index: get_balance(state, slashed_index) for slashed_index in slashed_indices } pre_withdrawalable_epochs = { slashed_index: state.validators[slashed_index].withdrawable_epoch for slashed_index in slashed_indices } total_proposer_rewards = sum(balance // spec.WHISTLEBLOWER_REWARD_QUOTIENT for balance in pre_slashings.values()) # Process slashing spec.process_attester_slashing(state, attester_slashing) for slashed_index in slashed_indices: pre_withdrawalable_epoch = pre_withdrawalable_epochs[slashed_index] slashed_validator = state.validators[slashed_index] # Check slashing assert slashed_validator.slashed assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH if pre_withdrawalable_epoch < spec.FAR_FUTURE_EPOCH: expected_withdrawable_epoch = max( pre_withdrawalable_epoch, spec.get_current_epoch(state) + spec.EPOCHS_PER_SLASHINGS_VECTOR) assert slashed_validator.withdrawable_epoch == expected_withdrawable_epoch else: assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH assert get_balance(state, slashed_index) < pre_slashings[slashed_index] if proposer_index not in slashed_indices: # gained whistleblower reward assert get_balance( state, proposer_index) == pre_proposer_balance + total_proposer_rewards else: # gained rewards for all slashings, which may include others. And only lost that of themselves. expected_balance = (pre_proposer_balance + total_proposer_rewards - pre_slashings[proposer_index] // spec.MIN_SLASHING_PENALTY_QUOTIENT) assert get_balance(state, proposer_index) == expected_balance yield 'post', state