From de859e7396cf93f4a4e97eabac7b37c8dee0daff Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 18 Feb 2021 10:28:40 +0000 Subject: [PATCH] Add hooks to register event types for decoding (#227) * Global registration of type segmenters for event decoding * Perform type sizes check when building client * Introduce EventTypeRegistry for global runtime type sizes * Fmt * Register runtime type sizes on creation of EventTypeRegistry * Register more default dispatch types * Add missing type sizes * fmt * Fix up register_type_size builder method * Update doc comments * Make register_default_type_sizes public * Don't allow duplicate registered types * Remove call to supertraits type registration, done manually in Runtime * Fix tests and warnings * Fix duplicate type registration * Fmt * review: use is_empty() Co-authored-by: Niklas Adolfsson * Add panic docs Co-authored-by: Niklas Adolfsson --- .rustfmt.toml | 2 +- Cargo.toml | 1 + examples/transfer_subscribe.rs | 5 +- proc-macro/src/call.rs | 14 -- proc-macro/src/lib.rs | 9 +- proc-macro/src/module.rs | 44 ++--- proc-macro/tests/balances.rs | 5 +- src/error.rs | 7 + src/events.rs | 339 +++++++++++++++++++-------------- src/frame/balances.rs | 11 +- src/frame/contracts.rs | 10 +- src/frame/mod.rs | 11 +- src/frame/session.rs | 10 +- src/frame/staking.rs | 13 +- src/frame/sudo.rs | 5 +- src/lib.rs | 78 ++++++-- src/rpc.rs | 6 +- src/runtimes.rs | 138 +++++++++++++- src/subscription.rs | 8 +- 19 files changed, 436 insertions(+), 280 deletions(-) diff --git a/.rustfmt.toml b/.rustfmt.toml index 0aa02fb14f..60f5650170 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -19,7 +19,7 @@ fn_single_line = false where_single_line = false imports_indent = "Block" imports_layout = "Vertical" # changed -imports_granularity= "Crate" # changed +imports_granularity = "Crate" # changed reorder_imports = true reorder_modules = true reorder_impl_items = false diff --git a/Cargo.toml b/Cargo.toml index 32e835e555..09b994b33c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ codec = { package = "parity-scale-codec", version = "2.0.0", default-features = # temporarily pinning funty via codec -> bitvec until https://github.com/myrrlyn/funty/issues/3 # and https://github.com/bitvecto-rs/bitvec/issues/105 are resolved funty = "=1.1.0" +dyn-clone = "1.0.4" frame-metadata = "13.0.0" frame-support = "3.0.0" diff --git a/examples/transfer_subscribe.rs b/examples/transfer_subscribe.rs index 99a1c35c40..11487133ac 100644 --- a/examples/transfer_subscribe.rs +++ b/examples/transfer_subscribe.rs @@ -17,7 +17,6 @@ use sp_keyring::AccountKeyring; use substrate_subxt::{ balances::{ - BalancesEventsDecoder, TransferCallExt, TransferEvent, }, @@ -25,7 +24,6 @@ use substrate_subxt::{ ClientBuilder, DefaultNodeRuntime, EventSubscription, - EventsDecoder, PairSigner, }; @@ -38,8 +36,7 @@ async fn main() -> Result<(), Box> { let client = ClientBuilder::::new().build().await?; let sub = client.subscribe_events().await?; - let mut decoder = EventsDecoder::::new(client.metadata().clone()); - decoder.with_balances(); + let decoder = client.events_decoder(); let mut sub = EventSubscription::::new(sub, decoder); sub.filter_event::>(); client.transfer(&signer, &dest, 10_000).await?; diff --git a/proc-macro/src/call.rs b/proc-macro/src/call.rs index f522e3207d..1e7d6dbe0f 100644 --- a/proc-macro/src/call.rs +++ b/proc-macro/src/call.rs @@ -32,10 +32,6 @@ pub fn call(s: Structure) -> TokenStream { let generics = &s.ast().generics; let params = utils::type_params(generics); let module = utils::module_name(generics); - let with_module = format_ident!( - "with_{}", - utils::path_to_ident(module).to_string().to_snake_case() - ); let call_name = utils::ident_to_name(ident, "Call").to_snake_case(); let bindings = utils::bindings(&s); let fields = utils::fields(&bindings); @@ -51,11 +47,6 @@ pub fn call(s: Structure) -> TokenStream { impl#generics #subxt::Call for #ident<#(#params),*> { const MODULE: &'static str = MODULE; const FUNCTION: &'static str = #call_name; - fn events_decoder( - decoder: &mut #subxt::EventsDecoder, - ) { - decoder.#with_module(); - } } /// Call extension trait. @@ -118,11 +109,6 @@ mod tests { impl<'a, T: Balances> substrate_subxt::Call for TransferCall<'a, T> { const MODULE: &'static str = MODULE; const FUNCTION: &'static str = "transfer"; - fn events_decoder( - decoder: &mut substrate_subxt::EventsDecoder, - ) { - decoder.with_balances(); - } } /// Call extension trait. diff --git a/proc-macro/src/lib.rs b/proc-macro/src/lib.rs index 18005e6fe7..fee2ded501 100644 --- a/proc-macro/src/lib.rs +++ b/proc-macro/src/lib.rs @@ -62,17 +62,16 @@ use synstructure::{ /// /// const MODULE: &str = "Herd"; /// -/// // `EventsDecoder` extension trait. -/// pub trait HerdEventsDecoder { +/// // `EventTypeRegistry` extension trait. +/// pub trait HerdEventTypeRegistry { /// // Registers this modules types. /// fn with_herd(&mut self); /// } /// -/// impl HerdEventsDecoder for -/// substrate_subxt::EventsDecoder +/// impl EventTypeRegistry for +/// substrate_subxt::EventTypeRegistry /// { /// fn with_herd(&mut self) { -/// self.with_husbandry(); /// self.register_type_size::("Hooves"); /// self.register_type_size::("Wool"); /// } diff --git a/proc-macro/src/module.rs b/proc-macro/src/module.rs index 694979aea3..cc14d5b3b4 100644 --- a/proc-macro/src/module.rs +++ b/proc-macro/src/module.rs @@ -62,8 +62,8 @@ fn ignore(attrs: &[syn::Attribute]) -> bool { false } -fn events_decoder_trait_name(module: &syn::Ident) -> syn::Ident { - format_ident!("{}EventsDecoder", module.to_string()) +fn event_type_registry_trait_name(module: &syn::Ident) -> syn::Ident { + format_ident!("{}EventTypeRegistry", module.to_string()) } fn with_module_ident(module: &syn::Ident) -> syn::Ident { @@ -128,20 +128,9 @@ pub fn module(_args: TokenStream, tokens: TokenStream) -> TokenStream { let subxt = utils::use_crate("substrate-subxt"); let module = &input.ident; let module_name = module.to_string(); - let module_events_decoder = events_decoder_trait_name(module); + let module_events_type_registry = event_type_registry_trait_name(module); let with_module = with_module_ident(module); - let bounds = input.supertraits.iter().filter_map(|bound| { - if let syn::TypeParamBound::Trait(syn::TraitBound { path, .. }) = bound { - let module = utils::path_to_ident(path); - let with_module = with_module_ident(module); - Some(quote! { - self.#with_module(); - }) - } else { - None - } - }); let associated_types = input.items.iter().filter_map(|item| { if let syn::TraitItem::Type(ty) = item { if ignore(&ty.attrs) { @@ -168,17 +157,16 @@ pub fn module(_args: TokenStream, tokens: TokenStream) -> TokenStream { const MODULE: &str = #module_name; - /// `EventsDecoder` extension trait. - pub trait #module_events_decoder { + /// `EventTypeRegistry` extension trait. + pub trait #module_events_type_registry { /// Registers this modules types. fn #with_module(&mut self); } - impl #module_events_decoder for - #subxt::EventsDecoder + impl #module_events_type_registry for + #subxt::EventTypeRegistry { fn #with_module(&mut self) { - #(#bounds)* #(#associated_types)* #(#types)* } @@ -221,17 +209,16 @@ mod tests { const MODULE: &str = "Balances"; - /// `EventsDecoder` extension trait. - pub trait BalancesEventsDecoder { + /// `EventTypeRegistry` extension trait. + pub trait BalancesEventTypeRegistry { /// Registers this modules types. fn with_balances(&mut self); } - impl BalancesEventsDecoder for - substrate_subxt::EventsDecoder + impl BalancesEventTypeRegistry for + substrate_subxt::EventTypeRegistry { fn with_balances(&mut self) { - self.with_system(); self.register_type_size::("Balance"); } } @@ -262,17 +249,16 @@ mod tests { const MODULE: &str = "Herd"; - /// `EventsDecoder` extension trait. - pub trait HerdEventsDecoder { + /// `EventTypeRegistry` extension trait. + pub trait HerdEventTypeRegistry { /// Registers this modules types. fn with_herd(&mut self); } - impl HerdEventsDecoder for - substrate_subxt::EventsDecoder + impl HerdEventTypeRegistry for + substrate_subxt::EventTypeRegistry { fn with_herd(&mut self) { - self.with_husbandry(); self.register_type_size::("Hoves"); self.register_type_size::("Wool"); } diff --git a/proc-macro/tests/balances.rs b/proc-macro/tests/balances.rs index f1fe7473a9..9dd87d8e75 100644 --- a/proc-macro/tests/balances.rs +++ b/proc-macro/tests/balances.rs @@ -30,10 +30,7 @@ use substrate_subxt::{ MaybeSerialize, Member, }, - system::{ - System, - SystemEventsDecoder, - }, + system::System, ClientBuilder, KusamaRuntime, PairSigner, diff --git a/src/error.rs b/src/error.rs index 65549dc15c..65dacc2b60 100644 --- a/src/error.rs +++ b/src/error.rs @@ -57,6 +57,13 @@ pub enum Error { /// Metadata error. #[error("Metadata error: {0}")] Metadata(#[from] MetadataError), + /// Unregistered type sizes. + #[error( + "The following types do not have a type size registered: \ + {0:?} \ + Use `ClientBuilder::register_type_size` to register missing type sizes." + )] + MissingTypeSizes(Vec), /// Type size unavailable. #[error("Type size unavailable while decoding event: {0:?}")] TypeSizeUnavailable(String), diff --git a/src/events.rs b/src/events.rs index 30b1b85bbd..2cda82f0fe 100644 --- a/src/events.rs +++ b/src/events.rs @@ -22,14 +22,17 @@ use codec::{ Input, Output, }; -use frame_support::dispatch::DispatchInfo; +use dyn_clone::DynClone; use sp_runtime::{ DispatchError, DispatchResult, }; use std::{ collections::{ - HashMap, + hash_map::{ + Entry, + HashMap, + }, HashSet, }, fmt, @@ -49,6 +52,7 @@ use crate::{ Metadata, }, Phase, + Runtime, System, }; @@ -72,16 +76,18 @@ impl std::fmt::Debug for RawEvent { } } -trait TypeSegmenter: Send { +pub trait TypeSegmenter: DynClone + Send + Sync { /// Consumes an object from an input stream, and output the serialized bytes. fn segment(&self, input: &mut &[u8], output: &mut Vec) -> Result<(), Error>; } -#[derive(Default)] +// derive object safe Clone impl for `Box` +dyn_clone::clone_trait_object!(TypeSegmenter); + struct TypeMarker(PhantomData); impl TypeSegmenter for TypeMarker where - T: Codec + Send, + T: Codec + Send + Sync, { fn segment(&self, input: &mut &[u8], output: &mut Vec) -> Result<(), Error> { T::decode(input).map_err(Error::from)?.encode_to(output); @@ -89,158 +95,41 @@ where } } +impl Clone for TypeMarker { + fn clone(&self) -> Self { + Self(Default::default()) + } +} + +impl Default for TypeMarker { + fn default() -> Self { + Self(Default::default()) + } +} + /// Events decoder. +#[derive(Debug)] pub struct EventsDecoder { metadata: Metadata, - type_segmenters: HashMap>, - marker: PhantomData T>, + event_type_registry: EventTypeRegistry, } -impl fmt::Debug for EventsDecoder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("EventsDecoder") - .field("metadata", &self.metadata) - .field( - "type_segmenters", - &self.type_segmenters.keys().cloned().collect::(), - ) - .finish() +impl Clone for EventsDecoder { + fn clone(&self) -> Self { + Self { + metadata: self.metadata.clone(), + event_type_registry: self.event_type_registry.clone(), + } } } -impl EventsDecoder { +impl EventsDecoder { /// Creates a new `EventsDecoder`. - pub fn new(metadata: Metadata) -> Self { - let mut decoder = Self { + pub fn new(metadata: Metadata, event_type_registry: EventTypeRegistry) -> Self { + Self { metadata, - type_segmenters: HashMap::new(), - marker: PhantomData, - }; - // register default event arg type sizes for dynamic decoding of events - decoder.register_type_size::<()>("PhantomData"); - decoder.register_type_size::("DispatchInfo"); - decoder.register_type_size::("bool"); - decoder.register_type_size::("ReferendumIndex"); - decoder.register_type_size::<[u8; 16]>("Kind"); - decoder.register_type_size::<[u8; 32]>("AuthorityId"); - decoder.register_type_size::("u8"); - decoder.register_type_size::("u32"); - decoder.register_type_size::("u64"); - decoder.register_type_size::("u128"); - decoder.register_type_size::("AccountIndex"); - decoder.register_type_size::("SessionIndex"); - decoder.register_type_size::("PropIndex"); - decoder.register_type_size::("ProposalIndex"); - decoder.register_type_size::("AuthorityIndex"); - decoder.register_type_size::("AuthorityWeight"); - decoder.register_type_size::("MemberCount"); - decoder.register_type_size::("AccountId"); - decoder.register_type_size::("BlockNumber"); - decoder.register_type_size::("Hash"); - decoder.register_type_size::("VoteThreshold"); - // Additional types - decoder.register_type_size::<(T::BlockNumber, u32)>("TaskAddress"); - decoder - } - - /// Register a type. - pub fn register_type_size(&mut self, name: &str) -> usize - where - U: Default + Codec + Send + 'static, - { - let size = U::default().encode().len(); - // A segmenter decodes a type from an input stream (&mut &[u8]) and returns the serialized - // type to the output stream (&mut Vec). - self.type_segmenters - .insert(name.to_string(), Box::new(TypeMarker::::default())); - size - } - - /// Check missing type sizes. - pub fn check_missing_type_sizes(&self) { - let mut missing = HashSet::new(); - for module in self.metadata.modules_with_events() { - for event in module.events() { - for arg in event.arguments() { - for primitive in arg.primitives() { - if !self.type_segmenters.contains_key(&primitive) { - missing.insert(format!( - "{}::{}::{}", - module.name(), - event.name, - primitive - )); - } - } - } - } + event_type_registry, } - if !missing.is_empty() { - log::warn!( - "The following primitive types do not have registered sizes: {:?} \ - If any of these events are received, an error will occur since we cannot decode them", - missing - ); - } - } - - fn decode_raw_bytes( - &self, - args: &[EventArg], - input: &mut &[u8], - output: &mut W, - errors: &mut Vec, - ) -> Result<(), Error> { - for arg in args { - match arg { - EventArg::Vec(arg) => { - let len = >::decode(input)?; - len.encode_to(output); - for _ in 0..len.0 { - self.decode_raw_bytes(&[*arg.clone()], input, output, errors)? - } - } - EventArg::Option(arg) => { - match input.read_byte()? { - 0 => output.push_byte(0), - 1 => { - output.push_byte(1); - self.decode_raw_bytes(&[*arg.clone()], input, output, errors)? - } - _ => { - return Err(Error::Other( - "unexpected first byte decoding Option".into(), - )) - } - } - } - EventArg::Tuple(args) => { - self.decode_raw_bytes(args, input, output, errors)? - } - EventArg::Primitive(name) => { - let result = match name.as_str() { - "DispatchResult" => DispatchResult::decode(input)?, - "DispatchError" => Err(DispatchError::decode(input)?), - _ => { - if let Some(seg) = self.type_segmenters.get(name) { - let mut buf = Vec::::new(); - seg.segment(input, &mut buf)?; - output.write(&buf); - Ok(()) - } else { - return Err(Error::TypeSizeUnavailable(name.to_owned())) - } - } - }; - if let Err(error) = result { - // since the input may contain any number of args we propagate - // runtime errors to the caller for handling - errors.push(RuntimeError::from_dispatch(&self.metadata, error)?); - } - } - } - } - Ok(()) } /// Decode events. @@ -290,7 +179,7 @@ impl EventsDecoder { Err(err) => return Err(err), }; - if event_errors.len() == 0 { + if event_errors.is_empty() { r.push((phase.clone(), raw)); } @@ -300,6 +189,156 @@ impl EventsDecoder { } Ok(r) } + + fn decode_raw_bytes( + &self, + args: &[EventArg], + input: &mut &[u8], + output: &mut W, + errors: &mut Vec, + ) -> Result<(), Error> { + for arg in args { + match arg { + EventArg::Vec(arg) => { + let len = >::decode(input)?; + len.encode_to(output); + for _ in 0..len.0 { + self.decode_raw_bytes(&[*arg.clone()], input, output, errors)? + } + } + EventArg::Option(arg) => { + match input.read_byte()? { + 0 => output.push_byte(0), + 1 => { + output.push_byte(1); + self.decode_raw_bytes(&[*arg.clone()], input, output, errors)? + } + _ => { + return Err(Error::Other( + "unexpected first byte decoding Option".into(), + )) + } + } + } + EventArg::Tuple(args) => { + self.decode_raw_bytes(args, input, output, errors)? + } + EventArg::Primitive(name) => { + let result = match name.as_str() { + "DispatchResult" => DispatchResult::decode(input)?, + "DispatchError" => Err(DispatchError::decode(input)?), + _ => { + if let Some(seg) = self.event_type_registry.resolve(name) { + let mut buf = Vec::::new(); + seg.segment(input, &mut buf)?; + output.write(&buf); + Ok(()) + } else { + return Err(Error::TypeSizeUnavailable(name.to_owned())) + } + } + }; + if let Err(error) = result { + // since the input may contain any number of args we propagate + // runtime errors to the caller for handling + errors.push(RuntimeError::from_dispatch(&self.metadata, error)?); + } + } + } + } + Ok(()) + } +} + +/// Registry for event types which cannot be directly inferred from the metadata. +#[derive(Default)] +pub struct EventTypeRegistry { + segmenters: HashMap>, + marker: PhantomData T>, +} + +impl Clone for EventTypeRegistry { + fn clone(&self) -> Self { + Self { + segmenters: self.segmenters.clone(), + marker: PhantomData, + } + } +} + +impl fmt::Debug for EventTypeRegistry { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("EventTypeRegistry") + .field( + "segmenters", + &self.segmenters.keys().cloned().collect::(), + ) + .finish() + } +} + +impl EventTypeRegistry { + /// Create a new [`EventTypeRegistry`]. + pub fn new() -> Self { + let mut registry = Self { + segmenters: HashMap::new(), + marker: PhantomData, + }; + T::register_type_sizes(&mut registry); + registry + } + + /// Register a type. + /// + /// # Panics + /// + /// If there is already a type size registered with this name. + pub fn register_type_size(&mut self, name: &str) + where + U: Codec + Send + Sync + 'static, + { + // A segmenter decodes a type from an input stream (&mut &[u8]) and returns te serialized + // type to the output stream (&mut Vec). + match self.segmenters.entry(name.to_string()) { + Entry::Occupied(_) => panic!("Already a type registered with key {}", name), + Entry::Vacant(entry) => entry.insert(Box::new(TypeMarker::::default())), + }; + } + + /// Check missing type sizes. + pub fn check_missing_type_sizes( + &self, + metadata: &Metadata, + ) -> Result<(), HashSet> { + let mut missing = HashSet::new(); + for module in metadata.modules_with_events() { + for event in module.events() { + for arg in event.arguments() { + for primitive in arg.primitives() { + if !self.segmenters.contains_key(&primitive) { + missing.insert(format!( + "{}::{}::{}", + module.name(), + event.name, + primitive + )); + } + } + } + } + } + + if !missing.is_empty() { + Err(missing) + } else { + Ok(()) + } + } + + /// Resolve a segmenter for a type by its name. + pub fn resolve(&self, name: &str) -> Option<&Box> { + self.segmenters.get(name) + } } /// Raw event or error event @@ -331,7 +370,10 @@ mod tests { #[test] fn test_decode_option() { - let decoder = EventsDecoder::::new(Metadata::default()); + let decoder = EventsDecoder::::new( + Metadata::default(), + EventTypeRegistry::new(), + ); let value = Some(0u8); let input = value.encode(); @@ -419,6 +461,7 @@ mod tests { }), )) .unwrap(), + EventTypeRegistry::new(), ); // [(ApplyExtrinsic(0), Event(RawEvent { module: "System", variant: "ExtrinsicSuccess", data: "482d7c09000000000200" })), (ApplyExtrinsic(1), Error(Module(ModuleError { module: "System", error: "NonDefaultComposite" }))), (ApplyExtrinsic(2), Error(Module(ModuleError { module: "System", error: "NonDefaultComposite" })))] diff --git a/src/frame/balances.rs b/src/frame/balances.rs index edbf78e168..501eb8a4b8 100644 --- a/src/frame/balances.rs +++ b/src/frame/balances.rs @@ -16,10 +16,7 @@ //! Implements support for the pallet_balances module. -use crate::frame::system::{ - System, - SystemEventsDecoder, -}; +use crate::frame::system::System; use codec::{ Decode, Encode, @@ -159,7 +156,6 @@ mod tests { ModuleError, RuntimeError, }, - events::EventsDecoder, extrinsic::{ PairSigner, Signer, @@ -301,9 +297,8 @@ mod tests { let bob_addr = bob.clone().into(); let (client, _) = test_client().await; let sub = client.subscribe_events().await.unwrap(); - let mut decoder = EventsDecoder::::new(client.metadata().clone()); - decoder.with_balances(); - let mut sub = EventSubscription::::new(sub, decoder); + let decoder = client.events_decoder(); + let mut sub = EventSubscription::::new(sub, &decoder); sub.filter_event::>(); client.transfer(&alice, &bob_addr, 10_000).await.unwrap(); let raw = sub.next().await.unwrap().unwrap(); diff --git a/src/frame/contracts.rs b/src/frame/contracts.rs index cd06349349..b224bdd1af 100644 --- a/src/frame/contracts.rs +++ b/src/frame/contracts.rs @@ -17,14 +17,8 @@ //! Implements support for the pallet_contracts module. use crate::frame::{ - balances::{ - Balances, - BalancesEventsDecoder, - }, - system::{ - System, - SystemEventsDecoder, - }, + balances::Balances, + system::System, }; use codec::{ Decode, diff --git a/src/frame/mod.rs b/src/frame/mod.rs index 1e880d871b..3f5781f098 100644 --- a/src/frame/mod.rs +++ b/src/frame/mod.rs @@ -16,12 +16,9 @@ //! Implements support for built-in runtime modules. -use crate::{ - events::EventsDecoder, - metadata::{ - Metadata, - MetadataError, - }, +use crate::metadata::{ + Metadata, + MetadataError, }; use codec::{ Decode, @@ -63,8 +60,6 @@ pub trait Call: Encode { const MODULE: &'static str; /// Function name. const FUNCTION: &'static str; - /// Load event decoder. - fn events_decoder(_decoder: &mut EventsDecoder) {} } /// Event trait. diff --git a/src/frame/session.rs b/src/frame/session.rs index 371d7c724c..7971455893 100644 --- a/src/frame/session.rs +++ b/src/frame/session.rs @@ -16,14 +16,8 @@ //! Session support use crate::frame::{ - balances::{ - Balances, - BalancesEventsDecoder as _, - }, - system::{ - System, - SystemEventsDecoder as _, - }, + balances::Balances, + system::System, }; use codec::Encode; use frame_support::Parameter; diff --git a/src/frame/staking.rs b/src/frame/staking.rs index eb24d73de5..899db767e3 100644 --- a/src/frame/staking.rs +++ b/src/frame/staking.rs @@ -16,10 +16,7 @@ //! Implements support for the pallet_staking module. -use super::balances::{ - Balances, - BalancesEventsDecoder as _, -}; +use super::balances::Balances; use codec::{ Decode, Encode, @@ -62,20 +59,12 @@ pub struct SetPayeeCall { pub _runtime: PhantomData, } -/// Identity of a Grandpa authority. -pub type AuthorityId = crate::runtimes::app::grandpa::Public; -/// The weight of an authority. -pub type AuthorityWeight = u64; -/// A list of Grandpa authorities with associated weights. -pub type AuthorityList = Vec<(AuthorityId, AuthorityWeight)>; - /// The subset of the `frame::Trait` that a client must implement. #[module] #[rustfmt::skip] pub trait Staking: Balances { #![event_alias(ElectionCompute = u8)] #![event_type(EraIndex)] - #![event_type(AuthorityList)] } /// Number of eras to keep in history. diff --git a/src/frame/sudo.rs b/src/frame/sudo.rs index 3b4eb770a5..e0aee9f085 100644 --- a/src/frame/sudo.rs +++ b/src/frame/sudo.rs @@ -17,10 +17,7 @@ //! Implements support for the frame_sudo module. use crate::{ - frame::system::{ - System, - SystemEventsDecoder, - }, + frame::system::System, Encoded, }; use codec::Encode; diff --git a/src/lib.rs b/src/lib.rs index c483b3a81c..a7a5628aa0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,7 +49,10 @@ pub use substrate_subxt_client as client; pub use sp_core; pub use sp_runtime; -use codec::Decode; +use codec::{ + Codec, + Decode, +}; use futures::future; use jsonrpsee::client::Subscription; use sp_core::{ @@ -76,6 +79,7 @@ mod subscription; pub use crate::{ error::Error, events::{ + EventTypeRegistry, EventsDecoder, RawEvent, }, @@ -115,20 +119,22 @@ use crate::{ /// ClientBuilder for constructing a Client. #[derive(Default)] pub struct ClientBuilder { - _marker: std::marker::PhantomData, url: Option, client: Option, page_size: Option, + event_type_registry: EventTypeRegistry, + skip_type_sizes_check: bool, } impl ClientBuilder { /// Creates a new ClientBuilder. pub fn new() -> Self { Self { - _marker: std::marker::PhantomData, url: None, client: None, page_size: None, + event_type_registry: EventTypeRegistry::new(), + skip_type_sizes_check: false, } } @@ -150,8 +156,30 @@ impl ClientBuilder { self } + /// Register a custom type segmenter, for consuming types in events where the size cannot + /// be inferred from the metadata. + /// + /// # Panics + /// + /// If there is already a type size registered with this name. + pub fn register_type_size(mut self, name: &str) -> Self + where + U: Codec + Send + Sync + 'static, + { + self.event_type_registry.register_type_size::(name); + self + } + + /// Disable the check for missing type sizes on `build`. + /// + /// *WARNING* can lead to runtime errors if receiving events with unknown types. + pub fn skip_type_sizes_check(mut self) -> Self { + self.skip_type_sizes_check = false; + self + } + /// Creates a new Client. - pub async fn build(self) -> Result, Error> { + pub async fn build<'a>(self) -> Result, Error> { let client = if let Some(client) = self.client { client } else { @@ -170,10 +198,33 @@ impl ClientBuilder { rpc.system_properties(), ) .await; + let metadata = metadata?; + + if let Err(missing) = self.event_type_registry.check_missing_type_sizes(&metadata) + { + if self.skip_type_sizes_check { + log::warn!( + "The following types do not have registered type segmenters: {:?} \ + If any events containing these types are received, this can cause a \ + `TypeSizeUnavailable` error and prevent decoding the actual event \ + being listened for.\ + \ + Use `ClientBuilder::register_type_size` to register missing type sizes.", + missing + ); + } else { + return Err(Error::MissingTypeSizes(missing.into_iter().collect())) + } + } + + let events_decoder = + EventsDecoder::new(metadata.clone(), self.event_type_registry); + Ok(Client { rpc, genesis_hash: genesis_hash?, - metadata: metadata?, + metadata, + events_decoder, properties: properties.unwrap_or_else(|_| Default::default()), runtime_version: runtime_version?, _marker: PhantomData, @@ -187,6 +238,7 @@ pub struct Client { rpc: Rpc, genesis_hash: T::Hash, metadata: Metadata, + events_decoder: EventsDecoder, properties: SystemProperties, runtime_version: RuntimeVersion, _marker: PhantomData<(fn() -> T::Signature, T::Extra)>, @@ -199,6 +251,7 @@ impl Clone for Client { rpc: self.rpc.clone(), genesis_hash: self.genesis_hash, metadata: self.metadata.clone(), + events_decoder: self.events_decoder.clone(), properties: self.properties.clone(), runtime_version: self.runtime_version.clone(), _marker: PhantomData, @@ -466,12 +519,9 @@ impl Client { Ok(signed) } - /// Returns an events decoder for a call. - pub fn events_decoder>(&self) -> EventsDecoder { - let metadata = self.metadata().clone(); - let mut decoder = EventsDecoder::new(metadata); - C::events_decoder(&mut decoder); - decoder + /// Returns the events decoder. + pub fn events_decoder(&self) -> &EventsDecoder { + &self.events_decoder } /// Create and submit an extrinsic and return corresponding Hash if successful @@ -486,10 +536,9 @@ impl Client { pub async fn submit_and_watch_extrinsic( &self, extrinsic: UncheckedExtrinsic, - decoder: EventsDecoder, ) -> Result, Error> { self.rpc - .submit_and_watch_extrinsic(extrinsic, decoder) + .submit_and_watch_extrinsic(extrinsic, &self.events_decoder) .await } @@ -518,8 +567,7 @@ impl Client { Send + Sync, { let extrinsic = self.create_signed(call, signer).await?; - let decoder = self.events_decoder::(); - self.submit_and_watch_extrinsic(extrinsic, decoder).await + self.submit_and_watch_extrinsic(extrinsic).await } /// Insert a key into the keystore. diff --git a/src/rpc.rs b/src/rpc.rs index e3805297fb..6aa0e982c7 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -423,10 +423,10 @@ impl Rpc { } /// Create and submit an extrinsic and return corresponding Event if successful - pub async fn submit_and_watch_extrinsic( + pub async fn submit_and_watch_extrinsic<'a, E: Encode + 'static>( &self, extrinsic: E, - decoder: EventsDecoder, + decoder: &'a EventsDecoder, ) -> Result, Error> { let ext_hash = T::Hashing::hash_of(&extrinsic); log::info!("Submitting Extrinsic `{:?}`", ext_hash); @@ -465,7 +465,7 @@ impl Rpc { ext_hash, )) })?; - let mut sub = EventSubscription::new(events_sub, decoder); + let mut sub = EventSubscription::new(events_sub, &decoder); sub.filter_extrinsic(block_hash, ext_index); let mut events = vec![]; while let Some(event) = sub.next().await { diff --git a/src/runtimes.rs b/src/runtimes.rs index bcdcabadf2..fdb5476054 100644 --- a/src/runtimes.rs +++ b/src/runtimes.rs @@ -147,13 +147,30 @@ use crate::{ balances::{ AccountData, Balances, + BalancesEventTypeRegistry, + }, + contracts::{ + Contracts, + ContractsEventTypeRegistry, + }, + session::{ + Session, + SessionEventTypeRegistry, + }, + staking::{ + Staking, + StakingEventTypeRegistry, + }, + sudo::{ + Sudo, + SudoEventTypeRegistry, + }, + system::{ + System, + SystemEventTypeRegistry, }, - contracts::Contracts, - session::Session, - staking::Staking, - sudo::Sudo, - system::System, }, + EventTypeRegistry, }; /// Runtime trait. @@ -162,6 +179,9 @@ pub trait Runtime: System + Sized + Send + Sync + 'static { type Signature: Verify + Encode + Send + Sync + 'static; /// Transaction extras. type Extra: SignedExtra + Send + Sync + 'static; + + /// Register type sizes for this runtime + fn register_type_sizes(event_type_registry: &mut EventTypeRegistry); } /// Concrete type definitions compatible with those in the default substrate `node_runtime` @@ -178,6 +198,15 @@ impl Staking for DefaultNodeRuntime {} impl Runtime for DefaultNodeRuntime { type Signature = MultiSignature; type Extra = DefaultExtra; + + fn register_type_sizes(event_type_registry: &mut EventTypeRegistry) { + event_type_registry.with_system(); + event_type_registry.with_balances(); + event_type_registry.with_session(); + event_type_registry.with_contracts(); + event_type_registry.with_sudo(); + register_default_type_sizes(event_type_registry); + } } impl System for DefaultNodeRuntime { @@ -217,6 +246,14 @@ pub struct NodeTemplateRuntime; impl Runtime for NodeTemplateRuntime { type Signature = MultiSignature; type Extra = DefaultExtra; + + fn register_type_sizes(event_type_registry: &mut EventTypeRegistry) { + event_type_registry.with_system(); + event_type_registry.with_balances(); + event_type_registry.with_session(); + event_type_registry.with_sudo(); + register_default_type_sizes(event_type_registry); + } } impl System for NodeTemplateRuntime { @@ -253,6 +290,14 @@ pub struct ContractsTemplateRuntime; impl Runtime for ContractsTemplateRuntime { type Signature = ::Signature; type Extra = DefaultExtra; + + fn register_type_sizes(event_type_registry: &mut EventTypeRegistry) { + event_type_registry.with_system(); + event_type_registry.with_balances(); + event_type_registry.with_contracts(); + event_type_registry.with_sudo(); + register_default_type_sizes(event_type_registry); + } } impl System for ContractsTemplateRuntime { @@ -287,6 +332,14 @@ pub struct KusamaRuntime; impl Runtime for KusamaRuntime { type Signature = MultiSignature; type Extra = DefaultExtra; + + fn register_type_sizes(event_type_registry: &mut EventTypeRegistry) { + event_type_registry.with_system(); + event_type_registry.with_balances(); + event_type_registry.with_session(); + event_type_registry.with_staking(); + register_default_type_sizes(event_type_registry); + } } impl System for KusamaRuntime { @@ -311,3 +364,78 @@ impl Staking for KusamaRuntime {} impl Balances for KusamaRuntime { type Balance = u128; } + +/// Identity of a Grandpa authority. +pub type AuthorityId = crate::runtimes::app::grandpa::Public; +/// The weight of an authority. +pub type AuthorityWeight = u64; +/// A list of Grandpa authorities with associated weights. +pub type AuthorityList = Vec<(AuthorityId, AuthorityWeight)>; + +/// Register default common runtime type sizes +pub fn register_default_type_sizes( + event_type_registry: &mut EventTypeRegistry, +) { + // primitives + event_type_registry.register_type_size::("bool"); + event_type_registry.register_type_size::("u8"); + event_type_registry.register_type_size::("u32"); + event_type_registry.register_type_size::("u64"); + event_type_registry.register_type_size::("u128"); + + event_type_registry.register_type_size::<()>("PhantomData"); + + // frame_support types + event_type_registry + .register_type_size::("DispatchInfo"); + event_type_registry + .register_type_size::("DispatchResult"); + event_type_registry + .register_type_size::("DispatchError"); + event_type_registry + .register_type_size::("Status"); + + // aliases etc. + event_type_registry.register_type_size::("ReferendumIndex"); + event_type_registry.register_type_size::<[u8; 16]>("Kind"); + + event_type_registry.register_type_size::("AccountIndex"); + event_type_registry.register_type_size::("PropIndex"); + event_type_registry.register_type_size::("ProposalIndex"); + event_type_registry.register_type_size::("AuthorityIndex"); + event_type_registry.register_type_size::("MemberCount"); + + event_type_registry.register_type_size::("VoteThreshold"); + event_type_registry + .register_type_size::<(T::BlockNumber, u32)>("TaskAddress"); + + event_type_registry.register_type_size::("AuthorityId"); + event_type_registry.register_type_size::("AuthorityWeight"); + event_type_registry + .register_type_size::>("AuthorityList"); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_register_default_runtime_type_sizes() { + EventTypeRegistry::::new(); + } + + #[test] + fn can_register_node_template_runtime_type_sizes() { + EventTypeRegistry::::new(); + } + + #[test] + fn can_register_contracts_template_runtime_type_sizes() { + EventTypeRegistry::::new(); + } + + #[test] + fn can_register_kusama_runtime_type_sizes() { + EventTypeRegistry::::new(); + } +} diff --git a/src/subscription.rs b/src/subscription.rs index 9f8b8ca710..aa74852199 100644 --- a/src/subscription.rs +++ b/src/subscription.rs @@ -34,9 +34,9 @@ use crate::{ /// Event subscription simplifies filtering a storage change set stream for /// events of interest. -pub struct EventSubscription { +pub struct EventSubscription<'a, T: Runtime> { subscription: Subscription>, - decoder: EventsDecoder, + decoder: &'a EventsDecoder, block: Option, extrinsic: Option, event: Option<(&'static str, &'static str)>, @@ -44,11 +44,11 @@ pub struct EventSubscription { finished: bool, } -impl EventSubscription { +impl<'a, T: Runtime> EventSubscription<'a, T> { /// Creates a new event subscription. pub fn new( subscription: Subscription>, - decoder: EventsDecoder, + decoder: &'a EventsDecoder, ) -> Self { Self { subscription,