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 <niklasadolfsson1@gmail.com>

* Add panic docs

Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
This commit is contained in:
Andrew Jones
2021-02-18 10:28:40 +00:00
committed by GitHub
parent 2c8e5211aa
commit de859e7396
19 changed files with 436 additions and 280 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ fn_single_line = false
where_single_line = false where_single_line = false
imports_indent = "Block" imports_indent = "Block"
imports_layout = "Vertical" # changed imports_layout = "Vertical" # changed
imports_granularity= "Crate" # changed imports_granularity = "Crate" # changed
reorder_imports = true reorder_imports = true
reorder_modules = true reorder_modules = true
reorder_impl_items = false reorder_impl_items = false
+1
View File
@@ -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 # 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 # and https://github.com/bitvecto-rs/bitvec/issues/105 are resolved
funty = "=1.1.0" funty = "=1.1.0"
dyn-clone = "1.0.4"
frame-metadata = "13.0.0" frame-metadata = "13.0.0"
frame-support = "3.0.0" frame-support = "3.0.0"
+1 -4
View File
@@ -17,7 +17,6 @@
use sp_keyring::AccountKeyring; use sp_keyring::AccountKeyring;
use substrate_subxt::{ use substrate_subxt::{
balances::{ balances::{
BalancesEventsDecoder,
TransferCallExt, TransferCallExt,
TransferEvent, TransferEvent,
}, },
@@ -25,7 +24,6 @@ use substrate_subxt::{
ClientBuilder, ClientBuilder,
DefaultNodeRuntime, DefaultNodeRuntime,
EventSubscription, EventSubscription,
EventsDecoder,
PairSigner, PairSigner,
}; };
@@ -38,8 +36,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = ClientBuilder::<DefaultNodeRuntime>::new().build().await?; let client = ClientBuilder::<DefaultNodeRuntime>::new().build().await?;
let sub = client.subscribe_events().await?; let sub = client.subscribe_events().await?;
let mut decoder = EventsDecoder::<DefaultNodeRuntime>::new(client.metadata().clone()); let decoder = client.events_decoder();
decoder.with_balances();
let mut sub = EventSubscription::<DefaultNodeRuntime>::new(sub, decoder); let mut sub = EventSubscription::<DefaultNodeRuntime>::new(sub, decoder);
sub.filter_event::<TransferEvent<_>>(); sub.filter_event::<TransferEvent<_>>();
client.transfer(&signer, &dest, 10_000).await?; client.transfer(&signer, &dest, 10_000).await?;
-14
View File
@@ -32,10 +32,6 @@ pub fn call(s: Structure) -> TokenStream {
let generics = &s.ast().generics; let generics = &s.ast().generics;
let params = utils::type_params(generics); let params = utils::type_params(generics);
let module = utils::module_name(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 call_name = utils::ident_to_name(ident, "Call").to_snake_case();
let bindings = utils::bindings(&s); let bindings = utils::bindings(&s);
let fields = utils::fields(&bindings); let fields = utils::fields(&bindings);
@@ -51,11 +47,6 @@ pub fn call(s: Structure) -> TokenStream {
impl#generics #subxt::Call<T> for #ident<#(#params),*> { impl#generics #subxt::Call<T> for #ident<#(#params),*> {
const MODULE: &'static str = MODULE; const MODULE: &'static str = MODULE;
const FUNCTION: &'static str = #call_name; const FUNCTION: &'static str = #call_name;
fn events_decoder(
decoder: &mut #subxt::EventsDecoder<T>,
) {
decoder.#with_module();
}
} }
/// Call extension trait. /// Call extension trait.
@@ -118,11 +109,6 @@ mod tests {
impl<'a, T: Balances> substrate_subxt::Call<T> for TransferCall<'a, T> { impl<'a, T: Balances> substrate_subxt::Call<T> for TransferCall<'a, T> {
const MODULE: &'static str = MODULE; const MODULE: &'static str = MODULE;
const FUNCTION: &'static str = "transfer"; const FUNCTION: &'static str = "transfer";
fn events_decoder(
decoder: &mut substrate_subxt::EventsDecoder<T>,
) {
decoder.with_balances();
}
} }
/// Call extension trait. /// Call extension trait.
+4 -5
View File
@@ -62,17 +62,16 @@ use synstructure::{
/// ///
/// const MODULE: &str = "Herd"; /// const MODULE: &str = "Herd";
/// ///
/// // `EventsDecoder` extension trait. /// // `EventTypeRegistry` extension trait.
/// pub trait HerdEventsDecoder { /// pub trait HerdEventTypeRegistry {
/// // Registers this modules types. /// // Registers this modules types.
/// fn with_herd(&mut self); /// fn with_herd(&mut self);
/// } /// }
/// ///
/// impl<T: Herd> HerdEventsDecoder for /// impl<T: Herd + Runtime> EventTypeRegistry for
/// substrate_subxt::EventsDecoder<T> /// substrate_subxt::EventTypeRegistry<T>
/// { /// {
/// fn with_herd(&mut self) { /// fn with_herd(&mut self) {
/// self.with_husbandry();
/// self.register_type_size::<T::Hooves>("Hooves"); /// self.register_type_size::<T::Hooves>("Hooves");
/// self.register_type_size::<T::Wool>("Wool"); /// self.register_type_size::<T::Wool>("Wool");
/// } /// }
+15 -29
View File
@@ -62,8 +62,8 @@ fn ignore(attrs: &[syn::Attribute]) -> bool {
false false
} }
fn events_decoder_trait_name(module: &syn::Ident) -> syn::Ident { fn event_type_registry_trait_name(module: &syn::Ident) -> syn::Ident {
format_ident!("{}EventsDecoder", module.to_string()) format_ident!("{}EventTypeRegistry", module.to_string())
} }
fn with_module_ident(module: &syn::Ident) -> syn::Ident { 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 subxt = utils::use_crate("substrate-subxt");
let module = &input.ident; let module = &input.ident;
let module_name = module.to_string(); 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 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| { let associated_types = input.items.iter().filter_map(|item| {
if let syn::TraitItem::Type(ty) = item { if let syn::TraitItem::Type(ty) = item {
if ignore(&ty.attrs) { if ignore(&ty.attrs) {
@@ -168,17 +157,16 @@ pub fn module(_args: TokenStream, tokens: TokenStream) -> TokenStream {
const MODULE: &str = #module_name; const MODULE: &str = #module_name;
/// `EventsDecoder` extension trait. /// `EventTypeRegistry` extension trait.
pub trait #module_events_decoder { pub trait #module_events_type_registry {
/// Registers this modules types. /// Registers this modules types.
fn #with_module(&mut self); fn #with_module(&mut self);
} }
impl<T: #module> #module_events_decoder for impl<T: #module + #subxt::Runtime> #module_events_type_registry for
#subxt::EventsDecoder<T> #subxt::EventTypeRegistry<T>
{ {
fn #with_module(&mut self) { fn #with_module(&mut self) {
#(#bounds)*
#(#associated_types)* #(#associated_types)*
#(#types)* #(#types)*
} }
@@ -221,17 +209,16 @@ mod tests {
const MODULE: &str = "Balances"; const MODULE: &str = "Balances";
/// `EventsDecoder` extension trait. /// `EventTypeRegistry` extension trait.
pub trait BalancesEventsDecoder { pub trait BalancesEventTypeRegistry {
/// Registers this modules types. /// Registers this modules types.
fn with_balances(&mut self); fn with_balances(&mut self);
} }
impl<T: Balances> BalancesEventsDecoder for impl<T: Balances + substrate_subxt::Runtime> BalancesEventTypeRegistry for
substrate_subxt::EventsDecoder<T> substrate_subxt::EventTypeRegistry<T>
{ {
fn with_balances(&mut self) { fn with_balances(&mut self) {
self.with_system();
self.register_type_size::<T::Balance>("Balance"); self.register_type_size::<T::Balance>("Balance");
} }
} }
@@ -262,17 +249,16 @@ mod tests {
const MODULE: &str = "Herd"; const MODULE: &str = "Herd";
/// `EventsDecoder` extension trait. /// `EventTypeRegistry` extension trait.
pub trait HerdEventsDecoder { pub trait HerdEventTypeRegistry {
/// Registers this modules types. /// Registers this modules types.
fn with_herd(&mut self); fn with_herd(&mut self);
} }
impl<T: Herd> HerdEventsDecoder for impl<T: Herd + substrate_subxt::Runtime> HerdEventTypeRegistry for
substrate_subxt::EventsDecoder<T> substrate_subxt::EventTypeRegistry<T>
{ {
fn with_herd(&mut self) { fn with_herd(&mut self) {
self.with_husbandry();
self.register_type_size::<T::Hoves>("Hoves"); self.register_type_size::<T::Hoves>("Hoves");
self.register_type_size::<T::Wool>("Wool"); self.register_type_size::<T::Wool>("Wool");
} }
+1 -4
View File
@@ -30,10 +30,7 @@ use substrate_subxt::{
MaybeSerialize, MaybeSerialize,
Member, Member,
}, },
system::{ system::System,
System,
SystemEventsDecoder,
},
ClientBuilder, ClientBuilder,
KusamaRuntime, KusamaRuntime,
PairSigner, PairSigner,
+7
View File
@@ -57,6 +57,13 @@ pub enum Error {
/// Metadata error. /// Metadata error.
#[error("Metadata error: {0}")] #[error("Metadata error: {0}")]
Metadata(#[from] MetadataError), 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<String>),
/// Type size unavailable. /// Type size unavailable.
#[error("Type size unavailable while decoding event: {0:?}")] #[error("Type size unavailable while decoding event: {0:?}")]
TypeSizeUnavailable(String), TypeSizeUnavailable(String),
+191 -148
View File
@@ -22,14 +22,17 @@ use codec::{
Input, Input,
Output, Output,
}; };
use frame_support::dispatch::DispatchInfo; use dyn_clone::DynClone;
use sp_runtime::{ use sp_runtime::{
DispatchError, DispatchError,
DispatchResult, DispatchResult,
}; };
use std::{ use std::{
collections::{ collections::{
HashMap, hash_map::{
Entry,
HashMap,
},
HashSet, HashSet,
}, },
fmt, fmt,
@@ -49,6 +52,7 @@ use crate::{
Metadata, Metadata,
}, },
Phase, Phase,
Runtime,
System, 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. /// Consumes an object from an input stream, and output the serialized bytes.
fn segment(&self, input: &mut &[u8], output: &mut Vec<u8>) -> Result<(), Error>; fn segment(&self, input: &mut &[u8], output: &mut Vec<u8>) -> Result<(), Error>;
} }
#[derive(Default)] // derive object safe Clone impl for `Box<dyn TypeSegmenter>`
dyn_clone::clone_trait_object!(TypeSegmenter);
struct TypeMarker<T>(PhantomData<T>); struct TypeMarker<T>(PhantomData<T>);
impl<T> TypeSegmenter for TypeMarker<T> impl<T> TypeSegmenter for TypeMarker<T>
where where
T: Codec + Send, T: Codec + Send + Sync,
{ {
fn segment(&self, input: &mut &[u8], output: &mut Vec<u8>) -> Result<(), Error> { fn segment(&self, input: &mut &[u8], output: &mut Vec<u8>) -> Result<(), Error> {
T::decode(input).map_err(Error::from)?.encode_to(output); T::decode(input).map_err(Error::from)?.encode_to(output);
@@ -89,158 +95,41 @@ where
} }
} }
impl<T> Clone for TypeMarker<T> {
fn clone(&self) -> Self {
Self(Default::default())
}
}
impl<T> Default for TypeMarker<T> {
fn default() -> Self {
Self(Default::default())
}
}
/// Events decoder. /// Events decoder.
#[derive(Debug)]
pub struct EventsDecoder<T> { pub struct EventsDecoder<T> {
metadata: Metadata, metadata: Metadata,
type_segmenters: HashMap<String, Box<dyn TypeSegmenter>>, event_type_registry: EventTypeRegistry<T>,
marker: PhantomData<fn() -> T>,
} }
impl<T> fmt::Debug for EventsDecoder<T> { impl<T> Clone for EventsDecoder<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn clone(&self) -> Self {
f.debug_struct("EventsDecoder<T>") Self {
.field("metadata", &self.metadata) metadata: self.metadata.clone(),
.field( event_type_registry: self.event_type_registry.clone(),
"type_segmenters", }
&self.type_segmenters.keys().cloned().collect::<String>(),
)
.finish()
} }
} }
impl<T: System> EventsDecoder<T> { impl<T: Runtime + System> EventsDecoder<T> {
/// Creates a new `EventsDecoder`. /// Creates a new `EventsDecoder`.
pub fn new(metadata: Metadata) -> Self { pub fn new(metadata: Metadata, event_type_registry: EventTypeRegistry<T>) -> Self {
let mut decoder = Self { Self {
metadata, metadata,
type_segmenters: HashMap::new(), event_type_registry,
marker: PhantomData,
};
// register default event arg type sizes for dynamic decoding of events
decoder.register_type_size::<()>("PhantomData");
decoder.register_type_size::<DispatchInfo>("DispatchInfo");
decoder.register_type_size::<bool>("bool");
decoder.register_type_size::<u32>("ReferendumIndex");
decoder.register_type_size::<[u8; 16]>("Kind");
decoder.register_type_size::<[u8; 32]>("AuthorityId");
decoder.register_type_size::<u8>("u8");
decoder.register_type_size::<u32>("u32");
decoder.register_type_size::<u64>("u64");
decoder.register_type_size::<u128>("u128");
decoder.register_type_size::<u32>("AccountIndex");
decoder.register_type_size::<u32>("SessionIndex");
decoder.register_type_size::<u32>("PropIndex");
decoder.register_type_size::<u32>("ProposalIndex");
decoder.register_type_size::<u32>("AuthorityIndex");
decoder.register_type_size::<u64>("AuthorityWeight");
decoder.register_type_size::<u32>("MemberCount");
decoder.register_type_size::<T::AccountId>("AccountId");
decoder.register_type_size::<T::BlockNumber>("BlockNumber");
decoder.register_type_size::<T::Hash>("Hash");
decoder.register_type_size::<u8>("VoteThreshold");
// Additional types
decoder.register_type_size::<(T::BlockNumber, u32)>("TaskAddress<BlockNumber>");
decoder
}
/// Register a type.
pub fn register_type_size<U>(&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<u8>).
self.type_segmenters
.insert(name.to_string(), Box::new(TypeMarker::<U>::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
));
}
}
}
}
} }
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<W: Output>(
&self,
args: &[EventArg],
input: &mut &[u8],
output: &mut W,
errors: &mut Vec<RuntimeError>,
) -> Result<(), Error> {
for arg in args {
match arg {
EventArg::Vec(arg) => {
let len = <Compact<u32>>::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::<u8>::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. /// Decode events.
@@ -290,7 +179,7 @@ impl<T: System> EventsDecoder<T> {
Err(err) => return Err(err), Err(err) => return Err(err),
}; };
if event_errors.len() == 0 { if event_errors.is_empty() {
r.push((phase.clone(), raw)); r.push((phase.clone(), raw));
} }
@@ -300,6 +189,156 @@ impl<T: System> EventsDecoder<T> {
} }
Ok(r) Ok(r)
} }
fn decode_raw_bytes<W: Output>(
&self,
args: &[EventArg],
input: &mut &[u8],
output: &mut W,
errors: &mut Vec<RuntimeError>,
) -> Result<(), Error> {
for arg in args {
match arg {
EventArg::Vec(arg) => {
let len = <Compact<u32>>::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::<u8>::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<T> {
segmenters: HashMap<String, Box<dyn TypeSegmenter>>,
marker: PhantomData<fn() -> T>,
}
impl<T> Clone for EventTypeRegistry<T> {
fn clone(&self) -> Self {
Self {
segmenters: self.segmenters.clone(),
marker: PhantomData,
}
}
}
impl<T> fmt::Debug for EventTypeRegistry<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventTypeRegistry")
.field(
"segmenters",
&self.segmenters.keys().cloned().collect::<String>(),
)
.finish()
}
}
impl<T: Runtime> EventTypeRegistry<T> {
/// 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<U>(&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<u8>).
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::<U>::default())),
};
}
/// Check missing type sizes.
pub fn check_missing_type_sizes(
&self,
metadata: &Metadata,
) -> Result<(), HashSet<String>> {
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<dyn TypeSegmenter>> {
self.segmenters.get(name)
}
} }
/// Raw event or error event /// Raw event or error event
@@ -331,7 +370,10 @@ mod tests {
#[test] #[test]
fn test_decode_option() { fn test_decode_option() {
let decoder = EventsDecoder::<TestRuntime>::new(Metadata::default()); let decoder = EventsDecoder::<TestRuntime>::new(
Metadata::default(),
EventTypeRegistry::new(),
);
let value = Some(0u8); let value = Some(0u8);
let input = value.encode(); let input = value.encode();
@@ -419,6 +461,7 @@ mod tests {
}), }),
)) ))
.unwrap(), .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" })))] // [(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" })))]
+3 -8
View File
@@ -16,10 +16,7 @@
//! Implements support for the pallet_balances module. //! Implements support for the pallet_balances module.
use crate::frame::system::{ use crate::frame::system::System;
System,
SystemEventsDecoder,
};
use codec::{ use codec::{
Decode, Decode,
Encode, Encode,
@@ -159,7 +156,6 @@ mod tests {
ModuleError, ModuleError,
RuntimeError, RuntimeError,
}, },
events::EventsDecoder,
extrinsic::{ extrinsic::{
PairSigner, PairSigner,
Signer, Signer,
@@ -301,9 +297,8 @@ mod tests {
let bob_addr = bob.clone().into(); let bob_addr = bob.clone().into();
let (client, _) = test_client().await; let (client, _) = test_client().await;
let sub = client.subscribe_events().await.unwrap(); let sub = client.subscribe_events().await.unwrap();
let mut decoder = EventsDecoder::<TestRuntime>::new(client.metadata().clone()); let decoder = client.events_decoder();
decoder.with_balances(); let mut sub = EventSubscription::<TestRuntime>::new(sub, &decoder);
let mut sub = EventSubscription::<TestRuntime>::new(sub, decoder);
sub.filter_event::<TransferEvent<_>>(); sub.filter_event::<TransferEvent<_>>();
client.transfer(&alice, &bob_addr, 10_000).await.unwrap(); client.transfer(&alice, &bob_addr, 10_000).await.unwrap();
let raw = sub.next().await.unwrap().unwrap(); let raw = sub.next().await.unwrap().unwrap();
+2 -8
View File
@@ -17,14 +17,8 @@
//! Implements support for the pallet_contracts module. //! Implements support for the pallet_contracts module.
use crate::frame::{ use crate::frame::{
balances::{ balances::Balances,
Balances, system::System,
BalancesEventsDecoder,
},
system::{
System,
SystemEventsDecoder,
},
}; };
use codec::{ use codec::{
Decode, Decode,
+3 -8
View File
@@ -16,12 +16,9 @@
//! Implements support for built-in runtime modules. //! Implements support for built-in runtime modules.
use crate::{ use crate::metadata::{
events::EventsDecoder, Metadata,
metadata::{ MetadataError,
Metadata,
MetadataError,
},
}; };
use codec::{ use codec::{
Decode, Decode,
@@ -63,8 +60,6 @@ pub trait Call<T>: Encode {
const MODULE: &'static str; const MODULE: &'static str;
/// Function name. /// Function name.
const FUNCTION: &'static str; const FUNCTION: &'static str;
/// Load event decoder.
fn events_decoder(_decoder: &mut EventsDecoder<T>) {}
} }
/// Event trait. /// Event trait.
+2 -8
View File
@@ -16,14 +16,8 @@
//! Session support //! Session support
use crate::frame::{ use crate::frame::{
balances::{ balances::Balances,
Balances, system::System,
BalancesEventsDecoder as _,
},
system::{
System,
SystemEventsDecoder as _,
},
}; };
use codec::Encode; use codec::Encode;
use frame_support::Parameter; use frame_support::Parameter;
+1 -12
View File
@@ -16,10 +16,7 @@
//! Implements support for the pallet_staking module. //! Implements support for the pallet_staking module.
use super::balances::{ use super::balances::Balances;
Balances,
BalancesEventsDecoder as _,
};
use codec::{ use codec::{
Decode, Decode,
Encode, Encode,
@@ -62,20 +59,12 @@ pub struct SetPayeeCall<T: Staking> {
pub _runtime: PhantomData<T>, pub _runtime: PhantomData<T>,
} }
/// 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. /// The subset of the `frame::Trait` that a client must implement.
#[module] #[module]
#[rustfmt::skip] #[rustfmt::skip]
pub trait Staking: Balances { pub trait Staking: Balances {
#![event_alias(ElectionCompute = u8)] #![event_alias(ElectionCompute = u8)]
#![event_type(EraIndex)] #![event_type(EraIndex)]
#![event_type(AuthorityList)]
} }
/// Number of eras to keep in history. /// Number of eras to keep in history.
+1 -4
View File
@@ -17,10 +17,7 @@
//! Implements support for the frame_sudo module. //! Implements support for the frame_sudo module.
use crate::{ use crate::{
frame::system::{ frame::system::System,
System,
SystemEventsDecoder,
},
Encoded, Encoded,
}; };
use codec::Encode; use codec::Encode;
+63 -15
View File
@@ -49,7 +49,10 @@ pub use substrate_subxt_client as client;
pub use sp_core; pub use sp_core;
pub use sp_runtime; pub use sp_runtime;
use codec::Decode; use codec::{
Codec,
Decode,
};
use futures::future; use futures::future;
use jsonrpsee::client::Subscription; use jsonrpsee::client::Subscription;
use sp_core::{ use sp_core::{
@@ -76,6 +79,7 @@ mod subscription;
pub use crate::{ pub use crate::{
error::Error, error::Error,
events::{ events::{
EventTypeRegistry,
EventsDecoder, EventsDecoder,
RawEvent, RawEvent,
}, },
@@ -115,20 +119,22 @@ use crate::{
/// ClientBuilder for constructing a Client. /// ClientBuilder for constructing a Client.
#[derive(Default)] #[derive(Default)]
pub struct ClientBuilder<T: Runtime> { pub struct ClientBuilder<T: Runtime> {
_marker: std::marker::PhantomData<T>,
url: Option<String>, url: Option<String>,
client: Option<jsonrpsee::Client>, client: Option<jsonrpsee::Client>,
page_size: Option<u32>, page_size: Option<u32>,
event_type_registry: EventTypeRegistry<T>,
skip_type_sizes_check: bool,
} }
impl<T: Runtime> ClientBuilder<T> { impl<T: Runtime> ClientBuilder<T> {
/// Creates a new ClientBuilder. /// Creates a new ClientBuilder.
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
_marker: std::marker::PhantomData,
url: None, url: None,
client: None, client: None,
page_size: None, page_size: None,
event_type_registry: EventTypeRegistry::new(),
skip_type_sizes_check: false,
} }
} }
@@ -150,8 +156,30 @@ impl<T: Runtime> ClientBuilder<T> {
self 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<U>(mut self, name: &str) -> Self
where
U: Codec + Send + Sync + 'static,
{
self.event_type_registry.register_type_size::<U>(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. /// Creates a new Client.
pub async fn build(self) -> Result<Client<T>, Error> { pub async fn build<'a>(self) -> Result<Client<T>, Error> {
let client = if let Some(client) = self.client { let client = if let Some(client) = self.client {
client client
} else { } else {
@@ -170,10 +198,33 @@ impl<T: Runtime> ClientBuilder<T> {
rpc.system_properties(), rpc.system_properties(),
) )
.await; .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 { Ok(Client {
rpc, rpc,
genesis_hash: genesis_hash?, genesis_hash: genesis_hash?,
metadata: metadata?, metadata,
events_decoder,
properties: properties.unwrap_or_else(|_| Default::default()), properties: properties.unwrap_or_else(|_| Default::default()),
runtime_version: runtime_version?, runtime_version: runtime_version?,
_marker: PhantomData, _marker: PhantomData,
@@ -187,6 +238,7 @@ pub struct Client<T: Runtime> {
rpc: Rpc<T>, rpc: Rpc<T>,
genesis_hash: T::Hash, genesis_hash: T::Hash,
metadata: Metadata, metadata: Metadata,
events_decoder: EventsDecoder<T>,
properties: SystemProperties, properties: SystemProperties,
runtime_version: RuntimeVersion, runtime_version: RuntimeVersion,
_marker: PhantomData<(fn() -> T::Signature, T::Extra)>, _marker: PhantomData<(fn() -> T::Signature, T::Extra)>,
@@ -199,6 +251,7 @@ impl<T: Runtime> Clone for Client<T> {
rpc: self.rpc.clone(), rpc: self.rpc.clone(),
genesis_hash: self.genesis_hash, genesis_hash: self.genesis_hash,
metadata: self.metadata.clone(), metadata: self.metadata.clone(),
events_decoder: self.events_decoder.clone(),
properties: self.properties.clone(), properties: self.properties.clone(),
runtime_version: self.runtime_version.clone(), runtime_version: self.runtime_version.clone(),
_marker: PhantomData, _marker: PhantomData,
@@ -466,12 +519,9 @@ impl<T: Runtime> Client<T> {
Ok(signed) Ok(signed)
} }
/// Returns an events decoder for a call. /// Returns the events decoder.
pub fn events_decoder<C: Call<T>>(&self) -> EventsDecoder<T> { pub fn events_decoder(&self) -> &EventsDecoder<T> {
let metadata = self.metadata().clone(); &self.events_decoder
let mut decoder = EventsDecoder::new(metadata);
C::events_decoder(&mut decoder);
decoder
} }
/// Create and submit an extrinsic and return corresponding Hash if successful /// Create and submit an extrinsic and return corresponding Hash if successful
@@ -486,10 +536,9 @@ impl<T: Runtime> Client<T> {
pub async fn submit_and_watch_extrinsic( pub async fn submit_and_watch_extrinsic(
&self, &self,
extrinsic: UncheckedExtrinsic<T>, extrinsic: UncheckedExtrinsic<T>,
decoder: EventsDecoder<T>,
) -> Result<ExtrinsicSuccess<T>, Error> { ) -> Result<ExtrinsicSuccess<T>, Error> {
self.rpc self.rpc
.submit_and_watch_extrinsic(extrinsic, decoder) .submit_and_watch_extrinsic(extrinsic, &self.events_decoder)
.await .await
} }
@@ -518,8 +567,7 @@ impl<T: Runtime> Client<T> {
Send + Sync, Send + Sync,
{ {
let extrinsic = self.create_signed(call, signer).await?; let extrinsic = self.create_signed(call, signer).await?;
let decoder = self.events_decoder::<C>(); self.submit_and_watch_extrinsic(extrinsic).await
self.submit_and_watch_extrinsic(extrinsic, decoder).await
} }
/// Insert a key into the keystore. /// Insert a key into the keystore.
+3 -3
View File
@@ -423,10 +423,10 @@ impl<T: Runtime> Rpc<T> {
} }
/// Create and submit an extrinsic and return corresponding Event if successful /// Create and submit an extrinsic and return corresponding Event if successful
pub async fn submit_and_watch_extrinsic<E: Encode + 'static>( pub async fn submit_and_watch_extrinsic<'a, E: Encode + 'static>(
&self, &self,
extrinsic: E, extrinsic: E,
decoder: EventsDecoder<T>, decoder: &'a EventsDecoder<T>,
) -> Result<ExtrinsicSuccess<T>, Error> { ) -> Result<ExtrinsicSuccess<T>, Error> {
let ext_hash = T::Hashing::hash_of(&extrinsic); let ext_hash = T::Hashing::hash_of(&extrinsic);
log::info!("Submitting Extrinsic `{:?}`", ext_hash); log::info!("Submitting Extrinsic `{:?}`", ext_hash);
@@ -465,7 +465,7 @@ impl<T: Runtime> Rpc<T> {
ext_hash, 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); sub.filter_extrinsic(block_hash, ext_index);
let mut events = vec![]; let mut events = vec![];
while let Some(event) = sub.next().await { while let Some(event) = sub.next().await {
+133 -5
View File
@@ -147,13 +147,30 @@ use crate::{
balances::{ balances::{
AccountData, AccountData,
Balances, 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. /// Runtime trait.
@@ -162,6 +179,9 @@ pub trait Runtime: System + Sized + Send + Sync + 'static {
type Signature: Verify + Encode + Send + Sync + 'static; type Signature: Verify + Encode + Send + Sync + 'static;
/// Transaction extras. /// Transaction extras.
type Extra: SignedExtra<Self> + Send + Sync + 'static; type Extra: SignedExtra<Self> + Send + Sync + 'static;
/// Register type sizes for this runtime
fn register_type_sizes(event_type_registry: &mut EventTypeRegistry<Self>);
} }
/// Concrete type definitions compatible with those in the default substrate `node_runtime` /// Concrete type definitions compatible with those in the default substrate `node_runtime`
@@ -178,6 +198,15 @@ impl Staking for DefaultNodeRuntime {}
impl Runtime for DefaultNodeRuntime { impl Runtime for DefaultNodeRuntime {
type Signature = MultiSignature; type Signature = MultiSignature;
type Extra = DefaultExtra<Self>; type Extra = DefaultExtra<Self>;
fn register_type_sizes(event_type_registry: &mut EventTypeRegistry<Self>) {
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 { impl System for DefaultNodeRuntime {
@@ -217,6 +246,14 @@ pub struct NodeTemplateRuntime;
impl Runtime for NodeTemplateRuntime { impl Runtime for NodeTemplateRuntime {
type Signature = MultiSignature; type Signature = MultiSignature;
type Extra = DefaultExtra<Self>; type Extra = DefaultExtra<Self>;
fn register_type_sizes(event_type_registry: &mut EventTypeRegistry<Self>) {
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 { impl System for NodeTemplateRuntime {
@@ -253,6 +290,14 @@ pub struct ContractsTemplateRuntime;
impl Runtime for ContractsTemplateRuntime { impl Runtime for ContractsTemplateRuntime {
type Signature = <NodeTemplateRuntime as Runtime>::Signature; type Signature = <NodeTemplateRuntime as Runtime>::Signature;
type Extra = DefaultExtra<Self>; type Extra = DefaultExtra<Self>;
fn register_type_sizes(event_type_registry: &mut EventTypeRegistry<Self>) {
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 { impl System for ContractsTemplateRuntime {
@@ -287,6 +332,14 @@ pub struct KusamaRuntime;
impl Runtime for KusamaRuntime { impl Runtime for KusamaRuntime {
type Signature = MultiSignature; type Signature = MultiSignature;
type Extra = DefaultExtra<Self>; type Extra = DefaultExtra<Self>;
fn register_type_sizes(event_type_registry: &mut EventTypeRegistry<Self>) {
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 { impl System for KusamaRuntime {
@@ -311,3 +364,78 @@ impl Staking for KusamaRuntime {}
impl Balances for KusamaRuntime { impl Balances for KusamaRuntime {
type Balance = u128; 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<T: Runtime>(
event_type_registry: &mut EventTypeRegistry<T>,
) {
// primitives
event_type_registry.register_type_size::<bool>("bool");
event_type_registry.register_type_size::<u8>("u8");
event_type_registry.register_type_size::<u32>("u32");
event_type_registry.register_type_size::<u64>("u64");
event_type_registry.register_type_size::<u128>("u128");
event_type_registry.register_type_size::<()>("PhantomData");
// frame_support types
event_type_registry
.register_type_size::<frame_support::dispatch::DispatchInfo>("DispatchInfo");
event_type_registry
.register_type_size::<frame_support::dispatch::DispatchResult>("DispatchResult");
event_type_registry
.register_type_size::<frame_support::dispatch::DispatchError>("DispatchError");
event_type_registry
.register_type_size::<frame_support::traits::BalanceStatus>("Status");
// aliases etc.
event_type_registry.register_type_size::<u32>("ReferendumIndex");
event_type_registry.register_type_size::<[u8; 16]>("Kind");
event_type_registry.register_type_size::<u32>("AccountIndex");
event_type_registry.register_type_size::<u32>("PropIndex");
event_type_registry.register_type_size::<u32>("ProposalIndex");
event_type_registry.register_type_size::<u32>("AuthorityIndex");
event_type_registry.register_type_size::<u32>("MemberCount");
event_type_registry.register_type_size::<u8>("VoteThreshold");
event_type_registry
.register_type_size::<(T::BlockNumber, u32)>("TaskAddress<BlockNumber>");
event_type_registry.register_type_size::<AuthorityId>("AuthorityId");
event_type_registry.register_type_size::<AuthorityWeight>("AuthorityWeight");
event_type_registry
.register_type_size::<Vec<(AuthorityId, AuthorityWeight)>>("AuthorityList");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_register_default_runtime_type_sizes() {
EventTypeRegistry::<DefaultNodeRuntime>::new();
}
#[test]
fn can_register_node_template_runtime_type_sizes() {
EventTypeRegistry::<NodeTemplateRuntime>::new();
}
#[test]
fn can_register_contracts_template_runtime_type_sizes() {
EventTypeRegistry::<ContractsTemplateRuntime>::new();
}
#[test]
fn can_register_kusama_runtime_type_sizes() {
EventTypeRegistry::<KusamaRuntime>::new();
}
}
+4 -4
View File
@@ -34,9 +34,9 @@ use crate::{
/// Event subscription simplifies filtering a storage change set stream for /// Event subscription simplifies filtering a storage change set stream for
/// events of interest. /// events of interest.
pub struct EventSubscription<T: Runtime> { pub struct EventSubscription<'a, T: Runtime> {
subscription: Subscription<StorageChangeSet<T::Hash>>, subscription: Subscription<StorageChangeSet<T::Hash>>,
decoder: EventsDecoder<T>, decoder: &'a EventsDecoder<T>,
block: Option<T::Hash>, block: Option<T::Hash>,
extrinsic: Option<usize>, extrinsic: Option<usize>,
event: Option<(&'static str, &'static str)>, event: Option<(&'static str, &'static str)>,
@@ -44,11 +44,11 @@ pub struct EventSubscription<T: Runtime> {
finished: bool, finished: bool,
} }
impl<T: Runtime> EventSubscription<T> { impl<'a, T: Runtime> EventSubscription<'a, T> {
/// Creates a new event subscription. /// Creates a new event subscription.
pub fn new( pub fn new(
subscription: Subscription<StorageChangeSet<T::Hash>>, subscription: Subscription<StorageChangeSet<T::Hash>>,
decoder: EventsDecoder<T>, decoder: &'a EventsDecoder<T>,
) -> Self { ) -> Self {
Self { Self {
subscription, subscription,