async def test_blocks_by_root(enr, beacon_state):
    async with SubprocessConn(cmd='rumor bare') as conn:
        async with trio.open_nursery() as nursery:
            rumor = Rumor(conn, nursery)
            peer_id = await connect_rumor(rumor, enr)

            req = List[Root, 64]([
                'ef64a1b94652cd9070baa4f9c0e8b1ce624bdb071b77b51b1a54b8babb1a5cd2'
            ]).encode_bytes().hex()
            blocks = []
            async for chunk in rumor.rpc.blocks_by_root.req.raw(
                    peer_id, req, raw=True, compression='snappy').chunk():
                if chunk['result_code'] == 0:
                    block = SignedBeaconBlock.decode_bytes(
                        bytes.fromhex(chunk['data']))
                    blocks.append(block)

            compare_vals(1, len(blocks), 'num_blocks')
            compare_containers(
                SignedBeaconBlock(message=BeaconBlock(
                    state_root=
                    '0x363bcbffb8bcbc8db51bda09cb68a5a3cc7cb2c1b79f8301a3157e37e24c687d'
                )), blocks[0])

            nursery.cancel_scope.cancel()
async def run_work():
    async with SubprocessConn(cmd='cd ../rumor && go run . bare') as conn:
        # A Trio nursery hosts all the async tasks of the Rumor instance.
        async with trio.open_nursery() as nursery:
            # And optionally use Rumor(conn, debug=True) to be super verbose about Rumor communication.
            await sync_experiment(Rumor(conn, nursery))
            # Cancel the nursery to signal that we are not using Rumor anymore
            nursery.cancel_scope.cancel()
Example #3
0
    async def wrapped_run_fn(args):
        async with SubprocessConn(
                cmd='./bin/rumor bare --level=trace') as conn:
            async with trio.open_nursery() as nursery:
                try:
                    rumor = Rumor(conn, nursery)
                    response_code = await async_run_fn(rumor, args)
                    return response_code

                finally:
                    nursery.cancel_scope.cancel()
async def run_rumor_function(fn: Callable[[Rumor, trio.Nursery], Coroutine]):
    async with trio.open_nursery() as nursery:
        try:
            async with SubprocessConn(
                    cmd='cd ../rumor && go run . bare') as conn:
                # A Trio nursery hosts all the async tasks of the Rumor instance.
                async with trio.open_nursery() as nursery:
                    # And optionally use Rumor(conn, debug=True) to be super verbose about Rumor communication.
                    await fn(Rumor(conn, nursery), nursery)
                    # Cancel the nursery to signal that we are not using Rumor anymore
                    nursery.cancel_scope.cancel()
        except Exception as e:
            print(e)
async def test_teku():
    config = InstanceConfig('teku', BEACON_STATE_LOCATION, TEST_ENR)
    start_instance(config)

    try:
        async with SubprocessConn(
                cmd='./bin/rumor bare --level=trace') as conn:
            async with trio.open_nursery() as nursery:
                rumor = Rumor(conn, nursery)
                peer_id = await connect_rumor(rumor, 'teku', TEST_ENR)

                # NOTE: this may not be the exact test
                assert peer_id is not None
                nursery.cancel_scope.cancel()

    finally:
        stop_instance(config.client)
async def server_blocks_by_range_example(rumor: Rumor, nursery: trio.Nursery):

    # Morty is us
    morty = rumor.actor('morty')
    await morty.host.start()
    await morty.host.listen(tcp=9000)
    print("started morty")

    # Rick is the other client
    rick_enr = "enr:-Iu4QGuiaVXBEoi4kcLbsoPYX7GTK9ExOODTuqYBp9CyHN_PSDtnLMCIL91ydxUDRPZ-jem-o0WotK6JoZjPQWhTfEsTgmlkgnY0gmlwhDbOLfeJc2VjcDI1NmsxoQLVqNEoCVTC74VmUx25USyFe7lL0TgpXHaCX9CDy9H6boN0Y3CCIyiDdWRwgiMo"

    rick_peer_id = await morty.peer.connect(rick_enr, "bootnode").peer_id()

    print(f"connected to Rick {rick_peer_id}")

    call = morty.rpc.blocks_by_range.listen(raw=True, compression='snappy')

    print("listening for requests")

    async for req in call.req():
        print(f"morty: Got request: {req}")

        parsed_req = BlocksByRange.decode_bytes(
            bytes.fromhex(req['chunk']['data']))
        print('parsed request: ', parsed_req)

        start = parsed_req.start_slot
        end = start + parsed_req.count * parsed_req.step

        for i, slot in zip(range(parsed_req.count),
                           range(start, end, parsed_req.step)):
            # Try any message:
            # resp = f"not a block, but can you decode this chunk though? chunk nr {i} here".encode()
            # Or construct a block (can make it more consensus-valid, but snappy compression testing can be simple):
            resp = SignedBeaconBlock(message=BeaconBlock(
                slot=slot)).encode_bytes().hex()
            print(f"responding chunk {i} slot {slot} chunk: {resp}")
            await morty.rpc.blocks_by_range.resp.chunk.raw(
                req['req_id'], resp, done=(i + 1 == parsed_req.count))

        print("done responding")

    print("morty: stopped listening for requests")
Example #7
0
async def check_topics(enr):
    async with SubprocessConn(cmd='rumor bare') as conn:
        async with trio.open_nursery() as nursery:
            rumor = Rumor(conn, nursery)
            peer_id = await connect_rumor(rumor, enr)

            await rumor.gossip.start()

            peer_id = await rumor.peer.connect(enr).peer_id()

            for topic in EXPECTED_TOPICS:
                await rumor.gossip.join(topic)

                resp = await rumor.gossip.list_peers(topic)
                peers = resp['peers']

                assert peers == [peer_id], f'actual peers for topic {topic}: {peers}'

            nursery.cancel_scope.cancel()
async def server_blocks_by_root_example(rumor: Rumor, nursery: trio.Nursery):

    # Morty is us
    morty = rumor.actor('morty')
    await morty.host.start()
    await morty.host.listen(tcp=9000)
    print("started morty")

    # Rick is the other client
    rick_enr = "enr:-Iu4QGuiaVXBEoi4kcLbsoPYX7GTK9ExOODTuqYBp9CyHN_PSDtnLMCIL91ydxUDRPZ-jem-o0WotK6JoZjPQWhTfEsTgmlkgnY0gmlwhDbOLfeJc2VjcDI1NmsxoQLVqNEoCVTC74VmUx25USyFe7lL0TgpXHaCX9CDy9H6boN0Y3CCIyiDdWRwgiMo"

    rick_peer_id = await morty.peer.connect(rick_enr, "bootnode").peer_id()

    print(f"connected to Rick {rick_peer_id}")

    call = morty.rpc.blocks_by_root.listen(raw=True, compression='snappy')

    print("listening for requests")

    async for req in call.req():
        print(f"morty: Got request: {req}")

        parsed_req = BlocksByRoot.decode_bytes(
            bytes.fromhex(req['chunk']['data']))
        print('parsed request: ', parsed_req)

        for i, root in enumerate(parsed_req):
            resp = SignedBeaconBlock(message=BeaconBlock(
                slot=slot)).encode_bytes().hex()
            print(f"responding chunk {i} root {root}, chunk: {resp}")
            await morty.rpc.blocks_by_range.resp.chunk.raw(
                req['req_id'], resp, done=(i + 1 == len(parsed_req)))

        print("done responding")

    print("morty: stopped listening for requests")
async def basic_status_example(rumor: Rumor, nursery: trio.Nursery):

    # Load some genesis state of the client (or use make_genesis.py)
    state = load_state('genesis.ssz')

    # Morty is us
    morty = rumor.actor('morty')
    await morty.host.start()
    await morty.host.listen(tcp=9000)
    print("started morty")

    # Rick is the other client
    rick_enr = "enr:-Iu4QGuiaVXBEoi4kcLbsoPYX7GTK9ExOODTuqYBp9CyHN_PSDtnLMCIL91ydxUDRPZ-jem-o0WotK6JoZjPQWhTfEsTgmlkgnY0gmlwhDbOLfeJc2VjcDI1NmsxoQLVqNEoCVTC74VmUx25USyFe7lL0TgpXHaCX9CDy9H6boN0Y3CCIyiDdWRwgiMo"

    rick_peer_id = await morty.peer.connect(rick_enr, "bootnode").peer_id()

    print(f"connected to Rick {rick_peer_id}")

    print("Testing a Status RPC request")

    head = state.latest_block_header.copy()
    head.state_root = state.hash_tree_root()

    # Sync status
    morty_status = Status(
        version=compute_fork_digest(state.fork.current_version,
                                    state.genesis_validators_root),
        finalized_root=state.finalized_checkpoint.root,
        finalized_epoch=0,
        head_root=head.hash_tree_root(),
        head_epoch=0,
    )

    req = morty_status.encode_bytes().hex()
    print(f"morty: sending rick a status request: {req}")

    # Note: public testnet node is not updated, only receiving an empty response if snappy is enabled.
    resp = await morty.rpc.status.req.raw(rick_peer_id, req, raw=True)

    print(f"morty: received status response from rick: {resp}")
    try:
        rick_status = Status.decode_bytes(bytes.fromhex(resp['chunk']['data']))
        print(rick_status)
    except Exception as e:
        print(f"could not decode status response: {e}")

    call = morty.rpc.status.listen(raw=True, compression='snappy')

    # Other keywords to try here:
    # Req-resp timeout: timeout=123000 (in milliseconds, 0 to disable)
    # Drop contents, not keeping track of them to reply later: drop=True
    # Ignore request bytes, do not read any: read=False
    async def process_requests():
        async for req in call.req():
            print(f"morty: Got request: {req}")

            # Respond with Input error
            # await morty.rpc.status.resp.invalid_request(req['req_id'], f"hello! Morty does not like your request!")

            # Respond with server error
            # await morty.rpc.status.resp.server_error(req['req_id'], f"hello! Morty failed, look for a new morty!")

            # Respond with valid chunk (and done=True to exit immediately after)
            resp = morty_status.encode_bytes().hex()
            await morty.rpc.status.resp.chunk.raw(req['req_id'],
                                                  resp,
                                                  done=True)

            # Or send arbitrary data
            # resp = bytes.fromhex('1337')
            # await morty.rpc.status.resp.chunk.raw(req['req_id'], resp, result_code=2, done=True)

        print("morty: stopped listening for requests")

    print("listening for requests")
    await process_requests()
Example #10
0
async def lit_morty(rumor: Rumor):

    state = load_state('lighthouse/genesis.ssz')

    morty = rumor.actor('morty')
    await morty.host.start()
    await morty.host.listen(tcp=9000)

    genesis_root = state.hash_tree_root()

    # Sync status
    morty_status = Status(
        version=spec.GENESIS_FORK_VERSION,
        finalized_root=genesis_root,
        finalized_epoch=0,
        head_root=genesis_root,
        head_epoch=0,
    )
    print("Morty status:")
    print(morty_status)
    print(morty_status.encode_bytes().hex())

    async def sync_from_bootnode(state: spec.BeaconState):
        await trio.sleep(2)

        peer_id = await morty.peer.connect(bootnodes[0], "bootnode").peer_id()
        print(f"connected bootnode peer: {peer_id}")

        # boot_status = await ask_status(peer_id)
        # print("Bootnode status:", boot_status)

        print(await morty.peer.list('all'))

        # Play nice, don't hit them with another request right away, wait half a minute. (Age?!)
        # await trio.sleep(31)
        async def sync_step(stats_csv: csv.DictWriter,
                            epochs_ctx: fast_spec.EpochsContext,
                            state: fast_spec.BeaconState) -> spec.BeaconState:
            range_req = BlocksByRange(start_slot=state.slot + 1,
                                      count=20,
                                      step=1).encode_bytes().hex()
            print("range req:", range_req)
            blocks = []
            async for chunk in morty.rpc.blocks_by_range.req.raw(
                    peer_id, range_req, max_chunks=10, raw=True).chunk():
                print("got chunk: ", chunk)
                if chunk['result_code'] == 0:
                    block = spec.SignedBeaconBlock.decode_bytes(
                        bytes.fromhex(chunk["data"]))
                    blocks.append(block)
                else:
                    print("failed to get block; msg: ", chunk["msg"])
                    break

            for b in blocks:
                print("processing block!")
                start_time = time.time()
                if state.slot > 500 and (state.slot +
                                         1) % fast_spec.SLOTS_PER_EPOCH == 0:
                    with io.open('pre.ssz', 'bw') as f:
                        state.serialize(f)
                    with io.open('block.ssz', 'bw') as f:
                        b.serialize(f)
                    import sys
                    sys.exit(1)

                transition_input_state = state.copy()
                fast_spec.state_transition(epochs_ctx, transition_input_state,
                                           b)

                end_time = time.time()
                elapsed_time = end_time - start_time
                print(
                    f"slot: {state.slot} state root: {state.hash_tree_root().hex()}  processing speed: {1.0 / elapsed_time} blocks / second  ({elapsed_time * 1000.0} ms/block)"
                )

                def subtree_size(n: Node) -> int:
                    if n.is_leaf():
                        return 1
                    return subtree_size(n.get_left()) + subtree_size(
                        n.get_right())

                def analyze_diff(a: Node, b: Node) -> (int, int):
                    """Iterate over the changes of b, not common with a. Left-to-right order.
                     Returns (a,b) tuples that can't be diffed deeper."""
                    if a.root != b.root:
                        a_leaf = a.is_leaf()
                        b_leaf = b.is_leaf()
                        if a_leaf or b_leaf:
                            return subtree_size(a), subtree_size(b)
                        else:
                            a_l, b_l = analyze_diff(a.get_left(), b.get_left())
                            a_r, b_r = analyze_diff(a.get_right(),
                                                    b.get_right())
                            return a_l + a_r + 1, b_l + b_r + 1
                    return 0, 0

                stats = {
                    'slot': b.message.slot,
                    'proposer': epochs_ctx.get_beacon_proposer(b.message.slot),
                    'process_time': elapsed_time,
                }
                # for i, key in enumerate(spec.BeaconState.fields().keys()):
                #     a = state.get(i).get_backing()
                #     b = transition_input_state.get(i).get_backing()
                #
                #     removed_nodes, added_nodes = analyze_diff(a, b)
                #     stats[f"added_nodes_{key}"] = added_nodes
                #     stats[f"removed_nodes_{key}"] = removed_nodes

                # stats_csv.writerow(stats)

                print(f"stats: {stats}")
                state = transition_input_state

            global morty_status
            morty_status = Status(
                version=spec.GENESIS_FORK_VERSION,
                finalized_root=state.finalized_checkpoint.root,
                finalized_epoch=state.finalized_checkpoint.epoch,
                head_root=state.latest_block_header.hash_tree_root(),
                head_epoch=state.slot,
            )

            return state

        async def sync_work(stats_csv: csv.DictWriter,
                            state: spec.BeaconState):
            epochs_ctx = fast_spec.EpochsContext()
            epochs_ctx.load_state(state)

            while True:
                state = await sync_step(stats_csv, epochs_ctx, state)
                if state.slot > 10000:
                    break  # synced enough (TODO: use bootnode status instead)

        with open(r'sync_stats.csv', 'a', newline='') as csvfile:
            fieldnames = ['slot', 'proposer', 'process_time']
            for key in spec.BeaconState.fields().keys():
                fieldnames.append(f"added_nodes_{key}")
                fieldnames.append(f"removed_nodes_{key}")
            writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
            writer.writeheader()
            await sync_work(writer, state)

        print("Saying goobye")
        ok_bye_bye = Goodbye(1)  # A.k.a "Client shut down"
        await morty.rpc.goodbye.req.raw(peer_id,
                                        ok_bye_bye.encode_bytes().hex(),
                                        raw=True)

        print("disconnecting")
        await morty.peer.disconnect(peer_id)
        print("disconnected")

    await sync_from_bootnode(state)

    # Close the Rumor process
    await rumor.stop()
Example #11
0
async def run_work():
    # Hook it up to your own local version of Rumor, if you like.
    # And optionally enable debug=True to be super verbose about Rumor communication.
    async with Rumor(cmd='cd ../rumor && go run .') as rumor:
        await lit_morty(rumor)
async def sync_experiment(rumor: Rumor):
    state = load_state('genesis.ssz')

    morty = rumor.actor('morty')
    await morty.host.start()
    await morty.host.listen(tcp=9000)

    head = state.latest_block_header.copy()
    head.state_root = state.hash_tree_root()

    # Sync status
    morty_status = Status(
        version=compute_fork_digest(state.fork.current_version,
                                    state.genesis_validators_root),
        finalized_root=state.finalized_checkpoint.root,
        finalized_epoch=0,
        head_root=head.hash_tree_root(),
        head_epoch=0,
    )
    print("Morty status:")
    print(morty_status)
    print(morty_status.encode_bytes().hex())

    async def sync_work():
        await trio.sleep(2)

        rick_enr = "enr:-Iu4QGuiaVXBEoi4kcLbsoPYX7GTK9ExOODTuqYBp9CyHN_PSDtnLMCIL91ydxUDRPZ-jem-o0WotK6JoZjPQWhTfEsTgmlkgnY0gmlwhDbOLfeJc2VjcDI1NmsxoQLVqNEoCVTC74VmUx25USyFe7lL0TgpXHaCX9CDy9H6boN0Y3CCIyiDdWRwgiMo"

        peer_id = await morty.peer.connect(rick_enr, "bootnode").peer_id()
        print(f"connected bootnode peer: {peer_id}")

        print(await morty.peer.list('all'))

        async def sync_step(current_slot: Slot):
            slot_count = 20
            range_req = BlocksByRange(start_slot=current_slot + 1,
                                      count=slot_count,
                                      step=1).encode_bytes().hex()
            print("range req:", range_req)
            blocks = []
            async for chunk in morty.rpc.blocks_by_range.req.raw(
                    peer_id, range_req, max_chunks=slot_count,
                    raw=True).chunk():
                print("got chunk: ", chunk)
                if chunk['result_code'] == 0:
                    block = SignedBeaconBlock.decode_bytes(
                        bytes.fromhex(chunk["data"]))
                    blocks.append(block)
                    current_slot = block.message.slot
                else:
                    print("failed to get block; msg: ", chunk["msg"])
                    break
            # TODO: could process blocks using as done with previous sync experiment. Just need to test RPC now though.

            return current_slot

        async def sync_work():
            current_slot = Slot(0)
            while True:
                current_slot = await sync_step(current_slot)
                if current_slot > 100:
                    break

        await sync_work()

        print("Saying goobye")
        ok_bye_bye = Goodbye(1)  # A.k.a "Client shut down"
        await morty.rpc.goodbye.req.raw(peer_id,
                                        ok_bye_bye.encode_bytes().hex(),
                                        raw=True)

        print("disconnecting")
        await morty.peer.disconnect(peer_id)
        print("disconnected")

    await sync_work()