We have been solving for these issues at the application level and have an alpha of a system that isn’t really ‘best effort’, but that assumes an event-based programming including archiving, replay, etc. It is specifically designed let canister ‘publish’ events to a trusted canister and not have to worry about any of the ‘untrusted’ stuff. The Broadcasters do everything via one-shots to subscribers and don’t wait for responses. If a subscriber is off-line it can catch up later by checking the nonce of the event stream.
The one thing it isn’t super good at for now is subnet awareness and/or optimizations, so it is possible to do something expensive like send an event to 100 subscribers on a different subnet instead of relaying to a broadcaster on the subnet and having it distribute to the 100 subscribers. I was hoping to get to that after the alpha.
It lays the foundations of some other cool features like cycle-neutral messaging, tokenization of data streams, etc. Given all of that and some grand designs that I may never actually have time to build…I would have actually loved something like this at the protocol layer.
Ethereum has events, but your contracts can’t respond to them. This event system fixes that glitch and lets you write programs in an event messaging style. When you do that you don’t have to stress about ‘what happens if I miss a message’ or ‘did the canister get it and do an update that I don’t have access to’ because you just assume that architecture from the beginning.
module {
let handleEvent = EventHandlerHelper([
("icrc1.transfer", onTransfer),
("icrc2.transfer", onApprove),
...
]);
var lastTransfer = 0;
var lastApprove = 0;
public shared(msg) handleEvent(eventId: Nat, publisherId: Principal, eventName: Text, payload: Candy.CandyValue){ //oneshot
handleEvent(eventID, publisherID, eventName, payload);
};
private func onTransfer(eventId: Nat, publisherId: Principal, eventName: Text, payload: Candy.CandyValue){
if(eventID != lastEvent+1){ //this won't work if you use a filter at the event system level
let missedEvents = eventSystem.catchUp(eventName, lastTransfer+1, eventID);
for(thisItem in missedEvents){
onTransfer(thisItem..);
}
};
//transfer logic
};
///etc
};
It certainly adds some state requirements and probably isn’t conducive to implementation in the replica, but I’d much rather be able to do the following then to have to write a bunch of retry code inline with the classic async/await approach:
public event({caller, msg}) some_namespace_schema{
//handle the event
}; //and even better if the replica/motoko took care of making sure I didn’t miss messages somehow.