-
Notifications
You must be signed in to change notification settings - Fork 248
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New Event Subscription API #442
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(partial review)
.filter_map(|events| future::ready(events.ok())) | ||
// Map events to just those we care about: | ||
.flat_map(|events| { | ||
let transfer_events = events | ||
.find::<polkadot::balances::events::Transfer>() | ||
.collect::<Vec<_>>(); | ||
stream::iter(transfer_events) | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if these two are common enough usage patterns that it would make sense to have them packaged up into a utility method of some sort. It would be nice to be able to do let transfer_events = api.events().subscribe().to<events::Transfer>().await?;
or something along those lines.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had that exact thought; it felt like it would be a nice API to add and I did ponder it, but I wasn’t sure enough on how people actually would use events to know whether to go ahead and add it or wait for a feature request to come in :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I think one thing I started pondering was; how likely is it that people would care about plucking out more than one event type, and then, should I try to support that (harder to do) here to avoid people creating multiple subscriptions? In fact, a general optimisation would be to have only one shared subscription in the background for some of these things to make that more palettable...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In most cases I would use such API since I only care about one event. However here is a case where I need two events: https://github.com/paritytech/cargo-contract/blob/0e9ffe78947f458c89c5ee0ef25234a146b797d9/src/cmd/extrinsics/instantiate.rs#L246.
Maybe tuples of event types would be one idea?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tuples would be the ideal way to provide the type params to the function, but the return type would need to be more sum-type in nature as we'll get back just one of the possible types each iteration. I have a couple of thoughts, but I think it'd be good to merge this first and then I can add this functionality in a separate PR so that it can be given a little more thought!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Absolutely! Defo not a job for this PR was just brainstorming here since it was brought up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hehe, I suppose I was going to just add it if it was simple enough (ie the one-event variation)! Thanks for your thoughts; very useful :))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WIP review
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just brilliant, have wanted Stream
for events for a long time 🙏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, this is a big chunk of work, nice job.
One question I have is if it would make sense to separate the a static API from the dynamic one more clearly. It might not be obvious to a user of find()
that they are using dynamic decoding. OTOH forcing users to use, dunno, dynamic_find()
would be pretty annoying.
Some(Ok(block_header)) => { | ||
use sp_runtime::traits::Header; | ||
// Note [jsdw]: We may be able to get rid of the per-item allocation | ||
// with https://github.com/oblique/reusable-box-future. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That looks pretty neat.
let mut decode_one_event = || -> Result<_, BasicError> { | ||
let phase = Phase::decode(cursor)?; | ||
let ev = Evs::decode(cursor)?; | ||
let _topics = Vec::<T::Hash>::decode(cursor)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we just tossing these away? Can we do better (at later date)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Answering myself here: no we can't do better and topics are not used for anything in modern substrate chains
if start_len == 0 || self.num_events == index { | ||
None | ||
} else { | ||
match decode_raw_event_details::<T>(self.metadata, index, cursor) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How much slower is it to do this dynamically vs statically?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've no idea; it did cross my mind to benchmark this at some point but I haven't gotten around to it! As a completely random plucked-out-of-air guess, I'd guess maybe 5-10x slower!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should do some measurements of this so we can provide users with some guidelines and annotate the docs with "NOTE: if performance is a concern, consider using … … instead"?
|
||
/// Iterate through the events using metadata to dynamically decode and skip | ||
/// them, and return only those which should decode to the provided `Ev` type. | ||
pub fn find<Ev: Event>(&self) -> impl Iterator<Item = Result<Ev, BasicError>> + '_ { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth documenting how this behaves on error (returns None
after first error?)?
subxt/src/events.rs
Outdated
/// A decoded event and associated details. | ||
#[derive(Debug, Clone, PartialEq)] | ||
pub struct EventDetails<Evs> { | ||
/// When was the event produced? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// When was the event produced? | |
/// During which `[Phase]` was the event produced? |
subxt/src/events.rs
Outdated
pub event: Evs, | ||
} | ||
|
||
/// The raw bytes for an event with associated details. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// The raw bytes for an event with associated details. | |
/// The raw bytes for an event with associated details about where and when it was emitted. |
// topics come after the event data in EventRecord. They aren't used for | ||
// anything at the moment, so just decode and throw them away. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reading paritytech/substrate#2491 I wonder if topics
will become a topic again (sorry) now that smoldot is a thing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At least we properly decode them, so things won't break if they do start being used! (though I expect other stuff may have changed that would be breaking by then...)
subxt/src/events.rs
Outdated
let metadata = metadata::<Event>(); | ||
|
||
// Encode our events in the format we expect back from a node, and | ||
// construst an Events object to iterate them: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// construst an Events object to iterate them: | |
// construct an Events object to iterate them: |
I've added notes to all of the methods to try and make it clear. I guess I'm not too fussed about making the dynamic bit clear in those method names because there is only one option to choose from (dynamic), and dynamic is more lenient anyway, so there is no significant disadvantage to doing so (I'll have to benchmark the performance; think I'll add an issue for benchmarks). |
* Add reworked event types * first pass implementing event subscriptions * make clear that some methods are private * comment tidy * use Events in transaction stuff * align transaction and event APIs * remove __private_ prefixes; they are ugly * fix examples and remove old events and subscription code * better comments on hidden event functions * re-add find_first_event; it's used a bunch in tests and examples * cargo check --all-targets now passes * Fix up existing event tests * cargo fmt * change todo to note * clippy and doc niggles * revert to find_first_event * Add specific subscription related tests * cargo fmt * Update tests and add/fix examples * cargo fmt * add a little to subscribe_all_events example * cargo fmt * move an example comment * easy access to root mod for more clarity * add a couple of tests to ensure that events properly decoded until naff bytes * Simplify EventSubscription Stream impl a little * Address some PR feedback
This PR introduces a new Event Subscription API, which aims to be simpler and more flexible.
Some notes:
api.events().subscribe()
orapi.events.subscribe_finalized()
.EventSubscription
you get back implementsStream
, so you can applyStreamExt
combinators to it.EventSubscription
hands backEvents
objects, which contain yet-to-be-decoded event data. We can decode statically using the events from the generated runtime code, or dynamically to search for specific events (or just return the raw bytes for all).block_hash()
thatEvents
come from, and event indexes, as well asPhase
.closes #413