From 43aec533466e90efef8edf51ce212ccaf11affce Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 7 Jan 2019 22:46:44 +0800 Subject: [PATCH] Sync ethereum/eth2.0-specs#374 part1: types, constants, deposit_helpers --- eth/beacon/deposit_helpers.py | 81 ++---- eth/beacon/enums.py | 9 +- eth/beacon/exceptions.py | 10 - eth/beacon/helpers.py | 45 +-- eth/beacon/state_machines/configs.py | 15 +- .../state_machines/forks/serenity/configs.py | 15 +- eth/beacon/types/deposit_data.py | 8 +- eth/beacon/types/states.py | 34 ++- eth/beacon/types/validator_records.py | 60 ++-- .../types/validator_registry_delta_block.py | 6 +- tests/beacon/conftest.py | 264 ++++++++++++++---- tests/beacon/helpers.py | 46 ++- tests/beacon/state_machines/conftest.py | 30 +- .../test_proposer_signature_validation.py | 15 +- tests/beacon/test_deposit_helpers.py | 172 ++---------- tests/beacon/test_helpers.py | 96 +++---- tests/beacon/types/test_deposit_data.py | 2 +- tests/beacon/types/test_states.py | 118 ++++---- tests/beacon/types/test_validator_record.py | 34 +-- .../test_validator_registry_delta_block.py | 1 + 20 files changed, 522 insertions(+), 539 deletions(-) diff --git a/eth/beacon/deposit_helpers.py b/eth/beacon/deposit_helpers.py index d4bcab1466..a1a8006f0a 100644 --- a/eth/beacon/deposit_helpers.py +++ b/eth/beacon/deposit_helpers.py @@ -1,5 +1,4 @@ from typing import ( - Sequence, Tuple, ) @@ -18,9 +17,6 @@ from eth.beacon.enums import ( SignatureDomain, ) -from eth.beacon.exceptions import ( - MinEmptyValidatorIndexNotFound, -) from eth.beacon.types.deposit_input import DepositInput from eth.beacon.types.states import BeaconState from eth.beacon.types.validator_records import ValidatorRecord @@ -36,20 +32,6 @@ ) -def get_min_empty_validator_index(validators: Sequence[ValidatorRecord], - validator_balances: Sequence[Gwei], - current_slot: SlotNumber, - zero_balance_validator_ttl: int) -> ValidatorIndex: - for index, (validator, balance) in enumerate(zip(validators, validator_balances)): - is_empty = ( - balance == 0 and - validator.latest_status_change_slot + zero_balance_validator_ttl <= current_slot - ) - if is_empty: - return ValidatorIndex(index) - raise MinEmptyValidatorIndexNotFound() - - def validate_proof_of_possession(state: BeaconState, pubkey: BLSPubkey, proof_of_possession: BLSSignature, @@ -84,32 +66,17 @@ def validate_proof_of_possession(state: BeaconState, def add_pending_validator(state: BeaconState, validator: ValidatorRecord, - deposit: Gwei, - zero_balance_validator_ttl: int) -> Tuple[BeaconState, ValidatorIndex]: + amount: Gwei) -> Tuple[BeaconState, int]: """ - Add a validator to the existing minimum empty validator index or - append to ``validator_registry``. + Add a validator to ``state``. """ - # Check if there's empty validator index in `validator_registry` - try: - index = get_min_empty_validator_index( - state.validator_registry, - state.validator_balances, - state.slot, - zero_balance_validator_ttl, - ) - except MinEmptyValidatorIndexNotFound: - index = None - # Append to the validator_registry - validator_registry = state.validator_registry + (validator,) - state = state.copy( - validator_registry=validator_registry, - validator_balances=state.validator_balances + (deposit, ) - ) - index = ValidatorIndex(len(state.validator_registry) - 1) - else: - # Use the empty validator index - state = state.update_validator(index, validator, deposit) + validator_registry = state.validator_registry + (validator,) + state = state.copy( + validator_registry=validator_registry, + validator_balances=state.validator_balances + (amount, ) + ) + + index = len(state.validator_registry) - 1 return state, index @@ -117,22 +84,22 @@ def add_pending_validator(state: BeaconState, def process_deposit(*, state: BeaconState, pubkey: BLSPubkey, - deposit: Gwei, + amount: Gwei, proof_of_possession: BLSSignature, withdrawal_credentials: Hash32, randao_commitment: Hash32, custody_commitment: Hash32, - zero_balance_validator_ttl: int) -> Tuple[BeaconState, ValidatorIndex]: + far_future_slot: SlotNumber) -> BeaconState: """ Process a deposit from Ethereum 1.0. """ validate_proof_of_possession( - state, - pubkey, - proof_of_possession, - withdrawal_credentials, - randao_commitment, - custody_commitment, + state=state, + pubkey=pubkey, + proof_of_possession=proof_of_possession, + withdrawal_credentials=withdrawal_credentials, + randao_commitment=randao_commitment, + custody_commitment=custody_commitment, ) validator_pubkeys = tuple(v.pubkey for v in state.validator_registry) @@ -141,15 +108,16 @@ def process_deposit(*, pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, randao_commitment=randao_commitment, - latest_status_change_slot=state.slot, custody_commitment=custody_commitment, + far_future_slot=far_future_slot, ) + # Note: In phase 2 registry indices that has been withdrawn for a long time + # will be recycled. state, index = add_pending_validator( state, validator, - deposit, - zero_balance_validator_ttl, + amount, ) else: # Top-up - increase balance by deposit @@ -166,10 +134,9 @@ def process_deposit(*, ) # Update validator's balance and state - state = state.update_validator( + state = state.update_validator_balance( validator_index=index, - validator=validator, - balance=state.validator_balances[index] + deposit, + balance=state.validator_balances[index] + amount, ) - return state, index + return state diff --git a/eth/beacon/enums.py b/eth/beacon/enums.py index ac50633ba4..37923f46dc 100644 --- a/eth/beacon/enums.py +++ b/eth/beacon/enums.py @@ -1,12 +1,9 @@ from enum import IntEnum -class ValidatorStatusCode(IntEnum): - PENDING_ACTIVATION = 0 - ACTIVE = 1 - ACTIVE_PENDING_EXIT = 2 - EXITED_WITHOUT_PENALTY = 3 - EXITED_WITH_PENALTY = 4 +class ValidatorStatusFlags(IntEnum): + INITIATED_EXIT = 1 + WITHDRAWABLE = 2 class ValidatorRegistryDeltaFlag(IntEnum): diff --git a/eth/beacon/exceptions.py b/eth/beacon/exceptions.py index d58c975fc3..e69de29bb2 100644 --- a/eth/beacon/exceptions.py +++ b/eth/beacon/exceptions.py @@ -1,10 +0,0 @@ -from eth.exceptions import ( - PyEVMError, -) - - -class MinEmptyValidatorIndexNotFound(PyEVMError): - """ - No empty slot in the validator registry - """ - pass diff --git a/eth/beacon/helpers.py b/eth/beacon/helpers.py index 525bf3b5dd..adda906a16 100644 --- a/eth/beacon/helpers.py +++ b/eth/beacon/helpers.py @@ -24,15 +24,11 @@ clamp, ) -from eth.beacon.types.validator_registry_delta_block import ( - ValidatorRegistryDeltaBlock, -) from eth.beacon.block_committees_info import ( BlockCommitteesInfo, ) from eth.beacon.enums import ( SignatureDomain, - ValidatorRegistryDeltaFlag, ) from eth.beacon.types.shard_committees import ( ShardCommittee, @@ -157,14 +153,14 @@ def get_shard_committees_at_slot(state: 'BeaconState', ) -def get_active_validator_indices( - validators: Sequence['ValidatorRecord']) -> Tuple[ValidatorIndex, ...]: +def get_active_validator_indices(validators: Sequence['ValidatorRecord'], + slot: SlotNumber) -> Tuple[int, ...]: """ Get indices of active validators from ``validators``. """ return tuple( - ValidatorIndex(i) for i, v in enumerate(validators) - if v.is_active + i for i, v in enumerate(validators) + if v.is_active(slot) ) @@ -189,13 +185,14 @@ def _get_shards_committees_for_shard_indices( @to_tuple -def get_new_shuffling(*, - seed: Hash32, - validators: Sequence['ValidatorRecord'], - crosslinking_start_shard: ShardNumber, - epoch_length: int, - target_committee_size: int, - shard_count: int) -> Iterable[Iterable[ShardCommittee]]: +def get_shuffling(*, + seed: Hash32, + validators: Sequence['ValidatorRecord'], + crosslinking_start_shard: ShardNumber, + slot: SlotNumber, + epoch_length: int, + target_committee_size: int, + shard_count: int) -> Iterable[Iterable[ShardCommittee]]: """ Return shuffled ``shard_committee_for_slots`` (``[[ShardCommittee]]``) of the given active ``validators`` using ``seed`` as entropy. @@ -238,7 +235,7 @@ def get_new_shuffling(*, ], ] """ - active_validators = get_active_validator_indices(validators) + active_validators = get_active_validator_indices(validators, slot) active_validators_size = len(active_validators) committees_per_slot = clamp( 1, @@ -393,22 +390,6 @@ def get_effective_balance( return min(validator_balances[index], max_deposit * denoms.gwei) -def get_new_validator_registry_delta_chain_tip(current_validator_registry_delta_chain_tip: Hash32, - validator_index: ValidatorIndex, - pubkey: BLSPubkey, - flag: ValidatorRegistryDeltaFlag) -> Hash32: - """ - Compute the next hash in the validator registry delta hash chain. - """ - # TODO: switch to SSZ tree hashing - return ValidatorRegistryDeltaBlock( - latest_registry_delta_root=current_validator_registry_delta_chain_tip, - validator_index=validator_index, - pubkey=pubkey, - flag=flag, - ).root - - def get_fork_version(fork_data: 'ForkData', slot: SlotNumber) -> int: """ diff --git a/eth/beacon/state_machines/configs.py b/eth/beacon/state_machines/configs.py index e8d9dfea32..69b93d01cd 100644 --- a/eth/beacon/state_machines/configs.py +++ b/eth/beacon/state_machines/configs.py @@ -22,10 +22,10 @@ ('EJECTION_BALANCE', Ether), ('MAX_BALANCE_CHURN_QUOTIENT', int), ('BEACON_CHAIN_SHARD_NUMBER', ShardNumber), - ('BLS_WITHDRAWAL_PREFIX_BYTE', bytes), ('MAX_CASPER_VOTES', int), ('LATEST_BLOCK_ROOTS_LENGTH', int), ('LATEST_RANDAO_MIXES_LENGTH', int), + ('LATEST_PENALIZED_EXIT_LENGTH', int), # EMPTY_SIGNATURE is defined in constants.py # Deposit contract ('DEPOSIT_CONTRACT_ADDRESS', Address), @@ -34,16 +34,19 @@ ('MAX_DEPOSIT', Ether), # ZERO_HASH (ZERO_HASH32) is defined in constants.py # Initial values - ('INITIAL_FORK_VERSION', int), - ('INITIAL_SLOT_NUMBER', SlotNumber), + ('GENESIS_FORK_VERSION', int), + ('GENESIS_SLOT', SlotNumber), + ('FAR_FUTURE_SLOT', SlotNumber), + ('BLS_WITHDRAWAL_PREFIX_BYTE', bytes), # Time parameters ('SLOT_DURATION', Second), ('MIN_ATTESTATION_INCLUSION_DELAY', int), ('EPOCH_LENGTH', int), + ('MIN_VALIDATOR_REGISTRY_CHANGE_INTERVAL', int), + ('SEED_LOOKAHEAD', int), + ('ENTRY_EXIT_DELAY', int), ('POW_RECEIPT_ROOT_VOTING_PERIOD', int), - ('SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD', int), - ('COLLECTIVE_PENALTY_CALCULATION_PERIOD', int), - ('ZERO_BALANCE_VALIDATOR_TTL', int), + ('MIN_VALIDATOR_WITHDRAWAL_TIME', int), # Reward and penalty quotients ('BASE_REWARD_QUOTIENT', int), ('WHISTLEBLOWER_REWARD_QUOTIENT', int), diff --git a/eth/beacon/state_machines/forks/serenity/configs.py b/eth/beacon/state_machines/forks/serenity/configs.py index 8ad2aa1784..fbfdf0998e 100644 --- a/eth/beacon/state_machines/forks/serenity/configs.py +++ b/eth/beacon/state_machines/forks/serenity/configs.py @@ -17,26 +17,29 @@ EJECTION_BALANCE=Ether(2**4), # (= 16) ETH MAX_BALANCE_CHURN_QUOTIENT=2**5, # (= 32) BEACON_CHAIN_SHARD_NUMBER=ShardNumber(2**64 - 1), - BLS_WITHDRAWAL_PREFIX_BYTE=b'\x00', MAX_CASPER_VOTES=2**10, # (= 1,024) votes LATEST_BLOCK_ROOTS_LENGTH=2**13, # (= 8,192) block roots LATEST_RANDAO_MIXES_LENGTH=2**13, # (= 8,192) randao mixes + LATEST_PENALIZED_EXIT_LENGTH=2**13, # (= 8,192) randao mixes # Deposit contract DEPOSIT_CONTRACT_ADDRESS=ZERO_ADDRESS, # TBD DEPOSIT_CONTRACT_TREE_DEPTH=2**5, # (= 32) MIN_DEPOSIT=Ether(2**0), # (= 1) ETH MAX_DEPOSIT=Ether(2**5), # (= 32) ETH # Initial values - INITIAL_FORK_VERSION=0, - INITIAL_SLOT_NUMBER=SlotNumber(0), + GENESIS_FORK_VERSION=0, + GENESIS_SLOT=SlotNumber(0), + FAR_FUTURE_SLOT=SlotNumber(2**63), + BLS_WITHDRAWAL_PREFIX_BYTE=b'\x00', # Time parameters SLOT_DURATION=Second(6), # seconds MIN_ATTESTATION_INCLUSION_DELAY=2**2, # (= 4) slots EPOCH_LENGTH=2**6, # (= 64) slots + MIN_VALIDATOR_REGISTRY_CHANGE_INTERVAL=2**8, # (= 256) slots + SEED_LOOKAHEAD=2**6, # (= 64) slots + ENTRY_EXIT_DELAY=2**8, # (= 256) slots POW_RECEIPT_ROOT_VOTING_PERIOD=2**10, # (= 1,024) slots - SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD=2**17, # (= 131,072) slots - COLLECTIVE_PENALTY_CALCULATION_PERIOD=2**20, # (= 1,048,576) slots - ZERO_BALANCE_VALIDATOR_TTL=2**22, # (= 4,194,304) slots + MIN_VALIDATOR_WITHDRAWAL_TIME=2**14, # (= 16,384) slots # Reward and penalty quotients BASE_REWARD_QUOTIENT=2**10, # (= 1,024) WHISTLEBLOWER_REWARD_QUOTIENT=2**9, # (= 512) diff --git a/eth/beacon/types/deposit_data.py b/eth/beacon/types/deposit_data.py index 5c9923e6a1..fe906b9f78 100644 --- a/eth/beacon/types/deposit_data.py +++ b/eth/beacon/types/deposit_data.py @@ -14,19 +14,19 @@ class DepositData(rlp.Serializable): """ fields = [ ('deposit_input', DepositInput), - # Value in Gwei - ('value', uint64), + # Amount in Gwei + ('amount', uint64), # Timestamp from deposit contract ('timestamp', uint64), ] def __init__(self, deposit_input: DepositInput, - value: Gwei, + amount: Gwei, timestamp: Timestamp) -> None: super().__init__( deposit_input, - value, + amount, timestamp, ) diff --git a/eth/beacon/types/states.py b/eth/beacon/types/states.py index 37e9f8b04e..4e8a3c81e0 100644 --- a/eth/beacon/types/states.py +++ b/eth/beacon/types/states.py @@ -115,6 +115,10 @@ def __init__( latest_attestations: Sequence[PendingAttestationRecord]=(), candidate_pow_receipt_roots: Sequence[CandidatePoWReceiptRootRecord]=() ) -> None: + if len(validator_registry) != len(validator_balances): + raise ValueError( + "The length of validator_registry and validator_balances should be the same." + ) super().__init__( # Misc slot=slot, @@ -177,18 +181,38 @@ def num_validators(self) -> int: def num_crosslinks(self) -> int: return len(self.latest_crosslinks) - def update_validator(self, - validator_index: ValidatorIndex, - validator: ValidatorRecord, - balance: Gwei) -> 'BeaconState': + def update_validator_registry(self, + validator_index: ValidatorIndex, + validator: ValidatorRecord) -> 'BeaconState': + if validator_index >= self.num_validators or validator_index < 0: + raise IndexError("Incorrect validator index") + validator_registry = list(self.validator_registry) validator_registry[validator_index] = validator + updated_state = self.copy( + validator_registry=tuple(validator_registry), + ) + return updated_state + + def update_validator_balance(self, + validator_index: ValidatorIndex, + balance: Gwei) -> 'BeaconState': + if validator_index >= self.num_validators or validator_index < 0: + raise IndexError("Incorrect validator index") + validator_balances = list(self.validator_balances) validator_balances[validator_index] = balance updated_state = self.copy( - validator_registry=tuple(validator_registry), validator_balances=tuple(validator_balances), ) return updated_state + + def update_validator(self, + validator_index: ValidatorIndex, + validator: ValidatorRecord, + balance: Gwei) -> 'BeaconState': + state = self.update_validator_registry(validator_index, validator) + state = state.update_validator_balance(validator_index, balance) + return state diff --git a/eth/beacon/types/validator_records.py b/eth/beacon/types/validator_records.py index 3fea4ae1ab..b0dd545323 100644 --- a/eth/beacon/types/validator_records.py +++ b/eth/beacon/types/validator_records.py @@ -3,9 +3,6 @@ ) import rlp -from eth.beacon.enums import ( - ValidatorStatusCode, -) from eth.rlp.sedes import ( uint64, uint384, @@ -17,12 +14,6 @@ ) -VALIDATOR_RECORD_ACTIVE_STATUSES = { - ValidatorStatusCode.ACTIVE, - ValidatorStatusCode.ACTIVE_PENDING_EXIT, -} - - class ValidatorRecord(rlp.Serializable): """ Note: using RLP until we have standardized serialization format. @@ -36,16 +27,23 @@ class ValidatorRecord(rlp.Serializable): ('randao_commitment', hash32), # Slot the proposer has skipped (ie. layers of RANDAO expected) ('randao_layers', uint64), - # Status code - ('status', uint64), - # Slot when validator last changed status (or 0) - ('latest_status_change_slot', uint64), - # Sequence number when validator exited (or 0) + # Slot when validator activated + ('activation_slot', uint64), + # Slot when validator exited + ('exit_slot', uint64), + # Slot when validator withdrew + ('withdrawal_slot', uint64), + # Slot when validator was penalized + ('penalized_slot', uint64), + # Exit counter when validator exited ('exit_count', uint64), + # Status flags + ('status_flags', uint64), # Proof of custody commitment ('custody_commitment', hash32), - # Slot the proof of custody seed was last changed + # Slot of latest custody reseed ('latest_custody_reseed_slot', uint64), + # Slot of second-latest custody reseed ('penultimate_custody_reseed_slot', uint64), ] @@ -54,9 +52,12 @@ def __init__(self, withdrawal_credentials: Hash32, randao_commitment: Hash32, randao_layers: int, - status: ValidatorStatusCode, - latest_status_change_slot: SlotNumber, + activation_slot: SlotNumber, + exit_slot: SlotNumber, + withdrawal_slot: SlotNumber, + penalized_slot: SlotNumber, exit_count: int, + status_flags: int, custody_commitment: Hash32, latest_custody_reseed_slot: SlotNumber, penultimate_custody_reseed_slot: SlotNumber) -> None: @@ -65,28 +66,30 @@ def __init__(self, withdrawal_credentials=withdrawal_credentials, randao_commitment=randao_commitment, randao_layers=randao_layers, - status=status, - latest_status_change_slot=latest_status_change_slot, + activation_slot=activation_slot, + exit_slot=exit_slot, + withdrawal_slot=withdrawal_slot, + penalized_slot=penalized_slot, exit_count=exit_count, + status_flags=status_flags, custody_commitment=custody_commitment, latest_custody_reseed_slot=latest_custody_reseed_slot, penultimate_custody_reseed_slot=penultimate_custody_reseed_slot, ) - @property - def is_active(self) -> bool: + def is_active(self, slot: int) -> bool: """ - Returns ``True`` if the validator is active. + Return ``True`` if the validator is active. """ - return self.status in VALIDATOR_RECORD_ACTIVE_STATUSES + return self.activation_slot <= slot < self.exit_slot @classmethod def get_pending_validator(cls, pubkey: BLSPubkey, withdrawal_credentials: Hash32, randao_commitment: Hash32, - latest_status_change_slot: SlotNumber, - custody_commitment: Hash32) -> 'ValidatorRecord': + custody_commitment: Hash32, + far_future_slot: SlotNumber) -> 'ValidatorRecord': """ Return a new pending ``ValidatorRecord`` with the given fields. """ @@ -95,9 +98,12 @@ def get_pending_validator(cls, withdrawal_credentials=withdrawal_credentials, randao_commitment=randao_commitment, randao_layers=0, - status=ValidatorStatusCode.PENDING_ACTIVATION, - latest_status_change_slot=latest_status_change_slot, + activation_slot=far_future_slot, + exit_slot=far_future_slot, + withdrawal_slot=far_future_slot, + penalized_slot=far_future_slot, exit_count=0, + status_flags=0, custody_commitment=custody_commitment, latest_custody_reseed_slot=SlotNumber(0), penultimate_custody_reseed_slot=SlotNumber(0), diff --git a/eth/beacon/types/validator_registry_delta_block.py b/eth/beacon/types/validator_registry_delta_block.py index 10b6973c7e..bc392706fd 100644 --- a/eth/beacon/types/validator_registry_delta_block.py +++ b/eth/beacon/types/validator_registry_delta_block.py @@ -8,6 +8,7 @@ ) from eth.beacon.typing import ( BLSPubkey, + SlotNumber, ValidatorIndex, ) @@ -31,18 +32,21 @@ class ValidatorRegistryDeltaBlock(rlp.Serializable): ('latest_registry_delta_root', hash32), ('validator_index', uint24), ('pubkey', uint384), - ('flag', uint64) + ('slot', uint64), + ('flag', uint64), ] def __init__(self, latest_registry_delta_root: Hash32, validator_index: ValidatorIndex, pubkey: BLSPubkey, + slot: SlotNumber, flag: ValidatorRegistryDeltaFlag) -> None: super().__init__( latest_registry_delta_root=latest_registry_delta_root, validator_index=validator_index, pubkey=pubkey, + slot=slot, flag=flag, ) diff --git a/tests/beacon/conftest.py b/tests/beacon/conftest.py index 18567ba6f1..4e8a6d6357 100644 --- a/tests/beacon/conftest.py +++ b/tests/beacon/conftest.py @@ -1,6 +1,11 @@ import pytest import rlp +from eth_utils import ( + denoms, + to_tuple, +) + from eth.constants import ( ZERO_HASH32, ) @@ -21,40 +26,31 @@ ) from eth.beacon.helpers import ( - get_new_shuffling, -) - -from eth.beacon.types.proposal_signed_data import ( - ProposalSignedData, -) - -from eth.beacon.types.slashable_vote_data import ( - SlashableVoteData, + get_shuffling, ) - from eth.beacon.types.attestation_data import AttestationData from eth.beacon.types.attestations import Attestation from eth.beacon.types.states import BeaconState from eth.beacon.types.crosslink_records import CrosslinkRecord -from eth.beacon.types.deposits import DepositData +from eth.beacon.types.deposits import Deposit +from eth.beacon.types.deposit_data import DepositData from eth.beacon.types.deposit_input import DepositInput +from eth.beacon.types.proposal_signed_data import ProposalSignedData +from eth.beacon.types.slashable_vote_data import SlashableVoteData from eth.beacon.types.blocks import ( BeaconBlockBody, ) - -from eth.beacon.enums import ( - ValidatorStatusCode, -) from eth.beacon.state_machines.forks.serenity.configs import SERENITY_CONFIG -from eth.beacon.types.validator_records import ( - ValidatorRecord, -) - from eth.beacon.types.fork_data import ( ForkData, ) +from tests.beacon.helpers import ( + make_deposit_input, + mock_validator_record, + sign_proof_of_possession, +) DEFAULT_SHUFFLING_SEED = b'\00' * 32 DEFAULT_RANDAO = b'\45' * 32 @@ -214,7 +210,7 @@ def sample_deposit_input_params(): def sample_deposit_data_params(sample_deposit_input_params): return { 'deposit_input': DepositInput(**sample_deposit_input_params), - 'value': 56, + 'amount': 56, 'timestamp': 1501851927, } @@ -312,15 +308,18 @@ def sample_casper_slashing_params(sample_slashable_vote_data_params): @pytest.fixture -def sample_validator_record_params(): +def sample_validator_record_params(far_future_slot): return { 'pubkey': 123, 'withdrawal_credentials': b'\x01' * 32, 'randao_commitment': b'\x01' * 32, 'randao_layers': 1, - 'status': 1, - 'latest_status_change_slot': 0, + 'activation_slot': far_future_slot, + 'exit_slot': far_future_slot, + 'withdrawal_slot': far_future_slot, + 'penalized_slot': far_future_slot, 'exit_count': 0, + 'status_flags': 0, 'custody_commitment': ZERO_HASH32, 'latest_custody_reseed_slot': 0, 'penultimate_custody_reseed_slot': 0, @@ -333,10 +332,67 @@ def sample_validator_registry_delta_block_params(): 'latest_registry_delta_root': b'\x01' * 32, 'validator_index': 1, 'pubkey': 123, + 'slot': 0, 'flag': 1, } +@pytest.fixture +def empty_beacon_state(latest_block_roots_length, + latest_penalized_exit_length): + return BeaconState( + slot=0, + genesis_time=0, + fork_data=ForkData( + pre_fork_version=0, + post_fork_version=0, + fork_slot=0, + ), + validator_registry=(), + validator_balances=(), + validator_registry_latest_change_slot=10, + validator_registry_exit_count=10, + validator_registry_delta_chain_tip=b'\x55' * 32, + latest_randao_mixes=(), + latest_vdf_outputs=(), + shard_committees_at_slots=(), + persistent_committees=(), + persistent_committee_reassignments=(), + previous_justified_slot=0, + justified_slot=0, + justification_bitfield=0, + finalized_slot=0, + latest_crosslinks=(), + latest_block_roots=tuple(ZERO_HASH32 for _ in range(latest_block_roots_length)), + latest_penalized_exit_balances=tuple( + 0 + for _ in range(latest_penalized_exit_length) + ), + latest_attestations=(), + batched_block_roots=(), + processed_pow_receipt_root=b'\x55' * 32, + candidate_pow_receipt_roots=(), + ) + + +@pytest.fixture() +def ten_validators_state(empty_beacon_state, max_deposit, far_future_slot): + validator_count = 10 + return empty_beacon_state.copy( + validator_registry=tuple( + mock_validator_record( + pubkey=pubkey, + far_future_slot=far_future_slot, + ) + for pubkey in range(validator_count) + ), + validator_balances=tuple( + max_deposit * denoms.gwei + for _ in range(validator_count) + ) + ) + + # # Temporary default values # @@ -356,7 +412,12 @@ def num_validators(): @pytest.fixture -def init_validator_keys(pubkeys, num_validators): +def init_validator_privkeys(privkeys, num_validators): + return privkeys[:num_validators] + + +@pytest.fixture +def init_validator_pubkeys(pubkeys, num_validators): return pubkeys[:num_validators] @@ -388,11 +449,6 @@ def beacon_chain_shard_number(): return SERENITY_CONFIG.BEACON_CHAIN_SHARD_NUMBER -@pytest.fixture -def bls_withdrawal_prefix_byte(): - return SERENITY_CONFIG.BLS_WITHDRAWAL_PREFIX_BYTE - - @pytest.fixture def max_casper_votes(): return SERENITY_CONFIG.MAX_CASPER_VOTES @@ -408,6 +464,11 @@ def latest_randao_mixes_length(): return SERENITY_CONFIG.LATEST_RANDAO_MIXES_LENGTH +@pytest.fixture +def latest_penalized_exit_length(): + return SERENITY_CONFIG.LATEST_PENALIZED_EXIT_LENGTH + + @pytest.fixture def deposit_contract_address(): return SERENITY_CONFIG.DEPOSIT_CONTRACT_ADDRESS @@ -423,19 +484,29 @@ def min_deposit(): return SERENITY_CONFIG.MIN_DEPOSIT -@pytest.fixture +@pytest.fixture(scope="session") def max_deposit(): return SERENITY_CONFIG.MAX_DEPOSIT -@pytest.fixture -def initial_fork_version(): - return SERENITY_CONFIG.INITIAL_FORK_VERSION +@pytest.fixture(scope="session") +def genesis_fork_version(): + return SERENITY_CONFIG.GENESIS_FORK_VERSION + + +@pytest.fixture(scope="session") +def genesis_slot(): + return SERENITY_CONFIG.GENESIS_SLOT + + +@pytest.fixture(scope="session") +def far_future_slot(): + return SERENITY_CONFIG.FAR_FUTURE_SLOT @pytest.fixture -def initial_slot_number(): - return SERENITY_CONFIG.INITIAL_SLOT_NUMBER +def bls_withdrawal_prefix_byte(): + return SERENITY_CONFIG.BLS_WITHDRAWAL_PREFIX_BYTE @pytest.fixture @@ -454,23 +525,28 @@ def epoch_length(): @pytest.fixture -def pow_receipt_root_voting_period(): - return SERENITY_CONFIG.POW_RECEIPT_ROOT_VOTING_PERIOD +def min_validator_registry_change_interval(): + return SERENITY_CONFIG.MIN_VALIDATOR_REGISTRY_CHANGE_INTERVAL @pytest.fixture -def shard_persistent_committee_change_period(): - return SERENITY_CONFIG.SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD +def seed_lookahead(): + return SERENITY_CONFIG.SEED_LOOKAHEAD @pytest.fixture -def collective_penalty_calculation_period(): - return SERENITY_CONFIG.COLLECTIVE_PENALTY_CALCULATION_PERIOD +def entry_exit_delay(): + return SERENITY_CONFIG.ENTRY_EXIT_DELAY @pytest.fixture -def zero_balance_validator_ttl(): - return SERENITY_CONFIG.ZERO_BALANCE_VALIDATOR_TTL +def pow_receipt_root_voting_period(): + return SERENITY_CONFIG.POW_RECEIPT_ROOT_VOTING_PERIOD + + +@pytest.fixture +def min_validator_withdrawal_time(): + return SERENITY_CONFIG.MIN_VALIDATOR_WITHDRAWAL_TIME @pytest.fixture @@ -520,31 +596,85 @@ def max_exits(): # # genesis -# +@pytest.fixture(scope="session") +def initial_validator_deposits(privkeys, + pubkeys, + genesis_fork_version, + genesis_slot, + max_deposit): + withdrawal_credentials = ZERO_HASH32 + randao_commitment = ZERO_HASH32 + custody_commitment = ZERO_HASH32 + fork_data = ForkData( + pre_fork_version=genesis_fork_version, + post_fork_version=genesis_fork_version, + fork_slot=genesis_slot, + ) + domain = get_domain( + fork_data=fork_data, + slot=genesis_slot, + domain_type=SignatureDomain.DOMAIN_DEPOSIT, + ) + validator_count = 10 + + return tuple( + Deposit( + merkle_branch=( + b'\x11' * 32 + for j in range(10) + ), + merkle_tree_index=i, + deposit_data=DepositData( + deposit_input=DepositInput( + pubkey=pubkeys[i], + withdrawal_credentials=withdrawal_credentials, + randao_commitment=randao_commitment, + custody_commitment=custody_commitment, + proof_of_possession=sign_proof_of_possession( + deposit_input=make_deposit_input( + pubkey=pubkeys[i], + withdrawal_credentials=withdrawal_credentials, + randao_commitment=randao_commitment, + custody_commitment=custody_commitment, + ), + privkey=privkeys[i], + domain=domain, + ), + ), + amount=max_deposit, + timestamp=0, + ), + ) + for i in range(validator_count) + ) + + @pytest.fixture def genesis_state(sample_beacon_state_params, genesis_validators, + genesis_balances, epoch_length, target_committee_size, - initial_slot_number, + genesis_slot, shard_count, latest_block_roots_length): - initial_shuffling = get_new_shuffling( + initial_shuffling = get_shuffling( seed=ZERO_HASH32, validators=genesis_validators, crosslinking_start_shard=0, + slot=genesis_slot, epoch_length=epoch_length, target_committee_size=target_committee_size, shard_count=shard_count ) - return BeaconState(**sample_beacon_state_params).copy( validator_registry=genesis_validators, + validator_balances=genesis_balances, shard_committees_at_slots=initial_shuffling + initial_shuffling, latest_block_roots=tuple(ZERO_HASH32 for _ in range(latest_block_roots_length)), latest_crosslinks=tuple( CrosslinkRecord( - slot=initial_slot_number, + slot=genesis_slot, shard_block_root=ZERO_HASH32, ) for _ in range(shard_count) @@ -553,22 +683,36 @@ def genesis_state(sample_beacon_state_params, @pytest.fixture -def genesis_validators(init_validator_keys, +def genesis_validators(init_validator_pubkeys, init_randao, - max_deposit): + max_deposit, + far_future_slot): return tuple( - ValidatorRecord( - pubkey=pub, + mock_validator_record( + pubkey=pubkey, + far_future_slot=far_future_slot, withdrawal_credentials=ZERO_HASH32, randao_commitment=init_randao, - randao_layers=0, - status=ValidatorStatusCode.ACTIVE, - latest_status_change_slot=0, - exit_count=0, - custody_commitment=ZERO_HASH32, - latest_custody_reseed_slot=0, - penultimate_custody_reseed_slot=0, - ) for pub in init_validator_keys + status_flags=0, + ) + for pubkey in init_validator_pubkeys + ) + + +@pytest.fixture +@to_tuple +def activated_genesis_validators(genesis_validators, genesis_slot, entry_exit_delay): + for validator in genesis_validators: + yield validator.copy( + activation_slot=genesis_slot + ) + + +@pytest.fixture +def genesis_balances(init_validator_pubkeys, max_deposit): + return tuple( + max_deposit * denoms.gwei + for _ in init_validator_pubkeys ) diff --git a/tests/beacon/helpers.py b/tests/beacon/helpers.py index 1d34999dec..f092e67fa2 100644 --- a/tests/beacon/helpers.py +++ b/tests/beacon/helpers.py @@ -1,22 +1,36 @@ -from eth.beacon.enums import ( - ValidatorStatusCode, +from eth_utils import to_tuple + +from eth._utils import bls + +from eth.constants import ( + ZERO_HASH32, ) +from eth.beacon.constants import ( + EMPTY_SIGNATURE, +) +from eth.beacon.types.deposit_input import DepositInput from eth.beacon.types.validator_records import ( ValidatorRecord, ) -from eth_utils import to_tuple - -def mock_validator_record(pubkey): +def mock_validator_record(pubkey, + far_future_slot, + withdrawal_credentials=ZERO_HASH32, + randao_commitment=ZERO_HASH32, + status_flags=0, + is_active=True): return ValidatorRecord( pubkey=pubkey, - withdrawal_credentials=b'\x44' * 32, - randao_commitment=b'\x55' * 32, + withdrawal_credentials=withdrawal_credentials, + randao_commitment=randao_commitment, randao_layers=0, - status=ValidatorStatusCode.ACTIVE, - latest_status_change_slot=0, + activation_slot=0 if is_active else far_future_slot, + exit_slot=far_future_slot, + withdrawal_slot=far_future_slot, + penalized_slot=far_future_slot, exit_count=0, + status_flags=status_flags, custody_commitment=b'\x55' * 32, latest_custody_reseed_slot=0, penultimate_custody_reseed_slot=0, @@ -36,3 +50,17 @@ def get_pseudo_chain(length, genesis_block): parent_root=block.root ) yield block + + +def sign_proof_of_possession(deposit_input, privkey, domain): + return bls.sign(deposit_input.root, privkey, domain) + + +def make_deposit_input(pubkey, withdrawal_credentials, randao_commitment, custody_commitment): + return DepositInput( + pubkey=pubkey, + withdrawal_credentials=withdrawal_credentials, + randao_commitment=randao_commitment, + custody_commitment=custody_commitment, + proof_of_possession=EMPTY_SIGNATURE, + ) diff --git a/tests/beacon/state_machines/conftest.py b/tests/beacon/state_machines/conftest.py index 70d8beb64f..a9c8c84fd5 100644 --- a/tests/beacon/state_machines/conftest.py +++ b/tests/beacon/state_machines/conftest.py @@ -13,23 +13,26 @@ def config( ejection_balance, max_balance_churn_quotient, beacon_chain_shard_number, - bls_withdrawal_prefix_byte, max_casper_votes, latest_block_roots_length, latest_randao_mixes_length, + latest_penalized_exit_length, deposit_contract_address, deposit_contract_tree_depth, min_deposit, max_deposit, - initial_fork_version, - initial_slot_number, + genesis_fork_version, + genesis_slot, + far_future_slot, + bls_withdrawal_prefix_byte, slot_duration, min_attestation_inclusion_delay, epoch_length, + min_validator_registry_change_interval, + seed_lookahead, + entry_exit_delay, pow_receipt_root_voting_period, - shard_persistent_committee_change_period, - collective_penalty_calculation_period, - zero_balance_validator_ttl, + min_validator_withdrawal_time, base_reward_quotient, whistleblower_reward_quotient, includer_reward_quotient, @@ -46,23 +49,26 @@ def config( EJECTION_BALANCE=ejection_balance, MAX_BALANCE_CHURN_QUOTIENT=max_balance_churn_quotient, BEACON_CHAIN_SHARD_NUMBER=beacon_chain_shard_number, - BLS_WITHDRAWAL_PREFIX_BYTE=bls_withdrawal_prefix_byte, MAX_CASPER_VOTES=max_casper_votes, LATEST_BLOCK_ROOTS_LENGTH=latest_block_roots_length, LATEST_RANDAO_MIXES_LENGTH=latest_randao_mixes_length, + LATEST_PENALIZED_EXIT_LENGTH=latest_penalized_exit_length, DEPOSIT_CONTRACT_ADDRESS=deposit_contract_address, DEPOSIT_CONTRACT_TREE_DEPTH=deposit_contract_tree_depth, MIN_DEPOSIT=min_deposit, MAX_DEPOSIT=max_deposit, - INITIAL_FORK_VERSION=initial_fork_version, - INITIAL_SLOT_NUMBER=initial_slot_number, + GENESIS_FORK_VERSION=genesis_fork_version, + GENESIS_SLOT=genesis_slot, + FAR_FUTURE_SLOT=far_future_slot, + BLS_WITHDRAWAL_PREFIX_BYTE=bls_withdrawal_prefix_byte, SLOT_DURATION=slot_duration, MIN_ATTESTATION_INCLUSION_DELAY=min_attestation_inclusion_delay, EPOCH_LENGTH=epoch_length, + MIN_VALIDATOR_REGISTRY_CHANGE_INTERVAL=min_validator_registry_change_interval, + SEED_LOOKAHEAD=seed_lookahead, + ENTRY_EXIT_DELAY=entry_exit_delay, POW_RECEIPT_ROOT_VOTING_PERIOD=pow_receipt_root_voting_period, - SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD=shard_persistent_committee_change_period, - COLLECTIVE_PENALTY_CALCULATION_PERIOD=collective_penalty_calculation_period, - ZERO_BALANCE_VALIDATOR_TTL=zero_balance_validator_ttl, + MIN_VALIDATOR_WITHDRAWAL_TIME=min_validator_withdrawal_time, BASE_REWARD_QUOTIENT=base_reward_quotient, WHISTLEBLOWER_REWARD_QUOTIENT=whistleblower_reward_quotient, INCLUDER_REWARD_QUOTIENT=includer_reward_quotient, diff --git a/tests/beacon/state_machines/test_proposer_signature_validation.py b/tests/beacon/state_machines/test_proposer_signature_validation.py index b0afb594f5..2ed723bae1 100644 --- a/tests/beacon/state_machines/test_proposer_signature_validation.py +++ b/tests/beacon/state_machines/test_proposer_signature_validation.py @@ -1,6 +1,7 @@ import pytest from eth_utils import ( + denoms, ValidationError, ) @@ -45,13 +46,19 @@ def test_validate_serenity_proposer_signature( sample_beacon_state_params, sample_shard_committee_params, beacon_chain_shard_number, - epoch_length): + epoch_length, + max_deposit, + far_future_slot): state = BeaconState(**sample_beacon_state_params).copy( - validator_registry=[ - mock_validator_record(proposer_pubkey) + validator_registry=tuple( + mock_validator_record(proposer_pubkey, far_future_slot) for _ in range(10) - ], + ), + validator_balances=tuple( + max_deposit * denoms.gwei + for _ in range(10) + ), shard_committees_at_slots=get_sample_shard_committees_at_slots( num_slot=128, num_shard_committee_per_slot=10, diff --git a/tests/beacon/test_deposit_helpers.py b/tests/beacon/test_deposit_helpers.py index c64fe96677..456c411f4b 100644 --- a/tests/beacon/test_deposit_helpers.py +++ b/tests/beacon/test_deposit_helpers.py @@ -5,125 +5,30 @@ ValidationError, ) -from eth._utils import bls - -from eth.beacon.constants import ( - EMPTY_SIGNATURE, -) from eth.beacon.deposit_helpers import ( add_pending_validator, - get_min_empty_validator_index, process_deposit, validate_proof_of_possession, ) from eth.beacon.enums import ( SignatureDomain, ) -from eth.beacon.exceptions import ( - MinEmptyValidatorIndexNotFound, -) from eth.beacon.helpers import ( get_domain, ) from eth.beacon.types.states import BeaconState -from eth.beacon.types.deposit_input import DepositInput from eth.beacon.types.validator_records import ValidatorRecord - -def sign_proof_of_possession(deposit_input, privkey, domain): - return bls.sign(deposit_input.root, privkey, domain) - - -def make_deposit_input(pubkey, withdrawal_credentials, randao_commitment, custody_commitment): - return DepositInput( - pubkey=pubkey, - withdrawal_credentials=withdrawal_credentials, - randao_commitment=randao_commitment, - custody_commitment=custody_commitment, - proof_of_possession=EMPTY_SIGNATURE, - ) - - -@pytest.mark.parametrize( - "balance," - "latest_status_change_slot," - "zero_balance_validator_ttl," - "current_slot," - "expected", - ( - (0, 1, 1, 2, 0), - (1, 1, 1, 2, MinEmptyValidatorIndexNotFound()), # not (balance == 0) - (0, 1, 1, 1, MinEmptyValidatorIndexNotFound()), # not (validator.latest_status_change_slot + zero_balance_validator_ttl <= current_slot) # noqa: E501 - ), +from tests.beacon.helpers import ( + make_deposit_input, + sign_proof_of_possession, ) -def test_get_min_empty_validator_index(sample_validator_record_params, - balance, - latest_status_change_slot, - zero_balance_validator_ttl, - current_slot, - max_deposit, - expected): - validators = [ - ValidatorRecord(**sample_validator_record_params).copy( - latest_status_change_slot=latest_status_change_slot, - ) - for _ in range(10) - ] - validator_balances = [ - balance - for _ in range(10) - ] - if isinstance(expected, Exception): - with pytest.raises(MinEmptyValidatorIndexNotFound): - get_min_empty_validator_index( - validators=validators, - validator_balances=validator_balances, - current_slot=current_slot, - zero_balance_validator_ttl=zero_balance_validator_ttl, - ) - else: - result = get_min_empty_validator_index( - validators=validators, - validator_balances=validator_balances, - current_slot=current_slot, - zero_balance_validator_ttl=zero_balance_validator_ttl, - ) - assert result == expected -@pytest.mark.parametrize( - "validator_registry_len," - "min_empty_validator_index_result," - "expected_index", - ( - (10, 1, 1), - (10, 5, 5), - (10, None, 10), - ), -) -def test_add_pending_validator(monkeypatch, - sample_beacon_state_params, - sample_validator_record_params, - validator_registry_len, - min_empty_validator_index_result, - expected_index): - from eth.beacon import deposit_helpers - - def mock_get_min_empty_validator_index(validators, - validator_balances, - current_slot, - zero_balance_validator_ttl): - if min_empty_validator_index_result is None: - raise MinEmptyValidatorIndexNotFound() - else: - return min_empty_validator_index_result - - monkeypatch.setattr( - deposit_helpers, - 'get_min_empty_validator_index', - mock_get_min_empty_validator_index - ) +def test_add_pending_validator(sample_beacon_state_params, + sample_validator_record_params): + validator_registry_len = 2 state = BeaconState(**sample_beacon_state_params).copy( validator_registry=[ ValidatorRecord(**sample_validator_record_params) @@ -135,14 +40,13 @@ def mock_get_min_empty_validator_index(validators, ], ) validator = ValidatorRecord(**sample_validator_record_params) - deposit = 5566 + amount = 5566 state, index = add_pending_validator( state, validator, - deposit, - zero_balance_validator_ttl=0, # it's for `get_min_empty_validator_index` + amount, ) - assert index == expected_index + assert index == validator_registry_len assert state.validator_registry[index] == validator @@ -198,17 +102,17 @@ def test_validate_proof_of_possession(sample_beacon_state_params, pubkeys, privk def test_process_deposit(sample_beacon_state_params, - zero_balance_validator_ttl, privkeys, - pubkeys): + pubkeys, + far_future_slot): state = BeaconState(**sample_beacon_state_params).copy( - slot=zero_balance_validator_ttl + 1, + slot=1, validator_registry=(), ) privkey_1 = privkeys[0] pubkey_1 = pubkeys[0] - deposit = 32 * denoms.gwei + amount = 32 * denoms.gwei withdrawal_credentials = b'\x34' * 32 custody_commitment = b'\x11' * 32 randao_commitment = b'\x56' * 32 @@ -226,15 +130,15 @@ def test_process_deposit(sample_beacon_state_params, ) proof_of_possession = sign_proof_of_possession(deposit_input, privkey_1, domain) # Add the first validator - result_state, index = process_deposit( + result_state = process_deposit( state=state, pubkey=pubkey_1, - deposit=deposit, + amount=amount, proof_of_possession=proof_of_possession, withdrawal_credentials=withdrawal_credentials, randao_commitment=randao_commitment, custody_commitment=custody_commitment, - zero_balance_validator_ttl=zero_balance_validator_ttl, + far_future_slot=far_future_slot, ) assert len(result_state.validator_registry) == 1 @@ -242,7 +146,7 @@ def test_process_deposit(sample_beacon_state_params, assert result_state.validator_registry[0].pubkey == pubkey_1 assert result_state.validator_registry[index].withdrawal_credentials == withdrawal_credentials assert result_state.validator_registry[index].randao_commitment == randao_commitment - assert result_state.validator_balances[index] == deposit + assert result_state.validator_balances[index] == amount # test immutable assert len(state.validator_registry) == 0 @@ -256,51 +160,15 @@ def test_process_deposit(sample_beacon_state_params, custody_commitment=custody_commitment, ) proof_of_possession = sign_proof_of_possession(deposit_input, privkey_2, domain) - result_state, index = process_deposit( + result_state = process_deposit( state=result_state, pubkey=pubkey_2, - deposit=deposit, + amount=amount, proof_of_possession=proof_of_possession, withdrawal_credentials=withdrawal_credentials, randao_commitment=randao_commitment, custody_commitment=custody_commitment, - zero_balance_validator_ttl=zero_balance_validator_ttl, + far_future_slot=far_future_slot, ) assert len(result_state.validator_registry) == 2 assert result_state.validator_registry[1].pubkey == pubkey_2 - - # Force the first validator exited -> a empty slot in state.validator_registry. - result_state = result_state.copy( - validator_registry=( - result_state.validator_registry[0].copy( - latest_status_change_slot=0, - ), - result_state.validator_registry[1], - ), - validator_balances=(0,) + result_state.validator_balances[1:] - ) - - # Add the third validator. - # Should overwrite previously exited validator. - privkey_3 = privkeys[2] - pubkey_3 = pubkeys[2] - deposit_input = make_deposit_input( - pubkey=pubkey_3, - withdrawal_credentials=withdrawal_credentials, - randao_commitment=randao_commitment, - custody_commitment=custody_commitment, - ) - proof_of_possession = sign_proof_of_possession(deposit_input, privkey_3, domain) - result_state, index = process_deposit( - state=result_state, - pubkey=pubkey_3, - deposit=deposit, - proof_of_possession=proof_of_possession, - withdrawal_credentials=withdrawal_credentials, - randao_commitment=randao_commitment, - custody_commitment=custody_commitment, - zero_balance_validator_ttl=zero_balance_validator_ttl, - ) - # Overwrite the second validator. - assert len(result_state.validator_registry) == 2 - assert result_state.validator_registry[0].pubkey == pubkey_3 diff --git a/tests/beacon/test_helpers.py b/tests/beacon/test_helpers.py index b7e218b781..d7995aa120 100644 --- a/tests/beacon/test_helpers.py +++ b/tests/beacon/test_helpers.py @@ -16,10 +16,7 @@ from eth.constants import ( ZERO_HASH32, ) - - from eth.beacon.enums import ( - ValidatorStatusCode, SignatureDomain, ) from eth.beacon.state_machines.forks.serenity.blocks import ( @@ -42,8 +39,7 @@ get_effective_balance, get_domain, get_fork_version, - get_new_shuffling, - get_new_validator_registry_delta_chain_tip, + get_shuffling, get_block_committees_info, get_pubkey_for_indices, generate_aggregate_pubkeys, @@ -263,16 +259,18 @@ def test_get_shard_committees_at_slot( (1000, 20, 10, 100), (100, 50, 10, 10), (20, 10, 3, 10), # active_validators_size < epoch_length * target_committee_size + # TODO: other slot cases ], ) -def test_get_new_shuffling_is_complete(genesis_validators, - epoch_length, - target_committee_size, - shard_count): - shuffling = get_new_shuffling( +def test_get_shuffling_is_complete(activated_genesis_validators, + epoch_length, + target_committee_size, + shard_count): + shuffling = get_shuffling( seed=b'\x35' * 32, - validators=genesis_validators, + validators=activated_genesis_validators, crosslinking_start_shard=0, + slot=0, epoch_length=epoch_length, target_committee_size=target_committee_size, shard_count=shard_count, @@ -287,7 +285,7 @@ def test_get_new_shuffling_is_complete(genesis_validators, for validator_index in shard_committee.committee: validators.add(validator_index) - assert len(validators) == len(genesis_validators) + assert len(validators) == len(activated_genesis_validators) @pytest.mark.parametrize( @@ -303,14 +301,15 @@ def test_get_new_shuffling_is_complete(genesis_validators, (20, 10, 3, 10), ], ) -def test_get_new_shuffling_handles_shard_wrap(genesis_validators, - epoch_length, - target_committee_size, - shard_count): - shuffling = get_new_shuffling( +def test_get_shuffling_handles_shard_wrap(genesis_validators, + epoch_length, + target_committee_size, + shard_count): + shuffling = get_shuffling( seed=b'\x35' * 32, validators=genesis_validators, crosslinking_start_shard=shard_count - 1, + slot=0, epoch_length=epoch_length, target_committee_size=target_committee_size, shard_count=shard_count, @@ -455,31 +454,25 @@ def mock_get_shard_committees_at_slot(state, ) -def test_get_active_validator_indices(sample_validator_record_params): +def test_get_active_validator_indices(sample_validator_record_params, far_future_slot): + current_slot = 1 # 3 validators are ACTIVE validators = [ ValidatorRecord( **sample_validator_record_params, ).copy( - status=ValidatorStatusCode.ACTIVE, + activation_slot=0, + exit_slot=far_future_slot, ) for i in range(3) ] - active_validator_indices = get_active_validator_indices(validators) - assert len(active_validator_indices) == 3 - - # Make one validator becomes ACTIVE_PENDING_EXIT. - validators[0] = validators[0].copy( - status=ValidatorStatusCode.ACTIVE_PENDING_EXIT, - ) - active_validator_indices = get_active_validator_indices(validators) + active_validator_indices = get_active_validator_indices(validators, current_slot) assert len(active_validator_indices) == 3 - # Make one validator becomes EXITED_WITHOUT_PENALTY. validators[0] = validators[0].copy( - status=ValidatorStatusCode.EXITED_WITHOUT_PENALTY, + activation_slot=2, # activation_slot < current_slot ) - active_validator_indices = get_active_validator_indices(validators) + active_validator_indices = get_active_validator_indices(validators, current_slot) assert len(active_validator_indices) == 2 @@ -600,35 +593,6 @@ def test_get_effective_balance(balance, max_deposit, expected, sample_validator_ assert result == expected -@pytest.mark.parametrize( - ( - 'validator_index,' - 'pubkey,' - 'flag,' - 'expected' - ), - [ - ( - 1, - 2 * 256 - 1, - 1, - b'\xb8K\xad[zDE\xef\x00Z\x9c\x04\xdc\x95\xff\x9c\xeaP\x15\xf5\xfb\xdd\x0f\x1c:\xd7U+\x81\x92:\xee' # noqa: E501 - ), - ] -) -def test_get_new_validator_registry_delta_chain_tip(validator_index, - pubkey, - flag, - expected): - result = get_new_validator_registry_delta_chain_tip( - current_validator_registry_delta_chain_tip=ZERO_HASH32, - validator_index=validator_index, - pubkey=pubkey, - flag=flag, - ) - assert result == expected - - @pytest.mark.parametrize( ( 'pre_fork_version,' @@ -849,9 +813,12 @@ def _create_slashable_vote_data_messages(params): def test_verify_slashable_vote_data_signature(privkeys, sample_beacon_state_params, genesis_validators, + genesis_balances, sample_slashable_vote_data_params): - sample_beacon_state_params["validator_registry"] = genesis_validators - state = BeaconState(**sample_beacon_state_params) + state = BeaconState(**sample_beacon_state_params).copy( + validator_registry=genesis_validators, + validator_balances=genesis_balances, + ) # NOTE: we can do this before "correcting" the params as they # touch disjoint subsets of the provided params @@ -897,10 +864,13 @@ def test_verify_slashable_vote_data(param_mapper, privkeys, sample_beacon_state_params, genesis_validators, + genesis_balances, sample_slashable_vote_data_params, max_casper_votes): - sample_beacon_state_params["validator_registry"] = genesis_validators - state = BeaconState(**sample_beacon_state_params) + state = BeaconState(**sample_beacon_state_params).copy( + validator_registry=genesis_validators, + validator_balances=genesis_balances, + ) # NOTE: we can do this before "correcting" the params as they # touch disjoint subsets of the provided params diff --git a/tests/beacon/types/test_deposit_data.py b/tests/beacon/types/test_deposit_data.py index 011c66af26..fcc30f1aa2 100644 --- a/tests/beacon/types/test_deposit_data.py +++ b/tests/beacon/types/test_deposit_data.py @@ -5,5 +5,5 @@ def test_defaults(sample_deposit_data_params): deposit_data = DepositData(**sample_deposit_data_params) assert deposit_data.deposit_input.pubkey == sample_deposit_data_params['deposit_input'].pubkey - assert deposit_data.value == sample_deposit_data_params['value'] + assert deposit_data.amount == sample_deposit_data_params['amount'] assert deposit_data.timestamp == sample_deposit_data_params['timestamp'] diff --git a/tests/beacon/types/test_states.py b/tests/beacon/types/test_states.py index 6700fcf821..56d605aab9 100644 --- a/tests/beacon/types/test_states.py +++ b/tests/beacon/types/test_states.py @@ -2,9 +2,10 @@ import rlp -from eth.beacon.types.fork_data import ( - ForkData, +from eth_utils import ( + denoms, ) + from eth.beacon.types.states import ( BeaconState, ) @@ -20,60 +21,40 @@ ) -@pytest.fixture -def empty_beacon_state(): - return BeaconState( - slot=0, - genesis_time=0, - fork_data=ForkData( - pre_fork_version=0, - post_fork_version=0, - fork_slot=0, - ), - validator_registry=(), - validator_registry_latest_change_slot=10, - validator_registry_exit_count=10, - validator_registry_delta_chain_tip=b'\x55' * 32, - latest_randao_mixes=(), - latest_vdf_outputs=(), - shard_committees_at_slots=(), - persistent_committees=(), - persistent_committee_reassignments=(), - previous_justified_slot=0, - justified_slot=0, - justification_bitfield=0, - finalized_slot=0, - latest_crosslinks=(), - latest_block_roots=(), - latest_penalized_exit_balances=(), - latest_attestations=(), - batched_block_roots=(), - processed_pow_receipt_root=b'\x55' * 32, - candidate_pow_receipt_roots=(), - ) - - def test_defaults(sample_beacon_state_params): state = BeaconState(**sample_beacon_state_params) assert state.validator_registry == sample_beacon_state_params['validator_registry'] assert state.validator_registry_latest_change_slot == sample_beacon_state_params['validator_registry_latest_change_slot'] # noqa: E501 +def test_validator_registry_and_balances_length(sample_beacon_state_params, far_future_slot): + # When len(BeaconState.validator_registry) != len(BeaconState.validtor_balances) + with pytest.raises(ValueError): + BeaconState(**sample_beacon_state_params).copy( + validator_registry=tuple( + mock_validator_record(pubkey, far_future_slot) + for pubkey in range(10) + ), + ) + + @pytest.mark.parametrize( 'expected', [(0), (1)] ) def test_num_validators(expected, max_deposit, - empty_beacon_state): + empty_beacon_state, + far_future_slot): state = empty_beacon_state.copy( - validator_registry=[ + validator_registry=tuple( mock_validator_record( pubkey, + far_future_slot, ) for pubkey in range(expected) - ], - validator_balances=( - max_deposit + ), + validator_balances=tuple( + max_deposit * denoms.gwei for _ in range(expected) ) ) @@ -103,31 +84,34 @@ def test_hash(sample_beacon_state_params): assert state.root == hash_eth2(rlp.encode(state)) -def test_update_validator(sample_beacon_state_params, sample_validator_record_params, max_deposit): - state = BeaconState(**sample_beacon_state_params).copy( - validator_registry=[ - mock_validator_record( - pubkey, - ) - for pubkey in range(10) - ], - validator_balances=( - max_deposit - for _ in range(10) +@pytest.mark.parametrize( + 'validator_index, new_pubkey, new_balance', + [ + (0, 5566, 100), + (100, 5566, 100), + ] +) +def test_update_validator(ten_validators_state, + validator_index, + new_pubkey, + new_balance, + far_future_slot): + state = ten_validators_state + validator = mock_validator_record(new_pubkey, far_future_slot) + + if validator_index < state.num_validators: + result_state = state.update_validator( + validator_index=validator_index, + validator=validator, + balance=new_balance, ) - ) - - new_pubkey = 100 - validator_index = 5 - balance = 5566 - validator = state.validator_registry[validator_index].copy( - pubkey=new_pubkey, - ) - result_state = state.update_validator( - validator_index=validator_index, - validator=validator, - balance=balance, - ) - assert result_state.validator_balances[validator_index] == balance - assert result_state.validator_registry[validator_index].pubkey == new_pubkey - assert state.validator_registry[validator_index].pubkey != new_pubkey + assert result_state.validator_balances[validator_index] == new_balance + assert result_state.validator_registry[validator_index].pubkey == new_pubkey + assert state.validator_registry[validator_index].pubkey != new_pubkey + else: + with pytest.raises(IndexError): + state.update_validator( + validator_index=validator_index, + validator=validator, + balance=new_balance, + ) diff --git a/tests/beacon/types/test_validator_record.py b/tests/beacon/types/test_validator_record.py index 11c2ced20c..039d00c0c0 100644 --- a/tests/beacon/types/test_validator_record.py +++ b/tests/beacon/types/test_validator_record.py @@ -1,8 +1,5 @@ import pytest -from eth.beacon.enums import ( - ValidatorStatusCode, -) from eth.beacon.types.validator_records import ( ValidatorRecord, ) @@ -15,46 +12,49 @@ def test_defaults(sample_validator_record_params): @pytest.mark.parametrize( - 'status,expected', + 'activation_slot,exit_slot,slot,expected', [ - (ValidatorStatusCode.PENDING_ACTIVATION, False), - (ValidatorStatusCode.ACTIVE, True), - (ValidatorStatusCode.ACTIVE_PENDING_EXIT, True), - (ValidatorStatusCode.EXITED_WITHOUT_PENALTY, False), - (ValidatorStatusCode.EXITED_WITH_PENALTY, False), + (0, 1, 0, True), + (1, 1, 1, False), + (0, 1, 1, False), + (0, 1, 2, False), ], ) def test_is_active(sample_validator_record_params, - status, + activation_slot, + exit_slot, + slot, expected): validator_record_params = { **sample_validator_record_params, - 'status': status + 'activation_slot': activation_slot, + 'exit_slot': exit_slot, } validator = ValidatorRecord(**validator_record_params) - assert validator.is_active == expected + assert validator.is_active(slot) == expected def test_get_pending_validator(): pubkey = 123 withdrawal_credentials = b'\x11' * 32 randao_commitment = b'\x22' * 32 - latest_status_change_slot = 10 custody_commitment = b'\x33' * 32 + far_future_slot = 1000 validator = ValidatorRecord.get_pending_validator( pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, randao_commitment=randao_commitment, - latest_status_change_slot=latest_status_change_slot, custody_commitment=custody_commitment, + far_future_slot=far_future_slot, ) assert validator.pubkey == pubkey assert validator.withdrawal_credentials == withdrawal_credentials assert validator.randao_commitment == randao_commitment - assert validator.latest_status_change_slot == latest_status_change_slot - - assert validator.status == ValidatorStatusCode.PENDING_ACTIVATION assert validator.randao_layers == 0 + assert validator.activation_slot == far_future_slot + assert validator.exit_slot == far_future_slot + assert validator.withdrawal_slot == far_future_slot + assert validator.penalized_slot == far_future_slot assert validator.exit_count == 0 diff --git a/tests/beacon/types/test_validator_registry_delta_block.py b/tests/beacon/types/test_validator_registry_delta_block.py index cd5c1c9f91..67849a5e59 100644 --- a/tests/beacon/types/test_validator_registry_delta_block.py +++ b/tests/beacon/types/test_validator_registry_delta_block.py @@ -10,4 +10,5 @@ def test_defaults(sample_validator_registry_delta_block_params): assert validator_registry_delta_block.latest_registry_delta_root == sample_validator_registry_delta_block_params['latest_registry_delta_root'] # noqa: E501 assert validator_registry_delta_block.validator_index == sample_validator_registry_delta_block_params['validator_index'] # noqa: E501 assert validator_registry_delta_block.pubkey == sample_validator_registry_delta_block_params['pubkey'] # noqa: E501 + assert validator_registry_delta_block.slot == sample_validator_registry_delta_block_params['slot'] # noqa: E501 assert validator_registry_delta_block.flag == sample_validator_registry_delta_block_params['flag'] # noqa: E501