Python interface for Rumor - Eth2 networking shell
Project description
pyrum
Pyrum ("Py Rumor") is a Python interface to interact with Rumor, an Eth2 networking shell.
This interface maps Rumor commands to async python functions.
The mapping is simple:
- A command path is equal to a series of fields
- A command argument is equal to a call argument
- A command flag is equal to a call keyword argument, every
_
in the keyword is replaced with-
Some examples:
host listen --tcp=9001
->
peer.listen(tcp=9001)
peer connect /dns4/prylabs.net/tcp/30001/p2p/16Uiu2HAm7Qwe19vz9WzD2Mxn7fXd1vgHHp4iccuyq7TxwRXoAGfc
->
peer.connect('/dns4/prylabs.net/tcp/30001/p2p/16Uiu2HAm7Qwe19vz9WzD2Mxn7fXd1vgHHp4iccuyq7TxwRXoAGfc')
Each of these calls returns a Call
object:
- Wait for successful call completion by awaiting the
my_call.ok
future. - Retrieve latest data from the
my_call.data
dict - Individual data can be retrieved by calling
await my_call.some_data_field()
to get the respective data as soon as it is available.
Full example:
from pyrum import Rumor
import asyncio
async def basic_connect_example():
rumor = Rumor()
await rumor.start(cmd='cd ../rumor && go run .') # Hook it up to your own local version of Rumor, if you like.
alice = rumor.actor('alice')
await alice.host.start().ok
# Flags are keyword arguments
await alice.host.listen(tcp=9000).ok
print("started alice")
# Concurrency in Rumor, planned with async in Pyrum
bob = rumor.actor('bob')
await bob.host.start().ok
await bob.host.listen(tcp=9001).ok
print("started bob")
long_call = alice.debug.sleep(5_000).ok # sleep 5 seconds
print('made long call')
short_call = alice.debug.sleep(3_000).ok # sleep 3 seconds
print('made short call')
await short_call
print("done with short call")
await long_call
print('done with long call')
# Getting a result should be as easy as calling, and waiting for the key we are after
bob_addr = await bob.host.view().enr()
print('BOB has ENR: ', bob_addr)
# Print all ENR contents
await rumor.enr.view(bob_addr).ok
# Command arguments are just call arguments
await alice.peer.connect(bob_addr).ok
print("connected alice to bob!")
# When there are multiple entries containing some specific key,
# you can also prefix with `listen_` to get each of the values:
async for msg in bob.peer.list().listen_msg():
print(f'bob peer list: {msg}')
async for msg in alice.peer.list().listen_msg():
print(f'alice peer list: {msg}')
# Or alternatively, collect all result data from the call:
bob_addr_data = await rumor.enr.view(bob_addr).ok
print("--- BOB host view data: ---")
print("\n".join(f"{k}: {v}" for k, v in bob_addr_data.items()))
# Close the Rumor process
await rumor.stop()
asyncio.run(basic_connect_example())
Example Output:
started alice
started bob
made long call
made short call
done with short call
done with long call
BOB has ENR: enr:-Iu4QDJZSANnGPSG2pcOC4DQWJIVjNSPKflgUrBKC62LTHX7faPJDiGbnhPhMcJgBW5FQUGVADtzgXwZnYBYWu60o92AgmlkgnY0gmlwhMCoAE2Jc2VjcDI1NmsxoQLvYiwojQCdzUu2cJRwPDNT50qQMahVG9cT1s8ReTHMSoN0Y3CCIymDdWRwgiMp
connected alice to bob!
bob peer list: 1 peers
bob peer list: 0: {16Uiu2HAm69HEANtD7weTtc3ogkeAfez59jN77AjrKurEV8t26JFY: [/ip4/192.168.0.77/tcp/9000]}
alice peer list: 1 peers
alice peer list: 0: {16Uiu2HAmBY8F78MRrsEQCChAdTswmoVGSuQBuxVjy3aPtPxs2bbf: [/ip4/192.168.0.77/tcp/9001]}
--- BOB host view data: ---
enode: enode://ef622c288d009dcd4bb67094703c3353e74a9031a8551bd713d6cf117931cc4a486aec036d6e1e341982d13c09aae879899875d8913d826ef57847399e74776c@192.168.0.77:9001
enr: enr:-Iu4QDJZSANnGPSG2pcOC4DQWJIVjNSPKflgUrBKC62LTHX7faPJDiGbnhPhMcJgBW5FQUGVADtzgXwZnYBYWu60o92AgmlkgnY0gmlwhMCoAE2Jc2VjcDI1NmsxoQLvYiwojQCdzUu2cJRwPDNT50qQMahVG9cT1s8ReTHMSoN0Y3CCIymDdWRwgiMp
msg: ENR parsed successfully
multi: /ip4/192.168.0.77/tcp/9001/p2p/16Uiu2HAmBY8F78MRrsEQCChAdTswmoVGSuQBuxVjy3aPtPxs2bbf
node_id: 161b281f870f3ac0e337d5b693ed3403329e67c51e923d2e7c7c4bd3e6a6856a
peer_id: 16Uiu2HAmBY8F78MRrsEQCChAdTswmoVGSuQBuxVjy3aPtPxs2bbf
seq: 0
xy: 108276226593835360659557713003549688209365448159795919906582200744288296488010 32755439791403710978696135607749543911669765691070631861819210454356664285036
Another example, exchanging RPC status between two actors:
from remerkleable.complex import Container
from remerkleable.byte_arrays import Bytes32, Bytes4
from remerkleable.basic import uint64
class StatusReq(Container):
version: Bytes4
finalized_root: Bytes32
finalized_epoch: uint64
head_root: Bytes32
head_epoch: uint64
async def basic_rpc_example():
rumor = Rumor()
await rumor.start(cmd='cd ../rumor && go run .')
alice = rumor.actor('alice')
await alice.host.start().ok
await alice.host.listen(tcp=9000).ok
print("started alice")
bob = rumor.actor('bob')
await bob.host.start().ok
await bob.host.listen(tcp=9001).ok
print("started bob")
bob_addr = await bob.host.view().enr()
alice_peer_id = await alice.host.view().peer_id()
await alice.peer.connect(bob_addr).ok
print("connected alice to bob!")
alice_status = StatusReq(head_epoch=42)
bob_status = StatusReq(head_epoch=123)
async def alice_work():
print("alice: listening for status requests")
async for req in alice.rpc.status.listen(raw=True).listen_req():
print(f"alice: Got request: {req}")
assert 'input_err' not in req
# Or send back an error; await alice.rpc.status.resp.invalid_request(req['req_id'], f"hello! Your request was invalid, because: {req['input_err']}").ok
assert req['data'] == bob_status.encode_bytes().hex()
resp = alice_status.encode_bytes().hex()
await alice.rpc.status.resp.chunk.raw(req['req_id'], resp, done=True).ok
break # simple test, don't wait for more requests
print("alice: stopped listening for status requests")
async def bob_work():
# Send alice a status request
req = bob_status.encode_bytes().hex()
print(f"bob: sending alice ({alice_peer_id}) a status request: {req}")
resp = await bob.rpc.status.req.raw(alice_peer_id, req, raw=True).ok
print(f"bob: received status response from alice: {resp}")
assert resp['chunk_index'] == 0 # only 1 chunk
assert resp['result_code'] == 0 # success chunk
assert resp['data'] == alice_status.encode_bytes().hex()
await asyncio.wait([alice_work(), bob_work()])
# Close the Rumor process
await rumor.stop()
asyncio.run(basic_rpc_example())
License
MIT, see LICENSE file.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
pyrum-0.0.6.tar.gz
(7.6 kB
view hashes)
Built Distribution
pyrum-0.0.6-py3-none-any.whl
(7.9 kB
view hashes)