As you may know, aiohivebot is my ongoing attempt at a robust fault tolerant all-node async python library for writing HIVE bots and HIVE L2 nodes. As I look into adding other chains to the library, the name aiohivebot is becoming a little bit too HIVE centric for comfort, and breaking API changes are needed to allow the everything to work intuitively and smoothly. I don't want any chain to be seen as inferior to any other chain when it comes to the API.
In this post I want to share my first attempt at the API changes I think are needed to turn aiohivebot into aiow3, a robust fault tolerant all-node async python library for writing Web 3.0 bots and Web 3.0 L2 nodes, first for HIVE, but in the future for other Web 3.0 chains ass well.
aiow3
The aiow3 lib (renamed from aiohivebot) will be an async Python library for writing robust Web 3.0 bots and layer 2 Web 3.0 nodes.
The code is currently being ported over in order to adjust the API for the fact that HIVE will later no longer be the only supported chain. The aiow3 lib won't be API compatible with aiohivebot, but the API will be very similar and easy for users to port to.
The API change is still in progress, keep using aiohivebot for now.
porting aiohivebot code to aiow3
An example, the following aiohivebot code:
import asyncio
from aiohivebot import BaseBot
class MyBot(BaseBot):
"""Example of an aiohivebot python bot without real utility"""
async def vote_operation(self, body):
"""Handler for cote_operation type operations in the HIVE block stream"""
if "voter" in body and "author" in body and "permlink" in body:
content = await self.bridge.get_post(author=body["author"], permlink=body["permlink"])
if content and "is_paidout" in content and content["is_paidout"]:
print("Vote by", body["voter"], "on expired post detected: @" + \
body["author"] + "/" + body["permlink"] )
else:
print("Vote by", body["voter"], "on active post")
pncset = MyBot()
loop = asyncio.get_event_loop()
loop.run_until_complete(pncset.run())
print("Done")
In aiow3 will change to
import asyncio
from aiow3 import event, Chain, BaseBot
class MyBot(BaseBot):
"""Example of an aiow3 python bot without real utility"""
@event(l1={Chain.HIVE: ["vote_operation"]})
async def process_vote(self, body):
"""Handler for vote_operation type operations in the HIVE block stream"""
if "voter" in body and "author" in body and "permlink" in body:
content = await self.chains[Chain.HIVE].bridge.get_post(author=body["author"], permlink=body["permlink"])
if content and "is_paidout" in content and content["is_paidout"]:
print("Vote by", body["voter"], "on expired post detected: @" + \
body["author"] + "/" + body["permlink"] )
else:
print("Vote by", body["voter"], "on active post")
pncset = MyBot()
loop = asyncio.get_event_loop()
loop.run_until_complete(pncset.run())
print("Done")
Let's walk through the changes
from aiohivebot import BaseBot
changes to
from aiow3 import event, Event, Chain, BaseBot
The initial line imports BaseBot from aiohivebot. It is obvious we need to import it from aiow3 now, but the second line migh not be that obvious.
The event is a decorator that changes the way we register our event handlers, while Chain is an enum that we will need for distinguishing between the chains.
async def vote_operation(self, body):
...
This now becomes:
@event(l1={Chain.HIVE: ["vote_operation"]})
async def process_vote(self, body):
...
Note how the name of the method no longer needs to match the operation. Mapping to both the chain and the operation is now done in the preceding decorator.
Another note: as we can now register a single method to multiple chains, you can add chain to the method fingerprint if you need it.
@event(l1={Chain.HIVE: ["vote_operation"], Chain.BLURT: ["vote_operation"]})
async def process_vote(self, body, chain):
...
if chain == Chain.BLURT:
# This is just an example, not sure if we want to support Blurt right now.
...
else:
...
Argument erasure will continue to work like it did in aiohivebot, so chain is fully optional.
Finaly when calling the API we need to specify which chain now too.
The code
content = await self.bridge.get_post(author=body["author"], permlink=body["permlink"])
now becomes
content = await self.chains[Chain.HIVE].bridge.get_post(author=body["author"], permlink=body["permlink"])
This was an example where there was no constructor, but the aiohivebot BaseBot constructor did have optional constructor arguments and these were partialy chain specific. Given that we will be allowing symultanious use of multiple chains, we need to update this. The aiohivebot vesion of the Basebot has the following constructor arguments:
- start_block
- eat_exceptions
- enable_layer2
- use_irreversible
- use_virtual
- maintain_order
Let's walk throug all of them.
start_block
This constructor argument took an integer before. We change this to a dict:
super().__init__(start_block={Chain.HIVE: 79600000})
eat_exceptions
We are keeping this one global for all chains
enable_layer2
Previously this one was string based
super().__init__(enable_layer2=["engine"])
This one will no longer be needed, but instead will be infered from decorators just as the activated chains are infered from decorators.
Now instead of:
async def engine_market_buy(self, required_auths, required_posting_auths, body, blockno, timestamp):
we need to write:
from aiow3 import L2Chain
...
@event(l2={L2Chain.HIVE_ENGINE: ["market_buy"]})
async def engine_market_buy(self, required_auths, required_posting_auths, body, blockno, timestamp):
...
use_irreversible
This is one again where we use the dict appoach:
super().__init__(use_irreversable={Chain.HIVE: True})
use_virtual
This is a tough one. virtual operations are probably HIVE specific, and other chains likely have their own non universal switches that would be usefull in the constructor, So we are going to phase this one out in exchange for a chain specific config dict.
super().__init__(chain_conf={Chain.HIVE: {"use_virtual": True}})
maintain_order
And one last time we use a dict aproach
super().__init__(maintain_order={Chain.HIVE: True})
;) lookin good. i like those names.