Skip to content
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

Merged
merged 27 commits into from
Feb 14, 2022
Merged

New Event Subscription API #442

merged 27 commits into from
Feb 14, 2022

Conversation

jsdw
Copy link
Collaborator

@jsdw jsdw commented Feb 9, 2022

This PR introduces a new Event Subscription API, which aims to be simpler and more flexible.

Some notes:

  • Subscribing to events is now done using api.events().subscribe() or api.events.subscribe_finalized().
  • The EventSubscription you get back implements Stream, so you can apply StreamExt combinators to it.
  • The transaction stuff now leans on the same code to obtain events.
  • EventSubscription hands back Events 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).
  • We can always see the block_hash() that Events come from, and event indexes, as well as Phase.
  • Modify subscription examples to clarify separation from subscription and actually doing things, and show how the Stream api can be used to filter events.

closes #413

@jsdw jsdw marked this pull request as ready for review February 10, 2022 15:13
@jsdw jsdw requested review from ascjones and a team February 10, 2022 15:13
Copy link
Contributor

@dvdplm dvdplm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(partial review)

examples/examples/custom_type_derives.rs Show resolved Hide resolved
examples/examples/subscribe_all_events.rs Show resolved Hide resolved
examples/examples/subscribe_all_events.rs Show resolved Hide resolved
examples/examples/subscribe_all_events.rs Show resolved Hide resolved
Comment on lines 61 to 68
.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)
});
Copy link
Contributor

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.

Copy link
Collaborator Author

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 :)

Copy link
Collaborator Author

@jsdw jsdw Feb 11, 2022

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...)

Copy link
Contributor

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?

Copy link
Collaborator Author

@jsdw jsdw Feb 11, 2022

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!

Copy link
Contributor

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.

Copy link
Collaborator Author

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 :))

subxt/src/client.rs Show resolved Hide resolved
Copy link
Contributor

@ascjones ascjones left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WIP review

subxt/tests/integration/events.rs Show resolved Hide resolved
codegen/src/api/calls.rs Outdated Show resolved Hide resolved
examples/examples/subscribe_all_events.rs Show resolved Hide resolved
subxt/src/events.rs Show resolved Hide resolved
Copy link
Contributor

@ascjones ascjones left a 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 🙏

subxt/src/events.rs Outdated Show resolved Hide resolved
subxt/src/events.rs Show resolved Hide resolved
subxt/src/events.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@ascjones ascjones left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@dvdplm dvdplm left a 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.
Copy link
Contributor

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)?;
Copy link
Contributor

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)?

Copy link
Contributor

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) {
Copy link
Contributor

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?

Copy link
Collaborator Author

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!

Copy link
Contributor

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>> + '_ {
Copy link
Contributor

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?)?

/// A decoded event and associated details.
#[derive(Debug, Clone, PartialEq)]
pub struct EventDetails<Evs> {
/// When was the event produced?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// When was the event produced?
/// During which `[Phase]` was the event produced?

pub event: Evs,
}

/// The raw bytes for an event with associated details.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// 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.

Comment on lines +456 to +457
// topics come after the event data in EventRecord. They aren't used for
// anything at the moment, so just decode and throw them away.
Copy link
Contributor

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?

Copy link
Collaborator Author

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...)

let metadata = metadata::<Event>();

// Encode our events in the format we expect back from a node, and
// construst an Events object to iterate them:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// construst an Events object to iterate them:
// construct an Events object to iterate them:

subxt/src/events.rs Outdated Show resolved Hide resolved
subxt/src/transaction.rs Outdated Show resolved Hide resolved
@jsdw
Copy link
Collaborator Author

jsdw commented Feb 14, 2022

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.

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).

@jsdw jsdw merged commit b1b7173 into master Feb 14, 2022
@jsdw jsdw deleted the jsdw-event-subscription branch February 14, 2022 11:18
@lexnv lexnv mentioned this pull request Feb 14, 2022
0623forbidden pushed a commit to DEIPworld/substrate-subxt that referenced this pull request Feb 15, 2022
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Simplify and Streamify EventSubscription
4 participants