def test_process_voluntary_exits( genesis_state, sample_beacon_block_params, sample_beacon_block_body_params, config, keymap, success, ): state = genesis_state.set( "slot", compute_start_slot_at_epoch( config.GENESIS_EPOCH + config.PERSISTENT_COMMITTEE_PERIOD, config.SLOTS_PER_EPOCH, ), ) validator_index = 0 validator = state.validators[validator_index].set("activation_epoch", config.GENESIS_EPOCH) state = state.transform(["validators", validator_index], validator) valid_voluntary_exit = create_mock_voluntary_exit(state, config, keymap, validator_index) if success: block_body = BeaconBlockBody.create( **sample_beacon_block_body_params).set("voluntary_exits", (valid_voluntary_exit, )) block = SerenityBeaconBlock.create(**sample_beacon_block_params).mset( "slot", state.slot, "body", block_body) new_state = process_voluntary_exits(state, block, config) updated_validator = new_state.validators[validator_index] assert updated_validator.exit_epoch != FAR_FUTURE_EPOCH assert updated_validator.exit_epoch > state.current_epoch( config.SLOTS_PER_EPOCH) assert updated_validator.withdrawable_epoch == ( updated_validator.exit_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY) else: invalid_voluntary_exit = valid_voluntary_exit.set( "signature", b"\x12" * 96 # Put wrong signature ) block_body = BeaconBlockBody.create( **sample_beacon_block_body_params).set("voluntary_exits", (invalid_voluntary_exit, )) block = SerenityBeaconBlock.create(**sample_beacon_block_params).mset( "slot", state.slot, "body", block_body) with pytest.raises(ValidationError): process_voluntary_exits(state, block, config)
def test_process_attester_slashings( genesis_state, sample_beacon_block_params, sample_beacon_block_body_params, config, keymap, min_attestation_inclusion_delay, success, ): attesting_state = genesis_state.mset( "slot", genesis_state.slot + config.SLOTS_PER_EPOCH, "block_roots", tuple( i.to_bytes(32, "little") for i in range(config.SLOTS_PER_HISTORICAL_ROOT)), ) valid_attester_slashing = create_mock_attester_slashing_is_double_vote( attesting_state, config, keymap, attestation_epoch=0) state = attesting_state.set( "slot", attesting_state.slot + min_attestation_inclusion_delay) if success: block_body = BeaconBlockBody.create( **sample_beacon_block_body_params).set("attester_slashings", (valid_attester_slashing, )) block = SerenityBeaconBlock.create(**sample_beacon_block_params).mset( "slot", state.slot, "body", block_body) attester_index = valid_attester_slashing.attestation_1.attesting_indices[ 0] new_state = process_attester_slashings(state, block, config) # Check if slashed assert not state.validators[attester_index].slashed assert new_state.validators[attester_index].slashed else: invalid_attester_slashing = valid_attester_slashing.transform( ["attestation_2", "data"], valid_attester_slashing.attestation_1.data) block_body = BeaconBlockBody.create( **sample_beacon_block_body_params).set( "attester_slashings", (invalid_attester_slashing, )) block = SerenityBeaconBlock.create(**sample_beacon_block_params).mset( "slot", state.slot, "body", block_body) with pytest.raises(ValidationError): process_attester_slashings(state, block, config)
def test_randao_processing_validates_randao_reveal( sample_beacon_block_params, sample_beacon_block_body_params, sample_beacon_state_params, sample_fork_params, keymap, config, ): proposer_pubkey, proposer_privkey = first(keymap.items()) state = SerenityBeaconState.create(**sample_beacon_state_params).mset( "validators", tuple( create_mock_validator(proposer_pubkey, config) for _ in range(config.TARGET_COMMITTEE_SIZE)), "balances", (config.MAX_EFFECTIVE_BALANCE, ) * config.TARGET_COMMITTEE_SIZE, "randao_mixes", tuple(ZERO_HASH32 for _ in range(config.EPOCHS_PER_HISTORICAL_VECTOR)), ) epoch = state.current_epoch(config.SLOTS_PER_EPOCH) message_hash = (epoch + 1).to_bytes(32, byteorder="little") domain = get_domain(state, SignatureDomain.DOMAIN_RANDAO, config.SLOTS_PER_EPOCH) randao_reveal = bls.sign(message_hash, proposer_privkey, domain) block_body = BeaconBlockBody.create(**sample_beacon_block_body_params).set( "randao_reveal", randao_reveal) block = SerenityBeaconBlock.create(**sample_beacon_block_params).set( "body", block_body) with pytest.raises(ValidationError): process_randao(state, block, config)
def test_ensure_update_eth1_vote_if_exists(genesis_state, config, vote_offsets): # one less than a majority is the majority divided by 2 threshold = config.SLOTS_PER_ETH1_VOTING_PERIOD // 2 data_votes = tuple( concat((Eth1Data.create(block_hash=(i).to_bytes(32, "little")), ) * (threshold + offset) for i, offset in enumerate(vote_offsets))) state = genesis_state for vote in data_votes: state = process_eth1_data( state, BeaconBlock.create(body=BeaconBlockBody.create(eth1_data=vote)), config, ) if not vote_offsets: assert state.eth1_data == genesis_state.eth1_data # we should update the 'latest' entry if we have a majority for offset in vote_offsets: if offset <= 0: assert genesis_state.eth1_data == state.eth1_data else: assert state.eth1_data == data_votes[0]
def test_process_eth1_data( original_votes, block_data, expected_votes, sample_beacon_state_params, sample_beacon_block_params, sample_beacon_block_body_params, config, ): eth1_data_votes = tuple(mapcat(_expand_eth1_votes, original_votes)) state = BeaconState.create(**sample_beacon_state_params).set( "eth1_data_votes", eth1_data_votes) block_body = BeaconBlockBody.create( **sample_beacon_block_body_params).mset( "eth1_data", Eth1Data.create(block_hash=block_data)) block = BeaconBlock.create(**sample_beacon_block_params).set( "body", block_body) updated_state = process_eth1_data(state, block, config) updated_votes = updated_state.eth1_data_votes expanded_expected_votes = tuple(mapcat(_expand_eth1_votes, expected_votes)) assert tuple(updated_votes) == expanded_expected_votes
def sample_beacon_block_params(sample_beacon_block_body_params): return { "slot": GENESIS_SLOT + 10, "parent_root": ZERO_HASH32, "state_root": b"\x55" * 32, "body": BeaconBlockBody.create(**sample_beacon_block_body_params), }
def sample_beacon_block_params(sample_beacon_block_body_params, genesis_slot): return { "slot": genesis_slot + 10, "parent_root": ZERO_HASH32, "state_root": b"\x55" * 32, "body": BeaconBlockBody.create(**sample_beacon_block_body_params), }
def create_block_proposal( slot: Slot, parent_root: Root, randao_reveal: BLSSignature, eth1_data: Eth1Data, attestations: Sequence[Attestation], state: BeaconState, state_machine: BaseBeaconStateMachine, ) -> BeaconBlock: config = state_machine.config state_at_slot, _ = state_machine.apply_state_transition(state, future_slot=slot) proposer_index = get_beacon_proposer_index(state_at_slot, config) block_body = BeaconBlockBody.create(randao_reveal=randao_reveal, eth1_data=eth1_data, attestations=attestations) proposal = BeaconBlock.create( slot=slot, parent_root=parent_root, body=block_body, proposer_index=proposer_index, ) block_with_empty_signature = SignedBeaconBlock.create( message=proposal, signature=EMPTY_SIGNATURE) post_state, block_with_state_root = state_machine.apply_state_transition( state, block_with_empty_signature, check_proposer_signature=False) return block_with_state_root.message
def create_unsigned_block_on_state( *, state: BeaconState, config: Eth2Config, block_class: Type[BaseBeaconBlock], parent_block: BaseBeaconBlock, slot: Slot, attestations: Sequence[Attestation], eth1_data: Eth1Data = None, deposits: Sequence[Deposit] = None, check_proposer_index: bool = True, ) -> BeaconBlock: """ Create a beacon block with the given parameters. """ block = block_class.from_parent(parent_block=parent_block, block_params=FromBlockParams(slot=slot)) # MAX_ATTESTATIONS attestations = attestations[:config.MAX_ATTESTATIONS] # TODO: Add more operations if eth1_data is None: eth1_data = state.eth1_data body = BeaconBlockBody.create(eth1_data=eth1_data, attestations=attestations) if deposits is not None and len(deposits) > 0: body = body.set("deposits", deposits) block = block.set("body", body) return block
def run_with( cls, inputs: Tuple[BeaconState, OperationOrBlockHeader], config: Optional[Eth2Config], ) -> BeaconState: state, operation = inputs # NOTE: we do not have an easy way to evaluate a single operation on the state # So, we wrap it in a beacon block. The following statement lets us rely on # the config given in a particular handler class while working w/in the # update API provided by `py-ssz`. # NOTE: we ignore the type here, otherwise need to spell out each of the keyword # arguments individually... save some work and just build them dynamically block = BeaconBlock.create(body=BeaconBlockBody.create( **{f"{cls.name}s": (operation, )} # type: ignore )) try: return cls.processor(state, block, config) except ValidationError as e: # if already a ValidationError, re-raise raise e except Exception as e: # check if the exception is expected... for exception in cls.expected_exceptions: if isinstance(e, exception): raise ValidationError() from e # else raise (and fail the pytest test case ...) raise e
async def _get_block_proposal(self, request: web.Request) -> web.Response: slot = Slot(int(request.query["slot"])) randao_reveal = BLSSignature( decode_hex(request.query["randao_reveal"]).ljust(96, b"\x00")) block = BeaconBlock.create( slot=slot, body=BeaconBlockBody.create(randao_reveal=randao_reveal)) return web.json_response(to_formatted_dict(block))
def genesis_state(eth2_config, genesis_time, genesis_validators): balances = tuple(validator.effective_balance for validator in genesis_validators) state = BeaconState.create( latest_block_header=BeaconBlockHeader.create( body_root=BeaconBlockBody.create().hash_tree_root ), validators=genesis_validators, balances=balances, config=eth2_config, ) state.genesis_time = genesis_time return state
def initialize_beacon_state_from_eth1(*, eth1_block_hash: Hash32, eth1_timestamp: Timestamp, deposits: Sequence[Deposit], config: Eth2Config) -> BeaconState: fork = Fork.create( previous_version=config.GENESIS_FORK_VERSION, current_version=config.GENESIS_FORK_VERSION, epoch=GENESIS_EPOCH, ) state = BeaconState.create( genesis_time=_genesis_time_from_eth1_timestamp(eth1_timestamp, config.GENESIS_DELAY), fork=fork, eth1_data=Eth1Data.create(block_hash=eth1_block_hash, deposit_count=len(deposits)), latest_block_header=BeaconBlockHeader.create( body_root=BeaconBlockBody.create().hash_tree_root), block_roots=(ZERO_ROOT, ) * config.SLOTS_PER_HISTORICAL_ROOT, state_roots=(ZERO_HASH32, ) * config.SLOTS_PER_HISTORICAL_ROOT, randao_mixes=(eth1_block_hash, ) * config.EPOCHS_PER_HISTORICAL_VECTOR, slashings=(Gwei(0), ) * config.EPOCHS_PER_SLASHINGS_VECTOR, config=config, ) # Process genesis deposits for index, deposit in enumerate(deposits): deposit_data_list = tuple(deposit.data for deposit in deposits[:index + 1]) deposit_root = ssz.get_hash_tree_root( deposit_data_list, ssz.List(DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH)) state = state.transform(("eth1_data", "deposit_root"), deposit_root) state = process_deposit(state=state, deposit=deposit, config=config) # Process genesis activations for validator_index in range(len(state.validators)): validator_index = ValidatorIndex(validator_index) balance = state.balances[validator_index] effective_balance = calculate_effective_balance(balance, config) state = state.transform( ("validators", validator_index, "effective_balance"), effective_balance) if effective_balance == config.MAX_EFFECTIVE_BALANCE: activated_validator = activate_validator( state.validators[validator_index], GENESIS_EPOCH) state = state.transform(("validators", validator_index), activated_validator) return state.set("genesis_validators_root", state.validators.hash_tree_root)
def test_block_body_empty(sample_attestation_params): block_body = BeaconBlockBody.create() assert tuple(block_body.proposer_slashings) == () assert tuple(block_body.attester_slashings) == () assert tuple(block_body.attestations) == () assert tuple(block_body.deposits) == () assert tuple(block_body.voluntary_exits) == () assert block_body.is_empty block_body = block_body.set( "attestations", (Attestation.create(**sample_attestation_params), )) assert not block_body.is_empty
def create_block_on_state( *, state: BeaconState, config: Eth2Config, state_machine: BaseBeaconStateMachine, block_class: Type[BaseBeaconBlock], parent_block: BaseBeaconBlock, slot: Slot, validator_index: ValidatorIndex, privkey: int, attestations: Sequence[Attestation], check_proposer_index: bool = True) -> BaseBeaconBlock: """ Create a beacon block with the given parameters. """ if check_proposer_index: validate_proposer_index(state, config, slot, validator_index) block = block_class.from_parent(parent_block=parent_block, block_params=FromBlockParams(slot=slot)) # MAX_ATTESTATIONS attestations = attestations[:config.MAX_ATTESTATIONS] # TODO: Add more operations randao_reveal = _generate_randao_reveal(privkey, slot, state, config) eth1_data = state.eth1_data body = BeaconBlockBody.create(randao_reveal=randao_reveal, eth1_data=eth1_data, attestations=attestations) block = block.set("body", body) # Apply state transition to get state root state, block = state_machine.import_block(block, state, check_proposer_signature=False) # Sign signature = sign_transaction( message_hash=block.signing_root, privkey=privkey, state=state, slot=slot, signature_domain=SignatureDomain.DOMAIN_BEACON_PROPOSER, slots_per_epoch=config.SLOTS_PER_EPOCH, ) block = block.set("signature", signature) return block
def test_process_attestations( genesis_state, genesis_block, sample_beacon_block_params, sample_beacon_block_body_params, config, keymap, fixture_sm_class, chaindb, genesis_fork_choice_context, success, ): attestation_slot = 0 current_slot = attestation_slot + config.MIN_ATTESTATION_INCLUSION_DELAY state = genesis_state.set("slot", current_slot) attestations = create_mock_signed_attestations_at_slot( state=state, config=config, state_machine=fixture_sm_class(chaindb, genesis_fork_choice_context), attestation_slot=attestation_slot, beacon_block_root=genesis_block.message.hash_tree_root, keymap=keymap, voted_attesters_ratio=1.0, ) assert len(attestations) > 0 if not success: # create invalid attestation # i.e. wrong slot invalid_attestation_data = attestations[-1].data.set( "slot", state.slot + 1) invalid_attestation = attestations[-1].set("data", invalid_attestation_data) attestations = attestations[:-1] + (invalid_attestation, ) block_body = BeaconBlockBody.create(**sample_beacon_block_body_params).set( "attestations", attestations) block = SerenityBeaconBlock.create(**sample_beacon_block_params).mset( "slot", current_slot, "body", block_body) if success: new_state = process_attestations(state, block, config) assert len(new_state.current_epoch_attestations) == len(attestations) else: with pytest.raises(ValidationError): process_attestations(state, block, config)
def create_block_proposal( slot: Slot, parent_root: Root, randao_reveal: BLSSignature, eth1_data: Eth1Data, state: BeaconState, state_machine: BaseBeaconStateMachine, ) -> BeaconBlock: proposal = BeaconBlock.create( slot=slot, parent_root=parent_root, body=BeaconBlockBody.create(randao_reveal=randao_reveal, eth1_data=eth1_data), ) signed_block = SignedBeaconBlock.create(message=proposal, signature=EMPTY_SIGNATURE) post_state, signed_block = state_machine.import_block( signed_block, state, check_proposer_signature=False ) return signed_block.message
def test_randao_processing( sample_beacon_block_params, sample_beacon_block_body_params, sample_beacon_state_params, keymap, config, ): proposer_pubkey, proposer_privkey = first(keymap.items()) state = SerenityBeaconState.create(**sample_beacon_state_params).mset( "validators", tuple( create_mock_validator(proposer_pubkey, config) for _ in range(config.TARGET_COMMITTEE_SIZE)), "balances", (config.MAX_EFFECTIVE_BALANCE, ) * config.TARGET_COMMITTEE_SIZE, "randao_mixes", tuple(ZERO_HASH32 for _ in range(config.EPOCHS_PER_HISTORICAL_VECTOR)), ) epoch = state.current_epoch(config.SLOTS_PER_EPOCH) slot = compute_start_slot_at_epoch(epoch, config.SLOTS_PER_EPOCH) randao_reveal = _generate_randao_reveal(privkey=proposer_privkey, slot=slot, state=state, config=config) block_body = BeaconBlockBody.create(**sample_beacon_block_body_params).set( "randao_reveal", randao_reveal) block = SerenityBeaconBlock.create(**sample_beacon_block_params).set( "body", block_body) new_state = process_randao(state, block, config) updated_index = epoch % config.EPOCHS_PER_HISTORICAL_VECTOR original_mixes = state.randao_mixes updated_mixes = new_state.randao_mixes assert all( updated == original if index != updated_index else updated != original for index, (updated, original) in enumerate(zip(updated_mixes, original_mixes)))
def test_process_proposer_slashings( genesis_state, sample_beacon_block_params, sample_beacon_block_body_params, config, keymap, block_root_1, block_root_2, success, ): current_slot = config.GENESIS_SLOT + 1 state = genesis_state.set("slot", current_slot) whistleblower_index = get_beacon_proposer_index(state, CommitteeConfig(config)) slashing_proposer_index = (whistleblower_index + 1) % len(state.validators) proposer_slashing = create_mock_proposer_slashing_at_block( state, config, keymap, block_root_1=block_root_1, block_root_2=block_root_2, proposer_index=slashing_proposer_index, ) proposer_slashings = (proposer_slashing, ) block_body = BeaconBlockBody.create(**sample_beacon_block_body_params).set( "proposer_slashings", proposer_slashings) block = SerenityBeaconBlock.create(**sample_beacon_block_params).mset( "slot", current_slot, "body", block_body) if success: new_state = process_proposer_slashings(state, block, config) # Check if slashed assert (new_state.balances[slashing_proposer_index] < state.balances[slashing_proposer_index]) else: with pytest.raises(ValidationError): process_proposer_slashings(state, block, config)
async def test_request_beacon_blocks_by_root(monkeypatch): async with ConnectionPairFactory() as (alice, bob): # Mock up block database head_block = BeaconBlock.create( slot=0, parent_root=ZERO_HASH32, state_root=ZERO_HASH32, signature=EMPTY_SIGNATURE, body=BeaconBlockBody.create(), ) blocks = [head_block.set("slot", slot) for slot in range(5)] mock_root_to_block_db = {block.signing_root: block for block in blocks} def get_block_by_root(root): validate_word(root) if root in mock_root_to_block_db: return mock_root_to_block_db[root] else: raise BlockNotFound monkeypatch.setattr(bob.chain, "get_block_by_root", get_block_by_root) requesting_block_roots = [ blocks[0].signing_root, b"\x12" * 32, # Unknown block root blocks[1].signing_root, b"\x23" * 32, # Unknown block root blocks[3].signing_root, ] requested_blocks = await alice.request_beacon_blocks_by_root( peer_id=bob.peer_id, block_roots=requesting_block_roots ) expected_blocks = [blocks[0], blocks[1], blocks[3]] assert len(requested_blocks) == len(expected_blocks) assert set(requested_blocks) == set(expected_blocks)
async def fetch_block_proposal(self, slot: Slot, randao_reveal: BLSSignature) -> BeaconBlock: body = BeaconBlockBody.create(randao_reveal=randao_reveal) return BeaconBlock.create(slot=slot, body=body)
async def test_request_beacon_blocks_by_range_invalid_request(monkeypatch): async with ConnectionPairFactory() as (alice, bob): head_slot = 1 request_head_block_root = b"\x56" * 32 head_block = BeaconBlock.create( slot=head_slot, parent_root=ZERO_HASH32, state_root=ZERO_HASH32, signature=EMPTY_SIGNATURE, body=BeaconBlockBody.create(), ) # TEST: Can not request blocks with `start_slot` greater than head block slot start_slot = 2 def get_block_by_root(root): return head_block monkeypatch.setattr(bob.chain, "get_block_by_root", get_block_by_root) count = 1 step = 1 with pytest.raises(RequestFailure): await alice.request_beacon_blocks_by_range( peer_id=bob.peer_id, head_block_root=request_head_block_root, start_slot=start_slot, count=count, step=step, ) # TEST: Can not request fork chain blocks with `start_slot` # lower than peer's latest finalized slot start_slot = head_slot state_machine = bob.chain.get_state_machine() old_state = bob.chain.get_head_state() new_checkpoint = old_state.finalized_checkpoint.set( "epoch", old_state.finalized_checkpoint.epoch + 1 ) def get_canonical_block_by_slot(slot): raise BlockNotFound monkeypatch.setattr( bob.chain, "get_canonical_block_by_slot", get_canonical_block_by_slot ) def get_state_machine(at_slot=None): class MockStateMachine: state = old_state.set("finalized_checkpoint", new_checkpoint) config = state_machine.config return MockStateMachine() def get_head_state(): return old_state.set("finalized_checkpoint", new_checkpoint) monkeypatch.setattr(bob.chain, "get_state_machine", get_state_machine) monkeypatch.setattr(bob.chain, "get_head_state", get_head_state) with pytest.raises(RequestFailure): await alice.request_beacon_blocks_by_range( peer_id=bob.peer_id, head_block_root=request_head_block_root, start_slot=start_slot, count=count, step=step, )
def test_get_genesis_beacon_state( validator_count, pubkeys, genesis_epoch, genesis_slot, max_committees_per_slot, slots_per_historical_root, epochs_per_slashings_vector, epochs_per_historical_vector, config, keymap, ): genesis_deposits, deposit_root = create_mock_deposits_and_root( pubkeys=pubkeys[:validator_count], keymap=keymap, config=config) genesis_eth1_data = Eth1Data.create( deposit_count=len(genesis_deposits), deposit_root=deposit_root, block_hash=ZERO_HASH32, ) eth1_timestamp = 10 eth1_block_hash = genesis_eth1_data.block_hash state = initialize_beacon_state_from_eth1( eth1_block_hash=eth1_block_hash, eth1_timestamp=eth1_timestamp, deposits=genesis_deposits, config=config, ) # Versioning assert state.slot == genesis_slot assert state.genesis_time == _genesis_time_from_eth1_timestamp( eth1_timestamp) assert state.fork == Fork.create() # History assert state.latest_block_header == BeaconBlockHeader.create( body_root=BeaconBlockBody.create().hash_tree_root) assert len(state.block_roots) == slots_per_historical_root assert tuple( state.block_roots) == (ZERO_HASH32, ) * slots_per_historical_root assert len(state.state_roots) == slots_per_historical_root assert tuple( state.block_roots) == (ZERO_HASH32, ) * slots_per_historical_root assert len(state.historical_roots) == 0 # Ethereum 1.0 chain data assert state.eth1_data == genesis_eth1_data assert len(state.eth1_data_votes) == 0 assert state.eth1_deposit_index == len(genesis_deposits) # Validator registry assert len(state.validators) == validator_count assert len(state.balances) == validator_count # Shuffling assert len(state.randao_mixes) == epochs_per_historical_vector assert (tuple(state.randao_mixes) == (eth1_block_hash, ) * epochs_per_historical_vector) # Slashings assert len(state.slashings) == epochs_per_slashings_vector assert tuple(state.slashings) == (Gwei(0), ) * epochs_per_slashings_vector # Attestations assert len(state.previous_epoch_attestations) == 0 assert len(state.current_epoch_attestations) == 0 # Justification assert state.previous_justified_checkpoint.epoch == genesis_epoch assert state.previous_justified_checkpoint.root == ZERO_HASH32 assert state.current_justified_checkpoint.epoch == genesis_epoch assert state.current_justified_checkpoint.root == ZERO_HASH32 assert state.justification_bits == (False, ) * JUSTIFICATION_BITS_LENGTH # Finalization assert state.finalized_checkpoint.epoch == genesis_epoch assert state.finalized_checkpoint.root == ZERO_HASH32 for i in range(len(genesis_deposits)): assert state.validators[i].is_active(genesis_epoch)