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 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 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 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
def _mk_minimum_viable_signed_beacon_blocks(slots, state_machine_provider, state, block): for slot in slots: message = BeaconBlock.create(slot=slot, parent_root=block.message.hash_tree_root) block = SignedBeaconBlock.create(message=message) state, block = state_machine_provider(slot).import_block(block, state) yield block
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 test_higher_slot_fork_choice_scoring(slot): block = BeaconBlock.create(slot=slot) expected_score = HigherSlotScore(slot) scoring = HigherSlotScoring() score = scoring.score(block) assert score == expected_score
def test_update_attestations(sample_attestation_params, sample_beacon_block_params): block = BeaconBlock.create(**sample_beacon_block_params) attestations = block.body.attestations attestations = list(attestations) attestations.append(Attestation.create(**sample_attestation_params)) body2 = block.body.set("attestations", attestations) block2 = block.set("body", body2) assert len(block2.body.attestations) == 1
def test_higher_slot_fork_choice_scoring(sample_beacon_block_params, slot): block = BeaconBlock.create(**sample_beacon_block_params).set("slot", slot) expected_score = HigherSlotScore(slot) scoring = HigherSlotScoring() score = scoring.score(block) assert score == expected_score
def _mk_block(block_params, slot, parent, block_offset): return BeaconBlock.create(**block_params).mset( "slot", slot, "parent_root", parent.signing_root, # mix in something unique "state_root", block_offset.to_bytes(32, byteorder="big"), )
def _mk_block(block_params, slot, parent, block_offset): block = BeaconBlock.create(**block_params).mset( "slot", slot, "parent_root", parent.message.hash_tree_root, # mix in something unique "state_root", block_offset.to_bytes(32, byteorder="big"), ) return SignedBeaconBlock.create(message=block)
def _mk_minimum_viable_signed_beacon_blocks(slots, state_machine_provider, state, block, config): for slot in slots: future_state, _ = state_machine_provider(slot).apply_state_transition( state, future_slot=block.slot + 1) proposer_index = get_beacon_proposer_index(future_state, config) message = BeaconBlock.create( slot=slot, parent_root=block.message.hash_tree_root, proposer_index=proposer_index, ) block = SignedBeaconBlock.create(message=message) state, block = state_machine_provider(slot).apply_state_transition( state, block) yield block
def test_validate_block_slot( sample_beacon_state_params, sample_beacon_block_params, state_slot, block_slot, expected, ): state = BeaconState.create(**sample_beacon_state_params).set( "slot", state_slot) block = BeaconBlock.create(**sample_beacon_block_params).set( "slot", block_slot) if isinstance(expected, Exception): with pytest.raises(ValidationError): validate_block_slot(state, block) else: validate_block_slot(state, block)
async def test_hosts_can_do_req_resp(host_factory): host_a_blocks = set() host_a, host_a_listen_maddr = host_factory("c", host_a_blocks) host_b_blocks = set( [SignedBeaconBlock.create(message=BeaconBlock.create(slot=0))]) host_b, host_b_listen_maddr = host_factory("d", host_b_blocks) with trio.move_on_after(2 * 60): async with _run_host(host_a, host_a_listen_maddr): async with _run_host(host_b, host_b_listen_maddr): await host_b.add_peer_from_maddr(host_a_listen_maddr) assert len(host_a.get_network().connections) == 1 assert len(host_b.get_network().connections) == 1 peer_b = host_b.get_id() status_b = await host_a.exchange_status( peer_b, host_a._status_provider()) assert status_b some_blocks = set() async for block in host_a.get_blocks_by_range(peer_b, 0, 20): some_blocks.add(block) assert len(some_blocks) == 1 no_blocks = set() async for block in host_a.get_blocks_by_range(peer_b, 30, 3): no_blocks.add(block) assert len(no_blocks) == 0 a_block = some_blocks.pop() the_same_block = await host_a.get_blocks_by_root( peer_b, a_block.message.hash_tree_root).__anext__() assert a_block == the_same_block b_seq_number = await host_a.send_ping(peer_b) assert b_seq_number == ord("d") b_metadata = await host_a.get_metadata(peer_b) assert b_metadata.seq_number == b_seq_number await host_a.send_goodbye_to(peer_b, GoodbyeReason.shutting_down) assert len(host_a.get_network().connections) == 0 assert len(host_b.get_network().connections) == 0
def test_chain2_at_genesis(base_db, genesis_state, genesis_block, config): genesis_block = genesis_block.message chain_db = BeaconChainDB.from_genesis(base_db, genesis_state, SignedBeaconBlock, config) block_at_genesis = chain_db.get_block_by_slot(GENESIS_SLOT, BeaconBlock) assert block_at_genesis == genesis_block block_at_genesis = chain_db.get_block_by_root(genesis_block.hash_tree_root, BeaconBlock) assert block_at_genesis == genesis_block genesis_signature = chain_db.get_block_signature_by_root( genesis_block.hash_tree_root) assert genesis_signature == EMPTY_SIGNATURE state_at_genesis = chain_db.get_state_by_slot(GENESIS_SLOT, BeaconState) assert state_at_genesis == genesis_state state_at_genesis = chain_db.get_state_by_root(genesis_state.hash_tree_root, BeaconState) assert state_at_genesis == genesis_state finalized_head = chain_db.get_finalized_head(BeaconBlock) assert finalized_head == genesis_block some_future_slot = Slot(22) assert not chain_db.get_block_by_slot(some_future_slot, BeaconBlock) assert not chain_db.get_state_by_slot(some_future_slot, BeaconState) block_at_future_slot = SignedBeaconBlock.create(message=BeaconBlock.create( slot=some_future_slot)) chain_db.persist_block(block_at_future_slot) future_block = chain_db.get_block_by_root( block_at_future_slot.message.hash_tree_root, BeaconBlock) assert block_at_future_slot.message == future_block # NOTE: only finalized blocks are stored by slot in the DB # non-finalized but canonical blocks are determined by fork choice, separately from the DB assert not chain_db.get_block_by_slot(some_future_slot, BeaconBlock) # assume the fork choice did finalize this block... chain_db.mark_canonical_block(block_at_future_slot.message) assert chain_db.get_block_by_slot(some_future_slot, BeaconBlock) == future_block
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_chain2_full(base_db, genesis_state, config): chain_db = BeaconChainDB.from_genesis(base_db, genesis_state, SignedBeaconBlock, config) state = genesis_state states = [genesis_state] blocks = {} num_slots = 500 skip_slots = [5, 64, 100, 300, 301, 302, 401] finalized_slots = [8, 24, 32, 72, 152, 160, 328, 336, 344, 352, 400] # create a new state at each slot using ``_mini_stf()`` and persist it for _ in range(1, num_slots): if state.slot not in skip_slots: new_block = BeaconBlock.create(slot=state.slot, state_root=state.hash_tree_root) blocks[state.slot] = new_block chain_db.persist_block(SignedBeaconBlock.create(message=new_block)) else: new_block = None state = _mini_stf(state, new_block, config) chain_db.persist_state(state, config) states.append(state) # test that each state created above equals the state stored at its root for state in states: # finalize a slot two epochs after processing it # this is here to test the reconstruction of ``state.randao_mixes`` maybe_finalized_slot = state.slot - config.SLOTS_PER_EPOCH * 2 if maybe_finalized_slot in finalized_slots: chain_db.mark_finalized_head(blocks[maybe_finalized_slot]) retrieved_state = chain_db._read_state(state.hash_tree_root, BeaconState, config) assert retrieved_state == state for slot in range(0, num_slots): if slot in blocks and slot <= finalized_slots[-1]: assert chain_db.get_block_by_slot(Slot(slot), BeaconBlock) == blocks[slot] else: assert chain_db.get_block_by_slot(Slot(slot), BeaconBlock) is None
def test_validate_proposer_signature( slots_per_epoch, max_committees_per_slot, proposer_privkey, proposer_pubkey, is_valid_signature, sample_beacon_block_params, sample_beacon_state_params, target_committee_size, max_effective_balance, config, ): state = BeaconState.create(**sample_beacon_state_params).mset( "validators", tuple( create_mock_validator(proposer_pubkey, config) for _ in range(10)), "balances", (max_effective_balance, ) * 10, ) block = BeaconBlock.create(**sample_beacon_block_params) header = block.header proposed_block = block.set( "signature", bls.sign( message_hash=header.signing_root, privkey=proposer_privkey, domain=get_domain(state, SignatureDomain.DOMAIN_BEACON_PROPOSER, slots_per_epoch), ), ) if is_valid_signature: validate_proposer_signature(state, proposed_block, CommitteeConfig(config)) else: with pytest.raises(ValidationError): validate_proposer_signature(state, proposed_block, CommitteeConfig(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 fetch_block_proposal(self, public_key: BLSPubkey, slot: Slot) -> BeaconBlock: return BeaconBlock.create(slot=slot)
def test_block_is_not_genesis(sample_beacon_block_params): genesis_block = BeaconBlock.create(**sample_beacon_block_params) another_block = BeaconBlock.from_parent(genesis_block, FromBlockParams()) assert genesis_block.is_genesis assert not another_block.is_genesis
def test_defaults(sample_beacon_block_params): block = BeaconBlock.create(**sample_beacon_block_params) assert block.slot == sample_beacon_block_params["slot"] assert block.is_genesis
@pytest.fixture async def http_server(chain, event_bus): server = RawTestServer(APIHandler.handle(chain)(event_bus)) return server @pytest.fixture async def http_client(http_server): client = TestClient(http_server) asyncio.ensure_future(client.start_server()) await asyncio.sleep(0.01) return client sample_block = BeaconBlock.create() sample_attestation = Attestation.create() @pytest.mark.parametrize( 'num_validators', (2, ), ) @pytest.mark.parametrize('method, resource, object, json_data, status_code', ( (GET_METHOD, 'beacon', 'head', '', 200), (GET_METHOD, 'beacon', 'block?slot=0', '', 200), (GET_METHOD, 'beacon', 'state?slot=4', '', 200), (GET_METHOD, 'beacon', 'state?root=AVAILABLE_STATE_ROOT', '', 200), )) @pytest.mark.asyncio async def test_restful_http_server(
def _mk_resolved_block_proposal_duty(slot, public_key): return ( BlockProposalDuty(public_key, Tick(0, slot, 0, 0), Tick(0, 0, 0, 0)), BeaconBlock.create(slot=slot), )
def test_block_root_and_block_header_root_equivalence(): block = BeaconBlock.create() header = block.header assert block.hash_tree_root == header.hash_tree_root
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, )